@xingular/harness 1.0.0 → 1.1.1
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 +33 -5
- package/README.md +70 -19
- package/dist/cli.js +10321 -5785
- package/dist/sdk/agents/builtin.d.ts +2 -0
- package/dist/sdk/agents/builtin.js +34 -0
- package/dist/sdk/agents/narrow.d.ts +13 -0
- package/dist/sdk/agents/narrow.js +29 -0
- package/dist/sdk/agents/profiles.d.ts +80 -0
- package/dist/sdk/agents/profiles.js +221 -0
- package/dist/sdk/annotations/bridge.d.ts +42 -0
- package/dist/sdk/annotations/bridge.js +264 -0
- package/dist/sdk/annotations/capture.d.ts +116 -0
- package/dist/sdk/annotations/capture.js +308 -0
- package/dist/sdk/annotations/delivery.d.ts +72 -0
- package/dist/sdk/annotations/delivery.js +130 -0
- package/dist/sdk/annotations/inspect.d.ts +10 -0
- package/dist/sdk/annotations/inspect.js +33 -0
- package/dist/sdk/annotations/isolated.d.ts +6 -0
- package/dist/sdk/annotations/isolated.js +51 -0
- package/dist/sdk/annotations/pageScript.d.ts +28 -0
- package/dist/sdk/annotations/pageScript.js +570 -0
- package/dist/sdk/annotations/part.d.ts +10 -0
- package/dist/sdk/annotations/part.js +144 -0
- package/dist/sdk/annotations/queue.d.ts +4 -0
- package/dist/sdk/annotations/queue.js +40 -0
- package/dist/sdk/annotations/serverMethods.d.ts +3 -0
- package/dist/sdk/annotations/serverMethods.js +62 -0
- package/dist/sdk/annotations/store.d.ts +160 -0
- package/dist/sdk/annotations/store.js +386 -0
- package/dist/sdk/browser/index.d.ts +14 -3
- package/dist/sdk/browser/index.js +11 -4
- package/dist/sdk/browser/serverMethods.d.ts +3 -0
- package/dist/sdk/browser/serverMethods.js +7 -0
- package/dist/sdk/browser/session.d.ts +1 -0
- package/dist/sdk/browser/session.js +45 -9
- package/dist/sdk/cli.js +19 -11
- package/dist/sdk/core/bus.js +1 -1
- package/dist/sdk/core/env.d.ts +1 -1
- package/dist/sdk/core/env.js +1 -1
- package/dist/sdk/doctor/serverMethods.d.ts +3 -0
- package/dist/sdk/doctor/serverMethods.js +8 -0
- package/dist/sdk/harness.d.ts +15 -0
- package/dist/sdk/harness.js +44 -5
- package/dist/sdk/history/recall.js +8 -8
- package/dist/sdk/history/serverMethods.d.ts +3 -0
- package/dist/sdk/history/serverMethods.js +8 -0
- package/dist/sdk/hub/cli.d.ts +1 -1
- package/dist/sdk/hub/cli.js +1 -1
- package/dist/sdk/hub/schema.d.ts +1 -1
- package/dist/sdk/hub/serverMethods.d.ts +3 -0
- package/dist/sdk/hub/serverMethods.js +11 -0
- package/dist/sdk/index.d.ts +10 -0
- package/dist/sdk/index.js +7 -0
- package/dist/sdk/mcp/connections.js +14 -7
- package/dist/sdk/mcp/serverMethods.d.ts +3 -0
- package/dist/sdk/mcp/serverMethods.js +22 -0
- package/dist/sdk/plugins/builtin.d.ts +1 -0
- package/dist/sdk/plugins/builtin.js +128 -33
- package/dist/sdk/plugins/builtinRegistry.d.ts +53 -0
- package/dist/sdk/plugins/builtinRegistry.js +49 -0
- package/dist/sdk/plugins/cli.d.ts +1 -1
- package/dist/sdk/plugins/cli.js +4 -3
- package/dist/sdk/prompt/compile.d.ts +5 -2
- package/dist/sdk/prompt/compile.js +25 -8
- package/dist/sdk/refine/cli.d.ts +1 -1
- package/dist/sdk/refine/cli.js +2 -2
- package/dist/sdk/refine/serverMethods.d.ts +3 -0
- package/dist/sdk/refine/serverMethods.js +20 -0
- package/dist/sdk/server/methods.js +9 -86
- package/dist/sdk/server/params.d.ts +4 -0
- package/dist/sdk/server/params.js +11 -0
- package/dist/sdk/sessions/contracts.d.ts +17 -0
- package/dist/sdk/sessions/contracts.js +42 -3
- package/dist/sdk/sessions/planMode.d.ts +5 -2
- package/dist/sdk/sessions/planMode.js +12 -3
- package/dist/sdk/sessions/reasoningSummaries.d.ts +2 -2
- package/dist/sdk/sessions/reasoningSummaries.js +3 -3
- package/dist/sdk/sessions/registry.d.ts +1 -0
- package/dist/sdk/sessions/registry.js +4 -1
- package/dist/sdk/sessions/runIndex.js +7 -7
- package/dist/sdk/sessions/runStore.js +3 -1
- package/dist/sdk/sessions/runner.js +2 -2
- package/dist/sdk/sessions/sessions.d.ts +17 -5
- package/dist/sdk/sessions/sessions.js +79 -12
- package/dist/sdk/sessions/sideQuestionServerMethods.d.ts +3 -0
- package/dist/sdk/sessions/sideQuestionServerMethods.js +19 -0
- package/dist/sdk/sessions/sideQuestions.d.ts +66 -0
- package/dist/sdk/sessions/sideQuestions.js +290 -0
- package/dist/sdk/sessions/subagentServerMethods.d.ts +3 -0
- package/dist/sdk/sessions/subagentServerMethods.js +41 -0
- package/dist/sdk/sessions/subagentService.d.ts +13 -2
- package/dist/sdk/sessions/subagentService.js +34 -16
- package/dist/sdk/sessions/subagents.d.ts +9 -1
- package/dist/sdk/sessions/subagents.js +19 -0
- package/dist/sdk/terminal/browser.d.ts +62 -0
- package/dist/sdk/terminal/browser.js +41 -0
- package/dist/sdk/terminal/browserControl.d.ts +29 -0
- package/dist/sdk/terminal/browserControl.js +186 -0
- package/dist/sdk/terminal/commandHelp.d.ts +17 -0
- package/dist/sdk/terminal/commandHelp.js +20 -0
- package/dist/sdk/terminal/controller.d.ts +7 -1
- package/dist/sdk/terminal/controller.js +34 -2
- package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +28 -0
- package/dist/sdk/terminal/opentui/subagentsWidget.js +52 -0
- package/dist/sdk/terminal/piExtension.js +33 -48
- package/dist/sdk/terminal/socket.d.ts +6 -3
- package/dist/sdk/terminal/socket.js +40 -9
- package/dist/sdk/terminal/standalone.d.ts +2 -0
- package/dist/sdk/terminal/standalone.js +50 -10
- package/dist/sdk/terminal/subagentMonitor.d.ts +38 -0
- package/dist/sdk/terminal/subagentMonitor.js +121 -0
- package/dist/sdk/terminal/terminals.d.ts +42 -6
- package/dist/sdk/terminal/terminals.js +359 -18
- package/dist/sdk/terminal/trajectory.js +2 -2
- package/dist/sdk/terminal/tui/annotateCommand.d.ts +3 -0
- package/dist/sdk/terminal/tui/annotateCommand.js +22 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +5 -1
- package/dist/sdk/terminal/tui/commandPages.js +54 -6
- package/dist/sdk/terminal/tui/commands.d.ts +8 -4
- package/dist/sdk/terminal/tui/commands.js +181 -58
- package/dist/sdk/terminal/tui/dialogGeometry.d.ts +2 -2
- package/dist/sdk/terminal/tui/dialogGeometry.js +4 -2
- package/dist/sdk/terminal/tui/dialogs.d.ts +6 -1
- package/dist/sdk/terminal/tui/dialogs.js +9 -2
- package/dist/sdk/terminal/tui/modelPicker.js +1 -6
- package/dist/sdk/terminal/tui/page.d.ts +3 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +17 -6
- package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +26 -4
- package/dist/sdk/terminal/tui/pages/accountsPage.js +264 -36
- package/dist/sdk/terminal/tui/pages/agentsPage.d.ts +74 -0
- package/dist/sdk/terminal/tui/pages/agentsPage.js +190 -0
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +94 -0
- package/dist/sdk/terminal/tui/pages/annotatePage.js +341 -0
- package/dist/sdk/terminal/tui/pages/browserPage.d.ts +87 -0
- package/dist/sdk/terminal/tui/pages/browserPage.js +738 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +2 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.js +103 -15
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +75 -0
- package/dist/sdk/terminal/tui/pages/notesPage.js +201 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +8 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.js +16 -1
- package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +23 -0
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +70 -0
- package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +12 -5
- package/dist/sdk/terminal/tui/pages/trajectoryPage.js +156 -83
- package/dist/sdk/terminal/tui/pages/worktreesPage.d.ts +72 -0
- package/dist/sdk/terminal/tui/pages/worktreesPage.js +203 -0
- package/dist/sdk/terminal/tui/planMode.d.ts +1 -1
- package/dist/sdk/terminal/tui/planMode.js +1 -1
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +14 -0
- package/dist/sdk/terminal/tui/pluginCommands.js +27 -0
- package/dist/sdk/terminal/tui/questions.d.ts +2 -0
- package/dist/sdk/terminal/tui/runtime.js +4 -2
- package/dist/sdk/terminal/tui/settingsModel.d.ts +2 -0
- package/dist/sdk/terminal/tui/settingsModel.js +32 -2
- package/dist/sdk/terminal/tui/sideQuestion.d.ts +26 -26
- package/dist/sdk/terminal/tui/sideQuestion.js +131 -98
- package/dist/sdk/terminal/tui/ui.d.ts +2 -0
- package/dist/sdk/terminal/tui/ui.js +1 -0
- package/dist/sdk/tools/askUser.js +26 -8
- package/dist/sdk/tools/checklist.js +2 -2
- package/dist/sdk/tools/harnessTools.d.ts +2 -0
- package/dist/sdk/tools/harnessTools.js +1 -1
- package/dist/sdk/tools/spawnSubagent.js +13 -4
- package/dist/sdk/web/part.d.ts +10 -0
- package/dist/sdk/web/part.js +6 -0
- package/dist/sdk/web/serverMethods.d.ts +3 -0
- package/dist/sdk/web/serverMethods.js +7 -0
- package/dist/sdk/wiki/serverMethods.d.ts +3 -0
- package/dist/sdk/wiki/serverMethods.js +26 -0
- package/dist/sdk/worktrees/cli.d.ts +11 -0
- package/dist/sdk/worktrees/cli.js +146 -0
- package/dist/sdk/worktrees/copy.d.ts +9 -0
- package/dist/sdk/worktrees/copy.js +111 -0
- package/dist/sdk/worktrees/git.d.ts +16 -0
- package/dist/sdk/worktrees/git.js +81 -0
- package/dist/sdk/worktrees/serverMethods.d.ts +3 -0
- package/dist/sdk/worktrees/serverMethods.js +30 -0
- package/dist/sdk/worktrees/service.d.ts +73 -0
- package/dist/sdk/worktrees/service.js +483 -0
- package/dist/sdk/worktrees/setup.d.ts +23 -0
- package/dist/sdk/worktrees/setup.js +65 -0
- package/dist/sdk/worktrees/types.d.ts +91 -0
- package/dist/sdk/worktrees/types.js +1 -0
- package/dist/sdk/world/cli.d.ts +2 -2
- package/dist/sdk/world/cli.js +6 -6
- package/dist/sdk/world/world.d.ts +3 -1
- package/dist/sdk/world/world.js +8 -9
- package/dist/terminal-opentui.js +14540 -7374
- package/dist/terminal-pi.js +285 -666
- package/docs/README.md +22 -2
- package/docs/architecture/00-overview.md +28 -16
- package/docs/architecture/02-sessions-and-runs.md +72 -15
- package/docs/architecture/03-variants.md +10 -2
- package/docs/architecture/04-prompt.md +26 -6
- package/docs/architecture/06-plugins-and-capabilities.md +39 -3
- package/docs/architecture/07-tools-and-gate.md +21 -6
- package/docs/architecture/09-accounts-and-connectors.md +8 -0
- package/docs/architecture/10-server-protocol.md +111 -2
- package/docs/architecture/11-terminal.md +185 -5
- package/docs/architecture/12-storage.md +15 -1
- package/docs/architecture/13-world-tools.md +28 -6
- package/docs/architecture/14-surfaces-and-parity.md +89 -0
- package/docs/architecture/README.md +15 -4
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +4 -2
- package/docs/architecture/proposals/PROP-0013-resource-browser.md +58 -0
- package/docs/architecture/proposals/PROP-0014-fullscreen-browser-tabs.md +58 -0
- package/docs/architecture/proposals/PROP-0015-one-capability-surface.md +88 -0
- package/docs/architecture/proposals/PROP-0016-managed-worktrees.md +156 -0
- package/docs/architecture/proposals/PROP-0017-page-annotations.md +127 -0
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +179 -0
- package/docs/architecture/proposals/PROP-0019-contextual-side-questions.md +122 -0
- package/docs/architecture/proposals/PROP-0020-builtin-feature-plugins.md +105 -0
- package/docs/architecture/proposals/PROP-0021-command-namespaces-and-duplicates.md +99 -0
- package/docs/architecture/proposals/PROP-0022-plan-only-questions.md +102 -0
- package/docs/architecture/proposals/PROP-0023-xagent-cli-name.md +61 -0
- package/docs/architecture/proposals/PROP-0024-question-sketch-previews.md +130 -0
- package/docs/architecture/proposals/PROP-0025-contextual-question-wireframes.md +49 -0
- package/docs/architecture/proposals/PROP-0026-connected-sketch-display.md +94 -0
- package/docs/architecture/proposals/PROP-0027-xingular-agent-product-identity.md +60 -0
- package/docs/architecture/proposals/PROP-0028-normalize-question-wireframes.md +55 -0
- package/docs/architecture/proposals/PROP-0029-agent-profiles.md +124 -0
- package/docs/architecture/proposals/PROP-0030-subagent-title.md +86 -0
- package/docs/architecture/proposals/README.md +25 -0
- package/docs/decisions/ADR-0009-xharness-command.md +6 -5
- package/docs/decisions/ADR-0014-public-release-baseline.md +3 -2
- package/docs/decisions/ADR-0015-resource-browser.md +28 -0
- package/docs/decisions/ADR-0016-fullscreen-browser-tabs.md +31 -0
- package/docs/decisions/ADR-0017-one-capability-surface.md +34 -0
- package/docs/decisions/ADR-0018-builtin-feature-plugins.md +35 -0
- package/docs/decisions/ADR-0019-managed-git-worktrees.md +31 -0
- package/docs/decisions/ADR-0020-page-annotations.md +37 -0
- package/docs/decisions/ADR-0021-required-command-namespaces.md +40 -0
- package/docs/decisions/ADR-0022-plan-only-question-tools.md +34 -0
- package/docs/decisions/ADR-0023-contextual-side-question-runs.md +35 -0
- package/docs/decisions/ADR-0024-xagent-command.md +40 -0
- package/docs/decisions/ADR-0025-question-choice-sketches.md +49 -0
- package/docs/decisions/ADR-0026-contextual-question-wireframes.md +58 -0
- package/docs/decisions/ADR-0027-connected-sketch-display.md +60 -0
- package/docs/decisions/ADR-0028-xingular-agent-product-identity.md +56 -0
- package/docs/decisions/ADR-0029-normalized-question-wireframe-borders.md +33 -0
- package/docs/decisions/ADR-0030-agent-profiles.md +43 -0
- package/docs/doctor.md +10 -4
- package/docs/features/README.md +26 -0
- package/docs/features/interfaces/README.md +1 -1
- package/docs/future-work.md +48 -0
- package/docs/parity.md +78 -0
- package/docs/releases/1.0.0.md +2 -2
- package/docs/releases/4.0.0.md +46 -0
- package/docs/releases/README.md +20 -15
- package/docs/study/00-index.md +5 -0
- package/docs/study/autonomous-engineering-sources.md +277 -0
- package/docs/study/autonomy-sources.md +147 -0
- package/docs/study/proactive-autonomy.md +408 -0
- package/docs/study/tui-resource-browser.md +78 -0
- package/docs/terminal-tui.md +325 -93
- package/package.json +3 -3
|
@@ -21,8 +21,13 @@ import * as variantStore from "../variant/store.js";
|
|
|
21
21
|
import { compile, copyToRun, record, render, saveForSession } from "../prompt/compile.js";
|
|
22
22
|
import { isActiveStatus } from "../sessions/contracts.js";
|
|
23
23
|
import { findRun, loadRun, saveRun, updateRun } from "../sessions/runStore.js";
|
|
24
|
+
import { CHECKLIST_WRITE } from "../tools/checklist.js";
|
|
25
|
+
import { createProfile, listProfiles, profileDirs, profileSection, removeProfile, resolveProfile } from "../agents/profiles.js";
|
|
24
26
|
import { SESSION_NAME } from "../tools/sessionName.js";
|
|
25
27
|
import { contributionId } from "../plugins/identity.js";
|
|
28
|
+
import { setupScript } from "../worktrees/setup.js";
|
|
29
|
+
import { inspectStep } from "../annotations/inspect.js";
|
|
30
|
+
import * as pageNotes from "../annotations/store.js";
|
|
26
31
|
import { APPS_COMMAND, WIKI_COMMAND } from "./cli.js";
|
|
27
32
|
import { SOCKET_ENV, TOKEN_ENV } from "./client.js";
|
|
28
33
|
import { TerminalController } from "./controller.js";
|
|
@@ -30,6 +35,7 @@ import { terminalCommandFiles } from "./launch.js";
|
|
|
30
35
|
import { SessionSocket, socketPath } from "./socket.js";
|
|
31
36
|
import { harnessLogInfo, readHarnessLogChunk } from "./logs.js";
|
|
32
37
|
import { listTerminalTrajectoryRuns, readTerminalTrajectory } from "./trajectory.js";
|
|
38
|
+
import { BrowserControl } from "./browserControl.js";
|
|
33
39
|
/** Identity of the exact resource decision that this terminal was prepared with. */
|
|
34
40
|
function resourceIdentity(loading) {
|
|
35
41
|
if (!loading)
|
|
@@ -63,8 +69,27 @@ export class Terminals {
|
|
|
63
69
|
worldReadOnly = new Set();
|
|
64
70
|
socket;
|
|
65
71
|
assets;
|
|
66
|
-
/** A session's `/accounts` switch, taken once its Pi process has ended. */
|
|
72
|
+
/** A session's `/accounts` or `/worktree` switch, taken once its Pi process has ended. */
|
|
67
73
|
relaunches = new Map();
|
|
74
|
+
/** The managed worktree each live session holds. */
|
|
75
|
+
worktreeLeases = new Map();
|
|
76
|
+
/** Each child's profile name and colour, looked up once per child run. */
|
|
77
|
+
childProfiles = new Map();
|
|
78
|
+
subagentProfile(record) {
|
|
79
|
+
if (!record.profile)
|
|
80
|
+
return null;
|
|
81
|
+
if (!this.childProfiles.has(record.id)) {
|
|
82
|
+
if (this.childProfiles.size > 500)
|
|
83
|
+
this.childProfiles.clear();
|
|
84
|
+
let color = 'blue';
|
|
85
|
+
try {
|
|
86
|
+
color = resolveProfile(this.options.env.root, record.workingDirectory, record.profile)?.color ?? color;
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
this.childProfiles.set(record.id, { name: record.profile, color });
|
|
90
|
+
}
|
|
91
|
+
return this.childProfiles.get(record.id) ?? null;
|
|
92
|
+
}
|
|
68
93
|
constructor(options) {
|
|
69
94
|
this.options = options;
|
|
70
95
|
this.assets = options.assets ?? defaultAssets();
|
|
@@ -83,6 +108,7 @@ export class Terminals {
|
|
|
83
108
|
const owner = this.sessions.get(parent.sessionId);
|
|
84
109
|
if (owner?.controller.activeRun?.runId === parent.id) {
|
|
85
110
|
owner.controller.observe(event, true);
|
|
111
|
+
owner.controller.observeSubagent(parent.id, record, event, this.subagentProfile(record));
|
|
86
112
|
break;
|
|
87
113
|
}
|
|
88
114
|
parentId = parent.parentRunId;
|
|
@@ -235,6 +261,14 @@ export class Terminals {
|
|
|
235
261
|
const settings = params.settings && typeof params.settings === 'object' && !Array.isArray(params.settings) ? params.settings : {};
|
|
236
262
|
return api.configure({ ...scope, pluginId: text('pluginId'), settings });
|
|
237
263
|
}
|
|
264
|
+
case 'plugins.install':
|
|
265
|
+
if (!api.install)
|
|
266
|
+
throw invalid('Package installation is unavailable in this host.');
|
|
267
|
+
return api.install({ ...scope, source: text('source') });
|
|
268
|
+
case 'plugins.remove':
|
|
269
|
+
if (!api.remove)
|
|
270
|
+
throw invalid('Package removal is unavailable in this host.');
|
|
271
|
+
return api.remove({ ...scope, pluginId: text('pluginId') });
|
|
238
272
|
default:
|
|
239
273
|
throw new HarnessError('unknown_method', `Unknown call ${call}.`);
|
|
240
274
|
}
|
|
@@ -324,6 +358,194 @@ export class Terminals {
|
|
|
324
358
|
throw new HarnessError('unknown_method', `Unknown call ${call}.`);
|
|
325
359
|
}
|
|
326
360
|
}
|
|
361
|
+
/** `/agents`: the agent profiles this session's folder can use (PROP-0029). */
|
|
362
|
+
agentsCall(live, call, params) {
|
|
363
|
+
const root = this.options.env.root;
|
|
364
|
+
const cwd = live.input.cwd;
|
|
365
|
+
const name = () => {
|
|
366
|
+
if (typeof params.name !== 'string' || !params.name.trim())
|
|
367
|
+
throw invalid('A profile name is required.');
|
|
368
|
+
return params.name.trim();
|
|
369
|
+
};
|
|
370
|
+
const scope = () => {
|
|
371
|
+
if (params.scope !== 'global' && params.scope !== 'project')
|
|
372
|
+
throw invalid('scope must be global or project.');
|
|
373
|
+
return params.scope;
|
|
374
|
+
};
|
|
375
|
+
switch (call.slice('terminal.agents.'.length)) {
|
|
376
|
+
case 'list': return { ...listProfiles(root, cwd, this.variantDirs.get(live.input.sessionId) ?? null), dirs: profileDirs(root, cwd) };
|
|
377
|
+
case 'create': return createProfile(root, cwd, { name: name(), scope: scope(), from: typeof params.from === 'string' && params.from ? params.from : null });
|
|
378
|
+
case 'remove':
|
|
379
|
+
removeProfile(root, cwd, { name: name(), scope: scope() });
|
|
380
|
+
return {};
|
|
381
|
+
default: throw invalid('Unknown profile operation.');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/** `/worktree`: the managed worktrees of the repository this session works in. */
|
|
385
|
+
async worktreesCall(live, call, params) {
|
|
386
|
+
const worktrees = this.options.worktrees;
|
|
387
|
+
if (!worktrees)
|
|
388
|
+
throw invalid('Worktrees are unavailable in this host.');
|
|
389
|
+
const sessionId = live.input.sessionId;
|
|
390
|
+
const cwd = live.input.cwd;
|
|
391
|
+
const current = this.worktreeLeases.get(sessionId) ?? null;
|
|
392
|
+
const id = () => {
|
|
393
|
+
if (typeof params.id !== 'string' || !params.id)
|
|
394
|
+
throw invalid('A worktree id is required.');
|
|
395
|
+
return params.id;
|
|
396
|
+
};
|
|
397
|
+
const action = call.slice('terminal.worktrees.'.length);
|
|
398
|
+
switch (action) {
|
|
399
|
+
case 'list': {
|
|
400
|
+
let repo;
|
|
401
|
+
try {
|
|
402
|
+
repo = await worktrees.repositoryOf(cwd);
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
return { repo: null, current, worktrees: [], removed: [], reason: error instanceof Error ? error.message : String(error) };
|
|
406
|
+
}
|
|
407
|
+
const statuses = await Promise.all((await worktrees.list(repo)).map((record) => worktrees.status(record.id)));
|
|
408
|
+
return { repo, current, worktrees: statuses, removed: worktrees.removed(repo), reason: null };
|
|
409
|
+
}
|
|
410
|
+
case 'status': return worktrees.status(id());
|
|
411
|
+
case 'create': return worktrees.create({
|
|
412
|
+
repo: cwd,
|
|
413
|
+
...(typeof params.name === 'string' && params.name ? { name: params.name } : {}),
|
|
414
|
+
...(typeof params.branch === 'string' && params.branch ? { branch: params.branch } : {}),
|
|
415
|
+
kind: params.kind === 'permanent' ? 'permanent' : 'managed'
|
|
416
|
+
});
|
|
417
|
+
case 'branch':
|
|
418
|
+
if (typeof params.name !== 'string' || !params.name)
|
|
419
|
+
throw invalid('A branch name is required.');
|
|
420
|
+
return worktrees.branch(id(), params.name);
|
|
421
|
+
case 'handoff': {
|
|
422
|
+
const direction = params.direction === 'toSource' || params.direction === 'toWorktree' ? params.direction : null;
|
|
423
|
+
if (!direction)
|
|
424
|
+
throw invalid('direction is toSource or toWorktree.');
|
|
425
|
+
return worktrees.handoff(id(), direction, { move: params.move !== false });
|
|
426
|
+
}
|
|
427
|
+
case 'remove':
|
|
428
|
+
if (id() === current)
|
|
429
|
+
throw new HarnessError('worktree_in_use', 'This session works in that worktree. Switch to another folder first.');
|
|
430
|
+
return worktrees.remove(id(), { force: params.force === true });
|
|
431
|
+
case 'restore': return worktrees.restore(id());
|
|
432
|
+
case 'prune': return worktrees.prune({ repo: cwd, ...(typeof params.keep === 'number' ? { keep: params.keep } : {}) });
|
|
433
|
+
case 'trustSetup': {
|
|
434
|
+
const repo = await worktrees.repositoryOf(cwd);
|
|
435
|
+
const script = setupScript(repo);
|
|
436
|
+
if (!script)
|
|
437
|
+
throw invalid('This repository has no .xharness/worktree-setup.');
|
|
438
|
+
await worktrees.trustSetup(repo, script.hash);
|
|
439
|
+
return { hash: script.hash };
|
|
440
|
+
}
|
|
441
|
+
case 'switch': {
|
|
442
|
+
// A new conversation in the chosen folder: Pi keeps one history per working folder.
|
|
443
|
+
const target = params.main === true ? null : id();
|
|
444
|
+
const folder = target ? worktrees.pathOf(target) : await worktrees.repositoryOf(cwd);
|
|
445
|
+
if (!folder)
|
|
446
|
+
throw new HarnessError('worktree_missing', 'That worktree has no folder.');
|
|
447
|
+
this.relaunches.set(sessionId, { accountId: live.account.id, nativeSessionId: null, cwd: folder, worktreeId: target });
|
|
448
|
+
return { cwd: folder, worktreeId: target };
|
|
449
|
+
}
|
|
450
|
+
default:
|
|
451
|
+
throw new HarnessError('unknown_method', `Unknown call ${call}.`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/** `/plugin:notes` and `/plugin:annotate`: the page notes a person keeps, and the visible browser they capture them in. */
|
|
455
|
+
async annotationsCall(live, call, params) {
|
|
456
|
+
const root = this.options.env.root;
|
|
457
|
+
const sessionId = live.input.sessionId;
|
|
458
|
+
const capture = this.options.noteCapture;
|
|
459
|
+
const id = () => {
|
|
460
|
+
if (typeof params.id !== 'string' || !params.id)
|
|
461
|
+
throw invalid('A note id is required.');
|
|
462
|
+
return params.id;
|
|
463
|
+
};
|
|
464
|
+
const version = () => {
|
|
465
|
+
if (typeof params.expectedVersion !== 'number')
|
|
466
|
+
throw invalid('The note version you last read is required.');
|
|
467
|
+
return params.expectedVersion;
|
|
468
|
+
};
|
|
469
|
+
const needCapture = () => {
|
|
470
|
+
if (!capture)
|
|
471
|
+
throw invalid('This host has no capture browser.');
|
|
472
|
+
return capture;
|
|
473
|
+
};
|
|
474
|
+
const needNotes = () => {
|
|
475
|
+
if (!this.options.pageNotes)
|
|
476
|
+
throw invalid('This host cannot send page notes.');
|
|
477
|
+
return this.options.pageNotes;
|
|
478
|
+
};
|
|
479
|
+
const action = call.slice('terminal.annotations.'.length);
|
|
480
|
+
switch (action) {
|
|
481
|
+
case 'list': {
|
|
482
|
+
const result = pageNotes.list(root, {
|
|
483
|
+
query: typeof params.query === 'string' ? params.query : null,
|
|
484
|
+
url: typeof params.url === 'string' ? params.url : null,
|
|
485
|
+
status: params.status === 'open' || params.status === 'resolved' ? params.status : null,
|
|
486
|
+
limit: 200
|
|
487
|
+
});
|
|
488
|
+
return { ...result, sessionId };
|
|
489
|
+
}
|
|
490
|
+
case 'read': return pageNotes.read(root, id());
|
|
491
|
+
case 'create': {
|
|
492
|
+
const note = pageNotes.create(root, { source: params.source, target: params.target, body: params.body, tags: params.tags });
|
|
493
|
+
return params.share === true ? pageNotes.share(root, note.id, { sessionId }) : note;
|
|
494
|
+
}
|
|
495
|
+
case 'update': return pageNotes.update(root, id(), version(), (params.patch ?? {}));
|
|
496
|
+
case 'delete':
|
|
497
|
+
pageNotes.remove(root, id(), version());
|
|
498
|
+
return {};
|
|
499
|
+
// A terminal shares with, and withdraws from, its own session only.
|
|
500
|
+
case 'share': return pageNotes.share(root, id(), { sessionId });
|
|
501
|
+
case 'unshare': return pageNotes.unshare(root, id(), { sessionId });
|
|
502
|
+
case 'open': return needCapture().open(sessionId, typeof params.url === 'string' && params.url ? params.url : null);
|
|
503
|
+
case 'identity': return needCapture().identity(sessionId);
|
|
504
|
+
case 'begin':
|
|
505
|
+
if (params.kind !== 'text' && params.kind !== 'element')
|
|
506
|
+
throw invalid('kind is text or element.');
|
|
507
|
+
return needCapture().begin(sessionId, params.kind);
|
|
508
|
+
case 'capture':
|
|
509
|
+
if (typeof params.captureId !== 'string')
|
|
510
|
+
throw invalid('A capture id is required.');
|
|
511
|
+
return needCapture().capture(sessionId, params.captureId);
|
|
512
|
+
case 'cancel':
|
|
513
|
+
await needCapture().cancel(sessionId, typeof params.captureId === 'string' ? params.captureId : undefined);
|
|
514
|
+
return {};
|
|
515
|
+
case 'reveal': {
|
|
516
|
+
const note = pageNotes.read(root, id());
|
|
517
|
+
return needCapture().reveal(sessionId, note.source.url, note.target);
|
|
518
|
+
}
|
|
519
|
+
// ---- The inspector and the queue (PROP-0018); a terminal queues and sends to its own session.
|
|
520
|
+
case 'target': return { ...needCapture().describe(sessionId), windows: await needCapture().windows(sessionId).catch(() => []) };
|
|
521
|
+
case 'attach': {
|
|
522
|
+
const kind = params.kind;
|
|
523
|
+
if (kind !== 'web' && kind !== 'cdp' && kind !== 'bridge')
|
|
524
|
+
throw invalid('kind is web, cdp or bridge.');
|
|
525
|
+
const spec = kind === 'cdp'
|
|
526
|
+
? { kind, endpoint: typeof params.endpoint === 'string' ? params.endpoint : '', name: typeof params.name === 'string' ? params.name : null }
|
|
527
|
+
: kind === 'bridge' ? { kind, name: typeof params.name === 'string' ? params.name : null } : { kind };
|
|
528
|
+
return needCapture().attach(sessionId, spec);
|
|
529
|
+
}
|
|
530
|
+
case 'windows': return needCapture().windows(sessionId);
|
|
531
|
+
case 'selectWindow':
|
|
532
|
+
if (typeof params.window !== 'string')
|
|
533
|
+
throw invalid('A window id is required.');
|
|
534
|
+
await needCapture().selectWindow(sessionId, params.window);
|
|
535
|
+
return {};
|
|
536
|
+
case 'inspect': return needCapture().inspect(sessionId);
|
|
537
|
+
case 'stopInspect':
|
|
538
|
+
await needCapture().stopInspect(sessionId);
|
|
539
|
+
return {};
|
|
540
|
+
case 'next': return inspectStep(needCapture(), needNotes(), sessionId, sessionId, 'terminal');
|
|
541
|
+
case 'queue': return needNotes().queue(id(), version(), sessionId);
|
|
542
|
+
case 'dequeue': return needNotes().dequeue(id(), version());
|
|
543
|
+
case 'confirm': return needNotes().confirm(id(), version());
|
|
544
|
+
case 'send': return needNotes().send(sessionId, { ids: Array.isArray(params.ids) ? params.ids.filter((entry) => typeof entry === 'string') : null });
|
|
545
|
+
default:
|
|
546
|
+
throw new HarnessError('unknown_method', `Unknown call ${call}.`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
327
549
|
/** The account a session asked to switch to. Taken once, after its Pi process has ended and before `dispose`. */
|
|
328
550
|
takeRelaunch(sessionId) {
|
|
329
551
|
const request = this.relaunches.get(sessionId) ?? null;
|
|
@@ -413,13 +635,15 @@ export class Terminals {
|
|
|
413
635
|
}
|
|
414
636
|
// With no application policy: reads run, a read-only session refuses the rest, and anything else asks.
|
|
415
637
|
const name = tool.toLowerCase();
|
|
416
|
-
if (name === SESSION_NAME || name === SPAWN_SUBAGENT || name === WAIT_SUBAGENTS || READ_ONLY_TOOLS.has(name) || this.worldReadOnly.has(name) || this.options.apps.toolNames.includes(name))
|
|
638
|
+
if (name === SESSION_NAME || name === CHECKLIST_WRITE || name === SPAWN_SUBAGENT || name === WAIT_SUBAGENTS || READ_ONLY_TOOLS.has(name) || this.worldReadOnly.has(name) || this.options.apps.toolNames.includes(name))
|
|
417
639
|
return { allowed: true };
|
|
418
640
|
if (live.input.readOnly)
|
|
419
641
|
return { allowed: false, reason: 'This session is read-only.' };
|
|
420
642
|
return { allowed: false, requiresApproval: true, rememberable: true, reason: 'This native action requires approval.' };
|
|
421
643
|
}
|
|
422
644
|
async subagentsCall(live, call, params) {
|
|
645
|
+
if (call === 'terminal.subagents.monitor')
|
|
646
|
+
return { children: live.controller.subagentSnapshot() };
|
|
423
647
|
const service = this.options.subagents;
|
|
424
648
|
if (!service || live.input.subagents === null) {
|
|
425
649
|
if (call === 'terminal.subagents.tools')
|
|
@@ -435,7 +659,7 @@ export class Terminals {
|
|
|
435
659
|
if (!Array.isArray(params.tools) || params.tools.some((name) => typeof name !== 'string'))
|
|
436
660
|
throw invalid('Native tool names are required.');
|
|
437
661
|
const readOnly = live.input.readOnly === true;
|
|
438
|
-
const readTools = new Set([...READ_ONLY_TOOLS, SESSION_NAME, 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']);
|
|
662
|
+
const readTools = new Set([...READ_ONLY_TOOLS, SESSION_NAME, CHECKLIST_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']);
|
|
439
663
|
const tools = params.tools.filter((name) => !readOnly || readTools.has(name));
|
|
440
664
|
const capabilities = this.options.resolveCapabilities ? await this.options.resolveCapabilities(live.caller, tools, live.input.cwd) : { version: 1, tools, connectors: [], contributions: [] };
|
|
441
665
|
freezeRun(this.options.env.root, run.runId, live.caller, capabilities);
|
|
@@ -446,6 +670,7 @@ export class Terminals {
|
|
|
446
670
|
effort: typeof params.effort === 'string' ? params.effort : null,
|
|
447
671
|
launch: live.input.launch, tools: { allow: capabilities.tools, gated: capabilities.tools.filter(name => !readTools.has(name)) },
|
|
448
672
|
connections: live.input.connections === false ? false : { readOnly }, subagents: live.input.subagents,
|
|
673
|
+
variant: this.variantDirs.has(live.input.sessionId) ? { dir: this.variantDirs.get(live.input.sessionId), scope: live.input.variant && 'scope' in live.input.variant ? live.input.variant.scope ?? null : null } : null,
|
|
449
674
|
capabilities
|
|
450
675
|
}, live.account.id, capabilities);
|
|
451
676
|
service.restore(run.runId, params.history ?? { owners: [], receipts: [] });
|
|
@@ -519,6 +744,43 @@ export class Terminals {
|
|
|
519
744
|
}
|
|
520
745
|
throw new HarnessError('unknown_method', `Unknown call ${call}.`);
|
|
521
746
|
}
|
|
747
|
+
async sideQuestionCall(live, call, params) {
|
|
748
|
+
const service = this.options.sideQuestions;
|
|
749
|
+
if (!service)
|
|
750
|
+
throw new HarnessError('unknown_method', `Unknown call ${call}.`);
|
|
751
|
+
if (call === 'terminal.sideQuestions.start') {
|
|
752
|
+
if (typeof params.question !== 'string')
|
|
753
|
+
throw invalid('A side question is required.');
|
|
754
|
+
const context = params.context && typeof params.context === 'object' && !Array.isArray(params.context)
|
|
755
|
+
? params.context
|
|
756
|
+
: undefined;
|
|
757
|
+
const latest = listTerminalTrajectoryRuns(this.options.env.root, live.input.sessionId, live.controller.nativeSessionId)
|
|
758
|
+
.find(run => findRun(this.options.env.root, run.id)?.trigger !== 'side-question');
|
|
759
|
+
return service.start({
|
|
760
|
+
sessionId: live.input.sessionId,
|
|
761
|
+
parentRunId: live.controller.activeRun?.runId ?? latest?.id ?? null,
|
|
762
|
+
parentSessionId: live.input.sessionId,
|
|
763
|
+
accountId: live.account.id,
|
|
764
|
+
model: typeof params.model === 'string' ? params.model : live.input.model ?? null,
|
|
765
|
+
effort: typeof params.effort === 'string' ? params.effort : undefined,
|
|
766
|
+
cwd: live.input.cwd,
|
|
767
|
+
caller: live.caller,
|
|
768
|
+
question: params.question,
|
|
769
|
+
context
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
if (typeof params.runId !== 'string')
|
|
773
|
+
throw invalid('A side-question run id is required.');
|
|
774
|
+
if (call === 'terminal.sideQuestions.read') {
|
|
775
|
+
const afterSequence = params.afterSequence === undefined ? -1 : params.afterSequence;
|
|
776
|
+
if (typeof afterSequence !== 'number')
|
|
777
|
+
throw invalid('A side-question event sequence is required.');
|
|
778
|
+
return service.read(params.runId, live.input.sessionId, afterSequence);
|
|
779
|
+
}
|
|
780
|
+
if (call === 'terminal.sideQuestions.interrupt')
|
|
781
|
+
return service.interrupt(params.runId, live.input.sessionId);
|
|
782
|
+
throw new HarnessError('unknown_method', `Unknown call ${call}.`);
|
|
783
|
+
}
|
|
522
784
|
/** The only terminal frontend: the Bun-run OpenTUI application. */
|
|
523
785
|
frontendCommand() {
|
|
524
786
|
return {
|
|
@@ -538,32 +800,51 @@ export class Terminals {
|
|
|
538
800
|
if (!input?.sessionId || !input.cwd || !input.sessionFolder)
|
|
539
801
|
throw invalid('A terminal session needs an id, a working folder, and its own folder.');
|
|
540
802
|
await this.dispose(input.sessionId);
|
|
541
|
-
const command = this.frontendCommand();
|
|
542
|
-
// A pinned account is used as it is; it is never exchanged for another one.
|
|
543
|
-
const account = input.accountId ? accounts.find(input.accountId) : accounts.resolve(null, null);
|
|
544
|
-
const launch = launchEnv(accounts.dir, account);
|
|
545
|
-
const loading = (await this.options.plugins?.loading({ cwd: input.cwd, agentDir: configDir(accounts.dir, account), install: true, caller: { projectId: input.projectId ?? null } })) ?? null;
|
|
546
|
-
await this.socket.listen();
|
|
547
|
-
await accounts.terminalBegin(account);
|
|
548
803
|
const sessionId = input.sessionId;
|
|
549
|
-
|
|
804
|
+
let acquiredWorktreeId = null;
|
|
805
|
+
let account = null;
|
|
806
|
+
let accountStarted = false;
|
|
550
807
|
let token = null;
|
|
551
808
|
try {
|
|
809
|
+
const worktreeId = input.run?.worktreeId ?? null;
|
|
810
|
+
const worktreePath = worktreeId ? this.options.worktrees?.pathOf(worktreeId) ?? null : null;
|
|
811
|
+
if (worktreeId && worktreePath) {
|
|
812
|
+
await this.options.worktrees.acquire(worktreeId, sessionId);
|
|
813
|
+
acquiredWorktreeId = worktreeId;
|
|
814
|
+
this.worktreeLeases.set(sessionId, worktreeId);
|
|
815
|
+
input.cwd = worktreePath;
|
|
816
|
+
}
|
|
817
|
+
const command = this.frontendCommand();
|
|
818
|
+
// A pinned account is used as it is; it is never exchanged for another one.
|
|
819
|
+
const preparedAccount = input.accountId ? accounts.find(input.accountId) : accounts.resolve(null, null);
|
|
820
|
+
account = preparedAccount;
|
|
821
|
+
const launch = launchEnv(accounts.dir, preparedAccount);
|
|
822
|
+
const loading = (await this.options.plugins?.loading({ cwd: input.cwd, agentDir: configDir(accounts.dir, preparedAccount), install: true, caller: { projectId: input.projectId ?? null } })) ?? null;
|
|
823
|
+
await this.socket.listen();
|
|
824
|
+
await accounts.terminalBegin(preparedAccount);
|
|
825
|
+
accountStarted = true;
|
|
826
|
+
const caller = { sessionId, projectId: input.projectId ?? null, actor: 'Code', surface: 'cli' };
|
|
552
827
|
const broker = input.connections === false ? null : apps.grant({ sessionId, runId: null, caller, readOnly: !!input.readOnly, depth: 0 });
|
|
553
828
|
// The session keeps these instructions; each of its runs records a copy, and a memory
|
|
554
829
|
// block that changes later is sent along as a note.
|
|
555
830
|
const variant = input.variant ? materialise(root, input.variant, { trust: this.options.trust ?? null }) : null;
|
|
831
|
+
const browser = new BrowserControl({ root, cwd: input.cwd, pinned: variant,
|
|
832
|
+
writable: Boolean(input.variant && !('dir' in input.variant)), apps, hub: this.options.hub,
|
|
833
|
+
plugins: () => this.options.pluginControl?.list({ cwd: input.cwd, accountId: preparedAccount.id, projectId: input.projectId ?? null }) ?? Promise.resolve([]) });
|
|
556
834
|
if (variant)
|
|
557
835
|
this.variantDirs.set(sessionId, variant.dir);
|
|
558
|
-
const worldTools = await this.worldToolNames(input,
|
|
559
|
-
const launchTools = [...new Set([...(input.launch?.tools ?? []), SESSION_NAME, ...worldTools])];
|
|
560
|
-
|
|
836
|
+
const worldTools = await this.worldToolNames(input, preparedAccount);
|
|
837
|
+
const launchTools = [...new Set([...(input.launch?.tools ?? []), CHECKLIST_WRITE, SESSION_NAME, ...worldTools])];
|
|
838
|
+
// The profiles replace the variant's own subagent list, which they include.
|
|
839
|
+
const profiles = this.options.subagents && input.subagents !== null ? profileSection(root, input.cwd, variant?.dir ?? null) : null;
|
|
840
|
+
const launchSections = profiles ? [...(input.launch?.sections ?? []).filter((section) => section.id !== profiles.id), profiles] : input.launch?.sections;
|
|
841
|
+
const pack = compile(env, caller, { ...input.launch, sections: launchSections, tools: launchTools }, variant && profiles ? { ...variant, sections: variant.sections.filter((section) => section.id !== profiles.id) } : variant);
|
|
561
842
|
record(root, pack);
|
|
562
843
|
mkdirSync(this.historyDir(input), { recursive: true });
|
|
563
844
|
saveForSession(input.sessionFolder, pack);
|
|
564
845
|
const live = {
|
|
565
846
|
input,
|
|
566
|
-
account,
|
|
847
|
+
account: preparedAccount,
|
|
567
848
|
broker,
|
|
568
849
|
caller,
|
|
569
850
|
token: '',
|
|
@@ -586,6 +867,46 @@ export class Terminals {
|
|
|
586
867
|
caller: () => ({ ...caller, runId: live.controller.activeRun?.runId ?? null }),
|
|
587
868
|
broker,
|
|
588
869
|
terminal: { accept: (event, data) => live.controller.accept(event, data), disconnected: () => live.controller.disconnected(), control: async (call, params) => {
|
|
870
|
+
if (call === 'terminal.browser.config') {
|
|
871
|
+
const world = this.options.world;
|
|
872
|
+
if (!world)
|
|
873
|
+
throw invalid('Browser settings are unavailable in this host.');
|
|
874
|
+
return { allowPrivate: world.browserConfig().allowPrivate };
|
|
875
|
+
}
|
|
876
|
+
if (call === 'terminal.browser.configure') {
|
|
877
|
+
const world = this.options.world;
|
|
878
|
+
if (!world)
|
|
879
|
+
throw invalid('Browser settings are unavailable in this host.');
|
|
880
|
+
if (typeof params.allowPrivate !== 'boolean')
|
|
881
|
+
throw invalid('allowPrivate must be a boolean.');
|
|
882
|
+
return { allowPrivate: world.configureBrowser({ allowPrivate: params.allowPrivate }).allowPrivate };
|
|
883
|
+
}
|
|
884
|
+
if (call === 'terminal.browser.snapshot')
|
|
885
|
+
return browser.snapshot(params.refresh === true);
|
|
886
|
+
if (call === 'terminal.browser.read') {
|
|
887
|
+
if (typeof params.id !== 'string')
|
|
888
|
+
throw invalid('A resource id is required.');
|
|
889
|
+
return browser.read(params.id, params.latest === true);
|
|
890
|
+
}
|
|
891
|
+
if (call === 'terminal.browser.importSkill') {
|
|
892
|
+
if (typeof params.path !== 'string' || !params.path.trim())
|
|
893
|
+
throw invalid('A skill folder is required.');
|
|
894
|
+
browser.importSkill(params.path);
|
|
895
|
+
bus.emit({ type: 'plugins.changed' });
|
|
896
|
+
return {};
|
|
897
|
+
}
|
|
898
|
+
if (call.startsWith('terminal.browser.connections.'))
|
|
899
|
+
return browser.connection(call.slice('terminal.browser.connections.'.length), params);
|
|
900
|
+
if (call === 'terminal.browser.hubPreview') {
|
|
901
|
+
if (!this.options.hub || typeof params.id !== 'string' || typeof params.version !== 'string')
|
|
902
|
+
throw invalid('A catalog entry and version are required.');
|
|
903
|
+
return this.options.hub.preview(params.id, params.version, typeof params.offset === 'number' ? params.offset : 0);
|
|
904
|
+
}
|
|
905
|
+
if (call === 'terminal.browser.hubDocument') {
|
|
906
|
+
if (!this.options.hub || typeof params.id !== 'string' || typeof params.version !== 'string')
|
|
907
|
+
throw invalid('An installed document and version are required.');
|
|
908
|
+
return this.options.hub.readDocument(params.id, params.version);
|
|
909
|
+
}
|
|
589
910
|
if (call.startsWith('terminal.doctor.')) {
|
|
590
911
|
const doctor = this.options.doctor;
|
|
591
912
|
if (!doctor)
|
|
@@ -616,8 +937,16 @@ export class Terminals {
|
|
|
616
937
|
return doctor.cancel(id);
|
|
617
938
|
throw invalid('Unknown diagnostic operation.');
|
|
618
939
|
}
|
|
940
|
+
if (call.startsWith('terminal.agents.'))
|
|
941
|
+
return this.agentsCall(live, call, params);
|
|
619
942
|
if (call.startsWith('terminal.subagents.'))
|
|
620
943
|
return this.subagentsCall(live, call, params);
|
|
944
|
+
if (call.startsWith('terminal.sideQuestions.'))
|
|
945
|
+
return this.sideQuestionCall(live, call, params);
|
|
946
|
+
if (call.startsWith('terminal.worktrees.'))
|
|
947
|
+
return this.worktreesCall(live, call, params);
|
|
948
|
+
if (call.startsWith('terminal.annotations.'))
|
|
949
|
+
return this.annotationsCall(live, call, params);
|
|
621
950
|
if (call === 'terminal.logs.info')
|
|
622
951
|
return harnessLogInfo(this.options.stateDir);
|
|
623
952
|
if (call === 'terminal.logs.read') {
|
|
@@ -653,7 +982,7 @@ export class Terminals {
|
|
|
653
982
|
}
|
|
654
983
|
if (call !== 'terminal.resources')
|
|
655
984
|
return live.controller.control(call, params);
|
|
656
|
-
const current = await this.options.plugins?.loading({ cwd: input.cwd, agentDir: configDir(accounts.dir,
|
|
985
|
+
const current = await this.options.plugins?.loading({ cwd: input.cwd, agentDir: configDir(accounts.dir, preparedAccount), install: false, caller: { projectId: input.projectId ?? null } }) ?? null;
|
|
657
986
|
if (resourceIdentity(current) !== resourceIdentity(loading))
|
|
658
987
|
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.');
|
|
659
988
|
return {};
|
|
@@ -691,7 +1020,7 @@ export class Terminals {
|
|
|
691
1020
|
[SOCKET_ENV]: this.socket.path,
|
|
692
1021
|
[TOKEN_ENV]: token
|
|
693
1022
|
},
|
|
694
|
-
accountId:
|
|
1023
|
+
accountId: preparedAccount.id,
|
|
695
1024
|
nativeSessionId: input.nativeSessionId ?? null
|
|
696
1025
|
};
|
|
697
1026
|
}
|
|
@@ -699,8 +1028,15 @@ export class Terminals {
|
|
|
699
1028
|
if (token)
|
|
700
1029
|
this.socket.revoke(token);
|
|
701
1030
|
this.sessions.delete(sessionId);
|
|
1031
|
+
this.variantDirs.delete(sessionId);
|
|
1032
|
+
this.worldNames.delete(sessionId);
|
|
1033
|
+
if (this.worktreeLeases.get(sessionId) === acquiredWorktreeId)
|
|
1034
|
+
this.worktreeLeases.delete(sessionId);
|
|
702
1035
|
apps.revokeSession(sessionId);
|
|
703
|
-
|
|
1036
|
+
if (accountStarted && account)
|
|
1037
|
+
await accounts.terminalEnd(account).catch(() => undefined);
|
|
1038
|
+
if (acquiredWorktreeId)
|
|
1039
|
+
await this.options.worktrees?.release(acquiredWorktreeId, sessionId).catch(() => undefined);
|
|
704
1040
|
throw error;
|
|
705
1041
|
}
|
|
706
1042
|
}
|
|
@@ -754,6 +1090,11 @@ export class Terminals {
|
|
|
754
1090
|
}
|
|
755
1091
|
this.sessions.delete(sessionId);
|
|
756
1092
|
this.worldNames.delete(sessionId);
|
|
1093
|
+
const worktreeId = this.worktreeLeases.get(sessionId);
|
|
1094
|
+
this.worktreeLeases.delete(sessionId);
|
|
1095
|
+
if (worktreeId)
|
|
1096
|
+
await this.options.worktrees?.release(worktreeId, sessionId).catch(() => undefined);
|
|
1097
|
+
void this.options.noteCapture?.release(sessionId).catch(() => undefined);
|
|
757
1098
|
void this.options.world?.release(sessionId).catch(() => undefined);
|
|
758
1099
|
live.controller.dispose();
|
|
759
1100
|
this.socket.revoke(live.token);
|
|
@@ -15,7 +15,7 @@ export function listTerminalTrajectoryRuns(root, sessionId, nativeSessionId) {
|
|
|
15
15
|
if (!nativeSessionId)
|
|
16
16
|
return [];
|
|
17
17
|
return listRuns(root, { sessionId })
|
|
18
|
-
.filter(run => run.nativeSessionId === nativeSessionId)
|
|
18
|
+
.filter(run => run.nativeSessionId === nativeSessionId || run.trigger === 'side-question' && run.sessionId === sessionId)
|
|
19
19
|
.map(summary);
|
|
20
20
|
}
|
|
21
21
|
/** Read a run only when it belongs to both this terminal and its current native conversation. */
|
|
@@ -23,7 +23,7 @@ export function readTerminalTrajectory(root, sessionId, nativeSessionId, runId,
|
|
|
23
23
|
if (!nativeSessionId || !Number.isSafeInteger(afterSequence ?? 0) || (afterSequence ?? 0) < 0)
|
|
24
24
|
throw notFound('Run');
|
|
25
25
|
const run = findRun(root, runId);
|
|
26
|
-
if (!run || run.sessionId !== sessionId || run.nativeSessionId !== nativeSessionId)
|
|
26
|
+
if (!run || run.sessionId !== sessionId || (run.nativeSessionId !== nativeSessionId && run.trigger !== 'side-question'))
|
|
27
27
|
throw notFound('Run');
|
|
28
28
|
const events = readEvents(root, runId).filter(event => afterSequence === undefined || event.sequence > afterSequence);
|
|
29
29
|
return { run: summary(run), events };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** `/plugin:annotate` accepts a page address, DevTools target, or page-bridge target. */
|
|
2
|
+
export function parseAnnotate(argument) {
|
|
3
|
+
const words = argument.trim().split(/\s+/).filter(Boolean);
|
|
4
|
+
if (!words.length)
|
|
5
|
+
return { kind: 'web' };
|
|
6
|
+
if (words[0] === '--app') {
|
|
7
|
+
const endpoint = words[1];
|
|
8
|
+
if (!endpoint)
|
|
9
|
+
return 'Give the app\'s DevTools address: /plugin:annotate --app http://127.0.0.1:9229';
|
|
10
|
+
const address = /^\d+$/.test(endpoint) ? `http://127.0.0.1:${endpoint}` : endpoint;
|
|
11
|
+
const name = words.slice(2).join(' ');
|
|
12
|
+
return { kind: 'cdp', endpoint: address, ...(name ? { name } : {}) };
|
|
13
|
+
}
|
|
14
|
+
if (words[0] === '--bridge') {
|
|
15
|
+
const name = words.slice(1).join(' ');
|
|
16
|
+
return { kind: 'bridge', ...(name ? { name } : {}) };
|
|
17
|
+
}
|
|
18
|
+
if (words.length > 1 || words[0].startsWith('--'))
|
|
19
|
+
return 'Use /plugin:annotate [url], /plugin:annotate --app <DevTools address>, or /plugin:annotate --bridge.';
|
|
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
|
+
return { kind: 'web', url };
|
|
22
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { TerminalPage } from "./page.js";
|
|
2
2
|
import type { TerminalCommandHost } from "./commands.js";
|
|
3
|
-
|
|
3
|
+
import { type AnnotateStart } from "./pages/annotatePage.js";
|
|
4
|
+
export type CommandPageId = 'doctor' | 'accounts' | 'permissions' | 'plugins' | 'hub' | 'components' | 'settings' | 'trajectory' | 'log' | 'harness' | 'skills' | 'connections' | 'tools' | 'worktree' | 'notes' | 'annotate' | 'subagents' | 'agents';
|
|
4
5
|
/** Construct one independent full-screen page for a slash command. */
|
|
5
6
|
export declare function createCommandPage(host: TerminalCommandHost, id: CommandPageId, options?: {
|
|
6
7
|
runId?: string;
|
|
8
|
+
url?: string;
|
|
9
|
+
annotate?: AnnotateStart;
|
|
10
|
+
close?(): void;
|
|
7
11
|
}): TerminalPage;
|