open-multi-agent-kit 0.96.1 → 0.97.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 (267) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +14 -2
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +1 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/commands/resource-doctor-cli.d.ts +22 -0
  7. package/dist/commands/resource-doctor-cli.d.ts.map +1 -0
  8. package/dist/commands/resource-doctor-cli.js +98 -0
  9. package/dist/commands/resource-doctor-cli.js.map +1 -0
  10. package/dist/core/agent-session.d.ts +46 -2
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +373 -12
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/attachment-store.d.ts +39 -0
  15. package/dist/core/attachment-store.d.ts.map +1 -0
  16. package/dist/core/attachment-store.js +81 -0
  17. package/dist/core/attachment-store.js.map +1 -0
  18. package/dist/core/command-safety.d.ts +8 -0
  19. package/dist/core/command-safety.d.ts.map +1 -1
  20. package/dist/core/command-safety.js +140 -14
  21. package/dist/core/command-safety.js.map +1 -1
  22. package/dist/core/compaction/compaction.d.ts +6 -1
  23. package/dist/core/compaction/compaction.d.ts.map +1 -1
  24. package/dist/core/compaction/compaction.js +35 -1
  25. package/dist/core/compaction/compaction.js.map +1 -1
  26. package/dist/core/completion-sound.d.ts +109 -0
  27. package/dist/core/completion-sound.d.ts.map +1 -0
  28. package/dist/core/completion-sound.js +165 -0
  29. package/dist/core/completion-sound.js.map +1 -0
  30. package/dist/core/context-budget-governor-v2.d.ts +1 -1
  31. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  32. package/dist/core/context-budget-governor-v2.js +1 -1
  33. package/dist/core/context-budget-governor-v2.js.map +1 -1
  34. package/dist/core/context-budget-system-prompt-items.d.ts.map +1 -1
  35. package/dist/core/context-budget-system-prompt-items.js +2 -2
  36. package/dist/core/context-budget-system-prompt-items.js.map +1 -1
  37. package/dist/core/context-budget-v2-cache-keys.d.ts +3 -0
  38. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  39. package/dist/core/context-budget-v2-cache-keys.js +2 -0
  40. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  41. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  42. package/dist/core/context-budget-v2-plan-cache-keys.js +3 -0
  43. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  44. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  45. package/dist/core/context-budget-v2-planner.js +2 -1
  46. package/dist/core/context-budget-v2-planner.js.map +1 -1
  47. package/dist/core/context-budget-v2-scoring.d.ts +17 -0
  48. package/dist/core/context-budget-v2-scoring.d.ts.map +1 -1
  49. package/dist/core/context-budget-v2-scoring.js +25 -8
  50. package/dist/core/context-budget-v2-scoring.js.map +1 -1
  51. package/dist/core/context-budget-v2-selection.d.ts +2 -2
  52. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  53. package/dist/core/context-budget-v2-selection.js +18 -1
  54. package/dist/core/context-budget-v2-selection.js.map +1 -1
  55. package/dist/core/context-budget-v2-types.d.ts +17 -0
  56. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  57. package/dist/core/context-budget-v2-types.js +15 -0
  58. package/dist/core/context-budget-v2-types.js.map +1 -1
  59. package/dist/core/extensions/builtin/command-safety-gate.d.ts +3 -1
  60. package/dist/core/extensions/builtin/command-safety-gate.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/command-safety-gate.js +7 -0
  62. package/dist/core/extensions/builtin/command-safety-gate.js.map +1 -1
  63. package/dist/core/extensions/types.d.ts +12 -1
  64. package/dist/core/extensions/types.d.ts.map +1 -1
  65. package/dist/core/extensions/types.js.map +1 -1
  66. package/dist/core/host-resource-probe.d.ts +63 -0
  67. package/dist/core/host-resource-probe.d.ts.map +1 -0
  68. package/dist/core/host-resource-probe.js +126 -0
  69. package/dist/core/host-resource-probe.js.map +1 -0
  70. package/dist/core/host-resource-snapshot.d.ts +54 -0
  71. package/dist/core/host-resource-snapshot.d.ts.map +1 -0
  72. package/dist/core/host-resource-snapshot.js +139 -0
  73. package/dist/core/host-resource-snapshot.js.map +1 -0
  74. package/dist/core/model-registry.d.ts +8 -3
  75. package/dist/core/model-registry.d.ts.map +1 -1
  76. package/dist/core/model-registry.js +71 -1
  77. package/dist/core/model-registry.js.map +1 -1
  78. package/dist/core/package-doctor-source-scan.d.ts +9 -0
  79. package/dist/core/package-doctor-source-scan.d.ts.map +1 -1
  80. package/dist/core/package-doctor-source-scan.js +77 -4
  81. package/dist/core/package-doctor-source-scan.js.map +1 -1
  82. package/dist/core/prompt-attachment.d.ts +51 -0
  83. package/dist/core/prompt-attachment.d.ts.map +1 -0
  84. package/dist/core/prompt-attachment.js +186 -0
  85. package/dist/core/prompt-attachment.js.map +1 -0
  86. package/dist/core/prompt-settlement.d.ts +81 -0
  87. package/dist/core/prompt-settlement.d.ts.map +1 -0
  88. package/dist/core/prompt-settlement.js +84 -0
  89. package/dist/core/prompt-settlement.js.map +1 -0
  90. package/dist/core/provider-resilience.d.ts +28 -0
  91. package/dist/core/provider-resilience.d.ts.map +1 -1
  92. package/dist/core/provider-resilience.js +51 -1
  93. package/dist/core/provider-resilience.js.map +1 -1
  94. package/dist/core/provider-retry.d.ts +7 -0
  95. package/dist/core/provider-retry.d.ts.map +1 -1
  96. package/dist/core/provider-retry.js +24 -0
  97. package/dist/core/provider-retry.js.map +1 -1
  98. package/dist/core/provider-usage.d.ts +15 -2
  99. package/dist/core/provider-usage.d.ts.map +1 -1
  100. package/dist/core/provider-usage.js +176 -2
  101. package/dist/core/provider-usage.js.map +1 -1
  102. package/dist/core/reasoning-router-promotion.d.ts +84 -0
  103. package/dist/core/reasoning-router-promotion.d.ts.map +1 -0
  104. package/dist/core/reasoning-router-promotion.js +92 -0
  105. package/dist/core/reasoning-router-promotion.js.map +1 -0
  106. package/dist/core/reasoning-router-resolver.d.ts.map +1 -1
  107. package/dist/core/reasoning-router-resolver.js +1 -1
  108. package/dist/core/reasoning-router-resolver.js.map +1 -1
  109. package/dist/core/resource-admission-config.d.ts +55 -0
  110. package/dist/core/resource-admission-config.d.ts.map +1 -0
  111. package/dist/core/resource-admission-config.js +97 -0
  112. package/dist/core/resource-admission-config.js.map +1 -0
  113. package/dist/core/resource-admission.d.ts +81 -0
  114. package/dist/core/resource-admission.d.ts.map +1 -0
  115. package/dist/core/resource-admission.js +128 -0
  116. package/dist/core/resource-admission.js.map +1 -0
  117. package/dist/core/resource-governor-format.d.ts +50 -0
  118. package/dist/core/resource-governor-format.d.ts.map +1 -0
  119. package/dist/core/resource-governor-format.js +113 -0
  120. package/dist/core/resource-governor-format.js.map +1 -0
  121. package/dist/core/resource-governor-settings.d.ts +56 -0
  122. package/dist/core/resource-governor-settings.d.ts.map +1 -0
  123. package/dist/core/resource-governor-settings.js +89 -0
  124. package/dist/core/resource-governor-settings.js.map +1 -0
  125. package/dist/core/resource-observation-journal.d.ts +74 -0
  126. package/dist/core/resource-observation-journal.d.ts.map +1 -0
  127. package/dist/core/resource-observation-journal.js +198 -0
  128. package/dist/core/resource-observation-journal.js.map +1 -0
  129. package/dist/core/resource-safety-gate.d.ts +54 -0
  130. package/dist/core/resource-safety-gate.d.ts.map +1 -0
  131. package/dist/core/resource-safety-gate.js +51 -0
  132. package/dist/core/resource-safety-gate.js.map +1 -0
  133. package/dist/core/run-journal.d.ts.map +1 -1
  134. package/dist/core/run-journal.js +13 -1
  135. package/dist/core/run-journal.js.map +1 -1
  136. package/dist/core/run-resource-lease.d.ts +73 -0
  137. package/dist/core/run-resource-lease.d.ts.map +1 -0
  138. package/dist/core/run-resource-lease.js +78 -0
  139. package/dist/core/run-resource-lease.js.map +1 -0
  140. package/dist/core/session-bash-service.d.ts +12 -0
  141. package/dist/core/session-bash-service.d.ts.map +1 -1
  142. package/dist/core/session-bash-service.js +18 -3
  143. package/dist/core/session-bash-service.js.map +1 -1
  144. package/dist/core/session-system-prompt.d.ts +2 -0
  145. package/dist/core/session-system-prompt.d.ts.map +1 -1
  146. package/dist/core/session-system-prompt.js +2 -0
  147. package/dist/core/session-system-prompt.js.map +1 -1
  148. package/dist/core/session-termination.d.ts +8 -3
  149. package/dist/core/session-termination.d.ts.map +1 -1
  150. package/dist/core/session-termination.js +40 -3
  151. package/dist/core/session-termination.js.map +1 -1
  152. package/dist/core/settings-manager.d.ts +12 -0
  153. package/dist/core/settings-manager.d.ts.map +1 -1
  154. package/dist/core/settings-manager.js +36 -0
  155. package/dist/core/settings-manager.js.map +1 -1
  156. package/dist/core/slash-commands.d.ts.map +1 -1
  157. package/dist/core/slash-commands.js +1 -0
  158. package/dist/core/slash-commands.js.map +1 -1
  159. package/dist/core/subagent-lane-launcher.d.ts +64 -0
  160. package/dist/core/subagent-lane-launcher.d.ts.map +1 -0
  161. package/dist/core/subagent-lane-launcher.js +107 -0
  162. package/dist/core/subagent-lane-launcher.js.map +1 -0
  163. package/dist/core/system-cpu-sampler.d.ts +45 -0
  164. package/dist/core/system-cpu-sampler.d.ts.map +1 -0
  165. package/dist/core/system-cpu-sampler.js +95 -0
  166. package/dist/core/system-cpu-sampler.js.map +1 -0
  167. package/dist/core/system-prompt.d.ts.map +1 -1
  168. package/dist/core/system-prompt.js +5 -8
  169. package/dist/core/system-prompt.js.map +1 -1
  170. package/dist/core/tools/bash.d.ts.map +1 -1
  171. package/dist/core/tools/bash.js +3 -7
  172. package/dist/core/tools/bash.js.map +1 -1
  173. package/dist/core/tools/grep.d.ts.map +1 -1
  174. package/dist/core/tools/grep.js +13 -48
  175. package/dist/core/tools/grep.js.map +1 -1
  176. package/dist/core/tools/read.d.ts.map +1 -1
  177. package/dist/core/tools/read.js +1 -34
  178. package/dist/core/tools/read.js.map +1 -1
  179. package/dist/core/workload-classifier.d.ts +32 -0
  180. package/dist/core/workload-classifier.d.ts.map +1 -0
  181. package/dist/core/workload-classifier.js +66 -0
  182. package/dist/core/workload-classifier.js.map +1 -0
  183. package/dist/core/workload-family-matcher.d.ts +3 -0
  184. package/dist/core/workload-family-matcher.d.ts.map +1 -0
  185. package/dist/core/workload-family-matcher.js +126 -0
  186. package/dist/core/workload-family-matcher.js.map +1 -0
  187. package/dist/core/workload-permit-pool.d.ts +78 -0
  188. package/dist/core/workload-permit-pool.d.ts.map +1 -0
  189. package/dist/core/workload-permit-pool.js +138 -0
  190. package/dist/core/workload-permit-pool.js.map +1 -0
  191. package/dist/core/workload-shard-executor.d.ts +68 -0
  192. package/dist/core/workload-shard-executor.d.ts.map +1 -0
  193. package/dist/core/workload-shard-executor.js +156 -0
  194. package/dist/core/workload-shard-executor.js.map +1 -0
  195. package/dist/core/workload-shard-plan.d.ts +81 -0
  196. package/dist/core/workload-shard-plan.d.ts.map +1 -0
  197. package/dist/core/workload-shard-plan.js +183 -0
  198. package/dist/core/workload-shard-plan.js.map +1 -0
  199. package/dist/core/workload-shard-runner.d.ts +11 -0
  200. package/dist/core/workload-shard-runner.d.ts.map +1 -0
  201. package/dist/core/workload-shard-runner.js +98 -0
  202. package/dist/core/workload-shard-runner.js.map +1 -0
  203. package/dist/core/workload-shard-store.d.ts +44 -0
  204. package/dist/core/workload-shard-store.d.ts.map +1 -0
  205. package/dist/core/workload-shard-store.js +189 -0
  206. package/dist/core/workload-shard-store.js.map +1 -0
  207. package/dist/core/workload-sharders.d.ts +47 -0
  208. package/dist/core/workload-sharders.d.ts.map +1 -0
  209. package/dist/core/workload-sharders.js +162 -0
  210. package/dist/core/workload-sharders.js.map +1 -0
  211. package/dist/core/workload-shell-scan.d.ts +12 -0
  212. package/dist/core/workload-shell-scan.d.ts.map +1 -0
  213. package/dist/core/workload-shell-scan.js +79 -0
  214. package/dist/core/workload-shell-scan.js.map +1 -0
  215. package/dist/index.d.ts +1 -1
  216. package/dist/index.d.ts.map +1 -1
  217. package/dist/index.js +1 -1
  218. package/dist/index.js.map +1 -1
  219. package/dist/main.d.ts.map +1 -1
  220. package/dist/main.js +6 -0
  221. package/dist/main.js.map +1 -1
  222. package/dist/modes/interactive/components/attachment-strip.d.ts +21 -0
  223. package/dist/modes/interactive/components/attachment-strip.d.ts.map +1 -0
  224. package/dist/modes/interactive/components/attachment-strip.js +136 -0
  225. package/dist/modes/interactive/components/attachment-strip.js.map +1 -0
  226. package/dist/modes/interactive/components/footer.d.ts +6 -0
  227. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  228. package/dist/modes/interactive/components/footer.js +26 -1
  229. package/dist/modes/interactive/components/footer.js.map +1 -1
  230. package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/status-sidebar.js +12 -1
  232. package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
  233. package/dist/modes/interactive/interactive-mode.d.ts +27 -4
  234. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  235. package/dist/modes/interactive/interactive-mode.js +255 -53
  236. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  237. package/docs/compaction.md +1 -1
  238. package/docs/extensions.md +12 -3
  239. package/docs/index.md +4 -1
  240. package/docs/metrics.md +74 -2
  241. package/docs/provider-resilience.md +11 -0
  242. package/docs/providers.md +1 -1
  243. package/docs/sdk.md +4 -0
  244. package/docs/settings.md +64 -2
  245. package/docs/skills.md +16 -0
  246. package/docs/usage.md +1 -1
  247. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  248. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  249. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  250. package/examples/extensions/gondolin/package-lock.json +2 -2
  251. package/examples/extensions/gondolin/package.json +1 -1
  252. package/examples/extensions/sandbox/package-lock.json +2 -2
  253. package/examples/extensions/sandbox/package.json +1 -1
  254. package/examples/extensions/subagent/agent-capability-router.test.ts +7 -0
  255. package/examples/extensions/subagent/domain-profiles.ts +0 -1
  256. package/examples/extensions/with-deps/package-lock.json +2 -2
  257. package/examples/extensions/with-deps/package.json +1 -1
  258. package/npm-shrinkwrap.json +18 -18
  259. package/package.json +8 -10
  260. package/dist/core/tools/omp-pure-seams.d.ts +0 -41
  261. package/dist/core/tools/omp-pure-seams.d.ts.map +0 -1
  262. package/dist/core/tools/omp-pure-seams.js +0 -142
  263. package/dist/core/tools/omp-pure-seams.js.map +0 -1
  264. package/dist/core/tools/omp-seam-runtime.d.ts +0 -100
  265. package/dist/core/tools/omp-seam-runtime.d.ts.map +0 -1
  266. package/dist/core/tools/omp-seam-runtime.js +0 -40
  267. package/dist/core/tools/omp-seam-runtime.js.map +0 -1
