@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
|
@@ -13,6 +13,8 @@ import { readAudit } from "../accounts/store.js";
|
|
|
13
13
|
import { log } from "../core/bus.js";
|
|
14
14
|
import { HarnessError, invalid } from "../core/errors.js";
|
|
15
15
|
import { newId, now } from "../core/fs.js";
|
|
16
|
+
import { formatBashCommandForApproval } from "../core/workspaceBashTool.js";
|
|
17
|
+
import { resolveWorkspaceRoot } from "../core/workspace.js";
|
|
16
18
|
import { prependSearchPath, searchPath } from "../core/path.js";
|
|
17
19
|
import { dispatch } from "../tools/dispatch.js";
|
|
18
20
|
import { materialise } from "../variant/materialise.js";
|
|
@@ -20,8 +22,14 @@ import * as componentLibrary from "../variant/components.js";
|
|
|
20
22
|
import * as variantStore from "../variant/store.js";
|
|
21
23
|
import { compile, copyToRun, record, render, saveForSession } from "../prompt/compile.js";
|
|
22
24
|
import { isActiveStatus } from "../sessions/contracts.js";
|
|
23
|
-
import { findRun, loadRun, saveRun, updateRun } from "../sessions/runStore.js";
|
|
24
|
-
import {
|
|
25
|
+
import { findRun, loadRun, readEvents, recordedSubagents, saveRun, updateRun } from "../sessions/runStore.js";
|
|
26
|
+
import { combineWorkspaceScope, inspectRunWorkspaceWrite } from "../sessions/workspaceBoundary.js";
|
|
27
|
+
import { TODO_WRITE } from "../tools/todo.js";
|
|
28
|
+
import { DEFAULT_TOOL_NAMES } from "../tools/definitions.js";
|
|
29
|
+
import { harnessTools } from "../tools/harnessTools.js";
|
|
30
|
+
import { HARNESS_PLUGINS } from "../plugins/builtin.js";
|
|
31
|
+
import * as attachments from "../plugins/attachments.js";
|
|
32
|
+
import { FIRST_PARTY_PLUGINS } from "../plugins/firstParty.js";
|
|
25
33
|
import { createProfile, listProfiles, profileDirs, profileSection, removeProfile, resolveProfile } from "../agents/profiles.js";
|
|
26
34
|
import { SESSION_NAME } from "../tools/sessionName.js";
|
|
27
35
|
import { contributionId } from "../plugins/identity.js";
|
|
@@ -34,7 +42,7 @@ import { TerminalController } from "./controller.js";
|
|
|
34
42
|
import { terminalCommandFiles } from "./launch.js";
|
|
35
43
|
import { SessionSocket, socketPath } from "./socket.js";
|
|
36
44
|
import { harnessLogInfo, readHarnessLogChunk } from "./logs.js";
|
|
37
|
-
import { listTerminalTrajectoryRuns, readTerminalTrajectory } from "./trajectory.js";
|
|
45
|
+
import { listTerminalTrajectoryRuns, readTerminalTrajectory, latestTerminalTodo } from "./trajectory.js";
|
|
38
46
|
import { BrowserControl } from "./browserControl.js";
|
|
39
47
|
/** Identity of the exact resource decision that this terminal was prepared with. */
|
|
40
48
|
function resourceIdentity(loading) {
|
|
@@ -90,12 +98,61 @@ export class Terminals {
|
|
|
90
98
|
}
|
|
91
99
|
return this.childProfiles.get(record.id) ?? null;
|
|
92
100
|
}
|
|
101
|
+
/** Account, model and effort for a monitor row, from the run record. */
|
|
102
|
+
monitorRuntime(live, record) {
|
|
103
|
+
let accountLabel = record.accountId === live.account.id ? live.account.label : null;
|
|
104
|
+
if (accountLabel === null && record.accountId) {
|
|
105
|
+
try {
|
|
106
|
+
accountLabel = this.options.accounts.find(record.accountId).label;
|
|
107
|
+
}
|
|
108
|
+
catch { }
|
|
109
|
+
}
|
|
110
|
+
return { accountLabel, model: record.model ?? null, effort: record.effort ?? null };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Shows the reopened conversation's latest parent run again: the last owner on its native branch
|
|
114
|
+
* that ran in this folder, with every descendant flat, as each ended.
|
|
115
|
+
*/
|
|
116
|
+
hydrateMonitor(live, owners) {
|
|
117
|
+
const { root } = this.options.env;
|
|
118
|
+
const native = live.controller.nativeSessionId;
|
|
119
|
+
if (!native)
|
|
120
|
+
return;
|
|
121
|
+
for (const id of [...owners].reverse()) {
|
|
122
|
+
const record = findRun(root, id);
|
|
123
|
+
if (!record || record.relation || record.nativeSessionId !== native || record.workingDirectory !== live.input.cwd)
|
|
124
|
+
continue;
|
|
125
|
+
const children = [];
|
|
126
|
+
const seen = new Set([record.id]);
|
|
127
|
+
const visit = (parentId) => {
|
|
128
|
+
for (const childId of recordedSubagents(root, parentId)) {
|
|
129
|
+
if (seen.has(childId))
|
|
130
|
+
continue;
|
|
131
|
+
seen.add(childId);
|
|
132
|
+
const child = findRun(root, childId);
|
|
133
|
+
if (child?.relation !== 'subagent')
|
|
134
|
+
continue;
|
|
135
|
+
children.push(child);
|
|
136
|
+
visit(child.id);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
visit(record.id);
|
|
140
|
+
const entry = (run, profile = null) => ({ record: run, events: readEvents(root, run.id), profile, runtime: this.monitorRuntime(live, run) });
|
|
141
|
+
live.controller.restoreMonitor(entry(record), children.map(child => entry(child, this.subagentProfile(child))));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
93
145
|
constructor(options) {
|
|
94
146
|
this.options = options;
|
|
95
147
|
this.assets = options.assets ?? defaultAssets();
|
|
96
148
|
options.runs.listen(event => {
|
|
97
|
-
this.sessions.get(event.sessionId)
|
|
149
|
+
const session = this.sessions.get(event.sessionId);
|
|
150
|
+
session?.controller.observe(event);
|
|
98
151
|
const record = findRun(options.env.root, event.runId);
|
|
152
|
+
if (record && !record.relation && session) {
|
|
153
|
+
session.controller.observeMain(record, event, this.monitorRuntime(session, record));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
99
156
|
if (record?.relation !== 'subagent')
|
|
100
157
|
return;
|
|
101
158
|
let parentId = record.parentRunId;
|
|
@@ -108,7 +165,12 @@ export class Terminals {
|
|
|
108
165
|
const owner = this.sessions.get(parent.sessionId);
|
|
109
166
|
if (owner?.controller.activeRun?.runId === parent.id) {
|
|
110
167
|
owner.controller.observe(event, true);
|
|
111
|
-
|
|
168
|
+
const settings = options.subagents?.runtimeSettings(record.id);
|
|
169
|
+
owner.controller.observeSubagent(parent.id, record, event, this.subagentProfile(record), {
|
|
170
|
+
accountLabel: owner.account.label,
|
|
171
|
+
model: settings?.model ?? record.model ?? null,
|
|
172
|
+
effort: settings?.effort ?? record.effort ?? null
|
|
173
|
+
});
|
|
112
174
|
break;
|
|
113
175
|
}
|
|
114
176
|
parentId = parent.parentRunId;
|
|
@@ -153,23 +215,48 @@ export class Terminals {
|
|
|
153
215
|
}
|
|
154
216
|
return bin;
|
|
155
217
|
}
|
|
156
|
-
/**
|
|
218
|
+
/**
|
|
219
|
+
* The harness's own History, Wiki and skill tools, then the broker's, then the world tools whose
|
|
220
|
+
* plugins are on for this session: the same tools a structured session has (INV-PAR-1).
|
|
221
|
+
*/
|
|
157
222
|
async tools(session, listing) {
|
|
158
223
|
const brokered = this.options.apps.brokerTools(session);
|
|
159
224
|
const live = this.sessions.get(session.sessionId);
|
|
160
|
-
|
|
161
|
-
if (!world || !live)
|
|
225
|
+
if (!live)
|
|
162
226
|
return brokered;
|
|
227
|
+
const own = await this.ownTools(live, listing);
|
|
228
|
+
const { world } = this.options;
|
|
229
|
+
if (!world)
|
|
230
|
+
return [...own, ...brokered];
|
|
163
231
|
const context = { run: live.controller.activeRun, caller: live.caller, attended: true, subagent: false, sessionKey: session.sessionId, cwd: live.input.cwd };
|
|
164
232
|
if (!listing)
|
|
165
|
-
return [...brokered, ...world.tools(context, (_plugin, tool) => this.worldNames.get(session.sessionId)?.has(tool) ?? false)];
|
|
233
|
+
return [...own, ...brokered, ...world.tools(context, (_plugin, tool) => this.worldNames.get(session.sessionId)?.has(tool) ?? false)];
|
|
166
234
|
const on = await this.worldOn(live);
|
|
167
235
|
const tools = world.tools(context, (plugin, tool) => on(plugin, tool));
|
|
168
236
|
this.worldNames.set(session.sessionId, new Set(tools.map((tool) => tool.name)));
|
|
169
237
|
for (const tool of tools)
|
|
170
238
|
if (tool.readOnly)
|
|
171
239
|
this.worldReadOnly.add(tool.name);
|
|
172
|
-
return [...brokered, ...tools];
|
|
240
|
+
return [...own, ...brokered, ...tools];
|
|
241
|
+
}
|
|
242
|
+
ownNames = new Map();
|
|
243
|
+
async ownTools(live, listing) {
|
|
244
|
+
const sessionId = live.caller.sessionId;
|
|
245
|
+
const all = harnessTools(this.options.env, live.caller, { variantDir: this.variantDirs.get(sessionId) ?? null })
|
|
246
|
+
.filter((tool) => DEFAULT_TOOL_NAMES.includes(tool.name));
|
|
247
|
+
if (!listing)
|
|
248
|
+
return all.filter((tool) => this.ownNames.get(sessionId)?.has(tool.name) ?? false);
|
|
249
|
+
const on = await this.worldOn(live);
|
|
250
|
+
const owner = (tool) => HARNESS_PLUGINS.find((plugin) => plugin.tools?.some((entry) => entry.name === tool))?.id ?? null;
|
|
251
|
+
const tools = all.filter((tool) => {
|
|
252
|
+
const plugin = owner(tool.name);
|
|
253
|
+
return !plugin || on(plugin, tool.name);
|
|
254
|
+
});
|
|
255
|
+
this.ownNames.set(sessionId, new Set(tools.map((tool) => tool.name)));
|
|
256
|
+
for (const tool of tools)
|
|
257
|
+
if (tool.readOnly)
|
|
258
|
+
this.worldReadOnly.add(tool.name);
|
|
259
|
+
return tools;
|
|
173
260
|
}
|
|
174
261
|
/** The world tools a new session will have, named in its instructions. */
|
|
175
262
|
async worldToolNames(input, account) {
|
|
@@ -181,6 +268,16 @@ export class Terminals {
|
|
|
181
268
|
const on = plugins ? new Set(plugins.flatMap((plugin) => plugin.contributions.filter((item) => item.state === 'on').map((item) => item.id))) : null;
|
|
182
269
|
return world.plugins.flatMap((plugin) => (plugin.tools ?? []).map((tool) => tool.name).filter((name) => !on || on.has(contributionId(plugin.id, 'tool', name))));
|
|
183
270
|
}
|
|
271
|
+
/** The harness's own History, Wiki and skill tools a new session will have. */
|
|
272
|
+
async ownToolNames(input, account) {
|
|
273
|
+
const list = this.options.pluginControl?.list;
|
|
274
|
+
const plugins = list ? await list({ cwd: input.cwd, accountId: account.id, projectId: input.projectId ?? null }) : null;
|
|
275
|
+
const on = plugins ? new Set(plugins.flatMap((plugin) => plugin.contributions.filter((item) => item.state === 'on').map((item) => item.id))) : null;
|
|
276
|
+
return DEFAULT_TOOL_NAMES.filter((name) => {
|
|
277
|
+
const plugin = HARNESS_PLUGINS.find((entry) => entry.tools?.some((tool) => tool.name === name));
|
|
278
|
+
return !on || !plugin || on.has(contributionId(plugin.id, 'tool', name));
|
|
279
|
+
});
|
|
280
|
+
}
|
|
184
281
|
/** Whether a world tool's plugin is switched on for a session's folder, account and project. */
|
|
185
282
|
async worldOn(live) {
|
|
186
283
|
const list = this.options.pluginControl?.list;
|
|
@@ -203,6 +300,17 @@ export class Terminals {
|
|
|
203
300
|
const { Type } = await import('typebox');
|
|
204
301
|
return { tools: (await this.tools(session, true)).map((tool) => ({ name: tool.name, label: tool.label, description: tool.description, parameters: tool.parameters(Type) })) };
|
|
205
302
|
}
|
|
303
|
+
case 'firstParty.list': {
|
|
304
|
+
// The harness's own extensions switched on for this session, with their settings.
|
|
305
|
+
const live = this.sessions.get(session.sessionId);
|
|
306
|
+
const list = this.options.pluginControl?.list;
|
|
307
|
+
if (!live || !list)
|
|
308
|
+
return { plugins: [] };
|
|
309
|
+
const plugins = await list({ cwd: live.input.cwd, accountId: live.account.id, projectId: live.input.projectId ?? null });
|
|
310
|
+
const on = new Set(plugins.flatMap((plugin) => plugin.contributions.filter((item) => item.state === 'on').map((item) => item.id)));
|
|
311
|
+
const settings = attachments.load(this.options.env.root).settings;
|
|
312
|
+
return { plugins: FIRST_PARTY_PLUGINS.filter((plugin) => plugin.code?.some((code) => on.has(`${plugin.id}/code/${code.name}`))).map((plugin) => ({ id: plugin.id, settings: settings[plugin.id] ?? {} })) };
|
|
313
|
+
}
|
|
206
314
|
case 'tools.call': {
|
|
207
315
|
const tool = (await this.tools(session, false)).find((candidate) => candidate.name === params.name);
|
|
208
316
|
if (!tool)
|
|
@@ -451,7 +559,45 @@ export class Terminals {
|
|
|
451
559
|
throw new HarnessError('unknown_method', `Unknown call ${call}.`);
|
|
452
560
|
}
|
|
453
561
|
}
|
|
454
|
-
/** `/
|
|
562
|
+
/** `/usage:analysis` and `/usage:limit`: usage for this session's own account, project or session. */
|
|
563
|
+
async usageCall(live, call, params) {
|
|
564
|
+
const usage = this.options.usage;
|
|
565
|
+
const limits = this.options.usageLimits;
|
|
566
|
+
if (!usage || !limits)
|
|
567
|
+
throw invalid('Usage is unavailable in this host.');
|
|
568
|
+
if (call === 'terminal.usage.summary') {
|
|
569
|
+
const scope = params.scope === 'session' || params.scope === 'project' || params.scope === 'all' ? params.scope : 'account';
|
|
570
|
+
if (scope === 'project' && !live.input.projectId)
|
|
571
|
+
throw invalid('This session has no project to report usage for.');
|
|
572
|
+
const period = params.period === 'today' || params.period === '30d' || params.period === 'all' ? params.period : '7d';
|
|
573
|
+
return usage.summary({
|
|
574
|
+
scope,
|
|
575
|
+
sessionId: live.input.sessionId,
|
|
576
|
+
accountId: live.account.id,
|
|
577
|
+
projectId: live.input.projectId ?? null,
|
|
578
|
+
period,
|
|
579
|
+
timezoneOffsetMinutes: typeof params.timezoneOffsetMinutes === 'number' ? params.timezoneOffsetMinutes : 0
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
if (call === 'terminal.usage.limits') {
|
|
583
|
+
return params.refresh === true ? limits.refresh(live.account.id, { force: true }) : limits.read(live.account.id);
|
|
584
|
+
}
|
|
585
|
+
if (call === 'terminal.usage.headers') {
|
|
586
|
+
if (typeof params.provider !== 'string' || !params.provider)
|
|
587
|
+
throw invalid('A provider is required.');
|
|
588
|
+
if (typeof params.status !== 'number' || !Number.isFinite(params.status))
|
|
589
|
+
throw invalid('A response status is required.');
|
|
590
|
+
const headers = params.headers && typeof params.headers === 'object' && !Array.isArray(params.headers)
|
|
591
|
+
? Object.fromEntries(Object.entries(params.headers).filter((entry) => typeof entry[1] === 'string'))
|
|
592
|
+
: {};
|
|
593
|
+
if (!Object.keys(headers).length)
|
|
594
|
+
return {};
|
|
595
|
+
limits.recordHeaders(live.account.id, params.provider, { status: params.status, headers });
|
|
596
|
+
return {};
|
|
597
|
+
}
|
|
598
|
+
throw invalid('Unknown usage operation.');
|
|
599
|
+
}
|
|
600
|
+
/** `/annotate:notes` and `/annotate:`: Web annotations and their visible capture browser. */
|
|
455
601
|
async annotationsCall(live, call, params) {
|
|
456
602
|
const root = this.options.env.root;
|
|
457
603
|
const sessionId = live.input.sessionId;
|
|
@@ -578,6 +724,7 @@ export class Terminals {
|
|
|
578
724
|
sessionId: input.sessionId,
|
|
579
725
|
nativeSessionId: live.controller.nativeSessionId,
|
|
580
726
|
workingDirectory: input.cwd,
|
|
727
|
+
workspaceRoot: resolveWorkspaceRoot(input.cwd),
|
|
581
728
|
worktreeId: input.run?.worktreeId ?? null,
|
|
582
729
|
goal: text,
|
|
583
730
|
attachmentCount: 0,
|
|
@@ -586,11 +733,13 @@ export class Terminals {
|
|
|
586
733
|
status: 'queued',
|
|
587
734
|
exitCode: null,
|
|
588
735
|
handoffId: null,
|
|
589
|
-
accountId: live.account.id
|
|
736
|
+
accountId: live.account.id,
|
|
737
|
+
variantScope: live.variant?.scope ?? null,
|
|
738
|
+
variantVersion: live.variant?.fingerprint ?? null
|
|
590
739
|
};
|
|
591
740
|
}
|
|
592
741
|
/** The run a native turn reports into: the one reserved by `send`, or a new one for what the person typed. */
|
|
593
|
-
begin(live, reservedRunId, text) {
|
|
742
|
+
begin(live, reservedRunId, text, launch = {}) {
|
|
594
743
|
const { root } = this.options.env;
|
|
595
744
|
const { sessionId } = live.input;
|
|
596
745
|
if (this.options.runs.activeForSession(sessionId))
|
|
@@ -606,63 +755,174 @@ export class Terminals {
|
|
|
606
755
|
saveRun(root, this.runRecord(live, runId, text || null));
|
|
607
756
|
copyToRun(root, live.input.sessionFolder, runId);
|
|
608
757
|
}
|
|
758
|
+
if (launch.model || launch.effort) {
|
|
759
|
+
updateRun(root, runId, (record) => {
|
|
760
|
+
if (launch.model)
|
|
761
|
+
record.model = launch.model;
|
|
762
|
+
if (launch.effort)
|
|
763
|
+
record.effort = launch.effort;
|
|
764
|
+
});
|
|
765
|
+
}
|
|
609
766
|
return this.options.runs.start(sessionId, runId);
|
|
610
767
|
}
|
|
611
768
|
async decide(live, run, tool, args) {
|
|
612
769
|
const record = loadRun(this.options.env.root, run.runId);
|
|
613
770
|
if (!isActiveStatus(record.status))
|
|
614
771
|
throw invalid(`The run is no longer active (its status is ${record.status}).`);
|
|
615
|
-
const
|
|
772
|
+
const name = tool.toLowerCase();
|
|
773
|
+
const workspaceRoot = record.workspaceRoot ?? resolveWorkspaceRoot(record.workingDirectory ?? live.input.cwd);
|
|
774
|
+
if (live.input.readOnly && ['bash', 'edit', 'write'].includes(name))
|
|
775
|
+
return { allowed: false, reason: 'This session is read-only.', workspaceRoot };
|
|
776
|
+
let inspection;
|
|
777
|
+
try {
|
|
778
|
+
inspection = inspectRunWorkspaceWrite(record, tool, args);
|
|
779
|
+
}
|
|
780
|
+
catch (error) {
|
|
781
|
+
return { allowed: false, reason: error instanceof Error ? error.message : 'The active project workspace could not be verified.', workspaceRoot };
|
|
782
|
+
}
|
|
783
|
+
const call = { run: record, caller: { ...live.caller, runId: run.runId }, tool, args: inspection.input };
|
|
616
784
|
const { policy } = this.options;
|
|
617
785
|
if (policy.scope) {
|
|
618
|
-
const scope = await policy.scope(call);
|
|
786
|
+
const scope = combineWorkspaceScope(await policy.scope(call), inspection);
|
|
619
787
|
if (!scope.allowed && (scope.automatic || !scope.requiresApproval))
|
|
620
|
-
return { allowed: false, reason: scope.reason ?? 'Outside what this session may do.' };
|
|
788
|
+
return { allowed: false, reason: scope.reason ?? 'Outside what this session may do.', workspaceRoot };
|
|
621
789
|
try {
|
|
622
790
|
await policy.authorize?.(call);
|
|
623
791
|
}
|
|
624
792
|
catch (error) {
|
|
625
|
-
return { allowed: false, reason: error instanceof Error ? error.message : 'Tool unavailable in the settings.' };
|
|
793
|
+
return { allowed: false, reason: error instanceof Error ? error.message : 'Tool unavailable in the settings.', workspaceRoot };
|
|
626
794
|
}
|
|
627
795
|
const requiresApproval = !!scope.requiresApproval || (scope.allowed && !!scope.humanOnly);
|
|
628
796
|
return {
|
|
629
797
|
allowed: scope.allowed,
|
|
630
798
|
requiresApproval,
|
|
631
799
|
reason: scope.reason ?? null,
|
|
800
|
+
workspaceRoot,
|
|
632
801
|
rememberable: scope.allowed && !scope.humanOnly,
|
|
633
802
|
...(scope.humanOnly ? { standing: scope.allowGlobalApproval ? 'switch' : 'none' } : {})
|
|
634
803
|
};
|
|
635
804
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (name === SESSION_NAME || name ===
|
|
639
|
-
return { allowed: true };
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
805
|
+
if (inspection.outside)
|
|
806
|
+
return { allowed: false, requiresApproval: true, rememberable: false, standing: 'switch', reason: inspection.reason, workspaceRoot };
|
|
807
|
+
if (name === SESSION_NAME || name === TODO_WRITE || name === SPAWN_SUBAGENT || name === WAIT_SUBAGENTS || READ_ONLY_TOOLS.has(name) || this.worldReadOnly.has(name) || this.options.apps.toolNames.includes(name) || ['bash', 'edit', 'write'].includes(name)) {
|
|
808
|
+
return { allowed: true, workspaceRoot };
|
|
809
|
+
}
|
|
810
|
+
return { allowed: false, requiresApproval: true, rememberable: true, reason: 'This native action requires approval.', workspaceRoot };
|
|
811
|
+
}
|
|
812
|
+
async workspaceEscape(live, run, tool, args, reason) {
|
|
813
|
+
if (tool !== 'bash' || live.input.readOnly)
|
|
814
|
+
return false;
|
|
815
|
+
const record = loadRun(this.options.env.root, run.runId);
|
|
816
|
+
if (!isActiveStatus(record.status) || (record.trigger != null && record.trigger !== 'user'))
|
|
817
|
+
return false;
|
|
818
|
+
const call = { run: record, caller: { ...live.caller, runId: run.runId }, tool, args };
|
|
819
|
+
const scope = (await this.options.policy.scope?.(call)) ?? { allowed: true };
|
|
820
|
+
if (scope.automatic || (!scope.allowed && !scope.requiresApproval))
|
|
821
|
+
return false;
|
|
822
|
+
try {
|
|
823
|
+
await this.options.policy.authorize?.(call);
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
const operationArgs = (args && typeof args === 'object' && !Array.isArray(args) ? args : {});
|
|
829
|
+
const command = typeof operationArgs.command === 'string' ? operationArgs.command : '';
|
|
830
|
+
if (operationArgs.workspaceWriteScope !== 'outside' || !command)
|
|
831
|
+
return false;
|
|
832
|
+
const workspaceRoot = record.workspaceRoot ?? resolveWorkspaceRoot(record.workingDirectory ?? live.input.cwd);
|
|
833
|
+
const decision = await run.requestApproval({
|
|
834
|
+
target: 'workspace-write',
|
|
835
|
+
action: `${reason} Workspace: ${workspaceRoot}. This exact command will run once with broader filesystem access.\n\nCommand:\n${formatBashCommandForApproval(command)}`,
|
|
836
|
+
operation: { tool, args: operationArgs },
|
|
837
|
+
rememberable: false,
|
|
838
|
+
standing: 'none'
|
|
839
|
+
});
|
|
840
|
+
if (decision !== 'approved')
|
|
841
|
+
return false;
|
|
842
|
+
const currentRecord = loadRun(this.options.env.root, run.runId);
|
|
843
|
+
if (!isActiveStatus(currentRecord.status) || (currentRecord.trigger != null && currentRecord.trigger !== 'user'))
|
|
844
|
+
return false;
|
|
845
|
+
const currentCall = { run: currentRecord, caller: { ...live.caller, runId: run.runId }, tool, args: operationArgs };
|
|
846
|
+
const currentScope = (await this.options.policy.scope?.(currentCall)) ?? { allowed: true };
|
|
847
|
+
if (currentScope.automatic || (!currentScope.allowed && !currentScope.requiresApproval))
|
|
848
|
+
return false;
|
|
849
|
+
try {
|
|
850
|
+
await this.options.policy.authorize?.(currentCall);
|
|
851
|
+
return true;
|
|
852
|
+
}
|
|
853
|
+
catch {
|
|
854
|
+
return false;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
async workspaceBoundaryBypass(live, run, tool, args) {
|
|
858
|
+
if (tool !== 'bash' || live.input.readOnly || !this.options.approvals.isAuto('pi'))
|
|
859
|
+
return false;
|
|
860
|
+
const record = loadRun(this.options.env.root, run.runId);
|
|
861
|
+
if (!isActiveStatus(record.status) || (record.trigger != null && record.trigger !== 'user'))
|
|
862
|
+
return false;
|
|
863
|
+
const operationArgs = (args && typeof args === 'object' && !Array.isArray(args) ? args : {});
|
|
864
|
+
if (typeof operationArgs.command !== 'string' || !operationArgs.command)
|
|
865
|
+
return false;
|
|
866
|
+
const call = { run: record, caller: { ...live.caller, runId: run.runId }, tool, args: operationArgs };
|
|
867
|
+
try {
|
|
868
|
+
const scope = (await this.options.policy.scope?.(call)) ?? { allowed: true };
|
|
869
|
+
if (scope.automatic || (!scope.allowed && !scope.requiresApproval) || (scope.humanOnly && !scope.allowGlobalApproval))
|
|
870
|
+
return false;
|
|
871
|
+
await this.options.policy.authorize?.(call);
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
catch {
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
643
877
|
}
|
|
644
878
|
async subagentsCall(live, call, params) {
|
|
645
879
|
if (call === 'terminal.subagents.monitor')
|
|
646
880
|
return { children: live.controller.subagentSnapshot() };
|
|
647
881
|
const service = this.options.subagents;
|
|
648
|
-
if (!service || live.input.subagents === null) {
|
|
882
|
+
if ((!service || live.input.subagents === null) && call !== 'terminal.subagents.register') {
|
|
649
883
|
if (call === 'terminal.subagents.tools')
|
|
650
884
|
return { tools: [] };
|
|
651
885
|
throw invalid('Subagents are disabled for this terminal.');
|
|
652
886
|
}
|
|
653
|
-
if (call === 'terminal.subagents.tools')
|
|
654
|
-
|
|
887
|
+
if (call === 'terminal.subagents.tools') {
|
|
888
|
+
const variantDir = this.variantDirs.get(live.input.sessionId) ?? null;
|
|
889
|
+
return {
|
|
890
|
+
tools: subagentTools(service, '').map(tool => ({ name: tool.name, label: tool.label, description: tool.description, parameters: tool.parameters(Type) })),
|
|
891
|
+
profiles: {
|
|
892
|
+
normal: profileSection(this.options.env.root, live.input.cwd, variantDir).body,
|
|
893
|
+
plan: profileSection(this.options.env.root, live.input.cwd, variantDir, true).body
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
}
|
|
655
897
|
const run = live.controller.activeRun;
|
|
656
898
|
if (!run)
|
|
657
899
|
throw new HarnessError('run_not_active', 'This terminal has no active parent run.');
|
|
900
|
+
if (call === 'terminal.subagents.planSafe') {
|
|
901
|
+
const reason = service.planSpawnRefusal(run.runId, params.input);
|
|
902
|
+
return { allowed: reason === null, ...(reason ? { reason } : {}) };
|
|
903
|
+
}
|
|
658
904
|
if (call === 'terminal.subagents.register') {
|
|
659
905
|
if (!Array.isArray(params.tools) || params.tools.some((name) => typeof name !== 'string'))
|
|
660
906
|
throw invalid('Native tool names are required.');
|
|
907
|
+
if (params.plan !== undefined && typeof params.plan !== 'boolean')
|
|
908
|
+
throw invalid('plan must be a boolean.');
|
|
661
909
|
const readOnly = live.input.readOnly === true;
|
|
662
|
-
const readTools = new Set([...READ_ONLY_TOOLS, SESSION_NAME,
|
|
663
|
-
const tools = params.tools.filter(
|
|
664
|
-
const
|
|
665
|
-
|
|
910
|
+
const readTools = new Set([...READ_ONLY_TOOLS, SESSION_NAME, TODO_WRITE, SPAWN_SUBAGENT, WAIT_SUBAGENTS, 'ask_user', 'mcp_list_tools', 'mcp_call_tool', 'history_search', 'history_read', 'wiki_search', 'wiki_read', 'wiki_list', 'memory_facts', 'skill_load']);
|
|
911
|
+
const tools = [...new Set(params.tools)].filter(name => (!readOnly || readTools.has(name)) && (params.plan === true || (name !== 'ask_user' && name !== 'ask_user_question')));
|
|
912
|
+
const caller = { ...live.caller, runId: run.runId };
|
|
913
|
+
const capabilities = this.options.resolveCapabilities ? await this.options.resolveCapabilities(caller, tools, live.input.cwd) : { version: 1, tools, connectors: [], contributions: [] };
|
|
914
|
+
const variantDir = this.variantDirs.get(live.input.sessionId) ?? null;
|
|
915
|
+
const subagentProfiles = {
|
|
916
|
+
normal: profileSection(this.options.env.root, live.input.cwd, variantDir).body,
|
|
917
|
+
plan: profileSection(this.options.env.root, live.input.cwd, variantDir, true, {
|
|
918
|
+
frozenTools: capabilities.tools,
|
|
919
|
+
contributions: capabilities.contributions,
|
|
920
|
+
descriptors: HARNESS_PLUGINS
|
|
921
|
+
}).body
|
|
922
|
+
};
|
|
923
|
+
freezeRun(this.options.env.root, run.runId, caller, capabilities);
|
|
924
|
+
if (!service || live.input.subagents === null)
|
|
925
|
+
return { runId: run.runId, connectors: capabilities.connectors, subagentProfiles };
|
|
666
926
|
service.register({
|
|
667
927
|
run: { id: run.runId, sessionId: live.input.sessionId, mode: live.input.run?.mode ?? 'code', projectId: live.input.projectId, worktreeId: live.input.run?.worktreeId },
|
|
668
928
|
cwd: live.input.cwd, sessionDir: this.historyDir(live.input), text: '', caller: live.caller,
|
|
@@ -670,11 +930,15 @@ export class Terminals {
|
|
|
670
930
|
effort: typeof params.effort === 'string' ? params.effort : null,
|
|
671
931
|
launch: live.input.launch, tools: { allow: capabilities.tools, gated: capabilities.tools.filter(name => !readTools.has(name)) },
|
|
672
932
|
connections: live.input.connections === false ? false : { readOnly }, subagents: live.input.subagents,
|
|
673
|
-
|
|
933
|
+
plan: params.plan === true,
|
|
934
|
+
variant: live.variant ? { dir: live.variant.dir, scope: live.variant.scope } : null,
|
|
674
935
|
capabilities
|
|
675
936
|
}, live.account.id, capabilities);
|
|
676
|
-
|
|
677
|
-
|
|
937
|
+
const history = params.history ?? { owners: [], receipts: [] };
|
|
938
|
+
service.restore(run.runId, history);
|
|
939
|
+
// Children an earlier reply left stopped, reported before the model answers.
|
|
940
|
+
const notice = await service.delivery(run.runId, history, false);
|
|
941
|
+
return { runId: run.runId, connectors: capabilities.connectors, subagentProfiles, notice };
|
|
678
942
|
}
|
|
679
943
|
if (call === 'terminal.subagents.verify') {
|
|
680
944
|
await service.verify(run.runId, params.history);
|
|
@@ -834,16 +1098,20 @@ export class Terminals {
|
|
|
834
1098
|
if (variant)
|
|
835
1099
|
this.variantDirs.set(sessionId, variant.dir);
|
|
836
1100
|
const worldTools = await this.worldToolNames(input, preparedAccount);
|
|
837
|
-
const
|
|
1101
|
+
const ownTools = await this.ownToolNames(input, preparedAccount);
|
|
1102
|
+
const launchTools = [...new Set([...(input.launch?.tools ?? []), TODO_WRITE, SESSION_NAME, ...ownTools, ...(broker ? apps.toolNames : []), ...worldTools])];
|
|
838
1103
|
// The profiles replace the variant's own subagent list, which they include.
|
|
839
1104
|
const profiles = this.options.subagents && input.subagents !== null ? profileSection(root, input.cwd, variant?.dir ?? null) : null;
|
|
840
1105
|
const launchSections = profiles ? [...(input.launch?.sections ?? []).filter((section) => section.id !== profiles.id), profiles] : input.launch?.sections;
|
|
841
|
-
|
|
1106
|
+
// A terminal is a person's own session: the same standard sections a host's session has.
|
|
1107
|
+
const standard = input.launch?.standard === null ? null : input.launch?.standard ?? { cwd: input.cwd };
|
|
1108
|
+
const pack = compile(env, caller, { ...input.launch, standard, sections: launchSections, tools: launchTools }, variant && profiles ? { ...variant, sections: variant.sections.filter((section) => section.id !== profiles.id) } : variant);
|
|
842
1109
|
record(root, pack);
|
|
843
1110
|
mkdirSync(this.historyDir(input), { recursive: true });
|
|
844
1111
|
saveForSession(input.sessionFolder, pack);
|
|
845
1112
|
const live = {
|
|
846
1113
|
input,
|
|
1114
|
+
variant,
|
|
847
1115
|
account: preparedAccount,
|
|
848
1116
|
broker,
|
|
849
1117
|
caller,
|
|
@@ -851,8 +1119,15 @@ export class Terminals {
|
|
|
851
1119
|
controller: new TerminalController({
|
|
852
1120
|
sessionId,
|
|
853
1121
|
nativeSessionId: input.nativeSessionId ?? null,
|
|
854
|
-
begin: ({ reservedRunId, text }) => this.begin(live, reservedRunId, text),
|
|
1122
|
+
begin: ({ reservedRunId, text, model, effort }) => this.begin(live, reservedRunId, text, { model, effort }),
|
|
1123
|
+
hydrate: (owners) => this.hydrateMonitor(live, owners),
|
|
855
1124
|
policy: (run, tool, args) => this.decide(live, run, tool, args),
|
|
1125
|
+
workspaceRoot: run => {
|
|
1126
|
+
const record = loadRun(this.options.env.root, run.runId);
|
|
1127
|
+
return record.workspaceRoot ?? resolveWorkspaceRoot(record.workingDirectory ?? live.input.cwd);
|
|
1128
|
+
},
|
|
1129
|
+
workspaceEscape: (run, tool, args, reason) => this.workspaceEscape(live, run, tool, args, reason),
|
|
1130
|
+
workspaceBoundaryBypass: (run, tool, args) => this.workspaceBoundaryBypass(live, run, tool, args),
|
|
856
1131
|
allowToolGlobally: target => { this.options.approvals.allowTool('pi', target); },
|
|
857
1132
|
native: (nativeSessionId) => bus.emit({ type: 'terminal.native', sessionId, nativeSessionId }),
|
|
858
1133
|
telemetry: (event) => bus.emit({ type: 'terminal.telemetry', sessionId, event }),
|
|
@@ -963,6 +1238,8 @@ export class Terminals {
|
|
|
963
1238
|
return this.componentsCall(live, call, params);
|
|
964
1239
|
if (call.startsWith('terminal.trajectory.'))
|
|
965
1240
|
return this.trajectoryCall(live, call, params);
|
|
1241
|
+
if (call === 'terminal.todo')
|
|
1242
|
+
return latestTerminalTodo(this.options.env.root, live.input.sessionId, live.controller.nativeSessionId);
|
|
966
1243
|
if (call === 'terminal.permissions.get') {
|
|
967
1244
|
const policy = this.options.approvals.read();
|
|
968
1245
|
return { full: this.options.approvals.isAuto('pi'), globalTools: policy.globalTools.pi ?? [] };
|
|
@@ -980,11 +1257,15 @@ export class Terminals {
|
|
|
980
1257
|
: this.options.approvals.revokeTool('pi', params.tool);
|
|
981
1258
|
return { globalTools: policy.globalTools.pi ?? [] };
|
|
982
1259
|
}
|
|
1260
|
+
if (call.startsWith('terminal.usage.'))
|
|
1261
|
+
return this.usageCall(live, call, params);
|
|
983
1262
|
if (call !== 'terminal.resources')
|
|
984
1263
|
return live.controller.control(call, params);
|
|
985
1264
|
const current = await this.options.plugins?.loading({ cwd: input.cwd, agentDir: configDir(accounts.dir, preparedAccount), install: false, caller: { projectId: input.projectId ?? null } }) ?? null;
|
|
986
|
-
|
|
1265
|
+
const currentVariant = variant ? materialise(root, { dir: variant.dir, scope: variant.scope }, { trust: this.options.trust ?? null }) : null;
|
|
1266
|
+
if (resourceIdentity(current) !== resourceIdentity(loading) || (variant && (currentVariant?.fingerprint !== variant.fingerprint || JSON.stringify(currentVariant.extensionPaths) !== JSON.stringify(variant.extensionPaths)))) {
|
|
987
1267
|
throw new HarnessError('terminal_resources_changed', 'Trusted resources changed since this terminal was prepared. Review /plugins and reopen the terminal to load the new selection.');
|
|
1268
|
+
}
|
|
988
1269
|
return {};
|
|
989
1270
|
} }
|
|
990
1271
|
});
|
|
@@ -993,16 +1274,17 @@ export class Terminals {
|
|
|
993
1274
|
const args = [...command.argsPrefix, '--append-system-prompt', render(env, pack)];
|
|
994
1275
|
if (input.model)
|
|
995
1276
|
args.push('--model', input.model);
|
|
996
|
-
|
|
1277
|
+
{
|
|
997
1278
|
// Pi's own discovery is off: the terminal loads what the harness found and nothing else.
|
|
998
1279
|
args.push('--no-extensions', '--no-skills', '--no-prompt-templates', '--no-themes');
|
|
999
|
-
|
|
1280
|
+
// The pi command's own extensions are not loaded: a host's session cannot have them (PROP-0039).
|
|
1281
|
+
for (const path of [...(loading?.extensionPaths ?? []), ...(variant?.extensionPaths ?? [])])
|
|
1000
1282
|
args.push('--extension', path);
|
|
1001
|
-
for (const path of [...(variant?.skillPaths ?? []), ...loading
|
|
1283
|
+
for (const path of [...(variant?.skillPaths ?? []), ...(loading?.skillPaths ?? [])])
|
|
1002
1284
|
args.push('--skill', path);
|
|
1003
|
-
for (const path of loading
|
|
1285
|
+
for (const path of loading?.promptPaths ?? [])
|
|
1004
1286
|
args.push('--prompt-template', path);
|
|
1005
|
-
for (const path of loading
|
|
1287
|
+
for (const path of loading?.themePaths ?? [])
|
|
1006
1288
|
args.push('--theme', path);
|
|
1007
1289
|
}
|
|
1008
1290
|
// Last, so its gate sees a tool call as every other extension left it.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RunEvent, RunRecord } from "../sessions/contracts.js";
|
|
1
|
+
import type { RunEvent, RunRecord, TodoItem } from "../sessions/contracts.js";
|
|
2
2
|
export type TerminalTrajectoryRun = Pick<RunRecord, 'id' | 'mode' | 'goal' | 'startedAt' | 'endedAt' | 'status'>;
|
|
3
3
|
export interface TerminalTrajectorySnapshot {
|
|
4
4
|
run: TerminalTrajectoryRun;
|
|
@@ -6,5 +6,10 @@ export interface TerminalTrajectorySnapshot {
|
|
|
6
6
|
}
|
|
7
7
|
/** The native conversation file is the terminal's boundary for this read-only view. */
|
|
8
8
|
export declare function listTerminalTrajectoryRuns(root: string, sessionId: string, nativeSessionId: string | null): TerminalTrajectoryRun[];
|
|
9
|
+
/** The todo of the latest reply in this native conversation, for a host's /todo view. */
|
|
10
|
+
export declare function latestTerminalTodo(root: string, sessionId: string, nativeSessionId: string | null): {
|
|
11
|
+
runId: string;
|
|
12
|
+
todo: TodoItem[];
|
|
13
|
+
};
|
|
9
14
|
/** Read a run only when it belongs to both this terminal and its current native conversation. */
|
|
10
15
|
export declare function readTerminalTrajectory(root: string, sessionId: string, nativeSessionId: string | null, runId: string, afterSequence?: number): TerminalTrajectorySnapshot;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { notFound } from "../core/errors.js";
|
|
2
2
|
import { findRun, listRuns, readEvents } from "../sessions/runStore.js";
|
|
3
|
+
import { todoOf } from "../tools/todo.js";
|
|
3
4
|
function summary(run) {
|
|
4
5
|
return {
|
|
5
6
|
id: run.id,
|
|
@@ -18,6 +19,13 @@ export function listTerminalTrajectoryRuns(root, sessionId, nativeSessionId) {
|
|
|
18
19
|
.filter(run => run.nativeSessionId === nativeSessionId || run.trigger === 'side-question' && run.sessionId === sessionId)
|
|
19
20
|
.map(summary);
|
|
20
21
|
}
|
|
22
|
+
/** The todo of the latest reply in this native conversation, for a host's /todo view. */
|
|
23
|
+
export function latestTerminalTodo(root, sessionId, nativeSessionId) {
|
|
24
|
+
if (!nativeSessionId)
|
|
25
|
+
return { runId: '', todo: [] };
|
|
26
|
+
const run = listRuns(root, { sessionId }).find(candidate => candidate.nativeSessionId === nativeSessionId && candidate.trigger !== 'side-question');
|
|
27
|
+
return run ? { runId: run.id, todo: todoOf(readEvents(root, run.id)) } : { runId: '', todo: [] };
|
|
28
|
+
}
|
|
21
29
|
/** Read a run only when it belongs to both this terminal and its current native conversation. */
|
|
22
30
|
export function readTerminalTrajectory(root, sessionId, nativeSessionId, runId, afterSequence) {
|
|
23
31
|
if (!nativeSessionId || !Number.isSafeInteger(afterSequence ?? 0) || (afterSequence ?? 0) < 0)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { AnnotateStart } from "./pages/annotatePage.js";
|
|
2
|
-
/** `/
|
|
2
|
+
/** `/annotate:annotate` accepts a page address, DevTools target, or page-bridge target. */
|
|
3
3
|
export declare function parseAnnotate(argument: string): AnnotateStart | string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** `/
|
|
1
|
+
/** `/annotate:annotate` accepts a page address, DevTools target, or page-bridge target. */
|
|
2
2
|
export function parseAnnotate(argument) {
|
|
3
3
|
const words = argument.trim().split(/\s+/).filter(Boolean);
|
|
4
4
|
if (!words.length)
|
|
@@ -6,7 +6,7 @@ export function parseAnnotate(argument) {
|
|
|
6
6
|
if (words[0] === '--app') {
|
|
7
7
|
const endpoint = words[1];
|
|
8
8
|
if (!endpoint)
|
|
9
|
-
return 'Give the app\'s DevTools address: /
|
|
9
|
+
return 'Give the app\'s DevTools address: /annotate:annotate --app http://127.0.0.1:9229';
|
|
10
10
|
const address = /^\d+$/.test(endpoint) ? `http://127.0.0.1:${endpoint}` : endpoint;
|
|
11
11
|
const name = words.slice(2).join(' ');
|
|
12
12
|
return { kind: 'cdp', endpoint: address, ...(name ? { name } : {}) };
|
|
@@ -16,7 +16,7 @@ export function parseAnnotate(argument) {
|
|
|
16
16
|
return { kind: 'bridge', ...(name ? { name } : {}) };
|
|
17
17
|
}
|
|
18
18
|
if (words.length > 1 || words[0].startsWith('--'))
|
|
19
|
-
return 'Use /
|
|
19
|
+
return 'Use /annotate:annotate [url], /annotate:annotate --app <DevTools address>, or /annotate:annotate --bridge.';
|
|
20
20
|
const url = /^[a-z][a-z0-9+.-]*:\/\//i.test(words[0]) ? words[0] : /^(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/.test(words[0]) ? `http://${words[0]}` : `https://${words[0]}`;
|
|
21
21
|
return { kind: 'web', url };
|
|
22
22
|
}
|