@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
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { DoctorPage } from "./pages/doctorPage.js";
|
|
2
2
|
import { AccountsPage } from "./pages/accountsPage.js";
|
|
3
|
-
import {
|
|
4
|
-
import { HubPage } from "./pages/hubPage.js";
|
|
3
|
+
import { BrowserPage } from "./pages/browserPage.js";
|
|
5
4
|
import { LogPage, formatRunEventLog } from "./pages/logPage.js";
|
|
6
5
|
import { PermissionsPage } from "./pages/permissionsPage.js";
|
|
7
|
-
import { PluginsPage } from "./pages/pluginsPage.js";
|
|
8
6
|
import { SettingsPage } from "./pages/settingsPage.js";
|
|
9
7
|
import { TrajectoryPage } from "./pages/trajectoryPage.js";
|
|
8
|
+
import { WorktreesPage } from "./pages/worktreesPage.js";
|
|
9
|
+
import { NotesPage } from "./pages/notesPage.js";
|
|
10
|
+
import { AnnotatePage } from "./pages/annotatePage.js";
|
|
11
|
+
import { SubagentsPage } from "./pages/subagentsPage.js";
|
|
12
|
+
import { AgentsPage } from "./pages/agentsPage.js";
|
|
10
13
|
import { createSettingsModel } from "./settingsModel.js";
|
|
11
14
|
/** Construct one independent full-screen page for a slash command. */
|
|
12
15
|
export function createCommandPage(host, id, options = {}) {
|
|
@@ -34,12 +37,50 @@ export function createCommandPage(host, id, options = {}) {
|
|
|
34
37
|
activeTools: () => host.session().getActiveToolNames(),
|
|
35
38
|
confirm: (title, message) => host.ui.confirm(title, message), notify, changed: host.changed
|
|
36
39
|
});
|
|
40
|
+
case 'harness':
|
|
41
|
+
case 'skills':
|
|
42
|
+
case 'connections':
|
|
43
|
+
case 'tools':
|
|
37
44
|
case 'plugins':
|
|
38
|
-
return new PluginsPage({ theme, call: host.control, ui: host.ui, changed: host.changed });
|
|
39
45
|
case 'hub':
|
|
40
|
-
return new HubPage({ theme, call: host.control, ui: host.ui, changed: host.changed });
|
|
41
46
|
case 'components':
|
|
42
|
-
return new
|
|
47
|
+
return new BrowserPage({
|
|
48
|
+
theme, call: host.control, changed: host.changed,
|
|
49
|
+
section: id === 'harness' || id === 'hub' ? 'overview' : id === 'components' ? 'library' : id,
|
|
50
|
+
source: id === 'hub' ? 'discover' : 'installed',
|
|
51
|
+
ui: {
|
|
52
|
+
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),
|
|
53
|
+
secretInput: (title, placeholder) => {
|
|
54
|
+
const ui = host.ui;
|
|
55
|
+
if (!ui.secretInput)
|
|
56
|
+
throw new Error('Masked credential entry is unavailable in this host.');
|
|
57
|
+
return ui.secretInput(title, placeholder);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
runtime: () => ({ skills: host.session().resourceLoader.getSkills().skills,
|
|
61
|
+
tools: host.session().getAllTools(), activeTools: host.session().getActiveToolNames(), skillCommands: host.session().settingsManager.getEnableSkillCommands() }),
|
|
62
|
+
permissions: () => host.runtime().permissions(), setGlobalTool: (tool, allowed) => host.runtime().setGlobalTool(tool, allowed),
|
|
63
|
+
useSkill: name => {
|
|
64
|
+
const draft = host.ui.getEditorText();
|
|
65
|
+
host.setEditorText(`$${name}${draft ? ' ' + draft : ' '}`);
|
|
66
|
+
options.close?.();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
case 'worktree':
|
|
70
|
+
return new WorktreesPage({
|
|
71
|
+
theme, call: host.control, changed: host.changed, relaunch: host.shutdown,
|
|
72
|
+
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) }
|
|
73
|
+
});
|
|
74
|
+
case 'notes':
|
|
75
|
+
return new NotesPage({
|
|
76
|
+
theme, call: host.control, changed: host.changed, ...(options.url !== undefined ? { start: options.url ? { url: options.url } : {} } : {}),
|
|
77
|
+
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) }
|
|
78
|
+
});
|
|
79
|
+
case 'annotate':
|
|
80
|
+
return new AnnotatePage({
|
|
81
|
+
theme, call: host.control, changed: host.changed, start: options.annotate ?? { kind: 'web' },
|
|
82
|
+
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) }
|
|
83
|
+
});
|
|
43
84
|
case 'settings':
|
|
44
85
|
return new SettingsPage({ theme, ...createSettingsModel(host), notify, changed: host.changed });
|
|
45
86
|
case 'trajectory':
|
|
@@ -47,6 +88,13 @@ export function createCommandPage(host, id, options = {}) {
|
|
|
47
88
|
theme, runs: host.trajectory.list, read: host.trajectory.read, copy: host.copyText,
|
|
48
89
|
initialRunId: options.runId || undefined, notify, changed: host.changed
|
|
49
90
|
});
|
|
91
|
+
case 'subagents':
|
|
92
|
+
return new SubagentsPage({ theme, snapshot: host.subagents, changed: host.changed });
|
|
93
|
+
case 'agents':
|
|
94
|
+
return new AgentsPage({
|
|
95
|
+
theme, call: host.control, changed: host.changed, ...(host.editFile ? { edit: host.editFile.bind(host) } : {}),
|
|
96
|
+
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) }
|
|
97
|
+
});
|
|
50
98
|
case 'log':
|
|
51
99
|
return new LogPage({
|
|
52
100
|
theme, changed: host.changed,
|
|
@@ -2,6 +2,10 @@ 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 { TerminalSubagentActivity } from "../subagentMonitor.js";
|
|
6
|
+
import { type TerminalCommandCompletion, type TerminalCommandHelpGroup } from "../commandHelp.js";
|
|
7
|
+
export type { TerminalCommandCompletion } from "../commandHelp.js";
|
|
8
|
+
export { parseAnnotate } from "./pluginCommands.js";
|
|
5
9
|
export interface TerminalCommandHost {
|
|
6
10
|
session(): AgentSession;
|
|
7
11
|
/** Navigation is scope checked by the Harness runtime adapter. */
|
|
@@ -14,6 +18,7 @@ export interface TerminalCommandHost {
|
|
|
14
18
|
sessionDirectory(): string;
|
|
15
19
|
refresh(): void;
|
|
16
20
|
changed(): void;
|
|
21
|
+
showHelp(groups: TerminalCommandHelpGroup[]): void;
|
|
17
22
|
plan(brief: string): void;
|
|
18
23
|
/** Asks a side question in its own tool-free session; empty text dismisses the one showing. */
|
|
19
24
|
btw(question: string): Promise<void>;
|
|
@@ -21,19 +26,18 @@ export interface TerminalCommandHost {
|
|
|
21
26
|
setEditorText(text: string): void;
|
|
22
27
|
copyText(text: string): Promise<void>;
|
|
23
28
|
suspend?(): Promise<void>;
|
|
29
|
+
/** Opens a file in the person's editor and resolves once the editor closes. */
|
|
30
|
+
editFile?(path: string): Promise<void>;
|
|
24
31
|
hotkeys?(): string;
|
|
25
32
|
logs: {
|
|
26
33
|
read(): Promise<string>;
|
|
27
34
|
};
|
|
35
|
+
subagents(): readonly TerminalSubagentActivity[];
|
|
28
36
|
trajectory: {
|
|
29
37
|
list(): Promise<TerminalTrajectoryRun[]>;
|
|
30
38
|
read(runId: string, afterSequence?: number): Promise<TerminalTrajectorySnapshot>;
|
|
31
39
|
};
|
|
32
40
|
}
|
|
33
|
-
export interface TerminalCommandCompletion {
|
|
34
|
-
name: string;
|
|
35
|
-
description?: string;
|
|
36
|
-
}
|
|
37
41
|
export declare function createTerminalCommands(host: TerminalCommandHost): {
|
|
38
42
|
execute: (text: string) => Promise<boolean>;
|
|
39
43
|
completions: () => TerminalCommandCompletion[];
|
|
@@ -1,41 +1,49 @@
|
|
|
1
1
|
import { isAbsolute, relative, resolve } from 'node:path';
|
|
2
2
|
import { SessionManager } from '@earendil-works/pi-coding-agent';
|
|
3
|
+
import { BUILTIN_PLUGIN_REGISTRY } from "../../plugins/builtin.js";
|
|
3
4
|
import { PageOverlay } from "./pageOverlay.js";
|
|
4
5
|
import { ModelPicker } from "./modelPicker.js";
|
|
5
6
|
import { TreePicker } from "./treePicker.js";
|
|
6
7
|
import { createCommandPage } from "./commandPages.js";
|
|
7
8
|
import { pageOverlayOptions } from "./dialogGeometry.js";
|
|
8
9
|
import { expandSkillInvocation } from "./skillInvocation.js";
|
|
10
|
+
import { groupTerminalCommands } from "../commandHelp.js";
|
|
11
|
+
import { createBuiltinPluginCommandHandlers } from "./pluginCommands.js";
|
|
12
|
+
export { parseAnnotate } from "./pluginCommands.js";
|
|
9
13
|
const BUILTINS = [
|
|
10
|
-
{ name: '
|
|
11
|
-
{ name: '
|
|
12
|
-
{ name: '
|
|
13
|
-
{ name: '
|
|
14
|
-
{ name: '
|
|
15
|
-
{ name: '
|
|
16
|
-
{ name: '
|
|
17
|
-
{ name: '
|
|
18
|
-
{ name: '
|
|
19
|
-
{ name: '
|
|
20
|
-
{ name: '
|
|
21
|
-
{ name: '
|
|
22
|
-
{ name: '
|
|
23
|
-
{ name: '
|
|
24
|
-
{ name: '
|
|
25
|
-
{ name: '
|
|
26
|
-
{ name: '
|
|
27
|
-
{ name: '
|
|
28
|
-
{ name: '
|
|
29
|
-
{ name: '
|
|
30
|
-
{ name: '
|
|
31
|
-
{ name: '
|
|
32
|
-
{ name: '
|
|
33
|
-
{ name: '
|
|
34
|
-
{ name: '
|
|
35
|
-
{ name: '
|
|
36
|
-
{ name: '
|
|
37
|
-
{ name: '
|
|
14
|
+
{ name: 'help', description: 'List the commands', group: 'Core' },
|
|
15
|
+
{ name: 'plan', description: 'Plan one request, read-only: /plan <brief>', group: 'Run' },
|
|
16
|
+
{ name: 'btw', aliases: ['side'], description: 'Ask a side question without interrupting: /btw <question>', group: 'Run' },
|
|
17
|
+
{ name: 'new', description: 'Start a new session', group: 'Session' },
|
|
18
|
+
{ name: 'stop', description: 'Stop the current reply', group: 'Run' },
|
|
19
|
+
{ name: 'session', aliases: ['resume'], description: 'Choose a saved session in this account and folder', group: 'Session' },
|
|
20
|
+
{ name: 'history', aliases: ['tree'], description: 'Navigate branches in the current session', group: 'Session' },
|
|
21
|
+
{ name: 'fork', description: 'Fork from an earlier message', group: 'Session' },
|
|
22
|
+
{ name: 'clone', description: 'Clone this branch into a new session', group: 'Session' },
|
|
23
|
+
{ name: 'model', description: 'Choose the model', group: 'Model' },
|
|
24
|
+
{ name: 'effort', description: 'Choose the thinking effort', group: 'Model' },
|
|
25
|
+
{ name: 'thinking', description: 'Toggle transcript thinking visibility: /thinking [show|hide]', group: 'Model' },
|
|
26
|
+
{ name: 'name', description: 'Name this session', group: 'Session' },
|
|
27
|
+
{ name: 'compact', description: 'Compact the context', group: 'Session' },
|
|
28
|
+
{ name: 'copy', description: 'Copy the last reply', group: 'Inspect' },
|
|
29
|
+
{ name: 'export', description: 'Export this branch to HTML or JSONL', group: 'Inspect' },
|
|
30
|
+
{ name: 'reload', description: 'Reload trusted resources and keybindings', group: 'Harness' },
|
|
31
|
+
{ name: 'accounts', description: 'Sign in, switch, check and remove Pi accounts', group: 'Harness' },
|
|
32
|
+
{ name: 'permissions', description: 'Full Permission and per-tool approvals', group: 'Harness' },
|
|
33
|
+
{ name: 'harness', description: 'Browse foundation, variant and all resources', group: 'Harness' },
|
|
34
|
+
{ name: 'skills', description: 'View, inspect and use skills; discover more', group: 'Harness' },
|
|
35
|
+
{ name: 'connections', description: 'Manage local and remote MCP connections', group: 'Harness' },
|
|
36
|
+
{ name: 'mcp', description: 'Open Connections in the Harness browser', group: 'Harness' },
|
|
37
|
+
{ name: 'tools', description: 'Inspect available tools, owners and approvals', group: 'Harness' },
|
|
38
|
+
{ name: 'plugins', description: 'Switch plugins on or off and trust their code', group: 'Harness' },
|
|
39
|
+
{ name: 'hub', description: 'Browse and install reviewed skills, plugins and documents', group: 'Harness' },
|
|
40
|
+
{ name: 'components', description: 'Browse skills, prompts and rules for this Harness scope', group: 'Harness' },
|
|
41
|
+
{ name: 'settings', description: 'Session, terminal and browser settings', group: 'Harness' },
|
|
42
|
+
{ name: 'log', description: "Harness diagnostics and this conversation's run events", group: 'Inspect' },
|
|
43
|
+
{ name: 'hotkeys', description: 'Keyboard controls', group: 'Core' },
|
|
44
|
+
{ name: 'quit', description: 'Restore the terminal and exit', group: 'Core' }
|
|
38
45
|
];
|
|
46
|
+
const COMMAND_ALIASES = new Map(BUILTINS.flatMap(command => (command.aliases ?? []).map(alias => [alias, command.name])));
|
|
39
47
|
export function createTerminalCommands(host) {
|
|
40
48
|
const notify = (text) => host.ui.notify(text, 'info');
|
|
41
49
|
const choose = async (title, items, label) => {
|
|
@@ -43,19 +51,6 @@ export function createTerminalCommands(host) {
|
|
|
43
51
|
const selected = await host.ui.select(title, labels);
|
|
44
52
|
return selected === undefined ? undefined : items[labels.indexOf(selected)];
|
|
45
53
|
};
|
|
46
|
-
const completions = () => {
|
|
47
|
-
const session = host.session();
|
|
48
|
-
const commands = new Map(BUILTINS.map(command => [command.name, command]));
|
|
49
|
-
for (const command of session.extensionRunner.getRegisteredCommands()) {
|
|
50
|
-
if (!commands.has(command.name))
|
|
51
|
-
commands.set(command.name, { name: command.name, description: command.description });
|
|
52
|
-
}
|
|
53
|
-
for (const prompt of session.resourceLoader.getPrompts().prompts) {
|
|
54
|
-
if (!commands.has(prompt.name))
|
|
55
|
-
commands.set(prompt.name, { name: prompt.name, description: prompt.description });
|
|
56
|
-
}
|
|
57
|
-
return [...commands.values()];
|
|
58
|
-
};
|
|
59
54
|
/** The skills this session may be asked to use, each invoked as `$name`. Off with the "Skill commands" setting. */
|
|
60
55
|
const skills = () => {
|
|
61
56
|
const session = host.session();
|
|
@@ -66,23 +61,144 @@ export function createTerminalCommands(host) {
|
|
|
66
61
|
/** The text to send: a leading `$name` of a loaded skill becomes the `/skill:name` form Pi expands. */
|
|
67
62
|
const expandSkill = (text) => expandSkillInvocation(text, new Set(skills().map((skill) => skill.name)));
|
|
68
63
|
async function openPage(id, options = {}) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
64
|
+
let close = () => { };
|
|
65
|
+
const page = createCommandPage(host, id, { ...options, close: () => close() });
|
|
66
|
+
await host.ui.custom((tui, _theme, _keys, done) => {
|
|
67
|
+
close = () => done(undefined);
|
|
68
|
+
return new PageOverlay({ page, tui, theme: () => host.ui.theme, close, changed: host.changed });
|
|
69
|
+
}, { overlay: true, overlayOptions: pageOverlayOptions(page.presentation) });
|
|
73
70
|
}
|
|
71
|
+
const pluginHandlers = createBuiltinPluginCommandHandlers(host, openPage);
|
|
72
|
+
const pluginCommands = BUILTIN_PLUGIN_REGISTRY.commands();
|
|
73
|
+
const coreNames = new Set([...BUILTINS.map(command => command.name), ...COMMAND_ALIASES.keys()]);
|
|
74
|
+
for (const command of pluginCommands) {
|
|
75
|
+
if (command.namespace === 'unprefixed' && coreNames.has(command.name)) {
|
|
76
|
+
throw new Error(`Required plugin command collides with a core command: ${command.pluginId}:${command.name}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const extensionCommands = () => {
|
|
80
|
+
const byName = new Map();
|
|
81
|
+
for (const command of host.session().extensionRunner.getRegisteredCommands()) {
|
|
82
|
+
byName.set(command.name, [...(byName.get(command.name) ?? []), command]);
|
|
83
|
+
}
|
|
84
|
+
const unique = new Map();
|
|
85
|
+
const duplicates = new Map();
|
|
86
|
+
for (const [name, commands] of byName) {
|
|
87
|
+
if (commands.length === 1)
|
|
88
|
+
unique.set(name, commands[0]);
|
|
89
|
+
else
|
|
90
|
+
duplicates.set(name, commands);
|
|
91
|
+
}
|
|
92
|
+
return { unique, duplicates };
|
|
93
|
+
};
|
|
94
|
+
const unprefixedCommandNames = (session) => new Set([
|
|
95
|
+
...BUILTINS.flatMap(command => [command.name, ...(command.aliases ?? [])]),
|
|
96
|
+
...pluginCommands.filter(command => command.namespace === 'unprefixed').map(command => command.name),
|
|
97
|
+
...session.resourceLoader.getPrompts().prompts.map(prompt => prompt.name)
|
|
98
|
+
]);
|
|
99
|
+
const runBuiltinPluginCommand = async (name, argument) => {
|
|
100
|
+
try {
|
|
101
|
+
const handler = pluginHandlers.get(name);
|
|
102
|
+
if (!handler)
|
|
103
|
+
throw new Error(`No terminal handler for built-in plugin command ${name}.`);
|
|
104
|
+
await handler(argument);
|
|
105
|
+
host.refresh();
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
host.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const completions = () => {
|
|
112
|
+
const session = host.session();
|
|
113
|
+
const prompts = session.resourceLoader.getPrompts().prompts;
|
|
114
|
+
const commands = new Map(BUILTINS.map(command => [command.name, command]));
|
|
115
|
+
const bareNames = unprefixedCommandNames(session);
|
|
116
|
+
const namespacedBuiltins = new Set();
|
|
117
|
+
for (const command of pluginCommands) {
|
|
118
|
+
if (command.namespace === 'unprefixed') {
|
|
119
|
+
commands.set(command.name, { name: command.name, description: command.description, group: 'Harness' });
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
namespacedBuiltins.add(command.name);
|
|
123
|
+
if (!bareNames.has(command.name)) {
|
|
124
|
+
const invocation = `plugin:${command.name}`;
|
|
125
|
+
commands.set(invocation, { name: invocation, description: command.description, group: 'Plugins' });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const extensions = extensionCommands();
|
|
130
|
+
for (const [name, command] of extensions.unique) {
|
|
131
|
+
if (!bareNames.has(name) && !namespacedBuiltins.has(name)) {
|
|
132
|
+
const invocation = `plugin:${name}`;
|
|
133
|
+
commands.set(invocation, { name: invocation, description: command.description, group: 'Plugins' });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const prompt of prompts) {
|
|
137
|
+
if (!commands.has(prompt.name) && !COMMAND_ALIASES.has(prompt.name))
|
|
138
|
+
commands.set(prompt.name, { name: prompt.name, description: prompt.description, group: 'Prompts' });
|
|
139
|
+
}
|
|
140
|
+
return [...commands.values()];
|
|
141
|
+
};
|
|
74
142
|
async function execute(text) {
|
|
75
143
|
const match = /^\/([^\s]+)(?:\s+([\s\S]*))?$/.exec(text.trim());
|
|
76
144
|
if (!match)
|
|
77
145
|
return false;
|
|
78
|
-
const
|
|
146
|
+
const typedName = match[1];
|
|
79
147
|
const argument = match[2]?.trim() ?? '';
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
148
|
+
if (typedName.startsWith('plugin:')) {
|
|
149
|
+
const pluginName = typedName.slice('plugin:'.length);
|
|
150
|
+
const session = host.session();
|
|
151
|
+
if (unprefixedCommandNames(session).has(pluginName)) {
|
|
152
|
+
host.ui.notify(`Use /${pluginName}; /plugin:${pluginName} is unavailable because an unprefixed command already uses that name.`, 'error');
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
const builtin = BUILTIN_PLUGIN_REGISTRY.command(pluginName);
|
|
156
|
+
const extensions = extensionCommands();
|
|
157
|
+
if (builtin?.namespace === 'plugin') {
|
|
158
|
+
const collisions = extensions.unique.get(pluginName) ? [extensions.unique.get(pluginName)] : extensions.duplicates.get(pluginName) ?? [];
|
|
159
|
+
if (collisions.length) {
|
|
160
|
+
const owners = collisions.map(command => command.sourceInfo.path).join(', ');
|
|
161
|
+
host.ui.notify(`Extension command /plugin:${pluginName} from ${owners} is unavailable because the name belongs to the built-in ${builtin.pluginId} plugin.`, 'error');
|
|
162
|
+
}
|
|
163
|
+
await runBuiltinPluginCommand(pluginName, argument);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
const duplicate = extensions.duplicates.get(pluginName);
|
|
167
|
+
if (duplicate) {
|
|
168
|
+
const owners = duplicate.map(command => command.sourceInfo.path).join(', ');
|
|
169
|
+
host.ui.notify(`Plugin command /plugin:${pluginName} is ambiguous. Registered by: ${owners}. Rename or remove one command.`, 'error');
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
const registered = extensions.unique.get(pluginName);
|
|
173
|
+
if (registered) {
|
|
174
|
+
try {
|
|
175
|
+
const command = host.session().extensionRunner.getCommand(registered.invocationName);
|
|
176
|
+
if (!command)
|
|
177
|
+
throw new Error(`Plugin command /plugin:${pluginName} is no longer registered. Reload trusted resources.`);
|
|
178
|
+
await command.handler(argument, host.session().extensionRunner.createCommandContext());
|
|
179
|
+
host.refresh();
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
host.ui.notify(error instanceof Error ? error.message : String(error), 'error');
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
host.ui.notify(`Unknown plugin command: /${typedName}. Use /help to list the commands.`, 'error');
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
const name = COMMAND_ALIASES.get(typedName) ?? typedName;
|
|
190
|
+
const builtin = BUILTINS.find(item => item.name === name);
|
|
191
|
+
const requiredPluginCommand = BUILTIN_PLUGIN_REGISTRY.command(name);
|
|
192
|
+
if (!builtin && requiredPluginCommand?.namespace === 'unprefixed') {
|
|
193
|
+
await runBuiltinPluginCommand(name, argument);
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
if (!builtin) {
|
|
83
197
|
// A skill is offered as $name; the typed /skill:name form still reaches the SDK, which expands it.
|
|
84
198
|
if (skills().some(skill => `skill:${skill.name}` === name))
|
|
85
199
|
return false;
|
|
200
|
+
if (host.session().resourceLoader.getPrompts().prompts.some(prompt => prompt.name === name))
|
|
201
|
+
return false;
|
|
86
202
|
host.ui.notify(`Unknown command: /${name}. Use /help to list commands.`, 'error');
|
|
87
203
|
return true;
|
|
88
204
|
}
|
|
@@ -90,7 +206,7 @@ export function createTerminalCommands(host) {
|
|
|
90
206
|
const session = host.session();
|
|
91
207
|
switch (name) {
|
|
92
208
|
case 'help':
|
|
93
|
-
|
|
209
|
+
host.showHelp(groupTerminalCommands(completions()));
|
|
94
210
|
break;
|
|
95
211
|
case 'plan': {
|
|
96
212
|
if (!argument || ['on', 'off', 'toggle'].includes(argument.toLowerCase()))
|
|
@@ -113,7 +229,7 @@ export function createTerminalCommands(host) {
|
|
|
113
229
|
case 'new':
|
|
114
230
|
await host.runtime().newSession();
|
|
115
231
|
break;
|
|
116
|
-
case '
|
|
232
|
+
case 'session': {
|
|
117
233
|
let path = argument;
|
|
118
234
|
if (!path) {
|
|
119
235
|
const sessions = await SessionManager.list(host.cwd(), host.sessionDirectory());
|
|
@@ -150,7 +266,7 @@ export function createTerminalCommands(host) {
|
|
|
150
266
|
}
|
|
151
267
|
break;
|
|
152
268
|
}
|
|
153
|
-
case '
|
|
269
|
+
case 'history': {
|
|
154
270
|
const tree = session.sessionManager.getTree();
|
|
155
271
|
if (!tree.length)
|
|
156
272
|
throw new Error('No entries in this session');
|
|
@@ -205,9 +321,6 @@ export function createTerminalCommands(host) {
|
|
|
205
321
|
await session.setModel(selected.model, { persist: selected.persist });
|
|
206
322
|
break;
|
|
207
323
|
}
|
|
208
|
-
case 'doctor':
|
|
209
|
-
await openPage('doctor');
|
|
210
|
-
break;
|
|
211
324
|
case 'settings':
|
|
212
325
|
await openPage('settings');
|
|
213
326
|
break;
|
|
@@ -217,6 +330,19 @@ export function createTerminalCommands(host) {
|
|
|
217
330
|
case 'accounts':
|
|
218
331
|
await openPage('accounts');
|
|
219
332
|
break;
|
|
333
|
+
case 'harness':
|
|
334
|
+
await openPage('harness');
|
|
335
|
+
break;
|
|
336
|
+
case 'skills':
|
|
337
|
+
await openPage('skills');
|
|
338
|
+
break;
|
|
339
|
+
case 'connections':
|
|
340
|
+
case 'mcp':
|
|
341
|
+
await openPage('connections');
|
|
342
|
+
break;
|
|
343
|
+
case 'tools':
|
|
344
|
+
await openPage('tools');
|
|
345
|
+
break;
|
|
220
346
|
case 'plugins':
|
|
221
347
|
await openPage('plugins');
|
|
222
348
|
break;
|
|
@@ -232,9 +358,6 @@ export function createTerminalCommands(host) {
|
|
|
232
358
|
session.setSessionName(name);
|
|
233
359
|
break;
|
|
234
360
|
}
|
|
235
|
-
case 'trajectory':
|
|
236
|
-
await openPage('trajectory', { runId: argument });
|
|
237
|
-
break;
|
|
238
361
|
case 'log':
|
|
239
362
|
await openPage('log');
|
|
240
363
|
break;
|
|
@@ -23,5 +23,5 @@ export declare function centeredDialogHost(tui: TUI): boolean;
|
|
|
23
23
|
export declare function pageRows(tui: TUI): number;
|
|
24
24
|
/** Centers a rectangle on the screen, clamped so it never starts outside the terminal. */
|
|
25
25
|
export declare function centeredRect(columns: number, rows: number, width: number, height: number): DialogRect;
|
|
26
|
-
/**
|
|
27
|
-
export declare function pageOverlayOptions(): HarnessOverlayOptions;
|
|
26
|
+
/** Page commands declare their presentation; OpenTUI centers only marked dialogs. */
|
|
27
|
+
export declare function pageOverlayOptions(presentation?: 'centered' | 'fullscreen'): HarnessOverlayOptions;
|
|
@@ -44,7 +44,9 @@ export function centeredRect(columns, rows, width, height) {
|
|
|
44
44
|
height: boxHeight
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
48
|
-
export function pageOverlayOptions() {
|
|
47
|
+
/** Page commands declare their presentation; OpenTUI centers only marked dialogs. */
|
|
48
|
+
export function pageOverlayOptions(presentation = 'centered') {
|
|
49
|
+
if (presentation === 'fullscreen')
|
|
50
|
+
return { anchor: 'top-left', width: '100%', maxHeight: '100%' };
|
|
49
51
|
return { anchor: 'top-left', width: '100%', maxHeight: '100%', dialog: 'page' };
|
|
50
52
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ExtensionUIContext, ExtensionUIDialogOptions, KeybindingsManager, Theme } from '@earendil-works/pi-coding-agent';
|
|
2
2
|
import { Input, type Component, type Focusable, type SelectListTheme, type TUI, type TuiMouseEventResult } from '@earendil-works/pi-tui';
|
|
3
3
|
import type { TerminalMouseInput } from "./mouse.js";
|
|
4
|
+
export interface GroupedSelectOption {
|
|
5
|
+
label: string;
|
|
6
|
+
group?: string;
|
|
7
|
+
}
|
|
4
8
|
/** `page` asks for a full-screen dialog; Pi's own dialogs ignore the field. */
|
|
5
9
|
export type TerminalDialogOptions = ExtensionUIDialogOptions & {
|
|
6
10
|
page?: boolean;
|
|
@@ -19,7 +23,7 @@ export declare class SearchableSelect implements Component, Focusable {
|
|
|
19
23
|
private readonly list;
|
|
20
24
|
private readonly labels;
|
|
21
25
|
private focusedValue;
|
|
22
|
-
constructor(options: string[], tui: TUI, theme: () => Theme, select: (index: number) => void, cancel: () => void, search?: 'always' | 'never');
|
|
26
|
+
constructor(options: string[], tui: TUI, theme: () => Theme, select: (index: number) => void, cancel: () => void, search?: 'always' | 'never', groups?: readonly (string | undefined)[]);
|
|
23
27
|
get focused(): boolean;
|
|
24
28
|
set focused(value: boolean);
|
|
25
29
|
get pinnedRows(): number;
|
|
@@ -66,6 +70,7 @@ export declare class TerminalDialogs {
|
|
|
66
70
|
constructor(tui: TUI, theme: () => Theme, keybindings: KeybindingsManager, changed: () => void);
|
|
67
71
|
private show;
|
|
68
72
|
select(title: string, options: string[], opts?: TerminalDialogOptions): Promise<string | undefined>;
|
|
73
|
+
selectGrouped(title: string, options: readonly GroupedSelectOption[], opts?: ExtensionUIDialogOptions): Promise<string | undefined>;
|
|
69
74
|
confirm(title: string, message: string, opts?: ExtensionUIDialogOptions): Promise<boolean>;
|
|
70
75
|
input(title: string, placeholder?: string, opts?: ExtensionUIDialogOptions, secret?: boolean): Promise<string | undefined>;
|
|
71
76
|
editor(title: string, prefill?: string): Promise<string | undefined>;
|
|
@@ -31,14 +31,14 @@ export class SearchableSelect {
|
|
|
31
31
|
list;
|
|
32
32
|
labels;
|
|
33
33
|
focusedValue = false;
|
|
34
|
-
constructor(options, tui, theme, select, cancel, search = 'always') {
|
|
34
|
+
constructor(options, tui, theme, select, cancel, search = 'always', groups) {
|
|
35
35
|
this.tui = tui;
|
|
36
36
|
this.select = select;
|
|
37
37
|
this.cancel = cancel;
|
|
38
38
|
this.labels = options.map(safe);
|
|
39
39
|
this.list = new FilterList({
|
|
40
40
|
theme, search,
|
|
41
|
-
rows: this.labels.map((label, index) => ({ id: String(index), label, data: index })),
|
|
41
|
+
rows: this.labels.map((label, index) => ({ id: String(index), label, ...(groups?.[index] ? { group: groups[index] } : {}), data: index })),
|
|
42
42
|
limit: () => this.windowRows(),
|
|
43
43
|
noun: ['option', 'options'],
|
|
44
44
|
onActivate: row => this.select(row.data ?? 0),
|
|
@@ -265,6 +265,13 @@ export class TerminalDialogs {
|
|
|
265
265
|
return new DialogPanel(title, list, this.theme, '↑/↓ select · Enter confirm · Esc cancel', undefined, opts?.page ? 'page' : 'block');
|
|
266
266
|
}, opts);
|
|
267
267
|
}
|
|
268
|
+
selectGrouped(title, options, opts) {
|
|
269
|
+
const labels = options.map(option => option.label);
|
|
270
|
+
return this.show(done => {
|
|
271
|
+
const list = new SearchableSelect(labels, this.tui, this.theme, index => done(labels[index]), () => done(undefined), 'always', options.map(option => option.group));
|
|
272
|
+
return new DialogPanel(title, list, this.theme, '↑/↓ select · Enter confirm · Esc cancel');
|
|
273
|
+
}, opts);
|
|
274
|
+
}
|
|
268
275
|
async confirm(title, message, opts) {
|
|
269
276
|
return (await this.show(done => {
|
|
270
277
|
const list = new SearchableSelect(['Yes', 'No'], this.tui, this.theme, index => done(index === 0), () => done(undefined), 'never');
|
|
@@ -67,11 +67,8 @@ export class ModelPicker {
|
|
|
67
67
|
this.error = 'Model refresh timed out; showing cached models.';
|
|
68
68
|
else if (result.errors.size)
|
|
69
69
|
this.error = `Could not refresh ${[...result.errors.keys()].join(', ')}; showing cached models.`;
|
|
70
|
-
else
|
|
70
|
+
else
|
|
71
71
|
this.error = this.options.runtime.getError();
|
|
72
|
-
if (!this.error)
|
|
73
|
-
this.status = 'Model catalogs refreshed.';
|
|
74
|
-
}
|
|
75
72
|
this.load();
|
|
76
73
|
}
|
|
77
74
|
catch (error) {
|
|
@@ -135,10 +132,8 @@ export class ModelPicker {
|
|
|
135
132
|
return [];
|
|
136
133
|
const centered = centeredDialogHost(this.options.tui) && dialogFits(this.options.tui.terminal.columns, this.options.tui.terminal.rows);
|
|
137
134
|
const inner = centered ? centerDialogContentWidth(width) : contentWidth(width);
|
|
138
|
-
const selected = this.list.selected?.data;
|
|
139
135
|
const notes = [
|
|
140
136
|
this.status ? theme.fg('muted', this.status) : '',
|
|
141
|
-
selected ? theme.fg('muted', `Model name: ${safeText(selected.name ?? selected.id)}`) : '',
|
|
142
137
|
this.error ? theme.fg('error', this.error) : ''
|
|
143
138
|
].filter(Boolean);
|
|
144
139
|
const chromeRows = centered ? centerDialogChromeRows(rows) : 4;
|
|
@@ -2,6 +2,9 @@ import type { TuiMouseEventResult } from '@earendil-works/pi-tui';
|
|
|
2
2
|
import type { TabInfo } from "./chrome.js";
|
|
3
3
|
import type { TerminalMouseInput } from "./mouse.js";
|
|
4
4
|
export interface TerminalPage {
|
|
5
|
+
presentation?: 'centered' | 'fullscreen';
|
|
6
|
+
ownsNavigation?: boolean;
|
|
7
|
+
selectTab?(id: string): void;
|
|
5
8
|
tabs(): readonly TabInfo[];
|
|
6
9
|
activeTab(): string;
|
|
7
10
|
render(width: number, rows: number): string[];
|
|
@@ -23,6 +23,12 @@ export class PageOverlay {
|
|
|
23
23
|
if (this.disposed)
|
|
24
24
|
return undefined;
|
|
25
25
|
if (event.type === 'wheel') {
|
|
26
|
+
const layout = this.pointerLayout;
|
|
27
|
+
if (layout && event.y >= layout.bodyStart) {
|
|
28
|
+
const handled = this.page.handleMouse?.({ ...event, x: event.x - layout.contentX, y: event.y - layout.bodyStart });
|
|
29
|
+
if (handled?.handled)
|
|
30
|
+
return handled;
|
|
31
|
+
}
|
|
26
32
|
const key = event.wheelDelta !== undefined && event.wheelDelta < 0 ? '\x1b[5~' : '\x1b[6~';
|
|
27
33
|
this.handleInput(key);
|
|
28
34
|
return { handled: true, render: true };
|
|
@@ -49,6 +55,11 @@ export class PageOverlay {
|
|
|
49
55
|
const target = tabIndex ?? (tabX < layout.contentWidth / 2
|
|
50
56
|
? (current - 1 + tabs.length) % tabs.length
|
|
51
57
|
: (current + 1) % tabs.length);
|
|
58
|
+
if (this.page.selectTab) {
|
|
59
|
+
this.page.selectTab(tabs[target].id);
|
|
60
|
+
this.hoveredTab = undefined;
|
|
61
|
+
return { handled: true, render: true };
|
|
62
|
+
}
|
|
52
63
|
const delta = (target - Math.max(0, current) + tabs.length) % tabs.length;
|
|
53
64
|
const reverse = (Math.max(0, current) - target + tabs.length) % tabs.length;
|
|
54
65
|
const direction = delta <= reverse ? '\t' : '\x1b[Z';
|
|
@@ -75,9 +86,9 @@ export class PageOverlay {
|
|
|
75
86
|
this.options.close();
|
|
76
87
|
return;
|
|
77
88
|
}
|
|
78
|
-
const centered = centeredDialogHost(this.options.tui) && dialogFits(this.options.tui.terminal.columns, this.options.tui.terminal.rows);
|
|
89
|
+
const centered = this.page.presentation !== 'fullscreen' && centeredDialogHost(this.options.tui) && dialogFits(this.options.tui.terminal.columns, this.options.tui.terminal.rows);
|
|
79
90
|
const tabs = this.options.page.tabs();
|
|
80
|
-
const input = centered && tabs.length > 1
|
|
91
|
+
const input = centered && tabs.length > 1 && !this.options.page.ownsNavigation
|
|
81
92
|
? matchesKey(data, 'right') ? '\t' : matchesKey(data, 'left') ? '\x1b[Z' : data
|
|
82
93
|
: data;
|
|
83
94
|
if (this.options.page.handleInput(input)) {
|
|
@@ -90,13 +101,13 @@ export class PageOverlay {
|
|
|
90
101
|
render(width) {
|
|
91
102
|
if (this.disposed)
|
|
92
103
|
return [];
|
|
93
|
-
const rows = pageRows(this.options.tui);
|
|
104
|
+
const rows = this.page.presentation === 'fullscreen' ? this.options.tui.terminal.rows : pageRows(this.options.tui);
|
|
94
105
|
const page = this.options.page;
|
|
95
106
|
const tabs = page.tabs();
|
|
96
107
|
if (width < 1 || rows < 1 || !tabs.length)
|
|
97
108
|
return [];
|
|
98
109
|
const theme = this.options.theme();
|
|
99
|
-
const centered = centeredDialogHost(this.options.tui) && dialogFits(this.options.tui.terminal.columns, this.options.tui.terminal.rows);
|
|
110
|
+
const centered = this.page.presentation !== 'fullscreen' && centeredDialogHost(this.options.tui) && dialogFits(this.options.tui.terminal.columns, this.options.tui.terminal.rows);
|
|
100
111
|
const inner = centered ? centerDialogContentWidth(width) : contentWidth(width);
|
|
101
112
|
if (centered) {
|
|
102
113
|
const tabRows = tabs.length > 1 ? 1 : 0;
|
|
@@ -121,9 +132,9 @@ export class PageOverlay {
|
|
|
121
132
|
contentWidth: contentWidth(width),
|
|
122
133
|
centered
|
|
123
134
|
};
|
|
124
|
-
const footer = [
|
|
135
|
+
const footer = page.ownsNavigation ? `Esc back/close · ${page.hint()}` : [
|
|
125
136
|
page.hint(),
|
|
126
|
-
tabs.length > 1 ? 'Tab/Shift+Tab sections' : '',
|
|
137
|
+
tabs.length > 1 && !page.ownsNavigation ? 'Tab/Shift+Tab sections' : '',
|
|
127
138
|
'Esc close'
|
|
128
139
|
].filter(Boolean).join(' · ');
|
|
129
140
|
return pageFrame({
|