@xingular/harness 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +28 -25
  3. package/dist/cli.js +6675 -2501
  4. package/dist/sdk/accounts/accounts.d.ts +27 -3
  5. package/dist/sdk/accounts/accounts.js +78 -24
  6. package/dist/sdk/accounts/secrets.d.ts +12 -0
  7. package/dist/sdk/accounts/secrets.js +114 -7
  8. package/dist/sdk/accounts/store.d.ts +26 -1
  9. package/dist/sdk/accounts/store.js +1 -1
  10. package/dist/sdk/agents/builtin.js +15 -1
  11. package/dist/sdk/agents/planResearch.d.ts +5 -0
  12. package/dist/sdk/agents/planResearch.js +19 -0
  13. package/dist/sdk/agents/profiles.d.ts +2 -1
  14. package/dist/sdk/agents/profiles.js +9 -4
  15. package/dist/sdk/annotations/pageScript.js +349 -48
  16. package/dist/sdk/annotations/part.js +34 -23
  17. package/dist/sdk/annotations/store.d.ts +7 -0
  18. package/dist/sdk/annotations/store.js +13 -5
  19. package/dist/sdk/browser/backend.d.ts +16 -0
  20. package/dist/sdk/browser/backend.js +17 -0
  21. package/dist/sdk/browser/contracts.d.ts +19 -0
  22. package/dist/sdk/browser/contracts.js +116 -0
  23. package/dist/sdk/browser/tools.d.ts +1 -4
  24. package/dist/sdk/browser/tools.js +14 -91
  25. package/dist/sdk/cli.js +5 -4
  26. package/dist/sdk/commands/list.d.ts +3 -0
  27. package/dist/sdk/commands/list.js +75 -0
  28. package/dist/sdk/commands/registry.d.ts +250 -0
  29. package/dist/sdk/commands/registry.js +47 -0
  30. package/dist/sdk/commands/routes.d.ts +16 -0
  31. package/dist/sdk/commands/routes.js +33 -0
  32. package/dist/sdk/core/commandBudget.d.ts +23 -0
  33. package/dist/sdk/core/commandBudget.js +57 -0
  34. package/dist/sdk/core/index.js +17 -1
  35. package/dist/sdk/core/interactiveProfile.d.ts +46 -0
  36. package/dist/sdk/core/interactiveProfile.js +143 -0
  37. package/dist/sdk/core/workspace.d.ts +13 -0
  38. package/dist/sdk/core/workspace.js +88 -0
  39. package/dist/sdk/core/workspaceBashTool.d.ts +4 -0
  40. package/dist/sdk/core/workspaceBashTool.js +36 -0
  41. package/dist/sdk/core/workspaceSandbox.d.ts +31 -0
  42. package/dist/sdk/core/workspaceSandbox.js +320 -0
  43. package/dist/sdk/core/workspaceWriteOperations.d.ts +22 -0
  44. package/dist/sdk/core/workspaceWriteOperations.js +206 -0
  45. package/dist/sdk/doctor/types.d.ts +2 -2
  46. package/dist/sdk/harness.d.ts +7 -0
  47. package/dist/sdk/harness.js +42 -2
  48. package/dist/sdk/index.d.ts +6 -1
  49. package/dist/sdk/index.js +3 -0
  50. package/dist/sdk/pi/runtime.d.ts +2 -1
  51. package/dist/sdk/pi/runtime.js +2 -2
  52. package/dist/sdk/plugins/builtin.js +29 -12
  53. package/dist/sdk/plugins/builtinRegistry.d.ts +10 -3
  54. package/dist/sdk/plugins/builtinRegistry.js +30 -8
  55. package/dist/sdk/plugins/catalogue.js +2 -1
  56. package/dist/sdk/plugins/commandPrefix.d.ts +8 -0
  57. package/dist/sdk/plugins/commandPrefix.js +21 -0
  58. package/dist/sdk/plugins/firstParty.d.ts +1 -1
  59. package/dist/sdk/plugins/firstParty.js +8 -2
  60. package/dist/sdk/plugins/inventory.js +4 -3
  61. package/dist/sdk/plugins/manifest.d.ts +2 -0
  62. package/dist/sdk/plugins/manifest.js +2 -1
  63. package/dist/sdk/plugins/surfaces.d.ts +5 -0
  64. package/dist/sdk/plugins/surfaces.js +11 -0
  65. package/dist/sdk/plugins/types.d.ts +15 -0
  66. package/dist/sdk/prompt/compile.d.ts +14 -0
  67. package/dist/sdk/prompt/compile.js +65 -29
  68. package/dist/sdk/prompt/standard.d.ts +17 -0
  69. package/dist/sdk/prompt/standard.js +21 -0
  70. package/dist/sdk/server/methods.js +18 -0
  71. package/dist/sdk/server/remote.js +36 -11
  72. package/dist/sdk/sessions/compactionCheckpoint.d.ts +11 -0
  73. package/dist/sdk/sessions/compactionCheckpoint.js +79 -0
  74. package/dist/sdk/sessions/continuation.d.ts +3 -0
  75. package/dist/sdk/sessions/continuation.js +5 -1
  76. package/dist/sdk/sessions/contracts.d.ts +168 -0
  77. package/dist/sdk/sessions/contracts.js +59 -1
  78. package/dist/sdk/sessions/control.d.ts +72 -0
  79. package/dist/sdk/sessions/control.js +158 -0
  80. package/dist/sdk/sessions/initialBash.d.ts +7 -0
  81. package/dist/sdk/sessions/initialBash.js +7 -0
  82. package/dist/sdk/sessions/planMode.d.ts +14 -3
  83. package/dist/sdk/sessions/planMode.js +73 -16
  84. package/dist/sdk/sessions/registry.d.ts +1 -1
  85. package/dist/sdk/sessions/registry.js +2 -2
  86. package/dist/sdk/sessions/runStore.d.ts +14 -0
  87. package/dist/sdk/sessions/runStore.js +35 -0
  88. package/dist/sdk/sessions/runner.d.ts +40 -2
  89. package/dist/sdk/sessions/runner.js +161 -13
  90. package/dist/sdk/sessions/sessions.d.ts +23 -3
  91. package/dist/sdk/sessions/sessions.js +202 -17
  92. package/dist/sdk/sessions/subagentService.d.ts +9 -0
  93. package/dist/sdk/sessions/subagentService.js +63 -4
  94. package/dist/sdk/sessions/workSlice.d.ts +103 -0
  95. package/dist/sdk/sessions/workSlice.js +297 -0
  96. package/dist/sdk/sessions/workspaceBoundary.d.ts +15 -0
  97. package/dist/sdk/sessions/workspaceBoundary.js +46 -0
  98. package/dist/sdk/terminal/browserControl.js +2 -1
  99. package/dist/sdk/terminal/controller.d.ts +11 -3
  100. package/dist/sdk/terminal/controller.js +40 -13
  101. package/dist/sdk/terminal/opentui/theme.d.ts +43 -0
  102. package/dist/sdk/terminal/opentui/theme.js +87 -0
  103. package/dist/sdk/terminal/piExtension.d.ts +6 -1
  104. package/dist/sdk/terminal/piExtension.js +154 -54
  105. package/dist/sdk/terminal/standalone.d.ts +7 -0
  106. package/dist/sdk/terminal/standalone.js +72 -13
  107. package/dist/sdk/terminal/subagentMonitor.d.ts +11 -2
  108. package/dist/sdk/terminal/subagentMonitor.js +32 -1
  109. package/dist/sdk/terminal/terminals.d.ts +19 -2
  110. package/dist/sdk/terminal/terminals.js +245 -35
  111. package/dist/sdk/terminal/tui/annotateCommand.d.ts +1 -1
  112. package/dist/sdk/terminal/tui/annotateCommand.js +3 -3
  113. package/dist/sdk/terminal/tui/commandPages.d.ts +3 -1
  114. package/dist/sdk/terminal/tui/commandPages.js +3 -0
  115. package/dist/sdk/terminal/tui/commands.d.ts +2 -0
  116. package/dist/sdk/terminal/tui/commands.js +205 -124
  117. package/dist/sdk/terminal/tui/list.d.ts +14 -1
  118. package/dist/sdk/terminal/tui/list.js +96 -14
  119. package/dist/sdk/terminal/tui/pageOverlay.js +13 -4
  120. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +1 -1
  121. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +5 -5
  122. package/dist/sdk/terminal/tui/pages/notesPage.js +18 -18
  123. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +0 -5
  124. package/dist/sdk/terminal/tui/pages/pluginsPage.js +9 -3
  125. package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +1 -0
  126. package/dist/sdk/terminal/tui/pages/subagentsPage.js +98 -16
  127. package/dist/sdk/terminal/tui/pages/usagePage.d.ts +43 -0
  128. package/dist/sdk/terminal/tui/pages/usagePage.js +370 -0
  129. package/dist/sdk/terminal/tui/piAdapter.d.ts +1 -2
  130. package/dist/sdk/terminal/tui/piAdapter.js +3 -5
  131. package/dist/sdk/terminal/tui/pluginCommands.d.ts +2 -0
  132. package/dist/sdk/terminal/tui/pluginCommands.js +13 -9
  133. package/dist/sdk/terminal/tui/runtime.js +23 -3
  134. package/dist/sdk/terminal/tui/sessionPicker.d.ts +40 -0
  135. package/dist/sdk/terminal/tui/sessionPicker.js +141 -0
  136. package/dist/sdk/tools/checklist.js +1 -0
  137. package/dist/sdk/tools/checkpoint.d.ts +27 -0
  138. package/dist/sdk/tools/checkpoint.js +154 -0
  139. package/dist/sdk/tools/getCapabilities.d.ts +31 -0
  140. package/dist/sdk/tools/getCapabilities.js +34 -0
  141. package/dist/sdk/tools/spawnSubagent.js +4 -3
  142. package/dist/sdk/usage/analytics.d.ts +20 -0
  143. package/dist/sdk/usage/analytics.js +151 -0
  144. package/dist/sdk/usage/ledger.d.ts +38 -0
  145. package/dist/sdk/usage/ledger.js +142 -0
  146. package/dist/sdk/usage/limits/adapters.d.ts +11 -0
  147. package/dist/sdk/usage/limits/adapters.js +349 -0
  148. package/dist/sdk/usage/limits/capture.d.ts +8 -0
  149. package/dist/sdk/usage/limits/capture.js +47 -0
  150. package/dist/sdk/usage/limits/http.d.ts +12 -0
  151. package/dist/sdk/usage/limits/http.js +51 -0
  152. package/dist/sdk/usage/limits/normalize.d.ts +15 -0
  153. package/dist/sdk/usage/limits/normalize.js +211 -0
  154. package/dist/sdk/usage/limits/service.d.ts +30 -0
  155. package/dist/sdk/usage/limits/service.js +176 -0
  156. package/dist/sdk/usage/limits/types.d.ts +65 -0
  157. package/dist/sdk/usage/limits/types.js +1 -0
  158. package/dist/sdk/usage/serverMethods.d.ts +3 -0
  159. package/dist/sdk/usage/serverMethods.js +22 -0
  160. package/dist/sdk/usage/service.d.ts +32 -0
  161. package/dist/sdk/usage/service.js +66 -0
  162. package/dist/sdk/usage/settings.d.ts +7 -0
  163. package/dist/sdk/usage/settings.js +28 -0
  164. package/dist/sdk/usage/types.d.ts +60 -0
  165. package/dist/sdk/usage/types.js +4 -0
  166. package/dist/terminal-opentui.js +5207 -1883
  167. package/dist/terminal-pi.js +1658 -271
  168. package/docs/architecture/02-sessions-and-runs.md +128 -26
  169. package/docs/architecture/04-prompt.md +50 -12
  170. package/docs/architecture/06-plugins-and-capabilities.md +24 -7
  171. package/docs/architecture/07-tools-and-gate.md +104 -24
  172. package/docs/architecture/08-memory-and-history.md +4 -2
  173. package/docs/architecture/09-accounts-and-connectors.md +31 -1
  174. package/docs/architecture/10-server-protocol.md +58 -3
  175. package/docs/architecture/11-terminal.md +105 -42
  176. package/docs/architecture/12-storage.md +30 -2
  177. package/docs/architecture/13-world-tools.md +22 -1
  178. package/docs/architecture/14-surfaces-and-parity.md +54 -7
  179. package/docs/architecture/README.md +3 -1
  180. package/docs/architecture/proposals/PROP-0017-page-annotations.md +6 -2
  181. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +1 -1
  182. package/docs/architecture/proposals/PROP-0031-plugin-command-owner-namespaces.md +54 -0
  183. package/docs/architecture/proposals/PROP-0032-planning-quality.md +196 -0
  184. package/docs/architecture/proposals/PROP-0033-compaction-plan-checkpoints.md +75 -0
  185. package/docs/architecture/proposals/PROP-0034-conversation-fast-mode.md +120 -0
  186. package/docs/architecture/proposals/PROP-0035-retain-subagent-status.md +47 -0
  187. package/docs/architecture/proposals/PROP-0036-first-class-harness-commands.md +59 -0
  188. package/docs/architecture/proposals/PROP-0037-workspace-write-boundary.md +63 -0
  189. package/docs/architecture/proposals/PROP-0038-annotate-open-shadow-roots.md +73 -0
  190. package/docs/architecture/proposals/PROP-0039-workspace-boundary-hardening.md +139 -0
  191. package/docs/architecture/proposals/PROP-0040-one-launch-path.md +87 -0
  192. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +247 -0
  193. package/docs/architecture/proposals/PROP-0042-usage-and-provider-limits.md +127 -0
  194. package/docs/architecture/proposals/PROP-0043-read-only-plan-bash.md +74 -0
  195. package/docs/architecture/proposals/README.md +14 -0
  196. package/docs/decisions/ADR-0001-owned-terminal.md +1 -1
  197. package/docs/decisions/ADR-0021-required-command-namespaces.md +1 -1
  198. package/docs/decisions/ADR-0031-plugin-command-owner-namespaces.md +37 -0
  199. package/docs/decisions/ADR-0032-compaction-run-checkpoints.md +33 -0
  200. package/docs/decisions/ADR-0033-plan-mode-research-delegation.md +47 -0
  201. package/docs/decisions/ADR-0034-first-class-harness-terminal-commands.md +25 -0
  202. package/docs/decisions/ADR-0035-one-launch-path.md +40 -0
  203. package/docs/decisions/ADR-0036-workspace-write-operation-boundary.md +54 -0
  204. package/docs/decisions/ADR-0037-usage-ledger-and-provider-limits.md +44 -0
  205. package/docs/decisions/ADR-0038-plan-bash-os-read-only.md +29 -0
  206. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime-replication.json +420 -0
  207. package/docs/experiments/EXP-0003-performance-audit/data/session-runtime.json +420 -0
  208. package/docs/experiments/EXP-0003-performance-audit/data/terminal-compact.json +376 -0
  209. package/docs/experiments/EXP-0003-performance-audit/data/terminal-history.json +422 -0
  210. package/docs/experiments/EXP-0003-performance-audit/data/terminal-question.json +95 -0
  211. package/docs/experiments/EXP-0003-performance-audit/data/terminal-wide.json +1036 -0
  212. package/docs/experiments/EXP-0003-performance-audit/plan.md +109 -0
  213. package/docs/experiments/EXP-0003-performance-audit/results.md +151 -0
  214. package/docs/handoff-2026-10-25-app-parity-and-solo-tabs.md +202 -0
  215. package/docs/parity.md +80 -24
  216. package/docs/releases/1.1.1.md +28 -0
  217. package/docs/releases/1.2.0.md +49 -0
  218. package/docs/releases/4.0.0.md +10 -9
  219. package/docs/releases/README.md +35 -30
  220. package/docs/study/00-index.md +3 -0
  221. package/docs/study/STUDY-0003-long-horizon-runs.md +210 -0
  222. package/docs/terminal-tui.md +100 -59
  223. package/package.json +4 -3
