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.
- package/README.md +24 -16
- package/bin/agentbox.mjs +18 -9
- package/briefs/message-rules.md +15 -4
- package/main/account-tooling.mjs +2 -1
- package/main/acp.mjs +296 -0
- package/main/agent-homes.mjs +158 -0
- package/main/agent-sessions.mjs +57 -20
- package/main/agent-updates.mjs +9 -20
- package/main/analytics.mjs +30 -0
- package/main/approvals.mjs +14 -5
- package/main/claude-bin.mjs +45 -310
- package/main/claude-input.mjs +9 -3
- package/main/claude-launch-env.mjs +54 -0
- package/main/claude-models.mjs +39 -7
- package/main/claude-plan.mjs +14 -2
- package/main/claude-usage.mjs +82 -25
- package/main/codex-bin.mjs +51 -248
- package/main/codex-launch-env.mjs +31 -0
- package/main/codex-session.mjs +38 -3
- package/main/codex-threads.mjs +41 -8
- package/main/codex-watch.mjs +67 -3
- package/main/config.mjs +40 -13
- package/main/corner-tag.mjs +489 -0
- package/main/crash-report.mjs +26 -3
- package/main/demo.mjs +17 -14
- package/main/engine-bin.mjs +595 -0
- package/main/engine-setup.mjs +27 -27
- package/main/feedback.mjs +11 -6
- package/main/fresh-user.mjs +1 -1
- package/main/git-change.mjs +8 -2
- package/main/harnesses/claude-stream.mjs +208 -0
- package/main/harnesses/claude.mjs +84 -0
- package/main/harnesses/codex.mjs +65 -0
- package/main/harnesses/common.mjs +78 -0
- package/main/harnesses/instructions-file.mjs +116 -0
- package/main/harnesses/opencode.mjs +234 -0
- package/main/harnesses.mjs +65 -0
- package/main/importable-threads.mjs +38 -0
- package/main/instruction-settings.mjs +47 -11
- package/main/ipc.mjs +175 -22
- package/main/local-app-frames.mjs +82 -0
- package/main/machine.mjs +1 -1
- package/main/main.mjs +120 -5
- package/main/memory-gate-history.mjs +51 -12
- package/main/memory-gate-server.mjs +67 -11
- package/main/memory-gate.mjs +49 -8
- package/main/memory-log.mjs +124 -0
- package/main/notify.mjs +48 -5
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-link.mjs +551 -0
- package/main/refresh-agent-models.mjs +2 -1
- package/main/relay-address.mjs +28 -0
- package/main/relay-client.mjs +246 -0
- package/main/row-label.mjs +8 -7
- package/main/runs-on.mjs +90 -0
- package/main/serve.mjs +124 -10
- package/main/settings.mjs +171 -11
- package/main/shell-path.mjs +86 -0
- package/main/ship-queue.mjs +25 -7
- package/main/sign-in-files.mjs +3 -7
- package/main/source-updater.mjs +27 -1
- package/main/start-hold.mjs +73 -0
- package/main/store/home.mjs +37 -5
- package/main/store/project.mjs +15 -19
- package/main/store.mjs +10 -4
- package/main/supervisor.mjs +1030 -447
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +491 -82
- package/main/task-terminals.mjs +49 -8
- package/main/team/index.mjs +2 -2
- package/main/team/projects.mjs +1 -1
- package/main/updater.mjs +71 -9
- package/main/worktree-include.mjs +494 -0
- package/mcp/core/page.mjs +1 -1
- package/mcp/core/work.mjs +34 -3
- package/mcp/tools.mjs +2 -1
- package/package.json +10 -1
- package/renderer/dist/assets/abnfDiagram-YUKMZFIV-DPPuMWt-.js +1 -0
- package/renderer/dist/assets/arc-HmFfdseH.js +1 -0
- package/renderer/dist/assets/architectureDiagram-47P4ROYG-DxJ3zHrH.js +36 -0
- package/renderer/dist/assets/blockDiagram-E7TT5TSB-CbTXzOqQ.js +140 -0
- package/renderer/dist/assets/c4Diagram-CRA5TL53-X5ItFyLn.js +38 -0
- package/renderer/dist/assets/channel-BlT2g9Sz.js +1 -0
- package/renderer/dist/assets/chunk-2Q5K7J3B-DC12Ho_L.js +1 -0
- package/renderer/dist/assets/chunk-5VM5RSS4-ClVtfPd0.js +15 -0
- package/renderer/dist/assets/chunk-7TKQ45FW-DgcOm0HT.js +1 -0
- package/renderer/dist/assets/chunk-AW2ZBBNX-C01s4yoF.js +88 -0
- package/renderer/dist/assets/chunk-FVRAUYC3-Bj0zxhR4.js +2 -0
- package/renderer/dist/assets/chunk-HTAEGDNF-DWDeEhLU.js +1 -0
- package/renderer/dist/assets/chunk-JWPE2WC7-Dsqwv3eW.js +1 -0
- package/renderer/dist/assets/chunk-KQW6MTUR-CE86ANL2.js +1 -0
- package/renderer/dist/assets/chunk-XXDRQBXY-CoNTASfs.js +1 -0
- package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-2wprLqtw.js +217 -0
- package/renderer/dist/assets/cornerTag-BLrxiDE5.css +1 -0
- package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +1 -0
- package/renderer/dist/assets/cose-bilkent-JH36ORCC-enqMiYbn.js +1 -0
- package/renderer/dist/assets/cynefin-EF2NZ3EQ-DHngLxvQ.js +141 -0
- package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-C0rZZIl3.js +62 -0
- package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
- package/renderer/dist/assets/dagre-W4DXFKR2-DQnaEWO7.js +4 -0
- package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/renderer/dist/assets/diagram-2UJZ2QOL-CS8AlGPx.js +200 -0
- package/renderer/dist/assets/diagram-OVF4WLC6-yiDMHIkY.js +24 -0
- package/renderer/dist/assets/diagram-PFPMY2P6-XbQKzbBA.js +30 -0
- package/renderer/dist/assets/diagram-UMYRVEAY-dhVvlfl_.js +24 -0
- package/renderer/dist/assets/diagram-YEKJPTXX-DZHe8SCf.js +41 -0
- package/renderer/dist/assets/diagram-ZIFT7M5P-B5h6aXVR.js +3 -0
- package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-PR9ScDco.js +1 -0
- package/renderer/dist/assets/elk-IJKZMXRS-Cg-yQXPd.js +27 -0
- package/renderer/dist/assets/erDiagram-O2IAPWRE-omOrJMxu.js +99 -0
- package/renderer/dist/assets/flowDiagram-OXPTDLAJ-aOX2hzvT.js +213 -0
- package/renderer/dist/assets/ganttDiagram-R7TSEDQI-B1H3pWyI.js +292 -0
- package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-Cvw304HC.js +106 -0
- package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
- package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-_ZXcNViH.js +2 -0
- package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
- package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js +70 -0
- package/renderer/dist/assets/journeyDiagram-COXZFDF6-fr62QoAt.js +139 -0
- package/renderer/dist/assets/kanban-definition-P3RFRI5V-J8zsabCe.js +89 -0
- package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
- package/renderer/dist/assets/linear-BmNdEtIi.js +1 -0
- package/renderer/dist/assets/main-ChNdnUvt.js +1132 -0
- package/renderer/dist/assets/main-nj0QR5kg.css +32 -0
- package/renderer/dist/assets/mermaid.core-CnYzvFkz.js +314 -0
- package/renderer/dist/assets/mindmap-definition-LZFPQGKD-Bfcxc7PM.js +96 -0
- package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/renderer/dist/assets/pegDiagram-BGZESJAR-CwUJOq6X.js +1 -0
- package/renderer/dist/assets/pieDiagram-CAPJLFHJ-BrwMmqfC.js +39 -0
- package/renderer/dist/assets/product-name-CTKLYNWY.js +40 -0
- package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-BN4Stoj-.js +7 -0
- package/renderer/dist/assets/railroadDiagram-SM67HX2B-CPZ5bbeR.js +1 -0
- package/renderer/dist/assets/requirementDiagram-X7JNWC4A-CgRnimVe.js +84 -0
- package/renderer/dist/assets/sankeyDiagram-UM26HJRW-Bt2rej__.js +40 -0
- package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js +169 -0
- package/renderer/dist/assets/sizeCapture-INFHLROL-usyNyXyh.js +1 -0
- package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-CRzUBb0u.js +291 -0
- package/renderer/dist/assets/swimlanes-N4OXWK64-C1GJvMS0.js +1 -0
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +8 -0
- package/renderer/dist/assets/timeline-definition-YOQAKGHF-caeyPlcc.js +120 -0
- package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-BN_SV6PC.js +370 -0
- package/renderer/dist/assets/vennDiagram-BLWOH2XV-BLGuaUX-.js +34 -0
- package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-Da0cJeNc.js +78 -0
- package/renderer/dist/assets/xychartDiagram-TAQBALBS-cGfUffMk.js +7 -0
- package/renderer/dist/corner-tag.html +13 -0
- package/renderer/dist/emulator/audio-rules.mjs +74 -0
- package/renderer/dist/emulator/data/compression/README.md +11 -0
- package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
- package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
- package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
- package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
- package/renderer/dist/emulator/data/cores/README.md +22 -0
- package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/package.json +65 -0
- package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
- package/renderer/dist/emulator/data/emulator.css +1614 -0
- package/renderer/dist/emulator/data/loader.js +170 -0
- package/renderer/dist/emulator/data/localization/README.md +63 -0
- package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
- package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
- package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
- package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
- package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
- package/renderer/dist/emulator/data/localization/en-US.json +339 -0
- package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
- package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
- package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
- package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
- package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
- package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
- package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
- package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
- package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
- package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
- package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
- package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
- package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
- package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
- package/renderer/dist/emulator/data/src/GameManager.js +464 -0
- package/renderer/dist/emulator/data/src/compression.js +142 -0
- package/renderer/dist/emulator/data/src/emulator.js +6158 -0
- package/renderer/dist/emulator/data/src/gamepad.js +167 -0
- package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
- package/renderer/dist/emulator/data/src/shaders.js +236 -0
- package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
- package/renderer/dist/emulator/data/src/storage.js +125 -0
- package/renderer/dist/emulator/data/version.json +5 -0
- package/renderer/dist/emulator/host.js +297 -0
- package/renderer/dist/emulator/index.html +28 -0
- package/renderer/dist/emulator/provenance.json +6 -0
- package/renderer/dist/index.html +16 -3
- package/scripts/after-install.mjs +14 -0
- package/scripts/approval-server.sh +31 -7
- package/scripts/before-publish.mjs +12 -10
- package/shared/agent-import.mjs +3 -1
- package/shared/agents.d.mts +1 -0
- package/shared/bridge-map.mjs +19 -0
- package/shared/claude-commands.generated.mjs +8 -6
- package/shared/claude-models.generated.mjs +10 -8
- package/shared/codex-modes.mjs +1 -1
- package/shared/corner-tag.d.mts +14 -0
- package/shared/corner-tag.mjs +141 -0
- package/shared/demo-world.mjs +1 -1
- package/shared/engines.mjs +20 -5
- package/shared/fresh-user-home.mjs +12 -1
- package/shared/harness-definitions.mjs +103 -0
- package/shared/instruction-defaults.json +1 -1
- package/shared/notify-rules.d.mts +25 -0
- package/shared/notify-rules.mjs +19 -0
- package/shared/project-folder-check.mjs +3 -3
- package/shared/provider-commands.mjs +10 -2
- package/shared/rank.d.mts +2 -0
- package/shared/rank.mjs +50 -0
- package/shared/relay-address.mjs +6 -0
- package/shared/relay-crypto.mjs +165 -0
- package/shared/runs-on.d.mts +10 -0
- package/shared/runs-on.mjs +55 -0
- package/shared/shortcuts.mjs +11 -5
- package/shared/spawn-trouble.mjs +58 -7
- package/shared/terminal-input.d.mts +1 -0
- package/shared/terminal-input.mjs +30 -0
- package/shared/terminal-keys.d.mts +1 -0
- package/shared/terminal-keys.mjs +16 -0
- package/shared/terminal-size.d.mts +4 -0
- package/shared/terminal-size.mjs +29 -0
- package/shared/usage.d.mts +3 -0
- package/shared/usage.mjs +7 -0
- package/shared/where-it-runs-from.d.mts +43 -0
- package/shared/where-it-runs-from.mjs +155 -0
- package/shared/work-items.mjs +28 -2
- package/shared/work-lines.mjs +1 -1
- package/shared/xterm-invalid-range.d.mts +2 -0
- package/shared/xterm-invalid-range.mjs +58 -0
- package/renderer/dist/assets/index-B-2Az952.css +0 -32
- package/renderer/dist/assets/index-D6qncgai.js +0 -1168
|
@@ -29,15 +29,39 @@ case "$here" in
|
|
|
29
29
|
*app.asar.unpacked/scripts)
|
|
30
30
|
res="$(cd "$here/../.." && pwd)"
|
|
31
31
|
app="$(cd "$res/.." && pwd)"
|
|
32
|
-
exe="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Info.plist" 2>/dev/null)"
|
|
33
|
-
|
|
32
|
+
exe="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Info.plist" 2>/dev/null || true)"
|
|
33
|
+
# A missing PlistBuddy leaves exe empty, and `[ -x dir/ ]` is true because
|
|
34
|
+
# a directory is executable. Require a regular file, then the one binary
|
|
35
|
+
# sitting in MacOS/.
|
|
36
|
+
if [ ! -f "$app/MacOS/$exe" ] || [ ! -x "$app/MacOS/$exe" ]; then
|
|
37
|
+
exe="$(ls "$app/MacOS" 2>/dev/null | head -1)"
|
|
38
|
+
fi
|
|
34
39
|
exec env ELECTRON_RUN_AS_NODE=1 "$app/MacOS/$exe" "$res/app.asar/main/approval-prompt-server.mjs"
|
|
35
40
|
;;
|
|
36
41
|
*)
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
# RUNNING FROM SOURCE, AND THIS LINE USED TO ASSUME NVM. It prepended
|
|
43
|
+
# $HOME/.nvm/versions/node/<newest>/bin and exec'd node. On a Mac that
|
|
44
|
+
# installed node by Homebrew, Volta, asdf or not at all, that folder is
|
|
45
|
+
# absent, PATH is untouched, and an MCP server booting from a bare login
|
|
46
|
+
# shell has no node on it: the exec failed and every agent died the same
|
|
47
|
+
# four-second death described at the top of this file. Reported from
|
|
48
|
+
# source on 2026-10-07 (issue 21).
|
|
49
|
+
#
|
|
50
|
+
# So the app hands its own binary down in ZERO_APPROVALS_RUNTIME, and
|
|
51
|
+
# ELECTRON_RUN_AS_NODE=1 makes that binary a node, exactly as the packaged
|
|
52
|
+
# branch above does. Nothing on the machine is needed.
|
|
53
|
+
server="$(cd "$here/.." && pwd)/main/approval-prompt-server.mjs"
|
|
54
|
+
if [ -f "$ZERO_APPROVALS_RUNTIME" ] && [ -x "$ZERO_APPROVALS_RUNTIME" ]; then
|
|
55
|
+
exec env ELECTRON_RUN_AS_NODE=1 "$ZERO_APPROVALS_RUNTIME" "$server"
|
|
56
|
+
fi
|
|
57
|
+
# Nobody handed one down, so this is somebody running the script by hand.
|
|
58
|
+
# A node already on PATH wins; nvm's newest is the last resort, for the
|
|
59
|
+
# bare login shell that has never sourced nvm.
|
|
60
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
61
|
+
nvm="$HOME/.nvm/versions/node"
|
|
62
|
+
newest="$(ls "$nvm" 2>/dev/null | tail -1)"
|
|
63
|
+
[ -n "$newest" ] && export PATH="$nvm/$newest/bin:$PATH"
|
|
64
|
+
fi
|
|
65
|
+
exec node "$server"
|
|
42
66
|
;;
|
|
43
67
|
esac
|
|
@@ -69,17 +69,19 @@ function wouldShip(changed) {
|
|
|
69
69
|
// If npm cannot tell us, the honest answer is the cautious one.
|
|
70
70
|
return changed;
|
|
71
71
|
}
|
|
72
|
-
// THE FILES
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
72
|
+
// THE FILES A PUBLISH USED TO REWRITE, which made every attempt poison the
|
|
73
|
+
// next one. `prepack` runs `npm run build`, and the build read the installed
|
|
74
|
+
// Claude Code and wrote what it found over shared/*.generated.*. So a failed
|
|
75
|
+
// publish left those modified and the next publish refused because of dirt the
|
|
76
|
+
// last publish had made. Measured 2026-09-28: one failed attempt left
|
|
77
|
+
// claude-models.generated.mjs changed by a version number, and that alone
|
|
78
|
+
// blocked the retry.
|
|
79
79
|
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// committed
|
|
80
|
+
// THE BUILD NO LONGER WRITES THEM (w-6bbb709b1f): it writes
|
|
81
|
+
// shared/*.local.json, which is gitignored, and `npm run read:claude` is what
|
|
82
|
+
// updates the committed tables. This filter stays as the belt for a folder an
|
|
83
|
+
// OLDER build already dirtied, and because what ships is whatever the build
|
|
84
|
+
// produces on the machine publishing, so a difference here changes nothing.
|
|
83
85
|
return changed.filter((f) => packed.has(f) && !/\.generated\./.test(f));
|
|
84
86
|
}
|
|
85
87
|
|
package/shared/agent-import.mjs
CHANGED
|
@@ -58,7 +58,7 @@ export function agentImportRow(agent, { projectName = null, home = '' } = {}) {
|
|
|
58
58
|
const inProject = projectName ? ` here in ${projectName}` : '';
|
|
59
59
|
const scopeLine = agent?.scope === 'project'
|
|
60
60
|
? 'It works in this project only.'
|
|
61
|
-
: 'It works in every project on this
|
|
61
|
+
: 'It works in every project on this computer.';
|
|
62
62
|
const body = [
|
|
63
63
|
`**Tell ${title} what to do first, in one sentence.**`,
|
|
64
64
|
...(line ? [line] : []),
|
|
@@ -148,6 +148,8 @@ export function whenWords(when, now = Date.now()) {
|
|
|
148
148
|
/** Where she was when she started it, in the two places she named. */
|
|
149
149
|
export function startedIn(source) {
|
|
150
150
|
if (source === 'codex') return 'in Codex';
|
|
151
|
+
// VS Code, Cursor or Windsurf: the extension says which family, not which one.
|
|
152
|
+
if (source === 'editor') return 'in your code editor';
|
|
151
153
|
return source === 'desktop' ? 'in the Claude Code desktop app' : 'in a terminal';
|
|
152
154
|
}
|
|
153
155
|
|
package/shared/agents.d.mts
CHANGED
package/shared/bridge-map.mjs
CHANGED
|
@@ -15,6 +15,10 @@ export const REQUEST_CHANNELS = {
|
|
|
15
15
|
terminal: 'zero:terminal',
|
|
16
16
|
agentUpdate: 'zero:agent-update',
|
|
17
17
|
snapshot: 'zero:snapshot',
|
|
18
|
+
refreshUsage: 'zero:refresh-usage',
|
|
19
|
+
// Whether a screen reader is running, which is the terminal pane's question
|
|
20
|
+
// before it builds xterm's accessibility tree.
|
|
21
|
+
assistiveTech: 'zero:assistiveTech',
|
|
18
22
|
// The next page of old finished threads, at the foot of Done and All.
|
|
19
23
|
olderItems: 'zero:older-items',
|
|
20
24
|
dashboard: 'zero:dashboard',
|
|
@@ -27,6 +31,7 @@ export const REQUEST_CHANNELS = {
|
|
|
27
31
|
sendNow: 'zero:send-now',
|
|
28
32
|
compose: 'zero:compose',
|
|
29
33
|
schedule: 'zero:schedule',
|
|
34
|
+
snooze: 'zero:snooze',
|
|
30
35
|
// Feedback to the Agentbox team, from the sidebar's Feedback card.
|
|
31
36
|
sendFeedback: 'zero:send-feedback',
|
|
32
37
|
// The team version: signing in, the team, sharing, routing a given task.
|
|
@@ -72,9 +77,11 @@ export const REQUEST_CHANNELS = {
|
|
|
72
77
|
agentThreads: 'zero:agent-threads',
|
|
73
78
|
importThreads: 'zero:import-threads',
|
|
74
79
|
badge: 'zero:badge',
|
|
80
|
+
cornerTag: 'zero:corner-tag',
|
|
75
81
|
bootInfo: 'zero:boot-info',
|
|
76
82
|
updateCheck: 'zero:update-check',
|
|
77
83
|
updateInstall: 'zero:update-install',
|
|
84
|
+
updateMove: 'zero:update-move',
|
|
78
85
|
notify: 'zero:notify',
|
|
79
86
|
stopSession: 'zero:stop-session',
|
|
80
87
|
runNow: 'zero:run-now',
|
|
@@ -90,6 +97,10 @@ export const REQUEST_CHANNELS = {
|
|
|
90
97
|
settingsRead: 'zero:settings-read',
|
|
91
98
|
claudeRecheck: 'zero:claude-recheck',
|
|
92
99
|
codexRecheck: 'zero:codex-recheck',
|
|
100
|
+
// Point at the coding agent yourself, or hand the choice back to the search.
|
|
101
|
+
// The browser tab has no file panel, so main answers it ok-with-nothing-done
|
|
102
|
+
// there (main/serve.mjs stubs showOpenDialog) and the row stays honest.
|
|
103
|
+
engineBin: 'zero:engine-bin',
|
|
93
104
|
engineSetup: 'zero:engine-setup',
|
|
94
105
|
codexAddAccount: 'zero:codex-add-account',
|
|
95
106
|
claudeAddAccount: 'zero:claude-add-account',
|
|
@@ -127,8 +138,13 @@ export const REQUEST_CHANNELS = {
|
|
|
127
138
|
resumeItems: 'zero:resume-items',
|
|
128
139
|
redeliver: 'zero:redeliver',
|
|
129
140
|
setProductOrder: 'zero:set-product-order',
|
|
141
|
+
setThreadPlaces: 'zero:set-thread-places',
|
|
142
|
+
setThreadPins: 'zero:set-thread-pins',
|
|
130
143
|
setProductHidden: 'zero:set-product-hidden',
|
|
131
144
|
approve: 'zero:approve',
|
|
145
|
+
// The phone door's Settings page (main/phone-link.mjs): its state, on, off,
|
|
146
|
+
// a new key, and which address the QR code carries. The desktop window only.
|
|
147
|
+
phoneLink: 'zero:phone-link',
|
|
132
148
|
};
|
|
133
149
|
|
|
134
150
|
/** Told rather than asked: the app talks and the screen listens. */
|
|
@@ -139,9 +155,12 @@ export const PUSH_CHANNELS = {
|
|
|
139
155
|
onZoomPercent: 'zero:zoom-percent',
|
|
140
156
|
onRecovered: 'zero:recovered',
|
|
141
157
|
onOpenItem: 'zero:open-item',
|
|
158
|
+
onOpenSettings: 'zero:open-settings',
|
|
142
159
|
onScreenDetail: 'zero:screen-detail',
|
|
143
160
|
onEscapeBrowser: 'zero:escape-browser',
|
|
144
161
|
onKeyInTheFile: 'zero:key-in-the-file',
|
|
162
|
+
// A phone connected, left, or the key changed.
|
|
163
|
+
onPhoneChanged: 'zero:phone-changed',
|
|
145
164
|
};
|
|
146
165
|
|
|
147
166
|
// THE FEW THAT ARE NOT A STRAIGHT HANDOVER. Each is called with bare values
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
// GENERATED BY scripts/read-claude-commands.mjs. DO NOT EDIT BY HAND.
|
|
2
2
|
//
|
|
3
3
|
// Claude Code's own words for the commands Agentbox's reply box offers,
|
|
4
|
-
// read out of the copy of Claude Code installed on
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// read out of the copy of Claude Code installed on a machine that ran
|
|
5
|
+
// `npm run read:claude`. A BUILD DOES NOT REWRITE THIS: a build wrote what it
|
|
6
|
+
// read over this tracked file, and every folder that had run the app then
|
|
7
|
+
// looked like it held work. Committed so a machine with no Claude Code on it
|
|
8
|
+
// can still build, and so a version that renames one of the eight shows up as
|
|
9
|
+
// a diff rather than as a wrong menu.
|
|
8
10
|
//
|
|
9
11
|
// WHY IT IS READ AND NOT TYPED: reading them out of the installed binary at
|
|
10
12
|
// build time, rather than typing them into a file, is the only way the list
|
|
11
13
|
// cannot rot quietly.
|
|
12
14
|
|
|
13
|
-
export const READ_FROM = "2.1.
|
|
14
|
-
export const READ_AT = "2026-10-
|
|
15
|
+
export const READ_FROM = "2.1.293";
|
|
16
|
+
export const READ_AT = "2026-10-08";
|
|
15
17
|
export const CLAUDE_COMMANDS = [
|
|
16
18
|
{
|
|
17
19
|
"name": "model",
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
// GENERATED BY scripts/read-claude-models.mjs. DO NOT EDIT BY HAND.
|
|
2
2
|
//
|
|
3
3
|
// Which model each alias really is, and Claude Code's own name for it, read
|
|
4
|
-
// out of the copy of Claude Code installed on
|
|
5
|
-
// `npm run
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// out of the copy of Claude Code installed on a machine that ran
|
|
5
|
+
// `npm run read:claude`. A BUILD DOES NOT REWRITE THIS: a build wrote what it
|
|
6
|
+
// read over this tracked file, and every folder that had run the app then
|
|
7
|
+
// looked like it held work. Committed so a machine with no Claude Code on it
|
|
8
|
+
// can still build, and so a version that points an alias at a different model
|
|
9
|
+
// shows up as a diff rather than as a wrong label on the picker.
|
|
8
10
|
//
|
|
9
11
|
// WHY IT IS READ AND NOT TYPED: labelling a model wrong is expensive, because
|
|
10
12
|
// Agentbox sends the alias, the CLI decides what the alias means, and a version
|
|
11
13
|
// number typed into a file of ours would look authoritative while being a
|
|
12
14
|
// guess. Running an old model by mistake is the failure being avoided.
|
|
13
15
|
|
|
14
|
-
export const READ_FROM = "2.1.
|
|
15
|
-
export const READ_AT = "2026-10-
|
|
16
|
+
export const READ_FROM = "2.1.293";
|
|
17
|
+
export const READ_AT = "2026-10-08";
|
|
16
18
|
|
|
17
19
|
export const CLAUDE_MODELS = [
|
|
18
20
|
{"alias":"opus","id":"claude-opus-5-5","label":"Opus 5.5","defaultLevel":"medium"},
|
|
19
21
|
{"alias":"claude-opus-5","id":"claude-opus-5","label":"Opus 5","defaultLevel":"high"},
|
|
20
22
|
{"alias":"sonnet","id":"claude-sonnet-5-5","label":"Sonnet 5.5","defaultLevel":"medium"},
|
|
21
23
|
{"alias":"claude-sonnet-5","id":"claude-sonnet-5","label":"Sonnet 5","defaultLevel":"high"},
|
|
22
|
-
{"alias":"haiku","id":"claude-haiku-
|
|
23
|
-
{"alias":"claude-
|
|
24
|
+
{"alias":"haiku","id":"claude-haiku-5-5","label":"Haiku 5.5","defaultLevel":"medium"},
|
|
25
|
+
{"alias":"claude-haiku-4-5","id":"claude-haiku-4-5","label":"Haiku 4.5","defaultLevel":null},
|
|
24
26
|
{"alias":"fable","id":"claude-fable-5-1","label":"Fable 5.1","defaultLevel":"high"},
|
|
25
27
|
{"alias":"claude-fable-5","id":"claude-fable-5","label":"Fable 5","defaultLevel":"high"},
|
|
26
28
|
];
|
package/shared/codex-modes.mjs
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Types for shared/corner-tag.mjs, the corner tag's rules, which main (Node)
|
|
2
|
+
// and the tag's page (Vite) share one copy of. Only what the page calls is
|
|
3
|
+
// declared; the window placement is main's half.
|
|
4
|
+
|
|
5
|
+
export type HideChoice = { key: '5m' | '30m' | 'today'; label: string };
|
|
6
|
+
export const HIDE_CHOICES: HideChoice[];
|
|
7
|
+
|
|
8
|
+
export function tagSays(counts: { ready?: number; working?: number }): { kind: 'ready' | 'working'; text: string } | null;
|
|
9
|
+
|
|
10
|
+
export type ReadyLine = { id: string; title: string; says: string; since?: number; open?: string | null };
|
|
11
|
+
export function cardLines<T extends ReadyLine>(ready: T[], now: number, max?: number): {
|
|
12
|
+
lines: (T & { waited: string })[];
|
|
13
|
+
more: number;
|
|
14
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// THE CORNER TAG (w-dafae58a23): the rules, and nothing that touches a window.
|
|
2
|
+
//
|
|
3
|
+
// You lose track of agents once you are in another app, and the ones waiting on
|
|
4
|
+
// you sit idle. The answer that survived eight rounds of drawings is a small tag
|
|
5
|
+
// in a corner of the screen that says "2 ready for you" when an agent has
|
|
6
|
+
// something for you and "20 working" when none does. Twenty agents running is
|
|
7
|
+
// ordinary, so working agents are a count and never a list: only what is ready
|
|
8
|
+
// for you is worth a line.
|
|
9
|
+
//
|
|
10
|
+
// It must never become a nag. So it hides for a while and comes back on its
|
|
11
|
+
// own, there is no one-click way to make it gone for good (that becomes a thing
|
|
12
|
+
// you closed once and forgot existed), it is turned off only in Settings, and
|
|
13
|
+
// it steps aside while you are in the app itself.
|
|
14
|
+
//
|
|
15
|
+
// main/corner-tag.mjs owns the window and renderer/src/corner-tag owns the
|
|
16
|
+
// page; both decide through this file so the tests pin behaviour, not wiring.
|
|
17
|
+
|
|
18
|
+
/** Space between the tag and the edge of the screen when it rests in a corner. */
|
|
19
|
+
export const MARGIN = 18;
|
|
20
|
+
|
|
21
|
+
/** The ways to hide it. All of them come back; none of them is permanent. */
|
|
22
|
+
export const HIDE_CHOICES = [
|
|
23
|
+
{ key: '5m', label: '5 min' },
|
|
24
|
+
{ key: '30m', label: '30 min' },
|
|
25
|
+
{ key: 'today', label: 'Today' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** When a hide chosen at `now` runs out. 0 means "not hidden". */
|
|
29
|
+
export function hiddenUntil(choice, now) {
|
|
30
|
+
if (choice === '5m') return now + 5 * 60_000;
|
|
31
|
+
if (choice === '30m') return now + 30 * 60_000;
|
|
32
|
+
if (choice === 'today') {
|
|
33
|
+
const d = new Date(now);
|
|
34
|
+
d.setHours(24, 0, 0, 0);
|
|
35
|
+
return d.getTime();
|
|
36
|
+
}
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The words on the tag, or null when there is nothing to say. Short, because
|
|
41
|
+
* "22 ready for you" in the corner of a full screen was more than it needed;
|
|
42
|
+
* "waiting" was picked over "ready" and "need you". */
|
|
43
|
+
export function tagSays({ ready = 0, working = 0 } = {}) {
|
|
44
|
+
if (ready > 0) return { kind: 'ready', text: `${ready} waiting` };
|
|
45
|
+
if (working > 0) return { kind: 'working', text: `${working} working` };
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* What the tag counts and lists: everything waiting on you, newest first.
|
|
51
|
+
* The first version put threads from weeks ago at the top of the list; the
|
|
52
|
+
* second cut everything older than a day, and then a Needs you of 58 read
|
|
53
|
+
* "3 waiting" in the corner (w-0961e5523b). So the count is the whole of it,
|
|
54
|
+
* the same number the app shows, and only the order keeps the old ones out
|
|
55
|
+
* of the way: the list shows the newest three and folds the rest into "and N
|
|
56
|
+
* more". Something with no moment it became ready (an agent stopped on a
|
|
57
|
+
* yes, right now) sorts as now.
|
|
58
|
+
*/
|
|
59
|
+
export function readyNow(ready, now) {
|
|
60
|
+
return [...(ready ?? [])]
|
|
61
|
+
.sort((a, b) => (Number.isFinite(b.since) ? b.since : now) - (Number.isFinite(a.since) ? a.since : now));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Whether the tag is on screen right now. */
|
|
65
|
+
export function tagShows({ on, hiddenUntil: until = 0, now, inApp, ready = 0, working = 0 }) {
|
|
66
|
+
if (on === false) return false;
|
|
67
|
+
if (until > now) return false;
|
|
68
|
+
// In the app itself the inbox already says all of this, bigger.
|
|
69
|
+
if (inApp) return false;
|
|
70
|
+
return !!tagSays({ ready, working });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** How long something has waited, in units. */
|
|
74
|
+
export function waitedFor(since, now) {
|
|
75
|
+
if (!Number.isFinite(since)) return '';
|
|
76
|
+
const min = Math.floor((now - since) / 60_000);
|
|
77
|
+
if (min < 1) return 'now';
|
|
78
|
+
if (min < 60) return `${min} min`;
|
|
79
|
+
return `${Math.floor(min / 60)} h`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The lines under the tag: the newest first, three at most. */
|
|
83
|
+
export function cardLines(ready, now, max = 3) {
|
|
84
|
+
const sorted = readyNow(ready, now, Infinity);
|
|
85
|
+
const lines = sorted.slice(0, max).map((r) => ({ ...r, waited: waitedFor(r.since, now) }));
|
|
86
|
+
return { lines, more: Math.max(0, sorted.length - lines.length) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
90
|
+
|
|
91
|
+
function areaFor(point, areas) {
|
|
92
|
+
return areas.find((a) => point.x >= a.x && point.x < a.x + a.width && point.y >= a.y && point.y < a.y + a.height) ?? areas[0];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Where the tag goes when it appears: back where you last dragged it, pulled
|
|
97
|
+
* onto a screen if that screen is gone, or the bottom-right corner the first
|
|
98
|
+
* time. `spot` is the tag's top-left, `areas` are the screens' work areas.
|
|
99
|
+
*/
|
|
100
|
+
export function restingSpot(spot, size, areas) {
|
|
101
|
+
if (!spot) {
|
|
102
|
+
const a = areas[0];
|
|
103
|
+
return { x: a.x + a.width - MARGIN - size.width, y: a.y + a.height - MARGIN - size.height };
|
|
104
|
+
}
|
|
105
|
+
const a = areaFor({ x: spot.x + size.width / 2, y: spot.y + size.height / 2 }, areas) ?? areas[0];
|
|
106
|
+
return {
|
|
107
|
+
x: clamp(spot.x, a.x, a.x + a.width - size.width),
|
|
108
|
+
y: clamp(spot.y, a.y, a.y + a.height - size.height),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function inRightHalf(r, a) { return r.x + r.width / 2 > a.x + a.width / 2; }
|
|
113
|
+
function inLowerHalf(r, a) { return r.y + r.height / 2 > a.y + a.height / 2; }
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The tag's new bounds when its words change length. The edge nearest its
|
|
117
|
+
* corner stays put, so "2 ready for you" turning into "20 working" does not
|
|
118
|
+
* walk the tag away from the edge you parked it against.
|
|
119
|
+
*/
|
|
120
|
+
export function keepCorner(bounds, size, area) {
|
|
121
|
+
const x = inRightHalf(bounds, area) ? bounds.x + bounds.width - size.width : bounds.x;
|
|
122
|
+
const y = inLowerHalf(bounds, area) ? bounds.y + bounds.height - size.height : bounds.y;
|
|
123
|
+
return { x, y, width: size.width, height: size.height };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Where the list opens when you point at the tag: toward the middle of the
|
|
128
|
+
* screen, so it never runs off an edge, lined up with the tag's outer edge.
|
|
129
|
+
*/
|
|
130
|
+
export function cardPlacement(tag, card, area, gap = 8) {
|
|
131
|
+
let above = inLowerHalf(tag, area);
|
|
132
|
+
const alignRight = inRightHalf(tag, area);
|
|
133
|
+
const x = clamp(alignRight ? tag.x + tag.width - card.width : tag.x, area.x, area.x + area.width - card.width);
|
|
134
|
+
const up = tag.y - gap - card.height;
|
|
135
|
+
const down = tag.y + tag.height + gap;
|
|
136
|
+
// A tall list near a short screen's middle may not fit the side it prefers.
|
|
137
|
+
if (above && up < area.y) above = false;
|
|
138
|
+
else if (!above && down + card.height > area.y + area.height) above = true;
|
|
139
|
+
const y = clamp(above ? up : down, area.y, area.y + area.height - card.height);
|
|
140
|
+
return { above, alignRight, card: { x, y, width: card.width, height: card.height } };
|
|
141
|
+
}
|
package/shared/demo-world.mjs
CHANGED
|
@@ -214,7 +214,7 @@ function lines(now) {
|
|
|
214
214
|
result: [
|
|
215
215
|
'The clause summariser is merged and running. It read all ten contracts in the test folder and named every termination, auto renewal and liability clause in all ten, with two false positives on the longest one.',
|
|
216
216
|
'',
|
|
217
|
-
'Ninety three seconds for the longest contract, four seconds for the shortest. Measured on this
|
|
217
|
+
'Ninety three seconds for the longest contract, four seconds for the shortest. Measured on this computer, not estimated.',
|
|
218
218
|
].join('\n'),
|
|
219
219
|
});
|
|
220
220
|
|
package/shared/engines.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { HARNESS_DEFINITIONS } from './harness-definitions.mjs';
|
|
1
2
|
// WHICH CODING AGENT A TASK IS HANDED TO.
|
|
2
3
|
//
|
|
3
4
|
// AND THE PREMISE SHE GAVE FOR IT DID NOT HOLD. Measured head to head on her
|
|
@@ -44,11 +45,25 @@
|
|
|
44
45
|
// main/codex.mjs when that comes back, because the renderer must never import
|
|
45
46
|
// spawn code.
|
|
46
47
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
/**
|
|
49
|
+
* The engines, in the order the picker draws them.
|
|
50
|
+
*
|
|
51
|
+
* ADMITTED ONES ONLY, and that word is doing real work as of 2026-10-07. A
|
|
52
|
+
* harness being integrated and a harness being offered are two different
|
|
53
|
+
* decisions, and this list is the second one. OpenCode is registered in
|
|
54
|
+
* main/harnesses.mjs, its transport is measured and tested, and it is
|
|
55
|
+
* deliberately absent from here -- so `ENGINE_IDS`, `isEngine`, `engineFor`,
|
|
56
|
+
* the composer's picker, the Settings row and the byline all behave as if it
|
|
57
|
+
* were not there, while `harnessFor('opencode')` works for anything that asks
|
|
58
|
+
* for it by name. Without this filter, adding the definition alone would have
|
|
59
|
+
* put a third word in front of every user the moment it landed.
|
|
60
|
+
*
|
|
61
|
+
* docs/harnesses.md holds the bar a harness clears before `admitted` goes
|
|
62
|
+
* true, and the product review that turning it on requires.
|
|
63
|
+
*/
|
|
64
|
+
export const ENGINES = HARNESS_DEFINITIONS
|
|
65
|
+
.filter(({ admitted }) => admitted !== false)
|
|
66
|
+
.map(({ id, label, word }) => ({ id, label, word }));
|
|
52
67
|
|
|
53
68
|
export const ENGINE_IDS = ENGINES.map((e) => e.id);
|
|
54
69
|
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
// would take her real setup with it, which is not a thing a test may cost. So
|
|
14
14
|
// NOTHING IS DELETED, EVER. A second copy is launched inside a fresh home under
|
|
15
15
|
// /tmp and all three land in there: fresh settings, no config file so it falls
|
|
16
|
-
// to DEFAULTS, and an empty store at
|
|
16
|
+
// to DEFAULTS, and an empty store at that home's defaultStoreRoot. On macOS
|
|
17
|
+
// that is `<throwaway>/<Name>`. On Linux it is `<throwaway>/.local/share/<slug>`,
|
|
18
|
+
// and only when the copy was not handed the real XDG_DATA_HOME.
|
|
17
19
|
//
|
|
18
20
|
// WHY IT DOES NOT KILL HER AGENTS, which is the whole reason it is usable.
|
|
19
21
|
// `main.mjs` quits on `app.requestSingleInstanceLock`, and that lock is keyed
|
|
@@ -180,6 +182,15 @@ export const NOT_INHERITED = Object.freeze([
|
|
|
180
182
|
// Where her own Claude Code and Codex sign-ins live, when she has moved them.
|
|
181
183
|
'CLAUDE_CONFIG_DIR',
|
|
182
184
|
'CODEX_HOME',
|
|
185
|
+
// The Linux store default reads XDG_DATA_HOME. A fresh copy that inherited
|
|
186
|
+
// the real one would write its empty store into her data directory, which is
|
|
187
|
+
// the 2026-09-27 bug again with a different variable. The other base
|
|
188
|
+
// directories go for the same reason. XDG_RUNTIME_DIR stays: it is this
|
|
189
|
+
// login's bus socket, not a directory we keep the inbox in.
|
|
190
|
+
'XDG_DATA_HOME',
|
|
191
|
+
'XDG_CONFIG_HOME',
|
|
192
|
+
'XDG_STATE_HOME',
|
|
193
|
+
'XDG_CACHE_HOME',
|
|
183
194
|
]);
|
|
184
195
|
|
|
185
196
|
/**
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Names, flags, paths, and capabilities are data. This file is safe for the
|
|
2
|
+
// renderer: it imports no processes, credentials, filesystem, or transports.
|
|
3
|
+
const freeze = value => {
|
|
4
|
+
if (value && typeof value === 'object') { for (const child of Object.values(value)) freeze(child); Object.freeze(value); }
|
|
5
|
+
return value;
|
|
6
|
+
};
|
|
7
|
+
export const HARNESS_DEFINITIONS = freeze([
|
|
8
|
+
{
|
|
9
|
+
id: 'claude', label: 'Claude Code', word: 'Claude Code', binKey: 'claudeBin',
|
|
10
|
+
homeEnv: 'CLAUDE_CONFIG_DIR', accountPrefix: '', package: '@anthropic-ai/claude-code',
|
|
11
|
+
commandMenu: 'claude', commandRoute: 'claude',
|
|
12
|
+
casks: ['claude-code', 'claude-code@latest'],
|
|
13
|
+
installer: { shell: '/bin/bash', url: 'https://claude.ai/install.sh', pipe: 'bash' },
|
|
14
|
+
loginArgs: ['auth', 'login', '--claudeai'], statusArgs: ['auth', 'status', '--json'],
|
|
15
|
+
credentialFiles: ['.claude.json', '.credentials.json'],
|
|
16
|
+
// HOW A WORKER IS STARTED, AS DATA. This was six hardcoded flag names in
|
|
17
|
+
// main/supervisor.mjs, which made the cheapest possible new engine — one
|
|
18
|
+
// that speaks this same stream-json and differs by a binary and the
|
|
19
|
+
// `outputFormat` value, as Grok Build does in
|
|
20
|
+
// github.com/savannahfeder/agentbox/issues/21 — a supervisor edit. A key
|
|
21
|
+
// left out here means this engine does not take that flag, and the builder
|
|
22
|
+
// leaves it off the line rather than the call site asking who it is.
|
|
23
|
+
run: {
|
|
24
|
+
print: '-p',
|
|
25
|
+
outputFormat: { flag: '--output-format', value: 'stream-json' },
|
|
26
|
+
stream: ['--verbose', '--include-partial-messages'],
|
|
27
|
+
effort: '--effort',
|
|
28
|
+
system: '--append-system-prompt',
|
|
29
|
+
resume: '--resume',
|
|
30
|
+
fork: '--fork-session',
|
|
31
|
+
// Replies arrive on stdin, so the prompt leaves the line when they do.
|
|
32
|
+
input: ['--input-format', 'stream-json', '--replay-user-messages'],
|
|
33
|
+
},
|
|
34
|
+
scrubPatterns: ['^ANTHROPIC_', '^CLAUDE_CODE_', '^CLAUDECODE$', '^CLAUDE_PID$', '^CLAUDE_EFFORT$', '^CLAUDE_CONFIG_DIR$'],
|
|
35
|
+
capabilities: { nativeCommands: true, fork: true, nativeReview: false, remoteControl: true, inlineImages: false, claudeEffort: true },
|
|
36
|
+
imageInstruction: 'Open each one with the Read tool, at the absolute path below.',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'codex', label: 'Codex', word: 'Codex', binKey: 'codexBin',
|
|
40
|
+
homeEnv: 'CODEX_HOME', accountPrefix: 'codex:', package: '@openai/codex', casks: ['codex'],
|
|
41
|
+
commandMenu: 'codex', commandRoute: 'codex',
|
|
42
|
+
installer: { shell: '/bin/sh', url: 'https://chatgpt.com/codex/install.sh', pipe: 'sh' },
|
|
43
|
+
loginArgs: ['login'], statusArgs: ['login', 'status'], credentialFiles: ['auth.json'],
|
|
44
|
+
// NO `run`, AND THAT IS THE POINT OF THE SHAPE. Codex is driven over its
|
|
45
|
+
// app-server RPC: the prompt is a turn's `input`, the system block a
|
|
46
|
+
// thread's `developerInstructions`, the resume a thread id. An engine with
|
|
47
|
+
// no command line is handed none rather than handed Claude Code's.
|
|
48
|
+
scrubPatterns: ['^ANTHROPIC_', '^CLAUDE_CODE_', '^CLAUDECODE$', '^CLAUDE_PID$', '^CLAUDE_EFFORT$', '^CLAUDE_CONFIG_DIR$', '^OPENAI_', '^CODEX_'],
|
|
49
|
+
capabilities: { nativeCommands: false, fork: false, nativeReview: true, remoteControl: false, inlineImages: true, claudeEffort: false },
|
|
50
|
+
imageInstruction: 'Each one is attached to this message as an image, and is also on disk at the absolute path below.',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
// OPENCODE, AND IT IS NOT OFFERED TO ANYBODY YET -- see `admitted` below.
|
|
54
|
+
//
|
|
55
|
+
// THE ONE THING THAT MAKES IT DIFFERENT FROM THE OTHER TWO: it needs no
|
|
56
|
+
// sign-in at all. Measured on a real OpenCode 1.18.35 on 2026-10-07 with
|
|
57
|
+
// `auth.json` holding zero credentials, `opencode models` still listed ten
|
|
58
|
+
// models and a run on one of them finished and reported `"cost":0`. They
|
|
59
|
+
// are the OpenCode Zen free tier. So this is the first harness where the
|
|
60
|
+
// first run costs nothing and asks for nothing, which is also why
|
|
61
|
+
// `signIn` below is a real command rather than a requirement.
|
|
62
|
+
//
|
|
63
|
+
// ITS CREDENTIAL STORE IS ITS OWN AND AGENTBOX NEVER TOUCHES IT. Keys go
|
|
64
|
+
// into ~/.local/share/opencode/auth.json by `opencode auth login`, which
|
|
65
|
+
// is the same shape as `claude auth login` and `codex login` above. The
|
|
66
|
+
// scrub list is therefore WIDER than the other two: it takes out every
|
|
67
|
+
// provider key OpenCode could otherwise read out of our environment,
|
|
68
|
+
// because CLAUDE.md says no API key goes into a worker, and OpenCode is
|
|
69
|
+
// the one tool here that would happily use one if it found it.
|
|
70
|
+
id: 'opencode', label: 'OpenCode', word: 'OpenCode', binKey: 'opencodeBin',
|
|
71
|
+
homeEnv: 'OPENCODE_CONFIG', accountPrefix: 'opencode:', package: 'opencode-ai',
|
|
72
|
+
commandMenu: 'opencode', commandRoute: 'opencode',
|
|
73
|
+
casks: ['opencode', 'sst/tap/opencode'],
|
|
74
|
+
installer: { shell: '/bin/bash', url: 'https://opencode.ai/install', pipe: 'bash' },
|
|
75
|
+
loginArgs: ['auth', 'login'], statusArgs: ['auth', 'list'],
|
|
76
|
+
credentialFiles: ['auth.json'],
|
|
77
|
+
scrubPatterns: [
|
|
78
|
+
'^ANTHROPIC_', '^CLAUDE_CODE_', '^CLAUDECODE$', '^CLAUDE_PID$', '^CLAUDE_EFFORT$', '^CLAUDE_CONFIG_DIR$',
|
|
79
|
+
'^OPENAI_', '^CODEX_', '^OPENCODE_', '^OPENROUTER_', '^GEMINI_', '^GOOGLE_',
|
|
80
|
+
'^AWS_', '^AZURE_', '^GROQ_', '^MISTRAL_', '^DEEPSEEK_', '^XAI_', '^CEREBRAS_',
|
|
81
|
+
'^TOGETHER_', '^FIREWORKS_', '^DEEPINFRA_', '^BASETEN_', '^NVIDIA_', '^GITLAB_TOKEN$',
|
|
82
|
+
'_API_KEY$', '_ACCESS_TOKEN$',
|
|
83
|
+
],
|
|
84
|
+
// NO USAGE AND NO PLAN. There is no endpoint on the server and no command
|
|
85
|
+
// on the CLI that reports what is left of anything, because OpenCode bills
|
|
86
|
+
// through whichever provider the user connected and does not know. Per-run
|
|
87
|
+
// tokens and cost DO arrive (`step-finish`), and the adapter reports those;
|
|
88
|
+
// a plan limit is declared missing rather than faked as zero.
|
|
89
|
+
capabilities: { nativeCommands: true, fork: true, nativeReview: false, remoteControl: false, inlineImages: false, claudeEffort: false, planLimits: false },
|
|
90
|
+
imageInstruction: 'Open each one with the read tool, at the absolute path below.',
|
|
91
|
+
// ADMISSION IS A SEPARATE DECISION FROM INTEGRATION, and it is not ours.
|
|
92
|
+
// docs/harnesses.md: "A provider is not offered until that path works", and
|
|
93
|
+
// "a new provider's networking or permission scope requires the
|
|
94
|
+
// corresponding product policy review before admission." The transport is
|
|
95
|
+
// verified (see main/opencode-server.mjs for what was measured and how);
|
|
96
|
+
// whether OpenCode appears in the picker is a product call, so this stays
|
|
97
|
+
// false until somebody turns it on deliberately.
|
|
98
|
+
admitted: false,
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
export function harnessDefinition(id = 'claude') {
|
|
102
|
+
return HARNESS_DEFINITIONS.find(h => h.id === id) ?? null;
|
|
103
|
+
}
|