agentbox-app 0.1.12 → 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 (214) hide show
  1. package/main/acp.mjs +296 -0
  2. package/main/agent-homes.mjs +158 -0
  3. package/main/agent-sessions.mjs +57 -20
  4. package/main/agent-updates.mjs +9 -22
  5. package/main/analytics.mjs +30 -0
  6. package/main/claude-bin.mjs +45 -311
  7. package/main/claude-input.mjs +9 -3
  8. package/main/claude-launch-env.mjs +54 -0
  9. package/main/claude-models.mjs +39 -7
  10. package/main/claude-plan.mjs +16 -16
  11. package/main/claude-usage.mjs +82 -25
  12. package/main/codex-bin.mjs +51 -249
  13. package/main/codex-session.mjs +38 -3
  14. package/main/codex-threads.mjs +41 -8
  15. package/main/codex-watch.mjs +67 -3
  16. package/main/config.mjs +26 -4
  17. package/main/corner-tag.mjs +489 -0
  18. package/main/crash-report.mjs +26 -3
  19. package/main/engine-bin.mjs +595 -0
  20. package/main/engine-setup.mjs +4 -15
  21. package/main/feedback.mjs +11 -6
  22. package/main/harnesses/claude-stream.mjs +208 -0
  23. package/main/harnesses/claude.mjs +84 -0
  24. package/main/harnesses/codex.mjs +65 -0
  25. package/main/harnesses/common.mjs +78 -0
  26. package/main/harnesses/instructions-file.mjs +116 -0
  27. package/main/harnesses/opencode.mjs +234 -0
  28. package/main/harnesses.mjs +65 -0
  29. package/main/importable-threads.mjs +38 -0
  30. package/main/ipc.mjs +165 -31
  31. package/main/local-app-frames.mjs +82 -0
  32. package/main/main.mjs +100 -4
  33. package/main/memory-gate-history.mjs +51 -12
  34. package/main/memory-gate-server.mjs +67 -11
  35. package/main/memory-gate.mjs +49 -8
  36. package/main/memory-log.mjs +124 -0
  37. package/main/notify.mjs +47 -4
  38. package/main/opencode-server.mjs +271 -0
  39. package/main/opencode.mjs +287 -0
  40. package/main/phone-link.mjs +551 -0
  41. package/main/relay-address.mjs +28 -0
  42. package/main/relay-client.mjs +246 -0
  43. package/main/row-label.mjs +6 -6
  44. package/main/runs-on.mjs +32 -35
  45. package/main/serve.mjs +113 -9
  46. package/main/settings.mjs +148 -10
  47. package/main/shell-path.mjs +86 -0
  48. package/main/ship-queue.mjs +24 -6
  49. package/main/sign-in-files.mjs +3 -7
  50. package/main/source-updater.mjs +27 -1
  51. package/main/start-hold.mjs +73 -0
  52. package/main/store.mjs +1 -1
  53. package/main/supervisor.mjs +832 -428
  54. package/main/task-commands.mjs +13 -17
  55. package/main/task-folders-offthread.mjs +6 -1
  56. package/main/task-folders.mjs +490 -98
  57. package/main/task-terminals.mjs +37 -7
  58. package/main/updater.mjs +71 -9
  59. package/main/worktree-include.mjs +494 -0
  60. package/package.json +10 -1
  61. package/renderer/dist/assets/abnfDiagram-YUKMZFIV-DPPuMWt-.js +1 -0
  62. package/renderer/dist/assets/arc-HmFfdseH.js +1 -0
  63. package/renderer/dist/assets/architectureDiagram-47P4ROYG-DxJ3zHrH.js +36 -0
  64. package/renderer/dist/assets/blockDiagram-E7TT5TSB-CbTXzOqQ.js +140 -0
  65. package/renderer/dist/assets/c4Diagram-CRA5TL53-X5ItFyLn.js +38 -0
  66. package/renderer/dist/assets/channel-BlT2g9Sz.js +1 -0
  67. package/renderer/dist/assets/chunk-2Q5K7J3B-DC12Ho_L.js +1 -0
  68. package/renderer/dist/assets/chunk-5VM5RSS4-ClVtfPd0.js +15 -0
  69. package/renderer/dist/assets/chunk-7TKQ45FW-DgcOm0HT.js +1 -0
  70. package/renderer/dist/assets/chunk-AW2ZBBNX-C01s4yoF.js +88 -0
  71. package/renderer/dist/assets/chunk-FVRAUYC3-Bj0zxhR4.js +2 -0
  72. package/renderer/dist/assets/chunk-HTAEGDNF-DWDeEhLU.js +1 -0
  73. package/renderer/dist/assets/chunk-JWPE2WC7-Dsqwv3eW.js +1 -0
  74. package/renderer/dist/assets/chunk-KQW6MTUR-CE86ANL2.js +1 -0
  75. package/renderer/dist/assets/chunk-XXDRQBXY-CoNTASfs.js +1 -0
  76. package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-2wprLqtw.js +217 -0
  77. package/renderer/dist/assets/cornerTag-BLrxiDE5.css +1 -0
  78. package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +1 -0
  79. package/renderer/dist/assets/cose-bilkent-JH36ORCC-enqMiYbn.js +1 -0
  80. package/renderer/dist/assets/cynefin-EF2NZ3EQ-DHngLxvQ.js +141 -0
  81. package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-C0rZZIl3.js +62 -0
  82. package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
  83. package/renderer/dist/assets/dagre-W4DXFKR2-DQnaEWO7.js +4 -0
  84. package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  85. package/renderer/dist/assets/diagram-2UJZ2QOL-CS8AlGPx.js +200 -0
  86. package/renderer/dist/assets/diagram-OVF4WLC6-yiDMHIkY.js +24 -0
  87. package/renderer/dist/assets/diagram-PFPMY2P6-XbQKzbBA.js +30 -0
  88. package/renderer/dist/assets/diagram-UMYRVEAY-dhVvlfl_.js +24 -0
  89. package/renderer/dist/assets/diagram-YEKJPTXX-DZHe8SCf.js +41 -0
  90. package/renderer/dist/assets/diagram-ZIFT7M5P-B5h6aXVR.js +3 -0
  91. package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-PR9ScDco.js +1 -0
  92. package/renderer/dist/assets/elk-IJKZMXRS-Cg-yQXPd.js +27 -0
  93. package/renderer/dist/assets/erDiagram-O2IAPWRE-omOrJMxu.js +99 -0
  94. package/renderer/dist/assets/flowDiagram-OXPTDLAJ-aOX2hzvT.js +213 -0
  95. package/renderer/dist/assets/ganttDiagram-R7TSEDQI-B1H3pWyI.js +292 -0
  96. package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-Cvw304HC.js +106 -0
  97. package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
  98. package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-_ZXcNViH.js +2 -0
  99. package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
  100. package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js +70 -0
  101. package/renderer/dist/assets/journeyDiagram-COXZFDF6-fr62QoAt.js +139 -0
  102. package/renderer/dist/assets/kanban-definition-P3RFRI5V-J8zsabCe.js +89 -0
  103. package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
  104. package/renderer/dist/assets/linear-BmNdEtIi.js +1 -0
  105. package/renderer/dist/assets/main-ChNdnUvt.js +1132 -0
  106. package/renderer/dist/assets/main-nj0QR5kg.css +32 -0
  107. package/renderer/dist/assets/mermaid.core-CnYzvFkz.js +314 -0
  108. package/renderer/dist/assets/mindmap-definition-LZFPQGKD-Bfcxc7PM.js +96 -0
  109. package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  110. package/renderer/dist/assets/pegDiagram-BGZESJAR-CwUJOq6X.js +1 -0
  111. package/renderer/dist/assets/pieDiagram-CAPJLFHJ-BrwMmqfC.js +39 -0
  112. package/renderer/dist/assets/product-name-CTKLYNWY.js +40 -0
  113. package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-BN4Stoj-.js +7 -0
  114. package/renderer/dist/assets/railroadDiagram-SM67HX2B-CPZ5bbeR.js +1 -0
  115. package/renderer/dist/assets/requirementDiagram-X7JNWC4A-CgRnimVe.js +84 -0
  116. package/renderer/dist/assets/sankeyDiagram-UM26HJRW-Bt2rej__.js +40 -0
  117. package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js +169 -0
  118. package/renderer/dist/assets/sizeCapture-INFHLROL-usyNyXyh.js +1 -0
  119. package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-CRzUBb0u.js +291 -0
  120. package/renderer/dist/assets/swimlanes-N4OXWK64-C1GJvMS0.js +1 -0
  121. package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +8 -0
  122. package/renderer/dist/assets/timeline-definition-YOQAKGHF-caeyPlcc.js +120 -0
  123. package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-BN_SV6PC.js +370 -0
  124. package/renderer/dist/assets/vennDiagram-BLWOH2XV-BLGuaUX-.js +34 -0
  125. package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-Da0cJeNc.js +78 -0
  126. package/renderer/dist/assets/xychartDiagram-TAQBALBS-cGfUffMk.js +7 -0
  127. package/renderer/dist/corner-tag.html +13 -0
  128. package/renderer/dist/emulator/audio-rules.mjs +74 -0
  129. package/renderer/dist/emulator/data/compression/README.md +11 -0
  130. package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
  131. package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
  132. package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
  133. package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
  134. package/renderer/dist/emulator/data/cores/README.md +22 -0
  135. package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
  136. package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
  137. package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
  138. package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
  139. package/renderer/dist/emulator/data/cores/package.json +65 -0
  140. package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
  141. package/renderer/dist/emulator/data/emulator.css +1614 -0
  142. package/renderer/dist/emulator/data/loader.js +170 -0
  143. package/renderer/dist/emulator/data/localization/README.md +63 -0
  144. package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
  145. package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
  146. package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
  147. package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
  148. package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
  149. package/renderer/dist/emulator/data/localization/en-US.json +339 -0
  150. package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
  151. package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
  152. package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
  153. package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
  154. package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
  155. package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
  156. package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
  157. package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
  158. package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
  159. package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
  160. package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
  161. package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
  162. package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
  163. package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
  164. package/renderer/dist/emulator/data/src/GameManager.js +464 -0
  165. package/renderer/dist/emulator/data/src/compression.js +142 -0
  166. package/renderer/dist/emulator/data/src/emulator.js +6158 -0
  167. package/renderer/dist/emulator/data/src/gamepad.js +167 -0
  168. package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
  169. package/renderer/dist/emulator/data/src/shaders.js +236 -0
  170. package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
  171. package/renderer/dist/emulator/data/src/storage.js +125 -0
  172. package/renderer/dist/emulator/data/version.json +5 -0
  173. package/renderer/dist/emulator/host.js +297 -0
  174. package/renderer/dist/emulator/index.html +28 -0
  175. package/renderer/dist/emulator/provenance.json +6 -0
  176. package/renderer/dist/index.html +16 -3
  177. package/scripts/approval-server.sh +24 -5
  178. package/scripts/before-publish.mjs +12 -10
  179. package/shared/agent-import.mjs +2 -0
  180. package/shared/agents.d.mts +1 -0
  181. package/shared/bridge-map.mjs +18 -0
  182. package/shared/claude-commands.generated.mjs +6 -4
  183. package/shared/claude-models.generated.mjs +6 -4
  184. package/shared/corner-tag.d.mts +14 -0
  185. package/shared/corner-tag.mjs +141 -0
  186. package/shared/engines.mjs +20 -5
  187. package/shared/harness-definitions.mjs +103 -0
  188. package/shared/notify-rules.d.mts +25 -0
  189. package/shared/notify-rules.mjs +19 -0
  190. package/shared/provider-commands.mjs +10 -2
  191. package/shared/rank.d.mts +2 -0
  192. package/shared/rank.mjs +50 -0
  193. package/shared/relay-address.mjs +6 -0
  194. package/shared/relay-crypto.mjs +165 -0
  195. package/shared/runs-on.d.mts +0 -10
  196. package/shared/runs-on.mjs +25 -49
  197. package/shared/shortcuts.mjs +10 -4
  198. package/shared/spawn-trouble.mjs +57 -6
  199. package/shared/terminal-input.d.mts +1 -0
  200. package/shared/terminal-input.mjs +30 -0
  201. package/shared/terminal-keys.d.mts +1 -0
  202. package/shared/terminal-keys.mjs +16 -0
  203. package/shared/terminal-size.d.mts +4 -0
  204. package/shared/terminal-size.mjs +29 -0
  205. package/shared/usage.d.mts +3 -0
  206. package/shared/usage.mjs +7 -0
  207. package/shared/where-it-runs-from.d.mts +43 -0
  208. package/shared/where-it-runs-from.mjs +155 -0
  209. package/shared/work-items.mjs +25 -1
  210. package/shared/work-lines.mjs +1 -1
  211. package/shared/xterm-invalid-range.d.mts +2 -0
  212. package/shared/xterm-invalid-range.mjs +58 -0
  213. package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
  214. package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
