@esso0428/pi-subagents 0.17.5 → 0.17.7
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 +14 -0
- package/CONTRIBUTING.md +4 -0
- package/README.md +1 -1
- package/dist/abortable.d.ts +13 -0
- package/dist/abortable.d.ts.map +1 -0
- package/dist/abortable.js +43 -0
- package/dist/abortable.js.map +1 -0
- package/dist/agent-color.d.ts +36 -0
- package/dist/agent-color.d.ts.map +1 -0
- package/dist/agent-color.js +124 -0
- package/dist/agent-color.js.map +1 -0
- package/dist/agent-file-toggle.d.ts +126 -0
- package/dist/agent-file-toggle.d.ts.map +1 -0
- package/dist/agent-file-toggle.js +259 -0
- package/dist/agent-file-toggle.js.map +1 -0
- package/dist/agent-history.d.ts +4 -0
- package/dist/agent-history.d.ts.map +1 -1
- package/dist/agent-history.js +47 -1
- package/dist/agent-history.js.map +1 -1
- package/dist/agent-manager.d.ts +370 -56
- package/dist/agent-manager.d.ts.map +1 -1
- package/dist/agent-manager.js +1123 -409
- package/dist/agent-manager.js.map +1 -1
- package/dist/agent-runner.d.ts +100 -10
- package/dist/agent-runner.d.ts.map +1 -1
- package/dist/agent-runner.js +166 -21
- package/dist/agent-runner.js.map +1 -1
- package/dist/agent-types.d.ts +57 -5
- package/dist/agent-types.d.ts.map +1 -1
- package/dist/agent-types.js +164 -32
- package/dist/agent-types.js.map +1 -1
- package/dist/child-context.d.ts +3 -0
- package/dist/child-context.d.ts.map +1 -0
- package/dist/child-context.js +13 -0
- package/dist/child-context.js.map +1 -0
- package/dist/cross-extension-rpc.d.ts +23 -3
- package/dist/cross-extension-rpc.d.ts.map +1 -1
- package/dist/cross-extension-rpc.js +79 -17
- package/dist/cross-extension-rpc.js.map +1 -1
- package/dist/custom-agents.d.ts +38 -1
- package/dist/custom-agents.d.ts.map +1 -1
- package/dist/custom-agents.js +164 -12
- package/dist/custom-agents.js.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1908 -495
- package/dist/index.js.map +1 -1
- package/dist/invocation-config.d.ts +87 -2
- package/dist/invocation-config.d.ts.map +1 -1
- package/dist/invocation-config.js +71 -3
- package/dist/invocation-config.js.map +1 -1
- package/dist/mention-clone.d.ts +88 -0
- package/dist/mention-clone.d.ts.map +1 -0
- package/dist/mention-clone.js +154 -0
- package/dist/mention-clone.js.map +1 -0
- package/dist/mention.d.ts +82 -0
- package/dist/mention.d.ts.map +1 -0
- package/dist/mention.js +132 -0
- package/dist/mention.js.map +1 -0
- package/dist/model-resolver.d.ts +17 -0
- package/dist/model-resolver.d.ts.map +1 -1
- package/dist/model-resolver.js +15 -0
- package/dist/model-resolver.js.map +1 -1
- package/dist/model-scope.d.ts +50 -0
- package/dist/model-scope.d.ts.map +1 -0
- package/dist/model-scope.js +49 -0
- package/dist/model-scope.js.map +1 -0
- package/dist/nested-tools.d.ts +57 -0
- package/dist/nested-tools.d.ts.map +1 -0
- package/dist/nested-tools.js +301 -0
- package/dist/nested-tools.js.map +1 -0
- package/dist/output-file.d.ts +22 -3
- package/dist/output-file.d.ts.map +1 -1
- package/dist/output-file.js +58 -7
- package/dist/output-file.js.map +1 -1
- package/dist/prompts.d.ts +23 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +20 -2
- package/dist/prompts.js.map +1 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +36 -15
- package/dist/schedule.js.map +1 -1
- package/dist/settings.d.ts +228 -2
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +94 -0
- package/dist/settings.js.map +1 -1
- package/dist/status-note.d.ts +49 -1
- package/dist/status-note.d.ts.map +1 -1
- package/dist/status-note.js +62 -1
- package/dist/status-note.js.map +1 -1
- package/dist/structured-output.d.ts +62 -0
- package/dist/structured-output.d.ts.map +1 -0
- package/dist/structured-output.js +113 -0
- package/dist/structured-output.js.map +1 -0
- package/dist/types.d.ts +176 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/ui/agent-mention.d.ts +83 -0
- package/dist/ui/agent-mention.d.ts.map +1 -0
- package/dist/ui/agent-mention.js +188 -0
- package/dist/ui/agent-mention.js.map +1 -0
- package/dist/ui/agent-widget.d.ts +96 -75
- package/dist/ui/agent-widget.d.ts.map +1 -1
- package/dist/ui/agent-widget.js +397 -420
- package/dist/ui/agent-widget.js.map +1 -1
- package/dist/ui/conversation-blocks.d.ts.map +1 -1
- package/dist/ui/conversation-blocks.js +6 -0
- package/dist/ui/conversation-blocks.js.map +1 -1
- package/dist/ui/conversation-timeline.d.ts +10 -2
- package/dist/ui/conversation-timeline.d.ts.map +1 -1
- package/dist/ui/conversation-timeline.js +130 -23
- package/dist/ui/conversation-timeline.js.map +1 -1
- package/dist/ui/conversation-viewer.d.ts +20 -5
- package/dist/ui/conversation-viewer.d.ts.map +1 -1
- package/dist/ui/conversation-viewer.js +274 -73
- package/dist/ui/conversation-viewer.js.map +1 -1
- package/dist/ui/fleet-list.d.ts +198 -0
- package/dist/ui/fleet-list.d.ts.map +1 -0
- package/dist/ui/fleet-list.js +487 -0
- package/dist/ui/fleet-list.js.map +1 -0
- package/dist/ui/schedule-menu.d.ts.map +1 -1
- package/dist/ui/schedule-menu.js +6 -7
- package/dist/ui/schedule-menu.js.map +1 -1
- package/dist/ui/select-item.d.ts +28 -0
- package/dist/ui/select-item.d.ts.map +1 -0
- package/dist/ui/select-item.js +35 -0
- package/dist/ui/select-item.js.map +1 -0
- package/dist/ui/workflow-card.d.ts +176 -0
- package/dist/ui/workflow-card.d.ts.map +1 -0
- package/dist/ui/workflow-card.js +333 -0
- package/dist/ui/workflow-card.js.map +1 -0
- package/dist/ui/workflow-dialog.d.ts +306 -0
- package/dist/ui/workflow-dialog.d.ts.map +1 -0
- package/dist/ui/workflow-dialog.js +844 -0
- package/dist/ui/workflow-dialog.js.map +1 -0
- package/dist/ui/workflow-menu.d.ts +61 -0
- package/dist/ui/workflow-menu.d.ts.map +1 -0
- package/dist/ui/workflow-menu.js +148 -0
- package/dist/ui/workflow-menu.js.map +1 -0
- package/dist/usage.d.ts +86 -1
- package/dist/usage.d.ts.map +1 -1
- package/dist/usage.js +72 -1
- package/dist/usage.js.map +1 -1
- package/dist/workflow/collisions.d.ts +96 -0
- package/dist/workflow/collisions.d.ts.map +1 -0
- package/dist/workflow/collisions.js +89 -0
- package/dist/workflow/collisions.js.map +1 -0
- package/dist/workflow/entry.d.ts +33 -0
- package/dist/workflow/entry.d.ts.map +1 -0
- package/dist/workflow/entry.js +30 -0
- package/dist/workflow/entry.js.map +1 -0
- package/dist/workflow/host.d.ts +63 -0
- package/dist/workflow/host.d.ts.map +1 -0
- package/dist/workflow/host.js +363 -0
- package/dist/workflow/host.js.map +1 -0
- package/dist/workflow/journal.d.ts +98 -0
- package/dist/workflow/journal.d.ts.map +1 -0
- package/dist/workflow/journal.js +121 -0
- package/dist/workflow/journal.js.map +1 -0
- package/dist/workflow/json-schema.d.ts +52 -0
- package/dist/workflow/json-schema.d.ts.map +1 -0
- package/dist/workflow/json-schema.js +112 -0
- package/dist/workflow/json-schema.js.map +1 -0
- package/dist/workflow/meta.d.ts +68 -0
- package/dist/workflow/meta.d.ts.map +1 -0
- package/dist/workflow/meta.js +318 -0
- package/dist/workflow/meta.js.map +1 -0
- package/dist/workflow/progress.d.ts +225 -0
- package/dist/workflow/progress.d.ts.map +1 -0
- package/dist/workflow/progress.js +362 -0
- package/dist/workflow/progress.js.map +1 -0
- package/dist/workflow/runtime.d.ts +335 -0
- package/dist/workflow/runtime.d.ts.map +1 -0
- package/dist/workflow/runtime.js +831 -0
- package/dist/workflow/runtime.js.map +1 -0
- package/dist/workflow/saved.d.ts +91 -0
- package/dist/workflow/saved.d.ts.map +1 -0
- package/dist/workflow/saved.js +204 -0
- package/dist/workflow/saved.js.map +1 -0
- package/dist/workflow/task.d.ts +137 -0
- package/dist/workflow/task.d.ts.map +1 -0
- package/dist/workflow/task.js +208 -0
- package/dist/workflow/task.js.map +1 -0
- package/dist/workflow/tool-description.d.ts +39 -0
- package/dist/workflow/tool-description.d.ts.map +1 -0
- package/dist/workflow/tool-description.js +200 -0
- package/dist/workflow/tool-description.js.map +1 -0
- package/dist/workflow/worker-source.d.ts +48 -0
- package/dist/workflow/worker-source.d.ts.map +1 -0
- package/dist/workflow/worker-source.js +779 -0
- package/dist/workflow/worker-source.js.map +1 -0
- package/dist/worktree.d.ts +10 -3
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +58 -54
- package/dist/worktree.js.map +1 -1
- package/dist/xml.d.ts +11 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +13 -0
- package/dist/xml.js.map +1 -0
- package/docs/rpc.md +183 -0
- package/docs/superpowers/plans/2026-09-30-conversation-viewer-scrollbar.md +216 -0
- package/docs/superpowers/plans/2026-09-30-upstream-event-workflow-partial-history.md +195 -0
- package/docs/superpowers/specs/2026-09-30-conversation-viewer-scrollbar-design.md +82 -0
- package/docs/superpowers/specs/2026-09-30-upstream-event-workflow-partial-history-design.md +49 -0
- package/docs/workflows.md +437 -0
- package/examples/agent-tool-description.md +7 -7
- package/examples/workflows/compose.js +51 -0
- package/examples/workflows/fan-out-audit.js +47 -0
- package/examples/workflows/gated-fix.js +60 -0
- package/examples/workflows/lib/count-child.js +27 -0
- package/examples/workflows/review-panel.js +63 -0
- package/examples/workflows/structured-findings.js +78 -0
- package/package.json +1 -1
- package/src/abortable.ts +43 -0
- package/src/agent-color.ts +161 -0
- package/src/agent-file-toggle.ts +269 -0
- package/src/agent-history.ts +54 -2
- package/src/agent-manager.ts +1263 -402
- package/src/agent-runner.ts +251 -27
- package/src/agent-types.ts +188 -32
- package/src/child-context.ts +15 -0
- package/src/cross-extension-rpc.ts +96 -20
- package/src/custom-agents.ts +170 -13
- package/src/index.ts +2024 -537
- package/src/invocation-config.ts +118 -3
- package/src/mention-clone.ts +196 -0
- package/src/mention.ts +141 -0
- package/src/model-resolver.ts +18 -0
- package/src/model-scope.ts +70 -0
- package/src/nested-tools.ts +424 -0
- package/src/output-file.ts +61 -6
- package/src/prompts.ts +45 -2
- package/src/schedule.ts +35 -14
- package/src/settings.ts +312 -2
- package/src/status-note.ts +66 -1
- package/src/structured-output.ts +130 -0
- package/src/types.ts +177 -10
- package/src/ui/agent-mention.ts +216 -0
- package/src/ui/agent-widget.ts +389 -441
- package/src/ui/conversation-blocks.ts +6 -0
- package/src/ui/conversation-timeline.ts +139 -25
- package/src/ui/conversation-viewer.ts +284 -69
- package/src/ui/fleet-list.ts +558 -0
- package/src/ui/schedule-menu.ts +9 -8
- package/src/ui/select-item.ts +45 -0
- package/src/ui/workflow-card.ts +470 -0
- package/src/ui/workflow-dialog.ts +1115 -0
- package/src/ui/workflow-menu.ts +193 -0
- package/src/usage.ts +109 -2
- package/src/workflow/collisions.ts +123 -0
- package/src/workflow/entry.ts +47 -0
- package/src/workflow/host.ts +403 -0
- package/src/workflow/journal.ts +164 -0
- package/src/workflow/json-schema.ts +128 -0
- package/src/workflow/meta.ts +325 -0
- package/src/workflow/progress.ts +550 -0
- package/src/workflow/runtime.ts +1219 -0
- package/src/workflow/saved.ts +217 -0
- package/src/workflow/task.ts +302 -0
- package/src/workflow/tool-description.ts +200 -0
- package/src/workflow/worker-source.ts +781 -0
- package/src/worktree.ts +69 -55
- package/src/xml.ts +13 -0
- package/vitest.config.ts +0 -18
package/src/agent-runner.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { readFileSync } from "node:fs";
|
|
|
6
6
|
import { homedir } from "node:os";
|
|
7
7
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
8
8
|
import type { Model } from "@earendil-works/pi-ai";
|
|
9
|
-
import type { ExtensionContext, LoadExtensionsResult
|
|
9
|
+
import type { ExtensionContext, LoadExtensionsResult } from "@earendil-works/pi-coding-agent";
|
|
10
10
|
import {
|
|
11
11
|
type AgentSession,
|
|
12
12
|
type AgentSessionEvent,
|
|
@@ -18,14 +18,18 @@ import {
|
|
|
18
18
|
SettingsManager,
|
|
19
19
|
} from "@earendil-works/pi-coding-agent";
|
|
20
20
|
import { BUILTIN_TOOL_NAMES, getAgentConfig, getConfig, getMemoryToolNames, getReadOnlyMemoryToolNames, getToolNamesForType } from "./agent-types.js";
|
|
21
|
+
import { runInChildSessionContext } from "./child-context.js";
|
|
21
22
|
import { buildParentContext, extractText } from "./context.js";
|
|
22
23
|
import { DEFAULT_AGENTS } from "./default-agents.js";
|
|
23
24
|
import { detectEnv } from "./env.js";
|
|
24
25
|
import { buildMemoryBlock, buildReadOnlyMemoryBlock } from "./memory.js";
|
|
26
|
+
import { createNestedSubagentTools, getMaxSubagentDepth, type NestedAgentManager } from "./nested-tools.js";
|
|
25
27
|
import { buildAgentPrompt, type PromptExtras } from "./prompts.js";
|
|
26
28
|
import { preloadSkills } from "./skill-loader.js";
|
|
29
|
+
import { createStructuredCapture, createStructuredOutputTool, structuredRetryPrompt } from "./structured-output.js";
|
|
27
30
|
import type { SubagentType, ThinkingLevel } from "./types.js";
|
|
28
|
-
import {
|
|
31
|
+
import type { LifetimeUsage } from "./usage.js";
|
|
32
|
+
import type { CompiledSchema } from "./workflow/json-schema.js";
|
|
29
33
|
|
|
30
34
|
/**
|
|
31
35
|
* Tool names registered by THIS extension. Single source of truth so the
|
|
@@ -35,6 +39,7 @@ import { createTrackedWriteTool } from "./write-execution.js";
|
|
|
35
39
|
*/
|
|
36
40
|
export const SUBAGENT_TOOL_NAMES = {
|
|
37
41
|
AGENT: "Agent",
|
|
42
|
+
WORKFLOW: "SubagentWorkflow",
|
|
38
43
|
GET_RESULT: "get_subagent_result",
|
|
39
44
|
STEER: "steer_subagent",
|
|
40
45
|
} as const;
|
|
@@ -231,9 +236,18 @@ export function installExtensionToolScope(
|
|
|
231
236
|
disallowedSet: Set<string> | undefined;
|
|
232
237
|
extNames: Set<string>;
|
|
233
238
|
narrowing: Map<string, Set<string>>;
|
|
239
|
+
/**
|
|
240
|
+
* Injected `customTools` to keep active regardless of the built-in list.
|
|
241
|
+
*
|
|
242
|
+
* Two kinds arrive here and they are blocked for different reasons: opt-in
|
|
243
|
+
* nested-delegation tools share EXCLUDED_TOOL_NAMES' names, and
|
|
244
|
+
* StructuredOutput is simply not a built-in, so neither survives a `keep`
|
|
245
|
+
* seeded from `toolNames`.
|
|
246
|
+
*/
|
|
247
|
+
readmitToolNames: Set<string>;
|
|
234
248
|
},
|
|
235
249
|
): void {
|
|
236
|
-
const { loader, toolNames, disallowedSet, extNames, narrowing } = ctx;
|
|
250
|
+
const { loader, toolNames, disallowedSet, extNames, narrowing, readmitToolNames } = ctx;
|
|
237
251
|
|
|
238
252
|
// The names allowed right now. Mirrors the `ext:` opt-in flip: when any `ext:`
|
|
239
253
|
// selector is present, extension tools become an explicit allowlist — a loaded
|
|
@@ -255,6 +269,11 @@ export function installExtensionToolScope(
|
|
|
255
269
|
}
|
|
256
270
|
}
|
|
257
271
|
for (const name of EXCLUDED_TOOL_NAMES) keep.delete(name);
|
|
272
|
+
// Injected tools are legitimately active for this agent — re-admit them so
|
|
273
|
+
// the renarrow keeps them in the active set and beforeToolCall doesn't
|
|
274
|
+
// block them. Already vetted against `disallowed_tools` by the caller,
|
|
275
|
+
// which is the only place that knows which kind may be taken back.
|
|
276
|
+
for (const name of readmitToolNames) keep.add(name);
|
|
258
277
|
return keep;
|
|
259
278
|
};
|
|
260
279
|
|
|
@@ -303,6 +322,33 @@ export function getDefaultMaxTurns(): number | undefined { return defaultMaxTurn
|
|
|
303
322
|
/** Set the default max turns value. undefined or 0 = unlimited, otherwise minimum 1. */
|
|
304
323
|
export function setDefaultMaxTurns(n: number | undefined): void { defaultMaxTurns = normalizeMaxTurns(n); }
|
|
305
324
|
|
|
325
|
+
/**
|
|
326
|
+
* The turn limit a run of `type` will actually enforce: an explicit value if the
|
|
327
|
+
* caller supplied one, else the agent's own `max_turns`, else the project
|
|
328
|
+
* default. `undefined` = unlimited.
|
|
329
|
+
*
|
|
330
|
+
* Exported because the widget's turn counter (`↻3≤20`) has to predict this
|
|
331
|
+
* before the run starts, and a second copy of the expression would drift from
|
|
332
|
+
* the one below that enforces it.
|
|
333
|
+
*/
|
|
334
|
+
export function resolveEffectiveMaxTurns(type: string, explicit?: number): number | undefined {
|
|
335
|
+
return normalizeMaxTurns(explicit ?? getAgentConfig(type)?.maxTurns ?? defaultMaxTurns);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Project default for `persist_session`, from the `rememberAgents` setting.
|
|
340
|
+
* On by default: a persisted session is what lets `@handle` reopen an agent's
|
|
341
|
+
* conversation after its record has been evicted, which is the whole point of
|
|
342
|
+
* addressing an agent by a name that outlives one run. Per-agent frontmatter
|
|
343
|
+
* still overrides it in both directions.
|
|
344
|
+
*/
|
|
345
|
+
let rememberAgents = true;
|
|
346
|
+
|
|
347
|
+
/** Whether subagent sessions are persisted by default. */
|
|
348
|
+
export function getRememberAgents(): boolean { return rememberAgents; }
|
|
349
|
+
/** Set whether subagent sessions are persisted by default. */
|
|
350
|
+
export function setRememberAgents(b: boolean): void { rememberAgents = b; }
|
|
351
|
+
|
|
306
352
|
/** Additional turns allowed after the soft limit steer message. */
|
|
307
353
|
let graceTurns = 5;
|
|
308
354
|
|
|
@@ -315,7 +361,7 @@ export function setGraceTurns(n: number): void { graceTurns = Math.max(1, n); }
|
|
|
315
361
|
* Try to find the right model for an agent type.
|
|
316
362
|
* Priority: explicit option > config.model > parent model.
|
|
317
363
|
*/
|
|
318
|
-
function resolveDefaultModel(
|
|
364
|
+
export function resolveDefaultModel(
|
|
319
365
|
parentModel: Model<any> | undefined,
|
|
320
366
|
registry: { find(provider: string, modelId: string): Model<any> | undefined; getAvailable?(): Model<any>[] },
|
|
321
367
|
configModel?: string,
|
|
@@ -359,8 +405,39 @@ export interface RunOptions {
|
|
|
359
405
|
isolated?: boolean;
|
|
360
406
|
inheritContext?: boolean;
|
|
361
407
|
thinkingLevel?: ThinkingLevel;
|
|
408
|
+
/**
|
|
409
|
+
* Reopen this pi session file rather than starting an empty conversation.
|
|
410
|
+
* `createAgentSession` seeds itself from whatever its SessionManager holds,
|
|
411
|
+
* so pointing it at an existing file rehydrates that agent's history and the
|
|
412
|
+
* prompt continues it. Everything else — tools, model, system prompt, turn
|
|
413
|
+
* caps — is still resolved from the agent type, so the continuation runs
|
|
414
|
+
* under the type's *current* definition, not the one the original run used.
|
|
415
|
+
*/
|
|
416
|
+
resumeSessionFile?: string;
|
|
417
|
+
/**
|
|
418
|
+
* True when another agent spawned this one. Only top-level agents get a
|
|
419
|
+
* handle, so only they can be reopened by name — which is the whole reason
|
|
420
|
+
* `rememberAgents` persists a session at all. A nested run's transcript would
|
|
421
|
+
* be unreachable by anything, so it stays in memory unless its own
|
|
422
|
+
* frontmatter asks otherwise.
|
|
423
|
+
*/
|
|
424
|
+
nested?: boolean;
|
|
425
|
+
/**
|
|
426
|
+
* True when a workflow run spawned this agent. Its final text is the value
|
|
427
|
+
* `agent()` resolves to rather than a report a person reads, and the prompt
|
|
428
|
+
* says so — but only when `structuredOutput` is unset, since that child
|
|
429
|
+
* already has a `StructuredOutput` tool to answer through and two competing
|
|
430
|
+
* "this is how you return your answer" instructions is worse than one.
|
|
431
|
+
*/
|
|
432
|
+
workflow?: boolean;
|
|
362
433
|
/** Override working directory (e.g. for worktree isolation). */
|
|
363
434
|
cwd?: string;
|
|
435
|
+
/**
|
|
436
|
+
* Directory the worktree copy was created from. Set only when `cwd` points
|
|
437
|
+
* into a worktree — the prompt then tells the agent to stay in the copy
|
|
438
|
+
* instead of following the inherited parent prompt back to the main tree.
|
|
439
|
+
*/
|
|
440
|
+
worktreeBase?: string;
|
|
364
441
|
/**
|
|
365
442
|
* Where .pi config is discovered (project extensions, skills, pi settings,
|
|
366
443
|
* agent memory). Default: same as the working directory. The manager sets
|
|
@@ -386,13 +463,33 @@ export interface RunOptions {
|
|
|
386
463
|
* Called once per assistant message_end with that message's usage delta.
|
|
387
464
|
* Lets callers maintain a lifetime accumulator that survives compaction
|
|
388
465
|
* (which replaces session.state.messages and resets stats-derived sums).
|
|
466
|
+
*
|
|
467
|
+
* `cost` is pi's own `usage.cost.total` for that message — priced from the
|
|
468
|
+
* model's rates, so it is 0 (not missing) for a model pi has no pricing for.
|
|
469
|
+
* We never price anything ourselves; every dollar figure this extension shows
|
|
470
|
+
* or reports traces back to this field.
|
|
389
471
|
*/
|
|
390
|
-
onAssistantUsage?: (usage:
|
|
472
|
+
onAssistantUsage?: (usage: LifetimeUsage) => void;
|
|
391
473
|
/**
|
|
392
474
|
* Called when the session successfully compacts. `tokensBefore` is upstream's
|
|
393
475
|
* pre-compaction context size estimate. Aborted compactions don't fire.
|
|
394
476
|
*/
|
|
395
477
|
onCompaction?: (info: { reason: "manual" | "threshold" | "overflow"; tokensBefore: number }) => void;
|
|
478
|
+
/**
|
|
479
|
+
* Make this child report through a `StructuredOutput` tool built from this
|
|
480
|
+
* schema, and put the validated payload on {@link RunResult.structuredJson}.
|
|
481
|
+
*
|
|
482
|
+
* Already compiled by the caller, so a schema this runtime cannot validate
|
|
483
|
+
* fails at the call that wrote it rather than inside the child.
|
|
484
|
+
*/
|
|
485
|
+
structuredOutput?: CompiledSchema;
|
|
486
|
+
/** Runtime bridge for opt-in child-safe nested delegation. */
|
|
487
|
+
nestedRuntime?: {
|
|
488
|
+
manager: NestedAgentManager;
|
|
489
|
+
parentAgentId: string;
|
|
490
|
+
depth: number;
|
|
491
|
+
maxSubagentDepth?: number;
|
|
492
|
+
};
|
|
396
493
|
}
|
|
397
494
|
|
|
398
495
|
export interface RunResult {
|
|
@@ -412,6 +509,18 @@ export interface RunResult {
|
|
|
412
509
|
* stop that produced text (a legitimate truncated answer).
|
|
413
510
|
*/
|
|
414
511
|
failure?: string;
|
|
512
|
+
/**
|
|
513
|
+
* The validated `StructuredOutput` payload as canonical JSON, when the caller
|
|
514
|
+
* asked for a schema and the child produced one.
|
|
515
|
+
*
|
|
516
|
+
* Deliberately not folded into {@link responseText}: `record.result` picks up
|
|
517
|
+
* a worktree branch note on the way out, which would leave the caller with
|
|
518
|
+
* unparseable JSON, and merging the two would make "produced structured
|
|
519
|
+
* output" indistinguishable from "happened to answer in JSON".
|
|
520
|
+
*/
|
|
521
|
+
structuredJson?: string;
|
|
522
|
+
/** Whether the extra structured-output prompt had to be sent. */
|
|
523
|
+
structuredRetried?: boolean;
|
|
415
524
|
}
|
|
416
525
|
|
|
417
526
|
/**
|
|
@@ -520,6 +629,8 @@ export async function runAgent(
|
|
|
520
629
|
|
|
521
630
|
// Build prompt extras (memory, skill preloading)
|
|
522
631
|
const extras: PromptExtras = {};
|
|
632
|
+
if (options.worktreeBase) extras.worktreeBase = options.worktreeBase;
|
|
633
|
+
if (options.workflow && !options.structuredOutput) extras.workflowChild = true;
|
|
523
634
|
|
|
524
635
|
// Resolve extensions/skills: isolated overrides to false
|
|
525
636
|
const extensions = options.isolated ? false : config.extensions;
|
|
@@ -646,7 +757,7 @@ export async function runAgent(
|
|
|
646
757
|
systemPromptOverride: () => systemPrompt,
|
|
647
758
|
appendSystemPromptOverride: () => [],
|
|
648
759
|
});
|
|
649
|
-
await loader.reload();
|
|
760
|
+
await runInChildSessionContext(() => loader.reload());
|
|
650
761
|
|
|
651
762
|
// Plain entries in `tools:` are expected to be built-in names (extension tools
|
|
652
763
|
// go through `ext:`), so an unknown name there is unambiguously a typo. Previously
|
|
@@ -729,6 +840,53 @@ export async function runAgent(
|
|
|
729
840
|
? new Set(agentConfig.disallowedTools)
|
|
730
841
|
: undefined;
|
|
731
842
|
|
|
843
|
+
// Nested delegation tools (opt-in, ownership-scoped). Empty unless the agent
|
|
844
|
+
// set `allowed_subagents` and a nestedRuntime was provided — and never when
|
|
845
|
+
// isolated. Their names collide with EXCLUDED_TOOL_NAMES by design, so the
|
|
846
|
+
// scoping below re-admits them explicitly (registry deny + active-set narrow).
|
|
847
|
+
const effectiveMaxDepth = options.nestedRuntime?.maxSubagentDepth ?? getMaxSubagentDepth();
|
|
848
|
+
// At (or past) the cap this agent can never spawn, so it can never own a child
|
|
849
|
+
// to fetch from or steer either — inject nothing rather than three tools whose
|
|
850
|
+
// every call is an error. This is also what makes `maxSubagentDepth` 0/1 mean
|
|
851
|
+
// "nesting off" instead of "nesting always fails".
|
|
852
|
+
const nestedRuntime = options.nestedRuntime && options.nestedRuntime.depth < effectiveMaxDepth
|
|
853
|
+
? options.nestedRuntime
|
|
854
|
+
: undefined;
|
|
855
|
+
const nestedTools = agentConfig?.allowedSubagents && nestedRuntime && !options.isolated
|
|
856
|
+
? createNestedSubagentTools({
|
|
857
|
+
manager: nestedRuntime.manager,
|
|
858
|
+
pi: options.pi,
|
|
859
|
+
parentAgentId: nestedRuntime.parentAgentId,
|
|
860
|
+
depth: nestedRuntime.depth,
|
|
861
|
+
maxSubagentDepth: effectiveMaxDepth,
|
|
862
|
+
allowedSubagents: agentConfig.allowedSubagents,
|
|
863
|
+
configCwd,
|
|
864
|
+
})
|
|
865
|
+
: [];
|
|
866
|
+
const nestedToolNames = new Set(nestedTools.map(tool => tool.name));
|
|
867
|
+
|
|
868
|
+
// The `agent({ schema })` contract: this child reports its answer by calling
|
|
869
|
+
// StructuredOutput, and `structuredJson` below is what the caller reads. The
|
|
870
|
+
// schema was already compiled by whoever asked for it, so a bad one failed
|
|
871
|
+
// before any of this ran.
|
|
872
|
+
const structuredCapture = options.structuredOutput ? createStructuredCapture() : undefined;
|
|
873
|
+
const structuredTools = options.structuredOutput && structuredCapture
|
|
874
|
+
? [createStructuredOutputTool(options.structuredOutput, structuredCapture)]
|
|
875
|
+
: [];
|
|
876
|
+
const structuredToolNames = new Set(structuredTools.map(tool => tool.name));
|
|
877
|
+
// Re-admitted together at every gate below. Kept as one set so a new injected
|
|
878
|
+
// tool cannot be added to some of the three gates and forgotten at the rest.
|
|
879
|
+
//
|
|
880
|
+
// `disallowed_tools` is applied HERE rather than at the gates, because the two
|
|
881
|
+
// kinds answer to it differently: a nested delegation tool is an opt-in the
|
|
882
|
+
// agent's own frontmatter can take back, while StructuredOutput exists only
|
|
883
|
+
// because this call asked for a schema — removing it would make the request
|
|
884
|
+
// unsatisfiable by construction rather than merely restricted.
|
|
885
|
+
const readmitToolNames = new Set([
|
|
886
|
+
...[...nestedToolNames].filter(name => !disallowedSet?.has(name)),
|
|
887
|
+
...structuredToolNames,
|
|
888
|
+
]);
|
|
889
|
+
|
|
732
890
|
// ─── Tool scoping ───────────────────────────────────────────────────────
|
|
733
891
|
//
|
|
734
892
|
// Some extensions register their tools ASYNCHRONOUSLY, long after the
|
|
@@ -761,17 +919,35 @@ export async function runAgent(
|
|
|
761
919
|
let sessionTools: string[] | undefined;
|
|
762
920
|
let sessionExcludeTools: string[] | undefined;
|
|
763
921
|
if (noExtensions) {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
922
|
+
// Strict allowlist: built-ins the agent asked for, plus any opt-in nested
|
|
923
|
+
// tools (whose names would otherwise be dropped as EXCLUDED_TOOL_NAMES).
|
|
924
|
+
sessionTools = [
|
|
925
|
+
...toolNames.filter(
|
|
926
|
+
(t) => !EXCLUDED_TOOL_NAMES.includes(t) && !disallowedSet?.has(t),
|
|
927
|
+
),
|
|
928
|
+
...[...nestedToolNames].filter((t) => !disallowedSet?.has(t)),
|
|
929
|
+
// Not filtered through `disallowedSet`, unlike the nested tools above:
|
|
930
|
+
// the caller asked for a schema, and removing the only tool that can
|
|
931
|
+
// satisfy it would make the request unsatisfiable by construction rather
|
|
932
|
+
// than merely restricted.
|
|
933
|
+
...structuredToolNames,
|
|
934
|
+
];
|
|
767
935
|
} else {
|
|
768
|
-
|
|
936
|
+
// Deny the orchestration tools EXCEPT the nested ones this agent opted into —
|
|
937
|
+
// those are injected as customTools and must survive the registry gate.
|
|
938
|
+
const denyTools = new Set<string>(
|
|
939
|
+
EXCLUDED_TOOL_NAMES.filter((t) => !nestedToolNames.has(t)),
|
|
940
|
+
);
|
|
769
941
|
// Keep only the built-ins the agent asked for — deny the rest.
|
|
770
942
|
for (const name of BUILTIN_TOOL_NAMES) {
|
|
771
943
|
if (!builtinToolNameSet.has(name)) denyTools.add(name);
|
|
772
944
|
}
|
|
773
945
|
if (disallowedSet) {
|
|
774
|
-
|
|
946
|
+
// disallowed_tools wins even over an opt-in nested tool of the same name.
|
|
947
|
+
// Not over StructuredOutput, though — see the allowlist branch above.
|
|
948
|
+
for (const name of disallowedSet) {
|
|
949
|
+
if (!structuredToolNames.has(name)) denyTools.add(name);
|
|
950
|
+
}
|
|
775
951
|
}
|
|
776
952
|
sessionExcludeTools = [...denyTools];
|
|
777
953
|
}
|
|
@@ -779,33 +955,48 @@ export async function runAgent(
|
|
|
779
955
|
const settingsManager = SettingsManager.create(configCwd, agentDir);
|
|
780
956
|
const configuredSessionDir = resolveConfiguredSessionDir(agentConfig?.sessionDir, effectiveCwd);
|
|
781
957
|
const defaultSessionDir = process.env.PI_CODING_AGENT_SESSION_DIR ?? settingsManager.getSessionDir?.();
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
958
|
+
// Frontmatter wins when it says anything; otherwise the project default,
|
|
959
|
+
// which `rememberAgents` supplies for top-level agents only. Same precedence
|
|
960
|
+
// as `outputTranscript`.
|
|
961
|
+
const persistSession = agentConfig?.persistSession ?? (options.nested ? false : rememberAgents);
|
|
962
|
+
const sessionManager = options.resumeSessionFile
|
|
963
|
+
// Reopening an existing conversation: the file already carries its own
|
|
964
|
+
// header (cwd, parent) and history, so none of the create-time options
|
|
965
|
+
// apply. `sessionDir` still matters for a later /new or /branch off it.
|
|
966
|
+
? SessionManager.open(options.resumeSessionFile, configuredSessionDir ?? defaultSessionDir)
|
|
967
|
+
: persistSession
|
|
968
|
+
? SessionManager.create(effectiveCwd, configuredSessionDir ?? defaultSessionDir, {
|
|
969
|
+
// Optional metadata — it only nests the subagent under its spawner in
|
|
970
|
+
// `/resume`. Until `rememberAgents` this ran solely for the rare
|
|
971
|
+
// `persist_session: true` agent; now it runs for every spawn, so a
|
|
972
|
+
// context without a session manager (a bare programmatic ctx) must
|
|
973
|
+
// still persist rather than take the whole spawn down.
|
|
974
|
+
parentSession: ctx.sessionManager?.getSessionFile?.(),
|
|
975
|
+
})
|
|
976
|
+
: SessionManager.inMemory(effectiveCwd);
|
|
790
977
|
|
|
791
978
|
// Pi 0.80.8 replaced createAgentSession's modelRegistry option with
|
|
792
979
|
// modelRuntime, but ExtensionContext still exposes only the registry facade.
|
|
793
980
|
// Pass both so the full supported Pi range retains the parent's providers.
|
|
794
|
-
const parentModelRuntime = (ctx.modelRegistry as unknown as { runtime?:
|
|
981
|
+
const parentModelRuntime = (ctx.modelRegistry as unknown as { runtime?: unknown }).runtime;
|
|
795
982
|
const sessionOpts: Parameters<typeof createAgentSession>[0] & {
|
|
796
983
|
modelRegistry: ExtensionContext["modelRegistry"];
|
|
797
|
-
modelRuntime?:
|
|
984
|
+
modelRuntime?: unknown;
|
|
798
985
|
} = {
|
|
799
986
|
cwd: effectiveCwd,
|
|
800
987
|
agentDir,
|
|
801
988
|
sessionManager,
|
|
802
989
|
settingsManager,
|
|
803
990
|
modelRegistry: ctx.modelRegistry,
|
|
804
|
-
|
|
991
|
+
// `as never` is what keeps this assignable across the supported Pi range:
|
|
992
|
+
// pre-0.80.8 the field exists only via the `modelRuntime?: unknown` shim
|
|
993
|
+
// above, while newer Pi types it as `ModelRuntime` — a shape an opaque
|
|
994
|
+
// `unknown` read off the private facade field can never satisfy.
|
|
995
|
+
...(parentModelRuntime !== undefined && { modelRuntime: parentModelRuntime as never }),
|
|
805
996
|
model,
|
|
806
997
|
tools: sessionTools,
|
|
998
|
+
customTools: [...nestedTools, ...structuredTools],
|
|
807
999
|
resourceLoader: loader,
|
|
808
|
-
...(trackedWriteTool && { customTools: [trackedWriteTool as any] }),
|
|
809
1000
|
};
|
|
810
1001
|
if (sessionExcludeTools) {
|
|
811
1002
|
sessionOpts.excludeTools = sessionExcludeTools;
|
|
@@ -814,7 +1005,7 @@ export async function runAgent(
|
|
|
814
1005
|
sessionOpts.thinkingLevel = thinkingLevel;
|
|
815
1006
|
}
|
|
816
1007
|
|
|
817
|
-
const { session } = await createAgentSession(sessionOpts);
|
|
1008
|
+
const { session } = await runInChildSessionContext(() => createAgentSession(sessionOpts));
|
|
818
1009
|
|
|
819
1010
|
const baseSessionName = agentConfig?.name ?? type;
|
|
820
1011
|
session.setSessionName(
|
|
@@ -847,6 +1038,7 @@ export async function runAgent(
|
|
|
847
1038
|
disallowedSet,
|
|
848
1039
|
extNames,
|
|
849
1040
|
narrowing,
|
|
1041
|
+
readmitToolNames,
|
|
850
1042
|
});
|
|
851
1043
|
}
|
|
852
1044
|
|
|
@@ -854,7 +1046,7 @@ export async function runAgent(
|
|
|
854
1046
|
|
|
855
1047
|
// Track turns for graceful max_turns enforcement
|
|
856
1048
|
let turnCount = 0;
|
|
857
|
-
const maxTurns =
|
|
1049
|
+
const maxTurns = resolveEffectiveMaxTurns(type, options.maxTurns);
|
|
858
1050
|
let softLimitReached = false;
|
|
859
1051
|
let aborted = false;
|
|
860
1052
|
|
|
@@ -892,6 +1084,8 @@ export async function runAgent(
|
|
|
892
1084
|
input: u.input ?? 0,
|
|
893
1085
|
output: u.output ?? 0,
|
|
894
1086
|
cacheWrite: u.cacheWrite ?? 0,
|
|
1087
|
+
cacheRead: u.cacheRead ?? 0,
|
|
1088
|
+
cost: u.cost?.total ?? 0,
|
|
895
1089
|
});
|
|
896
1090
|
}
|
|
897
1091
|
if (event.type === "compaction_end" && !event.aborted && event.result) {
|
|
@@ -914,8 +1108,20 @@ export async function runAgent(
|
|
|
914
1108
|
// Boundary for the history fallback: only assistant text produced from here
|
|
915
1109
|
// on counts as this run's output (a fresh session, so usually 0).
|
|
916
1110
|
const startLen = session.messages.length;
|
|
1111
|
+
let structuredRetried = false;
|
|
917
1112
|
try {
|
|
918
1113
|
await session.prompt(effectivePrompt);
|
|
1114
|
+
|
|
1115
|
+
// One more prompt when a schema was asked for and nothing usable came back
|
|
1116
|
+
// — the model answered in prose, or only ever called the tool invalidly.
|
|
1117
|
+
// Inside this `try`, so the turn tracking, the text collector and above all
|
|
1118
|
+
// the abort forwarding are still live: torn down first, a retry would be
|
|
1119
|
+
// unkillable.
|
|
1120
|
+
if (structuredCapture !== undefined && structuredCapture.json === undefined
|
|
1121
|
+
&& !aborted && options.signal?.aborted !== true) {
|
|
1122
|
+
structuredRetried = true;
|
|
1123
|
+
await session.prompt(structuredRetryPrompt(structuredCapture));
|
|
1124
|
+
}
|
|
919
1125
|
} finally {
|
|
920
1126
|
unsubTurns();
|
|
921
1127
|
collector.unsubscribe();
|
|
@@ -923,7 +1129,23 @@ export async function runAgent(
|
|
|
923
1129
|
}
|
|
924
1130
|
|
|
925
1131
|
const responseText = collector.getText().trim() || getLastAssistantText(session, startLen);
|
|
926
|
-
|
|
1132
|
+
// A child asked for structured output that never gave any has failed, however
|
|
1133
|
+
// articulate its prose was. Reported through `failure` so it travels the same
|
|
1134
|
+
// path as a provider error rather than arriving as a successful empty answer.
|
|
1135
|
+
const structuredFailure = structuredCapture !== undefined && structuredCapture.json === undefined
|
|
1136
|
+
? structuredCapture.lastError !== undefined
|
|
1137
|
+
? `The agent's StructuredOutput call did not match the required schema: ${structuredCapture.lastError}`
|
|
1138
|
+
: "The agent did not report its answer through StructuredOutput."
|
|
1139
|
+
: undefined;
|
|
1140
|
+
return {
|
|
1141
|
+
responseText,
|
|
1142
|
+
session,
|
|
1143
|
+
aborted,
|
|
1144
|
+
steered: softLimitReached,
|
|
1145
|
+
failure: finalTurnError(session, startLen) ?? structuredFailure,
|
|
1146
|
+
...(structuredCapture?.json !== undefined ? { structuredJson: structuredCapture.json } : {}),
|
|
1147
|
+
...(structuredRetried ? { structuredRetried } : {}),
|
|
1148
|
+
};
|
|
927
1149
|
}
|
|
928
1150
|
|
|
929
1151
|
/**
|
|
@@ -934,7 +1156,7 @@ export async function resumeAgent(
|
|
|
934
1156
|
prompt: string,
|
|
935
1157
|
options: {
|
|
936
1158
|
onToolActivity?: (activity: ToolActivity) => void;
|
|
937
|
-
onAssistantUsage?: (usage:
|
|
1159
|
+
onAssistantUsage?: (usage: LifetimeUsage) => void;
|
|
938
1160
|
onCompaction?: (info: { reason: "manual" | "threshold" | "overflow"; tokensBefore: number }) => void;
|
|
939
1161
|
signal?: AbortSignal;
|
|
940
1162
|
} = {},
|
|
@@ -956,6 +1178,8 @@ export async function resumeAgent(
|
|
|
956
1178
|
input: u.input ?? 0,
|
|
957
1179
|
output: u.output ?? 0,
|
|
958
1180
|
cacheWrite: u.cacheWrite ?? 0,
|
|
1181
|
+
cacheRead: u.cacheRead ?? 0,
|
|
1182
|
+
cost: u.cost?.total ?? 0,
|
|
959
1183
|
});
|
|
960
1184
|
}
|
|
961
1185
|
if (event.type === "compaction_end" && !event.aborted && event.result) {
|