@agi-fans/dsh-tui 0.20.0 → 0.22.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 (319) hide show
  1. package/lib/CHANGELOG.md +249 -3
  2. package/lib/chrome/box.d.ts +47 -16
  3. package/lib/chrome/box.js +172 -130
  4. package/lib/chrome/box.js.map +1 -1
  5. package/lib/chrome/code-highlight.d.ts +15 -0
  6. package/lib/chrome/code-highlight.js +60 -4
  7. package/lib/chrome/code-highlight.js.map +1 -1
  8. package/lib/chrome/command-output.d.ts +29 -2
  9. package/lib/chrome/command-output.js +100 -4
  10. package/lib/chrome/command-output.js.map +1 -1
  11. package/lib/chrome/main-screen-renderer.d.ts +18 -0
  12. package/lib/chrome/main-screen-renderer.js +58 -5
  13. package/lib/chrome/main-screen-renderer.js.map +1 -1
  14. package/lib/chrome/markdown.js +30 -10
  15. package/lib/chrome/markdown.js.map +1 -1
  16. package/lib/chrome/relative-time.d.ts +17 -0
  17. package/lib/chrome/relative-time.js +36 -0
  18. package/lib/chrome/relative-time.js.map +1 -1
  19. package/lib/chrome/renderer.d.ts +9 -0
  20. package/lib/chrome/renderer.js +1 -1
  21. package/lib/chrome/renderer.js.map +1 -1
  22. package/lib/chrome/secret-mask.d.ts +35 -0
  23. package/lib/chrome/secret-mask.js +180 -0
  24. package/lib/chrome/secret-mask.js.map +1 -0
  25. package/lib/chrome/status-config.d.ts +161 -51
  26. package/lib/chrome/status-config.js +237 -126
  27. package/lib/chrome/status-config.js.map +1 -1
  28. package/lib/chrome/status-line.d.ts +3 -2
  29. package/lib/chrome/status-line.js +105 -63
  30. package/lib/chrome/status-line.js.map +1 -1
  31. package/lib/chrome/terminal-colors.d.ts +85 -0
  32. package/lib/chrome/terminal-colors.js +230 -0
  33. package/lib/chrome/terminal-colors.js.map +1 -0
  34. package/lib/chrome/terminal-images.d.ts +9 -1
  35. package/lib/chrome/terminal-images.js +27 -4
  36. package/lib/chrome/terminal-images.js.map +1 -1
  37. package/lib/chrome/theme.d.ts +32 -9
  38. package/lib/chrome/theme.js +616 -520
  39. package/lib/chrome/theme.js.map +1 -1
  40. package/lib/chrome/tool-args.d.ts +1 -1
  41. package/lib/chrome/tool-args.js +3 -1
  42. package/lib/chrome/tool-args.js.map +1 -1
  43. package/lib/chrome/tool-renderers.d.ts +2 -0
  44. package/lib/chrome/tool-renderers.js +69 -3
  45. package/lib/chrome/tool-renderers.js.map +1 -1
  46. package/lib/chrome/tools-list.d.ts +1 -4
  47. package/lib/chrome/tools-list.js +1 -41
  48. package/lib/chrome/tools-list.js.map +1 -1
  49. package/lib/chrome/welcome-tips.d.ts +20 -7
  50. package/lib/chrome/welcome-tips.js +22 -41
  51. package/lib/chrome/welcome-tips.js.map +1 -1
  52. package/lib/commands/auth-ids.d.ts +25 -0
  53. package/lib/commands/auth-ids.js +27 -0
  54. package/lib/commands/auth-ids.js.map +1 -0
  55. package/lib/commands/auth-input.d.ts +34 -0
  56. package/lib/commands/auth-input.js +96 -0
  57. package/lib/commands/auth-input.js.map +1 -0
  58. package/lib/commands/auth-providers.d.ts +39 -0
  59. package/lib/commands/auth-providers.js +125 -0
  60. package/lib/commands/auth-providers.js.map +1 -0
  61. package/lib/commands/auth.d.ts +16 -22
  62. package/lib/commands/auth.js +346 -299
  63. package/lib/commands/auth.js.map +1 -1
  64. package/lib/commands/btw.d.ts +27 -0
  65. package/lib/commands/btw.js +59 -0
  66. package/lib/commands/btw.js.map +1 -0
  67. package/lib/commands/diff.js +30 -5
  68. package/lib/commands/diff.js.map +1 -1
  69. package/lib/commands/export.d.ts +1 -1
  70. package/lib/commands/export.js +5 -3
  71. package/lib/commands/export.js.map +1 -1
  72. package/lib/commands/files.d.ts +17 -1
  73. package/lib/commands/files.js +69 -61
  74. package/lib/commands/files.js.map +1 -1
  75. package/lib/commands/integrations.js +18 -6
  76. package/lib/commands/integrations.js.map +1 -1
  77. package/lib/commands/jobs.d.ts +19 -4
  78. package/lib/commands/jobs.js +50 -7
  79. package/lib/commands/jobs.js.map +1 -1
  80. package/lib/commands/model.js +109 -76
  81. package/lib/commands/model.js.map +1 -1
  82. package/lib/commands/permission.js +46 -13
  83. package/lib/commands/permission.js.map +1 -1
  84. package/lib/commands/plugins.js +196 -42
  85. package/lib/commands/plugins.js.map +1 -1
  86. package/lib/commands/schedule.d.ts +29 -0
  87. package/lib/commands/schedule.js +228 -0
  88. package/lib/commands/schedule.js.map +1 -0
  89. package/lib/commands/session-configuration.d.ts +1 -1
  90. package/lib/commands/session-configuration.js +29 -28
  91. package/lib/commands/session-configuration.js.map +1 -1
  92. package/lib/commands/session.js +75 -80
  93. package/lib/commands/session.js.map +1 -1
  94. package/lib/commands/terminal.d.ts +24 -4
  95. package/lib/commands/terminal.js +38 -94
  96. package/lib/commands/terminal.js.map +1 -1
  97. package/lib/commands/trajectory.js +4 -1
  98. package/lib/commands/trajectory.js.map +1 -1
  99. package/lib/definition.d.ts +150 -11
  100. package/lib/index.d.ts +1 -0
  101. package/lib/index.js +1 -0
  102. package/lib/index.js.map +1 -1
  103. package/lib/input/actions.d.ts +51 -0
  104. package/lib/input/actions.js +120 -0
  105. package/lib/input/actions.js.map +1 -0
  106. package/lib/input/editor-discovery.js +2 -2
  107. package/lib/input/editor-discovery.js.map +1 -1
  108. package/lib/input/editor.d.ts +0 -10
  109. package/lib/input/editor.js +11 -9
  110. package/lib/input/editor.js.map +1 -1
  111. package/lib/input/image-paste.d.ts +19 -1
  112. package/lib/input/image-paste.js +82 -1
  113. package/lib/input/image-paste.js.map +1 -1
  114. package/lib/input/keybindings-config.d.ts +15 -4
  115. package/lib/input/keybindings-config.js +57 -35
  116. package/lib/input/keybindings-config.js.map +1 -1
  117. package/lib/input/keys.d.ts +18 -0
  118. package/lib/input/keys.js +48 -3
  119. package/lib/input/keys.js.map +1 -1
  120. package/lib/input/wheel-scroll.d.ts +46 -0
  121. package/lib/input/wheel-scroll.js +69 -0
  122. package/lib/input/wheel-scroll.js.map +1 -0
  123. package/lib/prompt-intents.d.ts +182 -0
  124. package/lib/prompt-intents.js +127 -0
  125. package/lib/prompt-intents.js.map +1 -0
  126. package/lib/runtime/agent-profile.d.ts +5 -0
  127. package/lib/runtime/agent-profile.js +12 -0
  128. package/lib/runtime/agent-profile.js.map +1 -1
  129. package/lib/runtime/approval-rules.d.ts +59 -0
  130. package/lib/runtime/approval-rules.js +179 -0
  131. package/lib/runtime/approval-rules.js.map +1 -0
  132. package/lib/runtime/btw-policy.d.ts +7 -0
  133. package/lib/runtime/btw-policy.js +18 -0
  134. package/lib/runtime/btw-policy.js.map +1 -0
  135. package/lib/runtime/btw.d.ts +47 -0
  136. package/lib/runtime/btw.js +200 -0
  137. package/lib/runtime/btw.js.map +1 -0
  138. package/lib/runtime/cancellable.d.ts +75 -0
  139. package/lib/runtime/cancellable.js +76 -0
  140. package/lib/runtime/cancellable.js.map +1 -0
  141. package/lib/runtime/composer-images.js +5 -4
  142. package/lib/runtime/composer-images.js.map +1 -1
  143. package/lib/runtime/composer-shell.d.ts +30 -0
  144. package/lib/runtime/composer-shell.js +150 -0
  145. package/lib/runtime/composer-shell.js.map +1 -0
  146. package/lib/runtime/file-recovery-local.d.ts +73 -0
  147. package/lib/runtime/file-recovery-local.js +351 -0
  148. package/lib/runtime/file-recovery-local.js.map +1 -0
  149. package/lib/runtime/file-recovery-recording.d.ts +23 -0
  150. package/lib/runtime/file-recovery-recording.js +131 -0
  151. package/lib/runtime/file-recovery-recording.js.map +1 -0
  152. package/lib/runtime/file-recovery-review.d.ts +3 -0
  153. package/lib/runtime/file-recovery-review.js +130 -0
  154. package/lib/runtime/file-recovery-review.js.map +1 -0
  155. package/lib/runtime/file-recovery-store.d.ts +83 -0
  156. package/lib/runtime/file-recovery-store.js +329 -0
  157. package/lib/runtime/file-recovery-store.js.map +1 -0
  158. package/lib/runtime/file-recovery-worker.d.ts +22 -0
  159. package/lib/runtime/file-recovery-worker.js +153 -0
  160. package/lib/runtime/file-recovery-worker.js.map +1 -0
  161. package/lib/runtime/file-review.d.ts +6 -3
  162. package/lib/runtime/file-review.js +44 -7
  163. package/lib/runtime/file-review.js.map +1 -1
  164. package/lib/runtime/herdr-agent.d.ts +4 -13
  165. package/lib/runtime/herdr-agent.js +19 -63
  166. package/lib/runtime/herdr-agent.js.map +1 -1
  167. package/lib/runtime/plain-tui.d.ts +2 -0
  168. package/lib/runtime/plain-tui.js +7 -1
  169. package/lib/runtime/plain-tui.js.map +1 -1
  170. package/lib/runtime/plugin-settings.d.ts +25 -2
  171. package/lib/runtime/plugin-settings.js +329 -31
  172. package/lib/runtime/plugin-settings.js.map +1 -1
  173. package/lib/runtime/provider-local.d.ts +81 -24
  174. package/lib/runtime/provider-local.js +1807 -608
  175. package/lib/runtime/provider-local.js.map +1 -1
  176. package/lib/runtime/runner.js +52 -12
  177. package/lib/runtime/runner.js.map +1 -1
  178. package/lib/runtime/session-archive.d.ts +6 -0
  179. package/lib/runtime/session-archive.js +129 -3
  180. package/lib/runtime/session-archive.js.map +1 -1
  181. package/lib/runtime/session-runtime.js +1 -1
  182. package/lib/runtime/session-runtime.js.map +1 -1
  183. package/lib/runtime/settings-preferences.d.ts +5 -0
  184. package/lib/runtime/settings-preferences.js +34 -0
  185. package/lib/runtime/settings-preferences.js.map +1 -0
  186. package/lib/runtime/startup-notices.js +2 -0
  187. package/lib/runtime/startup-notices.js.map +1 -1
  188. package/lib/runtime/terminal-modes.d.ts +47 -0
  189. package/lib/runtime/terminal-modes.js +86 -0
  190. package/lib/runtime/terminal-modes.js.map +1 -0
  191. package/lib/runtime/terminal-notifications.d.ts +3 -5
  192. package/lib/runtime/terminal-notifications.js +4 -8
  193. package/lib/runtime/terminal-notifications.js.map +1 -1
  194. package/lib/runtime/terminal-probe.d.ts +36 -0
  195. package/lib/runtime/terminal-probe.js +37 -0
  196. package/lib/runtime/terminal-probe.js.map +1 -0
  197. package/lib/runtime/terminal-session.d.ts +57 -0
  198. package/lib/runtime/terminal-session.js +164 -0
  199. package/lib/runtime/terminal-session.js.map +1 -0
  200. package/lib/runtime/terminal-state.d.ts +29 -0
  201. package/lib/runtime/terminal-state.js +62 -0
  202. package/lib/runtime/terminal-state.js.map +1 -0
  203. package/lib/runtime/tool-presentation.js +50 -14
  204. package/lib/runtime/tool-presentation.js.map +1 -1
  205. package/lib/session/external-agents.d.ts +5 -0
  206. package/lib/session/external-agents.js +27 -0
  207. package/lib/session/external-agents.js.map +1 -0
  208. package/lib/session/feature-toggles.d.ts +1 -1
  209. package/lib/session/feature-toggles.js +1 -1
  210. package/lib/session/interaction-adapter.d.ts +1 -6
  211. package/lib/session/interaction-adapter.js +95 -18
  212. package/lib/session/interaction-adapter.js.map +1 -1
  213. package/lib/session/message-queue.js +1 -1
  214. package/lib/session/message-queue.js.map +1 -1
  215. package/lib/session/model-readiness.d.ts +16 -0
  216. package/lib/session/model-readiness.js +43 -0
  217. package/lib/session/model-readiness.js.map +1 -0
  218. package/lib/session/package-metadata.d.ts +8 -0
  219. package/lib/session/package-metadata.js +8 -0
  220. package/lib/session/package-metadata.js.map +1 -1
  221. package/lib/session/project-context.d.ts +5 -0
  222. package/lib/session/project-context.js +36 -0
  223. package/lib/session/project-context.js.map +1 -1
  224. package/lib/session/session-controller.d.ts +10 -13
  225. package/lib/session/session-controller.js +183 -98
  226. package/lib/session/session-controller.js.map +1 -1
  227. package/lib/session/session-library.js +2 -0
  228. package/lib/session/session-library.js.map +1 -1
  229. package/lib/session/session-tree.d.ts +3 -0
  230. package/lib/session/session-tree.js +3 -1
  231. package/lib/session/session-tree.js.map +1 -1
  232. package/lib/session/subagent-roster.d.ts +2 -1
  233. package/lib/session/subagent-roster.js +24 -3
  234. package/lib/session/subagent-roster.js.map +1 -1
  235. package/lib/session/subagent-tracker.d.ts +6 -2
  236. package/lib/session/subagent-tracker.js +49 -2
  237. package/lib/session/subagent-tracker.js.map +1 -1
  238. package/lib/session/tui-settings.d.ts +47 -13
  239. package/lib/session/tui-settings.js +76 -34
  240. package/lib/session/tui-settings.js.map +1 -1
  241. package/lib/session/working-directory.d.ts +4 -0
  242. package/lib/session/working-directory.js +4 -0
  243. package/lib/session/working-directory.js.map +1 -0
  244. package/lib/session/worktrees.d.ts +13 -0
  245. package/lib/session/worktrees.js +49 -0
  246. package/lib/session/worktrees.js.map +1 -0
  247. package/lib/views/approval-rule.d.ts +3 -0
  248. package/lib/views/approval-rule.js +4 -0
  249. package/lib/views/approval-rule.js.map +1 -0
  250. package/lib/views/at-complete.d.ts +8 -0
  251. package/lib/views/at-complete.js +64 -2
  252. package/lib/views/at-complete.js.map +1 -1
  253. package/lib/views/autocomplete.d.ts +22 -2
  254. package/lib/views/autocomplete.js +70 -40
  255. package/lib/views/autocomplete.js.map +1 -1
  256. package/lib/views/copy-targets.d.ts +13 -14
  257. package/lib/views/copy-targets.js +42 -54
  258. package/lib/views/copy-targets.js.map +1 -1
  259. package/lib/views/document-reader.js +18 -5
  260. package/lib/views/document-reader.js.map +1 -1
  261. package/lib/views/event-views.d.ts +1 -1
  262. package/lib/views/event-views.js +60 -6
  263. package/lib/views/event-views.js.map +1 -1
  264. package/lib/views/file-actions.d.ts +12 -0
  265. package/lib/views/file-actions.js +72 -0
  266. package/lib/views/file-actions.js.map +1 -0
  267. package/lib/views/history-search.js +15 -21
  268. package/lib/views/history-search.js.map +1 -1
  269. package/lib/views/hotkeys.d.ts +8 -1
  270. package/lib/views/hotkeys.js +53 -26
  271. package/lib/views/hotkeys.js.map +1 -1
  272. package/lib/views/list-navigation.d.ts +62 -0
  273. package/lib/views/list-navigation.js +73 -0
  274. package/lib/views/list-navigation.js.map +1 -0
  275. package/lib/views/list-view.d.ts +99 -0
  276. package/lib/views/list-view.js +165 -0
  277. package/lib/views/list-view.js.map +1 -0
  278. package/lib/views/prompt-selector.d.ts +34 -4
  279. package/lib/views/prompt-selector.js +270 -134
  280. package/lib/views/prompt-selector.js.map +1 -1
  281. package/lib/views/session-files.d.ts +1 -2
  282. package/lib/views/session-files.js +1 -11
  283. package/lib/views/session-files.js.map +1 -1
  284. package/lib/views/settings-list.d.ts +51 -10
  285. package/lib/views/settings-list.js +267 -315
  286. package/lib/views/settings-list.js.map +1 -1
  287. package/lib/views/trajectory-timeline.d.ts +72 -0
  288. package/lib/views/trajectory-timeline.js +133 -0
  289. package/lib/views/trajectory-timeline.js.map +1 -0
  290. package/lib/views/trajectory.d.ts +15 -4
  291. package/lib/views/trajectory.js +557 -123
  292. package/lib/views/trajectory.js.map +1 -1
  293. package/lib/views/transcript-export.js +9 -0
  294. package/lib/views/transcript-export.js.map +1 -1
  295. package/lib/views/transcript-render.d.ts +97 -25
  296. package/lib/views/transcript-render.js +542 -170
  297. package/lib/views/transcript-render.js.map +1 -1
  298. package/lib/views/transcript-search.d.ts +39 -6
  299. package/lib/views/transcript-search.js +46 -11
  300. package/lib/views/transcript-search.js.map +1 -1
  301. package/lib/views/transcript-types.d.ts +63 -5
  302. package/lib/views/transcript-types.js +23 -11
  303. package/lib/views/transcript-types.js.map +1 -1
  304. package/package.json +95 -65
  305. package/lib/commands/loop.d.ts +0 -52
  306. package/lib/commands/loop.js +0 -307
  307. package/lib/commands/loop.js.map +0 -1
  308. package/lib/commands/steer.d.ts +0 -5
  309. package/lib/commands/steer.js +0 -24
  310. package/lib/commands/steer.js.map +0 -1
  311. package/lib/runtime/terminal-reader.d.ts +0 -11
  312. package/lib/runtime/terminal-reader.js +0 -87
  313. package/lib/runtime/terminal-reader.js.map +0 -1
  314. package/lib/views/agent-hub.d.ts +0 -32
  315. package/lib/views/agent-hub.js +0 -206
  316. package/lib/views/agent-hub.js.map +0 -1
  317. package/lib/views/copy-selector.d.ts +0 -51
  318. package/lib/views/copy-selector.js +0 -139
  319. package/lib/views/copy-selector.js.map +0 -1
