@xingular/harness 1.2.0 → 1.3.1-unstable.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 +63 -0
- package/README.md +26 -17
- package/builtin/skills/plugin-authoring/SKILL.md +34 -0
- package/dist/cli.js +7362 -6239
- package/dist/sdk/agents/builtin.js +3 -3
- package/dist/sdk/agents/planResearch.d.ts +23 -1
- package/dist/sdk/agents/planResearch.js +71 -8
- package/dist/sdk/agents/profiles.d.ts +16 -4
- package/dist/sdk/agents/profiles.js +75 -12
- package/dist/sdk/annotations/bridge.d.ts +2 -0
- package/dist/sdk/annotations/bridge.js +4 -0
- package/dist/sdk/annotations/capture.d.ts +44 -2
- package/dist/sdk/annotations/capture.js +104 -3
- package/dist/sdk/annotations/delivery.d.ts +24 -17
- package/dist/sdk/annotations/delivery.js +82 -49
- package/dist/sdk/annotations/inspect.js +7 -6
- package/dist/sdk/annotations/pageScript.js +4 -4
- package/dist/sdk/annotations/part.d.ts +0 -2
- package/dist/sdk/annotations/part.js +4 -33
- package/dist/sdk/annotations/queue.d.ts +2 -2
- package/dist/sdk/annotations/queue.js +12 -7
- package/dist/sdk/annotations/serverMethods.js +11 -7
- package/dist/sdk/annotations/store.d.ts +13 -21
- package/dist/sdk/annotations/store.js +47 -37
- package/dist/sdk/capabilities/runSnapshot.js +10 -4
- package/dist/sdk/commands/list.js +2 -0
- package/dist/sdk/commands/registry.d.ts +11 -3
- package/dist/sdk/commands/registry.js +3 -2
- package/dist/sdk/core/links.d.ts +51 -0
- package/dist/sdk/core/links.js +155 -0
- package/dist/sdk/core/shell.d.ts +20 -0
- package/dist/sdk/core/shell.js +73 -0
- package/dist/sdk/core/workspaceBashTool.d.ts +17 -1
- package/dist/sdk/core/workspaceBashTool.js +16 -2
- package/dist/sdk/core/workspaceSandbox.d.ts +10 -0
- package/dist/sdk/core/workspaceSandbox.js +52 -12
- package/dist/sdk/core/workspaceWriteOperations.d.ts +11 -0
- package/dist/sdk/core/workspaceWriteOperations.js +26 -8
- package/dist/sdk/doctor/checks.js +71 -12
- package/dist/sdk/harness.d.ts +5 -0
- package/dist/sdk/harness.js +11 -6
- package/dist/sdk/hub/hub.js +5 -3
- package/dist/sdk/index.d.ts +3 -2
- package/dist/sdk/index.js +2 -1
- package/dist/sdk/plugins/builtin.js +14 -9
- package/dist/sdk/plugins/builtinRegistry.d.ts +3 -1
- package/dist/sdk/plugins/builtinRegistry.js +9 -3
- package/dist/sdk/plugins/discover.d.ts +7 -1
- package/dist/sdk/plugins/discover.js +41 -2
- package/dist/sdk/plugins/identity.js +1 -1
- package/dist/sdk/plugins/install.d.ts +6 -2
- package/dist/sdk/plugins/install.js +29 -5
- package/dist/sdk/plugins/inventory.js +3 -3
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +21 -4
- package/dist/sdk/plugins/plugins.d.ts +2 -0
- package/dist/sdk/plugins/plugins.js +1 -0
- package/dist/sdk/plugins/proposals.d.ts +17 -0
- package/dist/sdk/plugins/proposals.js +147 -0
- package/dist/sdk/plugins/types.d.ts +1 -1
- package/dist/sdk/plugins/types.js +1 -1
- package/dist/sdk/prompt/compile.d.ts +13 -4
- package/dist/sdk/prompt/compile.js +46 -20
- package/dist/sdk/prompt/standard.d.ts +1 -1
- package/dist/sdk/prompt/standard.js +1 -1
- package/dist/sdk/server/methods.js +14 -2
- package/dist/sdk/sessions/compactionCheckpoint.d.ts +3 -3
- package/dist/sdk/sessions/compactionCheckpoint.js +12 -12
- package/dist/sdk/sessions/continuation.d.ts +4 -2
- package/dist/sdk/sessions/continuation.js +7 -6
- package/dist/sdk/sessions/contracts.d.ts +22 -4
- package/dist/sdk/sessions/contracts.js +12 -7
- package/dist/sdk/sessions/planMode.js +2 -2
- package/dist/sdk/sessions/runStore.js +3 -1
- package/dist/sdk/sessions/runner.js +2 -2
- package/dist/sdk/sessions/sessionState.d.ts +13 -0
- package/dist/sdk/sessions/sessionState.js +31 -0
- package/dist/sdk/sessions/sessions.d.ts +7 -0
- package/dist/sdk/sessions/sessions.js +73 -15
- package/dist/sdk/sessions/subagentService.d.ts +15 -0
- package/dist/sdk/sessions/subagentService.js +195 -55
- package/dist/sdk/sessions/subagents.d.ts +10 -1
- package/dist/sdk/sessions/subagents.js +4 -0
- package/dist/sdk/sessions/workSlice.d.ts +8 -5
- package/dist/sdk/sessions/workSlice.js +77 -45
- package/dist/sdk/terminal/controller.d.ts +20 -0
- package/dist/sdk/terminal/controller.js +43 -18
- 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 +2 -0
- package/dist/sdk/terminal/opentui/theme.js +2 -0
- package/dist/sdk/terminal/piExtension.js +98 -15
- package/dist/sdk/terminal/subagentMonitor.d.ts +17 -4
- package/dist/sdk/terminal/subagentMonitor.js +29 -8
- package/dist/sdk/terminal/terminals.d.ts +16 -0
- package/dist/sdk/terminal/terminals.js +130 -26
- 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 +5 -1
- package/dist/sdk/terminal/tui/annotateCommand.js +21 -7
- package/dist/sdk/terminal/tui/annotateSend.d.ts +30 -0
- package/dist/sdk/terminal/tui/annotateSend.js +40 -0
- 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 +1 -1
- package/dist/sdk/terminal/tui/commandPages.js +16 -11
- package/dist/sdk/terminal/tui/commands.d.ts +4 -0
- package/dist/sdk/terminal/tui/commands.js +12 -2
- package/dist/sdk/terminal/tui/dialogs.js +1 -0
- package/dist/sdk/terminal/tui/pageOverlay.d.ts +3 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +25 -7
- package/dist/sdk/terminal/tui/pages/agentsPage.js +5 -2
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +70 -16
- package/dist/sdk/terminal/tui/pages/annotatePage.js +349 -115
- 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/pluginsPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +295 -25
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +8 -5
- package/dist/sdk/terminal/tui/pages/usagePage.d.ts +4 -2
- package/dist/sdk/terminal/tui/pages/usagePage.js +39 -12
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +1 -1
- package/dist/sdk/terminal/tui/pluginCommands.js +13 -3
- package/dist/sdk/tools/defaultSkills.d.ts +4 -0
- package/dist/sdk/tools/defaultSkills.js +20 -0
- package/dist/sdk/tools/definitions.d.ts +3 -1
- package/dist/sdk/tools/definitions.js +17 -1
- package/dist/sdk/tools/dispatch.js +12 -0
- package/dist/sdk/tools/harnessTools.js +1 -1
- package/dist/sdk/tools/sessionName.d.ts +9 -0
- package/dist/sdk/tools/sessionName.js +58 -11
- package/dist/sdk/tools/spawnSubagent.js +9 -7
- package/dist/sdk/tools/todo.d.ts +15 -0
- package/dist/sdk/tools/todo.js +64 -0
- package/dist/sdk/world/world.d.ts +0 -2
- package/dist/terminal-opentui.js +3331 -1584
- package/dist/terminal-pi.js +421 -133
- package/docs/README.md +3 -0
- 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 +73 -36
- package/docs/architecture/03-variants.md +1 -3
- package/docs/architecture/04-prompt.md +24 -13
- package/docs/architecture/06-plugins-and-capabilities.md +46 -10
- package/docs/architecture/07-tools-and-gate.md +49 -8
- package/docs/architecture/09-accounts-and-connectors.md +8 -2
- package/docs/architecture/10-server-protocol.md +68 -16
- package/docs/architecture/11-terminal.md +94 -45
- package/docs/architecture/12-storage.md +1 -0
- package/docs/architecture/13-world-tools.md +8 -6
- package/docs/architecture/14-surfaces-and-parity.md +4 -1
- package/docs/architecture/README.md +20 -18
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +6 -2
- package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +32 -21
- 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/PROP-0048-simple-annotate.md +85 -0
- package/docs/architecture/proposals/README.md +11 -6
- 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/decisions/ADR-0044-annotations-pending-or-sent.md +24 -0
- package/docs/decisions/ADR-0045-default-plugin-authoring-skill.md +9 -0
- package/docs/decisions/ADR-0046-hash-bound-plugin-proposals.md +20 -0
- package/docs/decisions/ADR-0047-links-open-with-the-os-handler.md +47 -0
- package/docs/doctor.md +14 -1
- package/docs/parity.md +16 -13
- package/docs/releases/1.3.0.md +21 -0
- package/docs/releases/1.3.1-unstable.1.md +8 -0
- package/docs/releases/README.md +9 -6
- package/docs/terminal-tui.md +127 -65
- package/package.json +1 -1
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +0 -75
- package/dist/sdk/terminal/tui/pages/notesPage.js +0 -201
- package/dist/sdk/tools/checklist.d.ts +0 -9
- package/dist/sdk/tools/checklist.js +0 -55
|
@@ -17,14 +17,15 @@ import { formatBashCommandForApproval } from "../core/workspaceBashTool.js";
|
|
|
17
17
|
import { resolveWorkspaceRoot } from "../core/workspace.js";
|
|
18
18
|
import { prependSearchPath, searchPath } from "../core/path.js";
|
|
19
19
|
import { dispatch } from "../tools/dispatch.js";
|
|
20
|
+
import { defaultSkillPaths } from "../tools/defaultSkills.js";
|
|
20
21
|
import { materialise } from "../variant/materialise.js";
|
|
21
22
|
import * as componentLibrary from "../variant/components.js";
|
|
22
23
|
import * as variantStore from "../variant/store.js";
|
|
23
24
|
import { compile, copyToRun, record, render, saveForSession } from "../prompt/compile.js";
|
|
24
25
|
import { isActiveStatus } from "../sessions/contracts.js";
|
|
25
|
-
import { findRun, loadRun, saveRun, updateRun } from "../sessions/runStore.js";
|
|
26
|
+
import { findRun, loadRun, readEvents, recordedSubagents, saveRun, updateRun } from "../sessions/runStore.js";
|
|
26
27
|
import { combineWorkspaceScope, inspectRunWorkspaceWrite } from "../sessions/workspaceBoundary.js";
|
|
27
|
-
import {
|
|
28
|
+
import { TODO_WRITE } from "../tools/todo.js";
|
|
28
29
|
import { DEFAULT_TOOL_NAMES } from "../tools/definitions.js";
|
|
29
30
|
import { harnessTools } from "../tools/harnessTools.js";
|
|
30
31
|
import { HARNESS_PLUGINS } from "../plugins/builtin.js";
|
|
@@ -42,7 +43,7 @@ import { TerminalController } from "./controller.js";
|
|
|
42
43
|
import { terminalCommandFiles } from "./launch.js";
|
|
43
44
|
import { SessionSocket, socketPath } from "./socket.js";
|
|
44
45
|
import { harnessLogInfo, readHarnessLogChunk } from "./logs.js";
|
|
45
|
-
import { listTerminalTrajectoryRuns, readTerminalTrajectory } from "./trajectory.js";
|
|
46
|
+
import { listTerminalTrajectoryRuns, readTerminalTrajectory, latestTerminalTodo } from "./trajectory.js";
|
|
46
47
|
import { BrowserControl } from "./browserControl.js";
|
|
47
48
|
/** Identity of the exact resource decision that this terminal was prepared with. */
|
|
48
49
|
function resourceIdentity(loading) {
|
|
@@ -50,7 +51,7 @@ function resourceIdentity(loading) {
|
|
|
50
51
|
return 'null';
|
|
51
52
|
return JSON.stringify({
|
|
52
53
|
extensions: loading.extensionPaths, builtins: loading.builtins, skills: loading.skillPaths,
|
|
53
|
-
prompts: loading.promptPaths, themes: loading.themePaths, detached: loading.detached,
|
|
54
|
+
prompts: loading.promptPaths, themes: loading.themePaths, profiles: loading.profilePaths, detached: loading.detached,
|
|
54
55
|
project: loading.projectCode, packages: loading.packageCode
|
|
55
56
|
});
|
|
56
57
|
}
|
|
@@ -72,6 +73,8 @@ export class Terminals {
|
|
|
72
73
|
diagnosticOwners = new Map();
|
|
73
74
|
/** The variant each live session runs, for loading its skills. */
|
|
74
75
|
variantDirs = new Map();
|
|
76
|
+
/** The subagent profile files each live session's packages bring, chosen when it started. */
|
|
77
|
+
packageProfiles = new Map();
|
|
75
78
|
/** The world tools each session was given, so a call can only reach one of them. */
|
|
76
79
|
worldNames = new Map();
|
|
77
80
|
worldReadOnly = new Set();
|
|
@@ -98,12 +101,61 @@ export class Terminals {
|
|
|
98
101
|
}
|
|
99
102
|
return this.childProfiles.get(record.id) ?? null;
|
|
100
103
|
}
|
|
104
|
+
/** Account, model and effort for a monitor row, from the run record. */
|
|
105
|
+
monitorRuntime(live, record) {
|
|
106
|
+
let accountLabel = record.accountId === live.account.id ? live.account.label : null;
|
|
107
|
+
if (accountLabel === null && record.accountId) {
|
|
108
|
+
try {
|
|
109
|
+
accountLabel = this.options.accounts.find(record.accountId).label;
|
|
110
|
+
}
|
|
111
|
+
catch { }
|
|
112
|
+
}
|
|
113
|
+
return { accountLabel, model: record.model ?? null, effort: record.effort ?? null };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Shows the reopened conversation's latest parent run again: the last owner on its native branch
|
|
117
|
+
* that ran in this folder, with every descendant flat, as each ended.
|
|
118
|
+
*/
|
|
119
|
+
hydrateMonitor(live, owners) {
|
|
120
|
+
const { root } = this.options.env;
|
|
121
|
+
const native = live.controller.nativeSessionId;
|
|
122
|
+
if (!native)
|
|
123
|
+
return;
|
|
124
|
+
for (const id of [...owners].reverse()) {
|
|
125
|
+
const record = findRun(root, id);
|
|
126
|
+
if (!record || record.relation || record.nativeSessionId !== native || record.workingDirectory !== live.input.cwd)
|
|
127
|
+
continue;
|
|
128
|
+
const children = [];
|
|
129
|
+
const seen = new Set([record.id]);
|
|
130
|
+
const visit = (parentId) => {
|
|
131
|
+
for (const childId of recordedSubagents(root, parentId)) {
|
|
132
|
+
if (seen.has(childId))
|
|
133
|
+
continue;
|
|
134
|
+
seen.add(childId);
|
|
135
|
+
const child = findRun(root, childId);
|
|
136
|
+
if (child?.relation !== 'subagent')
|
|
137
|
+
continue;
|
|
138
|
+
children.push(child);
|
|
139
|
+
visit(child.id);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
visit(record.id);
|
|
143
|
+
const entry = (run, profile = null) => ({ record: run, events: readEvents(root, run.id), profile, runtime: this.monitorRuntime(live, run) });
|
|
144
|
+
live.controller.restoreMonitor(entry(record), children.map(child => entry(child, this.subagentProfile(child))));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
101
148
|
constructor(options) {
|
|
102
149
|
this.options = options;
|
|
103
150
|
this.assets = options.assets ?? defaultAssets();
|
|
104
151
|
options.runs.listen(event => {
|
|
105
|
-
this.sessions.get(event.sessionId)
|
|
152
|
+
const session = this.sessions.get(event.sessionId);
|
|
153
|
+
session?.controller.observe(event);
|
|
106
154
|
const record = findRun(options.env.root, event.runId);
|
|
155
|
+
if (record && !record.relation && session) {
|
|
156
|
+
session.controller.observeMain(record, event, this.monitorRuntime(session, record));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
107
159
|
if (record?.relation !== 'subagent')
|
|
108
160
|
return;
|
|
109
161
|
let parentId = record.parentRunId;
|
|
@@ -119,8 +171,8 @@ export class Terminals {
|
|
|
119
171
|
const settings = options.subagents?.runtimeSettings(record.id);
|
|
120
172
|
owner.controller.observeSubagent(parent.id, record, event, this.subagentProfile(record), {
|
|
121
173
|
accountLabel: owner.account.label,
|
|
122
|
-
model: settings?.model ?? null,
|
|
123
|
-
effort: settings?.effort ?? null
|
|
174
|
+
model: settings?.model ?? record.model ?? null,
|
|
175
|
+
effort: settings?.effort ?? record.effort ?? null
|
|
124
176
|
});
|
|
125
177
|
break;
|
|
126
178
|
}
|
|
@@ -309,6 +361,19 @@ export class Terminals {
|
|
|
309
361
|
return params[name];
|
|
310
362
|
};
|
|
311
363
|
switch (call) {
|
|
364
|
+
case 'plugins.proposals.list':
|
|
365
|
+
if (!api.proposals)
|
|
366
|
+
throw invalid('Proposal review is unavailable in this host.');
|
|
367
|
+
return api.proposals.list();
|
|
368
|
+
case 'plugins.proposals.preview':
|
|
369
|
+
if (!api.proposals)
|
|
370
|
+
throw invalid('Proposal review is unavailable in this host.');
|
|
371
|
+
return api.proposals.preview(text('id'));
|
|
372
|
+
case 'plugins.proposals.accept':
|
|
373
|
+
case 'plugins.proposals.reject':
|
|
374
|
+
if (!api.proposals)
|
|
375
|
+
throw invalid('Proposal review is unavailable in this host.');
|
|
376
|
+
return api.proposals[call === 'plugins.proposals.accept' ? 'accept' : 'reject'](text('id'), text('hash'));
|
|
312
377
|
case 'plugins.attach':
|
|
313
378
|
case 'plugins.detach':
|
|
314
379
|
return api.attach({ ...scope, id: text('id'), scope: params.everywhere === true || !projectId ? {} : { projectId } }, call === 'plugins.attach');
|
|
@@ -432,7 +497,7 @@ export class Terminals {
|
|
|
432
497
|
return params.scope;
|
|
433
498
|
};
|
|
434
499
|
switch (call.slice('terminal.agents.'.length)) {
|
|
435
|
-
case 'list': return { ...listProfiles(root, cwd, this.variantDirs.get(live.input.sessionId) ?? null), dirs: profileDirs(root, cwd) };
|
|
500
|
+
case 'list': return { ...listProfiles(root, cwd, this.variantDirs.get(live.input.sessionId) ?? null, this.packageProfiles.get(live.input.sessionId)), dirs: profileDirs(root, cwd) };
|
|
436
501
|
case 'create': return createProfile(root, cwd, { name: name(), scope: scope(), from: typeof params.from === 'string' && params.from ? params.from : null });
|
|
437
502
|
case 'remove':
|
|
438
503
|
removeProfile(root, cwd, { name: name(), scope: scope() });
|
|
@@ -533,6 +598,9 @@ export class Terminals {
|
|
|
533
598
|
if (call === 'terminal.usage.limits') {
|
|
534
599
|
return params.refresh === true ? limits.refresh(live.account.id, { force: true }) : limits.read(live.account.id);
|
|
535
600
|
}
|
|
601
|
+
if (call === 'terminal.usage.allLimits') {
|
|
602
|
+
return params.refresh === true ? limits.refresh() : limits.read();
|
|
603
|
+
}
|
|
536
604
|
if (call === 'terminal.usage.headers') {
|
|
537
605
|
if (typeof params.provider !== 'string' || !params.provider)
|
|
538
606
|
throw invalid('A provider is required.');
|
|
@@ -587,14 +655,14 @@ export class Terminals {
|
|
|
587
655
|
case 'read': return pageNotes.read(root, id());
|
|
588
656
|
case 'create': {
|
|
589
657
|
const note = pageNotes.create(root, { source: params.source, target: params.target, body: params.body, tags: params.tags });
|
|
590
|
-
return params.share === true ?
|
|
658
|
+
return params.share === true ? needNotes().share(note.id, undefined, sessionId) : note;
|
|
591
659
|
}
|
|
592
660
|
case 'update': return pageNotes.update(root, id(), version(), (params.patch ?? {}));
|
|
593
661
|
case 'delete':
|
|
594
662
|
pageNotes.remove(root, id(), version());
|
|
595
663
|
return {};
|
|
596
664
|
// A terminal shares with, and withdraws from, its own session only.
|
|
597
|
-
case 'share': return
|
|
665
|
+
case 'share': return needNotes().share(id(), undefined, sessionId);
|
|
598
666
|
case 'unshare': return pageNotes.unshare(root, id(), { sessionId });
|
|
599
667
|
case 'open': return needCapture().open(sessionId, typeof params.url === 'string' && params.url ? params.url : null);
|
|
600
668
|
case 'identity': return needCapture().identity(sessionId);
|
|
@@ -613,7 +681,7 @@ export class Terminals {
|
|
|
613
681
|
const note = pageNotes.read(root, id());
|
|
614
682
|
return needCapture().reveal(sessionId, note.source.url, note.target);
|
|
615
683
|
}
|
|
616
|
-
// ---- The inspector and
|
|
684
|
+
// ---- The inspector and sending (PROP-0018, PROP-0042); a terminal shares with and sends to its own session.
|
|
617
685
|
case 'target': return { ...needCapture().describe(sessionId), windows: await needCapture().windows(sessionId).catch(() => []) };
|
|
618
686
|
case 'attach': {
|
|
619
687
|
const kind = params.kind;
|
|
@@ -635,10 +703,18 @@ export class Terminals {
|
|
|
635
703
|
await needCapture().stopInspect(sessionId);
|
|
636
704
|
return {};
|
|
637
705
|
case 'next': return inspectStep(needCapture(), needNotes(), sessionId, sessionId, 'terminal');
|
|
638
|
-
case 'queue': return needNotes().queue(id(), version(), sessionId);
|
|
639
|
-
case 'dequeue': return needNotes().dequeue(id(), version());
|
|
640
706
|
case 'confirm': return needNotes().confirm(id(), version());
|
|
641
|
-
case '
|
|
707
|
+
case 'confirmAll': return { notes: needNotes().confirmAll(sessionId) };
|
|
708
|
+
case 'clear': return { notes: needNotes().clear(sessionId) };
|
|
709
|
+
case 'send': return needNotes().send(sessionId, {
|
|
710
|
+
ids: Array.isArray(params.ids) ? params.ids.filter((entry) => typeof entry === 'string') : null,
|
|
711
|
+
prompt: typeof params.prompt === 'string' ? params.prompt : null
|
|
712
|
+
});
|
|
713
|
+
case 'discover': return { apps: await needCapture().discover() };
|
|
714
|
+
case 'launch':
|
|
715
|
+
if (typeof params.path !== 'string' || !params.path.trim())
|
|
716
|
+
throw invalid('Give the path of the app to start.');
|
|
717
|
+
return needCapture().launchApp(sessionId, params.path, Array.isArray(params.args) ? params.args.filter((entry) => typeof entry === 'string') : []);
|
|
642
718
|
default:
|
|
643
719
|
throw new HarnessError('unknown_method', `Unknown call ${call}.`);
|
|
644
720
|
}
|
|
@@ -690,7 +766,7 @@ export class Terminals {
|
|
|
690
766
|
};
|
|
691
767
|
}
|
|
692
768
|
/** The run a native turn reports into: the one reserved by `send`, or a new one for what the person typed. */
|
|
693
|
-
begin(live, reservedRunId, text) {
|
|
769
|
+
begin(live, reservedRunId, text, launch = {}) {
|
|
694
770
|
const { root } = this.options.env;
|
|
695
771
|
const { sessionId } = live.input;
|
|
696
772
|
if (this.options.runs.activeForSession(sessionId))
|
|
@@ -706,6 +782,14 @@ export class Terminals {
|
|
|
706
782
|
saveRun(root, this.runRecord(live, runId, text || null));
|
|
707
783
|
copyToRun(root, live.input.sessionFolder, runId);
|
|
708
784
|
}
|
|
785
|
+
if (launch.model || launch.effort) {
|
|
786
|
+
updateRun(root, runId, (record) => {
|
|
787
|
+
if (launch.model)
|
|
788
|
+
record.model = launch.model;
|
|
789
|
+
if (launch.effort)
|
|
790
|
+
record.effort = launch.effort;
|
|
791
|
+
});
|
|
792
|
+
}
|
|
709
793
|
return this.options.runs.start(sessionId, runId);
|
|
710
794
|
}
|
|
711
795
|
async decide(live, run, tool, args) {
|
|
@@ -747,7 +831,7 @@ export class Terminals {
|
|
|
747
831
|
}
|
|
748
832
|
if (inspection.outside)
|
|
749
833
|
return { allowed: false, requiresApproval: true, rememberable: false, standing: 'switch', reason: inspection.reason, workspaceRoot };
|
|
750
|
-
if (name === SESSION_NAME || name ===
|
|
834
|
+
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)) {
|
|
751
835
|
return { allowed: true, workspaceRoot };
|
|
752
836
|
}
|
|
753
837
|
return { allowed: false, requiresApproval: true, rememberable: true, reason: 'This native action requires approval.', workspaceRoot };
|
|
@@ -829,11 +913,12 @@ export class Terminals {
|
|
|
829
913
|
}
|
|
830
914
|
if (call === 'terminal.subagents.tools') {
|
|
831
915
|
const variantDir = this.variantDirs.get(live.input.sessionId) ?? null;
|
|
916
|
+
const packaged = this.packageProfiles.get(live.input.sessionId) ?? [];
|
|
832
917
|
return {
|
|
833
918
|
tools: subagentTools(service, '').map(tool => ({ name: tool.name, label: tool.label, description: tool.description, parameters: tool.parameters(Type) })),
|
|
834
919
|
profiles: {
|
|
835
|
-
normal: profileSection(this.options.env.root, live.input.cwd, variantDir).body,
|
|
836
|
-
plan: profileSection(this.options.env.root, live.input.cwd, variantDir, true).body
|
|
920
|
+
normal: profileSection(this.options.env.root, live.input.cwd, variantDir, false, undefined, packaged).body,
|
|
921
|
+
plan: profileSection(this.options.env.root, live.input.cwd, variantDir, true, undefined, packaged).body
|
|
837
922
|
}
|
|
838
923
|
};
|
|
839
924
|
}
|
|
@@ -850,13 +935,23 @@ export class Terminals {
|
|
|
850
935
|
if (params.plan !== undefined && typeof params.plan !== 'boolean')
|
|
851
936
|
throw invalid('plan must be a boolean.');
|
|
852
937
|
const readOnly = live.input.readOnly === true;
|
|
853
|
-
const readTools = new Set([...READ_ONLY_TOOLS, SESSION_NAME,
|
|
938
|
+
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']);
|
|
854
939
|
const tools = [...new Set(params.tools)].filter(name => (!readOnly || readTools.has(name)) && (params.plan === true || (name !== 'ask_user' && name !== 'ask_user_question')));
|
|
855
940
|
const caller = { ...live.caller, runId: run.runId };
|
|
856
941
|
const capabilities = this.options.resolveCapabilities ? await this.options.resolveCapabilities(caller, tools, live.input.cwd) : { version: 1, tools, connectors: [], contributions: [] };
|
|
942
|
+
const variantDir = this.variantDirs.get(live.input.sessionId) ?? null;
|
|
943
|
+
const packaged = this.packageProfiles.get(live.input.sessionId) ?? [];
|
|
944
|
+
const subagentProfiles = {
|
|
945
|
+
normal: profileSection(this.options.env.root, live.input.cwd, variantDir, false, undefined, packaged).body,
|
|
946
|
+
plan: profileSection(this.options.env.root, live.input.cwd, variantDir, true, {
|
|
947
|
+
frozenTools: capabilities.tools,
|
|
948
|
+
contributions: capabilities.contributions,
|
|
949
|
+
descriptors: HARNESS_PLUGINS
|
|
950
|
+
}, packaged).body
|
|
951
|
+
};
|
|
857
952
|
freezeRun(this.options.env.root, run.runId, caller, capabilities);
|
|
858
953
|
if (!service || live.input.subagents === null)
|
|
859
|
-
return { runId: run.runId, connectors: capabilities.connectors };
|
|
954
|
+
return { runId: run.runId, connectors: capabilities.connectors, subagentProfiles };
|
|
860
955
|
service.register({
|
|
861
956
|
run: { id: run.runId, sessionId: live.input.sessionId, mode: live.input.run?.mode ?? 'code', projectId: live.input.projectId, worktreeId: live.input.run?.worktreeId },
|
|
862
957
|
cwd: live.input.cwd, sessionDir: this.historyDir(live.input), text: '', caller: live.caller,
|
|
@@ -866,10 +961,14 @@ export class Terminals {
|
|
|
866
961
|
connections: live.input.connections === false ? false : { readOnly }, subagents: live.input.subagents,
|
|
867
962
|
plan: params.plan === true,
|
|
868
963
|
variant: live.variant ? { dir: live.variant.dir, scope: live.variant.scope } : null,
|
|
964
|
+
packageProfiles: packaged,
|
|
869
965
|
capabilities
|
|
870
966
|
}, live.account.id, capabilities);
|
|
871
|
-
|
|
872
|
-
|
|
967
|
+
const history = params.history ?? { owners: [], receipts: [] };
|
|
968
|
+
service.restore(run.runId, history);
|
|
969
|
+
// Children an earlier reply left stopped, reported before the model answers.
|
|
970
|
+
const notice = await service.delivery(run.runId, history, false);
|
|
971
|
+
return { runId: run.runId, connectors: capabilities.connectors, subagentProfiles, notice };
|
|
873
972
|
}
|
|
874
973
|
if (call === 'terminal.subagents.verify') {
|
|
875
974
|
await service.verify(run.runId, params.history);
|
|
@@ -1028,11 +1127,12 @@ export class Terminals {
|
|
|
1028
1127
|
plugins: () => this.options.pluginControl?.list({ cwd: input.cwd, accountId: preparedAccount.id, projectId: input.projectId ?? null }) ?? Promise.resolve([]) });
|
|
1029
1128
|
if (variant)
|
|
1030
1129
|
this.variantDirs.set(sessionId, variant.dir);
|
|
1130
|
+
this.packageProfiles.set(sessionId, loading?.profilePaths ?? []);
|
|
1031
1131
|
const worldTools = await this.worldToolNames(input, preparedAccount);
|
|
1032
1132
|
const ownTools = await this.ownToolNames(input, preparedAccount);
|
|
1033
|
-
const launchTools = [...new Set([...(input.launch?.tools ?? []),
|
|
1133
|
+
const launchTools = [...new Set([...(input.launch?.tools ?? []), TODO_WRITE, SESSION_NAME, ...ownTools, ...(broker ? apps.toolNames : []), ...worldTools])];
|
|
1034
1134
|
// The profiles replace the variant's own subagent list, which they include.
|
|
1035
|
-
const profiles = this.options.subagents && input.subagents !== null ? profileSection(root, input.cwd, variant?.dir ?? null) : null;
|
|
1135
|
+
const profiles = this.options.subagents && input.subagents !== null ? profileSection(root, input.cwd, variant?.dir ?? null, false, undefined, loading?.profilePaths ?? []) : null;
|
|
1036
1136
|
const launchSections = profiles ? [...(input.launch?.sections ?? []).filter((section) => section.id !== profiles.id), profiles] : input.launch?.sections;
|
|
1037
1137
|
// A terminal is a person's own session: the same standard sections a host's session has.
|
|
1038
1138
|
const standard = input.launch?.standard === null ? null : input.launch?.standard ?? { cwd: input.cwd };
|
|
@@ -1050,7 +1150,8 @@ export class Terminals {
|
|
|
1050
1150
|
controller: new TerminalController({
|
|
1051
1151
|
sessionId,
|
|
1052
1152
|
nativeSessionId: input.nativeSessionId ?? null,
|
|
1053
|
-
begin: ({ reservedRunId, text }) => this.begin(live, reservedRunId, text),
|
|
1153
|
+
begin: ({ reservedRunId, text, model, effort }) => this.begin(live, reservedRunId, text, { model, effort }),
|
|
1154
|
+
hydrate: (owners) => this.hydrateMonitor(live, owners),
|
|
1054
1155
|
policy: (run, tool, args) => this.decide(live, run, tool, args),
|
|
1055
1156
|
workspaceRoot: run => {
|
|
1056
1157
|
const record = loadRun(this.options.env.root, run.runId);
|
|
@@ -1168,6 +1269,8 @@ export class Terminals {
|
|
|
1168
1269
|
return this.componentsCall(live, call, params);
|
|
1169
1270
|
if (call.startsWith('terminal.trajectory.'))
|
|
1170
1271
|
return this.trajectoryCall(live, call, params);
|
|
1272
|
+
if (call === 'terminal.todo')
|
|
1273
|
+
return latestTerminalTodo(this.options.env.root, live.input.sessionId, live.controller.nativeSessionId);
|
|
1171
1274
|
if (call === 'terminal.permissions.get') {
|
|
1172
1275
|
const policy = this.options.approvals.read();
|
|
1173
1276
|
return { full: this.options.approvals.isAuto('pi'), globalTools: policy.globalTools.pi ?? [] };
|
|
@@ -1208,7 +1311,7 @@ export class Terminals {
|
|
|
1208
1311
|
// The pi command's own extensions are not loaded: a host's session cannot have them (PROP-0039).
|
|
1209
1312
|
for (const path of [...(loading?.extensionPaths ?? []), ...(variant?.extensionPaths ?? [])])
|
|
1210
1313
|
args.push('--extension', path);
|
|
1211
|
-
for (const path of [...(variant?.skillPaths ?? []), ...(loading?.skillPaths ?? [])])
|
|
1314
|
+
for (const path of [...(variant?.skillPaths ?? []), ...defaultSkillPaths(variant?.dir), ...(loading?.skillPaths ?? [])])
|
|
1212
1315
|
args.push('--skill', path);
|
|
1213
1316
|
for (const path of loading?.promptPaths ?? [])
|
|
1214
1317
|
args.push('--prompt-template', path);
|
|
@@ -1239,6 +1342,7 @@ export class Terminals {
|
|
|
1239
1342
|
this.socket.revoke(token);
|
|
1240
1343
|
this.sessions.delete(sessionId);
|
|
1241
1344
|
this.variantDirs.delete(sessionId);
|
|
1345
|
+
this.packageProfiles.delete(sessionId);
|
|
1242
1346
|
this.worldNames.delete(sessionId);
|
|
1243
1347
|
if (this.worktreeLeases.get(sessionId) === acquiredWorktreeId)
|
|
1244
1348
|
this.worktreeLeases.delete(sessionId);
|
|
@@ -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,7 @@
|
|
|
1
1
|
import type { AnnotateStart } from "./pages/annotatePage.js";
|
|
2
|
-
/**
|
|
2
|
+
/** A typed page address: a bare host gets https, localhost gets http. */
|
|
3
|
+
export declare function normalizeAddress(text: string): string;
|
|
4
|
+
/** A typed DevTools port or loopback address. */
|
|
5
|
+
export declare function normalizeDevTools(text: string): string;
|
|
6
|
+
/** `/annotate:` accepts a page address, an app's DevTools port, or the page bridge. */
|
|
3
7
|
export declare function parseAnnotate(argument: string): AnnotateStart | string;
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** A typed page address: a bare host gets https, localhost gets http. */
|
|
2
|
+
export function normalizeAddress(text) {
|
|
3
|
+
const word = text.trim();
|
|
4
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(word))
|
|
5
|
+
return word;
|
|
6
|
+
return /^(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/.test(word) ? `http://${word}` : `https://${word}`;
|
|
7
|
+
}
|
|
8
|
+
/** A typed DevTools port or loopback address. */
|
|
9
|
+
export function normalizeDevTools(text) {
|
|
10
|
+
const word = text.trim();
|
|
11
|
+
if (/^\d+$/.test(word))
|
|
12
|
+
return `http://127.0.0.1:${word}`;
|
|
13
|
+
if (/^(localhost|127\.0\.0\.1|\[::1\]):\d+$/.test(word))
|
|
14
|
+
return `http://${word}`;
|
|
15
|
+
return word;
|
|
16
|
+
}
|
|
17
|
+
/** `/annotate:` accepts a page address, an app's DevTools port, or the page bridge. */
|
|
2
18
|
export function parseAnnotate(argument) {
|
|
3
19
|
const words = argument.trim().split(/\s+/).filter(Boolean);
|
|
4
20
|
if (!words.length)
|
|
@@ -6,17 +22,15 @@ export function parseAnnotate(argument) {
|
|
|
6
22
|
if (words[0] === '--app') {
|
|
7
23
|
const endpoint = words[1];
|
|
8
24
|
if (!endpoint)
|
|
9
|
-
return
|
|
10
|
-
const address = /^\d+$/.test(endpoint) ? `http://127.0.0.1:${endpoint}` : endpoint;
|
|
25
|
+
return { kind: 'apps' };
|
|
11
26
|
const name = words.slice(2).join(' ');
|
|
12
|
-
return { kind: 'cdp', endpoint:
|
|
27
|
+
return { kind: 'cdp', endpoint: normalizeDevTools(endpoint), ...(name ? { name } : {}) };
|
|
13
28
|
}
|
|
14
29
|
if (words[0] === '--bridge') {
|
|
15
30
|
const name = words.slice(1).join(' ');
|
|
16
31
|
return { kind: 'bridge', ...(name ? { name } : {}) };
|
|
17
32
|
}
|
|
18
33
|
if (words.length > 1 || words[0].startsWith('--'))
|
|
19
|
-
return 'Use /annotate:
|
|
20
|
-
|
|
21
|
-
return { kind: 'web', url };
|
|
34
|
+
return 'Use /annotate: [url], /annotate: --app [port], or /annotate: --bridge.';
|
|
35
|
+
return { kind: 'web', url: normalizeAddress(words[0]) };
|
|
22
36
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MenuCall } from "../pluginMenu.js";
|
|
2
|
+
import type { Note } from "../../annotations/store.js";
|
|
3
|
+
type SendResult = {
|
|
4
|
+
state: 'sent';
|
|
5
|
+
ids: string[];
|
|
6
|
+
} | {
|
|
7
|
+
state: 'busy';
|
|
8
|
+
ids: string[];
|
|
9
|
+
} | {
|
|
10
|
+
state: 'host';
|
|
11
|
+
ids: string[];
|
|
12
|
+
} | {
|
|
13
|
+
state: 'empty';
|
|
14
|
+
ids: [];
|
|
15
|
+
unconfirmed?: number;
|
|
16
|
+
};
|
|
17
|
+
export interface AnnotateSendUi {
|
|
18
|
+
confirm(title: string, message: string): Promise<boolean>;
|
|
19
|
+
}
|
|
20
|
+
/** Unsent annotations of `sessionId`, oldest first. */
|
|
21
|
+
export declare function unsent(notes: readonly Note[], sessionId: string | null): Note[];
|
|
22
|
+
/**
|
|
23
|
+
* Sends every unsent annotation of this terminal's session, with `prompt` after them when given.
|
|
24
|
+
* Returns what to tell the person.
|
|
25
|
+
*/
|
|
26
|
+
export declare function sendUnsent(call: MenuCall, ui: AnnotateSendUi, options?: {
|
|
27
|
+
prompt?: string;
|
|
28
|
+
}): Promise<string>;
|
|
29
|
+
export declare function describeSend(result: SendResult, withPrompt?: boolean): string;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const count = (n) => `${n} annotation${n === 1 ? '' : 's'}`;
|
|
2
|
+
/** Unsent annotations of `sessionId`, oldest first. */
|
|
3
|
+
export function unsent(notes, sessionId) {
|
|
4
|
+
return notes
|
|
5
|
+
.filter((note) => note.status === 'open' && note.delivery?.state === 'pending' && note.delivery.sessionId === sessionId)
|
|
6
|
+
.sort((left, right) => left.delivery.queuedAt.localeCompare(right.delivery.queuedAt));
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Sends every unsent annotation of this terminal's session, with `prompt` after them when given.
|
|
10
|
+
* Returns what to tell the person.
|
|
11
|
+
*/
|
|
12
|
+
export async function sendUnsent(call, ui, options = {}) {
|
|
13
|
+
const listed = await call('terminal.annotations.list');
|
|
14
|
+
const waiting = unsent(listed.notes, listed.sessionId);
|
|
15
|
+
if (!waiting.length) {
|
|
16
|
+
return options.prompt
|
|
17
|
+
? 'No unsent annotations. Annotate something with /annotate: first, or send your request as a normal message.'
|
|
18
|
+
: 'Nothing to send. Annotate something first.';
|
|
19
|
+
}
|
|
20
|
+
const unconfirmed = waiting.filter((note) => note.delivery?.confirm);
|
|
21
|
+
if (unconfirmed.length) {
|
|
22
|
+
const yes = await ui.confirm(`Send ${count(waiting.length)} to the agent?`, `${count(unconfirmed.length)} came from the app's own page scripts, which could have written them. Check that the comments are yours.`);
|
|
23
|
+
if (!yes)
|
|
24
|
+
return 'Nothing was sent.';
|
|
25
|
+
await call('terminal.annotations.confirmAll');
|
|
26
|
+
}
|
|
27
|
+
const result = await call('terminal.annotations.send', options.prompt ? { prompt: options.prompt } : {});
|
|
28
|
+
return describeSend(result, !!options.prompt);
|
|
29
|
+
}
|
|
30
|
+
export function describeSend(result, withPrompt = false) {
|
|
31
|
+
if (result.state === 'sent')
|
|
32
|
+
return `Sent ${count(result.ids.length)} to the agent.`;
|
|
33
|
+
if (result.state === 'busy' && withPrompt)
|
|
34
|
+
return 'The agent is still working. Submit again when it finishes; your annotations stay unsent.';
|
|
35
|
+
if (result.state === 'busy')
|
|
36
|
+
return `The agent is working; ${count(result.ids.length)} go${result.ids.length === 1 ? 'es' : ''} out when it finishes.`;
|
|
37
|
+
if (result.state === 'host')
|
|
38
|
+
return `${count(result.ids.length)} handed to the app to send.`;
|
|
39
|
+
return result.unconfirmed ? `${count(result.unconfirmed)} still wait for your confirmation.` : 'Nothing to send.';
|
|
40
|
+
}
|
|
@@ -62,3 +62,7 @@ export declare function pageFrame({ title, meta, body, footer, width, rows, them
|
|
|
62
62
|
rows: number;
|
|
63
63
|
headerLine?: string;
|
|
64
64
|
}): string[];
|
|
65
|
+
/** A full-width two-pane list uses a 40/60 split once there is room to read both panes. */
|
|
66
|
+
export declare function inspectorListWidth(width: number): number | undefined;
|
|
67
|
+
/** Joins a list and passive detail pane without drawing a box around either. */
|
|
68
|
+
export declare function splitColumns(left: readonly string[], right: readonly string[], leftWidth: number, width: number, rows: number, theme: Theme): string[];
|
|
@@ -167,3 +167,16 @@ export function pageFrame({ title, meta, body, footer, width, rows, theme, heade
|
|
|
167
167
|
footerRow(footer, width, theme)
|
|
168
168
|
].slice(0, rows);
|
|
169
169
|
}
|
|
170
|
+
/** A full-width two-pane list uses a 40/60 split once there is room to read both panes. */
|
|
171
|
+
export function inspectorListWidth(width) {
|
|
172
|
+
return width < 88 ? undefined : Math.max(32, Math.floor((width - 3) * 0.4));
|
|
173
|
+
}
|
|
174
|
+
/** Joins a list and passive detail pane without drawing a box around either. */
|
|
175
|
+
export function splitColumns(left, right, leftWidth, width, rows, theme) {
|
|
176
|
+
const rightWidth = Math.max(0, width - leftWidth - 3);
|
|
177
|
+
return Array.from({ length: Math.max(0, Math.trunc(rows)) }, (_, index) => {
|
|
178
|
+
const leftLine = truncateToWidth(left[index] ?? '', leftWidth, '');
|
|
179
|
+
const rightLine = truncateToWidth(right[index] ?? '', rightWidth, '');
|
|
180
|
+
return leftLine + ' '.repeat(Math.max(0, leftWidth - visibleWidth(leftLine))) + theme.fg('dim', ' │ ') + rightLine;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
@@ -7,7 +7,7 @@ export declare function createCommandPage(host: TerminalCommandHost, id: Command
|
|
|
7
7
|
runId?: string;
|
|
8
8
|
url?: string;
|
|
9
9
|
annotate?: AnnotateStart;
|
|
10
|
-
usageTab?: 'analysis' | 'limit';
|
|
10
|
+
usageTab?: 'analysis' | 'limit' | 'accounts';
|
|
11
11
|
usagePeriod?: 'today' | '7d' | '30d' | 'all';
|
|
12
12
|
close?(): void;
|
|
13
13
|
}): TerminalPage;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { DoctorPage } from "./pages/doctorPage.js";
|
|
2
2
|
import { AccountsPage } from "./pages/accountsPage.js";
|
|
3
3
|
import { BrowserPage } from "./pages/browserPage.js";
|
|
4
|
+
import { PluginsPage } from "./pages/pluginsPage.js";
|
|
5
|
+
import { HubPage } from "./pages/hubPage.js";
|
|
4
6
|
import { LogPage, formatRunEventLog } from "./pages/logPage.js";
|
|
5
7
|
import { PermissionsPage } from "./pages/permissionsPage.js";
|
|
6
8
|
import { SettingsPage } from "./pages/settingsPage.js";
|
|
7
9
|
import { TrajectoryPage } from "./pages/trajectoryPage.js";
|
|
8
10
|
import { WorktreesPage } from "./pages/worktreesPage.js";
|
|
9
|
-
import { NotesPage } from "./pages/notesPage.js";
|
|
10
11
|
import { AnnotatePage } from "./pages/annotatePage.js";
|
|
11
12
|
import { SubagentsPage } from "./pages/subagentsPage.js";
|
|
12
13
|
import { AgentsPage } from "./pages/agentsPage.js";
|
|
@@ -18,7 +19,7 @@ export function createCommandPage(host, id, options = {}) {
|
|
|
18
19
|
const notify = (message, type) => host.ui.notify(message, type === 'error' || type === 'warning' ? type : 'info');
|
|
19
20
|
switch (id) {
|
|
20
21
|
case 'usage':
|
|
21
|
-
return new UsagePage({ theme, call: host.control, changed: host.changed, initialTab: options.usageTab === 'limit' ? 'limits' : 'analysis', ...(options.usagePeriod ? { initialPeriod: options.usagePeriod } : {}) });
|
|
22
|
+
return new UsagePage({ theme, call: host.control, changed: host.changed, initialTab: options.usageTab === 'limit' ? 'limits' : options.usageTab === 'accounts' ? 'accounts' : 'analysis', ...(options.usagePeriod ? { initialPeriod: options.usagePeriod } : {}) });
|
|
22
23
|
case 'doctor':
|
|
23
24
|
return new DoctorPage({ theme, call: host.control, confirm: host.ui.confirm.bind(host.ui), select: host.ui.select.bind(host.ui), changed: host.changed,
|
|
24
25
|
selection: () => {
|
|
@@ -40,17 +41,25 @@ export function createCommandPage(host, id, options = {}) {
|
|
|
40
41
|
activeTools: () => host.session().getActiveToolNames(),
|
|
41
42
|
confirm: (title, message) => host.ui.confirm(title, message), notify, changed: host.changed
|
|
42
43
|
});
|
|
44
|
+
case 'plugins':
|
|
45
|
+
return new PluginsPage({
|
|
46
|
+
theme, call: host.control, changed: host.changed,
|
|
47
|
+
ui: { select: host.ui.select.bind(host.ui), confirm: host.ui.confirm.bind(host.ui), notify, input: host.ui.input.bind(host.ui) }
|
|
48
|
+
});
|
|
49
|
+
case 'hub':
|
|
50
|
+
return new HubPage({
|
|
51
|
+
theme, call: host.control, changed: host.changed,
|
|
52
|
+
ui: { select: host.ui.select.bind(host.ui), confirm: host.ui.confirm.bind(host.ui), notify }
|
|
53
|
+
});
|
|
43
54
|
case 'harness':
|
|
44
55
|
case 'skills':
|
|
45
56
|
case 'connections':
|
|
46
57
|
case 'tools':
|
|
47
|
-
case 'plugins':
|
|
48
|
-
case 'hub':
|
|
49
58
|
case 'components':
|
|
50
59
|
return new BrowserPage({
|
|
51
60
|
theme, call: host.control, changed: host.changed,
|
|
52
|
-
section: id === 'harness'
|
|
53
|
-
source:
|
|
61
|
+
section: id === 'harness' ? 'overview' : id === 'components' ? 'library' : id,
|
|
62
|
+
source: 'installed',
|
|
54
63
|
ui: {
|
|
55
64
|
select: host.ui.select.bind(host.ui), confirm: host.ui.confirm.bind(host.ui), notify: host.ui.notify.bind(host.ui), input: host.ui.input.bind(host.ui),
|
|
56
65
|
secretInput: (title, placeholder) => {
|
|
@@ -75,13 +84,9 @@ export function createCommandPage(host, id, options = {}) {
|
|
|
75
84
|
ui: { select: host.ui.select.bind(host.ui), confirm: host.ui.confirm.bind(host.ui), notify: host.ui.notify.bind(host.ui), input: host.ui.input.bind(host.ui) }
|
|
76
85
|
});
|
|
77
86
|
case 'notes':
|
|
78
|
-
return new NotesPage({
|
|
79
|
-
theme, call: host.control, changed: host.changed, ...(options.url !== undefined ? { start: options.url ? { url: options.url } : {} } : {}),
|
|
80
|
-
ui: { select: host.ui.select.bind(host.ui), confirm: host.ui.confirm.bind(host.ui), notify: host.ui.notify.bind(host.ui), input: host.ui.input.bind(host.ui) }
|
|
81
|
-
});
|
|
82
87
|
case 'annotate':
|
|
83
88
|
return new AnnotatePage({
|
|
84
|
-
theme, call: host.control, changed: host.changed, start: options.annotate ?? { kind: 'web' },
|
|
89
|
+
theme, call: host.control, changed: host.changed, start: options.annotate ?? { kind: 'web' }, initialTab: id === 'notes' ? 'all' : 'annotate',
|
|
85
90
|
ui: { select: host.ui.select.bind(host.ui), confirm: host.ui.confirm.bind(host.ui), notify: host.ui.notify.bind(host.ui), input: host.ui.input.bind(host.ui) }
|
|
86
91
|
});
|
|
87
92
|
case 'settings':
|
|
@@ -2,6 +2,7 @@ import { type AgentSession, type ExtensionUIContext } from '@earendil-works/pi-c
|
|
|
2
2
|
import { type SkillEntry } from "./skillInvocation.js";
|
|
3
3
|
import type { TerminalRuntime } from "./runtime.js";
|
|
4
4
|
import type { TerminalTrajectoryRun, TerminalTrajectorySnapshot } from "../trajectory.js";
|
|
5
|
+
import type { TodoEntry } from "../opentui/model.js";
|
|
5
6
|
import type { TerminalSubagentActivity } from "../subagentMonitor.js";
|
|
6
7
|
import { type TerminalCommandCompletion, type TerminalCommandHelpGroup } from "../commandHelp.js";
|
|
7
8
|
export type { TerminalCommandCompletion } from "../commandHelp.js";
|
|
@@ -20,6 +21,9 @@ export interface TerminalCommandHost {
|
|
|
20
21
|
refresh(): void;
|
|
21
22
|
changed(): void;
|
|
22
23
|
showHelp(groups: TerminalCommandHelpGroup[]): void;
|
|
24
|
+
/** The latest todo list of this reply, or an empty list. */
|
|
25
|
+
todo(): Promise<readonly TodoEntry[]>;
|
|
26
|
+
showTodo(items: readonly TodoEntry[]): void;
|
|
23
27
|
plan(brief: string): void;
|
|
24
28
|
/** Asks a side question in its own tool-free session; empty text dismisses the one showing. */
|
|
25
29
|
btw(question: string): Promise<void>;
|