@@ -0,0 +1,75 @@
1
+ // `commands.list`: every slash command a front end offers for one scope, from one source. The
2
+ // terminal palette and a host's menu both read it, so they never keep their own lists.
3
+ import { BUILTIN_PLUGIN_REGISTRY } from "../plugins/builtin.js";
4
+ import { pluginCommandPrefix } from "../plugins/commandPrefix.js";
5
+ import { CORE_COMMANDS } from "./registry.js";
6
+ import { commandRoutes, promptCommandAvailable } from "./routes.js";
7
+ export function listCommands(plugins) {
8
+ const out = new Map();
9
+ const taken = new Set();
10
+ for (const command of CORE_COMMANDS) {
11
+ const aliases = 'aliases' in command ? [...command.aliases] : [];
12
+ out.set(command.name, {
13
+ name: command.name,
14
+ ...(aliases.length ? { aliases } : {}),
15
+ ...('args' in command ? { args: command.args } : {}),
16
+ description: command.description,
17
+ group: command.group,
18
+ source: 'core',
19
+ kind: command.kind
20
+ });
21
+ taken.add(command.name);
22
+ aliases.forEach((alias) => taken.add(alias));
23
+ }
24
+ for (const command of BUILTIN_PLUGIN_REGISTRY.commands()) {
25
+ const name = `${command.prefix}:${command.name}`;
26
+ out.set(name, { name, description: command.description, group: 'Plugins', source: 'plugin', owner: command.pluginId });
27
+ }
28
+ for (const module of BUILTIN_PLUGIN_REGISTRY.modules) {
29
+ const prefix = module.commandPrefix ?? module.descriptor.id;
30
+ const fallback = BUILTIN_PLUGIN_REGISTRY.defaultCommand(prefix);
31
+ if (fallback)
32
+ out.set(`${prefix}:`, { name: `${prefix}:`, description: fallback.description, group: 'Plugins', source: 'plugin', owner: module.descriptor.id });
33
+ }
34
+ const extensions = [];
35
+ const extensionCommandNames = new Set();
36
+ for (const plugin of plugins) {
37
+ for (const contribution of plugin.contributions) {
38
+ if (contribution.kind !== 'code' || contribution.state !== 'on' || !contribution.path || !contribution.registered)
39
+ continue;
40
+ contribution.registered.commands.forEach(name => extensionCommandNames.add(name));
41
+ }
42
+ }
43
+ for (const plugin of plugins) {
44
+ for (const contribution of plugin.contributions) {
45
+ if (contribution.state !== 'on')
46
+ continue;
47
+ if (contribution.kind === 'code' && contribution.registered && contribution.path) {
48
+ const packageRoot = (plugin.source === 'package' || plugin.id.startsWith('package:')) ? plugin.path : null;
49
+ const prefix = pluginCommandPrefix(packageRoot
50
+ ? { path: contribution.path, origin: 'package', baseDir: packageRoot }
51
+ : { path: contribution.path });
52
+ for (const command of contribution.registered.commands) {
53
+ const name = `${prefix}:${command}`;
54
+ extensions.push({ prefix, name: command, owner: packageRoot ? `package:${packageRoot}` : `extension:${contribution.path}`, path: contribution.path,
55
+ listed: { name, group: 'Plugins', source: 'extension', owner: plugin.id } });
56
+ }
57
+ }
58
+ // Catalogue entries without a path are observed extension commands, not templates.
59
+ if (contribution.kind === 'command' && contribution.path && promptCommandAvailable(contribution.name, taken, extensionCommandNames) && !out.has(contribution.name)) {
60
+ out.set(contribution.name, { name: contribution.name, description: contribution.description, group: 'Prompts', source: 'prompt', owner: plugin.id });
61
+ }
62
+ if (contribution.kind === 'skill') {
63
+ const name = `skill:${contribution.name}`;
64
+ if (!out.has(name))
65
+ out.set(name, { name, description: contribution.description, group: 'Skills', source: 'skill', owner: plugin.id });
66
+ }
67
+ }
68
+ }
69
+ const indexed = commandRoutes(extensions);
70
+ for (const [name, routes] of indexed.routes) {
71
+ if (routes.length === 1 && !indexed.conflictingPrefixes.has(routes[0].prefix) && !out.has(name))
72
+ out.set(name, routes[0].listed);
73
+ }
74
+ return [...out.values()];
75
+ }
@@ -0,0 +1,250 @@
1
+ /** Where a command's work happens. */
2
+ export type CoreCommandKind =
3
+ /** Changes or reads the current session through a Harness method. */
4
+ 'session'
5
+ /** Opens a capability page (doctor, worktrees, tools, ...). */
6
+ | 'page'
7
+ /** Presentation only: help, key bindings, copying, showing thinking, leaving. */
8
+ | 'local';
9
+ export type CoreCommandGroup = 'Core' | 'Run' | 'Model' | 'Session' | 'Inspect' | 'Harness';
10
+ export interface CoreCommand {
11
+ name: string;
12
+ aliases?: readonly string[];
13
+ /** What follows the name, for the palette: `<brief>`, `[path]`. */
14
+ args?: string;
15
+ description: string;
16
+ group: CoreCommandGroup;
17
+ kind: CoreCommandKind;
18
+ /** The Harness server methods the command uses, when it uses any. */
19
+ methods?: readonly string[];
20
+ }
21
+ export declare const CORE_COMMANDS: readonly [{
22
+ readonly name: "help";
23
+ readonly description: "List the commands";
24
+ readonly group: "Core";
25
+ readonly kind: "local";
26
+ }, {
27
+ readonly name: "plan";
28
+ readonly args: "[brief]";
29
+ readonly description: "Plan one request, read-only: /plan <brief>";
30
+ readonly group: "Run";
31
+ readonly kind: "session";
32
+ readonly methods: readonly ["sessions.run"];
33
+ }, {
34
+ readonly name: "btw";
35
+ readonly aliases: readonly ["side"];
36
+ readonly args: "[question]";
37
+ readonly description: "Ask a side question without interrupting: /btw <question>";
38
+ readonly group: "Run";
39
+ readonly kind: "session";
40
+ readonly methods: readonly ["sideQuestions.start", "sideQuestions.cancel"];
41
+ }, {
42
+ readonly name: "new";
43
+ readonly description: "Start a new session";
44
+ readonly group: "Session";
45
+ readonly kind: "session";
46
+ }, {
47
+ readonly name: "stop";
48
+ readonly description: "Stop the current reply";
49
+ readonly group: "Run";
50
+ readonly kind: "session";
51
+ readonly methods: readonly ["runs.interrupt"];
52
+ }, {
53
+ readonly name: "session";
54
+ readonly aliases: readonly ["resume"];
55
+ readonly args: "[path]";
56
+ readonly description: "Choose a saved session in this account and folder";
57
+ readonly group: "Session";
58
+ readonly kind: "session";
59
+ }, {
60
+ readonly name: "history";
61
+ readonly aliases: readonly ["tree"];
62
+ readonly description: "Navigate branches in the current session";
63
+ readonly group: "Session";
64
+ readonly kind: "session";
65
+ readonly methods: readonly ["sessions.tree", "sessions.navigate"];
66
+ }, {
67
+ readonly name: "fork";
68
+ readonly description: "Fork from an earlier message";
69
+ readonly group: "Session";
70
+ readonly kind: "session";
71
+ readonly methods: readonly ["sessions.tree", "sessions.fork"];
72
+ }, {
73
+ readonly name: "clone";
74
+ readonly description: "Clone this branch into a new session";
75
+ readonly group: "Session";
76
+ readonly kind: "session";
77
+ readonly methods: readonly ["sessions.clone"];
78
+ }, {
79
+ readonly name: "model";
80
+ readonly args: "[search]";
81
+ readonly description: "Choose the model";
82
+ readonly group: "Model";
83
+ readonly kind: "session";
84
+ readonly methods: readonly ["accounts.models"];
85
+ }, {
86
+ readonly name: "effort";
87
+ readonly args: "[level]";
88
+ readonly description: "Choose the thinking effort";
89
+ readonly group: "Model";
90
+ readonly kind: "session";
91
+ }, {
92
+ readonly name: "thinking";
93
+ readonly args: "[show|hide]";
94
+ readonly description: "Toggle transcript thinking visibility: /thinking [show|hide]";
95
+ readonly group: "Model";
96
+ readonly kind: "local";
97
+ }, {
98
+ readonly name: "name";
99
+ readonly args: "[name]";
100
+ readonly description: "Name this session";
101
+ readonly group: "Session";
102
+ readonly kind: "session";
103
+ readonly methods: readonly ["sessions.rename"];
104
+ }, {
105
+ readonly name: "compact";
106
+ readonly args: "[instructions]";
107
+ readonly description: "Compact the context";
108
+ readonly group: "Session";
109
+ readonly kind: "session";
110
+ readonly methods: readonly ["sessions.run"];
111
+ }, {
112
+ readonly name: "copy";
113
+ readonly description: "Copy the last reply";
114
+ readonly group: "Inspect";
115
+ readonly kind: "local";
116
+ readonly methods: readonly ["sessions.lastReply"];
117
+ }, {
118
+ readonly name: "export";
119
+ readonly args: "[path.html|path.jsonl]";
120
+ readonly description: "Export this branch to HTML or JSONL";
121
+ readonly group: "Inspect";
122
+ readonly kind: "session";
123
+ readonly methods: readonly ["sessions.export", "sessions.run"];
124
+ }, {
125
+ readonly name: "trajectory";
126
+ readonly args: "[run-id]";
127
+ readonly description: "Inspect recorded runs for this session";
128
+ readonly group: "Inspect";
129
+ readonly kind: "page";
130
+ readonly methods: readonly ["runs.list", "runs.events"];
131
+ }, {
132
+ readonly name: "reload";
133
+ readonly description: "Reload trusted resources and keybindings";
134
+ readonly group: "Harness";
135
+ readonly kind: "session";
136
+ readonly methods: readonly ["sessions.reload"];
137
+ }, {
138
+ readonly name: "accounts";
139
+ readonly description: "Sign in, switch, check and remove Pi accounts";
140
+ readonly group: "Harness";
141
+ readonly kind: "page";
142
+ readonly methods: readonly ["accounts.list"];
143
+ }, {
144
+ readonly name: "permissions";
145
+ readonly description: "Full Permission and per-tool approvals";
146
+ readonly group: "Harness";
147
+ readonly kind: "page";
148
+ readonly methods: readonly ["approvals.get"];
149
+ }, {
150
+ readonly name: "harness";
151
+ readonly description: "Browse foundation, variant and all resources";
152
+ readonly group: "Harness";
153
+ readonly kind: "page";
154
+ }, {
155
+ readonly name: "skills";
156
+ readonly description: "View, inspect and use skills; discover more";
157
+ readonly group: "Harness";
158
+ readonly kind: "page";
159
+ }, {
160
+ readonly name: "connections";
161
+ readonly description: "Manage local and remote MCP connections";
162
+ readonly group: "Harness";
163
+ readonly kind: "page";
164
+ }, {
165
+ readonly name: "mcp";
166
+ readonly description: "Open Connections in the Harness browser";
167
+ readonly group: "Harness";
168
+ readonly kind: "page";
169
+ }, {
170
+ readonly name: "tools";
171
+ readonly description: "Inspect available tools, owners and approvals";
172
+ readonly group: "Harness";
173
+ readonly kind: "page";
174
+ }, {
175
+ readonly name: "plugins";
176
+ readonly description: "Switch plugins on or off and trust their code";
177
+ readonly group: "Harness";
178
+ readonly kind: "page";
179
+ readonly methods: readonly ["plugins.inventory"];
180
+ }, {
181
+ readonly name: "hub";
182
+ readonly description: "Browse and install reviewed skills, plugins and documents";
183
+ readonly group: "Harness";
184
+ readonly kind: "page";
185
+ }, {
186
+ readonly name: "components";
187
+ readonly description: "Browse skills, prompts and rules for this Harness scope";
188
+ readonly group: "Harness";
189
+ readonly kind: "page";
190
+ }, {
191
+ readonly name: "settings";
192
+ readonly description: "Session, terminal and browser settings";
193
+ readonly group: "Harness";
194
+ readonly kind: "page";
195
+ }, {
196
+ readonly name: "doctor";
197
+ readonly description: "Check account, model, tools and runtime readiness";
198
+ readonly group: "Harness";
199
+ readonly kind: "page";
200
+ readonly methods: readonly ["doctor.start", "doctor.read"];
201
+ }, {
202
+ readonly name: "worktree";
203
+ readonly description: "Manage repository worktrees";
204
+ readonly group: "Harness";
205
+ readonly kind: "page";
206
+ readonly methods: readonly ["worktrees.list"];
207
+ }, {
208
+ readonly name: "subagents";
209
+ readonly description: "Watch current subagent tasks and activity";
210
+ readonly group: "Harness";
211
+ readonly kind: "page";
212
+ }, {
213
+ readonly name: "agents";
214
+ readonly description: "Browse and edit agent profiles";
215
+ readonly group: "Harness";
216
+ readonly kind: "page";
217
+ readonly methods: readonly ["agents.list"];
218
+ }, {
219
+ readonly name: "log";
220
+ readonly description: "Harness diagnostics and this conversation's run events";
221
+ readonly group: "Inspect";
222
+ readonly kind: "page";
223
+ }, {
224
+ readonly name: "hotkeys";
225
+ readonly description: "Keyboard controls";
226
+ readonly group: "Core";
227
+ readonly kind: "local";
228
+ }, {
229
+ readonly name: "quit";
230
+ readonly description: "Restore the terminal and exit";
231
+ readonly group: "Core";
232
+ readonly kind: "local";
233
+ }];
234
+ export type CoreCommandName = typeof CORE_COMMANDS[number]['name'];
235
+ /** The core command a typed name or alias stands for. */
236
+ export declare function coreCommand(name: string): CoreCommand | undefined;
237
+ /** Every listed command, as `commands.list` returns it. */
238
+ export interface ListedCommand {
239
+ /** What the person types after `/`: `fork`, `annotate:notes`, `skill:review`. */
240
+ name: string;
241
+ aliases?: string[];
242
+ args?: string;
243
+ description?: string;
244
+ group: string;
245
+ source: 'core' | 'plugin' | 'extension' | 'prompt' | 'skill';
246
+ /** Core commands only. */
247
+ kind?: CoreCommandKind;
248
+ /** The owning plugin of a plugin or extension command. */
249
+ owner?: string;
250
+ }
@@ -0,0 +1,47 @@
1
+ // The core slash commands every front end offers. This module is plain data with no imports, so a
2
+ // browser renderer can read it as well as the terminal. A front end must handle every `action`
3
+ // here (INV-PAR-1); `commands.list` adds plugin commands, prompt templates and skills.
4
+ export const CORE_COMMANDS = [
5
+ { name: 'help', description: 'List the commands', group: 'Core', kind: 'local' },
6
+ { name: 'plan', args: '[brief]', description: 'Plan one request, read-only: /plan <brief>', group: 'Run', kind: 'session', methods: ['sessions.run'] },
7
+ { name: 'btw', aliases: ['side'], args: '[question]', description: 'Ask a side question without interrupting: /btw <question>', group: 'Run', kind: 'session', methods: ['sideQuestions.start', 'sideQuestions.cancel'] },
8
+ { name: 'new', description: 'Start a new session', group: 'Session', kind: 'session' },
9
+ { name: 'stop', description: 'Stop the current reply', group: 'Run', kind: 'session', methods: ['runs.interrupt'] },
10
+ { name: 'session', aliases: ['resume'], args: '[path]', description: 'Choose a saved session in this account and folder', group: 'Session', kind: 'session' },
11
+ { name: 'history', aliases: ['tree'], description: 'Navigate branches in the current session', group: 'Session', kind: 'session', methods: ['sessions.tree', 'sessions.navigate'] },
12
+ { name: 'fork', description: 'Fork from an earlier message', group: 'Session', kind: 'session', methods: ['sessions.tree', 'sessions.fork'] },
13
+ { name: 'clone', description: 'Clone this branch into a new session', group: 'Session', kind: 'session', methods: ['sessions.clone'] },
14
+ { name: 'model', args: '[search]', description: 'Choose the model', group: 'Model', kind: 'session', methods: ['accounts.models'] },
15
+ { name: 'effort', args: '[level]', description: 'Choose the thinking effort', group: 'Model', kind: 'session' },
16
+ { name: 'thinking', args: '[show|hide]', description: 'Toggle transcript thinking visibility: /thinking [show|hide]', group: 'Model', kind: 'local' },
17
+ { name: 'name', args: '[name]', description: 'Name this session', group: 'Session', kind: 'session', methods: ['sessions.rename'] },
18
+ { name: 'compact', args: '[instructions]', description: 'Compact the context', group: 'Session', kind: 'session', methods: ['sessions.run'] },
19
+ { name: 'copy', description: 'Copy the last reply', group: 'Inspect', kind: 'local', methods: ['sessions.lastReply'] },
20
+ { name: 'export', args: '[path.html|path.jsonl]', description: 'Export this branch to HTML or JSONL', group: 'Inspect', kind: 'session', methods: ['sessions.export', 'sessions.run'] },
21
+ { name: 'trajectory', args: '[run-id]', description: 'Inspect recorded runs for this session', group: 'Inspect', kind: 'page', methods: ['runs.list', 'runs.events'] },
22
+ { name: 'reload', description: 'Reload trusted resources and keybindings', group: 'Harness', kind: 'session', methods: ['sessions.reload'] },
23
+ { name: 'accounts', description: 'Sign in, switch, check and remove Pi accounts', group: 'Harness', kind: 'page', methods: ['accounts.list'] },
24
+ { name: 'permissions', description: 'Full Permission and per-tool approvals', group: 'Harness', kind: 'page', methods: ['approvals.get'] },
25
+ { name: 'harness', description: 'Browse foundation, variant and all resources', group: 'Harness', kind: 'page' },
26
+ { name: 'skills', description: 'View, inspect and use skills; discover more', group: 'Harness', kind: 'page' },
27
+ { name: 'connections', description: 'Manage local and remote MCP connections', group: 'Harness', kind: 'page' },
28
+ { name: 'mcp', description: 'Open Connections in the Harness browser', group: 'Harness', kind: 'page' },
29
+ { name: 'tools', description: 'Inspect available tools, owners and approvals', group: 'Harness', kind: 'page' },
30
+ { name: 'plugins', description: 'Switch plugins on or off and trust their code', group: 'Harness', kind: 'page', methods: ['plugins.inventory'] },
31
+ { name: 'hub', description: 'Browse and install reviewed skills, plugins and documents', group: 'Harness', kind: 'page' },
32
+ { name: 'components', description: 'Browse skills, prompts and rules for this Harness scope', group: 'Harness', kind: 'page' },
33
+ { name: 'settings', description: 'Session, terminal and browser settings', group: 'Harness', kind: 'page' },
34
+ { name: 'doctor', description: 'Check account, model, tools and runtime readiness', group: 'Harness', kind: 'page', methods: ['doctor.start', 'doctor.read'] },
35
+ { name: 'worktree', description: 'Manage repository worktrees', group: 'Harness', kind: 'page', methods: ['worktrees.list'] },
36
+ { name: 'subagents', description: 'Watch current subagent tasks and activity', group: 'Harness', kind: 'page' },
37
+ { name: 'agents', description: 'Browse and edit agent profiles', group: 'Harness', kind: 'page', methods: ['agents.list'] },
38
+ { name: 'log', description: "Harness diagnostics and this conversation's run events", group: 'Inspect', kind: 'page' },
39
+ { name: 'hotkeys', description: 'Keyboard controls', group: 'Core', kind: 'local' },
40
+ { name: 'quit', description: 'Restore the terminal and exit', group: 'Core', kind: 'local' }
41
+ ];
42
+ const ALIASES = new Map(CORE_COMMANDS.flatMap((command) => ('aliases' in command ? command.aliases : []).map((alias) => [alias, command.name])));
43
+ /** The core command a typed name or alias stands for. */
44
+ export function coreCommand(name) {
45
+ const canonical = ALIASES.get(name) ?? name;
46
+ return CORE_COMMANDS.find((command) => command.name === canonical);
47
+ }
@@ -0,0 +1,16 @@
1
+ /** One observed registration, identified by its package or loose extension owner. */
2
+ export interface CommandRoute {
3
+ prefix: string;
4
+ name: string;
5
+ owner: string;
6
+ path: string;
7
+ }
8
+ /** Listing and dispatch reject the whole prefix when different owners claim it. */
9
+ export declare function commandRoutes<T extends CommandRoute>(commands: readonly T[]): {
10
+ routes: Map<string, T[]>;
11
+ byName: Map<string, T[]>;
12
+ ownersByPrefix: Map<string, Map<string, Set<string>>>;
13
+ conflictingPrefixes: Map<string, string[]>;
14
+ };
15
+ /** Namespace roots and reserved routes cannot be claimed by prompt templates. */
16
+ export declare function promptCommandAvailable(name: string, taken: ReadonlySet<string>, extensionCommands?: ReadonlySet<string>): boolean;
@@ -0,0 +1,33 @@
1
+ import { BUILTIN_PLUGIN_REGISTRY } from "../plugins/builtin.js";
2
+ /** Listing and dispatch reject the whole prefix when different owners claim it. */
3
+ export function commandRoutes(commands) {
4
+ const routes = new Map();
5
+ const byName = new Map();
6
+ const ownersByPrefix = new Map();
7
+ const builtinPrefixes = new Map(BUILTIN_PLUGIN_REGISTRY.commands().map(command => [command.prefix, command.pluginId]));
8
+ for (const command of commands) {
9
+ const owners = ownersByPrefix.get(command.prefix) ?? new Map();
10
+ const paths = owners.get(command.owner) ?? new Set();
11
+ paths.add(command.path);
12
+ owners.set(command.owner, paths);
13
+ ownersByPrefix.set(command.prefix, owners);
14
+ const route = `${command.prefix}:${command.name}`;
15
+ routes.set(route, [...(routes.get(route) ?? []), command]);
16
+ byName.set(command.name, [...(byName.get(command.name) ?? []), command]);
17
+ }
18
+ const conflictingPrefixes = new Map();
19
+ for (const [prefix, owners] of ownersByPrefix) {
20
+ const builtin = builtinPrefixes.get(prefix);
21
+ if (owners.size > 1 || builtin || prefix === 'plugin' || prefix === 'skill') {
22
+ conflictingPrefixes.set(prefix, [
23
+ ...(builtin ? [`built-in plugin ${builtin}`] : []),
24
+ ...[...owners.values()].flatMap(paths => [...paths])
25
+ ]);
26
+ }
27
+ }
28
+ return { routes, byName, ownersByPrefix, conflictingPrefixes };
29
+ }
30
+ /** Namespace roots and reserved routes cannot be claimed by prompt templates. */
31
+ export function promptCommandAvailable(name, taken, extensionCommands = new Set()) {
32
+ return !name.includes(':') && !taken.has(name) && !extensionCommands.has(name);
33
+ }
@@ -0,0 +1,23 @@
1
+ /** What a command gets when the caller says nothing. */
2
+ export declare const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;
3
+ /** Past this, a call must say why it needs longer. */
4
+ export declare const COMMAND_TIMEOUT_CEILING_SECONDS = 300;
5
+ /** The longest a single command may run at all. */
6
+ export declare const COMMAND_TIMEOUT_MAX_SECONDS = 900;
7
+ export type CommandTimeout = {
8
+ ok: true;
9
+ timeout: number;
10
+ } | {
11
+ ok: false;
12
+ message: string;
13
+ };
14
+ /** Resolves a requested timeout against the default, the ceiling and the maximum. */
15
+ export declare function resolveCommandTimeout(requested: unknown, reason: unknown): CommandTimeout;
16
+ /** A command's result when it was stopped by the budget rather than finishing. */
17
+ export declare function commandTimeoutText(seconds: number, output: string): string;
18
+ /**
19
+ * Runs a command with a wall-clock budget. The abort reaches the live shell, so the promise
20
+ * settles instead of leaving the reply blocked; a timeout is reported as a rejection the caller
21
+ * turns into a tool result.
22
+ */
23
+ export declare function withCommandBudget<T>(run: () => Promise<T>, abort: (() => void) | undefined, seconds: number): Promise<T>;
@@ -0,0 +1,57 @@
1
+ // One command budget for every path that runs a shell command (PROP-0041). A blocking command is
2
+ // how a reply spends a quarter of an hour: a default keeps ordinary commands honest, a ceiling
3
+ // makes a long one an explicit choice, and a timeout returns what it saw instead of waiting.
4
+ /** What a command gets when the caller says nothing. */
5
+ export const DEFAULT_COMMAND_TIMEOUT_SECONDS = 120;
6
+ /** Past this, a call must say why it needs longer. */
7
+ export const COMMAND_TIMEOUT_CEILING_SECONDS = 300;
8
+ /** The longest a single command may run at all. */
9
+ export const COMMAND_TIMEOUT_MAX_SECONDS = 900;
10
+ /** Resolves a requested timeout against the default, the ceiling and the maximum. */
11
+ export function resolveCommandTimeout(requested, reason) {
12
+ const why = typeof reason === 'string' ? reason.trim() : '';
13
+ if (typeof requested !== 'number' || !Number.isFinite(requested))
14
+ return { ok: true, timeout: DEFAULT_COMMAND_TIMEOUT_SECONDS };
15
+ const timeout = Math.floor(requested);
16
+ if (timeout < 1)
17
+ return { ok: false, message: 'timeout must be a positive number of seconds.' };
18
+ if (timeout > COMMAND_TIMEOUT_MAX_SECONDS)
19
+ return { ok: false, message: `A command may run for at most ${COMMAND_TIMEOUT_MAX_SECONDS} seconds. Split it into smaller commands.` };
20
+ if (timeout > COMMAND_TIMEOUT_CEILING_SECONDS && !why) {
21
+ return { ok: false, message: `A command over ${COMMAND_TIMEOUT_CEILING_SECONDS} seconds needs a reason: pass reason with why it cannot be narrowed or run in smaller steps.` };
22
+ }
23
+ return { ok: true, timeout };
24
+ }
25
+ /** A command's result when it was stopped by the budget rather than finishing. */
26
+ export function commandTimeoutText(seconds, output) {
27
+ return `The command was stopped after ${seconds} seconds by the command budget. Its output so far:\n${output || '(none)'}\n\nNarrow it: one test file, --grep for the failing case, or a line range, then run the full set at a checkpoint.`;
28
+ }
29
+ /**
30
+ * Runs a command with a wall-clock budget. The abort reaches the live shell, so the promise
31
+ * settles instead of leaving the reply blocked; a timeout is reported as a rejection the caller
32
+ * turns into a tool result.
33
+ */
34
+ export async function withCommandBudget(run, abort, seconds) {
35
+ let timer;
36
+ try {
37
+ return await Promise.race([
38
+ run(),
39
+ new Promise((_resolve, reject) => {
40
+ timer = setTimeout(() => {
41
+ try {
42
+ abort?.();
43
+ }
44
+ catch {
45
+ // The abort is best effort; the timeout still settles the reply.
46
+ }
47
+ reject(new Error(commandTimeoutText(seconds, '')));
48
+ }, seconds * 1000);
49
+ timer.unref?.();
50
+ })
51
+ ]);
52
+ }
53
+ finally {
54
+ if (timer)
55
+ clearTimeout(timer);
56
+ }
57
+ }
@@ -53,7 +53,23 @@ export const MIGRATIONS = [
53
53
  CREATE INDEX runs_agent ON runs (agent_id, started_at);
54
54
  CREATE INDEX runs_started ON runs (started_at);`,
55
55
  // 7: the per-account usage table goes
56
- 'DROP TABLE IF EXISTS usage;'
56
+ 'DROP TABLE IF EXISTS usage;',
57
+ // 8: the usage ledger behind `/usage:analysis`, rebuilt from each run's `usage.updated` events
58
+ `CREATE TABLE usage_requests (run_id TEXT NOT NULL, sequence INTEGER NOT NULL, request_index INTEGER NOT NULL,
59
+ at TEXT NOT NULL, day TEXT NOT NULL, account_id TEXT, project_id TEXT, session_id TEXT NOT NULL,
60
+ agent_id TEXT, mode TEXT NOT NULL, trigger TEXT, provider TEXT, model TEXT,
61
+ input_tokens INTEGER NOT NULL DEFAULT 0, cache_read_tokens INTEGER NOT NULL DEFAULT 0,
62
+ cache_write_tokens INTEGER NOT NULL DEFAULT 0, output_tokens INTEGER NOT NULL DEFAULT 0,
63
+ reasoning_tokens INTEGER NOT NULL DEFAULT 0, cost_usd REAL, cost_source TEXT,
64
+ context_tokens INTEGER, context_window INTEGER, duration_ms INTEGER, ttft_ms INTEGER,
65
+ requests INTEGER NOT NULL DEFAULT 0,
66
+ PRIMARY KEY (run_id, sequence, request_index));
67
+ CREATE INDEX usage_at ON usage_requests (at);
68
+ CREATE INDEX usage_day ON usage_requests (day);
69
+ CREATE INDEX usage_account_day ON usage_requests (account_id, day);
70
+ CREATE INDEX usage_session_day ON usage_requests (session_id, day);
71
+ CREATE INDEX usage_model_day ON usage_requests (model, day);
72
+ CREATE TABLE usage_indexed (run_id TEXT PRIMARY KEY, indexed_at TEXT NOT NULL, rows INTEGER NOT NULL);`
57
73
  ];
58
74
  const connections = new Map();
59
75
  export function dbPath(root) {
@@ -0,0 +1,46 @@
1
+ import { type SecretStore } from "../accounts/secrets.js";
2
+ export type SecretBackendDescriptor = {
3
+ kind: 'file';
4
+ path: string;
5
+ } | {
6
+ kind: 'host';
7
+ id: string;
8
+ };
9
+ export interface InteractiveProfile {
10
+ version: 1;
11
+ dataRoot: string;
12
+ stateDir: string;
13
+ defaultVariantScope: string;
14
+ secretBackend: SecretBackendDescriptor;
15
+ }
16
+ export type InteractiveProfileOverrides = Partial<Omit<InteractiveProfile, 'version'>>;
17
+ export interface InteractiveProfileLocation {
18
+ /** Overrides the home used for the default profile path. */
19
+ home?: string;
20
+ /** Selects a profile file directly, without searching any application-specific locations. */
21
+ configPath?: string;
22
+ }
23
+ /** Strict, bounded, credential-free configuration. Unknown fields are errors, not ignored secrets. */
24
+ export declare function validateInteractiveProfile(value: unknown): InteractiveProfile;
25
+ export declare function interactiveProfilePath(home?: string): string;
26
+ /** Read only. Missing is legacy mode; malformed or inaccessible is never silently ignored. */
27
+ export declare function readInteractiveProfile(location?: InteractiveProfileLocation): InteractiveProfile | null;
28
+ /** Explicit roots > selected profile > caller's legacy defaults. Never creates roots or merges data. */
29
+ export declare function resolveInteractiveProfile(options?: InteractiveProfileLocation & {
30
+ legacyDefaults?: InteractiveProfileOverrides;
31
+ overrides?: InteractiveProfileOverrides;
32
+ }): InteractiveProfile;
33
+ /**
34
+ * Explicit adoption only. Creates the profile file, never roots or credentials; it never replaces
35
+ * an existing profile or merges data. A pre-profile host must supply its selected backend in
36
+ * `legacyDefaults` so an absent profile cannot silently downgrade it to the file store.
37
+ */
38
+ export declare function adoptInteractiveProfile(profile: InteractiveProfile, location?: InteractiveProfileLocation): void;
39
+ /**
40
+ * Every participating process must map this host id to the same credential store. The account
41
+ * lock serializes access but cannot unify distinct native Keychain identities; no file fallback is
42
+ * allowed when a selected host adapter is unavailable.
43
+ */
44
+ export declare function interactiveProfileSecretStore(profile: InteractiveProfile, host?: (descriptor: Extract<SecretBackendDescriptor, {
45
+ kind: 'host';
46
+ }>) => SecretStore | Promise<SecretStore>): Promise<SecretStore>;