pi-subagents 0.67.0 → 0.69.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/CHANGELOG.md +90 -0
- package/README.md +1 -1
- package/docs/agents.md +41 -12
- package/docs/configuration.md +61 -19
- package/docs/extension-api.md +5 -1
- package/docs/missions.md +2 -2
- package/docs/models.md +11 -79
- package/docs/observability.md +18 -8
- package/docs/standalone-background.md +13 -3
- package/docs/tool-reference.md +38 -14
- package/docs/watchdog.md +10 -12
- package/docs/workflows.md +59 -1
- package/index.ts +5 -2
- package/package.json +4 -2
- package/runner-peer-loader.mjs +24 -0
- package/runner-peer-preload.mjs +25 -11
- package/skills/pi-subagents/SKILL.md +18 -21
- package/skills/pi-subagents/references/constraints-and-recipes.md +3 -2
- package/skills/pi-subagents/references/execution-controls.md +6 -4
- package/skills/pi-subagents/references/management-authoring-rpc.md +0 -1
- package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/skills/pi-subagents/references/prompting-and-roles.md +16 -12
- package/skills/pi-subagents/references/review-and-validation.md +3 -3
- package/src/agents/agent-management.ts +57 -58
- package/src/agents/agent-serializer.ts +4 -3
- package/src/agents/agents.ts +185 -72
- package/src/agents/chain-serializer.ts +5 -0
- package/src/agents/runtime-agent-registry.ts +7 -6
- package/src/agents/skills.ts +1 -1
- package/src/api/preflight.ts +20 -16
- package/src/api/required-child-extensions.ts +6 -0
- package/src/extension/config.ts +10 -37
- package/src/extension/fanout-child.ts +3 -0
- package/src/extension/herdr-pi-bridge.ts +160 -0
- package/src/extension/index.ts +42 -31
- package/src/extension/public-execution.ts +3 -3
- package/src/extension/schemas.ts +23 -6
- package/src/extension/tool-description.ts +8 -7
- package/src/inspectors/ghostty/plugin.ts +13 -1
- package/src/intercom/native-supervisor-channel.ts +22 -18
- package/src/policy/authority.ts +4 -0
- package/src/profiles/profiles.ts +12 -6
- package/src/runs/background/active-run-index.ts +17 -1
- package/src/runs/background/async-execution.ts +309 -126
- package/src/runs/background/async-job-tracker.ts +8 -6
- package/src/runs/background/async-resume.ts +13 -4
- package/src/runs/background/async-status.ts +15 -4
- package/src/runs/background/auto-drain.ts +20 -10
- package/src/runs/background/binary-bootstrap.ts +5 -0
- package/src/runs/background/chain-append.ts +1 -1
- package/src/runs/background/chain-root-attachment.ts +14 -33
- package/src/runs/background/notify.ts +74 -6
- package/src/runs/background/result-files.ts +8 -4
- package/src/runs/background/result-watcher.ts +19 -2
- package/src/runs/background/run-child-session.ts +20 -29
- package/src/runs/background/runner-aliases.ts +4 -33
- package/src/runs/background/runner-child-launch.ts +4 -1
- package/src/runs/background/runner-child-sessions.ts +2 -2
- package/src/runs/background/runner-http-dispatcher.ts +119 -0
- package/src/runs/background/scheduled-runs.ts +11 -5
- package/src/runs/background/stale-run-reconciler.ts +35 -11
- package/src/runs/background/subagent-runner.ts +413 -276
- package/src/runs/background/subagent-wait.ts +128 -23
- package/src/runs/background/wait-completions.ts +75 -27
- package/src/runs/background/wait-subscriptions.ts +9 -3
- package/src/runs/background/wait-tool.ts +4 -2
- package/src/runs/foreground/async-stop-action.ts +93 -3
- package/src/runs/foreground/execution.ts +115 -219
- package/src/runs/foreground/foreground-history.ts +2 -1
- package/src/runs/foreground/subagent-executor.ts +281 -80
- package/src/runs/shared/acceptance.ts +194 -37
- package/src/runs/shared/async-status-projection.ts +123 -33
- package/src/runs/shared/child-launch-plan.ts +15 -3
- package/src/runs/shared/child-launch.ts +19 -6
- package/src/runs/shared/child-runtime-config.ts +5 -0
- package/src/runs/shared/child-session.ts +94 -50
- package/src/runs/shared/child-tool-plan.ts +28 -16
- package/src/runs/shared/dynamic-fanout.ts +2 -2
- package/src/runs/shared/external-cli-contract.ts +11 -1
- package/src/runs/shared/external-cli-preflight.ts +6 -2
- package/src/runs/shared/herdr-connection.ts +134 -0
- package/src/runs/shared/herdr-external-adapters.ts +169 -0
- package/src/runs/shared/herdr-machine.ts +279 -0
- package/src/runs/shared/herdr-pi-protocol.ts +59 -0
- package/src/runs/shared/herdr-placed-run.ts +263 -0
- package/src/runs/shared/model-resolution-diagnostic.ts +76 -0
- package/src/runs/shared/{model-fallback.ts → model-resolution.ts} +22 -237
- package/src/runs/shared/model-scope.ts +1 -1
- package/src/runs/shared/nested-events.ts +11 -2
- package/src/runs/shared/parallel-utils.ts +7 -2
- package/src/runs/shared/pi-spawn.ts +1 -1
- package/src/runs/shared/subagent-prompt-runtime.ts +4 -2
- package/src/runs/shared/worktree-setup-command.ts +27 -4
- package/src/runs/shared/worktree.ts +30 -8
- package/src/shared/child-cache-retention.ts +43 -0
- package/src/shared/launch-contract.ts +6 -9
- package/src/shared/pruned-fork.ts +1 -1
- package/src/shared/required-child-extensions.ts +81 -0
- package/src/shared/settings.ts +5 -2
- package/src/shared/shortcuts.ts +0 -4
- package/src/shared/types.ts +81 -29
- package/src/slash/slash-commands.ts +0 -6
- package/src/slash/subagents-admin.ts +13 -9
- package/src/tui/render.ts +20 -10
- package/src/watchdog/child-status.ts +28 -36
- package/src/watchdog/lsp-diagnostics.ts +1 -1
- package/src/watchdog/model-selection.ts +1 -1
- package/src/watchdog/register-child.ts +10 -3
- package/src/watchdog/register-main.ts +20 -20
- package/src/watchdog/render.ts +1 -1
- package/src/watchdog/review.ts +14 -30
- package/src/watchdog/rules.ts +1 -1
- package/src/watchdog/runtime.ts +23 -12
- package/src/watchdog/settings.ts +3 -6
- package/src/watchdog/types.ts +3 -5
- package/src/watchdog/warning-format.ts +1 -1
- package/src/workflows/scripted-workflow.ts +68 -7
- package/src/workflows/workflow-receipt.ts +21 -3
- package/src/workflows/workflow-resources.ts +13 -2
- package/src/runs/shared/model-exclusions.ts +0 -374
- package/src/runs/shared/readonly-model-continuation.ts +0 -69
- package/src/runs/shared/readonly-session-evidence.ts +0 -307
package/src/extension/index.ts
CHANGED
|
@@ -71,7 +71,7 @@ import { SUBAGENT_CHILD_ENV, SUBAGENT_PARENT_SESSION_ENV } from "../runs/shared/
|
|
|
71
71
|
import { disposeChildSessions } from "../runs/shared/child-session.ts";
|
|
72
72
|
import { resolveCurrentSubagentCapabilityCeiling } from "../runs/shared/capability-ceiling.ts";
|
|
73
73
|
import { formatDuration, shortenPath } from "../shared/formatters.ts";
|
|
74
|
-
import {
|
|
74
|
+
import { loadConfig, resolveAsyncByDefault, resolveScheduledStoreRoot } from "./config.ts";
|
|
75
75
|
import { buildSubagentToolDescription, buildSubagentToolPromptMetadata } from "./tool-description.ts";
|
|
76
76
|
import { formatWorkflowPreflightSummary, normalizeWorkflowPreflight } from "../workflows/workflow-preflight.ts";
|
|
77
77
|
import { finalizeToolResult } from "./tool-result.ts";
|
|
@@ -431,8 +431,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
431
431
|
cleanupOldChainDirs();
|
|
432
432
|
|
|
433
433
|
const config = loadConfig();
|
|
434
|
-
// Apply the process-wide exclusion TTL before any child launch can record a model failure.
|
|
435
|
-
applyModelExclusionsConfig(config);
|
|
436
434
|
const waitToolConfig = resolveWaitToolConfig(config.waitTool);
|
|
437
435
|
const asyncByDefault = resolveAsyncByDefault(config);
|
|
438
436
|
const fleetViewEnabled = config.fleetView !== false;
|
|
@@ -442,14 +440,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
442
440
|
const tempArtifactsDir = getArtifactsDir(null);
|
|
443
441
|
const artifactCleanupDays = config.artifactConfig?.cleanupDays ?? DEFAULT_ARTIFACT_CONFIG.cleanupDays;
|
|
444
442
|
cleanupAllArtifactDirs(artifactCleanupDays);
|
|
445
|
-
|
|
446
|
-
try {
|
|
447
|
-
cleanupResultIndexes(DIRS.results);
|
|
448
|
-
} catch (error) {
|
|
449
|
-
console.error("Failed to clean stale subagent result indexes:", error);
|
|
450
|
-
}
|
|
451
|
-
}, 30_000);
|
|
452
|
-
resultIndexCleanupTimer.unref?.();
|
|
443
|
+
let resultIndexCleanupTimer: ReturnType<typeof setTimeout> | undefined;
|
|
453
444
|
|
|
454
445
|
const state: SubagentState = {
|
|
455
446
|
baseCwd: "",
|
|
@@ -595,23 +586,39 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
595
586
|
const { startResultWatcher, transitionResultDelivery, primeExistingResults, stopResultWatcher } = resultWatcher;
|
|
596
587
|
refreshResultDelivery = resultWatcher.refreshResultDelivery;
|
|
597
588
|
const asyncRetentionAbort = new AbortController();
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
});
|
|
610
|
-
} catch (error) {
|
|
611
|
-
console.error("Failed to clean retained async subagent state:", error);
|
|
589
|
+
let asyncRetentionTimer: ReturnType<typeof setTimeout> | undefined;
|
|
590
|
+
const startSessionMaintenance = () => {
|
|
591
|
+
if (!resultIndexCleanupTimer) {
|
|
592
|
+
resultIndexCleanupTimer = setTimeout(() => {
|
|
593
|
+
try {
|
|
594
|
+
cleanupResultIndexes(DIRS.results);
|
|
595
|
+
} catch (error) {
|
|
596
|
+
console.error("Failed to clean stale subagent result indexes:", error);
|
|
597
|
+
}
|
|
598
|
+
}, 30_000);
|
|
599
|
+
resultIndexCleanupTimer.unref?.();
|
|
612
600
|
}
|
|
613
|
-
|
|
614
|
-
|
|
601
|
+
waitSubscriptionManager.start();
|
|
602
|
+
if (!asyncRetentionTimer) {
|
|
603
|
+
asyncRetentionTimer = setTimeout(async () => {
|
|
604
|
+
try {
|
|
605
|
+
await cleanupAsyncRetention({
|
|
606
|
+
asyncDirRoot: DIRS.async,
|
|
607
|
+
resultsDir: DIRS.results,
|
|
608
|
+
signal: asyncRetentionAbort.signal,
|
|
609
|
+
protectedRunIds: new Set([
|
|
610
|
+
...state.asyncJobs.keys(),
|
|
611
|
+
...(state.workflowControllers?.keys() ?? []),
|
|
612
|
+
...scheduledRunManager.referencedAsyncRunIds(),
|
|
613
|
+
]),
|
|
614
|
+
});
|
|
615
|
+
} catch (error) {
|
|
616
|
+
console.error("Failed to clean retained async subagent state:", error);
|
|
617
|
+
}
|
|
618
|
+
}, ASYNC_RETENTION_DELAY_MS);
|
|
619
|
+
asyncRetentionTimer.unref?.();
|
|
620
|
+
}
|
|
621
|
+
};
|
|
615
622
|
|
|
616
623
|
const executorDeps: Parameters<typeof createSubagentExecutor>[0] = {
|
|
617
624
|
pi,
|
|
@@ -824,7 +831,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
824
831
|
registerWaitTool(pi, state, waitToolConfig.enabled, waitSubscriptionManager, waitToolConfig.defaultTimeoutMs);
|
|
825
832
|
|
|
826
833
|
pi.on("agent_end", async (_event, ctx) => {
|
|
827
|
-
if (!ctx.hasUI) await drainOutstandingWork({ state, events: pi.events });
|
|
834
|
+
if (!ctx.hasUI) await drainOutstandingWork({ state, events: pi.events, hasPendingSupervisorRequest: supervisorChannel.hasPendingRequests });
|
|
828
835
|
const ownerSessionId = state.currentSessionId;
|
|
829
836
|
if (!ownerSessionId) return;
|
|
830
837
|
goalTurnId += 1;
|
|
@@ -1041,8 +1048,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
1041
1048
|
state.workflowControllers?.clear();
|
|
1042
1049
|
state.workflowChildStops?.clear();
|
|
1043
1050
|
clearRuntimeAgentsForPi(pi);
|
|
1044
|
-
clearTimeout(resultIndexCleanupTimer);
|
|
1045
|
-
|
|
1051
|
+
if (resultIndexCleanupTimer) clearTimeout(resultIndexCleanupTimer);
|
|
1052
|
+
resultIndexCleanupTimer = undefined;
|
|
1053
|
+
if (asyncRetentionTimer) clearTimeout(asyncRetentionTimer);
|
|
1054
|
+
asyncRetentionTimer = undefined;
|
|
1046
1055
|
asyncRetentionAbort.abort();
|
|
1047
1056
|
stopResultWatcher();
|
|
1048
1057
|
resultDeliveryOwnership.clear();
|
|
@@ -1136,7 +1145,8 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
1136
1145
|
if (event.reason !== "manual") suspendWidgetsForCompaction();
|
|
1137
1146
|
});
|
|
1138
1147
|
|
|
1139
|
-
pi.on("session_compact", () => {
|
|
1148
|
+
pi.on("session_compact", (event) => {
|
|
1149
|
+
if (event.reason !== "manual") return;
|
|
1140
1150
|
const hasActiveAsyncWork = [...state.asyncJobs.values()].some((job) => job.status === "queued" || job.status === "running");
|
|
1141
1151
|
if (!hasActiveAsyncWork || !withLastUiContext(() => true)) return;
|
|
1142
1152
|
pi.sendMessage(
|
|
@@ -1151,6 +1161,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
1151
1161
|
|
|
1152
1162
|
pi.on("session_start", (event, ctx) => {
|
|
1153
1163
|
installRuntime(ctx);
|
|
1164
|
+
startSessionMaintenance();
|
|
1154
1165
|
const recovering = event.reason === "startup" || event.reason === "reload" || event.reason === "resume";
|
|
1155
1166
|
resetSessionState(ctx, recovering, event.previousSessionFile);
|
|
1156
1167
|
releaseHostSessionLiveness();
|
|
@@ -89,10 +89,10 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
89
89
|
if (hasNamedWorkflow && (params.workflowScript !== undefined || params.workflowScriptPath !== undefined)) {
|
|
90
90
|
return { ok: false, error: "workflow is mutually exclusive with workflowScript and workflowScriptPath.", mode: "workflow" };
|
|
91
91
|
}
|
|
92
|
-
if (!hasNamedWorkflow && params.args !== undefined) {
|
|
93
|
-
return { ok: false, error: "args requires a named workflow resource.", mode: "workflow" };
|
|
94
|
-
}
|
|
95
92
|
const hasWorkflowInput = params.workflowScript !== undefined || params.workflowScriptPath !== undefined || hasNamedWorkflow;
|
|
93
|
+
if (!hasWorkflowInput && params.args !== undefined) {
|
|
94
|
+
return { ok: false, error: "args requires workflow, workflowScript, or workflowScriptPath.", mode: "workflow" };
|
|
95
|
+
}
|
|
96
96
|
const hasCapacityOverride = params.globalConcurrencyLimit !== undefined || params.maxSubagentSpawnsPerRun !== undefined;
|
|
97
97
|
if (hasCapacityOverride) {
|
|
98
98
|
const capacityOverrideError = validateWorkflowCapacityOverrides(params);
|
package/src/extension/schemas.ts
CHANGED
|
@@ -65,6 +65,11 @@ const JsonSchemaObject = Type.Unsafe({
|
|
|
65
65
|
description: "Strict structured output; object-root JSON Schema only.",
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
const OutputSchemaOverride = Type.Unsafe({
|
|
69
|
+
anyOf: [JsonSchemaObject, { type: "boolean" }],
|
|
70
|
+
description: "Structured output schema override; false disables an agent default.",
|
|
71
|
+
});
|
|
72
|
+
|
|
68
73
|
// Provider boolean branches intentionally overapproximate false-only runtime inputs.
|
|
69
74
|
// Restricted function-declaration converters only support string enum members.
|
|
70
75
|
const AcceptanceOverride = Type.Unsafe({
|
|
@@ -78,6 +83,7 @@ const AcceptanceOverride = Type.Unsafe({
|
|
|
78
83
|
},
|
|
79
84
|
{
|
|
80
85
|
type: "string",
|
|
86
|
+
pattern: "^\\s*\\{",
|
|
81
87
|
},
|
|
82
88
|
{ type: "boolean" },
|
|
83
89
|
{ type: "object", additionalProperties: true },
|
|
@@ -148,8 +154,9 @@ export const ParallelTaskSchema = Type.Object({
|
|
|
148
154
|
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
149
155
|
label: Type.Optional(Type.String({ description: "Optional user-facing label for this parallel task." })),
|
|
150
156
|
as: Type.Optional(Type.String({ description: "Optional safe identifier used as {outputs.name} in later chain steps." })),
|
|
151
|
-
outputSchema: Type.Optional(
|
|
157
|
+
outputSchema: Type.Optional(OutputSchemaOverride),
|
|
152
158
|
cwd: Type.Optional(Type.String()),
|
|
159
|
+
machine: Type.Optional(Type.String({ minLength: 1, maxLength: 128, description: "Herdr saved machine id or label." })),
|
|
153
160
|
count: Type.Optional(Type.Integer({ minimum: 1, description: "Repeat this parallel task N times with the same settings." })),
|
|
154
161
|
output: Type.Optional(OutputOverride),
|
|
155
162
|
outputMode: Type.Optional(OutputModeOverride),
|
|
@@ -180,8 +187,9 @@ export const DynamicParallelTemplateSchema = Type.Object({
|
|
|
180
187
|
task: Type.Optional(Type.String({ description: "Task template with {item}, {item.path}, {task}, {previous}, {chain_dir}, and {outputs.name} variables." })),
|
|
181
188
|
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
182
189
|
label: Type.Optional(Type.String({ description: "Optional user-facing label; item templates are supported." })),
|
|
183
|
-
outputSchema: Type.Optional(
|
|
190
|
+
outputSchema: Type.Optional(OutputSchemaOverride),
|
|
184
191
|
cwd: Type.Optional(Type.String()),
|
|
192
|
+
machine: Type.Optional(Type.String({ minLength: 1, maxLength: 128, description: "Herdr saved machine id or label." })),
|
|
185
193
|
output: Type.Optional(OutputOverride),
|
|
186
194
|
outputMode: Type.Optional(OutputModeOverride),
|
|
187
195
|
reads: Type.Optional(ReadsOverride),
|
|
@@ -209,8 +217,9 @@ export const ChainItem = Type.Object({
|
|
|
209
217
|
phase: Type.Optional(Type.String({ description: "Optional phase/group label for status and graph rendering." })),
|
|
210
218
|
label: Type.Optional(Type.String({ description: "Optional user-facing label for this chain step." })),
|
|
211
219
|
as: Type.Optional(Type.String({ description: "Optional safe identifier used as {outputs.name} in later chain steps." })),
|
|
212
|
-
outputSchema: Type.Optional(
|
|
220
|
+
outputSchema: Type.Optional(OutputSchemaOverride),
|
|
213
221
|
cwd: Type.Optional(Type.String()),
|
|
222
|
+
machine: Type.Optional(Type.String({ minLength: 1, maxLength: 128, description: "Herdr saved machine id or label." })),
|
|
214
223
|
output: Type.Optional(OutputOverride),
|
|
215
224
|
outputMode: Type.Optional(OutputModeOverride),
|
|
216
225
|
reads: Type.Optional(ReadsOverride),
|
|
@@ -333,7 +342,7 @@ const SubagentParamProperties = {
|
|
|
333
342
|
description: "create/update agent config; object or JSON string."
|
|
334
343
|
})),
|
|
335
344
|
workflow: Type.Optional(Type.String({ minLength: 1, description: "Extension-owned workflow resource." })),
|
|
336
|
-
args: Type.Optional(Type.Unsafe({ type: "object", maxProperties: 16, additionalProperties: true, description: "Bounded plain-JSON
|
|
345
|
+
args: Type.Optional(Type.Unsafe({ type: "object", maxProperties: 16, additionalProperties: true, description: "Bounded plain-JSON args for named, inline, or file-backed workflows; raw-script args are exposed deeply frozen and persisted, so do not include secrets." })),
|
|
337
346
|
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Inline JavaScript statement body; raw/unknown provenance, no runs.host. Use explicit return and top-level await; see tool guidance/guide workflows." })),
|
|
338
347
|
workflowScriptPath: Type.Optional(Type.String({ minLength: 1, description: "Raw script file; host reads from request cwd before sandbox. Mutually exclusive with workflowScript and workflow." })),
|
|
339
348
|
globalConcurrencyLimit: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
@@ -351,11 +360,13 @@ const SubagentParamProperties = {
|
|
|
351
360
|
async: Type.Optional(Type.Boolean({ description: "Background; default asyncByDefault. false only to block parent." })),
|
|
352
361
|
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline. Alias maxRuntimeMs." })),
|
|
353
362
|
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs (same defaults)." })),
|
|
363
|
+
checkpointBeforeDeadlineMs: Type.Optional(Type.Integer({ minimum: 1, maximum: 2_147_483_647, description: "Async single-agent runs only: the runner requests that the child checkpoint and stop this many ms before the run deadline (best-effort; the deadline kill still applies)." })),
|
|
354
364
|
toolTimeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Per-tool deadline (ms); fast builtins default 5m." })),
|
|
355
365
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
356
366
|
usageBudget: Type.Optional(UsageBudgetOverride),
|
|
357
367
|
agentScope: Type.Optional(Type.String({ description: "user/project/both (default); project wins collisions." })),
|
|
358
368
|
cwd: Type.Optional(Type.String({ description: "Execution/project-pane directory." })),
|
|
369
|
+
machine: Type.Optional(Type.String({ minLength: 1, maxLength: 128, description: "Herdr saved machine id or label; runs an external CLI agent there. cwd then means the directory on that machine." })),
|
|
359
370
|
artifacts: Type.Optional(Type.Boolean({ description: "Debug artifacts; default true." })),
|
|
360
371
|
includeProgress: Type.Optional(Type.Boolean({ description: "Full result progress; default false." })),
|
|
361
372
|
share: Type.Optional(Type.Boolean({ description: "Upload session to GitHub Gist; default false." })),
|
|
@@ -375,10 +386,16 @@ const SubagentParamProperties = {
|
|
|
375
386
|
skill: Type.Optional(SkillOverride),
|
|
376
387
|
model: Type.Optional(Type.String({ description: "Child model provider/id; bare id only if unique. Suffix :off/minimal/low/medium/high/xhigh/max overrides agent thinking default." })),
|
|
377
388
|
fast: Type.Optional(Type.Boolean({ description: "Native OpenAI-Codex priority tier; default false, may cost more/quota." })),
|
|
378
|
-
outputSchema: Type.Optional(
|
|
389
|
+
outputSchema: Type.Optional(OutputSchemaOverride),
|
|
379
390
|
agentContract: Type.Optional(AgentContractOverride),
|
|
380
391
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
381
|
-
gate: Type.Optional(Type.
|
|
392
|
+
gate: Type.Optional(Type.Unsafe({
|
|
393
|
+
anyOf: [
|
|
394
|
+
{ type: "string", minLength: 1 },
|
|
395
|
+
{ type: "object", properties: { command: { type: "string", minLength: 1 }, output: { type: "string", enum: ["json"] }, schema: { type: "object" }, timeoutMs: { type: "integer", minimum: 1 } }, required: ["command"], additionalProperties: false },
|
|
396
|
+
],
|
|
397
|
+
description: "Host gate command run after the child finishes: a string, or { command, output: \"json\", schema?, timeoutMs? } whose passing stdout becomes structuredOutput (not with outputSchema). Cannot be combined with acceptance; an explicit acceptance of false is treated as omitted.",
|
|
398
|
+
})),
|
|
382
399
|
};
|
|
383
400
|
|
|
384
401
|
const SubagentParamsSchema = Type.Object(SubagentParamProperties);
|
|
@@ -9,27 +9,28 @@ const AGENT_SELECTION_GUIDANCE = 'First call {action:"list",capabilities:true}:
|
|
|
9
9
|
const SUBAGENT_FAILURE_RECOVERY_GUIDANCE = "Workflow, child launch, prompt runtime, extension load or child tooling failure is a lane infrastructure blocker. Stop; report exact failure, run/status and repo/cwd/worktree/branch/ref; verify clean worktree or capture partial diff before same-protocol retry or asking the owner. Never silently switch to interactive_shell, pi -ne, Codex/Claude/Cursor CLI or foreground/external mode: governed-workflow fallback requires explicit owner approval, not Pi core's generic pi -ne hint. Explicit foreground/CLI requests and work outside that protocol remain valid.";
|
|
10
10
|
|
|
11
11
|
export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
|
|
12
|
+
• Direct parent execution is the default. Invoke subagents only when delegation is authorized by the operator's current request or applicable user/project instructions; task size, complexity, risk, tool-call count, or recipe fit do not independently authorize delegation.
|
|
12
13
|
• ${AGENT_SELECTION_GUIDANCE}
|
|
13
14
|
• ${SUBAGENT_FAILURE_RECOVERY_GUIDANCE}
|
|
14
|
-
• Omit action for execution.
|
|
15
|
+
• Omit action for execution. For an authorized delegated multi-step/parallel workflow: exactly one top-level subagent workflow call with async:true; children launch only inside it.
|
|
15
16
|
• Async follows asyncByDefault (normally true); async:false only to block the parent, not for final reviews/gates. Consume results at dependency barriers. Native async completion wakes this session: return control, no sleep/poll or bg_wait merely for a wake. bg_wait is for provider/detached work without native notification needing a same-turn result.
|
|
16
|
-
• Ordinary child subagents are not orchestrators; only configured fanout within depth/session limits.
|
|
17
|
+
• Ordinary child subagents are not orchestrators; only configured fanout within depth/session limits. For an authorized delegated workflow, keep one writer per cwd/worktree and isolate concurrent writers. Use fresh-context read-only reviewers when independent review was requested, then parent synthesis/fixes. Oracle/advisor unknowns use supervisor dialogue; one-shot only when requested.
|
|
17
18
|
• Bind durable output on runs.run/runs.all, not task filename prose; return actual outputReference/outputPathMapping/artifactPaths, evidence and residual risks.
|
|
18
19
|
• children.list: resume only resumable rows. {action:"resume",id,message} detaches a follow-up/challenge with stored agent/model/tool contract. If none is resumable, label a same-role fallback challenge. Scripts await runs.run(newKey,{resume:runId,task}); continue from latest returned runId. Each distinct resume pass needs a new stable key; same-key reuse requires identical launch parameters.
|
|
19
20
|
• Named resources own authority; raw workflowScript/workflowScriptPath cannot use runs.host. Granted commands/relative outputs use workflow cwd, never per-step cwd.
|
|
20
21
|
• Inspect asyncId/asyncDir (status.json, events.jsonl, logs) with status/debug.run; control with interrupt/stop/resume/steer. Read {action:"guide",topic:"tool-reference"} for controls/evidence gates.`;
|
|
21
22
|
|
|
22
|
-
const EXECUTION_GUIDANCE = `Delegate one child with {agent,task?}; otherwise choose exactly one of workflowScript, workflowScriptPath or {workflow,args}. agent/task exclude workflow inputs; task excludes action. agent may target management actions. action is management/control; validate accepts either script without launching. workflowScriptPath loads from request cwd before sandbox execution.
|
|
23
|
+
const EXECUTION_GUIDANCE = `Delegate one child with {agent,task?}; otherwise choose exactly one of {workflowScript,args?}, {workflowScriptPath,args?} or {workflow,args}. agent/task exclude workflow inputs; task excludes action. agent may target management actions. action is management/control; validate accepts either script without launching. workflowScriptPath loads from request cwd before sandbox execution.
|
|
23
24
|
Scripts: JavaScript statement bodies with explicit return, top-level await, plain helpers/Promise chains; nested async function/arrow/method helpers are rejected. Await runs.run('key',{agent,task}) before .output; await runs.all([{key,agent,task},...]) for an ordered array, not a key map. Observe every stored run promise with direct await, Promise.race or Promise.all. Await/return runs.steer(key,message,options?) for a prior key, never raw run ids; queued/delivered/missed/failed receipts are not compliance proof.
|
|
24
|
-
Before advanced orchestration (runs.lanes, rolling fanout, mission state, handoffs), read {action:"guide",topic:"workflows"} or the pi-subagents skill.
|
|
25
|
+
Before advanced orchestration (runs.lanes, rolling fanout, mission state, handoffs), read {action:"guide",topic:"workflows"} or the pi-subagents skill. Raw-script sandboxes add deeply frozen args; all sandboxes provide runs, emit, console, JavaScript and enabled mission state, with no filesystem/shell/Pi tools/host globals. External CLI agents support native options only when their runner declares them; read guide tool-reference before passing model, structured output, acceptance/agentContract, tool budget, fast, fork context or skills/tools.
|
|
25
26
|
Model override: first call {action:"models"}; copy exact provider/id, not agent names. Thinking uses model suffix, not watchdog-only thinking.
|
|
26
|
-
Named resources: {workflow:'review',args:{task:'...'}} or {workflow:'run-ci',args:{command:'npm test'}}
|
|
27
|
+
Named resources: {workflow:'review',args:{task:'...'}} or {workflow:'run-ci',args:{command:'npm test'}}. Raw scripts also accept bounded plain-data args; raw-script args persist as evidence, so never include secrets. worktree:true requires clean source; baseRef defaults to HEAD at allocation or a supported named ref, never full 40/64-character commit IDs or revision expressions.`;
|
|
27
28
|
|
|
28
29
|
export const DEFAULT_SUBAGENT_TOOL_DESCRIPTION = `${EXECUTION_GUIDANCE}\n\n${SUBAGENT_SAFETY_GUIDANCE}`;
|
|
29
30
|
|
|
30
|
-
export const SUBAGENT_TOOL_PROMPT_SNIPPET = "
|
|
31
|
+
export const SUBAGENT_TOOL_PROMPT_SNIPPET = "For operator-requested delegation, use subagents; compose multi-child work in one workflow call.";
|
|
31
32
|
export const SUBAGENT_TOOL_PROMPT_GUIDELINES = [
|
|
32
|
-
"
|
|
33
|
+
"Do not invoke subagents unless the operator requested delegation directly or through applicable instructions.",
|
|
33
34
|
];
|
|
34
35
|
|
|
35
36
|
export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = DEFAULT_SUBAGENT_TOOL_DESCRIPTION;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { openGhosttyInspector, type GhosttyRunner } from "./actions.ts";
|
|
2
2
|
import type { InspectorPlugin } from "../types.ts";
|
|
3
3
|
|
|
4
|
+
/** 独立 Ghostty.app 的 bundle id (作者 Mitchell Hashimoto)。 */
|
|
5
|
+
const GHOSTTY_BUNDLE_ID = "com.mitchellh.ghostty";
|
|
6
|
+
|
|
4
7
|
export interface GhosttyPluginDeps {
|
|
5
8
|
platform?: NodeJS.Platform;
|
|
6
9
|
runner?: GhosttyRunner;
|
|
@@ -10,7 +13,16 @@ export function createGhosttyInspectorPlugin(deps: GhosttyPluginDeps = {}): Insp
|
|
|
10
13
|
const platform = deps.platform ?? process.platform;
|
|
11
14
|
return {
|
|
12
15
|
name: "ghostty",
|
|
13
|
-
available: (context) =>
|
|
16
|
+
available: (context) => {
|
|
17
|
+
// cmux 内嵌 Ghostty 内核, 也会把 TERM_PROGRAM 设成 "ghostty"。仅凭环境变量会让 plugin
|
|
18
|
+
// 在 cmux 下误接管, 随后 osascript 连不上真正的 Ghostty 应用而抛 -1728/-2741。
|
|
19
|
+
if (platform !== "darwin") return false;
|
|
20
|
+
if (context.env.TERM_PROGRAM?.toLowerCase() !== "ghostty") return false;
|
|
21
|
+
// macOS GUI 应用启动子进程时注入 __CFBundleIdentifier, 标识当前终端宿主 app。
|
|
22
|
+
// cmux 的 bundle id 是 com.cmuxterm.app, 而非 Ghostty; 即便系统同时装了独立 Ghostty,
|
|
23
|
+
// 也能据此判定当前终端不是 Ghostty, 避免误连独立 Ghostty 的窗口。
|
|
24
|
+
return context.env.__CFBundleIdentifier?.trim() === GHOSTTY_BUNDLE_ID;
|
|
25
|
+
},
|
|
14
26
|
owns: () => false,
|
|
15
27
|
open: (context, launch, params) => openGhosttyInspector(context, launch, params, deps.runner),
|
|
16
28
|
};
|
|
@@ -612,6 +612,7 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
|
|
|
612
612
|
start: () => void;
|
|
613
613
|
activateTransport: () => void;
|
|
614
614
|
findPendingAsks: (target: { runId: string; agent: string; childIndex: number }) => string[];
|
|
615
|
+
hasPendingRequests: () => boolean;
|
|
615
616
|
dispose: () => void;
|
|
616
617
|
pending: Map<string, PendingSupervisorRequest>;
|
|
617
618
|
getSupervisorRequestState: (event: ControlEvent) => SupervisorRequestState;
|
|
@@ -732,11 +733,15 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
|
|
|
732
733
|
continue;
|
|
733
734
|
}
|
|
734
735
|
seenFiles.add(file);
|
|
735
|
-
if (request.expectsReply) {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
736
|
+
if (!request.expectsReply) {
|
|
737
|
+
// Progress is already visible through child activity; do not inject a
|
|
738
|
+
// parent message or trigger a parent model turn.
|
|
739
|
+
removeRequestFile(request.requestFile);
|
|
740
|
+
continue;
|
|
739
741
|
}
|
|
742
|
+
rememberPendingRequest(request);
|
|
743
|
+
pending.set(request.id, request);
|
|
744
|
+
markForegroundSupervisorAttention(request, state);
|
|
740
745
|
// The ask is already queued above. A sendMessage failure (no UI, stale context) must not
|
|
741
746
|
// lose it, and must not abort the loop before the remaining asks register.
|
|
742
747
|
try {
|
|
@@ -755,25 +760,19 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
|
|
|
755
760
|
...(request.childTarget ? { childTarget: request.childTarget } : {}),
|
|
756
761
|
...(request.interview !== undefined ? { interview: request.interview } : {}),
|
|
757
762
|
requestBody: request.message,
|
|
758
|
-
|
|
763
|
+
replyHint: supervisorReplyHint(request.id),
|
|
759
764
|
},
|
|
760
765
|
}, { triggerTurn: true });
|
|
761
|
-
// sendMessage accepts synchronously; one-way updates stay on disk until it returns.
|
|
762
|
-
if (!request.expectsReply) removeRequestFile(request.requestFile);
|
|
763
766
|
} catch (error) {
|
|
764
|
-
// Allow an existing later scan to retry an unaccepted one-way update.
|
|
765
|
-
if (!request.expectsReply) seenFiles.delete(file);
|
|
766
767
|
console.error(`Failed to surface supervisor request ${request.id} as a user turn:`, error);
|
|
767
768
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
if (pending.has(request.id)) markForegroundSupervisorAttention(request, state);
|
|
776
|
-
}
|
|
769
|
+
(pi as { events?: IntercomEventBus }).events?.emit(INTERCOM_DETACH_REQUEST_EVENT, {
|
|
770
|
+
requestId: request.id,
|
|
771
|
+
runId: request.runId,
|
|
772
|
+
agent: request.agent,
|
|
773
|
+
childIndex: request.childIndex,
|
|
774
|
+
});
|
|
775
|
+
if (pending.has(request.id)) markForegroundSupervisorAttention(request, state);
|
|
777
776
|
}
|
|
778
777
|
channels?.retire?.();
|
|
779
778
|
};
|
|
@@ -860,6 +859,11 @@ export function createNativeSupervisorChannel(pi: ExtensionAPI, state: SubagentS
|
|
|
860
859
|
&& requestLifecycle(request, state, now, runState(request)) === "pending" ? [request.id] : [];
|
|
861
860
|
}).sort();
|
|
862
861
|
},
|
|
862
|
+
hasPendingRequests: () => {
|
|
863
|
+
if (!started) return false;
|
|
864
|
+
poll();
|
|
865
|
+
return pending.size > 0;
|
|
866
|
+
},
|
|
863
867
|
start: () => {
|
|
864
868
|
if (started) return;
|
|
865
869
|
started = true;
|
package/src/policy/authority.ts
CHANGED
|
@@ -5,6 +5,8 @@ export const AUTHORITY_ACTIONS = [
|
|
|
5
5
|
"scheduleCreate",
|
|
6
6
|
"stopRun",
|
|
7
7
|
"steerRun",
|
|
8
|
+
"inspectorOpen",
|
|
9
|
+
"projectOpen",
|
|
8
10
|
] as const;
|
|
9
11
|
|
|
10
12
|
export type AuthorityAction = typeof AUTHORITY_ACTIONS[number];
|
|
@@ -18,6 +20,8 @@ const DEFAULT_AUTHORITY_POLICY: Record<AuthorityAction, AuthorityDecision> = {
|
|
|
18
20
|
scheduleCreate: "auto",
|
|
19
21
|
stopRun: "auto",
|
|
20
22
|
steerRun: "auto",
|
|
23
|
+
inspectorOpen: "auto",
|
|
24
|
+
projectOpen: "confirm",
|
|
21
25
|
};
|
|
22
26
|
|
|
23
27
|
export function resolveAuthorityDecision(input: {
|
package/src/profiles/profiles.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type RecommendedRoleTier = "cheap" | "medium" | "strong";
|
|
|
20
20
|
interface ProfileAgentOverride {
|
|
21
21
|
model?: string;
|
|
22
22
|
thinking?: string | false;
|
|
23
|
-
|
|
23
|
+
machine?: string;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export interface SubagentProfileFile {
|
|
@@ -144,10 +144,7 @@ function validateSubagentProfile(filePath: string, parsed: Record<string, unknow
|
|
|
144
144
|
if (thinking !== undefined && thinking !== false && typeof thinking !== "string") {
|
|
145
145
|
throw new Error(`Profile '${filePath}' has invalid thinking for '${name}'; expected a string or false.`);
|
|
146
146
|
}
|
|
147
|
-
|
|
148
|
-
if (fallbackModels !== undefined && fallbackModels !== false && (!Array.isArray(fallbackModels) || fallbackModels.some((item) => typeof item !== "string"))) {
|
|
149
|
-
throw new Error(`Profile '${filePath}' has invalid fallbackModels for '${name}'; expected an array of strings or false.`);
|
|
150
|
-
}
|
|
147
|
+
if ((override as Record<string, unknown>).fallbackModels !== undefined) throw new Error(`Profile '${filePath}' uses removed field fallbackModels for '${name}'; configure one model instead.`);
|
|
151
148
|
}
|
|
152
149
|
const disableBuiltins = (subagents as Record<string, unknown>).disableBuiltins;
|
|
153
150
|
if (disableBuiltins !== undefined && typeof disableBuiltins !== "boolean") {
|
|
@@ -489,10 +486,19 @@ export function applySubagentProfile(name: string): { filePath: string; settings
|
|
|
489
486
|
: {};
|
|
490
487
|
// A profile owns the complete agent mapping, but unrelated subagent settings
|
|
491
488
|
// (notably disableBuiltins, modelScope, watchdog, etc.) survive profile switches.
|
|
489
|
+
// Machine placement is not a model choice, so an existing pin survives a profile switch too.
|
|
490
|
+
const agentOverrides: Record<string, ProfileAgentOverride> = { ...profile.subagents.agentOverrides };
|
|
491
|
+
const existingOverrides = existing.agentOverrides && typeof existing.agentOverrides === "object" && !Array.isArray(existing.agentOverrides)
|
|
492
|
+
? existing.agentOverrides as Record<string, unknown>
|
|
493
|
+
: {};
|
|
494
|
+
for (const [name, value] of Object.entries(existingOverrides)) {
|
|
495
|
+
const machine = value && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>).machine : undefined;
|
|
496
|
+
if (typeof machine === "string" && agentOverrides[name]?.machine === undefined) agentOverrides[name] = { ...agentOverrides[name], machine };
|
|
497
|
+
}
|
|
492
498
|
settings.subagents = {
|
|
493
499
|
...existing,
|
|
494
500
|
...profile.subagents,
|
|
495
|
-
agentOverrides
|
|
501
|
+
agentOverrides,
|
|
496
502
|
};
|
|
497
503
|
writeJsonFile(settingsPath, settings);
|
|
498
504
|
return { filePath, settingsPath };
|
|
@@ -76,7 +76,7 @@ export function releaseActiveRunIndex(asyncDir: string): void {
|
|
|
76
76
|
releaseToolCallAliases(asyncDir);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
export function updateActiveRunIndex(asyncDir: string, state: AsyncStatus["state"], toolCallId?: string, options: { retryCapacityErrors?: boolean } = {}): void {
|
|
79
|
+
export function updateActiveRunIndex(asyncDir: string, state: AsyncStatus["state"], toolCallId?: string, options: { retryCapacityErrors?: boolean; terminalIndexBeforeRelease?: boolean } = {}): void {
|
|
80
80
|
const marker = markerPath(asyncDir);
|
|
81
81
|
if (isActiveAsyncState(state)) {
|
|
82
82
|
fs.mkdirSync(path.dirname(marker), { recursive: true });
|
|
@@ -94,6 +94,22 @@ export function updateActiveRunIndex(asyncDir: string, state: AsyncStatus["state
|
|
|
94
94
|
}
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
|
+
if (options.terminalIndexBeforeRelease) {
|
|
98
|
+
fs.mkdirSync(path.dirname(marker), { recursive: true });
|
|
99
|
+
fs.writeFileSync(marker, "", { flag: "a" });
|
|
100
|
+
const status = readStatus(asyncDir);
|
|
101
|
+
if (status?.state === state) {
|
|
102
|
+
try {
|
|
103
|
+
updateTerminalRunIndex(asyncDir, status);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (options.retryCapacityErrors) throw error;
|
|
106
|
+
console.error(`Failed to write async terminal-run index for '${asyncDir}':`, error);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
releaseActiveRunIndex(asyncDir);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
97
113
|
releaseActiveRunIndex(asyncDir);
|
|
98
114
|
const status = readStatus(asyncDir);
|
|
99
115
|
if (status && status.state === state) {
|