@@ -0,0 +1,234 @@
1
+ // THE OPENCODE ADAPTER, AND IT IS THE POINT OF THE ENGINE ROW.
2
+ //
3
+ // The engine proposal (w-380b06bf53) counted 96 `=== 'codex'` or
4
+ // `=== 'claude'` branch points across 12 files and claimed one interface would
5
+ // turn the next harness into one new module. This is the test of that claim,
6
+ // and the answer is nearly yes: OpenCode is this file, main/opencode.mjs,
7
+ // main/opencode-server.mjs, one entry of data in shared/harness-definitions,
8
+ // one line registering it in main/harnesses.mjs -- and one filter in
9
+ // shared/engines.mjs, which was NOT free and is written up there. No existing
10
+ // file grew an engine branch.
11
+ //
12
+ // WHAT IS DECLARED MISSING RATHER THAN FAKED. `usage` is absent, so the
13
+ // registry answers `{ supported: false }` for it: OpenCode bills through
14
+ // whichever provider the user connected and has no endpoint that reports a
15
+ // plan limit. Per-run tokens and cost do arrive and are read
16
+ // (main/opencode.mjs), but "what is left" is a question nothing can answer, and
17
+ // docs/harnesses.md says never to mark missing usage as zero. `attachInput` is
18
+ // absent too: there is a `/tui/append-prompt` endpoint but it drives the
19
+ // terminal UI, not a headless run, so remoteControl is false.
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+ import { spawn } from 'node:child_process';
23
+ import { harnessDefinition } from '../../shared/harness-definitions.mjs';
24
+ import { openCodeClient, createOpenCodeWorker, splitModel } from '../opencode-server.mjs';
25
+ import {
26
+ captureOpenCodeEvent, openCodeStreamingText, summarizeOpenCodeEvent,
27
+ traceOpenCodeEvent, openCodeActivity, rememberOpenCodeProsePart,
28
+ openCodePermissionConfig,
29
+ } from '../opencode.mjs';
30
+ import { definitionMethods, commonUpdatePlan } from './common.mjs';
31
+
32
+ const definition = harnessDefinition('opencode');
33
+
34
+ /**
35
+ * WHERE THIS WORKER'S PERMISSION POLICY LIVES ON DISK.
36
+ *
37
+ * One file per task folder, inside the folder, so two workers cannot read each
38
+ * other's policy and nothing is written into the user's own OpenCode config.
39
+ * `OPENCODE_CONFIG` points at it. It is rewritten on every start rather than
40
+ * reused, because a stale one is how a run silently stops asking.
41
+ */
42
+ export function writePermissionConfig(cwd) {
43
+ const file = path.join(cwd, '.agentbox-opencode.json');
44
+ fs.writeFileSync(file, `${JSON.stringify(openCodePermissionConfig(), null, 2)}\n`, 'utf8');
45
+ return file;
46
+ }
47
+
48
+ /** The models OpenCode itself says are available, as `provider/model` rows. */
49
+ export function openCodeModelRows(providers) {
50
+ const list = Array.isArray(providers?.providers) ? providers.providers : [];
51
+ const rows = [];
52
+ for (const provider of list) {
53
+ for (const [id, model] of Object.entries(provider?.models ?? {})) {
54
+ rows.push({ id: `${provider.id}/${id}`, label: `${provider.name ?? provider.id} · ${model?.name ?? id}` });
55
+ }
56
+ }
57
+ return rows;
58
+ }
59
+
60
+ export const openCodeHarness = {
61
+ ...definitionMethods(definition),
62
+
63
+ // BINARY DISCOVERY. No bespoke resolver: the binary is whatever the config
64
+ // names, the same question `common.mjs` answers for the other two.
65
+ discover: (config = {}) => config?.[definition.binKey] ?? null,
66
+
67
+ // SIGN-IN. `opencode auth login` writes to OpenCode's own store and
68
+ // `opencode auth list` reports it. Agentbox launches those two and reads
69
+ // neither file, which is the whole of its involvement with credentials.
70
+ //
71
+ // AND SIGNED IN IS NOT REQUIRED. The other two harnesses cannot run without
72
+ // a login; OpenCode can, on the Zen free tier (measured: zero credentials,
73
+ // ten models, a finished run at cost 0). So this answers true rather than
74
+ // gating the run, and a provider that does need a key fails loudly at
75
+ // `session.error` with its own words, which the readers surface.
76
+ signedIn: () => true,
77
+ profiles: () => ['default'],
78
+ profileHome: () => null,
79
+
80
+ // THE WORKER'S ENVIRONMENT. `profileEnv` points OPENCODE_CONFIG at the
81
+ // policy file written for this folder; `workerEnv` (from the definition)
82
+ // strips every provider key first, so nothing we hold can reach it.
83
+ profileEnv: (env, profile) => (profile && profile !== 'default' ? { ...env, [definition.homeEnv]: profile } : env),
84
+ accountKey: () => `${definition.accountPrefix}default`,
85
+ signInFiles: ({ folder, home }) => (folder
86
+ ? definition.credentialFiles.map(file => path.join(folder, file))
87
+ : [path.join(home, '.local/share/opencode/auth.json')]),
88
+
89
+ // MODELS. Asked of the running server rather than kept in a list here, for
90
+ // the reason Conductor's own docs give for doing the same: the set depends
91
+ // entirely on what the user connected, and we do not know it.
92
+ async models({ client } = {}) {
93
+ if (!client) return { supported: false, engine: definition.id, capability: 'models' };
94
+ try { return openCodeModelRows(await client.providers()); }
95
+ catch { return { supported: false, engine: definition.id, capability: 'models' }; }
96
+ },
97
+ defaultModel: () => null,
98
+ workspaceModel: () => null,
99
+ effortLevels: () => ({ supported: false, engine: definition.id, capability: 'effortLevels' }),
100
+
101
+ tooling: () => null,
102
+
103
+ /**
104
+ * ONE RUN. A server for this folder, then a worker on a session in it.
105
+ *
106
+ * The server is started per folder and killed with the worker. `--port 0`
107
+ * is not used: OpenCode picks a random port when none is given and prints
108
+ * the address it chose, which is what `openCodeServerAddress` waits for.
109
+ */
110
+ spawn(sup, plan, { cwd, item, profile = 'default', env }) {
111
+ const configFile = writePermissionConfig(cwd);
112
+ const password = sup._openCodePassword?.() ?? null;
113
+ const server = spawn(sup.config[definition.binKey], ['serve', '--hostname', '127.0.0.1', '--port', '0'], {
114
+ cwd,
115
+ env: {
116
+ ...env,
117
+ [definition.homeEnv]: configFile,
118
+ ...(password ? { OPENCODE_SERVER_PASSWORD: password } : {}),
119
+ ZERO_PRODUCT: item.product, ZERO_ITEM: item.id,
120
+ },
121
+ stdio: ['ignore', 'pipe', 'pipe'],
122
+ });
123
+
124
+ const worker = createOpenCodeWorker({
125
+ client: openCodeServerClient(server, password),
126
+ plan,
127
+ cwd,
128
+ resumeSessionId: plan.resumeId ?? null,
129
+ title: item.id,
130
+ onApproval: sup._openCodeApprovals?.(item) ?? null,
131
+ });
132
+ worker.once('exit', () => { try { server.kill(); } catch { /* already gone */ } });
133
+ worker.server = server;
134
+ return worker;
135
+ },
136
+
137
+ readers: () => ({
138
+ capture: captureOpenCodeEvent,
139
+ stream: openCodeStreamingText,
140
+ summarize: summarizeOpenCodeEvent,
141
+ trace: traceOpenCodeEvent,
142
+ activity: openCodeActivity,
143
+ }),
144
+
145
+ // Native names through, untranslated, as the Codex adapter does. The prose
146
+ // part is remembered first so a reasoning delta is never streamed as prose.
147
+ subscribe(child, session, absorb) {
148
+ child.on('event', (type, properties) => {
149
+ rememberOpenCodeProsePart(session, type, properties);
150
+ absorb(type, properties);
151
+ });
152
+ },
153
+
154
+ transcript: () => null,
155
+ change: session => (session?.opencodeChange?.length ? { files: [...session.opencodeChange] } : null),
156
+ release: () => {},
157
+
158
+ // COMPACTION. `POST /session/:id/summarize` -- measured, returns true.
159
+ compact: (sup, rec) => (rec?.sessionId && rec.client
160
+ ? rec.client.summarize(rec.sessionId, rec.model ?? null)
161
+ : Promise.resolve(false)),
162
+
163
+ // SLASH COMMANDS. `GET /command` lists them and
164
+ // `POST /session/:id/command` runs one, both first-party.
165
+ runCommand: (sup, { client, sessionId, name, args = '' } = {}) => (client && sessionId
166
+ ? client.runCommand(sessionId, { command: name, arguments: args })
167
+ : Promise.resolve(null)),
168
+
169
+ // VERSION UPDATES. `opencode upgrade` is its own updater and knows how it
170
+ // was installed; brew and npm installs are still caught by the common plan
171
+ // first so the user's package manager stays in charge where it is the owner.
172
+ updatePlan: options => commonUpdatePlan(definition, options, ({ bin, real, name }) => (
173
+ path.isAbsolute(bin) && real
174
+ ? { name, file: bin, args: ['upgrade'], feed: 'https://api.github.com/repos/sst/opencode/releases/latest', format: 'text' }
175
+ : null)),
176
+ updateSettings: () => ({}),
177
+
178
+ smallModelArgs: (prompt, model) => ['run', '--format', 'json', ...(model ? ['--model', model] : []), prompt],
179
+ memoryGate: () => ({}),
180
+ };
181
+
182
+ /** A client bound to a server whose port is only known once it says so. */
183
+ export function openCodeServerClient(server, password = null) {
184
+ const address = openCodeServerAddress(server, { password });
185
+ const bind = method => async (...args) => {
186
+ const client = await address;
187
+ return client[method](...args);
188
+ };
189
+ const client = {};
190
+ for (const method of ['createSession', 'prompt', 'abort', 'replyPermission', 'summarize', 'runCommand', 'providers', 'commands', 'config', 'health']) {
191
+ client[method] = bind(method);
192
+ }
193
+ client.events = ({ signal } = {}) => ({
194
+ close: () => {},
195
+ async *[Symbol.asyncIterator]() {
196
+ const real = await address;
197
+ const stream = real.events({ signal });
198
+ client.events.close = () => stream.close();
199
+ yield* stream;
200
+ },
201
+ });
202
+ return client;
203
+ }
204
+
205
+ /**
206
+ * THE ADDRESS THE SERVER CHOSE, READ OFF ITS OWN FIRST LINE.
207
+ *
208
+ * `opencode serve` prints "opencode server listening on http://127.0.0.1:PORT"
209
+ * and nothing else before it is ready -- measured. Asking for port 0 and
210
+ * reading the answer is how two workers on one Mac never collide, which
211
+ * guessing a port does not survive.
212
+ */
213
+ export function openCodeServerAddress(server, { password = null, timeoutMs = 30_000 } = {}) {
214
+ return new Promise((resolve, reject) => {
215
+ let text = '';
216
+ const timer = setTimeout(() => reject(Error('OpenCode did not say which port it was listening on')), timeoutMs);
217
+ const done = outcome => {
218
+ clearTimeout(timer);
219
+ server.stdout?.off?.('data', look);
220
+ server.stderr?.off?.('data', look);
221
+ outcome();
222
+ };
223
+ const look = data => {
224
+ text += data.toString();
225
+ const found = text.match(/https?:\/\/[0-9.]+:\d+/);
226
+ if (found) done(() => resolve(openCodeClient({ baseUrl: found[0], password })));
227
+ };
228
+ server.stdout?.on?.('data', look);
229
+ server.stderr?.on?.('data', look);
230
+ server.once('exit', code => done(() => reject(Error(`OpenCode's server stopped before it was ready (${code})`))));
231
+ });
232
+ }
233
+
234
+ export { splitModel };
@@ -0,0 +1,65 @@
1
+ // One registry, no user-installed plugin execution or automatic enrollment.
2
+ // Optional reads report absence; unsupported actions fail before side effects.
3
+ import { claudeHarness } from './harnesses/claude.mjs';
4
+ import { codexHarness } from './harnesses/codex.mjs';
5
+ import { openCodeHarness } from './harnesses/opencode.mjs';
6
+
7
+ export const HARNESS_OPERATIONS = Object.freeze([
8
+ 'discover', 'binary', 'workerArgs', 'install', 'signIn', 'signInStatus', 'signInFiles',
9
+ 'workerEnv', 'launchEnv', 'profileEnv', 'profiles', 'signedIn', 'accountKey',
10
+ 'models', 'defaultModel', 'workspaceModel', 'profileHome', 'effortLevels', 'usage', 'spawn', 'readers',
11
+ 'subscribe', 'attachInput', 'tooling', 'transcript', 'change', 'release',
12
+ 'compact', 'settleApproval', 'approvalChanged', 'memoryGate', 'updatePlan',
13
+ 'smallModelArgs', 'updateSettings', 'runCommand', 'instructions',
14
+ ]);
15
+ const OPTIONAL_READS = new Set(['usage', 'models', 'effortLevels']);
16
+ export function createHarnessRegistry(adapters) {
17
+ const entries = new Map();
18
+ for (const adapter of adapters) {
19
+ if (!adapter || typeof adapter.id !== 'string' || !/^[a-z][a-z0-9-]*$/.test(adapter.id) || !adapter.label) throw Error('A harness needs an id and label.');
20
+ if (entries.has(adapter.id)) throw Error(`Duplicate harness: ${adapter.id}`);
21
+ for (const [name, value] of Object.entries(adapter.capabilities ?? {})) {
22
+ if (typeof value !== 'boolean') throw Error(`${adapter.id}.${name} must be a boolean.`);
23
+ }
24
+ if (adapter.capabilities?.remoteControl && typeof adapter.attachInput !== 'function') throw Error(`${adapter.id}.remoteControl requires attachInput.`);
25
+ if (adapter.available != null && typeof adapter.available !== 'function') throw Error(`${adapter.id}.available must be a function or absent.`);
26
+ // WHAT IT CAN DO IS WHAT IT SAYS, NOT WHAT IT HAS A METHOD FOR. Having the
27
+ // method was the whole truth while every adapter was written by hand for
28
+ // one tool. One adapter serving several tools over a shared protocol
29
+ // breaks it: it implements every operation for all of them, and what THIS
30
+ // tool can do is what it negotiated when the session opened. So an adapter
31
+ // may declare an operation unavailable, and a declined one is refused
32
+ // exactly as a missing one is — before it does anything.
33
+ const declines = name => adapter.available ? adapter.available(name) !== true : false;
34
+ const complete = { ...adapter };
35
+ for (const name of HARNESS_OPERATIONS) {
36
+ if (adapter[name] != null && typeof adapter[name] !== 'function') throw Error(`${adapter.id}.${name} must be a function or absent.`);
37
+ const absent = (..._args) => {
38
+ if (OPTIONAL_READS.has(name)) return { supported: false, engine: adapter.id, capability: name };
39
+ throw Error(`${adapter.label} does not support ${name}.`);
40
+ };
41
+ const own = complete[name];
42
+ complete[name] = own ? ((...args) => declines(name) ? absent(...args) : own(...args)) : absent;
43
+ }
44
+ entries.set(adapter.id, { adapter, complete: Object.freeze(complete) });
45
+ }
46
+ return Object.freeze({
47
+ get(id = 'claude') {
48
+ const entry = entries.get(id);
49
+ if (!entry) throw Error(`Unknown coding agent: ${String(id)}`);
50
+ return entry.complete;
51
+ },
52
+ supports(id, capability) {
53
+ const adapter = entries.get(id)?.adapter;
54
+ if (typeof adapter?.[capability] !== 'function') return false;
55
+ return adapter.available ? adapter.available(capability) === true : true;
56
+ },
57
+ });
58
+ }
59
+ // REGISTERED IS NOT OFFERED. OpenCode is here so anything that asks for it by
60
+ // name gets the real adapter, and it is deliberately absent from `ENGINES`
61
+ // (shared/engines.mjs) so no row can choose it and no picker draws it until
62
+ // that is turned on deliberately. The two decisions are separate on purpose.
63
+ const registry = createHarnessRegistry([claudeHarness, codexHarness, openCodeHarness]);
64
+ export const harnessFor = id => registry.get(id);
65
+ export const supportsHarnessOperation = (id, name) => registry.supports(id, name);
@@ -0,0 +1,38 @@
1
+ // WHAT THE IMPORT CARD IS HANDED, in one function, so the read setup's last
2
+ // card depends on can be tested whole rather than through a copy of it
3
+ // (w-e46f2e9cea, 2026-10-11: "confirm this works during onboarding, that's the
4
+ // critical location"). `zero:agent-threads` in main/ipc.mjs is this and the
5
+ // `imported` marks; the walk's finish card and the ⌘K card both read it.
6
+ //
7
+ // Every folder either tool keeps sessions in (main/agent-homes.mjs), both
8
+ // readers, newest first. A reader that throws costs its own half, never the
9
+ // other one.
10
+ //
11
+ // AND WHEN THE LAST TEN DAYS HOLD NOTHING, THE NEWEST OLDER ONES. Setup skips
12
+ // its last card when it has nothing to offer, so somebody whose newest session
13
+ // was eleven days old was never told importing exists. The ten days stay the
14
+ // list whenever they hold anything; only an empty one reaches back, ninety days
15
+ // at most and the twenty newest, and the card says so (`listLine` in
16
+ // renderer/src/agent-import-card.ts).
17
+
18
+ import os from 'node:os';
19
+ import { appSessionFolders } from './agent-homes.mjs';
20
+ import { RECENT_DAYS, readSessionThreads } from './agent-sessions.mjs';
21
+ import { readCodexThreads } from './codex-threads.mjs';
22
+
23
+ export const OLDER_DAYS = 90;
24
+ export const OLDER_MOST = 20;
25
+
26
+ export function readImportableThreads({ config = null, supervisor = null, env = process.env, shellEnv, home = os.homedir(), now = Date.now() } = {}) {
27
+ const { dirs, codexDirs } = appSessionFolders({ config, supervisor, env, home, ...(shellEnv ? { shellEnv } : {}) });
28
+ const read = (days, max) => {
29
+ let claude = [];
30
+ let codex = [];
31
+ try { ({ threads: claude } = readSessionThreads({ home, dirs, now, days, ...(max ? { max } : {}) })); } catch { claude = []; }
32
+ try { ({ threads: codex } = readCodexThreads({ home, codexDirs, now, days })); } catch { codex = []; }
33
+ return [...claude, ...codex].sort((a, b) => (b.when ?? 0) - (a.when ?? 0));
34
+ };
35
+ const recent = read(RECENT_DAYS);
36
+ if (recent.length) return recent;
37
+ return read(OLDER_DAYS, OLDER_MOST).slice(0, OLDER_MOST);
38
+ }