@selesai/code 0.8.6 → 0.8.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 +16 -0
- package/dist/core/agent-session.js +13 -1
- package/dist/core/extensions/types.d.ts +2 -0
- package/dist/core/provider-composer.js +17 -4
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.js +3 -0
- package/dist/core/system-prompt.js +2 -8
- package/dist/core/system-prompt.test.js +6 -0
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/schema-prune.d.ts +16 -0
- package/dist/core/tools/schema-prune.js +39 -0
- package/dist/core/tools/schema-prune.test.d.ts +1 -0
- package/dist/core/tools/schema-prune.test.js +111 -0
- package/dist/defaults/settings.json +1 -0
- package/dist/extensions/model-prompt-injector/config.json +10 -0
- package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
- package/dist/extensions/model-prompt-injector/index.ts +151 -0
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
- package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
- package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
- package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
- package/dist/extensions/pi-subagents/agents/scout.md +1 -1
- package/dist/extensions/pi-subagents/agents/worker.md +1 -1
- package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
- package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
- package/dist/extensions/pi-subagents/docs/missions.md +5 -3
- package/dist/extensions/pi-subagents/docs/models.md +1 -1
- package/dist/extensions/pi-subagents/docs/observability.md +42 -2
- package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
- package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
- package/dist/extensions/pi-subagents/install.mjs +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
- package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
- package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
- package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
- package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
- package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
- package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
- package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
- package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
- package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
- package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
- package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +205 -41
- package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
- package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
- package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
- package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
- package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
- package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
- package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
- package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
- package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
- package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
- package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
- package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
- package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +369 -98
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
- package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
- package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
- package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
- package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
- package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
- package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
- package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
- package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
- package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
- package/dist/extensions/question/index.ts +21 -6
- package/dist/extensions/question/question-list.ts +7 -1
- package/dist/extensions/question/row-layout.ts +3 -0
- package/dist/extensions/question/tests/exp.test.ts +70 -0
- package/dist/extensions/question/tests/exp2.test.ts +59 -0
- package/dist/extensions/question/tests/exp3.test.ts +47 -0
- package/dist/extensions/question/tests/helpers.test.ts +7 -0
- package/dist/extensions/question/tests/question-list.test.ts +140 -0
- package/dist/extensions/question/tests/row-layout.test.ts +13 -0
- package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
- package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
- package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
- package/dist/skills/pi-subagents/SKILL.md +1 -1
- package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
- package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
- package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
- package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
- package/docs/settings.md +6 -0
- package/package.json +3 -3
|
@@ -34,6 +34,7 @@ import { validateChainInput } from "./chain-validation.ts";
|
|
|
34
34
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
35
35
|
import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
|
|
36
36
|
import { getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession } from "../runs/background/active-async-capacity.ts";
|
|
37
|
+
import { cleanupResultIndexes } from "../runs/background/result-files.ts";
|
|
37
38
|
import { createResultWatcher } from "../runs/background/result-watcher.ts";
|
|
38
39
|
import { createScheduledRunManager } from "../runs/background/scheduled-runs.ts";
|
|
39
40
|
import { registerSlashCommands } from "../slash/slash-commands.ts";
|
|
@@ -62,6 +63,7 @@ import { resolveMissionStoreLocation } from "../missions/store.ts";
|
|
|
62
63
|
import { listRetainedChildren } from "../runs/background/retained-children.ts";
|
|
63
64
|
import {
|
|
64
65
|
type Details,
|
|
66
|
+
type MainWindowRendererConfig,
|
|
65
67
|
type SubagentState,
|
|
66
68
|
DIRS,
|
|
67
69
|
DEFAULT_ARTIFACT_CONFIG,
|
|
@@ -84,6 +86,13 @@ import {
|
|
|
84
86
|
|
|
85
87
|
export { loadConfig, resolveAsyncByDefault } from "./config.ts";
|
|
86
88
|
|
|
89
|
+
const SLOW_RELOAD_PHASE_MS = 250;
|
|
90
|
+
|
|
91
|
+
function logSlowPhase(label: string, startedAt: number): void {
|
|
92
|
+
const elapsed = Date.now() - startedAt;
|
|
93
|
+
if (elapsed >= SLOW_RELOAD_PHASE_MS) console.error(`Subagent reload phase '${label}' took ${elapsed}ms.`);
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
function workflowLaneKeys(script: string): string[] {
|
|
88
97
|
const keys: string[] = [];
|
|
89
98
|
const seen = new Set<string>();
|
|
@@ -260,12 +269,14 @@ function rebuildSlashResultContainer(
|
|
|
260
269
|
result: AgentToolResult<Details>,
|
|
261
270
|
options: { expanded: boolean },
|
|
262
271
|
theme: ExtensionContext["ui"]["theme"],
|
|
272
|
+
rendererConfig?: MainWindowRendererConfig,
|
|
273
|
+
foregroundDetachShortcut?: string,
|
|
263
274
|
): void {
|
|
264
275
|
container.clear();
|
|
265
276
|
container.addChild(new Spacer(1));
|
|
266
277
|
const boxTheme = isSlashResultRunning(result) ? "toolPendingBg" : isSlashResultError(result) ? "toolErrorBg" : "toolSuccessBg";
|
|
267
278
|
const box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));
|
|
268
|
-
box.addChild(renderSubagentResult(result, options, theme));
|
|
279
|
+
box.addChild(renderSubagentResult(result, options, theme, undefined, rendererConfig, foregroundDetachShortcut));
|
|
269
280
|
container.addChild(box);
|
|
270
281
|
}
|
|
271
282
|
|
|
@@ -273,6 +284,8 @@ function createSlashResultComponent(
|
|
|
273
284
|
details: SlashMessageDetails,
|
|
274
285
|
options: { expanded: boolean },
|
|
275
286
|
theme: ExtensionContext["ui"]["theme"],
|
|
287
|
+
rendererConfig?: MainWindowRendererConfig,
|
|
288
|
+
foregroundDetachShortcut?: string,
|
|
276
289
|
): Container {
|
|
277
290
|
const container = new Container();
|
|
278
291
|
let lastVersion = -1;
|
|
@@ -280,7 +293,7 @@ function createSlashResultComponent(
|
|
|
280
293
|
const snapshot = getSlashRenderableSnapshot(details);
|
|
281
294
|
if (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {
|
|
282
295
|
lastVersion = snapshot.version;
|
|
283
|
-
rebuildSlashResultContainer(container, snapshot.result, options, theme);
|
|
296
|
+
rebuildSlashResultContainer(container, snapshot.result, options, theme, rendererConfig, foregroundDetachShortcut);
|
|
284
297
|
}
|
|
285
298
|
return Container.prototype.render.call(container, width);
|
|
286
299
|
};
|
|
@@ -377,6 +390,14 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
377
390
|
const tempArtifactsDir = getArtifactsDir(null);
|
|
378
391
|
const artifactCleanupDays = config.artifactConfig?.cleanupDays ?? DEFAULT_ARTIFACT_CONFIG.cleanupDays;
|
|
379
392
|
cleanupAllArtifactDirs(artifactCleanupDays);
|
|
393
|
+
const resultIndexCleanupTimer = setTimeout(() => {
|
|
394
|
+
try {
|
|
395
|
+
cleanupResultIndexes(DIRS.results);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
console.error("Failed to clean stale subagent result indexes:", error);
|
|
398
|
+
}
|
|
399
|
+
}, 30_000);
|
|
400
|
+
resultIndexCleanupTimer.unref?.();
|
|
380
401
|
|
|
381
402
|
// Replay guard for lineage result adoption: accept result files written at
|
|
382
403
|
// most this long before the adoption window opened (covers completions that
|
|
@@ -391,6 +412,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
391
412
|
...(config.authorityPolicy ? { authorityPolicy: config.authorityPolicy } : {}),
|
|
392
413
|
...(config.missions ? { missionStoreConfig: config.missions } : {}),
|
|
393
414
|
parentSessionFile: null,
|
|
415
|
+
trustedSessionRoots: [],
|
|
394
416
|
subagentInProgress: false,
|
|
395
417
|
subagentSpawns: {
|
|
396
418
|
sessionId: null,
|
|
@@ -464,6 +486,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
464
486
|
);
|
|
465
487
|
|
|
466
488
|
const runtimeCleanup = () => {
|
|
489
|
+
clearTimeout(resultIndexCleanupTimer);
|
|
467
490
|
stopResultWatcher();
|
|
468
491
|
state.currentSessionId = null;
|
|
469
492
|
completionNotifier.dispose();
|
|
@@ -494,7 +517,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
494
517
|
pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
|
|
495
518
|
const details = resolveSlashMessageDetails(message.details);
|
|
496
519
|
if (!details) return undefined;
|
|
497
|
-
return createSlashResultComponent(details, options, theme);
|
|
520
|
+
return createSlashResultComponent(details, options, theme, config.mainWindowRenderer, config.foregroundDetachShortcut);
|
|
498
521
|
});
|
|
499
522
|
|
|
500
523
|
pi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {
|
|
@@ -588,28 +611,31 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
588
611
|
label: "Subagent",
|
|
589
612
|
description: buildSubagentToolDescription(config),
|
|
590
613
|
parameters,
|
|
614
|
+
keepParameterDescriptions: true,
|
|
591
615
|
|
|
592
616
|
execute(id, params, signal, onUpdate, ctx) {
|
|
593
617
|
return executeSubagentCollapsed(id, params as SubagentParamsLike, signal ?? new AbortController().signal, onUpdate, ctx);
|
|
594
618
|
},
|
|
595
619
|
|
|
596
620
|
renderCall(args, theme) {
|
|
621
|
+
const gap = " ".repeat(config.mainWindowRenderer?.horizontalSpacing ?? 1);
|
|
622
|
+
const title = theme.fg("toolTitle", theme.bold("subagent"));
|
|
597
623
|
if (args.action) {
|
|
598
624
|
const target = args.agent || args.chainName || "";
|
|
599
625
|
return new Text(
|
|
600
|
-
`${
|
|
626
|
+
`${title}${gap}${args.action}${target ? `${gap}${theme.fg("accent", target)}` : ""}`,
|
|
601
627
|
0, 0,
|
|
602
628
|
);
|
|
603
629
|
}
|
|
604
630
|
if (args.workflowScript)
|
|
605
631
|
return new Text(
|
|
606
|
-
`${
|
|
632
|
+
`${title}${gap}${formatWorkflowManifest(args.workflowScript, args.async, false)}`,
|
|
607
633
|
0,
|
|
608
634
|
0,
|
|
609
635
|
);
|
|
610
|
-
const asyncLabel = args.async === true ? theme.fg("warning", "
|
|
636
|
+
const asyncLabel = args.async === true ? `${gap}${theme.fg("warning", "[async]")}` : "";
|
|
611
637
|
return new Text(
|
|
612
|
-
`${
|
|
638
|
+
`${title}${gap}${theme.fg("accent", args.agent || "?")}${asyncLabel}`,
|
|
613
639
|
0,
|
|
614
640
|
0,
|
|
615
641
|
);
|
|
@@ -620,7 +646,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
620
646
|
const renderedResult = { ...result, isError: context.isError };
|
|
621
647
|
return summaryInlineToolDisplay
|
|
622
648
|
? renderSubagentSummary(renderedResult, options, theme)
|
|
623
|
-
: renderSubagentResult(renderedResult, options, theme);
|
|
649
|
+
: renderSubagentResult(renderedResult, options, theme, undefined, config.mainWindowRenderer, config.foregroundDetachShortcut);
|
|
624
650
|
},
|
|
625
651
|
|
|
626
652
|
};
|
|
@@ -664,7 +690,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
664
690
|
}
|
|
665
691
|
});
|
|
666
692
|
|
|
667
|
-
registerSlashCommands(pi, state, {
|
|
693
|
+
registerSlashCommands(pi, state, {
|
|
694
|
+
fleetKeybindings: config.fleetKeybindings,
|
|
695
|
+
foregroundDetachShortcut: config.foregroundDetachShortcut,
|
|
696
|
+
});
|
|
668
697
|
pi.on("resources_discover", () => {
|
|
669
698
|
return {
|
|
670
699
|
promptPaths: [fileURLToPath(new URL("../../prompts", import.meta.url))],
|
|
@@ -810,15 +839,19 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
810
839
|
};
|
|
811
840
|
|
|
812
841
|
const resetSessionState = (ctx: ExtensionContext, recovering: boolean, previousSessionFile?: string | null) => {
|
|
842
|
+
state.sessionLineage = resolveSessionLineage({ sessionManager: ctx.sessionManager }, { parentHint: previousSessionFile ?? null });
|
|
843
|
+
state.handoffResumePending = false;
|
|
844
|
+
if (state.sessionLineage.length > 1) state.adoptedResultWindowStart = Date.now() - ADOPTED_RESULT_SLACK_MS;
|
|
845
|
+
else state.adoptedResultWindowStart = undefined;
|
|
813
846
|
state.widgetsSuspended = false;
|
|
814
847
|
state.baseCwd = ctx.cwd;
|
|
815
848
|
goalTurnId = 0;
|
|
816
849
|
state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
817
850
|
state.parentSessionFile = ctx.sessionManager.getSessionFile();
|
|
818
|
-
state.
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
851
|
+
state.trustedSessionRoots = [...new Set([
|
|
852
|
+
...(config.defaultSessionDir ? [path.resolve(expandTilde(config.defaultSessionDir))] : []),
|
|
853
|
+
...(state.parentSessionFile ? [getSubagentSessionRoot(state.parentSessionFile)] : []),
|
|
854
|
+
])];
|
|
822
855
|
state.subagentSpawns = {
|
|
823
856
|
sessionId: state.currentSessionId,
|
|
824
857
|
count: 0,
|
|
@@ -838,19 +871,39 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
838
871
|
}
|
|
839
872
|
}
|
|
840
873
|
state.lastUiContext = ctx;
|
|
874
|
+
let phaseStartedAt = Date.now();
|
|
841
875
|
refreshActiveAsyncCapacity();
|
|
876
|
+
logSlowPhase("active-capacity", phaseStartedAt);
|
|
877
|
+
phaseStartedAt = Date.now();
|
|
842
878
|
cleanupSessionArtifacts(ctx);
|
|
879
|
+
logSlowPhase("session-artifact-cleanup", phaseStartedAt);
|
|
843
880
|
state.foregroundControls.clear();
|
|
844
881
|
state.lastForegroundControlId = null;
|
|
882
|
+
phaseStartedAt = Date.now();
|
|
845
883
|
resetJobs(ctx);
|
|
884
|
+
logSlowPhase("reset-jobs", phaseStartedAt);
|
|
885
|
+
phaseStartedAt = Date.now();
|
|
846
886
|
restoreForegroundRunHistory(state, { resultsDir: DIRS.results });
|
|
887
|
+
logSlowPhase("foreground-history", phaseStartedAt);
|
|
888
|
+
phaseStartedAt = Date.now();
|
|
847
889
|
const adoptedJobs = restoreActiveJobs(ctx);
|
|
890
|
+
logSlowPhase("active-job-restore", phaseStartedAt);
|
|
848
891
|
state.handoffResumePending = adoptedJobs > 0 && state.lastUiContext?.hasUI === true;
|
|
892
|
+
phaseStartedAt = Date.now();
|
|
849
893
|
scheduledRunManager.bindSession(ctx);
|
|
894
|
+
logSlowPhase("scheduled-runs", phaseStartedAt);
|
|
895
|
+
phaseStartedAt = Date.now();
|
|
850
896
|
restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
|
|
897
|
+
logSlowPhase("slash-snapshots", phaseStartedAt);
|
|
898
|
+
phaseStartedAt = Date.now();
|
|
851
899
|
waitSubscriptionManager.restore();
|
|
900
|
+
logSlowPhase("wait-subscriptions", phaseStartedAt);
|
|
901
|
+
phaseStartedAt = Date.now();
|
|
852
902
|
startResultWatcher();
|
|
903
|
+
logSlowPhase("result-watcher-start", phaseStartedAt);
|
|
904
|
+
phaseStartedAt = Date.now();
|
|
853
905
|
primeExistingResults({ triggerTurn: !recovering });
|
|
906
|
+
logSlowPhase("result-prime", phaseStartedAt);
|
|
854
907
|
fleetStatus?.setContext(ctx);
|
|
855
908
|
};
|
|
856
909
|
|
|
@@ -905,6 +958,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
905
958
|
|
|
906
959
|
pi.on("session_shutdown", async () => {
|
|
907
960
|
state.widgetsSuspended = false;
|
|
961
|
+
clearTimeout(resultIndexCleanupTimer);
|
|
908
962
|
stopResultWatcher();
|
|
909
963
|
state.currentSessionId = null;
|
|
910
964
|
state.parentSessionFile = null;
|
|
@@ -9,6 +9,7 @@ export interface PublicSubagentExecutionParams {
|
|
|
9
9
|
concurrency?: unknown;
|
|
10
10
|
chainDir?: unknown;
|
|
11
11
|
workflowScript?: unknown;
|
|
12
|
+
output?: unknown;
|
|
12
13
|
resume?: unknown;
|
|
13
14
|
clarify?: unknown;
|
|
14
15
|
runFanoutBudget?: unknown;
|
|
@@ -33,7 +34,7 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
33
34
|
}
|
|
34
35
|
const action = params.action;
|
|
35
36
|
if (action !== undefined && (typeof action !== "string" || !action.trim())) {
|
|
36
|
-
return { ok: false, error: "action must be a non-empty management/control action, or omit action and
|
|
37
|
+
return { ok: false, error: "action must be a non-empty management/control action, or omit action and use workflowScript.", mode: "management" };
|
|
37
38
|
}
|
|
38
39
|
const normalizedAction = typeof action === "string" ? action.trim() : undefined;
|
|
39
40
|
if (params.clarify === true && params.workflowScript !== undefined) {
|
|
@@ -59,10 +60,16 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
59
60
|
if (params.workflowScript !== undefined) {
|
|
60
61
|
return { ok: false, error: "workflowScript execution must omit action; only schedule.create accepts action with workflowScript.", mode: "management" };
|
|
61
62
|
}
|
|
63
|
+
if (params.task !== undefined) {
|
|
64
|
+
return { ok: false, error: "Structured single-child task cannot be combined with a management/control action.", mode: "management" };
|
|
65
|
+
}
|
|
62
66
|
return { ok: true, params: { ...params, action: normalizedAction } };
|
|
63
67
|
}
|
|
64
|
-
if (
|
|
65
|
-
return { ok:
|
|
68
|
+
if (params.step !== undefined) {
|
|
69
|
+
return { ok: false, error: "step is only available with action='append-step'; it is not an execution mode.", mode: "workflow" };
|
|
70
|
+
}
|
|
71
|
+
if (params.workflowScript !== undefined && (params.agent !== undefined || params.task !== undefined)) {
|
|
72
|
+
return { ok: false, error: "Structured single-child execution cannot be combined with workflowScript.", mode: "workflow" };
|
|
66
73
|
}
|
|
67
74
|
if (params.chain !== undefined) {
|
|
68
75
|
return { ok: true, params };
|
|
@@ -70,8 +77,29 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
|
|
|
70
77
|
if (params.tasks !== undefined) {
|
|
71
78
|
return { ok: true, params };
|
|
72
79
|
}
|
|
73
|
-
if (params.agent !== undefined || params.task !== undefined
|
|
74
|
-
|
|
80
|
+
if (params.agent !== undefined || params.task !== undefined) {
|
|
81
|
+
if (typeof params.agent !== "string" || !params.agent.trim()) {
|
|
82
|
+
return { ok: false, error: "Structured single-child execution requires agent to be a non-empty string.", mode: "workflow" };
|
|
83
|
+
}
|
|
84
|
+
if (params.task !== undefined && typeof params.task !== "string") {
|
|
85
|
+
return { ok: false, error: "Structured single-child task must be a string when provided.", mode: "workflow" };
|
|
86
|
+
}
|
|
87
|
+
const { agent: _agent, task: _task, output, ...workflowDefaults } = params;
|
|
88
|
+
const child = {
|
|
89
|
+
agent: params.agent.trim(),
|
|
90
|
+
...(params.task !== undefined ? { task: params.task } : {}),
|
|
91
|
+
output: output === undefined ? true : output,
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
ok: true,
|
|
95
|
+
params: {
|
|
96
|
+
...workflowDefaults,
|
|
97
|
+
workflowScript: `console.info("Converted structured single-child request to workflow runs.run('main', ...)."); return runs.run("main", ${JSON.stringify(child)})`,
|
|
98
|
+
} as T,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (typeof params.workflowScript !== "string" || !params.workflowScript.trim()) {
|
|
102
|
+
return { ok: false, error: "Execution requires one mode: { agent, task? }, { chain: [...] }, { tasks: [...] }, or { workflowScript }.", mode: "workflow" };
|
|
75
103
|
}
|
|
76
|
-
return { ok:
|
|
104
|
+
return { ok: true, params };
|
|
77
105
|
}
|
|
@@ -21,6 +21,7 @@ import { sanitizeDisplayText, truncateDisplayText } from "../shared/display-text
|
|
|
21
21
|
import { readStatus } from "../shared/utils.ts";
|
|
22
22
|
import { SubagentParams } from "./schemas.ts";
|
|
23
23
|
import { normalizePublicSubagentExecution } from "./public-execution.ts";
|
|
24
|
+
import { ASYNC_STATUS_SNAPSHOT_KIND, ASYNC_STATUS_SNAPSHOT_VERSION, buildAsyncStatusSnapshotForState } from "../runs/background/async-status-snapshot.ts";
|
|
24
25
|
|
|
25
26
|
export const SUBAGENT_RPC_PROTOCOL_VERSION = 1;
|
|
26
27
|
export const SUBAGENT_RPC_REQUEST_EVENT = "subagents:rpc:v1:request";
|
|
@@ -381,6 +382,7 @@ function pingData(ctx: ExtensionContext | null) {
|
|
|
381
382
|
capabilities: {
|
|
382
383
|
status: true,
|
|
383
384
|
fleetStatus: { version: 1 },
|
|
385
|
+
asyncStatusSnapshot: { kind: ASYNC_STATUS_SNAPSHOT_KIND, version: ASYNC_STATUS_SNAPSHOT_VERSION },
|
|
384
386
|
asyncSpawn: true,
|
|
385
387
|
steer: true,
|
|
386
388
|
nonRecoveringSteer: true,
|
|
@@ -550,13 +552,15 @@ async function handleRequest(
|
|
|
550
552
|
request.method,
|
|
551
553
|
{ action: "status", ...normalizeTargetParams(request.params, "status") },
|
|
552
554
|
);
|
|
555
|
+
const sessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
553
556
|
return {
|
|
554
557
|
...status,
|
|
555
558
|
fleet: buildFleetStatus(
|
|
556
559
|
options.state,
|
|
557
560
|
fleetKeys,
|
|
558
|
-
|
|
561
|
+
sessionId,
|
|
559
562
|
),
|
|
563
|
+
asyncSnapshot: buildAsyncStatusSnapshotForState(options.state, sessionId),
|
|
560
564
|
};
|
|
561
565
|
}
|
|
562
566
|
if (request.method === "steer") {
|
|
@@ -89,11 +89,11 @@ const AcceptanceOverride = Type.Unsafe({
|
|
|
89
89
|
{ type: "boolean", enum: [false] },
|
|
90
90
|
{ type: "object", additionalProperties: true },
|
|
91
91
|
],
|
|
92
|
-
description: `Optional acceptance policy.
|
|
92
|
+
description: `Optional acceptance policy. Omit for read-only calls. Example: { level: "checked", evidence: ["commands-run", "changed-files"] }. Supported evidence kinds: ${AcceptanceEvidenceKinds.join(", ")}. Evidence levels end at verified; use acceptance.review.required for review.`,
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
const AgentContractOverride = Type.Object({
|
|
96
|
-
version: Type.Integer({
|
|
96
|
+
version: Type.Integer({ minimum: 1, maximum: 1, description: "Enable compatibility behavior for this run/child." }),
|
|
97
97
|
}, { additionalProperties: false, description: "Compatibility behavior. Omit for the default behavior." });
|
|
98
98
|
|
|
99
99
|
const ChainGateOverride = Type.String({
|
|
@@ -255,49 +255,49 @@ const ControlOverrides = Type.Object({
|
|
|
255
255
|
});
|
|
256
256
|
|
|
257
257
|
const SubagentParamProperties = {
|
|
258
|
-
agent: Type.Optional(Type.String({ description: "Agent
|
|
259
|
-
task: Type.Optional(Type.String({ description: "
|
|
258
|
+
agent: Type.Optional(Type.String({ description: "Agent for one-child execution, or target for agent management actions." })),
|
|
259
|
+
task: Type.Optional(Type.String({ description: "Optional one-child task. Requires agent; cannot combine with action or workflowScript." })),
|
|
260
260
|
resume: Type.Optional(Type.String({ description: "Retained child run id for a workflowScript runs.run/runs.all item. Mutually exclusive with agent; task supplies the follow-up." })),
|
|
261
261
|
// Management action (when present, tool operates in management mode)
|
|
262
262
|
action: Type.Optional(Type.String({ minLength: 1,
|
|
263
|
-
description: "Optional management/control action. Omit this field for workflowScript execution; use it only for management/control actions."
|
|
263
|
+
description: "Optional management/control action. Omit this field for structured single-child or workflowScript execution; use it only for management/control actions."
|
|
264
264
|
})),
|
|
265
265
|
name: Type.Optional(Type.String({ description: "Human-readable name for action='schedule.create'." })),
|
|
266
266
|
id: Type.Optional(Type.String({
|
|
267
|
-
description: "Run id
|
|
267
|
+
description: "Run id/prefix for status/debug.run, interrupt, steer, append-step, approve-checkpoint, reject-checkpoint, or mission."
|
|
268
268
|
})),
|
|
269
269
|
runId: Type.Optional(Type.String({
|
|
270
|
-
description: "Target run ID for
|
|
270
|
+
description: "Target run ID for debug.run, interrupt, steer, append-step, or mission.attach-run. Prefer id."
|
|
271
271
|
})),
|
|
272
272
|
dir: Type.Optional(Type.String({
|
|
273
|
-
description: "Async run directory for
|
|
273
|
+
description: "Async run directory for status/debug.run, stop, resume, or steer."
|
|
274
274
|
})),
|
|
275
275
|
handoffPath: Type.Optional(Type.String({ description: "worktree.discard manifest." })),
|
|
276
276
|
index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child or transcript." })),
|
|
277
277
|
view: Type.Optional(Type.String({
|
|
278
278
|
enum: ["fleet", "transcript"],
|
|
279
|
-
description: "Optional status view
|
|
279
|
+
description: "Optional status view: 'fleet' for a read-only fleet surface, or 'transcript' with id/dir (and optional index) to tail a run transcript.",
|
|
280
280
|
})),
|
|
281
281
|
lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
|
|
282
282
|
topic: Type.Optional(Type.String()),
|
|
283
283
|
message: Type.Optional(Type.String({ description: "Follow-up message for resume, live guidance for steer, or optional startup prompt for project.open." })),
|
|
284
|
-
mode: Type.Optional(Type.String({ enum: ["steer", "follow_up", "auto"], description: "Delivery mode for action='steer'
|
|
285
|
-
steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls
|
|
286
|
-
additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only
|
|
287
|
-
scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session
|
|
284
|
+
mode: Type.Optional(Type.String({ enum: ["steer", "follow_up", "auto"], description: "Delivery mode for action='steer': steer interrupts at the next safe point (default), follow_up waits for the next turn boundary, auto follows up mid-turn." })),
|
|
285
|
+
steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls; false for extension RPC steering." })),
|
|
286
|
+
additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only." })),
|
|
287
|
+
scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session." })),
|
|
288
288
|
target: Type.Optional(Type.String({ enum: ["main", "children", "child"], description: "Target for watchdog actions." })),
|
|
289
289
|
focus: Type.Optional(Type.Boolean({ description: "Focus the new Herdr pane for inspector.open or project.open." })),
|
|
290
290
|
thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [false] }], description: "Thinking level for action='watchdog.configure' (off/minimal/low/medium/high/xhigh/max, inherit, or false for off)." })),
|
|
291
|
-
schedule: Type.Optional(Type.String({ deprecated: true, description: "
|
|
292
|
-
scheduleName: Type.Optional(Type.String({ deprecated: true, description: "
|
|
291
|
+
schedule: Type.Optional(Type.String({ deprecated: true, description: "Deprecated. Use action='schedule.create' with at." })),
|
|
292
|
+
scheduleName: Type.Optional(Type.String({ deprecated: true, description: "Deprecated. Use name." })),
|
|
293
293
|
at: Type.Optional(Type.String({ description: "One-shot trigger for action='schedule.create': a relative delay such as '+10m' or an ISO timestamp with timezone." })),
|
|
294
294
|
every: Type.Optional(Type.String({ description: "Fixed recurring interval for action='schedule.create', such as '30m', '6h', '2d', or '2w'." })),
|
|
295
|
-
on: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "integer" }], description: "
|
|
296
|
-
timezone: Type.Optional(Type.String({ description: "
|
|
295
|
+
on: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "integer" }], description: "Reserved for a later calendar schedule slice." })),
|
|
296
|
+
timezone: Type.Optional(Type.String({ description: "Reserved for a later calendar schedule slice." })),
|
|
297
297
|
overlap: Type.Optional(Type.String({ enum: ["skip"], description: "Overlap policy. This slice supports skip only." })),
|
|
298
298
|
catchUp: Type.Optional(Type.String({ enum: ["none", "latest"], description: "Missed occurrence policy for recurring schedules. Defaults to latest." })),
|
|
299
299
|
missionId: Type.Optional(Type.String({ description: "Mission id." })),
|
|
300
|
-
mission: Type.Optional(Type.Unsafe({ ...MissionLaunchOverride, description: "Mission object, or false for no mission.
|
|
300
|
+
mission: Type.Optional(Type.Unsafe({ ...MissionLaunchOverride, description: "Mission object, or false for no mission. Requires exactly one non-empty title or summary; goal may only be true and then requires budget.tokens." })),
|
|
301
301
|
missionUpdate: Type.Optional(Type.Unsafe({ ...MissionUpdateOverride, description: "Mission update: objective, goal false or {paused:boolean}, budget, summary, labels, decisions, artifacts, or delivery receipts." })),
|
|
302
302
|
missionStatus: Type.Optional(Type.String({ description: "Mission status." })),
|
|
303
303
|
missionScope: Type.Optional(Type.String({ description: "Mission list scope: project (default) or global pointer index." })),
|
|
@@ -316,10 +316,10 @@ const SubagentParamProperties = {
|
|
|
316
316
|
],
|
|
317
317
|
description: "Agent/chain config for create/update. Object or JSON string; presence of steps creates a chain."
|
|
318
318
|
})),
|
|
319
|
-
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body
|
|
319
|
+
workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body; use explicit return for output. Available: runs.run(key, {agent, task, ...}) and runs.run(key, {resume, task}), runs.all([...]), runs.status(id), runs.ref(s), await prompts.render(ref, vars?), emit, console; mission workflows also have async state.get/set. Scripts start async by default. Set worktree:true at workflow or child level for a separate managed worktree; child fields override workflow defaults. Compose sequential and parallel phases dynamically. No filesystem, shell, Pi tools, or host globals." })),
|
|
320
320
|
chain: Type.Optional(Type.Unsafe({
|
|
321
321
|
anyOf: [Type.Array(ChainItem, { minItems: 1, description: "Sequential chain of steps" })],
|
|
322
|
-
description: "Sequential chain of steps. Steps support template variables {task}, {previous}, {chain_dir}, and {outputs.name}; use as: \"name\" on a step to bind {outputs.name} for later steps
|
|
322
|
+
description: "Sequential chain of steps. Steps support template variables {task}, {previous}, {chain_dir}, and {outputs.name}; use as: \"name\" on a step to bind {outputs.name} for later steps; { checkpoint: \"name\", message?: \"...\" } pauses for approve-checkpoint / reject-checkpoint; gateOn: \"acceptance\" advances on acceptance instead of execution.",
|
|
323
323
|
})),
|
|
324
324
|
tasks: Type.Optional(Type.Unsafe({
|
|
325
325
|
anyOf: [
|
|
@@ -328,27 +328,28 @@ const SubagentParamProperties = {
|
|
|
328
328
|
],
|
|
329
329
|
description: "Top-level parallel tasks array, or a single dynamic fanout child template when expand/collect are present.",
|
|
330
330
|
})),
|
|
331
|
-
chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "terminal", "milestones", "live-card"], description: "WorkflowScript chat progress projection
|
|
332
|
-
worktree: Type.Optional(Type.Boolean({ description: "Managed child isolation. true gives each workflow child a separate git worktree;
|
|
331
|
+
chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "terminal", "milestones", "live-card"], description: "WorkflowScript chat progress projection: auto shows a live in-chat card for watched foreground workflows in the same Git repository, milestones for background runs, terminal otherwise." })),
|
|
332
|
+
worktree: Type.Optional(Type.Boolean({ description: "Managed child isolation. true gives each workflow child a separate git worktree; individual runs.run/runs.all items can override with worktree:false." })),
|
|
333
333
|
step: Type.Optional(Type.Unsafe({ ...ChainItem, description: "One chain step for action='append-step' only. Not an execution mode." })),
|
|
334
334
|
context: Type.Optional(Type.String({
|
|
335
335
|
enum: ["fresh", "fork"],
|
|
336
|
-
description: "'fresh' or 'fork' to branch from parent session. Explicit context overrides every child in the invocation. If omitted, each requested agent uses its own defaultContext; agents without
|
|
336
|
+
description: "'fresh' or 'fork' to branch from parent session. Explicit context overrides every child in the invocation. If omitted, each requested agent uses its own defaultContext; agents without one run 'fork' fresh.",
|
|
337
337
|
})),
|
|
338
338
|
async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
|
|
339
|
-
clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution
|
|
340
|
-
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout.
|
|
341
|
-
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias
|
|
339
|
+
clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution; explicit true keeps the run foreground." })),
|
|
340
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout. Defaults to config timeoutMs or 30m for foreground/single async runs. Alias maxRuntimeMs." })),
|
|
341
|
+
maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias of timeoutMs." })),
|
|
342
|
+
toolTimeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Optional hard per-tool-call timeout in milliseconds." })),
|
|
342
343
|
turnBudget: Type.Optional(TurnBudgetOverride),
|
|
343
344
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
344
345
|
usageBudget: Type.Optional(UsageBudgetOverride),
|
|
345
|
-
agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default
|
|
346
|
+
agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default 'both')." })),
|
|
346
347
|
cwd: Type.Optional(Type.String({ description: "Execution cwd, or target project directory for project.open/status/close." })),
|
|
347
348
|
artifacts: Type.Optional(Type.Boolean({ description: "Write debug artifacts (default: true)" })),
|
|
348
349
|
includeProgress: Type.Optional(Type.Boolean({ description: "Include full progress in result (default: false)" })),
|
|
349
350
|
share: Type.Optional(Type.Boolean({ description: "Upload session to GitHub Gist for sharing (default: false)" })),
|
|
350
351
|
sessionDir: Type.Optional(
|
|
351
|
-
Type.String({ description: "Directory to store session logs (default: temp
|
|
352
|
+
Type.String({ description: "Directory to store session logs (default: temp)." }),
|
|
352
353
|
),
|
|
353
354
|
control: Type.Optional(ControlOverrides),
|
|
354
355
|
// Workflow defaults forwarded to each runs.run/runs.all child unless overridden there.
|
|
@@ -372,10 +373,10 @@ const { step: _legacyChainStep, ...subagentParamPropertiesWithoutStep } = Subage
|
|
|
372
373
|
const trimmedSubagentParamProperties = {
|
|
373
374
|
...subagentParamPropertiesWithoutStep,
|
|
374
375
|
id: Type.Optional(Type.String({
|
|
375
|
-
description: "Run id
|
|
376
|
+
description: "Run id/prefix for status/debug.run, interrupt, steer, or mission.attach-run."
|
|
376
377
|
})),
|
|
377
378
|
runId: Type.Optional(Type.String({
|
|
378
|
-
description: "Target run ID for
|
|
379
|
+
description: "Target run ID for debug.run, interrupt, steer, or mission.attach-run. Prefer id."
|
|
379
380
|
})),
|
|
380
381
|
};
|
|
381
382
|
const SubagentParamsSchema = Type.Object(SubagentParamProperties);
|
|
@@ -8,11 +8,12 @@ const CUSTOM_TOOL_DESCRIPTION_MAX_BYTES = 50 * 1024;
|
|
|
8
8
|
|
|
9
9
|
export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
|
|
10
10
|
• Use { action: "list" } before execution and only run executable/non-disabled agents.
|
|
11
|
-
• Keep execution and management separate: omit action for workflowScript execution; use action only for management/control.
|
|
12
|
-
• Async/background runs are the default. Use async:false only when a blocking foreground result is needed. Do not sleep or poll status just to wait; use subagent_wait only when the current request must finish in this turn.
|
|
11
|
+
• Keep execution and management separate: omit action for structured single-child or workflowScript execution; use action only for management/control.
|
|
12
|
+
• Async/background runs are the default. Use async:false only when a blocking foreground result is needed. After an async launch, continue independent work only until its next dependency barrier; consume the result before work that depends on it. Do not sleep or poll status just to wait; use subagent_wait only when the current request must finish in this turn.
|
|
13
13
|
• Ordinary child subagents are not orchestrators. Only explicitly configured fanout children may use the child-safe subagent tool, still bounded by depth/session limits.
|
|
14
|
+
• Oracle/advisor consultations should use supervisor dialogue for material unknowns when available; request one-shot only when desired.
|
|
14
15
|
• Keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers for independent review, then have the parent synthesize and apply fixes.
|
|
15
|
-
• Async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs,
|
|
16
|
+
• Async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, status via { action: "status", id }, and lifecycle diagnostics via { action: "debug.run", id }. Include output paths and residual risks when reporting results.`;
|
|
16
17
|
|
|
17
18
|
export const FULL_SUBAGENT_TOOL_DESCRIPTION = `Delegate one child with { agent, task }, compose steps with { chain }, fan out with { tasks }, or orchestrate with { workflowScript }; omit action. Use action only for management/control actions.
|
|
18
19
|
|
|
@@ -21,7 +22,7 @@ EXECUTION (use exactly one mode):
|
|
|
21
22
|
• SINGLE: { agent, task? } launches one child. Omit task for a self-contained agent.
|
|
22
23
|
• CHAIN: { chain: [{ agent: "explorer", task: "..." }, { agent: "builder", task: "Implement from {previous}" }] } runs sequential steps. Steps support template variables {task}, {previous}, {chain_dir}, and {outputs.name}; use as: "name" on a step to bind {outputs.name} for later steps, phase/label for status readability, outputSchema for structured results, and { checkpoint: "name", message?: "..." } for human gates paused until approve-checkpoint / reject-checkpoint. Set gateOn: "acceptance" to advance on acceptance instead of execution. agentContract: { version: 1 } gives generic result projections.
|
|
23
24
|
• PARALLEL: { tasks: [{ agent, task }, ...], concurrency? } runs top-level parallel children (count: N repeats one template). Parallel groups also work inside chain steps with { parallel: [...] }, plus dynamic fanout via { expand: { from: { output, path }, item?, maxItems, parallel: {...}, collect: { as, outputSchema? } } }.
|
|
24
|
-
• SCRIPTED WORKFLOW: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. workflowScript is an ordinary JavaScript statement body, so use an explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("
|
|
25
|
+
• SCRIPTED WORKFLOW: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. workflowScript is an ordinary JavaScript statement body, so use an explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\n")\`. Scripts start asynchronously by default; pass async:false only for a small foreground run. Same-repo foreground workflows default to a live in-chat card; set chatProgress to auto, off, terminal, milestones, or live-card to control that projection. Workflow-level child controls default onto each runs.run launch, and explicit child fields override them. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable plain task text, then pass the result explicitly as task. Use {action:"children.list"} to list recent retained children with resumable/not-resumable states. Resume only rows reported resumable; {action:"resume", id:"run-id", message:"..."} keeps the stored agent/model/tool contract, or use runs.run(key, {resume:"run-id", task:"follow-up"}) inside workflowScript. Workflow resumes wait for completed output, and loops must continue from each latest returned runId. If no resumable child is listed, launch a same-role fallback challenge and label it as fallback. For repository mutation lanes, set worktree:true on a direct single child, chain, workflow, or individual runs.run/runs.all item for managed isolation; each parallel child gets a separate worktree and handoff artifact. A workflow usageBudget is enforced once across the workflow. Available globals are runs.run, runs.all, runs.status, runs.ref/refs, prompts.render, emit, console, and standard JavaScript only. Workflows get async state.get(key) and state.set(key, JSONValue) through their automatic or explicit mission; mission:false workflows do not have a state global. Scripts cannot access filesystem, shell, arbitrary Pi tools, or host globals.
|
|
25
26
|
• Sequential example: { workflowScript: "const a = await runs.run('analyze', {agent:'agent-a', task:'Analyze the request'}); return (await runs.run('plan', {agent:'agent-b', task:'Plan from: '+a.output})).output" }
|
|
26
27
|
• Parallel example: { workflowScript: "const [a,b] = await runs.all([{key:'correctness',agent:'agent-a',task:'Review correctness'},{key:'tests',agent:'agent-b',task:'Review tests'}]); return {correctness:a.output,tests:b.output}" }
|
|
27
28
|
• Optional context is "fresh" or "fork". timeoutMs/maxRuntimeMs apply to foreground and async runs; foreground defaults to 30 minutes absent call/agent. Omit acceptance for read-only calls; evidence levels end at verified, and acceptance.review.required requests independent writer review. For one host-run verification command, gate: "npm test" on a runs.run/runs.all item is shorthand; it cannot be combined with acceptance.
|
|
@@ -41,7 +42,7 @@ export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `Delegate one child with { agen
|
|
|
41
42
|
EXECUTE:
|
|
42
43
|
• Call { action:"list" } first and use only executable/non-disabled agents.
|
|
43
44
|
• SINGLE {agent, task?}; CHAIN {chain:[{agent,task,...}]} with {previous}/{outputs.name} templates and { checkpoint } human gates; PARALLEL {tasks:[...], concurrency?} with optional count and in-chain { parallel: [...] } / dynamic { expand }+{ collect } fanout.
|
|
44
|
-
• SCRIPT {workflowScript:"return runs.run('main', {agent:'worker', task:'...'})"}. Use stable-key runs.run for one child and runs.all for parallel work. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable task text and pass it explicitly to runs.run. Use {action:"children.list"} for
|
|
45
|
+
• SCRIPT {workflowScript:"return runs.run('main', {agent:'worker', task:'...'})"}. Use stable-key runs.run for one child and runs.all for parallel work. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable task text and pass it explicitly to runs.run. Use {action:"children.list"} for recent retained children and resume only rows reported resumable, with {action:"resume", id, message} or runs.run(key,{resume:"run-id",task:"follow-up"}); workflow resumes wait for completion and loops continue from the latest returned runId. Workflows get async state.get/state.set through their automatic or explicit mission; mission:false does not. Scripts are ordinary JavaScript statement bodies; use explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\n")\`. Use JavaScript for sequence, branching, retries, and aggregation. For repository mutation lanes, use worktree:true on a direct single child, chain, or runs.run/runs.all item for managed isolation. Scripts start async by default; async:false is the foreground escape hatch and auto-enables a same-repo live chat card unless chatProgress is off/terminal/milestones.
|
|
45
46
|
• Example: {workflowScript:"const [a,b]=await runs.all([{key:'a',agent:'agent-a',task:'Implement A',worktree:true},{key:'b',agent:'agent-b',task:'Implement B',worktree:true}]); return [a.output,b.output]"}
|
|
46
47
|
• context can be fresh or fork. timeoutMs/maxRuntimeMs apply to foreground and async runs; foreground defaults to 30 minutes absent call/agent. Omit acceptance for read-only calls.
|
|
47
48
|
|
|
@@ -72,10 +73,10 @@ export interface ToolDescriptionOptions {
|
|
|
72
73
|
|
|
73
74
|
export function resolveToolDescriptionMode(config: Pick<ExtensionConfig, "toolDescriptionMode">, options?: ToolDescriptionOptions): ToolDescriptionMode {
|
|
74
75
|
const mode = config.toolDescriptionMode;
|
|
75
|
-
if (mode === undefined) return "
|
|
76
|
+
if (mode === undefined) return "compact";
|
|
76
77
|
if (isToolDescriptionMode(mode)) return mode;
|
|
77
78
|
warn(options, `Ignoring invalid toolDescriptionMode ${JSON.stringify(mode)}; expected "full", "compact", or "custom".`);
|
|
78
|
-
return "
|
|
79
|
+
return "compact";
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
function customDescriptionPaths(options?: ToolDescriptionOptions): string[] {
|
|
@@ -178,8 +179,8 @@ export function buildSubagentToolDescription(config: Pick<ExtensionConfig, "tool
|
|
|
178
179
|
const custom = loadCustomToolDescription(options);
|
|
179
180
|
if (custom) description = withMandatorySafetyGuidance(custom);
|
|
180
181
|
else {
|
|
181
|
-
warn(options, `${CUSTOM_TOOL_DESCRIPTION_FILE} was not found or valid for toolDescriptionMode "custom"; using
|
|
182
|
-
description =
|
|
182
|
+
warn(options, `${CUSTOM_TOOL_DESCRIPTION_FILE} was not found or valid for toolDescriptionMode "custom"; using compact description.`);
|
|
183
|
+
description = COMPACT_SUBAGENT_TOOL_DESCRIPTION;
|
|
183
184
|
}
|
|
184
185
|
} else description = FULL_SUBAGENT_TOOL_DESCRIPTION;
|
|
185
186
|
return config.legacyChainControls === false ? withoutLegacyChainControlGuidance(description) : description;
|
|
@@ -10,7 +10,9 @@ import { writeAtomicJson } from "../../shared/atomic-json.ts";
|
|
|
10
10
|
import { DIRS, type Details, type SubagentState } from "../../shared/types.ts";
|
|
11
11
|
import { readStatus } from "../../shared/utils.ts";
|
|
12
12
|
import { resolveSubagentRunId } from "../../runs/background/run-id-resolver.ts";
|
|
13
|
+
import { resolveNodeExecutable } from "../../shared/node-executable.ts";
|
|
13
14
|
import { createHerdrClient, detectHerdr, type HerdrClient, type HerdrErrorCode, type HerdrResult } from "./client.ts";
|
|
15
|
+
import { formatShellCommand } from "./shell-command.ts";
|
|
14
16
|
|
|
15
17
|
export const HERDR_INSPECTOR_ACTIONS = ["inspector.open", "inspector.status", "inspector.close"] as const;
|
|
16
18
|
export type HerdrInspectorAction = typeof HERDR_INSPECTOR_ACTIONS[number];
|
|
@@ -45,6 +47,7 @@ interface InspectorDeps {
|
|
|
45
47
|
client?: HerdrClient;
|
|
46
48
|
missions?: MissionStoreConfig;
|
|
47
49
|
authorityPolicy?: AuthorityPolicyConfig;
|
|
50
|
+
sessionRoots?: string[];
|
|
48
51
|
cwd: string;
|
|
49
52
|
signal?: AbortSignal;
|
|
50
53
|
now?: () => Date;
|
|
@@ -84,16 +87,11 @@ function extractPaneId(value: unknown): string | undefined {
|
|
|
84
87
|
return undefined;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function inspectorCommand(input: { runnerPath: string; asyncDir: string; runId: string; index?: number; missionPath?: string; allowSteer: boolean; allowStop: boolean }): string {
|
|
93
|
-
const args = [process.execPath, input.runnerPath, "--async-dir", input.asyncDir, "--run-id", input.runId, "--allow-steer", String(input.allowSteer), "--allow-stop", String(input.allowStop)];
|
|
90
|
+
function inspectorCommand(input: { runnerPath: string; asyncDir: string; runId: string; index?: number; missionPath?: string; allowSteer: boolean; allowStop: boolean; sessionRoots: string[] }): string {
|
|
91
|
+
const args = [input.runnerPath, "--async-dir", input.asyncDir, "--run-id", input.runId, "--allow-steer", String(input.allowSteer), "--allow-stop", String(input.allowStop), "--session-roots", JSON.stringify(input.sessionRoots)];
|
|
94
92
|
if (input.index !== undefined) args.push("--index", String(input.index));
|
|
95
93
|
if (input.missionPath) args.push("--mission-path", input.missionPath);
|
|
96
|
-
return
|
|
94
|
+
return formatShellCommand(resolveNodeExecutable(), args);
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
function missionForRun(asyncDir: string, cwd: string, config: MissionStoreConfig | undefined, runId: string): { id: string; path: string } | undefined {
|
|
@@ -114,6 +112,12 @@ function pathWithin(base: string, candidate: string): boolean {
|
|
|
114
112
|
return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`);
|
|
115
113
|
}
|
|
116
114
|
|
|
115
|
+
function herdrSessionRoots(target: { runId: string }, deps: InspectorDeps): string[] {
|
|
116
|
+
const roots = deps.sessionRoots ?? deps.state?.trustedSessionRoots ?? [];
|
|
117
|
+
const job = deps.state?.asyncJobs.get(target.runId) ?? deps.state?.fleetJobs?.get(target.runId);
|
|
118
|
+
return [...new Set([...roots, ...(job?.sessionRoot ? [job.sessionRoot] : [])])];
|
|
119
|
+
}
|
|
120
|
+
|
|
117
121
|
function isTrustedAsyncDir(asyncDir: string, deps: InspectorDeps): boolean {
|
|
118
122
|
try {
|
|
119
123
|
if (fs.lstatSync(asyncDir).isSymbolicLink() || !fs.statSync(asyncDir).isDirectory()) return false;
|
|
@@ -204,6 +208,7 @@ export async function handleHerdrInspectorAction(action: HerdrInspectorAction, p
|
|
|
204
208
|
missionPath: mission?.path,
|
|
205
209
|
allowSteer: resolveAuthorityDecision({ action: "steerRun", policy: deps.authorityPolicy }) === "auto",
|
|
206
210
|
allowStop: resolveAuthorityDecision({ action: "stopRun", policy: deps.authorityPolicy }) === "auto",
|
|
211
|
+
sessionRoots: herdrSessionRoots(target, deps),
|
|
207
212
|
});
|
|
208
213
|
const started = await client.run(["pane", "run", paneId, command], { timeoutMs: 15_000, signal: deps.signal });
|
|
209
214
|
if (started.ok === false) {
|