@@ -3,25 +3,31 @@
3
3
  * allow: SIZE_OK - legacy TUI coordinator; this change only adds a two-line CONTROL status bridge.
4
4
  * Handles TUI rendering and user interaction, delegating business logic to AgentSession.
5
5
  */
6
- import * as crypto from "node:crypto";
7
6
  import * as fs from "node:fs";
8
7
  import * as os from "node:os";
9
8
  import * as path from "node:path";
10
9
  import { spawn, spawnSync } from "child_process";
11
10
  import { getProviders, } from "omk-ai";
11
+ import { AttachmentStore } from "../../core/attachment-store.js";
12
+ import { describePromptImageAttachment } from "../../core/prompt-attachment.js";
13
+ import { createAttachmentStrip } from "./components/attachment-strip.js";
12
14
  import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "omk-tui";
13
15
  import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
14
16
  import { parseSkillBlock } from "../../core/agent-session.js";
15
17
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
16
18
  import { parseBangInvocation } from "../../core/bang-skill-invocation.js";
19
+ import { CompletionSoundService, resolveCompletionSoundSettings, selectCompletionSoundCandidates, } from "../../core/completion-sound.js";
17
20
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
18
21
  import { OMK_GITHUB_REPOSITORY_URL } from "../../core/github-repository.js";
