@xingular/harness 1.1.1 → 1.3.1-unstable.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 +48 -0
- package/README.md +36 -28
- package/dist/cli.js +10837 -6159
- package/dist/sdk/accounts/accounts.d.ts +27 -3
- package/dist/sdk/accounts/accounts.js +78 -24
- package/dist/sdk/accounts/secrets.d.ts +12 -0
- package/dist/sdk/accounts/secrets.js +114 -7
- package/dist/sdk/accounts/store.d.ts +26 -1
- package/dist/sdk/accounts/store.js +1 -1
- package/dist/sdk/agents/builtin.js +18 -4
- package/dist/sdk/agents/planResearch.d.ts +27 -0
- package/dist/sdk/agents/planResearch.js +82 -0
- package/dist/sdk/agents/profiles.d.ts +5 -1
- package/dist/sdk/agents/profiles.js +21 -5
- package/dist/sdk/annotations/pageScript.js +349 -48
- package/dist/sdk/annotations/part.js +34 -23
- package/dist/sdk/annotations/store.d.ts +7 -0
- package/dist/sdk/annotations/store.js +13 -5
- package/dist/sdk/browser/backend.d.ts +16 -0
- package/dist/sdk/browser/backend.js +17 -0
- package/dist/sdk/browser/contracts.d.ts +19 -0
- package/dist/sdk/browser/contracts.js +116 -0
- package/dist/sdk/browser/tools.d.ts +1 -4
- package/dist/sdk/browser/tools.js +14 -91
- package/dist/sdk/capabilities/runSnapshot.js +10 -4
- package/dist/sdk/cli.js +5 -4
- package/dist/sdk/commands/list.d.ts +3 -0
- package/dist/sdk/commands/list.js +75 -0
- package/dist/sdk/commands/registry.d.ts +258 -0
- package/dist/sdk/commands/registry.js +48 -0
- package/dist/sdk/commands/routes.d.ts +16 -0
- package/dist/sdk/commands/routes.js +33 -0
- package/dist/sdk/core/commandBudget.d.ts +23 -0
- package/dist/sdk/core/commandBudget.js +57 -0
- package/dist/sdk/core/index.js +17 -1
- package/dist/sdk/core/interactiveProfile.d.ts +46 -0
- package/dist/sdk/core/interactiveProfile.js +143 -0
- package/dist/sdk/core/shell.d.ts +20 -0
- package/dist/sdk/core/shell.js +73 -0
- package/dist/sdk/core/workspace.d.ts +13 -0
- package/dist/sdk/core/workspace.js +88 -0
- package/dist/sdk/core/workspaceBashTool.d.ts +20 -0
- package/dist/sdk/core/workspaceBashTool.js +50 -0
- package/dist/sdk/core/workspaceSandbox.d.ts +41 -0
- package/dist/sdk/core/workspaceSandbox.js +360 -0
- package/dist/sdk/core/workspaceWriteOperations.d.ts +33 -0
- package/dist/sdk/core/workspaceWriteOperations.js +224 -0
- package/dist/sdk/doctor/checks.js +71 -12
- package/dist/sdk/doctor/types.d.ts +2 -2
- package/dist/sdk/harness.d.ts +7 -0
- package/dist/sdk/harness.js +42 -2
- package/dist/sdk/hub/hub.js +5 -3
- package/dist/sdk/index.d.ts +8 -3
- package/dist/sdk/index.js +4 -1
- package/dist/sdk/pi/runtime.d.ts +2 -1
- package/dist/sdk/pi/runtime.js +2 -2
- package/dist/sdk/plugins/builtin.js +32 -15
- package/dist/sdk/plugins/builtinRegistry.d.ts +10 -3
- package/dist/sdk/plugins/builtinRegistry.js +30 -8
- package/dist/sdk/plugins/catalogue.js +2 -1
- package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
- package/dist/sdk/plugins/commandPrefix.js +21 -0
- package/dist/sdk/plugins/firstParty.d.ts +1 -1
- package/dist/sdk/plugins/firstParty.js +8 -2
- package/dist/sdk/plugins/install.d.ts +6 -2
- package/dist/sdk/plugins/install.js +29 -5
- package/dist/sdk/plugins/inventory.js +6 -5
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +2 -1
- package/dist/sdk/plugins/surfaces.d.ts +5 -0
- package/dist/sdk/plugins/surfaces.js +11 -0
- package/dist/sdk/plugins/types.d.ts +15 -0
- package/dist/sdk/prompt/compile.d.ts +14 -0
- package/dist/sdk/prompt/compile.js +65 -31
- package/dist/sdk/prompt/standard.d.ts +17 -0
- package/dist/sdk/prompt/standard.js +21 -0
- package/dist/sdk/server/methods.js +22 -2
- package/dist/sdk/server/remote.js +36 -11
- package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
- package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
- package/dist/sdk/sessions/continuation.d.ts +7 -2
- package/dist/sdk/sessions/continuation.js +11 -6
- package/dist/sdk/sessions/contracts.d.ts +190 -4
- package/dist/sdk/sessions/contracts.js +71 -8
- package/dist/sdk/sessions/control.d.ts +72 -0
- package/dist/sdk/sessions/control.js +158 -0
- package/dist/sdk/sessions/initialBash.d.ts +7 -0
- package/dist/sdk/sessions/initialBash.js +7 -0
- package/dist/sdk/sessions/planMode.d.ts +14 -3
- package/dist/sdk/sessions/planMode.js +74 -17
- package/dist/sdk/sessions/registry.d.ts +1 -1
- package/dist/sdk/sessions/registry.js +2 -2
- package/dist/sdk/sessions/runStore.d.ts +14 -0
- package/dist/sdk/sessions/runStore.js +38 -1
- package/dist/sdk/sessions/runner.d.ts +40 -2
- package/dist/sdk/sessions/runner.js +161 -13
- package/dist/sdk/sessions/sessions.d.ts +23 -3
- package/dist/sdk/sessions/sessions.js +225 -22
- package/dist/sdk/sessions/subagentService.d.ts +24 -0
- package/dist/sdk/sessions/subagentService.js +225 -26
- package/dist/sdk/sessions/subagents.d.ts +10 -1
- package/dist/sdk/sessions/subagents.js +4 -0
- package/dist/sdk/sessions/workSlice.d.ts +106 -0
- package/dist/sdk/sessions/workSlice.js +329 -0
- package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
- package/dist/sdk/sessions/workspaceBoundary.js +46 -0
- package/dist/sdk/terminal/browserControl.js +2 -1
- package/dist/sdk/terminal/controller.d.ts +31 -3
- package/dist/sdk/terminal/controller.js +81 -29
- package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
- package/dist/sdk/terminal/opentui/ansi.js +192 -0
- package/dist/sdk/terminal/opentui/model.d.ts +163 -0
- package/dist/sdk/terminal/opentui/model.js +482 -0
- package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
- package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
- package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
- package/dist/sdk/terminal/opentui/theme.js +88 -0
- package/dist/sdk/terminal/piExtension.d.ts +6 -1
- package/dist/sdk/terminal/piExtension.js +177 -63
- package/dist/sdk/terminal/standalone.d.ts +7 -0
- package/dist/sdk/terminal/standalone.js +72 -13
- package/dist/sdk/terminal/subagentMonitor.d.ts +28 -6
- package/dist/sdk/terminal/subagentMonitor.js +61 -9
- package/dist/sdk/terminal/terminals.d.ts +26 -2
- package/dist/sdk/terminal/terminals.js +325 -43
- package/dist/sdk/terminal/trajectory.d.ts +6 -1
- package/dist/sdk/terminal/trajectory.js +8 -0
- package/dist/sdk/terminal/tui/annotateCommand.d.ts +1 -1
- package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
- package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
- package/dist/sdk/terminal/tui/chrome.js +13 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +3 -1
- package/dist/sdk/terminal/tui/commandPages.js +17 -4
- package/dist/sdk/terminal/tui/commands.d.ts +6 -0
- package/dist/sdk/terminal/tui/commands.js +213 -124
- package/dist/sdk/terminal/tui/list.d.ts +14 -1
- package/dist/sdk/terminal/tui/list.js +96 -14
- package/dist/sdk/terminal/tui/pageOverlay.js +21 -5
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
- package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
- package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +5 -5
- package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +8 -6
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +206 -28
- package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +104 -19
- package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
- package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
- package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
- package/dist/sdk/terminal/tui/piAdapter.js +3 -5
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +2 -0
- package/dist/sdk/terminal/tui/pluginCommands.js +13 -9
- package/dist/sdk/terminal/tui/runtime.js +23 -3
- package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
- package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
- package/dist/sdk/tools/checkpoint.d.ts +27 -0
- package/dist/sdk/tools/checkpoint.js +154 -0
- package/dist/sdk/tools/getCapabilities.d.ts +31 -0
- package/dist/sdk/tools/getCapabilities.js +34 -0
- package/dist/sdk/tools/sessionName.d.ts +2 -0
- package/dist/sdk/tools/sessionName.js +5 -3
- package/dist/sdk/tools/spawnSubagent.js +11 -8
- package/dist/sdk/tools/todo.d.ts +15 -0
- package/dist/sdk/tools/todo.js +64 -0
- package/dist/sdk/usage/analytics.d.ts +20 -0
- package/dist/sdk/usage/analytics.js +151 -0
- package/dist/sdk/usage/ledger.d.ts +38 -0
- package/dist/sdk/usage/ledger.js +142 -0
- package/dist/sdk/usage/limits/adapters.d.ts +11 -0
- package/dist/sdk/usage/limits/adapters.js +349 -0
- package/dist/sdk/usage/limits/capture.d.ts +8 -0
- package/dist/sdk/usage/limits/capture.js +47 -0
- package/dist/sdk/usage/limits/http.d.ts +12 -0
- package/dist/sdk/usage/limits/http.js +51 -0
- package/dist/sdk/usage/limits/normalize.d.ts +15 -0
- package/dist/sdk/usage/limits/normalize.js +211 -0
- package/dist/sdk/usage/limits/service.d.ts +30 -0
- package/dist/sdk/usage/limits/service.js +176 -0
- package/dist/sdk/usage/limits/types.d.ts +65 -0
- package/dist/sdk/usage/limits/types.js +1 -0
- package/dist/sdk/usage/serverMethods.d.ts +3 -0
- package/dist/sdk/usage/serverMethods.js +22 -0
- package/dist/sdk/usage/service.d.ts +32 -0
- package/dist/sdk/usage/service.js +66 -0
- package/dist/sdk/usage/settings.d.ts +7 -0
- package/dist/sdk/usage/settings.js +28 -0
- package/dist/sdk/usage/types.d.ts +60 -0
- package/dist/sdk/usage/types.js +4 -0
- package/dist/terminal-opentui.js +9204 -4812
- package/dist/terminal-pi.js +1848 -272
- package/docs/architecture/00-overview.md +1 -1
- package/docs/architecture/01-extension-points.md +1 -1
- package/docs/architecture/02-sessions-and-runs.md +169 -36
- package/docs/architecture/04-prompt.md +55 -15
- package/docs/architecture/06-plugins-and-capabilities.md +36 -15
- package/docs/architecture/07-tools-and-gate.md +131 -25
- package/docs/architecture/08-memory-and-history.md +4 -2
- package/docs/architecture/09-accounts-and-connectors.md +39 -3
- package/docs/architecture/10-server-protocol.md +71 -9
- package/docs/architecture/11-terminal.md +148 -60
- package/docs/architecture/12-storage.md +30 -2
- package/docs/architecture/13-world-tools.md +22 -1
- package/docs/architecture/14-surfaces-and-parity.md +58 -8
- package/docs/architecture/README.md +20 -17
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
- package/docs/architecture/proposals/PROP-0017-page-annotations.md +6 -2
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +1 -1
- package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
- package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
- package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
- package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
- package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
- package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
- package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
- package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
- package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
- package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
- package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +258 -0
- package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
- package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
- package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
- package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
- package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
- package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
- package/docs/architecture/proposals/README.md +24 -6
- package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
- package/docs/decisions/ADR-0021-required-command-namespaces.md +1 -1
- package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
- package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
- package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
- package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
- package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
- package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
- package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
- package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
- package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
- package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
- package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
- package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
- package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
- package/docs/doctor.md +14 -1
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
- package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
- package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
- package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
- package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
- package/docs/parity.md +83 -27
- package/docs/releases/1.1.1.md +28 -0
- package/docs/releases/1.2.0.md +49 -0
- package/docs/releases/1.3.0.md +21 -0
- package/docs/releases/4.0.0.md +10 -9
- package/docs/releases/README.md +35 -30
- package/docs/study/00-index.md +3 -0
- package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
- package/docs/terminal-tui.md +162 -82
- package/package.json +4 -3
- package/dist/sdk/tools/checklist.d.ts +0 -9
- package/dist/sdk/tools/checklist.js +0 -54
|
@@ -6,9 +6,11 @@ import { now } from "../core/fs.js";
|
|
|
6
6
|
import { takeChars } from "../core/text.js";
|
|
7
7
|
import { isActiveStatus } from "./contracts.js";
|
|
8
8
|
import { narrowForProfile } from "../agents/narrow.js";
|
|
9
|
+
import { planResearchEligibility } from "../agents/planResearch.js";
|
|
9
10
|
import { resolveProfile } from "../agents/profiles.js";
|
|
11
|
+
import { continuationOf } from "./continuation.js";
|
|
10
12
|
import { findRun, loadRun, readEvents, recordedSubagents, recordSubagent, saveRun } from "./runStore.js";
|
|
11
|
-
import { SPAWN_SUBAGENT, SUBAGENT_RELATION, SUBAGENT_OWNER, SUBAGENT_RECEIPT, SUBAGENT_RESULTS, briefTitle, subagentHistory, subagentTitle } from "./subagents.js";
|
|
13
|
+
import { SPAWN_SUBAGENT, SUBAGENT_RELATION, SUBAGENT_OWNER, SUBAGENT_RECEIPT, SUBAGENT_RESULTS, briefTitle, continueHint, subagentHistory, subagentTitle } from "./subagents.js";
|
|
12
14
|
function until(promise, signal) {
|
|
13
15
|
if (signal.aborted) {
|
|
14
16
|
void promise.catch(() => { });
|
|
@@ -27,6 +29,7 @@ export class Subagents {
|
|
|
27
29
|
launch;
|
|
28
30
|
options;
|
|
29
31
|
parents = new Map();
|
|
32
|
+
runtimeSettingsByRun = new Map();
|
|
30
33
|
constructor(env, runs, launch, options = {}) {
|
|
31
34
|
this.env = env;
|
|
32
35
|
this.runs = runs;
|
|
@@ -66,12 +69,26 @@ export class Subagents {
|
|
|
66
69
|
started();
|
|
67
70
|
this.parents.set(input.run.id, { ready, started, input: structuredClone({ ...input, text: '', images: undefined, requiresImageInput: false, preamble: null, resume: null }), accountId, capabilities: ceiling, stopped: new AbortController(), recovered: new Set(), launching: new Map() });
|
|
68
71
|
}
|
|
72
|
+
runtimeSettings(runId) {
|
|
73
|
+
return this.runtimeSettingsByRun.get(runId) ?? null;
|
|
74
|
+
}
|
|
75
|
+
rememberRuntimeSettings(runId, settings) {
|
|
76
|
+
this.runtimeSettingsByRun.set(runId, settings);
|
|
77
|
+
if (this.runtimeSettingsByRun.size > 500) {
|
|
78
|
+
const oldest = this.runtimeSettingsByRun.keys().next().value;
|
|
79
|
+
if (oldest !== undefined)
|
|
80
|
+
this.runtimeSettingsByRun.delete(oldest);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
69
83
|
resolvedModel(runId, model, effort) {
|
|
70
84
|
const parent = this.parents.get(runId);
|
|
71
85
|
if (parent) {
|
|
72
86
|
parent.input.model = model;
|
|
73
87
|
parent.input.effort = effort ?? parent.input.effort;
|
|
74
88
|
}
|
|
89
|
+
const settings = this.runtimeSettingsByRun.get(runId);
|
|
90
|
+
if (settings)
|
|
91
|
+
this.rememberRuntimeSettings(runId, { model, effort: effort ?? settings.effort });
|
|
75
92
|
}
|
|
76
93
|
parent(runId) {
|
|
77
94
|
const parent = this.parents.get(runId);
|
|
@@ -79,11 +96,53 @@ export class Subagents {
|
|
|
79
96
|
throw new HarnessError('run_not_active', 'The parent run is not accepting subagents.');
|
|
80
97
|
return parent;
|
|
81
98
|
}
|
|
99
|
+
planSpawnCheck(parentRunId, args) {
|
|
100
|
+
const parent = this.parents.get(parentRunId);
|
|
101
|
+
if (!parent || !this.runs.active(parentRunId))
|
|
102
|
+
return { refusal: 'The parent run is not accepting subagents.' };
|
|
103
|
+
if (!parent.input.plan)
|
|
104
|
+
return { refusal: 'Read-only research delegation is available only during plan mode.' };
|
|
105
|
+
if (!parent.capabilities.tools.includes(SPAWN_SUBAGENT))
|
|
106
|
+
return { refusal: 'Subagents are not enabled for this run.' };
|
|
107
|
+
if (!args || typeof args !== 'object' || Array.isArray(args))
|
|
108
|
+
return { refusal: 'Plan-mode delegation requires a named, eligible read-only research profile.' };
|
|
109
|
+
let request = args;
|
|
110
|
+
if (request.continue !== undefined) {
|
|
111
|
+
// A continued child keeps its own profile; judge that profile.
|
|
112
|
+
const id = typeof request.continue === 'string' ? request.continue.trim() : '';
|
|
113
|
+
const record = id && this.owned(parentRunId, parent).has(id) ? findRun(this.env.root, id) : null;
|
|
114
|
+
if (!record?.profile)
|
|
115
|
+
return { refusal: 'Plan mode can continue only a stopped research subagent of this conversation that has a named profile.' };
|
|
116
|
+
if (request.agent !== undefined || request.tools !== undefined)
|
|
117
|
+
return { refusal: 'A continued subagent keeps its profile and tools; omit agent and tools.' };
|
|
118
|
+
request = { agent: record.profile };
|
|
119
|
+
}
|
|
120
|
+
if (typeof request.agent !== 'string' || !request.agent.trim())
|
|
121
|
+
return { refusal: 'Plan-mode delegation requires a named, eligible read-only research profile.' };
|
|
122
|
+
if (request.tools !== undefined && (!Array.isArray(request.tools) || request.tools.some(name => typeof name !== 'string'))) {
|
|
123
|
+
return { refusal: 'The optional tools field must be an array of tool names. Omit it to inherit the selected profile’s safe research intersection.' };
|
|
124
|
+
}
|
|
125
|
+
const profile = resolveProfile(this.env.root, parent.input.cwd, request.agent.trim(), parent.input.variant && 'dir' in parent.input.variant ? parent.input.variant.dir : null);
|
|
126
|
+
if (!profile)
|
|
127
|
+
return { refusal: `There is no agent profile named ${request.agent.trim()}.` };
|
|
128
|
+
const eligibility = planResearchEligibility(profile, {
|
|
129
|
+
frozenTools: parent.capabilities.tools,
|
|
130
|
+
contributions: parent.capabilities.contributions,
|
|
131
|
+
descriptors: this.options.descriptors?.() ?? []
|
|
132
|
+
}, request.tools);
|
|
133
|
+
return { refusal: eligibility.refusal, profile, eligibility };
|
|
134
|
+
}
|
|
135
|
+
/** The plan-mode per-call gate and spawn implementation share this eligibility check. */
|
|
136
|
+
planSpawnRefusal(parentRunId, args) {
|
|
137
|
+
return this.planSpawnCheck(parentRunId, args).refusal;
|
|
138
|
+
}
|
|
82
139
|
async spawn(request) {
|
|
83
140
|
const parent = this.parent(request.parentRunId);
|
|
84
141
|
const { input, accountId, capabilities } = parent;
|
|
85
142
|
if (!capabilities.tools.includes(SPAWN_SUBAGENT))
|
|
86
143
|
throw invalid('Subagents are not enabled for this run.');
|
|
144
|
+
if (request.continue !== undefined)
|
|
145
|
+
return this.continueChild(parent, request);
|
|
87
146
|
const brief = typeof request.brief === 'string' ? request.brief.trim() : '';
|
|
88
147
|
if (!brief)
|
|
89
148
|
throw invalid('Give the subagent a brief.');
|
|
@@ -94,22 +153,33 @@ export class Subagents {
|
|
|
94
153
|
throw invalid('wait must be a boolean.');
|
|
95
154
|
if (request.tools !== undefined && (!Array.isArray(request.tools) || request.tools.some((name) => typeof name !== 'string')))
|
|
96
155
|
throw invalid('Tools must be an array of tool names.');
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
156
|
+
let profile = null;
|
|
157
|
+
let planEligibility = null;
|
|
158
|
+
if (input.plan) {
|
|
159
|
+
const check = this.planSpawnCheck(request.parentRunId, request);
|
|
160
|
+
if (check.refusal || !check.profile || !check.eligibility)
|
|
161
|
+
throw invalid(check.refusal ?? 'The selected profile has no eligible research tools in this run.');
|
|
162
|
+
profile = check.profile;
|
|
163
|
+
planEligibility = check.eligibility;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const outside = request.tools?.filter((name) => !capabilities.tools.includes(name)) ?? [];
|
|
167
|
+
if (outside.length)
|
|
168
|
+
throw invalid(`A subagent cannot have tools you do not have: ${outside.join(', ')}.`);
|
|
169
|
+
if (request.agent !== undefined) {
|
|
170
|
+
if (typeof request.agent !== 'string' || !request.agent.trim())
|
|
171
|
+
throw invalid('agent must be the name of an agent profile.');
|
|
172
|
+
profile = resolveProfile(this.env.root, input.cwd, request.agent.trim(), input.variant && 'dir' in input.variant ? input.variant.dir : null);
|
|
173
|
+
if (!profile)
|
|
174
|
+
throw invalid(`There is no agent profile named ${request.agent.trim()}.`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
100
177
|
await until(parent.ready, parent.stopped.signal);
|
|
101
178
|
let variant = input.variant ?? null;
|
|
102
179
|
let launch = input.launch;
|
|
103
180
|
let model = input.model;
|
|
104
181
|
let effort = input.effort;
|
|
105
|
-
|
|
106
|
-
if (request.agent !== undefined) {
|
|
107
|
-
if (typeof request.agent !== 'string' || !request.agent.trim())
|
|
108
|
-
throw invalid('agent must be the name of an agent profile.');
|
|
109
|
-
const dir = variant && 'dir' in variant ? variant.dir : null;
|
|
110
|
-
profile = resolveProfile(this.env.root, input.cwd, request.agent.trim(), dir);
|
|
111
|
-
if (!profile)
|
|
112
|
-
throw invalid(`There is no agent profile named ${request.agent.trim()}.`);
|
|
182
|
+
if (profile) {
|
|
113
183
|
// A variant's own subagent works from its definition alone; a profile keeps the parent's
|
|
114
184
|
// variant unless it names another one, or none.
|
|
115
185
|
if (profile.source === 'variant' || profile.variant === 'none')
|
|
@@ -122,25 +192,36 @@ export class Subagents {
|
|
|
122
192
|
effort = profile.effort;
|
|
123
193
|
launch = { ...(input.launch ?? {}), sections: [...(input.launch?.sections ?? []).filter((section) => section.id !== 'subagent'), { id: 'subagent', title: `Your role: ${profile.name}`, body: profile.instructions || profile.description, budget: 16_000, order: 22 }] };
|
|
124
194
|
}
|
|
125
|
-
const narrowed =
|
|
126
|
-
|
|
195
|
+
const narrowed = input.plan
|
|
196
|
+
? { capabilities: planEligibility.capabilities, unavailable: planEligibility.unavailableTools }
|
|
197
|
+
: narrowForProfile(capabilities, profile, request.tools, this.options.descriptors?.() ?? []);
|
|
198
|
+
const childCapabilities = narrowed.capabilities;
|
|
199
|
+
const tools = childCapabilities.tools;
|
|
127
200
|
const runId = randomUUID();
|
|
128
201
|
const sessionId = randomUUID();
|
|
129
|
-
recordSubagent(this.env.root, request.parentRunId, runId);
|
|
130
202
|
const child = {
|
|
131
203
|
...input,
|
|
132
|
-
run: { ...input.run, id: runId, sessionId, parentRunId: request.parentRunId, relation: SUBAGENT_RELATION, goal: takeChars(brief, 4000), title, attachmentCount: 0, resumedFromRunId: null, handoffId: null, profile: profile?.name ?? null },
|
|
204
|
+
run: { ...input.run, ...(input.plan ? { worktreeId: null } : {}), id: runId, sessionId, parentRunId: request.parentRunId, relation: SUBAGENT_RELATION, goal: takeChars(brief, 4000), title, attachmentCount: 0, resumedFromRunId: null, handoffId: null, profile: profile?.name ?? null },
|
|
133
205
|
accountId, poolId: null, sessionDir: join(input.sessionDir, 'subagents', runId), resume: null,
|
|
134
206
|
text: brief, images: undefined, requiresImageInput: false, preamble: null,
|
|
135
207
|
caller: { ...input.caller, runId: undefined, sessionId },
|
|
136
208
|
tools: { allow: tools, gated: (input.tools.gated ?? []).filter((name) => tools.includes(name)) },
|
|
137
|
-
capabilities:
|
|
138
|
-
variant, launch, model, effort
|
|
209
|
+
capabilities: childCapabilities, depth: (input.depth ?? 0) + 1,
|
|
210
|
+
variant, launch, model, effort,
|
|
211
|
+
...(input.plan ? { connections: false, subagents: null } : {})
|
|
139
212
|
};
|
|
213
|
+
const result = await this.launchChild(parent, request, child);
|
|
214
|
+
return { ...result, ...(profile ? { agent: profile.name } : {}), ...(narrowed.unavailable.length ? { unavailableTools: narrowed.unavailable } : {}) };
|
|
215
|
+
}
|
|
216
|
+
/** Records ownership, starts the child, and writes a failed record when it never opened. */
|
|
217
|
+
async launchChild(parent, request, child) {
|
|
218
|
+
const runId = child.run.id;
|
|
219
|
+
this.rememberRuntimeSettings(runId, { model: child.model ?? null, effort: child.effort ?? null });
|
|
220
|
+
recordSubagent(this.env.root, request.parentRunId, runId);
|
|
140
221
|
const started = this.launch(child).catch((error) => {
|
|
141
222
|
if (!findRun(this.env.root, runId)) {
|
|
142
223
|
const source = loadRun(this.env.root, request.parentRunId);
|
|
143
|
-
saveRun(this.env.root, { ...source, id: runId, sessionId, parentRunId: request.parentRunId, relation: SUBAGENT_RELATION, nativeSessionId: null, resumedFromRunId: null, handoffId: null, exitCode: null, goal:
|
|
224
|
+
saveRun(this.env.root, { ...source, id: runId, sessionId: child.run.sessionId, parentRunId: request.parentRunId, relation: SUBAGENT_RELATION, nativeSessionId: null, resumedFromRunId: child.run.resumedFromRunId ?? null, handoffId: null, exitCode: null, goal: child.run.goal ?? null, title: child.run.title ?? null, attachmentCount: 0, status: 'failed', startedAt: now(), endedAt: now(), terminalReason: error instanceof HarnessError ? error.message : 'Subagent startup failed before its session opened.', profile: child.run.profile ?? null, model: child.model ?? null, effort: child.effort ?? null });
|
|
144
225
|
}
|
|
145
226
|
return { runId, nativeSessionId: null };
|
|
146
227
|
});
|
|
@@ -149,10 +230,113 @@ export class Subagents {
|
|
|
149
230
|
const result = await until(started, parent.stopped.signal);
|
|
150
231
|
if (request.wait)
|
|
151
232
|
await this.wait({ parentRunId: request.parentRunId, runIds: [runId] });
|
|
152
|
-
return
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
/** The children this parent may read or continue: its own, and those recovered from its history. */
|
|
236
|
+
owned(parentRunId, parent) {
|
|
237
|
+
return new Set([...recordedSubagents(this.env.root, parentRunId), ...(parent?.recovered ?? [])]);
|
|
238
|
+
}
|
|
239
|
+
/** The newest run in a continued child's chain, among the runs this parent owns. */
|
|
240
|
+
latestLink(start, owned) {
|
|
241
|
+
const records = [...owned].flatMap((id) => findRun(this.env.root, id) ?? []);
|
|
242
|
+
let latest = start;
|
|
243
|
+
const seen = new Set([start.id]);
|
|
244
|
+
for (;;) {
|
|
245
|
+
const next = records.filter((record) => record.resumedFromRunId === latest.id && !seen.has(record.id)).sort((left, right) => left.startedAt.localeCompare(right.startedAt)).at(-1);
|
|
246
|
+
if (!next)
|
|
247
|
+
return latest;
|
|
248
|
+
seen.add(next.id);
|
|
249
|
+
latest = next;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/** The first run of a chain: its folder holds the child's history for every later link. */
|
|
253
|
+
firstLink(record) {
|
|
254
|
+
let first = record;
|
|
255
|
+
const seen = new Set([record.id]);
|
|
256
|
+
while (first.resumedFromRunId && !seen.has(first.resumedFromRunId)) {
|
|
257
|
+
const previous = findRun(this.env.root, first.resumedFromRunId);
|
|
258
|
+
if (!previous || previous.relation !== SUBAGENT_RELATION)
|
|
259
|
+
break;
|
|
260
|
+
seen.add(previous.id);
|
|
261
|
+
first = previous;
|
|
262
|
+
}
|
|
263
|
+
return first;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Continues a stopped child as a new child run with `continueFrom`: the same history, profile,
|
|
267
|
+
* account, variant version and capability set, which must still lie within the parent's.
|
|
268
|
+
*/
|
|
269
|
+
async continueChild(parent, request) {
|
|
270
|
+
const { root } = this.env;
|
|
271
|
+
const { input, accountId, capabilities } = parent;
|
|
272
|
+
const requested = typeof request.continue === 'string' ? request.continue.trim() : '';
|
|
273
|
+
if (!requested)
|
|
274
|
+
throw invalid('continue must be the run ID of a subagent that stopped.');
|
|
275
|
+
if (request.agent !== undefined || request.tools !== undefined)
|
|
276
|
+
throw invalid('A continued subagent keeps its profile and tools; omit agent and tools.');
|
|
277
|
+
if (request.brief !== undefined && typeof request.brief !== 'string')
|
|
278
|
+
throw invalid('brief must be text.');
|
|
279
|
+
if (request.wait !== undefined && typeof request.wait !== 'boolean')
|
|
280
|
+
throw invalid('wait must be a boolean.');
|
|
281
|
+
await until(parent.ready, parent.stopped.signal);
|
|
282
|
+
const owned = this.owned(request.parentRunId, parent);
|
|
283
|
+
const named = owned.has(requested) ? findRun(root, requested) : null;
|
|
284
|
+
if (!named || named.relation !== SUBAGENT_RELATION)
|
|
285
|
+
throw invalid('Continue only a subagent that this conversation started.');
|
|
286
|
+
const latest = this.latestLink(named, owned);
|
|
287
|
+
if (isActiveStatus(latest.status))
|
|
288
|
+
throw invalid(`Subagent ${latest.id} is still running; wait for it with wait_subagents.`);
|
|
289
|
+
if (latest.status === 'completed')
|
|
290
|
+
throw invalid(`Subagent ${latest.id} already finished and its result was delivered. Start a new subagent for more work.`);
|
|
291
|
+
const sessionDir = join(input.sessionDir, 'subagents', this.firstLink(latest).id);
|
|
292
|
+
const continuation = continuationOf(root, latest.id, sessionDir);
|
|
293
|
+
if (!continuation.resumable || !continuation.resume)
|
|
294
|
+
throw invalid(`Subagent ${latest.id} cannot be continued: ${continuation.reason === 'no_history' ? 'it stopped before its conversation started' : 'its saved conversation is missing'}. Start a new subagent instead.`);
|
|
295
|
+
if (continuation.accountId !== accountId)
|
|
296
|
+
throw invalid(`Subagent ${latest.id} ran on another account; start a new subagent instead.`);
|
|
297
|
+
const pinned = continuation.capabilities;
|
|
298
|
+
if (!pinned)
|
|
299
|
+
throw invalid(`Subagent ${latest.id} has no recorded capabilities; start a new subagent instead.`);
|
|
300
|
+
const missing = [
|
|
301
|
+
...pinned.tools.filter((name) => !capabilities.tools.includes(name)),
|
|
302
|
+
...pinned.connectors.filter((id) => !capabilities.connectors.includes(id)),
|
|
303
|
+
...pinned.contributions.filter((id) => !capabilities.contributions.includes(id))
|
|
304
|
+
];
|
|
305
|
+
if (missing.length)
|
|
306
|
+
throw invalid(`Subagent ${latest.id} needs capabilities you do not have in this run: ${missing.join(', ')}. Start a new subagent instead.`);
|
|
307
|
+
let profile = null;
|
|
308
|
+
if (latest.profile)
|
|
309
|
+
profile = resolveProfile(root, input.cwd, latest.profile, input.variant && 'dir' in input.variant ? input.variant.dir : null);
|
|
310
|
+
if (input.plan) {
|
|
311
|
+
// A plan run continues only a research child whose pinned tools still qualify.
|
|
312
|
+
const check = latest.profile ? this.planSpawnCheck(request.parentRunId, { agent: latest.profile }) : { refusal: 'Plan mode can continue only a stopped research subagent that has a named profile.' };
|
|
313
|
+
if (check.refusal || !check.eligibility)
|
|
314
|
+
throw invalid(check.refusal ?? 'This subagent is not eligible for plan-mode research.');
|
|
315
|
+
const outside = pinned.tools.filter((name) => !check.eligibility.capabilities.tools.includes(name));
|
|
316
|
+
if (outside.length)
|
|
317
|
+
throw invalid(`Subagent ${latest.id} has tools plan mode does not allow: ${outside.join(', ')}.`);
|
|
318
|
+
}
|
|
319
|
+
const launch = profile
|
|
320
|
+
? { ...(input.launch ?? {}), sections: [...(input.launch?.sections ?? []).filter((section) => section.id !== 'subagent'), { id: 'subagent', title: `Your role: ${profile.name}`, body: profile.instructions || profile.description, budget: 16_000, order: 22 }] }
|
|
321
|
+
: input.launch;
|
|
322
|
+
const runId = randomUUID();
|
|
323
|
+
const child = {
|
|
324
|
+
...input,
|
|
325
|
+
run: { ...input.run, ...(input.plan ? { worktreeId: null } : {}), id: runId, sessionId: latest.sessionId, parentRunId: request.parentRunId, relation: SUBAGENT_RELATION, goal: latest.goal, title: latest.title ?? briefTitle(latest.goal), attachmentCount: 0, resumedFromRunId: latest.id, handoffId: null, profile: latest.profile ?? null },
|
|
326
|
+
accountId, poolId: null, sessionDir, resume: null, continueFrom: latest.id,
|
|
327
|
+
text: typeof request.brief === 'string' ? request.brief.trim() : '', images: undefined, requiresImageInput: false, preamble: null,
|
|
328
|
+
caller: { ...input.caller, runId: undefined, sessionId: latest.sessionId },
|
|
329
|
+
tools: { allow: pinned.tools, gated: (input.tools.gated ?? []).filter((name) => pinned.tools.includes(name)) },
|
|
330
|
+
capabilities: pinned, depth: (input.depth ?? 0) + 1,
|
|
331
|
+
variant: null, launch,
|
|
332
|
+
model: latest.model ?? input.model, effort: latest.effort ?? input.effort,
|
|
333
|
+
...(input.plan ? { connections: false, subagents: null } : {})
|
|
334
|
+
};
|
|
335
|
+
const result = await this.launchChild(parent, request, child);
|
|
336
|
+
return { ...result, continued: latest.id, ...(latest.profile ? { agent: latest.profile } : {}) };
|
|
153
337
|
}
|
|
154
338
|
selected(selection, parent) {
|
|
155
|
-
const owned =
|
|
339
|
+
const owned = this.owned(selection.parentRunId, parent);
|
|
156
340
|
if (selection.runIds !== undefined && (!Array.isArray(selection.runIds) || selection.runIds.some((id) => typeof id !== 'string')))
|
|
157
341
|
throw invalid('runIds must be an array of child run IDs.');
|
|
158
342
|
const ids = [...new Set(selection.runIds ?? [...owned])];
|
|
@@ -192,7 +376,9 @@ export class Subagents {
|
|
|
192
376
|
if (owner === parentRunId)
|
|
193
377
|
continue;
|
|
194
378
|
const old = findRun(this.env.root, owner);
|
|
195
|
-
|
|
379
|
+
// The owner marker on this native branch is the lineage; a host may give each launch of the
|
|
380
|
+
// same conversation a new session id (INV-RUN-4).
|
|
381
|
+
if (!old || old.accountId !== current.accountId || old.agentId !== current.agentId || old.projectId !== current.projectId || old.workingDirectory !== current.workingDirectory || !old.nativeSessionId || old.nativeSessionId !== current.nativeSessionId)
|
|
196
382
|
continue;
|
|
197
383
|
for (const id of recordedSubagents(this.env.root, owner)) {
|
|
198
384
|
let child = findRun(this.env.root, id);
|
|
@@ -218,12 +404,19 @@ export class Subagents {
|
|
|
218
404
|
}
|
|
219
405
|
}
|
|
220
406
|
const delivered = new Set(history.receipts.flatMap((receipt) => receipt.delivered ?? []));
|
|
221
|
-
const
|
|
407
|
+
const all = this.results({ parentRunId });
|
|
408
|
+
const records = new Map(all.map((result) => [result.runId, findRun(this.env.root, result.runId)]));
|
|
409
|
+
// A stopped child that was continued is reported through its continuation instead.
|
|
410
|
+
const superseded = new Set([...records.values()].flatMap((record) => record?.resumedFromRunId ? [record.resumedFromRunId] : []));
|
|
411
|
+
const pending = all.filter((result) => !isActiveStatus(result.status) && !delivered.has(result.runId) && !superseded.has(result.runId));
|
|
222
412
|
const chunks = [];
|
|
223
413
|
const ids = [];
|
|
224
414
|
let size = 0;
|
|
225
415
|
for (const result of pending) {
|
|
226
|
-
const
|
|
416
|
+
const record = records.get(result.runId);
|
|
417
|
+
const label = [record?.profile, record?.title].filter(Boolean).join(' · ');
|
|
418
|
+
const stopped = (result.status === 'interrupted' || result.status === 'failed') && continuationOf(this.env.root, result.runId).resumable;
|
|
419
|
+
const text = `Subagent run ${result.runId} · ${result.status}${label ? ` (${label})` : ''}\n${result.text || result.terminalReason || 'No written reply.'}${stopped ? `\nIt stopped before finishing. ${continueHint(result.runId)}` : ''}`;
|
|
227
420
|
if (size + text.length > 32_000 && ids.length)
|
|
228
421
|
break;
|
|
229
422
|
chunks.push(text);
|
|
@@ -242,12 +435,18 @@ export class Subagents {
|
|
|
242
435
|
}
|
|
243
436
|
extension(runId) {
|
|
244
437
|
return { name: 'native-subagents', factory: (pi) => {
|
|
245
|
-
pi.on('before_agent_start', (_event, ctx) => {
|
|
438
|
+
pi.on('before_agent_start', async (_event, ctx) => {
|
|
246
439
|
if (!this.parents.has(runId))
|
|
247
440
|
return;
|
|
248
|
-
|
|
441
|
+
const history = subagentHistory(ctx.sessionManager.getBranch());
|
|
442
|
+
this.restore(runId, history);
|
|
249
443
|
pi.appendEntry(SUBAGENT_OWNER, { runId });
|
|
250
444
|
this.parent(runId).started();
|
|
445
|
+
// Children an earlier reply left stopped are reported before the model answers, so it can
|
|
446
|
+
// continue them instead of starting over.
|
|
447
|
+
const notice = await this.delivery(runId, history, false);
|
|
448
|
+
if (notice)
|
|
449
|
+
return { message: { customType: SUBAGENT_RESULTS, content: notice.text, display: false, details: { [SUBAGENT_RECEIPT]: notice.receipt } } };
|
|
251
450
|
});
|
|
252
451
|
const deliver = async (event, ctx, settle) => {
|
|
253
452
|
if (!this.parents.has(runId) || (settle && event.outcome !== 'completed') || ctx.signal?.aborted)
|
|
@@ -11,14 +11,23 @@ export interface SubagentOptions {
|
|
|
11
11
|
export declare function subagentDepth(options: SubagentOptions | undefined): number;
|
|
12
12
|
export interface SubagentInput {
|
|
13
13
|
parentRunId: string;
|
|
14
|
-
|
|
14
|
+
/** The task for a new child; with `continue`, an optional extra instruction. */
|
|
15
|
+
brief?: string;
|
|
15
16
|
/** The short name the roster shows for the child (PROP-0030); the spawn tool requires it. */
|
|
16
17
|
title?: string;
|
|
17
18
|
tools?: string[];
|
|
18
19
|
wait?: boolean;
|
|
19
20
|
/** An agent profile by name (PROP-0029), or one of the parent variant's subagents (c5). */
|
|
20
21
|
agent?: string;
|
|
22
|
+
/**
|
|
23
|
+
* A stopped child to continue instead of starting a fresh one: its run id. The new child reopens
|
|
24
|
+
* that child's own history and keeps its profile, tools, account and variant; `brief` is then an
|
|
25
|
+
* optional extra instruction.
|
|
26
|
+
*/
|
|
27
|
+
continue?: string;
|
|
21
28
|
}
|
|
29
|
+
/** The words that tell a parent a stopped child can be continued rather than started again. */
|
|
30
|
+
export declare function continueHint(runId: string): string;
|
|
22
31
|
/** A subagent title is one short imperative, at most this many words (PROP-0030). */
|
|
23
32
|
export declare const MAX_SUBAGENT_TITLE_WORDS = 6;
|
|
24
33
|
/** The title a subagent title input means after cleaning, or undefined when it is not one. */
|
|
@@ -10,6 +10,10 @@ export function subagentDepth(options) {
|
|
|
10
10
|
const value = Math.trunc(options?.maxDepth ?? DEFAULT_SUBAGENT_DEPTH);
|
|
11
11
|
return Math.min(MAX_SUBAGENT_DEPTH, Math.max(1, Number.isFinite(value) ? value : DEFAULT_SUBAGENT_DEPTH));
|
|
12
12
|
}
|
|
13
|
+
/** The words that tell a parent a stopped child can be continued rather than started again. */
|
|
14
|
+
export function continueHint(runId) {
|
|
15
|
+
return `Its conversation is saved: continue it with spawn_subagent { "continue": "${runId}" } if the work is still needed, instead of starting it again from scratch.`;
|
|
16
|
+
}
|
|
13
17
|
/** A subagent title is one short imperative, at most this many words (PROP-0030). */
|
|
14
18
|
export const MAX_SUBAGENT_TITLE_WORDS = 6;
|
|
15
19
|
const BRIEF_TITLE_LENGTH = 60;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Checkpoint } from "./contracts.js";
|
|
2
|
+
export declare const WORK_SLICE_EXTENSION = "work-slice";
|
|
3
|
+
export interface WorkSliceInput {
|
|
4
|
+
/** False turns the guard off for this run: no budget, no stall notes, no continuation. */
|
|
5
|
+
enabled?: boolean;
|
|
6
|
+
/** Tool calls in one reply; 0 disables this limit. */
|
|
7
|
+
maxToolCalls?: number;
|
|
8
|
+
/** Minutes in one reply; 0 disables this limit. */
|
|
9
|
+
maxMinutes?: number;
|
|
10
|
+
/** New tokens in one reply (new input + cache writes + output); 0 disables this limit, and no limit is the default. */
|
|
11
|
+
maxNewTokens?: number;
|
|
12
|
+
/** Slices one task may use before it is handed back to the person; 0 disables the cap. */
|
|
13
|
+
maxSlices?: number;
|
|
14
|
+
/** Start the next slice from the checkpoint without asking. */
|
|
15
|
+
continue?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface SlicePolicy {
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
maxToolCalls: number;
|
|
20
|
+
maxMinutes: number;
|
|
21
|
+
maxNewTokens: number;
|
|
22
|
+
maxSlices: number;
|
|
23
|
+
continue: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** The foundation defaults: enough for one meaningful step of work, short enough to report. */
|
|
26
|
+
export declare const DEFAULT_SLICE_POLICY: SlicePolicy;
|
|
27
|
+
export declare class WorkSliceError extends Error {
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The policy a run asked for, or the defaults. `XINGULAR_WORK_SLICE=off` at launch turns the guard
|
|
31
|
+
* off for every run in this process; nothing is persisted, so restarting without it restores the
|
|
32
|
+
* defaults.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseWorkSlice(input: unknown): SlicePolicy;
|
|
35
|
+
export type SliceEnd = 'budget' | 'stuck' | 'compaction-thrash';
|
|
36
|
+
/** What a live reply tracks for its slice; the runner reads it after the reply. */
|
|
37
|
+
export interface SliceState {
|
|
38
|
+
slice: number;
|
|
39
|
+
steps: number;
|
|
40
|
+
tokens: number;
|
|
41
|
+
startedAt: number;
|
|
42
|
+
lastTool: string | null;
|
|
43
|
+
compactions: number;
|
|
44
|
+
checkpointWritten: boolean;
|
|
45
|
+
/** Set when the guard ended the reply instead of the model finishing it. */
|
|
46
|
+
endedBy: SliceEnd | null;
|
|
47
|
+
/** The limit that ended it, for the record. */
|
|
48
|
+
endedByLimit: string | null;
|
|
49
|
+
/** Steps past which every non-exempt tool call is refused, once one was refused. */
|
|
50
|
+
refusedAt: number | null;
|
|
51
|
+
}
|
|
52
|
+
export declare function createSliceState(_policy: SlicePolicy, slice?: number, now?: number): SliceState;
|
|
53
|
+
/** Counters start again for each reply; a terminal session keeps one guard across replies. */
|
|
54
|
+
export declare function resetSliceState(state: SliceState, now?: number): void;
|
|
55
|
+
/** The first limit this reply has passed, named as a person would say it. */
|
|
56
|
+
export declare function sliceLimitReached(policy: SlicePolicy, state: SliceState, now?: number): string | null;
|
|
57
|
+
/** Commands that read as a check for the repeated-failure rule. */
|
|
58
|
+
export declare function looksLikeCheck(command: string): boolean;
|
|
59
|
+
interface ToolCall {
|
|
60
|
+
toolName: string;
|
|
61
|
+
input?: unknown;
|
|
62
|
+
}
|
|
63
|
+
interface ToolResult {
|
|
64
|
+
toolName: string;
|
|
65
|
+
isError?: boolean;
|
|
66
|
+
input?: unknown;
|
|
67
|
+
}
|
|
68
|
+
/** The part of Pi's extension API this guard uses. Structural, so the module needs no Pi import. */
|
|
69
|
+
interface GuardApi {
|
|
70
|
+
on(event: 'agent_start', handler: () => unknown): void;
|
|
71
|
+
on(event: 'tool_call', handler: (event: ToolCall) => unknown): void;
|
|
72
|
+
on(event: 'tool_result', handler: (event: ToolResult) => unknown): void;
|
|
73
|
+
on(event: 'message_end', handler: (event: {
|
|
74
|
+
message: {
|
|
75
|
+
role: string;
|
|
76
|
+
usage?: {
|
|
77
|
+
input?: number;
|
|
78
|
+
output?: number;
|
|
79
|
+
cacheRead?: number;
|
|
80
|
+
cacheWrite?: number;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
}) => unknown): void;
|
|
84
|
+
}
|
|
85
|
+
export interface WorkSliceGuardOptions {
|
|
86
|
+
policy: SlicePolicy;
|
|
87
|
+
state: SliceState;
|
|
88
|
+
/** Whether checkpoint_write is active; a function supports the terminal's live plan-mode switch. */
|
|
89
|
+
checkpointAvailable?: boolean | (() => boolean);
|
|
90
|
+
/** Called when a triggering call terminates the reply; the runner ends it. */
|
|
91
|
+
onEnd?: (end: SliceEnd, limit: string) => void;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Bounds one reply and names repeated work. Refusals request a checkpoint when available; without
|
|
95
|
+
* one, they ask for a partial report and prohibit further tools. A repeated call or failing command
|
|
96
|
+
* is named once, then ends the reply.
|
|
97
|
+
*/
|
|
98
|
+
export declare function workSliceGuard(options: WorkSliceGuardOptions): (pi: GuardApi) => void;
|
|
99
|
+
/**
|
|
100
|
+
* Which slice of its session a run is, from 1. Counts the recorded continuation chain, so it
|
|
101
|
+
* survives a restart and needs no extra field on the run record.
|
|
102
|
+
*/
|
|
103
|
+
export declare function sliceOf(root: string, runId: string, limit?: number): number;
|
|
104
|
+
/** The reason a checkpoint the harness derives (no model handoff) records. */
|
|
105
|
+
export declare function derivedReason(end: SliceEnd | null): Checkpoint['reason'];
|
|
106
|
+
export {};
|