agentbox-app 0.1.11 → 0.1.13

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 (239) hide show
  1. package/README.md +24 -16
  2. package/bin/agentbox.mjs +18 -9
  3. package/briefs/message-rules.md +15 -4
  4. package/main/account-tooling.mjs +2 -1
  5. package/main/acp.mjs +296 -0
  6. package/main/agent-homes.mjs +158 -0
  7. package/main/agent-sessions.mjs +57 -20
  8. package/main/agent-updates.mjs +9 -20
  9. package/main/analytics.mjs +30 -0
  10. package/main/approvals.mjs +14 -5
  11. package/main/claude-bin.mjs +45 -310
  12. package/main/claude-input.mjs +9 -3
  13. package/main/claude-launch-env.mjs +54 -0
  14. package/main/claude-models.mjs +39 -7
  15. package/main/claude-plan.mjs +14 -2
  16. package/main/claude-usage.mjs +82 -25
  17. package/main/codex-bin.mjs +51 -248
  18. package/main/codex-launch-env.mjs +31 -0
  19. package/main/codex-session.mjs +38 -3
  20. package/main/codex-threads.mjs +41 -8
  21. package/main/codex-watch.mjs +67 -3
  22. package/main/config.mjs +40 -13
  23. package/main/corner-tag.mjs +489 -0
  24. package/main/crash-report.mjs +26 -3
  25. package/main/demo.mjs +17 -14
  26. package/main/engine-bin.mjs +595 -0
  27. package/main/engine-setup.mjs +27 -27
  28. package/main/feedback.mjs +11 -6
  29. package/main/fresh-user.mjs +1 -1
  30. package/main/git-change.mjs +8 -2
  31. package/main/harnesses/claude-stream.mjs +208 -0
  32. package/main/harnesses/claude.mjs +84 -0
  33. package/main/harnesses/codex.mjs +65 -0
  34. package/main/harnesses/common.mjs +78 -0
  35. package/main/harnesses/instructions-file.mjs +116 -0
  36. package/main/harnesses/opencode.mjs +234 -0
  37. package/main/harnesses.mjs +65 -0
  38. package/main/importable-threads.mjs +38 -0
  39. package/main/instruction-settings.mjs +47 -11
  40. package/main/ipc.mjs +175 -22
  41. package/main/local-app-frames.mjs +82 -0
  42. package/main/machine.mjs +1 -1
  43. package/main/main.mjs +120 -5
  44. package/main/memory-gate-history.mjs +51 -12
  45. package/main/memory-gate-server.mjs +67 -11
  46. package/main/memory-gate.mjs +49 -8
  47. package/main/memory-log.mjs +124 -0
  48. package/main/notify.mjs +48 -5
  49. package/main/opencode-server.mjs +271 -0
  50. package/main/opencode.mjs +287 -0
  51. package/main/phone-link.mjs +551 -0
  52. package/main/refresh-agent-models.mjs +2 -1
  53. package/main/relay-address.mjs +28 -0
  54. package/main/relay-client.mjs +246 -0
  55. package/main/row-label.mjs +8 -7
  56. package/main/runs-on.mjs +90 -0
  57. package/main/serve.mjs +124 -10
  58. package/main/settings.mjs +171 -11
  59. package/main/shell-path.mjs +86 -0
  60. package/main/ship-queue.mjs +25 -7
  61. package/main/sign-in-files.mjs +3 -7
  62. package/main/source-updater.mjs +27 -1
  63. package/main/start-hold.mjs +73 -0
  64. package/main/store/home.mjs +37 -5
  65. package/main/store/project.mjs +15 -19
  66. package/main/store.mjs +10 -4
  67. package/main/supervisor.mjs +1030 -447
  68. package/main/task-commands.mjs +13 -17
  69. package/main/task-folders-offthread.mjs +6 -1
  70. package/main/task-folders.mjs +491 -82
  71. package/main/task-terminals.mjs +49 -8
  72. package/main/team/index.mjs +2 -2
  73. package/main/team/projects.mjs +1 -1
  74. package/main/updater.mjs +71 -9
  75. package/main/worktree-include.mjs +494 -0
  76. package/mcp/core/page.mjs +1 -1
  77. package/mcp/core/work.mjs +34 -3
  78. package/mcp/tools.mjs +2 -1
  79. package/package.json +10 -1
  80. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-DPPuMWt-.js +1 -0
  81. package/renderer/dist/assets/arc-HmFfdseH.js +1 -0
  82. package/renderer/dist/assets/architectureDiagram-47P4ROYG-DxJ3zHrH.js +36 -0
  83. package/renderer/dist/assets/blockDiagram-E7TT5TSB-CbTXzOqQ.js +140 -0
  84. package/renderer/dist/assets/c4Diagram-CRA5TL53-X5ItFyLn.js +38 -0
  85. package/renderer/dist/assets/channel-BlT2g9Sz.js +1 -0
  86. package/renderer/dist/assets/chunk-2Q5K7J3B-DC12Ho_L.js +1 -0
  87. package/renderer/dist/assets/chunk-5VM5RSS4-ClVtfPd0.js +15 -0
  88. package/renderer/dist/assets/chunk-7TKQ45FW-DgcOm0HT.js +1 -0
  89. package/renderer/dist/assets/chunk-AW2ZBBNX-C01s4yoF.js +88 -0
  90. package/renderer/dist/assets/chunk-FVRAUYC3-Bj0zxhR4.js +2 -0
  91. package/renderer/dist/assets/chunk-HTAEGDNF-DWDeEhLU.js +1 -0
  92. package/renderer/dist/assets/chunk-JWPE2WC7-Dsqwv3eW.js +1 -0
  93. package/renderer/dist/assets/chunk-KQW6MTUR-CE86ANL2.js +1 -0
  94. package/renderer/dist/assets/chunk-XXDRQBXY-CoNTASfs.js +1 -0
  95. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-2wprLqtw.js +217 -0
  96. package/renderer/dist/assets/cornerTag-BLrxiDE5.css +1 -0
  97. package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +1 -0
  98. package/renderer/dist/assets/cose-bilkent-JH36ORCC-enqMiYbn.js +1 -0
  99. package/renderer/dist/assets/cynefin-EF2NZ3EQ-DHngLxvQ.js +141 -0
  100. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-C0rZZIl3.js +62 -0
  101. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  102. package/renderer/dist/assets/dagre-W4DXFKR2-DQnaEWO7.js +4 -0
  103. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  104. package/renderer/dist/assets/diagram-2UJZ2QOL-CS8AlGPx.js +200 -0
  105. package/renderer/dist/assets/diagram-OVF4WLC6-yiDMHIkY.js +24 -0
  106. package/renderer/dist/assets/diagram-PFPMY2P6-XbQKzbBA.js +30 -0
  107. package/renderer/dist/assets/diagram-UMYRVEAY-dhVvlfl_.js +24 -0
  108. package/renderer/dist/assets/diagram-YEKJPTXX-DZHe8SCf.js +41 -0
  109. package/renderer/dist/assets/diagram-ZIFT7M5P-B5h6aXVR.js +3 -0
  110. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-PR9ScDco.js +1 -0
  111. package/renderer/dist/assets/elk-IJKZMXRS-Cg-yQXPd.js +27 -0
  112. package/renderer/dist/assets/erDiagram-O2IAPWRE-omOrJMxu.js +99 -0
  113. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-aOX2hzvT.js +213 -0
  114. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-B1H3pWyI.js +292 -0
  115. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-Cvw304HC.js +106 -0
  116. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  117. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-_ZXcNViH.js +2 -0
  118. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  119. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js +70 -0
  120. package/renderer/dist/assets/journeyDiagram-COXZFDF6-fr62QoAt.js +139 -0
  121. package/renderer/dist/assets/kanban-definition-P3RFRI5V-J8zsabCe.js +89 -0
  122. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  123. package/renderer/dist/assets/linear-BmNdEtIi.js +1 -0
  124. package/renderer/dist/assets/main-ChNdnUvt.js +1132 -0
  125. package/renderer/dist/assets/main-nj0QR5kg.css +32 -0
  126. package/renderer/dist/assets/mermaid.core-CnYzvFkz.js +314 -0
  127. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-Bfcxc7PM.js +96 -0
  128. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  129. package/renderer/dist/assets/pegDiagram-BGZESJAR-CwUJOq6X.js +1 -0
  130. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-BrwMmqfC.js +39 -0
  131. package/renderer/dist/assets/product-name-CTKLYNWY.js +40 -0
  132. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-BN4Stoj-.js +7 -0
  133. package/renderer/dist/assets/railroadDiagram-SM67HX2B-CPZ5bbeR.js +1 -0
  134. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-CgRnimVe.js +84 -0
  135. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-Bt2rej__.js +40 -0
  136. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js +169 -0
  137. package/renderer/dist/assets/sizeCapture-INFHLROL-usyNyXyh.js +1 -0
  138. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-CRzUBb0u.js +291 -0
  139. package/renderer/dist/assets/swimlanes-N4OXWK64-C1GJvMS0.js +1 -0
  140. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +8 -0
  141. package/renderer/dist/assets/timeline-definition-YOQAKGHF-caeyPlcc.js +120 -0
  142. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-BN_SV6PC.js +370 -0
  143. package/renderer/dist/assets/vennDiagram-BLWOH2XV-BLGuaUX-.js +34 -0
  144. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-Da0cJeNc.js +78 -0
  145. package/renderer/dist/assets/xychartDiagram-TAQBALBS-cGfUffMk.js +7 -0
  146. package/renderer/dist/corner-tag.html +13 -0
  147. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  148. package/renderer/dist/emulator/data/compression/README.md +11 -0
  149. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  150. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  151. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  152. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  153. package/renderer/dist/emulator/data/cores/README.md +22 -0
  154. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  155. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  156. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  157. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  158. package/renderer/dist/emulator/data/cores/package.json +65 -0
  159. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  160. package/renderer/dist/emulator/data/emulator.css +1614 -0
  161. package/renderer/dist/emulator/data/loader.js +170 -0
  162. package/renderer/dist/emulator/data/localization/README.md +63 -0
  163. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  164. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  165. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  166. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  167. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  168. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  169. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  170. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  171. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  172. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  173. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  174. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  175. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  176. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  177. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  178. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  179. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  180. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  181. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  182. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  183. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  184. package/renderer/dist/emulator/data/src/compression.js +142 -0
  185. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  186. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  187. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  188. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  189. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  190. package/renderer/dist/emulator/data/src/storage.js +125 -0
  191. package/renderer/dist/emulator/data/version.json +5 -0
  192. package/renderer/dist/emulator/host.js +297 -0
  193. package/renderer/dist/emulator/index.html +28 -0
  194. package/renderer/dist/emulator/provenance.json +6 -0
  195. package/renderer/dist/index.html +16 -3
  196. package/scripts/after-install.mjs +14 -0
  197. package/scripts/approval-server.sh +31 -7
  198. package/scripts/before-publish.mjs +12 -10
  199. package/shared/agent-import.mjs +3 -1
  200. package/shared/agents.d.mts +1 -0
  201. package/shared/bridge-map.mjs +19 -0
  202. package/shared/claude-commands.generated.mjs +8 -6
  203. package/shared/claude-models.generated.mjs +10 -8
  204. package/shared/codex-modes.mjs +1 -1
  205. package/shared/corner-tag.d.mts +14 -0
  206. package/shared/corner-tag.mjs +141 -0
  207. package/shared/demo-world.mjs +1 -1
  208. package/shared/engines.mjs +20 -5
  209. package/shared/fresh-user-home.mjs +12 -1
  210. package/shared/harness-definitions.mjs +103 -0
  211. package/shared/instruction-defaults.json +1 -1
  212. package/shared/notify-rules.d.mts +25 -0
  213. package/shared/notify-rules.mjs +19 -0
  214. package/shared/project-folder-check.mjs +3 -3
  215. package/shared/provider-commands.mjs +10 -2
  216. package/shared/rank.d.mts +2 -0
  217. package/shared/rank.mjs +50 -0
  218. package/shared/relay-address.mjs +6 -0
  219. package/shared/relay-crypto.mjs +165 -0
  220. package/shared/runs-on.d.mts +10 -0
  221. package/shared/runs-on.mjs +55 -0
  222. package/shared/shortcuts.mjs +11 -5
  223. package/shared/spawn-trouble.mjs +58 -7
  224. package/shared/terminal-input.d.mts +1 -0
  225. package/shared/terminal-input.mjs +30 -0
  226. package/shared/terminal-keys.d.mts +1 -0
  227. package/shared/terminal-keys.mjs +16 -0
  228. package/shared/terminal-size.d.mts +4 -0
  229. package/shared/terminal-size.mjs +29 -0
  230. package/shared/usage.d.mts +3 -0
  231. package/shared/usage.mjs +7 -0
  232. package/shared/where-it-runs-from.d.mts +43 -0
  233. package/shared/where-it-runs-from.mjs +155 -0
  234. package/shared/work-items.mjs +28 -2
  235. package/shared/work-lines.mjs +1 -1
  236. package/shared/xterm-invalid-range.d.mts +2 -0
  237. package/shared/xterm-invalid-range.mjs +58 -0
  238. package/renderer/dist/assets/index-B-2Az952.css +0 -32
  239. package/renderer/dist/assets/index-D6qncgai.js +0 -1168