22
+ import { captureHostResourceSnapshot } from "../../core/host-resource-snapshot.js";
19
23
  import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
20
24
  import { KeybindingsManager } from "../../core/keybindings.js";
21
25
  import { createCompactionSummaryMessage } from "../../core/messages.js";
22
26
  import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
23
27
  import { DefaultPackageManager } from "../../core/package-manager.js";
24
28
  import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
29
+ import { decideResourceAdmission } from "../../core/resource-admission.js";
30
+ import { formatResourcePolicyLines, formatResourceProbeLines, formatResourceSummaryLines, resolveResourceGovernorSettings, } from "../../core/resource-governor-settings.js";
25
31
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
26
32
  import { SessionManager } from "../../core/session-manager.js";
27
33
  import { formatSessionTermination } from "../../core/session-termination.js";
@@ -29,7 +35,7 @@ import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
29
35
  import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
30
36
  import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
31
37
  import { copyToClipboard } from "../../utils/clipboard.js";
32
- import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
38
+ import { readClipboardImage } from "../../utils/clipboard-image.js";
33
39
  import { parseGitUrl } from "../../utils/git.js";
34
40
  import { openBrowser } from "../../utils/open-browser.js";
35
41
  import { getCwdRelativePath } from "../../utils/paths.js";
@@ -215,7 +221,11 @@ export class InteractiveMode {
215
221
  version;
216
222
  isInitialized = false;
217
223
  onInputCallback;
218
- pendingUserInputs = [];
224
+ pendingPromptPayloads = [];
225
+ attachmentStore = new AttachmentStore();
226
+ draftAttachmentIds = [];
227
+ attachmentStripContainer = new Container();
228
+ interactiveLoopActive = true;
219
229
  loadingAnimation = undefined;
220
230
  // Wall-clock start of the current agent turn, for the working loader elapsed suffix
221
231
  workingStartedAt = undefined;
@@ -648,6 +658,7 @@ export class InteractiveMode {
648
658
  this.ui.addChild(this.statusContainer);
649
659
  this.renderWidgets(); // Initialize with default spacer
650
660
  this.ui.addChild(this.widgetContainerAbove);
661
+ this.ui.addChild(this.attachmentStripContainer);
651
662
  this.ui.addChild(this.editorContainer);
652
663
  this.ui.addChild(this.widgetContainerBelow);
653
664
  this.ui.addChild(this.noticeContainer);
@@ -776,13 +787,17 @@ export class InteractiveMode {
776
787
  }
777
788
  }
778
789
  // Main interactive loop
779
- while (true) {
780
- const userInput = await this.getUserInput();
790
+ while (this.interactiveLoopActive) {
791
+ const payload = await this.getUserInput();
781
792
  const previousTermination = this.session.lastTermination;
782
793
  try {
783
- await this.session.prompt(userInput);
794
+ await this.session.prompt(payload.text, payload.images ? { images: payload.images } : undefined);
795
+ // Acceptance: release exactly the attachments that were sent.
796
+ if (payload.imageIds && payload.imageIds.length > 0)
797
+ this.consumeDraftAttachments(payload.imageIds);
784
798
  }
785
799
  catch (error) {
800
+ // Preflight/model failure: attachments stay attached for retry.
786
801
  this.showPromptError(error, previousTermination);
787
802
  }
788
803
  }
@@ -1230,9 +1245,10 @@ export class InteractiveMode {
1230
1245
  const body = visibleLabels.length > 0 ? `${visibleLabels.join(", ")}${suffix}` : "(none)";
1231
1246
  return theme.fg("dim", ` ${body}`);
1232
1247
  };
1233
- const addLoadedSection = (name, collapsedBody, expandedBody = collapsedBody, color = "mdHeading") => {
1248
+ const addLoadedSection = (name, collapsedBody, expandedBody, color = "mdHeading") => {
1249
+ const resolvedExpandedBody = expandedBody ?? collapsedBody;
1234
1250
  const getCollapsedBody = typeof collapsedBody === "function" ? collapsedBody : () => collapsedBody;
1235
- const getExpandedBody = typeof expandedBody === "function" ? expandedBody : () => expandedBody;
1251
+ const getExpandedBody = typeof resolvedExpandedBody === "function" ? resolvedExpandedBody : () => resolvedExpandedBody;
1236
1252
  const section = new ExpandableText(() => `${sectionHeader(name, color)}\n${getCollapsedBody()}`, () => `${sectionHeader(name, color)}\n${getExpandedBody()}`, this.getStartupExpansionState(), 0, 0);
1237
1253
  this.chatContainer.addChild(section);
1238
1254
  this.chatContainer.addChild(new Spacer(1));
@@ -2212,18 +2228,98 @@ export class InteractiveMode {
2212
2228
  if (!image) {
2213
2229
  return;
2214
2230
  }
2215
- // Write to temp file
2216
- const tmpDir = os.tmpdir();
2217
- const ext = extensionForImageMimeType(image.mimeType) ?? "png";
2218
- const fileName = `omk-clipboard-${crypto.randomUUID()}.${ext}`;
2219
- const filePath = path.join(tmpDir, fileName);
2220
- fs.writeFileSync(filePath, Buffer.from(image.bytes));
2221
- // Insert file path directly
2222
- this.editor.insertTextAtCursor?.(filePath);
2223
- this.ui.requestRender();
2231
+ let attachment;
2232
+ try {
2233
+ attachment = this.attachmentStore.put(new Uint8Array(image.bytes), "clipboard");
2234
+ }
2235
+ catch (error) {
2236
+ this.showWarning(error instanceof Error ? error.message : String(error));
2237
+ return;
2238
+ }
2239
+ this.draftAttachmentIds.push(attachment.id);
2240
+ this.refreshAttachmentStrip();
2224
2241
  }
2225
- catch {
2226
- // Silently ignore clipboard errors (may not have permission, etc.)
2242
+ catch (error) {
2243
+ this.showWarning(`Clipboard paste failed: ${error instanceof Error ? error.message : String(error)}`);
2244
+ }
2245
+ }
2246
+ /** Rebuild the preview strip above the editor from the current draft ids. */
2247
+ refreshAttachmentStrip() {
2248
+ this.attachmentStripContainer.clear();
2249
+ const items = this.draftAttachmentIds
2250
+ .map((id) => this.attachmentStore.get(id))
2251
+ .filter((item) => item !== undefined);
2252
+ if (items.length > 0) {
2253
+ this.attachmentStripContainer.addChild(createAttachmentStrip(items, (id) => this.attachmentStore.getBytes(id)));
2254
+ }
2255
+ this.ui.requestRender();
2256
+ }
2257
+ materializeDraftImages(ids) {
2258
+ return this.attachmentStore.materializeImages(ids);
2259
+ }
2260
+ /** Release exactly the attachments that were accepted with a sent prompt. */
2261
+ consumeDraftAttachments(ids) {
2262
+ for (const id of ids) {
2263
+ this.attachmentStore.remove(id);
2264
+ const index = this.draftAttachmentIds.indexOf(id);
2265
+ if (index >= 0)
2266
+ this.draftAttachmentIds.splice(index, 1);
2267
+ }
2268
+ this.refreshAttachmentStrip();
2269
+ }
2270
+ clearDraftAttachments() {
2271
+ this.draftAttachmentIds = [];
2272
+ this.attachmentStore.clear();
2273
+ this.refreshAttachmentStrip();
2274
+ }
2275
+ removeLastDraftAttachment() {
2276
+ const id = this.draftAttachmentIds.pop();
2277
+ if (id)
2278
+ this.attachmentStore.remove(id);
2279
+ this.refreshAttachmentStrip();
2280
+ }
2281
+ isVisionCapable() {
2282
+ const model = this.session.model;
2283
+ return Array.isArray(model?.input) ? model.input.includes("image") : true;
2284
+ }
2285
+ handleAttachCommand(argument) {
2286
+ const [verb, rest] = argument.split(/\s+/);
2287
+ switch (verb ?? "") {
2288
+ case "":
2289
+ case "list": {
2290
+ const items = this.draftAttachmentIds
2291
+ .map((id) => this.attachmentStore.get(id))
2292
+ .filter((item) => item !== undefined);
2293
+ if (items.length === 0) {
2294
+ this.showStatus("No image attachments.");
2295
+ }
2296
+ else {
2297
+ this.showStatus(items.map((item, index) => `${index + 1}. ${describePromptImageAttachment(item)}`).join(" · "));
2298
+ }
2299
+ break;
2300
+ }
2301
+ case "clear":
2302
+ this.clearDraftAttachments();
2303
+ this.showStatus("Image attachments cleared.");
2304
+ break;
2305
+ case "remove":
2306
+ case "rm": {
2307
+ const position = Number.parseInt(rest ?? "", 10);
2308
+ if (Number.isFinite(position) && position >= 1 && position <= this.draftAttachmentIds.length) {
2309
+ const id = this.draftAttachmentIds.splice(position - 1, 1)[0];
2310
+ if (id)
2311
+ this.attachmentStore.remove(id);
2312
+ }
2313
+ else {
2314
+ this.removeLastDraftAttachment();
2315
+ }
2316
+ this.refreshAttachmentStrip();
2317
+ this.showStatus("Image attachment removed.");
2318
+ break;
2319
+ }
2320
+ default:
2321
+ this.showStatus("Usage: /attach [list|clear|remove [n]]");
2322
+ break;
2227
2323
  }
2228
2324
  }
2229
2325
  parseBangInput(text) {
@@ -2241,9 +2337,16 @@ export class InteractiveMode {
2241
2337
  setupEditorSubmitHandler() {
2242
2338
  this.defaultEditor.onSubmit = async (text) => {
2243
2339
  text = text.trim();
2244
- if (!text)
2340
+ const draftImageIds = [...(this.draftAttachmentIds ?? [])];
2341
+ const hasAttachments = draftImageIds.length > 0;
2342
+ if (!text && !hasAttachments)
2245
2343
  return;
2246
2344
  // Handle commands
2345
+ if (text === "/attach" || text.startsWith("/attach ")) {
2346
+ this.editor.setText("");
2347
+ this.handleAttachCommand(text.slice("/attach".length).trim());
2348
+ return;
2349
+ }
2247
2350
  if (text === "/settings") {
2248
2351
  this.showSettingsSelector();
2249
2352
  this.editor.setText("");
@@ -2296,6 +2399,11 @@ export class InteractiveMode {
2296
2399
  this.editor.setText("");
2297
2400
  return;
2298
2401
  }
2402
+ if (text === "/resource" || text.startsWith("/resource ")) {
2403
+ this.editor.setText("");
2404
+ await this.handleResourceCommand(text);
2405
+ return;
2406
+ }
2299
2407
  if (text === "/star") {
2300
2408
  this.handleStarCommand();
2301
2409
  return true;
@@ -2400,8 +2508,26 @@ export class InteractiveMode {
2400
2508
  return;
2401
2509
  }
2402
2510
  }
2511
+ let draftImages;
2512
+ if (hasAttachments) {
2513
+ try {
2514
+ draftImages = this.materializeDraftImages(draftImageIds);
2515
+ }
2516
+ catch (error) {
2517
+ this.showWarning(error instanceof Error ? error.message : String(error));
2518
+ return;
2519
+ }
2520
+ if (!this.isVisionCapable()) {
2521
+ this.showWarning("The selected model cannot accept images. Switch model (/model) or run /attach clear.");
2522
+ return;
2523
+ }
2524
+ }
2403
2525
  // Queue input during compaction (extension commands execute immediately)
2404
2526
  if (this.session.isCompacting) {
2527
+ if (hasAttachments) {
2528
+ this.showWarning("Attachments are held while compaction finishes — resubmit after it completes.");
2529
+ return;
2530
+ }
2405
2531
  if (this.isExtensionCommand(text)) {
2406
2532
  this.editor.addToHistory?.(text);
2407
2533
  this.editor.setText("");
@@ -2417,7 +2543,10 @@ export class InteractiveMode {
2417
2543
  if (this.session.isStreaming) {
2418
2544
  this.editor.addToHistory?.(text);
2419
2545
  this.editor.setText("");
2420
- await this.session.prompt(text, { streamingBehavior: "steer" });
2546
+ await this.session.prompt(text, draftImages ? { images: draftImages, streamingBehavior: "steer" } : { streamingBehavior: "steer" });
2547
+ // Queued without throwing: acceptance — release exactly what was sent.
2548
+ if (draftImageIds.length > 0)
2549
+ this.consumeDraftAttachments(draftImageIds);
2421
2550
  this.updatePendingMessagesDisplay();
2422
2551
  this.ui.requestRender();
2423
2552
  return;
@@ -2425,11 +2554,14 @@ export class InteractiveMode {
2425
2554
  // Normal message submission
2426
2555
  // First, move any pending bash components to chat
2427
2556
  this.flushPendingBashComponents();
2557
+ const payload = draftImages
2558
+ ? { text, images: draftImages, imageIds: draftImageIds }
2559
+ : { text };
2428
2560
  if (this.onInputCallback) {
2429
- this.onInputCallback(text);
2561
+ this.onInputCallback(payload);
2430
2562
  }
2431
2563
  else {
2432
- this.pendingUserInputs.push(text);
2564
+ this.pendingPromptPayloads.push(payload);
2433
2565
  }
2434
2566
  this.editor.addToHistory?.(text);
2435
2567
  };
@@ -2445,6 +2577,13 @@ export class InteractiveMode {
2445
2577
  }
2446
2578
  this.footer.invalidate();
2447
2579
  switch (event.type) {
2580
+ case "prompt_settled":
2581
+ // §17 (M4): fire-and-forget — the sound must never delay the prompt
2582
+ // path or change outcomes; failures are diagnostics only.
2583
+ void this._completionSoundService()
2584
+ .handleSettled(event)
2585
+ .then((result) => this.session.recordCompletionSoundResult(event.promptRunId, result));
2586
+ break;
2448
2587
  case "agent_start":
2449
2588
  this.workingStartedAt = Date.now();
2450
2589
  this.pendingTools.clear();
@@ -2962,14 +3101,14 @@ export class InteractiveMode {
2962
3101
  }
2963
3102
  }
2964
3103
  async getUserInput() {
2965
- const queuedInput = this.pendingUserInputs.shift();
2966
- if (queuedInput !== undefined) {
2967
- return queuedInput;
3104
+ const queuedPayload = this.pendingPromptPayloads.shift();
3105
+ if (queuedPayload !== undefined) {
3106
+ return queuedPayload;
2968
3107
  }
2969
3108
  return new Promise((resolve) => {
2970
- this.onInputCallback = (text) => {
3109
+ this.onInputCallback = (payload) => {
2971
3110
  this.onInputCallback = undefined;
2972
- resolve(text);
3111
+ resolve(payload);
2973
3112
  };
2974
3113
  });
2975
3114
  }
@@ -3012,6 +3151,7 @@ export class InteractiveMode {
3012
3151
  */
3013
3152
  isShuttingDown = false;
3014
3153
  async shutdown(options) {
3154
+ this.interactiveLoopActive = false;
3015
3155
  if (this.isShuttingDown)
3016
3156
  return;
3017
3157
  this.isShuttingDown = true;
@@ -4853,6 +4993,71 @@ export class InteractiveMode {
4853
4993
  this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${name}`), 1, 0));
4854
4994
  this.ui.requestRender();
4855
4995
  }
4996
+ /**
4997
+ * `/resource [probe|policy]` (roadmap §19.1, observe-only in this release):
4998
+ * summary shows pressure/action/effective caps, `probe` shows verbose local
4999
+ * host details, `policy` shows mode/thresholds/caps. Permits and shards
5000
+ * views arrive with their subsystems (M3/M5).
5001
+ */
5002
+ async handleResourceCommand(text) {
5003
+ const subcommand = text.replace(/^\/resource\s*/, "").trim();
5004
+ if (subcommand !== "" && subcommand !== "probe" && subcommand !== "policy") {
5005
+ this.showWarning("Usage: /resource [probe|policy]");
5006
+ this.ui.requestRender();
5007
+ return;
5008
+ }
5009
+ const resolved = resolveResourceGovernorSettings(this.settingsManager.getResourceGovernorSettings());
5010
+ const lines = [];
5011
+ if (subcommand === "policy") {
5012
+ lines.push(theme.bold("Resource Policy"), "", ...formatResourcePolicyLines(resolved));
5013
+ }
5014
+ else {
5015
+ const snapshot = await captureHostResourceSnapshot({
5016
+ maxProbeMs: resolved.maxProbeMs,
5017
+ cpuSampleMs: resolved.cpuSampleMs,
5018
+ });
5019
+ if (subcommand === "probe") {
5020
+ lines.push(theme.bold("Resource Probe"), "", ...formatResourceProbeLines(snapshot));
5021
+ }
5022
+ else {
5023
+ const decision = decideResourceAdmission({
5024
+ snapshot,
5025
+ config: resolved.admission,
5026
+ configuredCaps: {
5027
+ maxToolConcurrency: this.settingsManager.getAgentRuntimeSettings().maxToolConcurrency,
5028
+ },
5029
+ });
5030
+ lines.push(theme.bold("Resource Status"), "", ...formatResourceSummaryLines(resolved.mode, decision));
5031
+ }
5032
+ for (const error of resolved.errors) {
5033
+ lines.push(theme.fg("dim", `settings error: ${error}`));
5034
+ }
5035
+ }
5036
+ this.chatContainer.addChild(new Spacer(1));
5037
+ this.chatContainer.addChild(new Text(lines.join("\n"), 1, 0));
5038
+ this.ui.requestRender();
5039
+ }
5040
+ _completionSound;
5041
+ /** §17.4: sounds play only on this interactive TTY surface, never RPC/JSON/print/CI. */
5042
+ _completionSoundService() {
5043
+ this._completionSound ??= new CompletionSoundService({
5044
+ getSettings: () => this.settingsManager.getNotificationSettings().completionSound,
5045
+ surface: () => ({
5046
+ isTui: true,
5047
+ isTty: process.stdout.isTTY === true,
5048
+ isCi: process.env.CI !== undefined && process.env.CI !== "",
5049
+ }),
5050
+ candidates: () => {
5051
+ const resolved = resolveCompletionSoundSettings(this.settingsManager.getNotificationSettings().completionSound);
5052
+ return selectCompletionSoundCandidates({
5053
+ platform: process.platform,
5054
+ isWsl: process.platform === "linux" && /microsoft/i.test(os.release()),
5055
+ terminalBellFallback: resolved.terminalBellFallback,
5056
+ });
5057
+ },
5058
+ });
5059
+ return this._completionSound;
5060
+ }
4856
5061
  handleSessionCommand() {
4857
5062
  const stats = this.session.getSessionStats();
4858
5063
  const sessionName = this.sessionManager.getSessionName();
@@ -4941,34 +5146,31 @@ export class InteractiveMode {
4941
5146
  /**
4942
5147
  * Get capitalized display string for an editor keybinding action.
4943
5148
  */
4944
- getEditorKeyDisplay(action) {
4945
- return keyDisplayText(action);
4946
- }
4947
5149
  handleHotkeysCommand() {
4948
5150
  // Navigation keybindings
4949
- const cursorUp = this.getEditorKeyDisplay("tui.editor.cursorUp");
4950
- const cursorDown = this.getEditorKeyDisplay("tui.editor.cursorDown");
4951
- const cursorLeft = this.getEditorKeyDisplay("tui.editor.cursorLeft");
4952
- const cursorRight = this.getEditorKeyDisplay("tui.editor.cursorRight");
4953
- const cursorWordLeft = this.getEditorKeyDisplay("tui.editor.cursorWordLeft");
4954
- const cursorWordRight = this.getEditorKeyDisplay("tui.editor.cursorWordRight");
4955
- const cursorLineStart = this.getEditorKeyDisplay("tui.editor.cursorLineStart");
4956
- const cursorLineEnd = this.getEditorKeyDisplay("tui.editor.cursorLineEnd");
4957
- const jumpForward = this.getEditorKeyDisplay("tui.editor.jumpForward");
4958
- const jumpBackward = this.getEditorKeyDisplay("tui.editor.jumpBackward");
4959
- const pageUp = this.getEditorKeyDisplay("tui.editor.pageUp");
4960
- const pageDown = this.getEditorKeyDisplay("tui.editor.pageDown");
5151
+ const cursorUp = keyDisplayText("tui.editor.cursorUp");
5152
+ const cursorDown = keyDisplayText("tui.editor.cursorDown");
5153
+ const cursorLeft = keyDisplayText("tui.editor.cursorLeft");
5154
+ const cursorRight = keyDisplayText("tui.editor.cursorRight");
5155
+ const cursorWordLeft = keyDisplayText("tui.editor.cursorWordLeft");
5156
+ const cursorWordRight = keyDisplayText("tui.editor.cursorWordRight");
5157
+ const cursorLineStart = keyDisplayText("tui.editor.cursorLineStart");
5158
+ const cursorLineEnd = keyDisplayText("tui.editor.cursorLineEnd");
5159
+ const jumpForward = keyDisplayText("tui.editor.jumpForward");
5160
+ const jumpBackward = keyDisplayText("tui.editor.jumpBackward");
5161
+ const pageUp = keyDisplayText("tui.editor.pageUp");
5162
+ const pageDown = keyDisplayText("tui.editor.pageDown");
4961
5163
  // Editing keybindings
4962
- const submit = this.getEditorKeyDisplay("tui.input.submit");
4963
- const newLine = this.getEditorKeyDisplay("tui.input.newLine");
4964
- const deleteWordBackward = this.getEditorKeyDisplay("tui.editor.deleteWordBackward");
4965
- const deleteWordForward = this.getEditorKeyDisplay("tui.editor.deleteWordForward");
4966
- const deleteToLineStart = this.getEditorKeyDisplay("tui.editor.deleteToLineStart");
4967
- const deleteToLineEnd = this.getEditorKeyDisplay("tui.editor.deleteToLineEnd");
4968
- const yank = this.getEditorKeyDisplay("tui.editor.yank");
4969
- const yankPop = this.getEditorKeyDisplay("tui.editor.yankPop");
4970
- const undo = this.getEditorKeyDisplay("tui.editor.undo");
4971
- const tab = this.getEditorKeyDisplay("tui.input.tab");
5164
+ const submit = keyDisplayText("tui.input.submit");
5165
+ const newLine = keyDisplayText("tui.input.newLine");
5166
+ const deleteWordBackward = keyDisplayText("tui.editor.deleteWordBackward");
5167
+ const deleteWordForward = keyDisplayText("tui.editor.deleteWordForward");
5168
+ const deleteToLineStart = keyDisplayText("tui.editor.deleteToLineStart");
5169
+ const deleteToLineEnd = keyDisplayText("tui.editor.deleteToLineEnd");
5170
+ const yank = keyDisplayText("tui.editor.yank");
5171
+ const yankPop = keyDisplayText("tui.editor.yankPop");
5172
+ const undo = keyDisplayText("tui.editor.undo");
5173
+ const tab = keyDisplayText("tui.input.tab");
4972
5174
  // App keybindings
4973
5175
  const interrupt = this.getAppKeyDisplay("app.interrupt");
4974
5176
  const clear = this.getAppKeyDisplay("app.clear");