@ferris1225/pi-subagents 4.3.0 → 4.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +91 -131
- package/agents/artisan.md +11 -39
- package/agents/scout.md +11 -28
- package/agents/steward.md +11 -43
- package/package.json +1 -1
- package/src/agents.ts +31 -18
- package/src/announcements.ts +9 -23
- package/src/background.ts +56 -9
- package/src/completion.ts +0 -6
- package/src/config.ts +11 -180
- package/src/dispatch.ts +48 -52
- package/src/durable.ts +6 -53
- package/src/index.ts +6 -9
- package/src/prompt.ts +101 -46
- package/src/recovery.ts +35 -10
- package/src/rpc-run.ts +59 -1
- package/src/runtime.ts +74 -17
- package/src/setup.ts +5 -19
- package/src/spawn.ts +6 -4
- package/src/thread-lifecycle.ts +119 -75
- package/src/tools.ts +4 -20
package/src/thread-lifecycle.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
12
|
import { rm } from "node:fs/promises";
|
|
13
13
|
import { realpath } from "node:fs/promises";
|
|
14
|
-
import { join, resolve
|
|
14
|
+
import { join, resolve } from "node:path";
|
|
15
15
|
import {
|
|
16
16
|
discoverAgents,
|
|
17
17
|
isWriteCapableAgent,
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
} from "./config.ts";
|
|
28
28
|
import {
|
|
29
29
|
isCurrentBoot,
|
|
30
|
-
migrateLegacyThreadsManifest,
|
|
31
30
|
readThreadRecords,
|
|
32
31
|
referencedDurablePaths,
|
|
33
32
|
removeThreadRecord,
|
|
@@ -35,7 +34,7 @@ import {
|
|
|
35
34
|
pruneThreadRecords,
|
|
36
35
|
restoredResultFromSummary,
|
|
37
36
|
threadRecordFromThread,
|
|
38
|
-
ThreadRecord,
|
|
37
|
+
type ThreadRecord,
|
|
39
38
|
upsertThreadRecord,
|
|
40
39
|
} from "./durable.ts";
|
|
41
40
|
import {
|
|
@@ -54,13 +53,15 @@ import {
|
|
|
54
53
|
resolveThinkingLevel,
|
|
55
54
|
} from "./models.ts";
|
|
56
55
|
import { monitor } from "./monitor.ts";
|
|
56
|
+
import { findDuplicateActiveDispatch } from "./prompt.ts";
|
|
57
57
|
import { persistRecoveryRecords, recoveryRecordFromFinalization } from "./recovery.ts";
|
|
58
|
+
import { emptyUsage } from "./rpc-run.ts";
|
|
58
59
|
import type { SubagentRuntime, SubagentThread, ThreadState } from "./runtime.ts";
|
|
59
60
|
import { forkRetainedSession } from "./session-fork.ts";
|
|
60
61
|
import {
|
|
61
62
|
buildResumePrompt,
|
|
62
63
|
getProjectRoot,
|
|
63
|
-
|
|
64
|
+
getSubagentsRoot,
|
|
64
65
|
RpcRunControl,
|
|
65
66
|
isFailedResult,
|
|
66
67
|
isModelLevelFailure,
|
|
@@ -273,6 +274,8 @@ export interface StartBackgroundOptions {
|
|
|
273
274
|
environment?: DispatchEnvironment;
|
|
274
275
|
seed?: SessionSeed;
|
|
275
276
|
resumeReservation?: ResumeReservation;
|
|
277
|
+
/** Chosen by the tool call before the queue can start a fast child. */
|
|
278
|
+
deliveryRoute?: "background" | "await";
|
|
276
279
|
}
|
|
277
280
|
|
|
278
281
|
export type StartBackgroundInternal = (
|
|
@@ -372,6 +375,7 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
372
375
|
environment,
|
|
373
376
|
seed,
|
|
374
377
|
resumeReservation,
|
|
378
|
+
deliveryRoute = "background",
|
|
375
379
|
} = startOptions;
|
|
376
380
|
if (!runtime.sessionActive) {
|
|
377
381
|
return failedStartResult(agentName, task, "Parent session shut down before this subagent generation could start.");
|
|
@@ -396,33 +400,31 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
396
400
|
}
|
|
397
401
|
|
|
398
402
|
const originalCwd = resolve(cwd ?? runCtx.cwd);
|
|
403
|
+
if (!existingThread) {
|
|
404
|
+
const duplicate = findDuplicateActiveDispatch(runtime.threads.values(), task, originalCwd);
|
|
405
|
+
if (duplicate) {
|
|
406
|
+
return failedStartResult(
|
|
407
|
+
agentName,
|
|
408
|
+
task,
|
|
409
|
+
`Duplicate active dispatch matches run #${duplicate.id} (${duplicate.agentName}). Use that logical thread instead; resume #${duplicate.id} when it is eligible.`,
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
399
413
|
const projectRoot = getProjectRoot(runtime.configPath, originalCwd);
|
|
400
414
|
const sessionsRoot = join(projectRoot, "sessions");
|
|
401
415
|
const worktreesRoot = join(projectRoot, "worktrees");
|
|
402
416
|
const scratchRoot = join(projectRoot, "tmp");
|
|
403
417
|
const previousWorktree = existingThread?.worktree;
|
|
404
418
|
let worktree = seed?.worktree ?? previousWorktree;
|
|
405
|
-
if (isolation === "worktree") {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
if (!worktree) {
|
|
414
|
-
try {
|
|
415
|
-
worktree = await createWorktreeIsolation(originalCwd, { tempBaseDir: worktreesRoot });
|
|
416
|
-
} catch (error) {
|
|
417
|
-
return {
|
|
418
|
-
...failedStartResult(agentName, task, error instanceof Error ? error.message : String(error)),
|
|
419
|
-
isolation,
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
}
|
|
419
|
+
if (isolation === "worktree" && worktree && worktree.state !== "active") {
|
|
420
|
+
return {
|
|
421
|
+
...failedStartResult(agentName, task, `Run #${existingThread?.id ?? "?"} has no active continuation worktree.`),
|
|
422
|
+
isolation,
|
|
423
|
+
integrationStatus: worktree.state === "finalizing" ? "pending" : worktree.state,
|
|
424
|
+
};
|
|
423
425
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
+
let executionCwd = worktree?.cwd ?? originalCwd;
|
|
427
|
+
let worktreeGroup = worktree ? worktreeGroupId(worktree) : undefined;
|
|
426
428
|
// A resume re-runs at the strength its dispatch asked for, so the retained
|
|
427
429
|
// request survives generations (and, via the durable record, restarts).
|
|
428
430
|
const resolvedRoute = resolveDispatchModelRoute(agent, runConfig, runCtx);
|
|
@@ -440,6 +442,7 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
440
442
|
isolation,
|
|
441
443
|
...(worktreeGroup ? { worktreeId: worktreeGroup } : {}),
|
|
442
444
|
});
|
|
445
|
+
runtime.claimRunDelivery(runId, deliveryRoute);
|
|
443
446
|
const generation = (existingThread?.generation ?? 0) + 1;
|
|
444
447
|
const pending: SingleResult = {
|
|
445
448
|
...queuedResult(route.agent, task, thinkingLevel),
|
|
@@ -523,11 +526,12 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
523
526
|
};
|
|
524
527
|
runtime.threads.set(runId, thread);
|
|
525
528
|
}
|
|
526
|
-
installThreadLifecycle(thread, {
|
|
529
|
+
const installCurrentLifecycle = (): void => installThreadLifecycle(thread, {
|
|
527
530
|
runtime,
|
|
528
531
|
runCtx,
|
|
529
532
|
startBackground: (...args) => startBackground(...args),
|
|
530
533
|
});
|
|
534
|
+
if (isolation === "shared" || worktree) installCurrentLifecycle();
|
|
531
535
|
|
|
532
536
|
const onLive = makeLiveHandler(runId, generation);
|
|
533
537
|
// Shared write-capable runs serialize on the repository lane so their
|
|
@@ -536,6 +540,20 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
536
540
|
const reserveManagedLane = isolation === "shared" && isWriteCapableAgent(agent);
|
|
537
541
|
const runGeneration = async (backgroundSignal: AbortSignal, controller: AbortController): Promise<void> => {
|
|
538
542
|
if (runtime.threads.get(runId)?.generation !== generation) return;
|
|
543
|
+
if (isolation === "worktree" && !worktree) {
|
|
544
|
+
const prepared = await createWorktreeIsolation(originalCwd, { tempBaseDir: worktreesRoot });
|
|
545
|
+
if (runtime.threads.get(runId)?.generation !== generation || backgroundSignal.aborted) {
|
|
546
|
+
await prepared.discard().catch(() => undefined);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
worktree = prepared;
|
|
550
|
+
executionCwd = prepared.cwd;
|
|
551
|
+
worktreeGroup = worktreeGroupId(prepared);
|
|
552
|
+
thread.worktree = prepared;
|
|
553
|
+
thread.executionCwd = executionCwd;
|
|
554
|
+
monitor.setIsolation(runId, "worktree", "pending", worktreeGroup);
|
|
555
|
+
installCurrentLifecycle();
|
|
556
|
+
}
|
|
539
557
|
// The generation body owns a process slot from here (a lane wait, if
|
|
540
558
|
// any, was granted above). Recording the transition synchronously keeps
|
|
541
559
|
// every "queued" surface truthful: only runs still pending in the pool
|
|
@@ -649,10 +667,10 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
649
667
|
thread.lifecycleOperation === "settle" &&
|
|
650
668
|
!thread.retired;
|
|
651
669
|
try {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
670
|
+
// The child process has exited. Isolated Git finalization remains
|
|
671
|
+
// lifecycle-owned and awaited, but no longer consumes a process slot.
|
|
672
|
+
if (isolation === "worktree") runtime.backgroundQueue.suspend(controller);
|
|
673
|
+
await thread.finalizeIsolation(generation, result);
|
|
656
674
|
if (!ownsSettlement()) return;
|
|
657
675
|
|
|
658
676
|
const failed = isFailedResult(result);
|
|
@@ -683,41 +701,33 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
683
701
|
block: modelLevel
|
|
684
702
|
? `${formatCompletionBlock(result, runConfig.maxResultLines, { resultRoot: projectResultsRoot(runtime.configPath, result.projectCwd ?? originalCwd) })}\n\n${modelLevelTakeoverNote(result, { runId })}`
|
|
685
703
|
: formatCompletionBlock(result, runConfig.maxResultLines, { resultRoot: projectResultsRoot(runtime.configPath, result.projectCwd ?? originalCwd) }),
|
|
686
|
-
triggerTurn: true,
|
|
687
704
|
usage: result.usage,
|
|
688
705
|
};
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
runtime.sendCompletionGroup([completion]);
|
|
697
|
-
runtime.completionBatcher.flush();
|
|
698
|
-
} else {
|
|
699
|
-
runtime.completionBatcher.push(completion);
|
|
700
|
-
}
|
|
706
|
+
if (modelLevel) {
|
|
707
|
+
const detail = result.errorMessage?.trim() || "model unavailable or broken";
|
|
708
|
+
runCtx.ui.notify(`✗ ${result.agent} dispatch failed: ${detail} — task handed to the main window`, "error");
|
|
709
|
+
} else if (dispatchFailed) {
|
|
710
|
+
runCtx.ui.notify(`✗ ${result.agent} dispatch failed: ${result.errorMessage ?? "dispatch crashed"}`, "error");
|
|
711
|
+
}
|
|
712
|
+
runtime.publishRunCompletion(runId, completion, failed);
|
|
701
713
|
} finally {
|
|
702
714
|
if (ownsSettlement()) thread.lifecycleOperation = undefined;
|
|
703
715
|
}
|
|
704
716
|
};
|
|
705
717
|
const queuedGeneration = reserveManagedLane
|
|
706
718
|
? async (backgroundSignal: AbortSignal, controller: AbortController): Promise<void> => {
|
|
707
|
-
//
|
|
708
|
-
// lane
|
|
709
|
-
//
|
|
710
|
-
// writers park the entire pool and starve independent read-only
|
|
711
|
-
// dispatches that could have started immediately, so the slot is
|
|
712
|
-
// released up front; the lane itself still serializes same-repo
|
|
713
|
-
// writers, and abort/quiesce guarantees are unchanged.
|
|
719
|
+
// Waiting for repository serialization must not consume a process
|
|
720
|
+
// slot. Once the lane is granted, reacquire through the same FIFO
|
|
721
|
+
// scheduler before any child process can spawn.
|
|
714
722
|
runtime.backgroundQueue.suspend(controller);
|
|
715
|
-
// A lane wait is write serialization, not slot pacing: the model
|
|
716
|
-
// must never read it as an exhausted pool.
|
|
717
723
|
monitor.setWaitReason(runId, "repository-lane");
|
|
718
724
|
await runInManagedRepositoryLane(
|
|
719
725
|
originalCwd,
|
|
720
|
-
() =>
|
|
726
|
+
async () => {
|
|
727
|
+
monitor.setWaitReason(runId, "process-slot");
|
|
728
|
+
if (!(await runtime.backgroundQueue.acquire(controller))) return;
|
|
729
|
+
await runGeneration(backgroundSignal, controller);
|
|
730
|
+
},
|
|
721
731
|
backgroundSignal,
|
|
722
732
|
);
|
|
723
733
|
}
|
|
@@ -776,6 +786,7 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
776
786
|
};
|
|
777
787
|
thread.lastResult = crashed;
|
|
778
788
|
runtime.retainSession(crashed);
|
|
789
|
+
if (isolation === "worktree" && thread.worktree) runtime.backgroundQueue.suspend(thread.queueController);
|
|
779
790
|
await thread.finalizeIsolation(generation, crashed);
|
|
780
791
|
if (!ownsSettlement()) return;
|
|
781
792
|
thread.state = "failed";
|
|
@@ -789,15 +800,11 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
789
800
|
if (!runtime.sessionActive || !ownsSettlement()) return;
|
|
790
801
|
try {
|
|
791
802
|
runCtx.ui.notify(`✗ ${crashed.agent} dispatch failed: ${crashed.errorMessage}`, "error");
|
|
792
|
-
runtime.
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
usage: crashed.usage,
|
|
798
|
-
},
|
|
799
|
-
]);
|
|
800
|
-
runtime.completionBatcher.flush();
|
|
803
|
+
runtime.publishRunCompletion(runId, {
|
|
804
|
+
agent: crashed.agent,
|
|
805
|
+
block: formatCompletionBlock(crashed, runConfig.maxResultLines, { resultRoot: projectResultsRoot(runtime.configPath, crashed.projectCwd ?? originalCwd) }),
|
|
806
|
+
usage: crashed.usage,
|
|
807
|
+
}, true);
|
|
801
808
|
} catch {
|
|
802
809
|
/* a second delivery failure must not throw through the queue */
|
|
803
810
|
}
|
|
@@ -977,6 +984,9 @@ export function installThreadLifecycle(thread: SubagentThread, deps: ThreadLifec
|
|
|
977
984
|
return failedStartResult(thread.agentName, thread.task, "resume objective must be non-blank when provided.");
|
|
978
985
|
}
|
|
979
986
|
if (thread.retired) return failedStartResult(thread.agentName, thread.task, `Run #${runId} was retired by subagent_stop.`);
|
|
987
|
+
if (thread.resumeUnavailableReason) {
|
|
988
|
+
return failedStartResult(thread.agentName, thread.task, thread.resumeUnavailableReason);
|
|
989
|
+
}
|
|
980
990
|
if (thread.lifecycleOperation) {
|
|
981
991
|
return failedStartResult(thread.agentName, thread.task, `Run #${runId} is already resuming.`);
|
|
982
992
|
}
|
|
@@ -1237,16 +1247,53 @@ export async function restoreDurableThreads(runtime: SubagentRuntime): Promise<n
|
|
|
1237
1247
|
const worktree = record.worktree
|
|
1238
1248
|
? await restoreWorktreeIsolation(record.worktree).catch(() => undefined)
|
|
1239
1249
|
: undefined;
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
const state: ThreadState = worktree
|
|
1243
|
-
? "parked"
|
|
1244
|
-
: record.isolation === "worktree" && record.worktree
|
|
1245
|
-
? "failed"
|
|
1246
|
-
: "parked";
|
|
1247
|
-
const thread = createRestoredThread(runtime, record, worktree, state);
|
|
1250
|
+
const restorationFailed = record.isolation === "worktree" && record.worktree !== undefined && !worktree;
|
|
1251
|
+
const thread = createRestoredThread(runtime, record, worktree, restorationFailed ? "failed" : "parked");
|
|
1248
1252
|
runtime.threads.set(record.runId, thread);
|
|
1249
1253
|
runtime.sessionDirs.add(record.sessionDir!);
|
|
1254
|
+
if (restorationFailed) {
|
|
1255
|
+
const reason = `Run #${record.runId}'s recorded worktree could not be restored; isolated edits may be unavailable. The retained session and durable record were kept, but this thread cannot be resumed.`;
|
|
1256
|
+
thread.resumeUnavailableReason = reason;
|
|
1257
|
+
thread.restorationRecord = record;
|
|
1258
|
+
const previous = restoredResultFromSummary(record);
|
|
1259
|
+
const failed: SingleResult = {
|
|
1260
|
+
agent: record.agentName,
|
|
1261
|
+
task: record.task,
|
|
1262
|
+
exitCode: 1,
|
|
1263
|
+
messages: previous?.messages ?? [],
|
|
1264
|
+
stderr: reason,
|
|
1265
|
+
usage: previous?.usage ?? emptyUsage(),
|
|
1266
|
+
model: previous?.model,
|
|
1267
|
+
thinking: previous?.thinking,
|
|
1268
|
+
stopReason: "error",
|
|
1269
|
+
errorMessage: reason,
|
|
1270
|
+
dispatchFailed: true,
|
|
1271
|
+
sessionId: record.sessionId,
|
|
1272
|
+
sessionDir: record.sessionDir,
|
|
1273
|
+
projectCwd: record.cwd,
|
|
1274
|
+
runId: record.runId,
|
|
1275
|
+
isolation: "worktree",
|
|
1276
|
+
integrationStatus: "retained",
|
|
1277
|
+
};
|
|
1278
|
+
thread.lastResult = failed;
|
|
1279
|
+
runtime.registerRunResult(record.runId, failed);
|
|
1280
|
+
monitor.restoreRun({
|
|
1281
|
+
id: record.runId,
|
|
1282
|
+
agent: record.agentName,
|
|
1283
|
+
task: record.task,
|
|
1284
|
+
status: "failed",
|
|
1285
|
+
elapsedMs: record.elapsedMs,
|
|
1286
|
+
isolation: "worktree",
|
|
1287
|
+
integrationStatus: "retained",
|
|
1288
|
+
});
|
|
1289
|
+
runtime.claimRunDelivery(record.runId, "background");
|
|
1290
|
+
runtime.publishRunCompletion(record.runId, {
|
|
1291
|
+
agent: record.agentName,
|
|
1292
|
+
block: `### Subagent restoration failed: #${record.runId} ${record.agentName}\n\n${reason}`,
|
|
1293
|
+
usage: failed.usage,
|
|
1294
|
+
}, true);
|
|
1295
|
+
continue;
|
|
1296
|
+
}
|
|
1250
1297
|
monitor.restoreRun({
|
|
1251
1298
|
id: record.runId,
|
|
1252
1299
|
agent: record.agentName,
|
|
@@ -1268,9 +1315,9 @@ export async function restoreDurableThreads(runtime: SubagentRuntime): Promise<n
|
|
|
1268
1315
|
return restoredIds;
|
|
1269
1316
|
}
|
|
1270
1317
|
|
|
1271
|
-
/**
|
|
1318
|
+
/** Session-start durable bootstrap: restore threads, age out expired records, and
|
|
1272
1319
|
* sweep leaked temp/state directories. Every stage is best-effort so a broken
|
|
1273
|
-
* manifest never blocks
|
|
1320
|
+
* manifest never blocks the session.
|
|
1274
1321
|
*
|
|
1275
1322
|
* Restore is published on the runtime as `durableRestore` before this returns,
|
|
1276
1323
|
* so callers that must see restored threads await that pass alone and never the
|
|
@@ -1279,9 +1326,6 @@ export async function restoreDurableThreads(runtime: SubagentRuntime): Promise<n
|
|
|
1279
1326
|
export function bootstrapDurableState(runtime: SubagentRuntime): Promise<void> {
|
|
1280
1327
|
const restore = (async () => {
|
|
1281
1328
|
try {
|
|
1282
|
-
// Records from a pre-per-project manifest must land in their project
|
|
1283
|
-
// roots before restore reads anything.
|
|
1284
|
-
await migrateLegacyThreadsManifest(runtime.configPath);
|
|
1285
1329
|
runtime.restoredRunIds = await restoreDurableThreads(runtime);
|
|
1286
1330
|
} catch {
|
|
1287
1331
|
/* restore is best-effort */
|
|
@@ -1295,7 +1339,7 @@ export function bootstrapDurableState(runtime: SubagentRuntime): Promise<void> {
|
|
|
1295
1339
|
} catch {
|
|
1296
1340
|
/* retention is best-effort */
|
|
1297
1341
|
}
|
|
1298
|
-
const projectRoots =
|
|
1342
|
+
const projectRoots = getSubagentsRoot(runtime.configPath);
|
|
1299
1343
|
try {
|
|
1300
1344
|
sweepProjectTempDirs(projectRoots);
|
|
1301
1345
|
} catch {
|
package/src/tools.ts
CHANGED
|
@@ -46,14 +46,7 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
46
46
|
pi.registerTool({
|
|
47
47
|
name: "subagent_control",
|
|
48
48
|
label: "Subagent Control",
|
|
49
|
-
description:
|
|
50
|
-
"Resume an existing sub-agent thread by stable run id: a parked, completed, or failed retained thread restarts with the same run id and cumulative active time.",
|
|
51
|
-
"Omit objective to continue the current goal, or provide one to append it to retained context and make it the displayed goal. Threads parked or interrupted by a shutdown/reload are restorable; use subagent_stop for destructive cancellation.",
|
|
52
|
-
].join(" "),
|
|
53
|
-
promptSnippet: "Resume a parked or settled subagent thread with its retained context.",
|
|
54
|
-
promptGuidelines: [
|
|
55
|
-
"Resume keeps the run id and retained context; use subagent_stop only for destructive cancellation, which retires that thread's session.",
|
|
56
|
-
],
|
|
49
|
+
description: "Resume a parked or settled child thread by run id, reusing its retained session when available. An optional objective is appended to its current goal.",
|
|
57
50
|
parameters: SubagentControlParams,
|
|
58
51
|
|
|
59
52
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -91,10 +84,8 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
91
84
|
const mode = objective
|
|
92
85
|
? `appended objective: ${currentObjective}`
|
|
93
86
|
: `continuing current objective: ${currentObjective}`;
|
|
94
|
-
const context = hadRetainedSession
|
|
95
|
-
|
|
96
|
-
: "no prior child session existed, so only the logical run and objective are continued";
|
|
97
|
-
return { content: [{ type: "text", text: `Resumed run #${thread.id}, ${mode}; ${context}, and cumulative active time is preserved. It runs in the background — keep working; the result resumes you automatically.` }], details: {} };
|
|
87
|
+
const context = hadRetainedSession ? "retained context reused" : "no prior child context";
|
|
88
|
+
return { content: [{ type: "text", text: `Resumed run #${thread.id}: ${mode}; ${context}.` }], details: {} };
|
|
98
89
|
}
|
|
99
90
|
}
|
|
100
91
|
} catch (error) {
|
|
@@ -125,14 +116,7 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
125
116
|
pi.registerTool({
|
|
126
117
|
name: "subagent_stop",
|
|
127
118
|
label: "Subagent Stop",
|
|
128
|
-
description:
|
|
129
|
-
"Destructively stop a sub-agent thread: terminate active work, deliver its aborted partial result, and retire any retained session so it cannot be resumed.",
|
|
130
|
-
"Pass id (run id or prefix) to stop one active, parked, or completed thread; all: true stops every active run.",
|
|
131
|
-
].join(" "),
|
|
132
|
-
promptSnippet: "Stop a running background subagent (id from dispatch output; or all: true).",
|
|
133
|
-
promptGuidelines: [
|
|
134
|
-
"Stop a run when its task is obsolete, stuck, or superseded — do not leave it burning tokens. It then reports as failed with 'aborted' plus its partial output.",
|
|
135
|
-
],
|
|
119
|
+
description: "Stop and retire one child thread by run id/prefix, or every active thread with all: true.",
|
|
136
120
|
parameters: SubagentStopParams,
|
|
137
121
|
|
|
138
122
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|