@@ -0,0 +1,287 @@
1
+ // READING AN OPENCODE RUN, IN OPENCODE'S OWN WORDS.
2
+ //
3
+ // The same five readers Claude Code and Codex each provide -- capture, live
4
+ // text, summary, trace, activity -- over OpenCode's `/event` stream. Nothing
5
+ // here translates an OpenCode event into a pretend Claude JSON line or a
6
+ // pretend Codex notification, for the reason docs/harnesses.md gives: the
7
+ // vocabularies are not the same shape and a fake one loses what the real one
8
+ // carries. OpenCode's `edit` approval, for instance, arrives with a whole
9
+ // unified diff in it, which neither of the other two does.
10
+ //
11
+ // EVERY SHAPE BELOW WAS READ OFF A RUNNING OPENCODE 1.18.35 ON 2026-10-07,
12
+ // not off the docs, which do not enumerate the event payloads at all. The
13
+ // ones that matter:
14
+ //
15
+ // session.updated properties.info.{cost,tokens,title,model,directory}
16
+ // message.part.updated properties.part.type: text | reasoning | tool |
17
+ // step-start | step-finish
18
+ // message.part.delta properties.{partID,field,delta} ("field":"text")
19
+ // permission.asked properties.{id,sessionID,permission,patterns,
20
+ // metadata:{command} | metadata:{filepath,diff}}
21
+ // session.idle properties.sessionID
22
+ // session.error properties.{sessionID,error}
23
+ //
24
+ // A tool part carries `state.status` of pending | running | completed | error,
25
+ // with `state.input`, `state.output` and `state.error` beside it.
26
+
27
+ /** How much streamed prose is kept before it is dropped, as the others do. */
28
+ const SAYING_CAP = 20_000;
29
+
30
+ /** Tool names worth naming on the row, in the words a person would use. */
31
+ const TOOL_WORDS = {
32
+ bash: 'Bash', edit: 'Edit', write: 'Write', read: 'Read', glob: 'Glob',
33
+ grep: 'Grep', list: 'List', webfetch: 'Fetch', websearch: 'Search',
34
+ task: 'Task', todowrite: 'Todo', patch: 'Edit', skill: 'Skill',
35
+ };
36
+
37
+ const toolWord = name => {
38
+ const id = String(name ?? '').trim();
39
+ if (!id) return null;
40
+ return TOOL_WORDS[id.toLowerCase()] ?? id;
41
+ };
42
+
43
+ const partOf = properties => (properties && typeof properties === 'object' ? properties.part ?? null : null);
44
+
45
+ /**
46
+ * WHAT THE RUN WAS, kept on the session as it goes.
47
+ *
48
+ * `sessionId` is the one OpenCode resumes by, and it is on nearly every event
49
+ * as `properties.sessionID`. It is taken from the first event that carries one
50
+ * rather than from the session-creation response, so a resumed run and a fresh
51
+ * one record it the same way.
52
+ *
53
+ * The result is the LAST assistant text of the turn. OpenCode has no single
54
+ * "here is the answer" event the way Codex's `turn/completed` does: prose
55
+ * arrives as `text` parts that are updated in place, and the final one is the
56
+ * answer. So each completed text part overwrites the last, and whatever is
57
+ * standing when the session goes idle is the result.
58
+ */
59
+ export function captureOpenCodeEvent(session, type, properties) {
60
+ try {
61
+ if (!session || typeof type !== 'string') return;
62
+ const p = properties ?? {};
63
+ if (!session.sessionId && typeof p.sessionID === 'string' && p.sessionID) session.sessionId = p.sessionID;
64
+
65
+ if (type === 'session.updated') {
66
+ const info = p.info ?? {};
67
+ if (Number.isFinite(info.cost)) session.opencodeCost = info.cost;
68
+ if (info.tokens && typeof info.tokens === 'object') session.opencodeTokens = info.tokens;
69
+ return;
70
+ }
71
+
72
+ if (type === 'session.error') {
73
+ session.result = openCodeErrorText(p.error) ?? 'OpenCode stopped with an error it did not name.';
74
+ session.resultIsError = true;
75
+ return;
76
+ }
77
+
78
+ if (type === 'message.part.updated') {
79
+ const part = partOf(p);
80
+ if (!part) return;
81
+ // A WRITE THIS RUN MADE, so the card can say what changed. The `edit` and
82
+ // `write` tools report the file they touched in `state.input.filePath`,
83
+ // which is the only place a path appears for a change OpenCode has
84
+ // already applied.
85
+ if (part.type === 'tool' && part.state?.status === 'completed') {
86
+ const path = part.state?.input?.filePath ?? part.state?.input?.path;
87
+ if (typeof path === 'string' && path && (part.tool === 'edit' || part.tool === 'write' || part.tool === 'patch')) {
88
+ session.opencodeChange ??= [];
89
+ if (!session.opencodeChange.includes(path)) session.opencodeChange.push(path);
90
+ }
91
+ }
92
+ if (part.type === 'step-finish') {
93
+ if (Number.isFinite(part.cost)) session.opencodeStepCost = part.cost;
94
+ if (part.tokens && typeof part.tokens === 'object') session.opencodeTokens = part.tokens;
95
+ }
96
+ // Only the assistant's prose is an answer. `reasoning` is the model
97
+ // thinking out loud and is never the result, which is why it is matched
98
+ // by name here rather than falling through to the text branch.
99
+ if (part.type === 'text') {
100
+ const text = String(part.text ?? '').trim();
101
+ if (text) { session.result = text; session.resultIsError = false; }
102
+ }
103
+ return;
104
+ }
105
+ } catch {
106
+ // A reader that throws kills a run that was going fine. Same rule as the
107
+ // other two harnesses: read what parses and ignore what does not.
108
+ }
109
+ }
110
+
111
+ /** The error on `session.error`, which is an object and not a string. */
112
+ export function openCodeErrorText(error) {
113
+ if (!error) return null;
114
+ if (typeof error === 'string') return error.trim() || null;
115
+ const name = typeof error.name === 'string' ? error.name : '';
116
+ const message = typeof error.data?.message === 'string' ? error.data.message
117
+ : typeof error.message === 'string' ? error.message : '';
118
+ const joined = [name, message].filter(Boolean).join(': ');
119
+ return joined || null;
120
+ }
121
+
122
+ /**
123
+ * THE PROSE AS IT IS TYPED, for the line on the row that moves.
124
+ *
125
+ * Deltas arrive as `message.part.delta` with a `field` saying which field of
126
+ * which part is growing. Only `text` is shown: `reasoning` deltas are the
127
+ * model's private thinking, and streaming those onto her row would put words
128
+ * on screen that are not what the run decided.
129
+ */
130
+ export function openCodeStreamingText(session, type, properties) {
131
+ try {
132
+ if (!session) return false;
133
+ const p = properties ?? {};
134
+
135
+ if (type === 'message.part.updated') {
136
+ const part = partOf(p);
137
+ // A tool starting means the talking stopped. What is already typed stays
138
+ // until the trace carries the same words, exactly as Codex's reader does.
139
+ if (part?.type === 'text' && !String(part.text ?? '')) { session.saying = ''; session.sayingAt = Date.now(); return true; }
140
+ return false;
141
+ }
142
+
143
+ if (type !== 'message.part.delta') return false;
144
+ if (p.field !== 'text') return false;
145
+ const piece = String(p.delta ?? '');
146
+ if (!piece) return false;
147
+ // A delta for a reasoning part has the same `field: "text"`, so the part it
148
+ // belongs to is what tells them apart. Without a remembered prose part id
149
+ // nothing is streamed, which is the quiet answer rather than the wrong one.
150
+ if (session.opencodeProsePart && p.partID && p.partID !== session.opencodeProsePart) return false;
151
+ if (!session.sayingAt) session.sayingAt = Date.now();
152
+ const next = (session.saying ?? '') + piece;
153
+ session.saying = next.length > SAYING_CAP ? '' : next;
154
+ return true;
155
+ } catch {
156
+ return false;
157
+ }
158
+ }
159
+
160
+ /** Which part the prose is going into, so reasoning deltas can be told apart. */
161
+ export function rememberOpenCodeProsePart(session, type, properties) {
162
+ if (!session || type !== 'message.part.updated') return;
163
+ const part = partOf(properties);
164
+ if (part?.type === 'text' && typeof part.id === 'string') session.opencodeProsePart = part.id;
165
+ if (part?.type === 'step-start') session.opencodeProsePart = null;
166
+ }
167
+
168
+ /** One short line for the row: what it just did. */
169
+ export function summarizeOpenCodeEvent(type, properties) {
170
+ try {
171
+ const p = properties ?? {};
172
+ if (type !== 'message.part.updated') return null;
173
+ const part = partOf(p);
174
+ if (!part) return null;
175
+ if (part.type === 'text') {
176
+ const text = String(part.text ?? '').trim();
177
+ return text ? text.slice(0, 300) : null;
178
+ }
179
+ if (part.type === 'tool' && part.state?.status === 'completed') {
180
+ const word = toolWord(part.tool);
181
+ return word ? `tool: ${word}` : null;
182
+ }
183
+ return null;
184
+ } catch {
185
+ return null;
186
+ }
187
+ }
188
+
189
+ /** One line for the run trace, the record she can open afterwards. */
190
+ export function traceOpenCodeEvent(type, properties) {
191
+ try {
192
+ const p = properties ?? {};
193
+ const at = new Date().toISOString().slice(11, 19);
194
+
195
+ if (type === 'session.error') {
196
+ const text = openCodeErrorText(p.error);
197
+ return text ? `${at} [error] ${text.slice(0, 300)}` : null;
198
+ }
199
+
200
+ if (type !== 'message.part.updated') return null;
201
+ const part = partOf(p);
202
+ if (!part) return null;
203
+
204
+ if (part.type === 'text') {
205
+ const text = String(part.text ?? '').trim();
206
+ return text ? `${at} ${text}` : null;
207
+ }
208
+
209
+ if (part.type === 'tool' && (part.state?.status === 'completed' || part.state?.status === 'error')) {
210
+ const word = toolWord(part.tool);
211
+ if (!word) return null;
212
+ const input = part.state?.input ?? {};
213
+ const detail = String(input.command ?? input.filePath ?? input.path ?? input.pattern ?? '').trim();
214
+ const failed = part.state?.status === 'error' ? ' (refused)' : '';
215
+ return `${at} [${word}]${failed}${detail ? ` ${detail.slice(0, 200)}` : ''}`;
216
+ }
217
+
218
+ return null;
219
+ } catch {
220
+ return null;
221
+ }
222
+ }
223
+
224
+ /** What it is doing right now, for the row's activity line. */
225
+ export function openCodeActivity(type, properties) {
226
+ try {
227
+ if (type !== 'message.part.updated') return null;
228
+ const part = partOf(properties);
229
+ if (part?.type !== 'tool') return null;
230
+ if (part.state?.status !== 'pending' && part.state?.status !== 'running') return null;
231
+ const word = toolWord(part.tool);
232
+ return word ? { tool: word } : null;
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * THE APPROVAL, IN THE WORDS A CARD NEEDS.
240
+ *
241
+ * `bash` asks carry `metadata.command`. `edit` asks carry `metadata.filepath`
242
+ * AND `metadata.diff`, a whole unified diff of the change it wants to make --
243
+ * measured, not assumed. That is strictly more than a Codex patch approval
244
+ * arrives with, and it means an OpenCode card can show the change itself
245
+ * rather than a filename.
246
+ */
247
+ export function openCodeApprovalCard(properties) {
248
+ const p = properties ?? {};
249
+ const kind = typeof p.permission === 'string' ? p.permission : '';
250
+ const metadata = p.metadata ?? {};
251
+ const command = typeof metadata.command === 'string' ? metadata.command.trim() : '';
252
+ const filepath = typeof metadata.filepath === 'string' ? metadata.filepath.trim() : '';
253
+ const diff = typeof metadata.diff === 'string' ? metadata.diff : '';
254
+ const patterns = Array.isArray(p.patterns) ? p.patterns.filter(x => typeof x === 'string') : [];
255
+
256
+ if (command) return { kind: kind || 'bash', title: 'Run a command', detail: command, command, patterns };
257
+ if (filepath) return { kind: kind || 'edit', title: 'Change a file', detail: filepath, filepath, diff, patterns };
258
+ return { kind: kind || 'tool', title: 'Use a tool', detail: patterns.join(', ') || kind || 'a tool', patterns };
259
+ }
260
+
261
+ /**
262
+ * THE PERMISSION POLICY A WORKER RUNS UNDER, as OpenCode's own config.
263
+ *
264
+ * This is the whole reason Agentbox can draw a card for OpenCode at all.
265
+ * Measured 2026-10-07: with no permission config the server runs shell
266
+ * commands and writes files with no card; with this config it raises
267
+ * `permission.asked` and waits. The server reports back what it loaded at
268
+ * `GET /config`, which is how the adapter can tell the policy really took.
269
+ *
270
+ * `read`, `glob`, `grep` and `list` are deliberately left alone: a card for
271
+ * every file read is the 2026-08-05 card flood again, and reading inside the
272
+ * task's own folder is what the folder is for.
273
+ */
274
+ export function openCodePermissionConfig() {
275
+ return {
276
+ $schema: 'https://opencode.ai/config.json',
277
+ permission: {
278
+ bash: 'ask',
279
+ edit: 'ask',
280
+ webfetch: 'ask',
281
+ websearch: 'ask',
282
+ external_directory: 'ask',
283
+ task: 'allow',
284
+ todowrite: 'allow',
285
+ },
286
+ };
287
+ }