@xingular/harness 1.2.0 → 1.3.1-unstable.1

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