@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
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
// reply leaves it open for the next request. Each terminal session holds a random token bound to
|
|
6
6
|
// its caller, its connected-apps grant, and its controller; the token stops working when the
|
|
7
7
|
// terminal ends.
|
|
8
|
-
import {
|
|
9
|
-
import { chmodSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
9
|
+
import { chmodSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
10
10
|
import { createServer } from 'node:net';
|
|
11
11
|
import { tmpdir } from 'node:os';
|
|
12
12
|
import { dirname, join } from 'node:path';
|
|
@@ -17,21 +17,50 @@ const MAX_REQUEST_BYTES = 1024 * 1024;
|
|
|
17
17
|
/** macOS refuses a socket path longer than 103 bytes. */
|
|
18
18
|
const MAX_SOCKET_PATH = 100;
|
|
19
19
|
/**
|
|
20
|
-
* Windows has no socket files for Node's `net`: it
|
|
21
|
-
* machine-wide namespace
|
|
22
|
-
*
|
|
20
|
+
* Each Harness process listens on its own path. Windows has no socket files for Node's `net`: it
|
|
21
|
+
* listens on a named pipe in a machine-wide namespace, and a random name keeps another local process
|
|
22
|
+
* from guessing it. On Unix the file is named for this process, because several Harness processes
|
|
23
|
+
* may share one state folder: closing a server removes its socket file, so a shared name would let
|
|
24
|
+
* one process delete, and the next one take over, the path another's sessions still call. The token
|
|
25
|
+
* still decides what a caller may do.
|
|
23
26
|
*/
|
|
24
27
|
export function socketPath(stateDir) {
|
|
25
28
|
if (process.platform === 'win32')
|
|
26
29
|
return `\\\\.\\pipe\\xingular-harness-${randomBytes(12).toString('hex')}`;
|
|
27
|
-
const path = join(stateDir, 'run',
|
|
30
|
+
const path = join(stateDir, 'run', `harness-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
|
|
28
31
|
if (Buffer.byteLength(path) <= MAX_SOCKET_PATH)
|
|
29
32
|
return path;
|
|
30
|
-
return join(tmpdir(), `xh-${
|
|
33
|
+
return join(tmpdir(), `xh-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
|
|
34
|
+
}
|
|
35
|
+
const OWN_SOCKET = /^harness-(\d+)-[0-9a-f]+\.sock$/;
|
|
36
|
+
/** Removes socket files left in `folder` by Harness processes that are no longer running. */
|
|
37
|
+
function pruneDeadSockets(folder) {
|
|
38
|
+
let names;
|
|
39
|
+
try {
|
|
40
|
+
names = readdirSync(folder);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const name of names) {
|
|
46
|
+
const pid = Number(OWN_SOCKET.exec(name)?.[1]);
|
|
47
|
+
if (!pid || pid === process.pid || isRunning(pid))
|
|
48
|
+
continue;
|
|
49
|
+
rmSync(join(folder, name), { force: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function isRunning(pid) {
|
|
53
|
+
try {
|
|
54
|
+
process.kill(pid, 0);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return error.code === 'EPERM';
|
|
59
|
+
}
|
|
31
60
|
}
|
|
32
61
|
function alternateSocketPath(path) {
|
|
33
62
|
const candidate = join(dirname(path), `harness-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
|
|
34
|
-
return Buffer.byteLength(candidate) <= MAX_SOCKET_PATH ? candidate : join(tmpdir(), `xh-${randomBytes(
|
|
63
|
+
return Buffer.byteLength(candidate) <= MAX_SOCKET_PATH ? candidate : join(tmpdir(), `xh-${process.pid}-${randomBytes(8).toString('hex')}.sock`);
|
|
35
64
|
}
|
|
36
65
|
/** A named pipe has no folder, file mode, or owner to check or clean up. */
|
|
37
66
|
const isPipePath = (path) => path.startsWith('\\\\.\\pipe\\');
|
|
@@ -216,8 +245,10 @@ export class SessionSocket {
|
|
|
216
245
|
}
|
|
217
246
|
/** Binds the usual path, or an instance-specific path if another Harness owns it. */
|
|
218
247
|
async bindAvailablePath() {
|
|
219
|
-
if (!isPipePath(this.path))
|
|
248
|
+
if (!isPipePath(this.path)) {
|
|
220
249
|
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
250
|
+
pruneDeadSockets(dirname(this.path));
|
|
251
|
+
}
|
|
221
252
|
try {
|
|
222
253
|
await this.bind(this.path);
|
|
223
254
|
}
|
|
@@ -3,6 +3,8 @@ import { type Harness, type HarnessOptions } from "../harness.js";
|
|
|
3
3
|
export interface StandalonePaths {
|
|
4
4
|
dataRoot: string;
|
|
5
5
|
stateDir: string;
|
|
6
|
+
/** `--worktree [name]`: start in a new managed worktree (true) or the named one. */
|
|
7
|
+
worktree?: string | true | null;
|
|
6
8
|
}
|
|
7
9
|
export interface StandaloneDependencies {
|
|
8
10
|
home?: string;
|
|
@@ -5,10 +5,11 @@ import { appendFileSync } from 'node:fs';
|
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
7
7
|
import { setLogSink } from "../core/bus.js";
|
|
8
|
+
import { DEFAULT_PRODUCT_NAME } from "../core/env.js";
|
|
8
9
|
import { createHarness } from "../harness.js";
|
|
9
10
|
export function standaloneArguments(argv) {
|
|
10
11
|
const command = argv[0];
|
|
11
|
-
if (!command || command.startsWith('
|
|
12
|
+
if (!command || command.startsWith('-'))
|
|
12
13
|
return argv;
|
|
13
14
|
return command === 'terminal' ? argv.slice(1) : null;
|
|
14
15
|
}
|
|
@@ -21,6 +22,16 @@ export function parseStandaloneOptions(argv, home = homedir(), cwd = process.cwd
|
|
|
21
22
|
const given = new Set();
|
|
22
23
|
for (let index = 0; index < argv.length; index += 1) {
|
|
23
24
|
const argument = argv[index];
|
|
25
|
+
if (argument === '--worktree' || argument === '-w') {
|
|
26
|
+
const name = argv[index + 1];
|
|
27
|
+
if (name && !name.startsWith('-')) {
|
|
28
|
+
paths.worktree = name;
|
|
29
|
+
index += 1;
|
|
30
|
+
}
|
|
31
|
+
else
|
|
32
|
+
paths.worktree = true;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
24
35
|
if (argument !== '--data' && argument !== '--state')
|
|
25
36
|
throw new Error(`Unknown standalone option: ${argument}`);
|
|
26
37
|
const value = argv[index + 1];
|
|
@@ -99,14 +110,32 @@ function spawnFrontend(launch, cwd, spawn = nodeSpawn) {
|
|
|
99
110
|
child.once('exit', (code, signal) => finish(() => resolveExit(exitCode(code, signal))));
|
|
100
111
|
});
|
|
101
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* `--worktree`: the named worktree of the repository in `cwd`, or a new managed one. Older managed
|
|
115
|
+
* worktrees beyond the kept number are pruned (unsaved work is kept as a snapshot).
|
|
116
|
+
*/
|
|
117
|
+
async function enterWorktree(harness, cwd, wanted) {
|
|
118
|
+
const name = wanted === true ? undefined : wanted;
|
|
119
|
+
const existing = name ? (await harness.worktrees.list(cwd)).find((record) => record.name === name) : undefined;
|
|
120
|
+
if (existing)
|
|
121
|
+
return existing;
|
|
122
|
+
const created = await harness.worktrees.create({ repo: cwd, ...(name ? { name } : {}) });
|
|
123
|
+
process.stdout.write(`Working in worktree ${created.worktree.name} (${created.worktree.path}).\n`);
|
|
124
|
+
if (created.setup.state === 'untrusted')
|
|
125
|
+
process.stdout.write('This repository has a setup script you have not trusted; read .xharness/worktree-setup, then run: xagent worktree trust-setup\n');
|
|
126
|
+
if (created.setup.state === 'failed')
|
|
127
|
+
process.stdout.write(`The worktree setup script failed; see ${created.setup.log}.\n`);
|
|
128
|
+
await harness.worktrees.prune({ repo: cwd }).catch(() => undefined);
|
|
129
|
+
return created.worktree;
|
|
130
|
+
}
|
|
102
131
|
export async function runStandaloneTerminal(argv, dependencies = {}) {
|
|
103
132
|
if (!(dependencies.stdinIsTTY ?? process.stdin.isTTY) || !(dependencies.stdoutIsTTY ?? process.stdout.isTTY)) {
|
|
104
|
-
throw new Error('The interactive Harness terminal needs a TTY. Run it from a terminal, or use `
|
|
133
|
+
throw new Error('The interactive Harness terminal needs a TTY. Run it from a terminal, or use `xagent serve` for a host process.');
|
|
105
134
|
}
|
|
106
|
-
|
|
135
|
+
let cwd = resolve(dependencies.cwd ?? process.cwd());
|
|
107
136
|
const paths = parseStandaloneOptions(argv, dependencies.home, cwd);
|
|
108
137
|
const makeHarness = dependencies.createHarness ?? createHarness;
|
|
109
|
-
const harness = makeHarness({ dataRoot: paths.dataRoot, stateDir: paths.stateDir, productName:
|
|
138
|
+
const harness = makeHarness({ dataRoot: paths.dataRoot, stateDir: paths.stateDir, productName: DEFAULT_PRODUCT_NAME });
|
|
110
139
|
const newSessionId = dependencies.createSessionId ?? randomUUID;
|
|
111
140
|
let current = { sessionId: newSessionId(), accountId: null, nativeSessionId: null };
|
|
112
141
|
// The frontend draws on this terminal, so the host's diagnostics go to a file the person can read instead.
|
|
@@ -123,17 +152,28 @@ export async function runStandaloneTerminal(argv, dependencies = {}) {
|
|
|
123
152
|
await harness.ready;
|
|
124
153
|
if (harness.accounts.overview().accounts.length === 0)
|
|
125
154
|
harness.accounts.create('Default', 'subscription');
|
|
126
|
-
|
|
127
|
-
|
|
155
|
+
let worktreeId = null;
|
|
156
|
+
if (paths.worktree) {
|
|
157
|
+
const worktree = await enterWorktree(harness, cwd, paths.worktree);
|
|
158
|
+
cwd = worktree.path;
|
|
159
|
+
worktreeId = worktree.id;
|
|
160
|
+
}
|
|
128
161
|
for (;;) {
|
|
129
|
-
const
|
|
162
|
+
const sessionFolder = join(paths.dataRoot, 'sessions', 'terminal', createHash('sha256').update(cwd).digest('hex'));
|
|
163
|
+
const launch = await harness.terminals.prepare({ ...current, cwd, sessionFolder, ...(worktreeId ? { run: { worktreeId } } : {}) });
|
|
130
164
|
const code = await spawnFrontend(launch, cwd, dependencies.spawn);
|
|
131
|
-
// `/accounts`
|
|
165
|
+
// `/accounts` and `/worktree` ask for a switch and end the frontend. An account switch resumes
|
|
166
|
+
// the conversation; a folder switch starts a new one there.
|
|
132
167
|
const next = harness.terminals.takeRelaunch(current.sessionId);
|
|
133
168
|
if (!next)
|
|
134
169
|
return code;
|
|
135
|
-
|
|
136
|
-
|
|
170
|
+
if (next.cwd) {
|
|
171
|
+
process.stdout.write(`Opening ${next.cwd}.\n`);
|
|
172
|
+
cwd = next.cwd;
|
|
173
|
+
worktreeId = next.worktreeId ?? null;
|
|
174
|
+
}
|
|
175
|
+
else
|
|
176
|
+
process.stdout.write(`Switching this session to account ${harness.accounts.find(next.accountId).label}.\n`);
|
|
137
177
|
await harness.terminals.dispose(current.sessionId);
|
|
138
178
|
current = { sessionId: newSessionId(), accountId: next.accountId, nativeSessionId: next.nativeSessionId };
|
|
139
179
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { RunEvent, RunStatus } from "../sessions/contracts.js";
|
|
2
|
+
/** The agent profile a child started from, as the roster shows it (PROP-0029). */
|
|
3
|
+
export interface TerminalSubagentProfile {
|
|
4
|
+
name: string;
|
|
5
|
+
color: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TerminalSubagentRun {
|
|
8
|
+
id: string;
|
|
9
|
+
parentRunId: string | null;
|
|
10
|
+
relation: 'subagent';
|
|
11
|
+
/** The short name the parent gave the child (PROP-0030). */
|
|
12
|
+
title: string;
|
|
13
|
+
goal: string | null;
|
|
14
|
+
status: RunStatus;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
endedAt: string | null;
|
|
17
|
+
profile?: TerminalSubagentProfile | null;
|
|
18
|
+
}
|
|
19
|
+
export interface TerminalSubagentActivity extends TerminalSubagentRun {
|
|
20
|
+
activity: string;
|
|
21
|
+
/** How far the child's own checklist is: counts only, never the items. */
|
|
22
|
+
checklist: {
|
|
23
|
+
done: number;
|
|
24
|
+
total: number;
|
|
25
|
+
} | null;
|
|
26
|
+
}
|
|
27
|
+
export declare const ACTIVE_SUBAGENT_STATUSES: Set<"queued" | "running" | "waiting_for_approval" | "waiting_for_input" | "interrupted" | "completed" | "failed">;
|
|
28
|
+
export declare function terminalSubagentDuration(activity: TerminalSubagentRun, now?: number): string;
|
|
29
|
+
export declare function terminalSubagentTone(status: RunStatus): 'success' | 'warning' | 'error' | 'muted';
|
|
30
|
+
/** Volatile, read-only activity projection for the active terminal parent run. */
|
|
31
|
+
export declare class SubagentMonitor {
|
|
32
|
+
private parentRunId;
|
|
33
|
+
private readonly activities;
|
|
34
|
+
begin(parentRunId: string): void;
|
|
35
|
+
observe(parentRunId: string, run: TerminalSubagentRun, event: RunEvent): void;
|
|
36
|
+
snapshot(parentRunId: string): TerminalSubagentActivity[];
|
|
37
|
+
clear(parentRunId?: string): void;
|
|
38
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export const ACTIVE_SUBAGENT_STATUSES = new Set(['queued', 'running', 'waiting_for_approval', 'waiting_for_input']);
|
|
2
|
+
export function terminalSubagentDuration(activity, now = Date.now()) {
|
|
3
|
+
const start = Date.parse(activity.startedAt);
|
|
4
|
+
const end = activity.endedAt ? Date.parse(activity.endedAt) : now;
|
|
5
|
+
if (!Number.isFinite(start) || !Number.isFinite(end))
|
|
6
|
+
return '—';
|
|
7
|
+
const seconds = Math.max(0, Math.floor((end - start) / 1000));
|
|
8
|
+
if (seconds < 60)
|
|
9
|
+
return `${seconds}s`;
|
|
10
|
+
const minutes = Math.floor(seconds / 60);
|
|
11
|
+
if (minutes < 60)
|
|
12
|
+
return `${minutes}:${String(seconds % 60).padStart(2, '0')}`;
|
|
13
|
+
return `${Math.floor(minutes / 60)}h${String(minutes % 60).padStart(2, '0')}m`;
|
|
14
|
+
}
|
|
15
|
+
export function terminalSubagentTone(status) {
|
|
16
|
+
if (status === 'completed')
|
|
17
|
+
return 'success';
|
|
18
|
+
if (status === 'failed')
|
|
19
|
+
return 'error';
|
|
20
|
+
if (ACTIVE_SUBAGENT_STATUSES.has(status))
|
|
21
|
+
return 'warning';
|
|
22
|
+
return 'muted';
|
|
23
|
+
}
|
|
24
|
+
function statusActivity(status) {
|
|
25
|
+
switch (status) {
|
|
26
|
+
case 'queued': return 'Queued';
|
|
27
|
+
case 'waiting_for_approval': return 'Waiting for approval';
|
|
28
|
+
case 'waiting_for_input': return 'Waiting for input';
|
|
29
|
+
case 'completed': return 'Completed';
|
|
30
|
+
case 'failed': return 'Failed';
|
|
31
|
+
case 'interrupted': return 'Interrupted';
|
|
32
|
+
case 'running': return 'Working';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function payloadOf(event) {
|
|
36
|
+
return event.payload && typeof event.payload === 'object' ? event.payload : {};
|
|
37
|
+
}
|
|
38
|
+
function stringOf(value) {
|
|
39
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
40
|
+
}
|
|
41
|
+
function latestTool(tools) {
|
|
42
|
+
return [...tools.values()].sort((left, right) => right.sequence - left.sequence)[0]?.text;
|
|
43
|
+
}
|
|
44
|
+
/** Volatile, read-only activity projection for the active terminal parent run. */
|
|
45
|
+
export class SubagentMonitor {
|
|
46
|
+
parentRunId = null;
|
|
47
|
+
activities = new Map();
|
|
48
|
+
begin(parentRunId) {
|
|
49
|
+
this.parentRunId = parentRunId;
|
|
50
|
+
this.activities.clear();
|
|
51
|
+
}
|
|
52
|
+
observe(parentRunId, run, event) {
|
|
53
|
+
if (this.parentRunId !== parentRunId || run.relation !== 'subagent')
|
|
54
|
+
return;
|
|
55
|
+
const row = this.activities.get(run.id) ?? {
|
|
56
|
+
id: run.id,
|
|
57
|
+
parentRunId: run.parentRunId,
|
|
58
|
+
relation: run.relation,
|
|
59
|
+
title: run.title,
|
|
60
|
+
goal: run.goal,
|
|
61
|
+
status: run.status,
|
|
62
|
+
startedAt: run.startedAt,
|
|
63
|
+
endedAt: run.endedAt,
|
|
64
|
+
activity: statusActivity(run.status),
|
|
65
|
+
checklist: null,
|
|
66
|
+
profile: run.profile ?? null,
|
|
67
|
+
tools: new Map(),
|
|
68
|
+
lastTool: null
|
|
69
|
+
};
|
|
70
|
+
row.parentRunId = run.parentRunId;
|
|
71
|
+
if (run.profile !== undefined)
|
|
72
|
+
row.profile = run.profile;
|
|
73
|
+
row.title = run.title;
|
|
74
|
+
row.goal = run.goal;
|
|
75
|
+
row.status = run.status;
|
|
76
|
+
row.startedAt = run.startedAt;
|
|
77
|
+
row.endedAt = run.endedAt;
|
|
78
|
+
const payload = payloadOf(event);
|
|
79
|
+
if (event.type === 'tool.started') {
|
|
80
|
+
const toolCallId = stringOf(payload.toolCallId);
|
|
81
|
+
const name = stringOf(payload.name) ?? 'Tool';
|
|
82
|
+
if (toolCallId)
|
|
83
|
+
row.tools.set(toolCallId, { sequence: event.sequence, text: name });
|
|
84
|
+
}
|
|
85
|
+
else if (event.type === 'tool.completed') {
|
|
86
|
+
const toolCallId = stringOf(payload.toolCallId);
|
|
87
|
+
const tool = toolCallId ? row.tools.get(toolCallId) : undefined;
|
|
88
|
+
if (tool && (!row.lastTool || tool.sequence >= row.lastTool.sequence))
|
|
89
|
+
row.lastTool = tool;
|
|
90
|
+
if (toolCallId)
|
|
91
|
+
row.tools.delete(toolCallId);
|
|
92
|
+
}
|
|
93
|
+
else if (event.type === 'message.delta') {
|
|
94
|
+
row.lastTool = null;
|
|
95
|
+
row.activity = 'Responding';
|
|
96
|
+
}
|
|
97
|
+
else if (event.type === 'checklist.updated') {
|
|
98
|
+
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
99
|
+
row.checklist = items.length ? { done: items.filter(item => item?.status === 'completed').length, total: items.length } : null;
|
|
100
|
+
}
|
|
101
|
+
if (run.status !== 'running')
|
|
102
|
+
row.tools.clear();
|
|
103
|
+
row.activity = run.status === 'running'
|
|
104
|
+
? latestTool(row.tools) ?? row.lastTool?.text ?? (event.type === 'message.delta' ? 'Responding' : 'Working')
|
|
105
|
+
: statusActivity(run.status);
|
|
106
|
+
this.activities.set(run.id, row);
|
|
107
|
+
}
|
|
108
|
+
snapshot(parentRunId) {
|
|
109
|
+
if (this.parentRunId !== parentRunId)
|
|
110
|
+
return [];
|
|
111
|
+
return [...this.activities.values()]
|
|
112
|
+
.sort((left, right) => left.startedAt.localeCompare(right.startedAt) || left.id.localeCompare(right.id))
|
|
113
|
+
.map(({ tools: _tools, lastTool: _lastTool, ...activity }) => activity);
|
|
114
|
+
}
|
|
115
|
+
clear(parentRunId) {
|
|
116
|
+
if (parentRunId && this.parentRunId !== parentRunId)
|
|
117
|
+
return;
|
|
118
|
+
this.parentRunId = null;
|
|
119
|
+
this.activities.clear();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CapabilitySet } from "../capabilities/types.js";
|
|
2
2
|
import type { Subagents } from "../sessions/subagentService.js";
|
|
3
|
+
import type { SideQuestions } from "../sessions/sideQuestions.js";
|
|
3
4
|
import { type SubagentOptions } from "../sessions/subagents.js";
|
|
4
5
|
import type { Accounts } from "../accounts/accounts.js";
|
|
5
6
|
import type { Approvals } from "../sessions/approvals.js";
|
|
@@ -18,11 +19,14 @@ import type { RunRecord } from "../sessions/contracts.js";
|
|
|
18
19
|
import type { ToolPolicy } from "../sessions/policy.js";
|
|
19
20
|
import type { RunRegistry } from "../sessions/registry.js";
|
|
20
21
|
import type { World } from "../world/world.js";
|
|
22
|
+
import type { Worktrees } from "../worktrees/service.js";
|
|
23
|
+
import type { AnnotationCapture } from "../annotations/capture.js";
|
|
24
|
+
import type { NoteDeliveries } from "../annotations/delivery.js";
|
|
21
25
|
/** The files a terminal session needs on disk, outside this process. */
|
|
22
26
|
export interface TerminalAssets {
|
|
23
27
|
/** The Node binary that runs the commands. */
|
|
24
28
|
node: string;
|
|
25
|
-
/** The built `
|
|
29
|
+
/** The built `xagent` command (`dist/cli.js`). */
|
|
26
30
|
cli: string;
|
|
27
31
|
/** The built Pi extension (`dist/terminal-pi.js`). */
|
|
28
32
|
piExtension: string;
|
|
@@ -52,6 +56,15 @@ export interface TerminalPrepareInput {
|
|
|
52
56
|
/** Fields copied onto each run this session records. */
|
|
53
57
|
run?: Partial<Pick<RunRecord, 'mode' | 'worktreeId'>>;
|
|
54
58
|
}
|
|
59
|
+
/** What a session asked to restart as: another account, or another folder (a worktree or the main checkout). */
|
|
60
|
+
export interface TerminalRelaunch {
|
|
61
|
+
accountId: string;
|
|
62
|
+
nativeSessionId: string | null;
|
|
63
|
+
/** Restart in this folder, as a new conversation there. */
|
|
64
|
+
cwd?: string;
|
|
65
|
+
/** The managed worktree the new folder belongs to; null for the main checkout. */
|
|
66
|
+
worktreeId?: string | null;
|
|
67
|
+
}
|
|
55
68
|
export interface TerminalLaunch {
|
|
56
69
|
program: string;
|
|
57
70
|
args: string[];
|
|
@@ -65,6 +78,8 @@ export interface TerminalsOptions {
|
|
|
65
78
|
/** Which plugin code in variants a person trusted. */
|
|
66
79
|
trust?: VariantTrust | null;
|
|
67
80
|
subagents?: Subagents;
|
|
81
|
+
/** Shared tool-free side-question runs. */
|
|
82
|
+
sideQuestions?: SideQuestions;
|
|
68
83
|
resolveCapabilities?: (caller: CallerContext, tools: readonly string[], cwd: string) => Promise<CapabilitySet>;
|
|
69
84
|
env: HarnessEnv;
|
|
70
85
|
stateDir: string;
|
|
@@ -82,6 +97,12 @@ export interface TerminalsOptions {
|
|
|
82
97
|
assets?: TerminalAssets;
|
|
83
98
|
/** Web, browser and computer tools, offered when their plugins are on. */
|
|
84
99
|
world?: World;
|
|
100
|
+
/** Managed worktrees: a session naming one works in it and holds it while it lives. */
|
|
101
|
+
worktrees?: Worktrees;
|
|
102
|
+
/** The visible browser a person captures page notes in. */
|
|
103
|
+
noteCapture?: AnnotationCapture;
|
|
104
|
+
/** Page notes on their way to a session (PROP-0018). */
|
|
105
|
+
pageNotes?: NoteDeliveries;
|
|
85
106
|
}
|
|
86
107
|
/** What `/plugins` in a terminal can do. Each takes the session's folder, account and project. */
|
|
87
108
|
export interface PluginControl {
|
|
@@ -98,6 +119,12 @@ export interface PluginControl {
|
|
|
98
119
|
pluginId: string;
|
|
99
120
|
settings: Record<string, unknown>;
|
|
100
121
|
}): Promise<Plugin[]>;
|
|
122
|
+
install?(params: PluginScope & {
|
|
123
|
+
source: string;
|
|
124
|
+
}): Promise<Plugin[]>;
|
|
125
|
+
remove?(params: PluginScope & {
|
|
126
|
+
pluginId: string;
|
|
127
|
+
}): Promise<Plugin[]>;
|
|
101
128
|
}
|
|
102
129
|
export declare class Terminals {
|
|
103
130
|
private readonly options;
|
|
@@ -110,8 +137,13 @@ export declare class Terminals {
|
|
|
110
137
|
private readonly worldReadOnly;
|
|
111
138
|
private readonly socket;
|
|
112
139
|
private readonly assets;
|
|
113
|
-
/** A session's `/accounts` switch, taken once its Pi process has ended. */
|
|
140
|
+
/** A session's `/accounts` or `/worktree` switch, taken once its Pi process has ended. */
|
|
114
141
|
private readonly relaunches;
|
|
142
|
+
/** The managed worktree each live session holds. */
|
|
143
|
+
private readonly worktreeLeases;
|
|
144
|
+
/** Each child's profile name and colour, looked up once per child run. */
|
|
145
|
+
private readonly childProfiles;
|
|
146
|
+
private subagentProfile;
|
|
115
147
|
constructor(options: TerminalsOptions);
|
|
116
148
|
/** The commands a terminal session finds on PATH. Rewritten only when they change. */
|
|
117
149
|
private ensureCommands;
|
|
@@ -133,11 +165,14 @@ export declare class Terminals {
|
|
|
133
165
|
* or removing an account is a person's choice, never the model's shell command.
|
|
134
166
|
*/
|
|
135
167
|
private accountsCall;
|
|
168
|
+
/** `/agents`: the agent profiles this session's folder can use (PROP-0029). */
|
|
169
|
+
private agentsCall;
|
|
170
|
+
/** `/worktree`: the managed worktrees of the repository this session works in. */
|
|
171
|
+
private worktreesCall;
|
|
172
|
+
/** `/plugin:notes` and `/plugin:annotate`: the page notes a person keeps, and the visible browser they capture them in. */
|
|
173
|
+
private annotationsCall;
|
|
136
174
|
/** The account a session asked to switch to. Taken once, after its Pi process has ended and before `dispose`. */
|
|
137
|
-
takeRelaunch(sessionId: string):
|
|
138
|
-
accountId: string;
|
|
139
|
-
nativeSessionId: string | null;
|
|
140
|
-
} | null;
|
|
175
|
+
takeRelaunch(sessionId: string): TerminalRelaunch | null;
|
|
141
176
|
private live;
|
|
142
177
|
private historyDir;
|
|
143
178
|
private runRecord;
|
|
@@ -147,6 +182,7 @@ export declare class Terminals {
|
|
|
147
182
|
private subagentsCall;
|
|
148
183
|
private componentsCall;
|
|
149
184
|
private trajectoryCall;
|
|
185
|
+
private sideQuestionCall;
|
|
150
186
|
/** The only terminal frontend: the Bun-run OpenTUI application. */
|
|
151
187
|
private frontendCommand;
|
|
152
188
|
/**
|