feat(run): integrate session origins and agent detection into continuation state
- Update continuation-state to use session_origins from boulder state
- Add isTrackedDescendantSession helper for lineage detection
- Integrate getLastAgentFromSession for reliable agent detection
- Update completion tests for new lineage-aware continuation logic
- Add JSON backend tests for continuation state
🤖 Generated with assistance of OhMyOpenCode
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
declare const require: (name: string) => any
|
||||
const { afterEach, describe, expect, mock, test, afterAll } = require("bun:test")
|
||||
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
|
||||
const testDirs: string[] = []
|
||||
|
||||
const TEST_STORAGE_ROOT = join(tmpdir(), `omo-run-json-storage-${Date.now()}`)
|
||||
const TEST_MESSAGE_STORAGE = join(TEST_STORAGE_ROOT, "message")
|
||||
|
||||
mock.module("../../shared/opencode-storage-detection", () => ({
|
||||
isSqliteBackend: () => false,
|
||||
}))
|
||||
|
||||
mock.module("../../shared/opencode-message-dir", () => ({
|
||||
getMessageDir: (sessionID: string) => {
|
||||
const directPath = join(TEST_MESSAGE_STORAGE, sessionID)
|
||||
return require("node:fs").existsSync(directPath) ? directPath : null
|
||||
},
|
||||
}))
|
||||
|
||||
afterAll(() => { mock.restore() })
|
||||
|
||||
afterEach(() => {
|
||||
while (testDirs.length > 0) {
|
||||
const dir = testDirs.pop()
|
||||
if (dir) {
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function createTempDir(): string {
|
||||
const directory = mkdtempSync(join(tmpdir(), "omo-run-json-backend-"))
|
||||
testDirs.push(directory)
|
||||
return directory
|
||||
}
|
||||
|
||||
function writeJsonMessage(sessionID: string, fileName: string, agent: string): void {
|
||||
const messageDir = join(TEST_MESSAGE_STORAGE, sessionID)
|
||||
mkdirSync(messageDir, { recursive: true })
|
||||
writeFileSync(
|
||||
join(messageDir, fileName),
|
||||
JSON.stringify({
|
||||
agent,
|
||||
model: { providerID: "openai", modelID: "gpt-5.4" },
|
||||
time: { created: fileName.includes("002") ? 200 : 100 },
|
||||
}),
|
||||
"utf-8",
|
||||
)
|
||||
}
|
||||
|
||||
describe("getContinuationState JSON backend descendant coverage", () => {
|
||||
test("returns active boulder for explicitly tracked appended descendant on JSON message storage backend", async () => {
|
||||
// given
|
||||
const directory = createTempDir()
|
||||
const plansDir = join(directory, ".sisyphus", "plans")
|
||||
mkdirSync(plansDir, { recursive: true })
|
||||
const planPath = join(plansDir, "json-descendant-plan.md")
|
||||
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
|
||||
mkdirSync(join(directory, ".sisyphus"), { recursive: true })
|
||||
writeFileSync(join(directory, ".sisyphus", "boulder.json"), JSON.stringify({
|
||||
active_plan: planPath,
|
||||
started_at: new Date().toISOString(),
|
||||
session_ids: ["ses_root_session", "ses_child_session"],
|
||||
session_origins: {
|
||||
"ses_root_session": "direct",
|
||||
"ses_child_session": "appended",
|
||||
},
|
||||
plan_name: "json-descendant-plan",
|
||||
agent: "atlas",
|
||||
}), "utf-8")
|
||||
writeJsonMessage("ses_child_session", "msg_001.json", "atlas")
|
||||
writeJsonMessage("ses_child_session", "msg_002.json", "compaction")
|
||||
|
||||
const { getContinuationState } = await import("./continuation-state")
|
||||
|
||||
// when
|
||||
const state = await getContinuationState(directory, "ses_child_session", {
|
||||
session: {
|
||||
get: async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === "ses_child_session" ? "ses_root_session" : undefined,
|
||||
},
|
||||
}),
|
||||
},
|
||||
} as never)
|
||||
|
||||
// then
|
||||
expect(state.hasActiveBoulder).toBe(true)
|
||||
})
|
||||
|
||||
test("prefers earliest JSON agent by time.created instead of filename order for first-message fallback helpers", async () => {
|
||||
// given
|
||||
const directory = createTempDir()
|
||||
const sessionID = "ses_json_first_agent"
|
||||
writeJsonMessage(sessionID, "msg_ffff0000_000001.json", "later-agent")
|
||||
writeFileSync(
|
||||
join(TEST_MESSAGE_STORAGE, sessionID, "msg_00000000_000999.json"),
|
||||
JSON.stringify({
|
||||
agent: "earliest-agent",
|
||||
model: { providerID: "openai", modelID: "gpt-5.4" },
|
||||
time: { created: 10 },
|
||||
}),
|
||||
"utf-8",
|
||||
)
|
||||
|
||||
const { findFirstMessageWithAgent } = await import("../../features/hook-message-injector")
|
||||
|
||||
// when
|
||||
const result = findFirstMessageWithAgent(join(TEST_MESSAGE_STORAGE, sessionID))
|
||||
|
||||
// then
|
||||
expect(result).toBe("earliest-agent")
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test("prefers newest JSON agent by time.created even when filenames look reversed and timestamps tie-break by filename only", async () => {
|
||||
// given
|
||||
const directory = createTempDir()
|
||||
const plansDir = join(directory, ".sisyphus", "plans")
|
||||
mkdirSync(plansDir, { recursive: true })
|
||||
const planPath = join(plansDir, "json-random-id-plan.md")
|
||||
writeFileSync(planPath, "- [ ] unfinished task\n", "utf-8")
|
||||
mkdirSync(join(directory, ".sisyphus"), { recursive: true })
|
||||
writeFileSync(join(directory, ".sisyphus", "boulder.json"), JSON.stringify({
|
||||
active_plan: planPath,
|
||||
started_at: new Date().toISOString(),
|
||||
session_ids: ["ses_root_random"],
|
||||
plan_name: "json-random-id-plan",
|
||||
agent: "atlas",
|
||||
}), "utf-8")
|
||||
const sessionID = "ses_child_random"
|
||||
const messageDir = join(TEST_MESSAGE_STORAGE, sessionID)
|
||||
mkdirSync(messageDir, { recursive: true })
|
||||
writeFileSync(join(messageDir, "msg_a91f00ab_000001.json"), JSON.stringify({
|
||||
agent: "atlas",
|
||||
model: { providerID: "openai", modelID: "gpt-5.4" },
|
||||
time: { created: 100 },
|
||||
}), "utf-8")
|
||||
writeFileSync(join(messageDir, "msg_f0e1d2c3_000002.json"), JSON.stringify({
|
||||
agent: "compaction",
|
||||
model: { providerID: "openai", modelID: "gpt-5.4" },
|
||||
time: { created: 200 },
|
||||
}), "utf-8")
|
||||
writeFileSync(join(messageDir, "msg_d4c3b2a1_000003.json"), JSON.stringify({
|
||||
agent: "sisyphus-junior",
|
||||
model: { providerID: "openai", modelID: "gpt-5.4" },
|
||||
time: { created: 100 },
|
||||
}), "utf-8")
|
||||
|
||||
const { getContinuationState } = await import("./continuation-state")
|
||||
|
||||
// when
|
||||
const state = await getContinuationState(directory, sessionID, {
|
||||
session: {
|
||||
get: async ({ path }: { path: { id: string } }) => ({
|
||||
data: {
|
||||
id: path.id,
|
||||
parentID: path.id === sessionID ? "ses_root_random" : undefined,
|
||||
},
|
||||
}),
|
||||
},
|
||||
} as never)
|
||||
|
||||
// then
|
||||
expect(state.hasActiveBoulder).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user