@narumitw/pi-subagents 0.49.3 → 0.52.0
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 +362 -53
- package/package.json +10 -7
- package/src/adaptive-scheduler.ts +224 -0
- package/src/admission-benchmark.ts +95 -0
- package/src/admission-policy.ts +78 -0
- package/src/agent-projection.ts +53 -0
- package/src/agents.ts +58 -1
- package/src/auto-transport.ts +114 -0
- package/src/blocking-status.ts +63 -0
- package/src/capabilities.ts +145 -0
- package/src/capability-grant.ts +115 -0
- package/src/capability-router.ts +107 -0
- package/src/completion-delivery.ts +257 -0
- package/src/config-status.ts +221 -0
- package/src/config-ui.ts +215 -236
- package/src/consult-resources.ts +4 -27
- package/src/consult.ts +9 -1
- package/src/create-stateful-transport.ts +55 -0
- package/src/delegation-contract.ts +417 -0
- package/src/execution-plan.ts +322 -0
- package/src/execution-profiles.ts +95 -0
- package/src/execution-ui.ts +320 -0
- package/src/execution.ts +1098 -158
- package/src/in-process-transport.ts +269 -25
- package/src/inspect-render.ts +101 -1
- package/src/inspect.ts +321 -3
- package/src/integration-controller.ts +98 -0
- package/src/limits.ts +3 -0
- package/src/orchestration-metrics.ts +109 -0
- package/src/outcome.ts +61 -0
- package/src/panel-child-group.ts +35 -0
- package/src/panel-contract.ts +343 -0
- package/src/panel-evidence.ts +59 -0
- package/src/panel-execution.ts +770 -0
- package/src/panel-failure.ts +56 -0
- package/src/panel-planning.ts +175 -0
- package/src/panel-prompts.ts +132 -0
- package/src/panel-reconciliation.ts +57 -0
- package/src/panel-render.ts +103 -0
- package/src/parallel-limit-ui.ts +112 -0
- package/src/params.ts +179 -3
- package/src/persistence.ts +182 -32
- package/src/prompt-resources.ts +38 -0
- package/src/registry-types.ts +175 -0
- package/src/registry.ts +466 -143
- package/src/render.ts +72 -6
- package/src/result-contract.ts +416 -0
- package/src/retained-semantic-state.ts +100 -0
- package/src/rpc-timeout-finalization.ts +207 -0
- package/src/rpc-transport-metadata.ts +65 -0
- package/src/rpc-transport.ts +990 -0
- package/src/rpc-turn-capture.ts +142 -0
- package/src/runner-result.ts +55 -0
- package/src/runner-usage.ts +48 -0
- package/src/runner.ts +325 -73
- package/src/semantic-snapshot.ts +214 -0
- package/src/settings.ts +254 -35
- package/src/spawn-idempotency.ts +61 -0
- package/src/stateful-config.ts +13 -0
- package/src/stateful-guidance.ts +1 -0
- package/src/stateful-lifecycle.ts +45 -2
- package/src/stateful-limit-ui.ts +246 -0
- package/src/stateful-limits.ts +96 -0
- package/src/stateful-prompt.ts +11 -2
- package/src/stateful-render.ts +48 -3
- package/src/stateful.ts +467 -357
- package/src/subagents.ts +114 -46
- package/src/subprocess-transport.ts +64 -5
- package/src/supervision.ts +103 -0
- package/src/timeout-checkpoint.ts +305 -0
- package/src/timeout-finalization.ts +75 -0
- package/src/transport-types.ts +68 -0
- package/src/transport-ui.ts +169 -0
- package/src/transport.ts +16 -4
- package/src/turn-budget.ts +109 -0
- package/src/verification-policy.ts +67 -0
- package/src/work-item-ledger.ts +931 -0
- package/src/work-item-persistence.ts +223 -0
- package/src/workflow-planning.ts +162 -0
- package/src/workflow-tree-identity.ts +289 -0
- package/src/workflow-ui.ts +61 -0
- package/src/workflow-verification.ts +296 -0
- package/src/workspace.ts +69 -12
|
@@ -9,12 +9,29 @@ import {
|
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import { type AgentConfig, discoverAgents, type SubagentThinkingLevel } from "./agents.js";
|
|
11
11
|
import { redactPrivateText } from "./context.js";
|
|
12
|
+
import { appendDelegationContract } from "./delegation-contract.js";
|
|
12
13
|
import { resolveDefaultSubagentTimeoutMs } from "./execution.js";
|
|
13
14
|
import { DEFAULT_MAX_CONTEXT_BYTES, DEFAULT_MAX_OUTPUT_BYTES, truncateUtf8 } from "./limits.js";
|
|
14
|
-
import {
|
|
15
|
+
import { resolvePiPromptResources } from "./prompt-resources.js";
|
|
15
16
|
import type { AgentTurn, ManagedAgent, TurnOutcome } from "./registry.js";
|
|
17
|
+
import { appendResultInstruction } from "./result-contract.js";
|
|
18
|
+
import { safeTerminalLine } from "./safe-text.js";
|
|
16
19
|
import { readSubagentSettings } from "./settings.js";
|
|
20
|
+
import {
|
|
21
|
+
formatTimeoutCheckpoint,
|
|
22
|
+
formatTurnTerminationMessage,
|
|
23
|
+
journalMessages,
|
|
24
|
+
TimeoutProgressJournal,
|
|
25
|
+
TURN_TERMINATION_VERSION,
|
|
26
|
+
type TurnTerminationReport,
|
|
27
|
+
} from "./timeout-checkpoint.js";
|
|
28
|
+
import {
|
|
29
|
+
buildTimeoutFinalizationPrompt,
|
|
30
|
+
resolveTimeoutFinalizationMs,
|
|
31
|
+
} from "./timeout-finalization.js";
|
|
17
32
|
import type { SubagentTransport } from "./transport.js";
|
|
33
|
+
import type { TransportProgressCallback, TransportTelemetry } from "./transport-types.js";
|
|
34
|
+
import { TurnBudgetMonitor, type TurnBudgetStop, type TurnLimits } from "./turn-budget.js";
|
|
18
35
|
|
|
19
36
|
const BUILT_IN_TOOL_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
|
20
37
|
const DEFAULT_ABORT_GRACE_MS = 5_000;
|
|
@@ -57,6 +74,9 @@ export interface ParentRuntimeSnapshot {
|
|
|
57
74
|
export interface ChildSession {
|
|
58
75
|
readonly sessionId: string;
|
|
59
76
|
readonly messages: readonly unknown[];
|
|
77
|
+
readonly provider?: string;
|
|
78
|
+
readonly model?: string;
|
|
79
|
+
readonly thinkingLevel?: SubagentThinkingLevel;
|
|
60
80
|
prompt(text: string): Promise<void>;
|
|
61
81
|
subscribe(listener: (event: unknown) => void): () => void;
|
|
62
82
|
abort(): Promise<void>;
|
|
@@ -83,6 +103,7 @@ export interface InProcessTransportOptions {
|
|
|
83
103
|
discoverAgent?: (agent: ManagedAgent) => AgentConfig | undefined;
|
|
84
104
|
defaultTimeoutMs?: number;
|
|
85
105
|
abortGraceMs?: number;
|
|
106
|
+
timeoutFinalizationMs?: number;
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
interface ChildSessionRecord {
|
|
@@ -96,6 +117,7 @@ type PromptSettlement =
|
|
|
96
117
|
| { kind: "completed" }
|
|
97
118
|
| { kind: "failed"; error: unknown }
|
|
98
119
|
| { kind: "timeout" }
|
|
120
|
+
| { kind: "limit"; stop: TurnBudgetStop }
|
|
99
121
|
| { kind: "aborted" };
|
|
100
122
|
|
|
101
123
|
export class InProcessTransport implements SubagentTransport {
|
|
@@ -118,37 +140,101 @@ export class InProcessTransport implements SubagentTransport {
|
|
|
118
140
|
this.abortGraceMs = options.abortGraceMs ?? DEFAULT_ABORT_GRACE_MS;
|
|
119
141
|
}
|
|
120
142
|
|
|
121
|
-
async runTurn(
|
|
122
|
-
|
|
143
|
+
async runTurn(
|
|
144
|
+
agent: ManagedAgent,
|
|
145
|
+
task: string,
|
|
146
|
+
signal: AbortSignal,
|
|
147
|
+
onProgress?: TransportProgressCallback,
|
|
148
|
+
): Promise<TurnOutcome> {
|
|
149
|
+
const startedAt = Date.now();
|
|
150
|
+
let telemetry: TransportTelemetry = {
|
|
151
|
+
transport: "in-process",
|
|
152
|
+
phase: "starting",
|
|
153
|
+
updatedAt: startedAt,
|
|
154
|
+
timing: { startedAt, transportStartedAt: startedAt },
|
|
155
|
+
};
|
|
156
|
+
const publish = (patch: Partial<TransportTelemetry>) => {
|
|
157
|
+
telemetry = {
|
|
158
|
+
...telemetry,
|
|
159
|
+
...patch,
|
|
160
|
+
timing: { ...telemetry.timing, ...patch.timing },
|
|
161
|
+
updatedAt: Date.now(),
|
|
162
|
+
};
|
|
163
|
+
onProgress?.({ ...telemetry, timing: { ...telemetry.timing } });
|
|
164
|
+
};
|
|
165
|
+
publish({});
|
|
166
|
+
if (signal.aborted) {
|
|
167
|
+
publish({ phase: "interrupted", failurePhase: "starting" });
|
|
168
|
+
return { ...interruptedOutcome(""), telemetry };
|
|
169
|
+
}
|
|
123
170
|
const agentConfig = this.discoverAgent(agent);
|
|
124
171
|
if (!agentConfig) {
|
|
125
|
-
|
|
172
|
+
publish({ phase: "failed", failurePhase: "starting" });
|
|
173
|
+
return {
|
|
174
|
+
output: "",
|
|
175
|
+
exitCode: 1,
|
|
176
|
+
error: `Unknown subagent: ${agent.agent}`,
|
|
177
|
+
telemetry,
|
|
178
|
+
};
|
|
126
179
|
}
|
|
127
180
|
let tools: string[] | undefined;
|
|
128
181
|
try {
|
|
129
|
-
tools = validateInProcessTools(agentConfig.tools);
|
|
182
|
+
tools = validateInProcessTools(agent.executionPlan?.effectiveTools ?? agentConfig.tools);
|
|
130
183
|
} catch (error) {
|
|
131
|
-
|
|
184
|
+
publish({ phase: "failed", failurePhase: "starting" });
|
|
185
|
+
return { output: "", exitCode: 1, error: errorMessage(error), telemetry };
|
|
132
186
|
}
|
|
133
187
|
let record: ChildSessionRecord;
|
|
134
188
|
try {
|
|
135
189
|
record = await this.getOrCreate(agent, agentConfig, tools);
|
|
136
190
|
} catch (error) {
|
|
137
|
-
|
|
191
|
+
publish({ phase: "failed", failurePhase: "starting" });
|
|
192
|
+
return { output: "", exitCode: 1, error: errorMessage(error), telemetry };
|
|
193
|
+
}
|
|
194
|
+
publish({
|
|
195
|
+
phase: "ready",
|
|
196
|
+
provider: record.session.provider,
|
|
197
|
+
model: record.session.model,
|
|
198
|
+
thinkingLevel: record.session.thinkingLevel,
|
|
199
|
+
timing: { readyAt: Date.now() },
|
|
200
|
+
});
|
|
201
|
+
if (signal.aborted) {
|
|
202
|
+
await this.releaseById(agent.id).catch(() => undefined);
|
|
203
|
+
publish({ phase: "interrupted", failurePhase: "ready" });
|
|
204
|
+
return { ...interruptedOutcome(""), telemetry };
|
|
138
205
|
}
|
|
139
|
-
if (signal.aborted) return interruptedOutcome("");
|
|
140
206
|
const prompt = buildCurrentTurnPrompt(agent, task);
|
|
141
|
-
const timeoutMs =
|
|
207
|
+
const timeoutMs =
|
|
208
|
+
agent.currentTimeoutMs ?? agent.timeoutMs ?? agentConfig.timeoutMs ?? this.defaultTimeoutMs;
|
|
142
209
|
const startingMessageCount = record.session.messages.length;
|
|
143
210
|
record.lastOutput = "";
|
|
144
|
-
|
|
211
|
+
publish({ phase: "running", timing: { promptAcceptedAt: Date.now() } });
|
|
212
|
+
const settlement = await this.runPrompt(record, prompt, signal, timeoutMs, {
|
|
213
|
+
idleTimeoutMs: agent.currentIdleTimeoutMs ?? agent.idleTimeoutMs,
|
|
214
|
+
maxTurns: agent.currentMaxTurns ?? agent.maxTurns,
|
|
215
|
+
maxToolCalls: agent.currentMaxToolCalls ?? agent.maxToolCalls,
|
|
216
|
+
});
|
|
145
217
|
const final = latestAssistant(record.session.messages.slice(startingMessageCount));
|
|
146
218
|
const output = truncateUtf8(final.output || record.lastOutput, DEFAULT_MAX_OUTPUT_BYTES);
|
|
147
219
|
const truncated = output.truncated || agent.contextTruncated;
|
|
148
220
|
const policy = inProcessPolicy(agentConfig, agent);
|
|
221
|
+
const settledAt = Date.now();
|
|
222
|
+
if (signal.aborted && (settlement.kind === "timeout" || settlement.kind === "limit")) {
|
|
223
|
+
publish({ phase: "interrupted", failurePhase: "running", timing: { settledAt } });
|
|
224
|
+
return {
|
|
225
|
+
...interruptedOutcome(output.text),
|
|
226
|
+
truncated,
|
|
227
|
+
policy,
|
|
228
|
+
telemetry,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
149
231
|
|
|
150
232
|
switch (settlement.kind) {
|
|
151
233
|
case "completed":
|
|
234
|
+
publish({
|
|
235
|
+
phase: final.stopReason === "error" ? "failed" : "settled",
|
|
236
|
+
timing: { settledAt },
|
|
237
|
+
});
|
|
152
238
|
if (final.stopReason === "error") {
|
|
153
239
|
return {
|
|
154
240
|
output: output.text,
|
|
@@ -156,13 +242,16 @@ export class InProcessTransport implements SubagentTransport {
|
|
|
156
242
|
truncated,
|
|
157
243
|
error: final.error || "In-process subagent returned an error",
|
|
158
244
|
policy,
|
|
245
|
+
telemetry,
|
|
159
246
|
};
|
|
160
247
|
}
|
|
161
248
|
if (final.stopReason === "aborted") {
|
|
249
|
+
publish({ phase: "interrupted", failurePhase: "running" });
|
|
162
250
|
return {
|
|
163
251
|
...interruptedOutcome(output.text),
|
|
164
252
|
truncated,
|
|
165
253
|
policy,
|
|
254
|
+
telemetry,
|
|
166
255
|
};
|
|
167
256
|
}
|
|
168
257
|
return {
|
|
@@ -170,28 +259,104 @@ export class InProcessTransport implements SubagentTransport {
|
|
|
170
259
|
exitCode: 0,
|
|
171
260
|
truncated,
|
|
172
261
|
policy,
|
|
262
|
+
telemetry,
|
|
173
263
|
};
|
|
174
264
|
case "failed":
|
|
265
|
+
publish({ phase: "failed", failurePhase: "running", timing: { settledAt } });
|
|
175
266
|
return {
|
|
176
267
|
output: output.text,
|
|
177
268
|
exitCode: 1,
|
|
178
269
|
truncated,
|
|
179
270
|
error: errorMessage(settlement.error),
|
|
180
271
|
policy,
|
|
272
|
+
telemetry,
|
|
181
273
|
};
|
|
182
274
|
case "timeout":
|
|
275
|
+
case "limit": {
|
|
276
|
+
const stop =
|
|
277
|
+
settlement.kind === "limit"
|
|
278
|
+
? settlement.stop
|
|
279
|
+
: ({ reason: "work_timeout", limit: timeoutMs } as const);
|
|
280
|
+
const journal = new TimeoutProgressJournal();
|
|
281
|
+
journalMessages(journal, record.session.messages.slice(startingMessageCount));
|
|
282
|
+
const termination: TurnTerminationReport = {
|
|
283
|
+
version: TURN_TERMINATION_VERSION,
|
|
284
|
+
reason: stop.reason,
|
|
285
|
+
limit: stop.limit,
|
|
286
|
+
checkpoint: journal.checkpoint(task, output.text),
|
|
287
|
+
finalization: { attempted: false, status: "skipped", durationMs: 0 },
|
|
288
|
+
};
|
|
289
|
+
let finalizedOutput = output.text || formatTimeoutCheckpoint(termination.checkpoint);
|
|
290
|
+
let finalizationError: string | undefined;
|
|
291
|
+
if (!signal.aborted && this.sessions.get(agent.id) === record) {
|
|
292
|
+
publish({ phase: "finalizing", failurePhase: "running" });
|
|
293
|
+
const summaryStart = record.session.messages.length;
|
|
294
|
+
const finalizationStartedAt = Date.now();
|
|
295
|
+
record.lastOutput = "";
|
|
296
|
+
const summarySettlement = await this.runPrompt(
|
|
297
|
+
record,
|
|
298
|
+
buildTimeoutFinalizationPrompt({
|
|
299
|
+
task,
|
|
300
|
+
partialOutput: output.text,
|
|
301
|
+
checkpoint: termination.checkpoint,
|
|
302
|
+
terminationReason: stop.reason,
|
|
303
|
+
resultFormat: agent.resultFormat,
|
|
304
|
+
}),
|
|
305
|
+
signal,
|
|
306
|
+
resolveTimeoutFinalizationMs(timeoutMs, this.options.timeoutFinalizationMs),
|
|
307
|
+
);
|
|
308
|
+
const summary = latestAssistant(record.session.messages.slice(summaryStart));
|
|
309
|
+
const boundedSummary = truncateUtf8(
|
|
310
|
+
summary.output || record.lastOutput,
|
|
311
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
312
|
+
);
|
|
313
|
+
if (
|
|
314
|
+
summarySettlement.kind === "completed" &&
|
|
315
|
+
summary.stopReason !== "error" &&
|
|
316
|
+
boundedSummary.text.trim()
|
|
317
|
+
) {
|
|
318
|
+
finalizedOutput = boundedSummary.text;
|
|
319
|
+
termination.finalization = {
|
|
320
|
+
attempted: true,
|
|
321
|
+
status: "completed",
|
|
322
|
+
durationMs: Date.now() - finalizationStartedAt,
|
|
323
|
+
};
|
|
324
|
+
} else {
|
|
325
|
+
finalizationError =
|
|
326
|
+
summarySettlement.kind === "failed"
|
|
327
|
+
? errorMessage(summarySettlement.error)
|
|
328
|
+
: `timeout summary ${summarySettlement.kind}`;
|
|
329
|
+
termination.finalization = {
|
|
330
|
+
attempted: true,
|
|
331
|
+
status: summarySettlement.kind === "timeout" ? "timed_out" : "failed",
|
|
332
|
+
durationMs: Date.now() - finalizationStartedAt,
|
|
333
|
+
error: finalizationError,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
publish({ phase: "failed", failurePhase: "running", timing: { settledAt: Date.now() } });
|
|
183
338
|
return {
|
|
184
|
-
output:
|
|
339
|
+
output: finalizedOutput,
|
|
185
340
|
exitCode: 124,
|
|
186
341
|
truncated,
|
|
187
|
-
error:
|
|
342
|
+
error: [
|
|
343
|
+
formatTurnTerminationMessage(stop.reason, stop.limit, "In-process subagent"),
|
|
344
|
+
finalizationError,
|
|
345
|
+
]
|
|
346
|
+
.filter(Boolean)
|
|
347
|
+
.join("; "),
|
|
188
348
|
policy,
|
|
349
|
+
termination,
|
|
350
|
+
telemetry,
|
|
189
351
|
};
|
|
352
|
+
}
|
|
190
353
|
case "aborted":
|
|
354
|
+
publish({ phase: "interrupted", failurePhase: "running", timing: { settledAt } });
|
|
191
355
|
return {
|
|
192
356
|
...interruptedOutcome(output.text),
|
|
193
357
|
truncated,
|
|
194
358
|
policy,
|
|
359
|
+
telemetry,
|
|
195
360
|
};
|
|
196
361
|
}
|
|
197
362
|
}
|
|
@@ -289,11 +454,41 @@ export class InProcessTransport implements SubagentTransport {
|
|
|
289
454
|
prompt: string,
|
|
290
455
|
signal: AbortSignal,
|
|
291
456
|
timeoutMs: number,
|
|
457
|
+
limits: TurnLimits = {},
|
|
292
458
|
): Promise<PromptSettlement> {
|
|
459
|
+
if (signal.aborted) return { kind: "aborted" };
|
|
293
460
|
let timeout: NodeJS.Timeout | undefined;
|
|
294
461
|
let abortHandler: (() => void) | undefined;
|
|
295
|
-
|
|
296
|
-
|
|
462
|
+
let resolveLimit!: (settlement: PromptSettlement) => void;
|
|
463
|
+
const limitSettlement = new Promise<PromptSettlement>((resolve) => {
|
|
464
|
+
resolveLimit = resolve;
|
|
465
|
+
});
|
|
466
|
+
const monitor = new TurnBudgetMonitor({
|
|
467
|
+
...limits,
|
|
468
|
+
onExceeded: (stop) => resolveLimit({ kind: "limit", stop }),
|
|
469
|
+
});
|
|
470
|
+
const unsubscribeBudget = record.session.subscribe((event) => {
|
|
471
|
+
const type = childEventType(event);
|
|
472
|
+
if (type === "tool_execution_end") monitor.recordActivity();
|
|
473
|
+
if (type !== "message_end") return;
|
|
474
|
+
const message = eventMessage(event);
|
|
475
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) return;
|
|
476
|
+
const value = message as Record<string, unknown>;
|
|
477
|
+
if (value.role === "toolResult") {
|
|
478
|
+
monitor.recordActivity();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (value.role !== "assistant") return;
|
|
482
|
+
monitor.recordToolCalls(assistantToolCallCount(value.content));
|
|
483
|
+
monitor.recordAssistantTurn(
|
|
484
|
+
typeof value.stopReason === "string" ? value.stopReason : undefined,
|
|
485
|
+
);
|
|
486
|
+
});
|
|
487
|
+
const promptSettlement: Promise<PromptSettlement> = Promise.resolve()
|
|
488
|
+
.then(() => {
|
|
489
|
+
if (signal.aborted) throw new Error("In-process subagent prompt aborted before start");
|
|
490
|
+
return record.session.prompt(prompt);
|
|
491
|
+
})
|
|
297
492
|
.then(() => ({ kind: "completed" as const }))
|
|
298
493
|
.catch((error: unknown) => ({ kind: "failed" as const, error }));
|
|
299
494
|
const timeoutSettlement = new Promise<PromptSettlement>((resolve) => {
|
|
@@ -302,13 +497,31 @@ export class InProcessTransport implements SubagentTransport {
|
|
|
302
497
|
const abortSettlement = new Promise<PromptSettlement>((resolve) => {
|
|
303
498
|
abortHandler = () => resolve({ kind: "aborted" });
|
|
304
499
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
500
|
+
if (signal.aborted) abortHandler();
|
|
305
501
|
});
|
|
306
|
-
const settlement = await Promise.race([
|
|
502
|
+
const settlement = await Promise.race([
|
|
503
|
+
promptSettlement,
|
|
504
|
+
timeoutSettlement,
|
|
505
|
+
abortSettlement,
|
|
506
|
+
limitSettlement,
|
|
507
|
+
]);
|
|
307
508
|
if (timeout) clearTimeout(timeout);
|
|
308
509
|
if (abortHandler) signal.removeEventListener("abort", abortHandler);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
510
|
+
try {
|
|
511
|
+
unsubscribeBudget();
|
|
512
|
+
} catch {
|
|
513
|
+
// The owning record's unsubscribe/dispose path remains authoritative.
|
|
514
|
+
}
|
|
515
|
+
monitor.dispose();
|
|
516
|
+
if (
|
|
517
|
+
settlement.kind === "timeout" ||
|
|
518
|
+
settlement.kind === "limit" ||
|
|
519
|
+
settlement.kind === "aborted"
|
|
520
|
+
) {
|
|
521
|
+
const [, settledAfterAbort] = await Promise.all([
|
|
522
|
+
settleWithin(record.session.abort(), this.abortGraceMs),
|
|
523
|
+
completesWithin(promptSettlement, this.abortGraceMs),
|
|
524
|
+
]);
|
|
312
525
|
if (!settledAfterAbort) this.discardRecord(record);
|
|
313
526
|
}
|
|
314
527
|
return settlement;
|
|
@@ -331,7 +544,7 @@ export function validateInProcessTools(tools: string[] | undefined): string[] |
|
|
|
331
544
|
const unsupported = unique.filter((tool) => !BUILT_IN_TOOL_NAMES.has(tool));
|
|
332
545
|
if (unsupported.length > 0) {
|
|
333
546
|
throw new Error(
|
|
334
|
-
`In-process subagents cannot load extension/custom tools: ${unsupported.join(", ")}. Use stateful.transport "subprocess" for this agent.`,
|
|
547
|
+
`In-process subagents cannot load extension/custom tools: ${unsupported.map((tool) => safeTerminalLine(tool, 256)).join(", ")}. Use stateful.transport "subprocess" for this agent.`,
|
|
335
548
|
);
|
|
336
549
|
}
|
|
337
550
|
return unique;
|
|
@@ -392,6 +605,15 @@ export async function createSdkChildSession(
|
|
|
392
605
|
get messages() {
|
|
393
606
|
return session.messages;
|
|
394
607
|
},
|
|
608
|
+
get provider() {
|
|
609
|
+
return session.model?.provider;
|
|
610
|
+
},
|
|
611
|
+
get model() {
|
|
612
|
+
return session.model?.id;
|
|
613
|
+
},
|
|
614
|
+
get thinkingLevel() {
|
|
615
|
+
return session.thinkingLevel;
|
|
616
|
+
},
|
|
395
617
|
prompt: (text) => session.prompt(text),
|
|
396
618
|
subscribe: (listener) => session.subscribe((event) => listener(event)),
|
|
397
619
|
abort: () => session.abort(),
|
|
@@ -491,7 +713,7 @@ async function prepareInProcessServices(
|
|
|
491
713
|
agentSystemPrompt: string,
|
|
492
714
|
projectTrusted: boolean,
|
|
493
715
|
): Promise<{ services: AgentSessionServices; support: CoreSessionSupport }> {
|
|
494
|
-
|
|
716
|
+
const promptResources = await resolvePiPromptResources(cwd, projectTrusted, agentDir);
|
|
495
717
|
const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted });
|
|
496
718
|
const support = await loadCoreSessionSupport();
|
|
497
719
|
if (!support) throw unsupportedInProcessCoreError();
|
|
@@ -501,7 +723,10 @@ async function prepareInProcessServices(
|
|
|
501
723
|
settingsManager,
|
|
502
724
|
resourceLoaderOptions: {
|
|
503
725
|
noExtensions: true,
|
|
504
|
-
appendSystemPrompt:
|
|
726
|
+
appendSystemPrompt: [
|
|
727
|
+
...promptResources.appendSystemPromptPaths,
|
|
728
|
+
...(agentSystemPrompt.trim() ? [agentSystemPrompt] : []),
|
|
729
|
+
],
|
|
505
730
|
},
|
|
506
731
|
});
|
|
507
732
|
return { services, support };
|
|
@@ -556,17 +781,19 @@ export function seedChildSessionManager(
|
|
|
556
781
|
}
|
|
557
782
|
}
|
|
558
783
|
|
|
559
|
-
function buildCurrentTurnPrompt(agent: ManagedAgent, task: string): string {
|
|
784
|
+
export function buildCurrentTurnPrompt(agent: ManagedAgent, task: string): string {
|
|
560
785
|
const ids = new Set(agent.currentMailboxMessageIds ?? []);
|
|
561
786
|
const messages = agent.mailbox
|
|
562
787
|
.filter((message) => ids.has(message.id))
|
|
563
788
|
.slice(-20)
|
|
564
789
|
.map((message) => `From ${message.senderId}: ${redactPrivateText(message.content)}`)
|
|
565
790
|
.join("\n");
|
|
791
|
+
const base = messages
|
|
792
|
+
? `${redactPrivateText(task)}\n\nMailbox messages:\n${messages}`
|
|
793
|
+
: redactPrivateText(task);
|
|
794
|
+
const contracted = appendDelegationContract(base, agent.contract, DEFAULT_MAX_CONTEXT_BYTES);
|
|
566
795
|
return truncateUtf8(
|
|
567
|
-
|
|
568
|
-
? `${redactPrivateText(task)}\n\nMailbox messages:\n${messages}`
|
|
569
|
-
: redactPrivateText(task),
|
|
796
|
+
appendResultInstruction(contracted.text, agent.resultFormat),
|
|
570
797
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
571
798
|
).text;
|
|
572
799
|
}
|
|
@@ -590,6 +817,23 @@ function latestAssistant(messages: readonly unknown[]): {
|
|
|
590
817
|
return { output: "" };
|
|
591
818
|
}
|
|
592
819
|
|
|
820
|
+
function childEventType(event: unknown): string | undefined {
|
|
821
|
+
if (!event || typeof event !== "object" || Array.isArray(event)) return undefined;
|
|
822
|
+
const type = (event as { type?: unknown }).type;
|
|
823
|
+
return typeof type === "string" ? type : undefined;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function assistantToolCallCount(content: unknown): number {
|
|
827
|
+
if (!Array.isArray(content)) return 0;
|
|
828
|
+
return content.filter(
|
|
829
|
+
(part) =>
|
|
830
|
+
part &&
|
|
831
|
+
typeof part === "object" &&
|
|
832
|
+
!Array.isArray(part) &&
|
|
833
|
+
(part as { type?: unknown }).type === "toolCall",
|
|
834
|
+
).length;
|
|
835
|
+
}
|
|
836
|
+
|
|
593
837
|
function eventMessage(event: unknown): unknown {
|
|
594
838
|
if (!event || typeof event !== "object") return undefined;
|
|
595
839
|
const candidate = event as { type?: string; message?: unknown };
|
package/src/inspect-render.ts
CHANGED
|
@@ -24,6 +24,7 @@ export function renderInspectCall(args: Partial<SubagentInspectParams>, theme: T
|
|
|
24
24
|
if (args.agentScope) metadata.push(`[${args.agentScope}]`);
|
|
25
25
|
if (args.agent) metadata.push(`agent:${safeLine(args.agent, "", 256)}`);
|
|
26
26
|
if (args.agentId) metadata.push(`id:${safeLine(args.agentId, "", 256)}`);
|
|
27
|
+
if (args.workflowId) metadata.push(`workflow:${safeLine(args.workflowId, "", 256)}`);
|
|
27
28
|
if (args.includeClosed) metadata.push("include closed");
|
|
28
29
|
return new Text(toolHeader(theme, "subagent_inspect", action, metadata), 0, 0);
|
|
29
30
|
}
|
|
@@ -74,6 +75,23 @@ function renderAction(
|
|
|
74
75
|
if (!expanded) lines.push(expansionHint());
|
|
75
76
|
return lines.join("\n");
|
|
76
77
|
}
|
|
78
|
+
case "list_workflows":
|
|
79
|
+
return renderList(
|
|
80
|
+
"workflow",
|
|
81
|
+
recordList(details.workflows),
|
|
82
|
+
details,
|
|
83
|
+
expanded,
|
|
84
|
+
theme,
|
|
85
|
+
formatWorkflow,
|
|
86
|
+
);
|
|
87
|
+
case "get_workflow": {
|
|
88
|
+
const workflow = recordValue(details.workflow);
|
|
89
|
+
if (!workflow) return undefined;
|
|
90
|
+
return [
|
|
91
|
+
`${statusBadge(theme, "completed")} · workflow ${theme.fg("accent", safeLine(workflow.workflowId, "workflow", 256))}`,
|
|
92
|
+
formatWorkflow(workflow, theme, expanded),
|
|
93
|
+
].join("\n");
|
|
94
|
+
}
|
|
77
95
|
case "list_models":
|
|
78
96
|
return renderList(
|
|
79
97
|
"model",
|
|
@@ -84,6 +102,17 @@ function renderAction(
|
|
|
84
102
|
formatModel,
|
|
85
103
|
stringValue(details.source),
|
|
86
104
|
);
|
|
105
|
+
case "preview_context": {
|
|
106
|
+
const preview = recordValue(details.preview);
|
|
107
|
+
if (!preview) return undefined;
|
|
108
|
+
return [
|
|
109
|
+
`${statusBadge(theme, "completed")} · context ${theme.fg("accent", safeLine(preview.mode, "none", 128))}`,
|
|
110
|
+
theme.fg(
|
|
111
|
+
"dim",
|
|
112
|
+
`${numberValue(preview.turns)} turns · ${numberValue(preview.sourceCount)} sources · ${numberValue(preview.bytes)} bytes${preview.truncated === true ? " · truncated" : ""}`,
|
|
113
|
+
),
|
|
114
|
+
].join("\n");
|
|
115
|
+
}
|
|
87
116
|
case "status":
|
|
88
117
|
return renderStatus(details, expanded, theme);
|
|
89
118
|
case "diagnose":
|
|
@@ -146,17 +175,61 @@ function formatRun(run: Record<string, unknown>, theme: Theme, expanded: boolean
|
|
|
146
175
|
];
|
|
147
176
|
if (expanded) {
|
|
148
177
|
const thinking = stringValue(run.thinkingLevel);
|
|
178
|
+
const timeout = numberValue(run.currentTimeoutMs) || numberValue(run.timeoutMs);
|
|
179
|
+
const idleTimeout = numberValue(run.currentIdleTimeoutMs) || numberValue(run.idleTimeoutMs);
|
|
180
|
+
const maxTurns = numberValue(run.currentMaxTurns) || numberValue(run.maxTurns);
|
|
181
|
+
const maxToolCalls = numberValue(run.currentMaxToolCalls) || numberValue(run.maxToolCalls);
|
|
149
182
|
const task = safeBlock(run.currentTask, "", 2 * 1024).trim();
|
|
150
183
|
const error = safeBlock(run.error, "", 2 * 1024).trim();
|
|
151
184
|
lines.push(
|
|
152
|
-
` ${theme.fg("dim", `${numberValue(run.historyCount)} history · ${thinking ? `thinking:${safeLine(thinking, "", 128)} · ` : ""}${numberValue(run.children)} children`)}`,
|
|
185
|
+
` ${theme.fg("dim", `${numberValue(run.historyCount)} history · ${thinking ? `thinking:${safeLine(thinking, "", 128)} · ` : ""}${timeout ? `timeout:${timeout}ms · ` : ""}${idleTimeout ? `idle:${idleTimeout}ms · ` : ""}${maxTurns ? `turns:${maxTurns} · ` : ""}${maxToolCalls ? `tools:${maxToolCalls} · ` : ""}${numberValue(run.children)} children`)}`,
|
|
153
186
|
);
|
|
187
|
+
const context = recordValue(run.context);
|
|
188
|
+
const telemetry = recordValue(run.telemetry);
|
|
189
|
+
if (context) {
|
|
190
|
+
lines.push(
|
|
191
|
+
` ${theme.fg("dim", `context: ${numberValue(context.turns)} turns · ${numberValue(context.sources)} sources · ${numberValue(context.bytes)} bytes${context.truncated === true ? " · truncated" : ""}`)}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (telemetry) {
|
|
195
|
+
lines.push(
|
|
196
|
+
` ${theme.fg("dim", `transport: ${safeLine(telemetry.transport, "unknown", 128)} · phase:${safeLine(telemetry.phase, "unknown", 128)}${stringValue(telemetry.protocol) ? ` · ${safeLine(telemetry.protocol, "", 128)}` : ""}`)}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
154
199
|
if (task) lines.push(` ${theme.fg("dim", `task: ${task}`)}`);
|
|
155
200
|
if (error) lines.push(` ${theme.fg("error", `error: ${error}`)}`);
|
|
156
201
|
}
|
|
157
202
|
return lines.join("\n");
|
|
158
203
|
}
|
|
159
204
|
|
|
205
|
+
function formatWorkflow(
|
|
206
|
+
workflow: Record<string, unknown>,
|
|
207
|
+
theme: Theme,
|
|
208
|
+
expanded: boolean,
|
|
209
|
+
): string {
|
|
210
|
+
const id = safeLine(workflow.workflowId, "workflow", 256);
|
|
211
|
+
const states = recordValue(workflow.states);
|
|
212
|
+
const stateText = states
|
|
213
|
+
? Object.entries(states)
|
|
214
|
+
.map(([state, count]) => `${safeLine(state, "unknown", 128)}:${numberValue(count)}`)
|
|
215
|
+
.join(" · ")
|
|
216
|
+
: "";
|
|
217
|
+
const lines = [
|
|
218
|
+
`${theme.fg("muted", "• ")}${theme.fg("accent", id)} ${theme.fg("muted", `${numberValue(workflow.itemCount)} items · generation:${numberValue(workflow.generation)}`)}`,
|
|
219
|
+
];
|
|
220
|
+
if (stateText) lines.push(` ${theme.fg("dim", stateText)}`);
|
|
221
|
+
if (expanded) {
|
|
222
|
+
for (const item of recordList(workflow.items).slice(0, COLLAPSED_LIST_LIMIT)) {
|
|
223
|
+
lines.push(
|
|
224
|
+
` ${theme.fg("muted", "- ")}${theme.fg("toolOutput", safeLine(item.id, "task", 256))} ${theme.fg("dim", safeLine(item.state, "unknown", 128))}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const omitted = numberValue(workflow.omittedItems);
|
|
228
|
+
if (omitted > 0) lines.push(theme.fg("muted", ` … ${omitted} tasks omitted`));
|
|
229
|
+
}
|
|
230
|
+
return lines.join("\n");
|
|
231
|
+
}
|
|
232
|
+
|
|
160
233
|
function formatModel(model: Record<string, unknown>, theme: Theme, expanded: boolean): string {
|
|
161
234
|
const identity = `${safeLine(model.provider, "provider", 256)}/${safeLine(model.id, "model", 256)}`;
|
|
162
235
|
const current = booleanValue(model.current) ? theme.fg("success", " · current") : "";
|
|
@@ -181,11 +254,15 @@ function renderStatus(
|
|
|
181
254
|
const status = recordValue(details.status);
|
|
182
255
|
const stateful = recordValue(status?.stateful);
|
|
183
256
|
if (!status || !stateful) return undefined;
|
|
257
|
+
const currentLimits = recordValue(status.statefulLimits) ?? recordValue(stateful.limits);
|
|
184
258
|
const lines = [
|
|
185
259
|
`${statusBadge(theme, "completed")} · runtime status`,
|
|
186
260
|
`${theme.fg("muted", "workflow: ")}${theme.fg("accent", safeLine(status.workflow, "unknown", 128))} · ${numberValue(stateful.activeAgents)} active · ${numberValue(stateful.retainedAgents)} retained`,
|
|
187
261
|
`${theme.fg("muted", "stateful: ")}${stateful.initialized === true ? "initialized" : "not initialized"} · resources: ${safeLine(status.consultResources, "unknown", 128)}`,
|
|
188
262
|
];
|
|
263
|
+
if (currentLimits) {
|
|
264
|
+
lines.push(`${theme.fg("muted", "limits: ")}${formatDetachedLimits(currentLimits)}`);
|
|
265
|
+
}
|
|
189
266
|
if (expanded) {
|
|
190
267
|
const delivery = stringValue(stateful.completionDelivery);
|
|
191
268
|
const transport = stringValue(stateful.transport);
|
|
@@ -202,10 +279,33 @@ function renderStatus(
|
|
|
202
279
|
),
|
|
203
280
|
);
|
|
204
281
|
}
|
|
282
|
+
const configured = recordValue(status.configuredStatefulLimits);
|
|
283
|
+
const sources = recordValue(status.configuredStatefulLimitSources);
|
|
284
|
+
if (configured) {
|
|
285
|
+
lines.push(theme.fg("dim", `configured: ${formatDetachedLimits(configured, sources)}`));
|
|
286
|
+
}
|
|
205
287
|
} else lines.push(expansionHint());
|
|
206
288
|
return lines.join("\n");
|
|
207
289
|
}
|
|
208
290
|
|
|
291
|
+
function formatDetachedLimits(
|
|
292
|
+
limits: Record<string, unknown>,
|
|
293
|
+
sources?: Record<string, unknown>,
|
|
294
|
+
): string {
|
|
295
|
+
return [
|
|
296
|
+
["agents", "maxAgents"],
|
|
297
|
+
["active", "maxActiveTurns"],
|
|
298
|
+
["children", "maxChildrenPerAgent"],
|
|
299
|
+
["depth", "maxDepth"],
|
|
300
|
+
["stored", "maxStoredAgents"],
|
|
301
|
+
]
|
|
302
|
+
.map(([label, field]) => {
|
|
303
|
+
const source = stringValue(sources?.[field]);
|
|
304
|
+
return `${label}:${numberValue(limits[field])}${source ? ` (${safeLine(source, "", 64)})` : ""}`;
|
|
305
|
+
})
|
|
306
|
+
.join(" · ");
|
|
307
|
+
}
|
|
308
|
+
|
|
209
309
|
function renderDiagnose(details: Record<string, unknown>, expanded: boolean, theme: Theme): string {
|
|
210
310
|
const checks = recordList(details.checks);
|
|
211
311
|
const hasFail = checks.some((check) => check.status === "fail");
|