@ferris1225/pi-subagents 4.1.12 → 4.1.15
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/README.md +57 -43
- package/agents/cleaner.md +3 -3
- package/agents/documenter.md +3 -3
- package/agents/explorer.md +1 -1
- package/agents/reviewer.md +21 -13
- package/agents/worker.md +2 -2
- package/package.json +55 -55
- package/src/announcements.ts +8 -1
- package/src/config.ts +1 -1
- package/src/dispatch.ts +93 -125
- package/src/durable.ts +13 -12
- package/src/format.ts +8 -0
- package/src/prompt.ts +14 -27
- package/src/runtime.ts +15 -11
- package/src/setup.ts +112 -18
- package/src/thread-lifecycle.ts +45 -68
- package/src/tools.ts +1 -1
- package/src/workflow.ts +96 -144
package/src/setup.ts
CHANGED
|
@@ -2,20 +2,24 @@
|
|
|
2
2
|
* Interactive configuration wizard for /subagents-setup.
|
|
3
3
|
*
|
|
4
4
|
* The wizard stays one level deep and exposes only what most users touch:
|
|
5
|
-
* which agents run, the model each runs on, and the
|
|
6
|
-
* toggle. Everything else (
|
|
5
|
+
* which agents run, the model and thinking strength each runs on, and the
|
|
6
|
+
* delegation directive toggle. Everything else (agent scope, idle timeout,
|
|
7
7
|
* result lines, notifications) is config-file-only; model failures hand
|
|
8
8
|
* directly to the current main model, and thinking defaults to capability-
|
|
9
9
|
* aware Auto.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { stat } from "node:fs/promises";
|
|
13
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
13
14
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { discoverAgents } from "./agents.ts";
|
|
14
16
|
import {
|
|
15
17
|
BUILTIN_AGENT_NAMES,
|
|
16
18
|
DEFAULT_CONFIG,
|
|
17
19
|
DEFAULT_ENABLED_AGENTS,
|
|
20
|
+
DEFAULT_THINKING_LEVEL,
|
|
18
21
|
type SubagentsConfig,
|
|
22
|
+
type ThinkingLevel,
|
|
19
23
|
errorMessage,
|
|
20
24
|
getConfigPath,
|
|
21
25
|
loadConfig,
|
|
@@ -27,7 +31,10 @@ import {
|
|
|
27
31
|
availableModelsInScope,
|
|
28
32
|
buildModelPickerItems,
|
|
29
33
|
currentModelRef,
|
|
34
|
+
findModelByRef,
|
|
30
35
|
modelRef,
|
|
36
|
+
resolveThinkingLevel,
|
|
37
|
+
supportedThinkingLevels,
|
|
31
38
|
} from "./models.ts";
|
|
32
39
|
import { promptSelectMany, promptSelectOne } from "./ui.ts";
|
|
33
40
|
|
|
@@ -98,6 +105,76 @@ async function pickAgentModel(
|
|
|
98
105
|
return pickConfiguredModel(ctx, `Model for "${agentName}"?`, currentRef, escNote);
|
|
99
106
|
}
|
|
100
107
|
|
|
108
|
+
const AUTO_THINKING = "__auto_thinking__";
|
|
109
|
+
|
|
110
|
+
function actualAgentThinkingDefault(
|
|
111
|
+
ctx: ExtensionCommandContext,
|
|
112
|
+
config: SubagentsConfig,
|
|
113
|
+
agentName: string,
|
|
114
|
+
): ThinkingLevel {
|
|
115
|
+
const { agents } = discoverAgents(ctx.cwd, {
|
|
116
|
+
scope: config.agentScope,
|
|
117
|
+
enabledNames: config.enabledAgents,
|
|
118
|
+
projectTrusted: ctx.isProjectTrusted(),
|
|
119
|
+
});
|
|
120
|
+
return agents.find((agent) => agent.name === agentName)?.thinking ?? DEFAULT_THINKING_LEVEL;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const THINKING_LEVEL_HINTS: Record<ThinkingLevel, string> = {
|
|
124
|
+
off: "no reasoning tokens",
|
|
125
|
+
minimal: "minimal reasoning",
|
|
126
|
+
low: "light reasoning",
|
|
127
|
+
medium: "balanced reasoning",
|
|
128
|
+
high: "deep reasoning",
|
|
129
|
+
xhigh: "extra-deep reasoning",
|
|
130
|
+
max: "strongest reasoning",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
function effectiveModelForChoice(
|
|
134
|
+
ctx: ExtensionCommandContext,
|
|
135
|
+
choice: string,
|
|
136
|
+
): Model<Api> | undefined {
|
|
137
|
+
if (choice === CURRENT_MAIN_MODEL) return ctx.model;
|
|
138
|
+
return findModelByRef(availableModelsInScope(ctx), choice);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Auto is the default. Manual rows are exactly the levels Pi exposes for the
|
|
142
|
+
* selected model; unsupported xhigh/max entries never appear. */
|
|
143
|
+
async function pickAgentStrength(
|
|
144
|
+
ctx: ExtensionCommandContext,
|
|
145
|
+
agentName: string,
|
|
146
|
+
model: Model<Api> | undefined,
|
|
147
|
+
current: ThinkingLevel | undefined,
|
|
148
|
+
agentDefault: ThinkingLevel,
|
|
149
|
+
escNote = "cancels this setup pass",
|
|
150
|
+
): Promise<ThinkingLevel | typeof AUTO_THINKING | undefined> {
|
|
151
|
+
const supported = supportedThinkingLevels(model);
|
|
152
|
+
const automatic = resolveThinkingLevel(model, agentDefault);
|
|
153
|
+
// No model metadata, or a non-reasoning model whose only valid value is off:
|
|
154
|
+
// Auto is already the complete and least surprising choice.
|
|
155
|
+
if (supported.length <= 1) return AUTO_THINKING;
|
|
156
|
+
|
|
157
|
+
const currentEffective = current ? resolveThinkingLevel(model, current) : undefined;
|
|
158
|
+
const modelName = model ? modelRef(model) : "current main model";
|
|
159
|
+
const options = [
|
|
160
|
+
{
|
|
161
|
+
value: AUTO_THINKING,
|
|
162
|
+
label: `auto — ${automatic} for ${modelName}${current === undefined ? " (current, recommended)" : " (recommended)"}`,
|
|
163
|
+
},
|
|
164
|
+
...supported.map((level) => ({
|
|
165
|
+
value: level,
|
|
166
|
+
label: `${level} — ${THINKING_LEVEL_HINTS[level]}${current !== undefined && currentEffective === level ? " (current)" : ""}`,
|
|
167
|
+
})),
|
|
168
|
+
];
|
|
169
|
+
return promptSelectOne(
|
|
170
|
+
ctx,
|
|
171
|
+
`Thinking for "${agentName}"?`,
|
|
172
|
+
`Only levels supported by ${modelName} are shown • Enter selects • Esc ${escNote}`,
|
|
173
|
+
options,
|
|
174
|
+
current === undefined ? AUTO_THINKING : currentEffective,
|
|
175
|
+
) as Promise<ThinkingLevel | typeof AUTO_THINKING | undefined>;
|
|
176
|
+
}
|
|
177
|
+
|
|
101
178
|
async function pickAgentToConfigure(
|
|
102
179
|
ctx: ExtensionCommandContext,
|
|
103
180
|
enabledAgents: readonly string[],
|
|
@@ -117,10 +194,11 @@ async function pickAgentToConfigure(
|
|
|
117
194
|
interface ConfiguredAgentChoice {
|
|
118
195
|
name: string;
|
|
119
196
|
model: string;
|
|
197
|
+
strength: ThinkingLevel | typeof AUTO_THINKING;
|
|
120
198
|
}
|
|
121
199
|
|
|
122
|
-
/** Configure
|
|
123
|
-
*
|
|
200
|
+
/** Configure one agent while preserving the UI back stack: thinking → model →
|
|
201
|
+
* agent selection. Esc from agent selection ends this configuration pass. */
|
|
124
202
|
async function configureOneAgent(
|
|
125
203
|
ctx: ExtensionCommandContext,
|
|
126
204
|
config: SubagentsConfig,
|
|
@@ -128,14 +206,27 @@ async function configureOneAgent(
|
|
|
128
206
|
while (true) {
|
|
129
207
|
const name = await pickAgentToConfigure(ctx, config.enabledAgents);
|
|
130
208
|
if (name === undefined) return undefined;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
209
|
+
|
|
210
|
+
while (true) {
|
|
211
|
+
const modelChoice = await pickAgentModel(
|
|
212
|
+
ctx,
|
|
213
|
+
name,
|
|
214
|
+
config.agentModels[name],
|
|
215
|
+
"returns to agent selection",
|
|
216
|
+
);
|
|
217
|
+
if (modelChoice === undefined) break;
|
|
218
|
+
const model = effectiveModelForChoice(ctx, modelChoice);
|
|
219
|
+
const strength = await pickAgentStrength(
|
|
220
|
+
ctx,
|
|
221
|
+
name,
|
|
222
|
+
model,
|
|
223
|
+
config.agentThinkingLevels[name],
|
|
224
|
+
actualAgentThinkingDefault(ctx, config, name),
|
|
225
|
+
"returns to model selection",
|
|
226
|
+
);
|
|
227
|
+
if (strength === undefined) continue;
|
|
228
|
+
return { name, model: modelChoice, strength };
|
|
229
|
+
}
|
|
139
230
|
}
|
|
140
231
|
}
|
|
141
232
|
|
|
@@ -186,7 +277,7 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
186
277
|
while (true) {
|
|
187
278
|
const choice = await ctx.ui.select("pi-subagents settings", [
|
|
188
279
|
"Enable/disable agents",
|
|
189
|
-
"Configure agent
|
|
280
|
+
"Configure an agent (model + thinking)",
|
|
190
281
|
"Proactive injection",
|
|
191
282
|
"Full re-setup",
|
|
192
283
|
]);
|
|
@@ -216,9 +307,9 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
216
307
|
next.agentThinkingLevels.cleaner = config.agentThinkingLevels.reviewer;
|
|
217
308
|
}
|
|
218
309
|
}
|
|
219
|
-
// Documenter intentionally follows the faster explorer route.
|
|
220
|
-
//
|
|
221
|
-
// overrides instead of silently choosing a stronger model.
|
|
310
|
+
// Documenter intentionally follows the faster explorer route. When it
|
|
311
|
+
// is re-enabled after being explicitly disabled, it inherits any
|
|
312
|
+
// explorer overrides instead of silently choosing a stronger model.
|
|
222
313
|
if (!config.enabledAgents.includes("documenter") && enabled.includes("documenter")) {
|
|
223
314
|
if (!next.agentModels.documenter && config.agentModels.explorer) {
|
|
224
315
|
next.agentModels.documenter = config.agentModels.explorer;
|
|
@@ -230,14 +321,17 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
230
321
|
next.agentModels = keepAgentEntries(next.agentModels, enabled);
|
|
231
322
|
next.agentThinkingLevels = keepAgentEntries(next.agentThinkingLevels, enabled);
|
|
232
323
|
} else if (choice.startsWith("Configure")) {
|
|
233
|
-
// Per-agent loop:
|
|
234
|
-
// Esc
|
|
324
|
+
// Per-agent loop: thinking Esc returns to that agent's model picker;
|
|
325
|
+
// model Esc returns to the agent picker; agent-picker Esc saves completed
|
|
326
|
+
// choices and returns to this settings menu.
|
|
235
327
|
let configuredAny = false;
|
|
236
328
|
while (true) {
|
|
237
329
|
const picked = await configureOneAgent(ctx, next);
|
|
238
330
|
if (!picked) break;
|
|
239
331
|
configuredAny = true;
|
|
240
332
|
next.agentModels = applyAgentModelChoice(next.agentModels, picked.name, picked.model);
|
|
333
|
+
if (picked.strength === AUTO_THINKING) delete next.agentThinkingLevels[picked.name];
|
|
334
|
+
else next.agentThinkingLevels[picked.name] = picked.strength;
|
|
241
335
|
}
|
|
242
336
|
if (!configuredAny) continue;
|
|
243
337
|
await saveConfig(next, configPath);
|
package/src/thread-lifecycle.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
failedStartResult,
|
|
43
43
|
formatCompletionBlock,
|
|
44
44
|
modelLevelTakeoverNote,
|
|
45
|
+
reviewFailFollowUpNote,
|
|
45
46
|
queuedResult,
|
|
46
47
|
} from "./format.ts";
|
|
47
48
|
import {
|
|
@@ -220,16 +221,19 @@ export function ownsResumeReservation(
|
|
|
220
221
|
);
|
|
221
222
|
}
|
|
222
223
|
|
|
223
|
-
/** Fire-and-forget durable checkpoint
|
|
224
|
-
*
|
|
224
|
+
/** Fire-and-forget durable checkpoint. Parked threads stay resumable across
|
|
225
|
+
* reloads; a settled thread drops its record so the manifest only exists
|
|
226
|
+
* while unfinished work needs it. The live session keeps working when the
|
|
227
|
+
* manifest is unwritable; only cross-reload resume is degraded. */
|
|
225
228
|
export function persistThreadCheckpoint(
|
|
226
229
|
runtime: SubagentRuntime,
|
|
227
230
|
thread: SubagentThread,
|
|
228
231
|
state: "parked" | "completed" | "failed",
|
|
229
232
|
): void {
|
|
230
|
-
|
|
231
|
-
(
|
|
232
|
-
|
|
233
|
+
const write = state === "parked"
|
|
234
|
+
? upsertThreadRecord(runtime.configPath, threadRecordFromThread(thread, state))
|
|
235
|
+
: removeThreadRecord(runtime.configPath, thread.id);
|
|
236
|
+
void write.catch(() => undefined);
|
|
233
237
|
}
|
|
234
238
|
|
|
235
239
|
const WORKTREE_ISOLATION_INSTRUCTIONS =
|
|
@@ -246,27 +250,6 @@ export function isWorktreeCapableAgent(agent: AgentConfig): boolean {
|
|
|
246
250
|
return isWriteCapableAgent(agent);
|
|
247
251
|
}
|
|
248
252
|
|
|
249
|
-
/** A direct reviewer otherwise cannot infer enabled-role availability from its
|
|
250
|
-
* isolated task. Managed internal gates receive the same contract in their
|
|
251
|
-
* generated briefs. Advisory reviews still emit neither machine marker. */
|
|
252
|
-
function withEnabledDocumenterReviewContract(agent: AgentConfig): AgentConfig {
|
|
253
|
-
return {
|
|
254
|
-
...agent,
|
|
255
|
-
systemPrompt: `${agent.systemPrompt.trimEnd()}\n\nRuntime workflow context: documenter is enabled. In gate reviews, documentation drift is non-gating: emit DOCUMENTATION: NEEDED with ## Documentation notes, or DOCUMENTATION: CLEAN when no sync is needed. Advisory reviews still emit neither VERDICT nor DOCUMENTATION markers.`.trim(),
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** The dispatcher explicitly requested a report-only review: forbid the gate
|
|
260
|
-
* markers at the source and (in dispatch) refuse to chain on them anyway. */
|
|
261
|
-
function withAdvisoryReviewContract(agent: AgentConfig): AgentConfig {
|
|
262
|
-
return {
|
|
263
|
-
...agent,
|
|
264
|
-
systemPrompt: `${agent.systemPrompt.trimEnd()}
|
|
265
|
-
|
|
266
|
-
Runtime workflow context: this dispatch is advisory. Report findings only; do not emit VERDICT or DOCUMENTATION markers — the runtime will not act on them.`.trim(),
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
253
|
export interface DispatchEnvironment {
|
|
271
254
|
ctx: ExtensionContext;
|
|
272
255
|
config: SubagentsConfig;
|
|
@@ -296,7 +279,6 @@ export interface StartBackgroundOptions {
|
|
|
296
279
|
environment?: DispatchEnvironment;
|
|
297
280
|
seed?: SessionSeed;
|
|
298
281
|
resumeReservation?: ResumeReservation;
|
|
299
|
-
advisoryReview?: boolean;
|
|
300
282
|
}
|
|
301
283
|
|
|
302
284
|
export type StartBackgroundInternal = (
|
|
@@ -426,15 +408,7 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
426
408
|
if (!discoveredAgent) return failedStartResult(agentName, task, `Unknown agent: "${agentName}".`);
|
|
427
409
|
const resolveLiveAgentTools = (candidate: AgentConfig): AgentConfig =>
|
|
428
410
|
resolveAgentTools({ ...candidate, tools: discoveredAgent.tools }, runtime.getActiveTools());
|
|
429
|
-
const
|
|
430
|
-
const advisoryReview = startOptions.advisoryReview ?? existingThread?.advisoryReview ?? false;
|
|
431
|
-
const agent = agentName === "reviewer"
|
|
432
|
-
? advisoryReview
|
|
433
|
-
? withAdvisoryReviewContract(resolvedAgent)
|
|
434
|
-
: runAgents.some((candidate) => candidate.name === "documenter")
|
|
435
|
-
? withEnabledDocumenterReviewContract(resolvedAgent)
|
|
436
|
-
: resolvedAgent
|
|
437
|
-
: resolvedAgent;
|
|
411
|
+
const agent = resolveLiveAgentTools(discoveredAgent);
|
|
438
412
|
if (isolation === "worktree" && !isWorktreeCapableAgent(agent)) {
|
|
439
413
|
return {
|
|
440
414
|
...failedStartResult(agentName, task, `Agent "${agentName}" is read-only; worktree isolation is available only to write-capable agents such as worker, cleaner, or documenter.`),
|
|
@@ -528,7 +502,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
528
502
|
thread.executionCwd = executionCwd;
|
|
529
503
|
thread.thinkingLevel = thinkingLevel;
|
|
530
504
|
thread.isolation = isolation;
|
|
531
|
-
thread.advisoryReview = advisoryReview;
|
|
532
505
|
thread.worktree = worktree;
|
|
533
506
|
thread.state = "queued";
|
|
534
507
|
thread.control = control;
|
|
@@ -552,7 +525,6 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
552
525
|
executionCwd,
|
|
553
526
|
thinkingLevel,
|
|
554
527
|
isolation,
|
|
555
|
-
advisoryReview,
|
|
556
528
|
worktree,
|
|
557
529
|
state: "queued",
|
|
558
530
|
control,
|
|
@@ -670,13 +642,13 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
670
642
|
if (thread.lifecycleOperation === "stop") return;
|
|
671
643
|
|
|
672
644
|
// A shutdown can win in the microtask gap after the top-level RPC
|
|
673
|
-
// settles. Do not launch an obsolete
|
|
645
|
+
// settles. Do not launch an obsolete downstream gate or replace the
|
|
674
646
|
// stable top-level session with an aborted downstream attempt.
|
|
675
647
|
if (backgroundSignal.aborted || lifecycleInterrupted() || !runtime.sessionActive) return;
|
|
676
648
|
|
|
677
649
|
if (thread.retireOnSettle) runtime.retireThreadSession(thread);
|
|
678
650
|
let workflowOutcome: ManagedWorkflowOutcome | undefined;
|
|
679
|
-
const workflowPlan = getManagedWorkflowPlan(result, workflowAvailability
|
|
651
|
+
const workflowPlan = getManagedWorkflowPlan(result, workflowAvailability);
|
|
680
652
|
if (workflowPlan && runtime.sessionActive) {
|
|
681
653
|
// The continuation is runtime-initiated (gate review,
|
|
682
654
|
// documentation sync): release this generation's
|
|
@@ -801,6 +773,9 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
801
773
|
if (needsFullFinal) {
|
|
802
774
|
block += `\n\n${formatCompletionBlock(result, runConfig.maxResultLines, originalCwd)}`;
|
|
803
775
|
}
|
|
776
|
+
if (finalVerdict === "fail") {
|
|
777
|
+
block += `\n\n${reviewFailFollowUpNote()}`;
|
|
778
|
+
}
|
|
804
779
|
if (modelLevel) block += `\n\n${modelLevelTakeoverNote(result, { runId })}`;
|
|
805
780
|
runtime.sendCompletionGroup([{
|
|
806
781
|
agent: `managed workflow (${result.agent})`,
|
|
@@ -816,7 +791,9 @@ export function createBackgroundDispatcher(options: BackgroundDispatcherOptions)
|
|
|
816
791
|
agent: result.agent,
|
|
817
792
|
block: modelLevel
|
|
818
793
|
? `${formatCompletionBlock(result, runConfig.maxResultLines, result.projectCwd ?? originalCwd)}\n\n${modelLevelTakeoverNote(result, { runId })}`
|
|
819
|
-
:
|
|
794
|
+
: result.agent === "reviewer" && reviewVerdict(getResultOutput(result)) === "fail"
|
|
795
|
+
? `${formatCompletionBlock(result, runConfig.maxResultLines, result.projectCwd ?? originalCwd)}\n\n${reviewFailFollowUpNote()}`
|
|
796
|
+
: formatCompletionBlock(result, runConfig.maxResultLines, result.projectCwd ?? originalCwd),
|
|
820
797
|
triggerTurn: completionTriggersTurn(result, runConfig.notifyOnReviewPass),
|
|
821
798
|
usage: result.usage,
|
|
822
799
|
};
|
|
@@ -1290,7 +1267,6 @@ function createRestoredThread(
|
|
|
1290
1267
|
executionCwd: record.executionCwd,
|
|
1291
1268
|
...(record.thinkingLevel ? { thinkingLevel: record.thinkingLevel as ThinkingLevel } : {}),
|
|
1292
1269
|
isolation: record.isolation,
|
|
1293
|
-
advisoryReview: false,
|
|
1294
1270
|
worktree,
|
|
1295
1271
|
state,
|
|
1296
1272
|
control: new RpcRunControl(record.task, record.generation),
|
|
@@ -1320,15 +1296,20 @@ function createRestoredThread(
|
|
|
1320
1296
|
return thread;
|
|
1321
1297
|
}
|
|
1322
1298
|
|
|
1323
|
-
/** Rebuild parked
|
|
1324
|
-
* or restart. Orphaned children recorded by the previous process are
|
|
1325
|
-
* first; records whose retained session vanished
|
|
1326
|
-
*
|
|
1299
|
+
/** Rebuild interrupted (parked) threads from the durable manifest after a
|
|
1300
|
+
* reload or restart. Orphaned children recorded by the previous process are
|
|
1301
|
+
* killed first; records whose retained session vanished — and settled records
|
|
1302
|
+
* left by older versions, which hold no work worth resuming — drop out with
|
|
1303
|
+
* their artifacts. Returns the restored run ids. */
|
|
1327
1304
|
export async function restoreDurableThreads(runtime: SubagentRuntime): Promise<number[]> {
|
|
1328
1305
|
const records = await readThreadRecords(runtime.configPath);
|
|
1329
1306
|
const restoredIds: number[] = [];
|
|
1330
1307
|
for (const record of records) {
|
|
1331
1308
|
if (runtime.threads.has(record.runId) || monitor.findRun(record.runId)) continue;
|
|
1309
|
+
if (record.state !== "parked") {
|
|
1310
|
+
await discardRestoredRecord(runtime, record);
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1332
1313
|
// A child orphaned by reload/crash may still hold the retained session.
|
|
1333
1314
|
// The on-disk session checkpoint is what survives; kill the writer.
|
|
1334
1315
|
for (const pid of record.childPids) {
|
|
@@ -1348,33 +1329,29 @@ export async function restoreDurableThreads(runtime: SubagentRuntime): Promise<n
|
|
|
1348
1329
|
// A worktree thread whose isolated filesystem is gone cannot continue its
|
|
1349
1330
|
// isolation invariant; surface it as failed instead of pretending.
|
|
1350
1331
|
const state: ThreadState = worktree
|
|
1351
|
-
?
|
|
1332
|
+
? "parked"
|
|
1352
1333
|
: record.isolation === "worktree" && record.worktree
|
|
1353
1334
|
? "failed"
|
|
1354
|
-
:
|
|
1335
|
+
: "parked";
|
|
1355
1336
|
const thread = createRestoredThread(runtime, record, worktree, state);
|
|
1356
1337
|
runtime.threads.set(record.runId, thread);
|
|
1357
1338
|
runtime.sessionDirs.add(record.sessionDir!);
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
});
|
|
1375
|
-
} else if (thread.lastResult) {
|
|
1376
|
-
runtime.registerRunResult(record.runId, thread.lastResult);
|
|
1377
|
-
}
|
|
1339
|
+
monitor.restoreRun({
|
|
1340
|
+
id: record.runId,
|
|
1341
|
+
agent: record.agentName,
|
|
1342
|
+
task: record.task,
|
|
1343
|
+
status: "parked",
|
|
1344
|
+
elapsedMs: record.elapsedMs,
|
|
1345
|
+
isolation: record.isolation,
|
|
1346
|
+
...(record.worktree
|
|
1347
|
+
? {
|
|
1348
|
+
integrationStatus: record.worktree.state === "active"
|
|
1349
|
+
? ("pending" as const)
|
|
1350
|
+
: record.worktree.state,
|
|
1351
|
+
...(worktree ? { worktreeId: worktreeGroupId(worktree) } : {}),
|
|
1352
|
+
}
|
|
1353
|
+
: {}),
|
|
1354
|
+
});
|
|
1378
1355
|
restoredIds.push(record.runId);
|
|
1379
1356
|
}
|
|
1380
1357
|
return restoredIds;
|
package/src/tools.ts
CHANGED
|
@@ -106,7 +106,7 @@ export function registerLookupTools(pi: ExtensionAPI, runtime: SubagentRuntime):
|
|
|
106
106
|
const context = hadRetainedSession
|
|
107
107
|
? "the same retained session and prior context are preserved"
|
|
108
108
|
: "no prior child session existed, so only the logical run and objective are continued";
|
|
109
|
-
return { content: [{ type: "text", text: `Resumed run #${thread.id}, ${mode}; ${context}, and cumulative active time is preserved.
|
|
109
|
+
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: {} };
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
} catch (error) {
|