@@ -11,12 +11,10 @@ import { steerAgent } from "./steering.js";
11
11
  import { openMessageQueue } from "./message-queue.js";
12
12
  import { WorkflowProjection } from "./workflow-projection.js";
13
13
  import { join } from 'node:path';
14
- import { installModelSelection, } from '@deepseek-ai/dsh-agent';
15
14
  import { createUserMessage, } from '@deepseek-ai/dsh-llm';
16
15
  import { isUserInvocable } from '@deepseek-ai/dsh-skill';
17
- import { SessionAlreadyOwnedError } from '@deepseek-ai/dsh-session-persistence';
18
16
  import { readColdSessionLog } from '@deepseek-ai/dsh-session-query';
19
- import { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
17
+ import { appendPluginRecord, pluginRecordOf, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
20
18
  import { queueHostSubagentPrompt } from '@deepseek-ai/dsh-subagent/internal';
21
19
  import { firstVisibleStreamTime } from "../views/stream-time.js";
22
20
  import { LiveAttemptTracker } from "./live-attempt-tracker.js";
@@ -27,22 +25,18 @@ export { conversationTurns } from "./conversation-turns.js";
27
25
  import { jobNoticeFor } from "./job-notice.js";
28
26
  import { descendantDepth, isSteerableSubagent } from "./subagent-roster.js";
29
27
  import { readRecentRows, writeRecentRows, readSessionLibrary, sessionLibraryPath, setSessionLabel } from "./session-library.js";
30
- import { readJsonFile, writeJsonAtomic } from "./json-file.js";
28
+ import { currentWorkingDirectory } from "./working-directory.js";
31
29
  import { SubagentTracker } from "./subagent-tracker.js";
32
30
  import { commandPermission } from "../commands/permission.js";
31
+ import { configureAgentContext } from "../runtime/agent-profile.js";
32
+ import { BTW_PRESET } from "../runtime/btw-policy.js";
33
+ import { reviewFileRecovery } from "../runtime/file-recovery-review.js";
33
34
  import { isBlankSession, } from "./session-configuration.js";
34
35
  import { stripComposerImageMarkers } from "../input/image-paste.js";
35
36
  async function setupAgentContext(agentCtx, agent, selection) {
36
- installModelSelection(agentCtx, selection);
37
- const agentPresets = agentCtx.get('agentPresets');
38
- const sessionProjections = agentCtx.get('sessionProjections');
39
- if (agentPresets === undefined || sessionProjections === undefined) {
40
- throw new Error('agent configuration services are unavailable');
41
- }
42
- const agentPreset = sessionProjections.stateOf(agent.session, 'agentPreset') ?? agentPresets.defaultId;
43
- const mounted = await agentPresets.mount(agentCtx, agentPreset);
37
+ const configured = await configureAgentContext(agentCtx, agent, selection);
44
38
  await agentCtx.plugin(commandPermission(agent));
45
- return { agentPreset: mounted.id };
39
+ return configured;
46
40
  }
47
41
  function parseControl(line) {
48
42
  const match = /^\/([a-z][a-z0-9_-]*(?::[a-z0-9][a-z0-9_-]*)?)(?:\s+(.*))?$/su.exec(line.trim());
@@ -317,11 +311,38 @@ export async function createSubmissionMessage(submission, attachments) {
317
311
  const refs = inputs.length === 0 || attachments === undefined
318
312
  ? []
319
313
  : [...await attachments.saveImages(inputs)];
320
- const content = [
321
- ...(submission.text === '' ? [] : [{ type: 'text', text: submission.text }]),
322
- ...refs.map(attachment => ({ type: 'image', attachment })),
323
- ...(submission.files ?? []).map(attachment => ({ type: 'file', attachment })),
324
- ];
314
+ // A recorded order keeps the picture where the reader put it; without one the
315
+ // message falls back to text followed by its attachments.
316
+ const content = [];
317
+ if (submission.segments === undefined || submission.segments.length === 0) {
318
+ if (submission.text !== '')
319
+ content.push({ type: 'text', text: submission.text });
320
+ for (const attachment of refs)
321
+ content.push({ type: 'image', attachment });
322
+ for (const attachment of submission.files ?? [])
323
+ content.push({ type: 'file', attachment });
324
+ }
325
+ else {
326
+ for (const segment of submission.segments) {
327
+ if (segment.kind === 'text') {
328
+ if (segment.text !== '')
329
+ content.push({ type: 'text', text: segment.text });
330
+ continue;
331
+ }
332
+ const attachment = refs[segment.index];
333
+ if (attachment !== undefined)
334
+ content.push({ type: 'image', attachment });
335
+ }
336
+ // An attachment the composer did not mark in the text still belongs to the
337
+ // message: keep it rather than dropping it with the marker.
338
+ const placedImages = new Set(submission.segments.flatMap(segment => segment.kind === 'image' ? [segment.index] : []));
339
+ for (const [index, attachment] of refs.entries()) {
340
+ if (!placedImages.has(index))
341
+ content.push({ type: 'image', attachment });
342
+ }
343
+ for (const attachment of submission.files ?? [])
344
+ content.push({ type: 'file', attachment });
345
+ }
325
346
  if (content.length === 0)
326
347
  throw new Error('Cannot submit an empty message.');
327
348
  return createUserMessage({ content, source: { kind: 'user' } });
@@ -368,6 +389,14 @@ export class SessionRuntime {
368
389
  #recent = [];
369
390
  #archivedSessions = new Set();
370
391
  #skillCommands = [];
392
+ /**
393
+ * Live argument vocabularies, keyed by command.
394
+ *
395
+ * A snapshot rather than a callback into the services: completion runs on every
396
+ * keystroke and must not await a catalog. It is rebuilt whenever the commands
397
+ * are pushed, which is also when a model or provider list can have changed.
398
+ */
399
+ #argumentOptions = new Map();
371
400
  #started = false;
372
401
  #retired = [];
373
402
  // Weak: deduplication must not retain disposed session graphs.
@@ -380,7 +409,6 @@ export class SessionRuntime {
380
409
  #recentCache = new Map();
381
410
  #libraryPath;
382
411
  #recentRowsPath;
383
- #upgradeMarkerPath;
384
412
  #recentRowsLoaded = false;
385
413
  #refreshInFlight = null;
386
414
  #refreshDirty = false;
@@ -392,7 +420,6 @@ export class SessionRuntime {
392
420
  this.#tui = tui;
393
421
  this.#libraryPath = sessionLibraryPath(options.stateDir);
394
422
  this.#recentRowsPath = options.stateDir === undefined ? undefined : join(options.stateDir, 'recent-sessions.json');
395
- this.#upgradeMarkerPath = options.stateDir === undefined ? undefined : join(options.stateDir, 'sessions-upgraded.json');
396
423
  this.#tracker = new SubagentTracker({
397
424
  rootId: () => this.#active?.handle.agent.id,
398
425
  depth: (session) => {
@@ -424,7 +451,7 @@ export class SessionRuntime {
424
451
  const fileReferences = this.#ctx.get('fileReferences');
425
452
  if (fileReferences !== undefined) {
426
453
  tui.setFileSearch(async (query, signal) => {
427
- const agent = this.#active?.handle.agent;
454
+ const agent = this.#inspectedId === undefined ? this.#active?.handle.agent : this.#ctx.get('agents')?.get(SessionId(this.#inspectedId));
428
455
  if (agent === undefined)
429
456
  return [];
430
457
  return fileReferences.list(agent, query, signal ?? new AbortController().signal);
@@ -471,6 +498,9 @@ export class SessionRuntime {
471
498
  this.#off.push(ctx.on('session/created', (session) => {
472
499
  this.#tracker.noteSession(session);
473
500
  }));
501
+ this.#off.push(ctx.on('subagent/start', info => { this.#tracker.noteExternalRun(info); }));
502
+ this.#off.push(ctx.on('subagent/end', info => { this.#tracker.noteExternalRun(info); }));
503
+ this.#off.push(ctx.on('subagent/external-activity', info => { this.#tracker.noteExternalActivity(info.id, info.activity); }));
474
504
  this.#off.push(ctx.on('session/disposed', (session) => {
475
505
  this.#tracker.noteGone(session);
476
506
  }));
@@ -478,6 +508,13 @@ export class SessionRuntime {
478
508
  const active = this.#active;
479
509
  if (active === undefined)
480
510
  return;
511
+ if (event.type === 'working-directory/change') {
512
+ if (session.id === (this.#inspectedId ?? active.handle.agent.id)) {
513
+ tui.setWorkingDirectory?.(currentWorkingDirectory(ctx, session));
514
+ }
515
+ if (session === active.handle.agent.session)
516
+ void this.#refreshSkills().catch(error => { ctx.logger.warn(error); });
517
+ }
481
518
  if (session.id === this.#inspectedId) {
482
519
  const child = ctx.get('agents')?.get(session.id);
483
520
  tui.event(event, child === undefined ? undefined : ctx.get('tuiToolPresentation')?.event(child, event));
@@ -485,6 +522,8 @@ export class SessionRuntime {
485
522
  return;
486
523
  }
487
524
  if (session === active.handle.agent.session) {
525
+ if (event.type === 'turn/end')
526
+ tui.setStatus(active.handle.agent.status, { root: true, outcome: event.data.reason.kind });
488
527
  if (this.#workflows.apply(event))
489
528
  tui.setSubagents(this.#workflows.roster(this.#tracker.snapshot()));
490
529
  if (this.#inspectedId === undefined) {
@@ -545,13 +584,6 @@ export class SessionRuntime {
545
584
  this.#pushSessionInfo();
546
585
  }));
547
586
  }
548
- // One-time per session format, off the first render: stored logs an earlier
549
- // release wrote get their current-format generation here rather than on the
550
- // next listing or search that has to decode them.
551
- ctx.inject(['sessionPersistence'], (persistenceCtx) => {
552
- void this.#upgradeStoredSessions(persistenceCtx.sessionPersistence)
553
- .catch((error) => { persistenceCtx.logger.warn(error); });
554
- });
555
587
  }
556
588
  get agent() {
557
589
  return this.#active?.handle.agent;
@@ -685,11 +717,11 @@ export class SessionRuntime {
685
717
  active.contextWindow = resolved?.context?.contextWindow;
686
718
  const status = modelStatus(selection, resolved);
687
719
  active.reasoningEffort = status.reasoningEffort;
688
- this.#tui.setModel(status.model, status.reasoningEffort);
689
- this.#pushSessionInfo();
690
720
  if (options?.persist !== false) {
691
721
  await this.#ctx.get('agentDefaultModel')?.saveSelection(selection);
692
722
  }
723
+ this.#tui.setModel(status.model, status.reasoningEffort);
724
+ this.#pushSessionInfo();
693
725
  }
694
726
  /** Start a new top-level session with the current model selection. */
695
727
  async newSession(agent) {
@@ -806,7 +838,7 @@ export class SessionRuntime {
806
838
  return {
807
839
  label: label === undefined ? node.label : `◆ ${label} · ${node.label}`,
808
840
  value: node.id, ...(node.parentId === undefined ? {} : { parentValue: node.parentId }),
809
- preview: node.preview, description: node.description,
841
+ preview: node.preview, previewFormat: node.previewFormat, description: node.description,
810
842
  submitLabel: node.kind === 'turn' ? 'edit from here' : 'continue branch',
811
843
  ...(node.current ? { badge: { label: 'Current', tone: 'success' } } : {}),
812
844
  };
@@ -816,8 +848,9 @@ export class SessionRuntime {
816
848
  { key: 'Alt+L', label: 'label', valuePrefix: 'label:' },
817
849
  { key: 'Alt+U', label: 'clear label', valuePrefix: 'unlabel:' },
818
850
  { key: 'Alt+B', label: markedOnly ? 'full tree' : 'marked nodes', valuePrefix: 'marked:', scope: 'list' },
851
+ ...(this.#ctx.get('omdshFileRecovery') === undefined ? [] : [{ key: 'Alt+R', label: 'restore Turn files', valuePrefix: 'restore:' }]),
819
852
  ],
820
- ...(selectedId === undefined ? {} : { initialValue: selectedId }), presentation: 'fullscreen-tree', filterable: true, allowCustom: false, signal,
853
+ ...(selectedId === undefined ? {} : { initialValue: selectedId }), presentation: 'tree', filterable: true, allowCustom: false, signal,
821
854
  });
822
855
  if (answer === null)
823
856
  return;
@@ -829,6 +862,17 @@ export class SessionRuntime {
829
862
  markedOnly = !markedOnly;
830
863
  continue;
831
864
  }
865
+ if (answer.startsWith('restore:')) {
866
+ const restoreId = answer.slice('restore:'.length);
867
+ const node = visible.find(item => item.id === restoreId);
868
+ if (node?.kind !== 'turn' || node.turn === undefined) {
869
+ this.#tui.notice('Select a Turn to restore its files.');
870
+ continue;
871
+ }
872
+ selectedId = node.id;
873
+ await reviewFileRecovery(this.#ctx, agent, node.sessionId, node.turn, signal, () => this.assertActive(agent));
874
+ continue;
875
+ }
832
876
  const clear = answer.startsWith('unlabel:');
833
877
  if (clear || answer.startsWith('label:')) {
834
878
  const id = answer.slice(clear ? 'unlabel:'.length : 'label:'.length);
@@ -846,7 +890,7 @@ export class SessionRuntime {
846
890
  const label = await this.#tui.prompt({
847
891
  title: 'Label Conversation Node', question: `Label for ${node.label}`,
848
892
  initialInput: draft, detail: error ?? 'Up to 120 characters. Alt+U in the tree clears a label.',
849
- allowCustom: true, notify: false, signal,
893
+ signal,
850
894
  });
851
895
  if (label === null)
852
896
  break;
@@ -982,7 +1026,7 @@ export class SessionRuntime {
982
1026
  }
983
1027
  /** Agent presets available from the live Harness roster. */
984
1028
  async agentPresets() {
985
- return (await this.#ctx.agentPresets.list()).sort((left, right) => (left.order ?? Number.MAX_SAFE_INTEGER) - (right.order ?? Number.MAX_SAFE_INTEGER)
1029
+ return (await this.#ctx.agentPresets.list()).filter(preset => preset.id !== BTW_PRESET).sort((left, right) => (left.order ?? Number.MAX_SAFE_INTEGER) - (right.order ?? Number.MAX_SAFE_INTEGER)
986
1030
  || left.id.localeCompare(right.id));
987
1031
  }
988
1032
  /** Recompose one blank session, including the preset's fixed tool exposure. */
@@ -1004,16 +1048,6 @@ export class SessionRuntime {
1004
1048
  this.#pushSessionInfo();
1005
1049
  return active.agentPreset;
1006
1050
  }
1007
- /** Select the Harness Plan Mode controller and immediately refresh terminal state. */
1008
- changeWorkflow(agent, active) {
1009
- this.assertActive(agent);
1010
- const planMode = this.#ctx.get('planMode');
1011
- if (planMode === undefined)
1012
- throw new Error('Plan workflow is not configured.');
1013
- const outcome = planMode.set(agent, active);
1014
- this.#pushSessionInfo();
1015
- return outcome;
1016
- }
1017
1051
  /**
1018
1052
  * Resolve and attach the changed-file summary a `workspace/changes` event
1019
1053
  * announces.
@@ -1038,53 +1072,57 @@ export class SessionRuntime {
1038
1072
  const { seq, data } = event;
1039
1073
  queueMicrotask(() => {
1040
1074
  const summary = service.summary(id, seq);
1041
- if (summary !== undefined)
1042
- tui.setWorkspaceSummary(data.turn, summary);
1075
+ if (summary === undefined)
1076
+ return;
1077
+ tui.setWorkspaceSummary(data.turn, summary);
1078
+ const recorded = session.snapshotEvents().some(candidate => {
1079
+ const record = pluginRecordOf(candidate);
1080
+ return record?.type === 'plugin:omdsh/workspace-changes'
1081
+ && typeof record.data === 'object'
1082
+ && record.data !== null
1083
+ && record.data.turn === data.turn;
1084
+ });
1085
+ if (!recorded) {
1086
+ try {
1087
+ appendPluginRecord(session, 'plugin:omdsh/workspace-changes', { turn: data.turn, summary });
1088
+ }
1089
+ catch {
1090
+ // A read-only or closed session log still keeps the in-memory summary.
1091
+ }
1092
+ }
1043
1093
  });
1044
1094
  }
1045
1095
  /**
1046
- * Publish a current-format generation for every stored session an earlier
1047
- * release wrote. A read open never upgrades a log, so without this pass
1048
- * every listing and search keeps decoding the historical corpus — the pass
1049
- * runs once per session format, in the background, and skips a session
1050
- * another live process owns.
1096
+ * Rebuild the argument vocabularies the composer completes.
1097
+ *
1098
+ * A snapshot, because completion runs per keystroke: `/model provider/model`,
1099
+ * `/resume <id>` and `/login <provider>` are all names that already exist in
1100
+ * this process, so the reader types toward them instead of opening a picker.
1051
1101
  */
1052
- async #upgradeStoredSessions(persistence) {
1053
- const markerPath = this.#upgradeMarkerPath;
1054
- if (markerPath === undefined)
1055
- return;
1056
- const marker = readJsonFile(markerPath);
1057
- if (typeof marker?.sessionFormat === 'number' && marker.sessionFormat >= SESSION_FORMAT_VERSION)
1058
- return;
1059
- const before = await persistence.list();
1060
- const revisions = new Map(before.map(snapshot => [String(snapshot.header.id), snapshot.revision]));
1061
- const active = this.#active?.handle.agent.id;
1062
- for (const snapshot of before) {
1063
- if (this.#disposed)
1064
- return;
1065
- if (String(snapshot.header.id) === active)
1066
- continue;
1067
- try {
1068
- const handle = await persistence.open(snapshot.header.id, 'write');
1069
- await handle.close();
1070
- }
1071
- catch (error) {
1072
- // Another process owns it, or the log is unreadable: leave it alone.
1073
- if (!(error instanceof SessionAlreadyOwnedError))
1074
- this.#ctx.logger.debug(error);
1102
+ async #refreshArgumentOptions() {
1103
+ const options = new Map();
1104
+ const llm = this.#ctx.get('llm');
1105
+ if (llm !== undefined) {
1106
+ const models = [];
1107
+ const providers = [];
1108
+ for (const provider of llm.listProviders()) {
1109
+ providers.push({ value: provider.id, ...(provider.name === undefined ? {} : { description: provider.name }) });
1110
+ for (const model of await llm.listModels(provider.id)) {
1111
+ models.push({
1112
+ value: `${provider.id}/${model.id}`,
1113
+ ...(model.name === undefined ? {} : { description: model.name }),
1114
+ });
1115
+ }
1075
1116
  }
1117
+ options.set('model', models);
1118
+ options.set('login', providers);
1076
1119
  }
1077
- if (this.#disposed)
1078
- return;
1079
- let upgraded = 0;
1080
- for (const snapshot of await persistence.list()) {
1081
- if (revisions.get(String(snapshot.header.id)) !== snapshot.revision)
1082
- upgraded += 1;
1083
- }
1084
- writeJsonAtomic(markerPath, { sessionFormat: SESSION_FORMAT_VERSION });
1085
- if (upgraded > 0) {
1086
- this.#tui.notice(`Upgraded ${upgraded} stored session${upgraded === 1 ? '' : 's'} to the current format.`);
1087
- }
1120
+ options.set('resume', this.#recent.map(row => ({
1121
+ value: row.id,
1122
+ ...(row.title === undefined ? {} : { description: row.title }),
1123
+ })));
1124
+ this.#argumentOptions = options;
1125
+ this.#pushCommands();
1088
1126
  }
1089
1127
  async refreshRecent() {
1090
1128
  // Coalesce concurrent callers: the recent list is one snapshot, and a
@@ -1108,6 +1146,9 @@ export class SessionRuntime {
1108
1146
  }
1109
1147
  async #refreshRecentNow() {
1110
1148
  this.#archivedSessions = new Set(readSessionLibrary(this.#libraryPath).archived);
1149
+ // The recent list is what `/resume` completes from, so a refresh of one is a
1150
+ // refresh of the other. Deferred to avoid recursing through #pushCommands.
1151
+ queueMicrotask(() => { void this.#refreshArgumentOptions().catch(() => { }); });
1111
1152
  const persistence = this.#ctx.get('sessionPersistence');
1112
1153
  if (persistence === undefined) {
1113
1154
  this.#recent = [];
@@ -1136,9 +1177,15 @@ export class SessionRuntime {
1136
1177
  for (const snapshot of snapshots) {
1137
1178
  const header = snapshot.header;
1138
1179
  const cached = this.#recentCache.get(header.id);
1180
+ if (snapshot.formatStatus === 'migration-required') {
1181
+ // Do not open the log merely to browse it. A previous label is a hint;
1182
+ // the stored format classification always comes from this listing.
1183
+ rows.push({ ...(cached?.row ?? { id: header.id, title: header.id, createdAt: header.createdAt }), formatStatus: 'migration-required' });
1184
+ continue;
1185
+ }
1139
1186
  if (cached?.revision === snapshot.revision) {
1140
1187
  if (cached.row !== null)
1141
- rows.push(cached.row);
1188
+ rows.push({ ...cached.row, ...(snapshot.formatStatus === undefined ? {} : { formatStatus: snapshot.formatStatus }) });
1142
1189
  continue;
1143
1190
  }
1144
1191
  try {
@@ -1194,7 +1241,7 @@ export class SessionRuntime {
1194
1241
  // Own every handle here so a late activation cannot double-release it.
1195
1242
  for (const handle of handles)
1196
1243
  this.#releasedHandles.add(handle);
1197
- await Promise.allSettled(handles.map(handle => handle.dispose()));
1244
+ await this.#disposeHandles(handles);
1198
1245
  }
1199
1246
  async #create(selection) {
1200
1247
  const ref = { current: selection, assembled: undefined };
@@ -1330,15 +1377,15 @@ export class SessionRuntime {
1330
1377
  this.#tui.setStatus(agent.status);
1331
1378
  this.#tracker.sync();
1332
1379
  this.#tui.setSubagents(this.#workflows.roster(this.#tracker.snapshot()));
1380
+ const selected = this.selection(agent);
1381
+ const status = modelStatus(selected, undefined);
1382
+ this.#tui.setModel(status.model, status.reasoningEffort ?? active.reasoningEffort);
1333
1383
  // Seed welcome metadata before replaceSession commits the startup header to
1334
1384
  // native scrollback; later updates cannot rewrite that frozen first frame.
1335
1385
  this.#pushSessionInfo();
1336
1386
  this.#replaceTranscript(agent, reason);
1337
1387
  this.#pushTools();
1338
1388
  this.#pushCommands();
1339
- const selected = this.selection(agent);
1340
- const status = modelStatus(selected, undefined);
1341
- this.#tui.setModel(status.model, status.reasoningEffort ?? active.reasoningEffort);
1342
1389
  }
1343
1390
  /** Retire one agent handle once; a raced dispose takes it directly. */
1344
1391
  #releaseHandle(handle) {
@@ -1354,11 +1401,15 @@ export class SessionRuntime {
1354
1401
  #pushCommands() {
1355
1402
  const agent = this.agent;
1356
1403
  const runtime = agent === undefined ? [] : (this.#ctx.get('commands')?.list(agent) ?? []);
1357
- const commands = runtime.map(command => ({
1358
- name: command.name,
1359
- description: command.description,
1360
- ...(command.input?.hint === undefined ? {} : { inputHint: command.input.hint }),
1361
- }));
1404
+ const commands = runtime.map(command => {
1405
+ const options = this.#argumentOptions.get(command.name);
1406
+ return {
1407
+ name: command.name,
1408
+ description: command.description,
1409
+ ...(command.input?.hint === undefined ? {} : { inputHint: command.input.hint }),
1410
+ ...(options === undefined ? {} : { complete: (prefix) => matchArgumentOptions(options, prefix) }),
1411
+ };
1412
+ });
1362
1413
  const names = new Set(commands.map(command => command.name));
1363
1414
  for (const skill of this.#skillCommands) {
1364
1415
  if (names.has(skill.name))
@@ -1385,7 +1436,10 @@ export class SessionRuntime {
1385
1436
  this.#skillCommands = [];
1386
1437
  }
1387
1438
  else {
1388
- const list = await skills.list({ cwd: agent.session.header.cwd, scope: agent, signal });
1439
+ const cwd = currentWorkingDirectory(this.#ctx, agent.session);
1440
+ const list = await skills.list({ cwd, scope: agent, signal });
1441
+ if (this.#disposed || this.agent !== agent || currentWorkingDirectory(this.#ctx, agent.session) !== cwd)
1442
+ return;
1389
1443
  this.#skillCommands = userSkillCommands(list);
1390
1444
  }
1391
1445
  this.#pushCommands();
@@ -1395,7 +1449,7 @@ export class SessionRuntime {
1395
1449
  const skills = this.#ctx.get('skills');
1396
1450
  if (skills === undefined)
1397
1451
  return undefined;
1398
- const list = await skills.list({ cwd: agent.session.header.cwd, scope: agent, signal });
1452
+ const list = await skills.list({ cwd: currentWorkingDirectory(this.#ctx, agent.session), scope: agent, signal });
1399
1453
  return list.find(skill => skill.name === name && isUserInvocable(skill));
1400
1454
  }
1401
1455
  #lookupSession = (id) => this.#ctx.get('sessions')?.get(SessionId(id));
@@ -1456,6 +1510,7 @@ export class SessionRuntime {
1456
1510
  });
1457
1511
  }
1458
1512
  #replaceTranscript(agent, reason = 'refresh') {
1513
+ this.#tui.setWorkingDirectory?.(currentWorkingDirectory(this.#ctx, agent.session));
1459
1514
  const events = agent.session.snapshotEvents();
1460
1515
  this.#tracker.observeCatalog(agent.session, events);
1461
1516
  this.#tui.replaceSession(events, this.#ctx.get('tuiToolPresentation')?.session(agent, events), agent.status, reason);
@@ -1489,7 +1544,12 @@ export class SessionRuntime {
1489
1544
  this.#tui.notice('That subagent is no longer available.', { level: 'error' });
1490
1545
  return;
1491
1546
  }
1547
+ if (this.#tracker.snapshot()?.agents.find(agent => agent.id === id)?.mode === 'external') {
1548
+ this.#tui.notice('External executions have no local transcript. Their results appear in the parent session.');
1549
+ return;
1550
+ }
1492
1551
  const live = this.#ctx.get('sessions')?.get(SessionId(id));
1552
+ let cwd = live === undefined ? undefined : currentWorkingDirectory(this.#ctx, live);
1493
1553
  let events;
1494
1554
  if (live !== undefined) {
1495
1555
  events = live.ownEvents();
@@ -1501,6 +1561,8 @@ export class SessionRuntime {
1501
1561
  if (log === undefined)
1502
1562
  throw new Error('subagent transcript is unavailable');
1503
1563
  events = log.events.slice(log.inheritedEventCount);
1564
+ const change = log.events.findLast(event => event.type === 'working-directory/change');
1565
+ cwd = change?.type === 'working-directory/change' ? change.data.cwd : log.header.cwd;
1504
1566
  }
1505
1567
  catch {
1506
1568
  if (request === this.#inspectEpoch) {
@@ -1512,6 +1574,8 @@ export class SessionRuntime {
1512
1574
  if (request !== this.#inspectEpoch)
1513
1575
  return;
1514
1576
  this.#inspectedId = id;
1577
+ if (cwd !== undefined)
1578
+ this.#tui.setWorkingDirectory?.(cwd);
1515
1579
  const child = this.#ctx.get('agents')?.get(SessionId(id));
1516
1580
  this.#tui.replaceSession(events, child === undefined ? undefined : this.#ctx.get('tuiToolPresentation')?.session(child, events), child?.status ?? 'idle', 'refresh');
1517
1581
  this.#replayLivePrefix(id);
@@ -1586,7 +1650,28 @@ export class SessionRuntime {
1586
1650
  return this.#requiredActive().handle.agent;
1587
1651
  }
1588
1652
  async #disposeRetired() {
1589
- await Promise.allSettled(this.#retired.splice(0).map(handle => handle.dispose()));
1653
+ await this.#disposeHandles(this.#retired.splice(0));
1654
+ }
1655
+ async #disposeHandles(handles) {
1656
+ if (handles.length === 0)
1657
+ return;
1658
+ try {
1659
+ await this.#ctx.get('subagents')?.drainDescendants(handles.map(handle => handle.agent));
1660
+ }
1661
+ catch (error) {
1662
+ this.#ctx.logger.warn(error);
1663
+ }
1664
+ finally {
1665
+ await Promise.allSettled(handles.map(handle => handle.dispose()));
1666
+ }
1590
1667
  }
1591
1668
  }
1669
+ /** Candidates for an argument prefix, matched the way the command list matches. */
1670
+ export function matchArgumentOptions(options, prefix) {
1671
+ const lower = prefix.trim().toLowerCase();
1672
+ if (lower === '')
1673
+ return options;
1674
+ return options.filter(option => option.value.toLowerCase().includes(lower)
1675
+ || (option.description ?? '').toLowerCase().includes(lower));
1676
+ }
1592
1677
  //# sourceMappingURL=session-controller.js.map