Skip to content

Commit e67de7e

Browse files
authored
fix: prune missing task sessions (#6)
* Defer goal continuation while tasks are active * Fix task deferral terminal edge cases
1 parent 798c144 commit e67de7e

3 files changed

Lines changed: 60 additions & 4 deletions

File tree

dist/server.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,7 @@ class TaskTracker {
11441144
}
11451145
async refreshLiveChildren(client, parentSessionID) {
11461146
const session = client.session;
1147-
if (!session.children || !session.status)
1147+
if (!session.children)
11481148
return;
11491149
let childIDs;
11501150
try {
@@ -1154,7 +1154,8 @@ class TaskTracker {
11541154
} catch {
11551155
return;
11561156
}
1157-
if (childIDs.length === 0)
1157+
this.markAbsentRunningChildren(parentSessionID, new Set(childIDs));
1158+
if (childIDs.length === 0 || !session.status)
11581159
return;
11591160
let statuses;
11601161
try {
@@ -1240,6 +1241,16 @@ class TaskTracker {
12401241
continue;
12411242
this.snapshotIdleHolds.delete(key);
12421243
this.settledSnapshotIdleTasks.add(key);
1244+
const task = this.tasks.get(hold.taskID);
1245+
if (task?.parentSessionID === hold.parentSessionID && task.state === "running")
1246+
this.tasks.delete(hold.taskID);
1247+
}
1248+
}
1249+
markAbsentRunningChildren(parentSessionID, liveChildIDs) {
1250+
for (const task of this.tasks.values()) {
1251+
if (task.parentSessionID !== parentSessionID || task.state !== "running" || liveChildIDs.has(task.taskID))
1252+
continue;
1253+
this.markSnapshotIdle(parentSessionID, task.taskID);
12431254
}
12441255
}
12451256
snapshotIdleKey(parentSessionID, taskID) {
@@ -1364,6 +1375,8 @@ var server = async ({ client }, options) => {
13641375
await pauseGoalForPlanMode(sessionID);
13651376
return;
13661377
}
1378+
if (busySessions.has(sessionID))
1379+
return;
13671380
if (!fromTaskDeferral && taskDeferredSessions.has(sessionID)) {
13681381
scheduleSettledContinuation(sessionID);
13691382
return;

src/server.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ class TaskTracker {
438438
children?: (input: { path: { id: string } }) => Promise<{ data?: unknown } | unknown[]>
439439
status?: () => Promise<{ data?: unknown } | Record<string, unknown>>
440440
}
441-
if (!session.children || !session.status) return
441+
if (!session.children) return
442442
let childIDs: string[]
443443
try {
444444
const result = await session.children({ path: { id: parentSessionID } })
@@ -447,7 +447,8 @@ class TaskTracker {
447447
} catch {
448448
return
449449
}
450-
if (childIDs.length === 0) return
450+
this.markAbsentRunningChildren(parentSessionID, new Set(childIDs))
451+
if (childIDs.length === 0 || !session.status) return
451452
let statuses: Record<string, unknown>
452453
try {
453454
const result = await session.status()
@@ -540,6 +541,15 @@ class TaskTracker {
540541
if (hold.expiresAt > now) continue
541542
this.snapshotIdleHolds.delete(key)
542543
this.settledSnapshotIdleTasks.add(key)
544+
const task = this.tasks.get(hold.taskID)
545+
if (task?.parentSessionID === hold.parentSessionID && task.state === "running") this.tasks.delete(hold.taskID)
546+
}
547+
}
548+
549+
private markAbsentRunningChildren(parentSessionID: string, liveChildIDs: Set<string>) {
550+
for (const task of this.tasks.values()) {
551+
if (task.parentSessionID !== parentSessionID || task.state !== "running" || liveChildIDs.has(task.taskID)) continue
552+
this.markSnapshotIdle(parentSessionID, task.taskID)
543553
}
544554
}
545555

@@ -662,6 +672,7 @@ const server: Plugin = async ({ client }, options?: Options) => {
662672
if (current.status === "active") await pauseGoalForPlanMode(sessionID)
663673
return
664674
}
675+
if (busySessions.has(sessionID)) return
665676
if (!fromTaskDeferral && taskDeferredSessions.has(sessionID)) {
666677
scheduleSettledContinuation(sessionID)
667678
return

test/server.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,38 @@ test("idle live child bounded retry does not inject while parent session is busy
839839
expect(JSON.stringify(calls[0])).toContain("Continue working toward the active session goal")
840840
})
841841

842+
test("tracked running child absent from live children stops blocking after grace period", async () => {
843+
const calls: unknown[] = []
844+
let children = [{ id: "task_1" }]
845+
const hooks = await plugin.server(
846+
{
847+
client: {
848+
session: {
849+
children: async () => ({ data: children }),
850+
status: async () => ({ data: { task_1: { type: "busy" } } }),
851+
promptAsync: async (input: unknown) => {
852+
calls.push(input)
853+
},
854+
},
855+
},
856+
} as never,
857+
{ auto_continue: true, max_auto_turns: 1, min_continue_interval_seconds: 0 },
858+
)
859+
const tools = hooks.tool
860+
if (!tools) throw new Error("expected goal tools to be registered")
861+
862+
await requireTool(tools.create_goal, "create_goal").execute({ objective: "keep going" }, { sessionID: "ses_1" } as never)
863+
await hooks.event!({ event: { type: "session.idle", properties: { sessionID: "ses_1" } } as never })
864+
expect(calls).toHaveLength(0)
865+
866+
children = []
867+
await hooks.event!({ event: { type: "session.idle", properties: { sessionID: "ses_1" } } as never })
868+
869+
expect(calls).toHaveLength(0)
870+
await waitForContinuation(calls)
871+
expect(JSON.stringify(calls[0])).toContain("Continue working toward the active session goal")
872+
})
873+
842874
test("task deferral can be disabled with config", async () => {
843875
const calls: unknown[] = []
844876
const hooks = await plugin.server(

0 commit comments

Comments
 (0)