agentbox-app 0.1.13 → 0.1.16
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 -9
- package/bin/agentbox.mjs +23 -1
- package/briefs/message-rules.md +36 -8
- package/briefs/worker.md +46 -23
- package/main/account-discovery.mjs +122 -30
- package/main/account-tooling.mjs +29 -1
- package/main/account-usage.mjs +46 -0
- package/main/agent-sessions.mjs +282 -268
- package/main/agents.mjs +179 -466
- package/main/analytics.mjs +12 -2
- package/main/answer-modes.mjs +103 -37
- package/main/approval-prompt-core.mjs +197 -0
- package/main/approval-prompt-server.mjs +4 -138
- package/main/approvals.mjs +101 -297
- package/main/artifact-path.mjs +47 -18
- package/main/claude-input.mjs +48 -4
- package/main/claude-plan.mjs +48 -0
- package/main/claude-session-open.mjs +28 -0
- package/main/claude-usage-process.mjs +40 -0
- package/main/claude-usage.mjs +18 -40
- package/main/cloud/account.mjs +179 -0
- package/main/cloud/aws.mjs +127 -0
- package/main/cloud/browser-return.mjs +58 -0
- package/main/cloud/clients.mjs +15 -0
- package/main/cloud/codex-cloud.mjs +53 -0
- package/main/cloud/config.mjs +107 -0
- package/main/cloud/docs.mjs +219 -0
- package/main/cloud/exec.mjs +27 -0
- package/main/cloud/host.mjs +165 -0
- package/main/cloud/kit.mjs +80 -0
- package/main/cloud/logins.mjs +543 -0
- package/main/cloud/managed.mjs +115 -0
- package/main/cloud/marker.mjs +104 -0
- package/main/cloud/org.mjs +53 -0
- package/main/cloud/reuse.mjs +208 -0
- package/main/cloud/run-cli.mjs +177 -0
- package/main/cloud/run.mjs +1934 -0
- package/main/cloud/usage.mjs +76 -0
- package/main/cloud-permission.mjs +51 -0
- package/main/codex-account.mjs +78 -0
- package/main/codex-app-server.mjs +276 -409
- package/main/codex-approvals.mjs +148 -433
- package/main/codex-models.mjs +20 -2
- package/main/codex-session.mjs +212 -507
- package/main/codex-threads-worker.mjs +12 -0
- package/main/codex-threads.mjs +71 -3
- package/main/codex-usage-file.mjs +5 -3
- package/main/codex-usage.mjs +56 -7
- package/main/codex-watch.mjs +17 -3
- package/main/codex.mjs +95 -363
- package/main/config.mjs +11 -1
- package/main/corner-tag.mjs +340 -19
- package/main/dev-window.mjs +1 -1
- package/main/engine-bin.mjs +232 -282
- package/main/engine-setup.mjs +21 -9
- package/main/feedback.mjs +3 -25
- package/main/folder-pictures.mjs +2 -2
- package/main/freeze-recorder-thread.mjs +86 -0
- package/main/freeze-recorder.mjs +146 -0
- package/main/fresh-user.mjs +20 -9
- package/main/git-change-offthread.mjs +17 -3
- package/main/git-change-thread.mjs +5 -2
- package/main/harnesses/claude.mjs +32 -11
- package/main/harnesses/codex.mjs +9 -2
- package/main/importable-threads.mjs +64 -6
- package/main/ipc.mjs +362 -111
- package/main/leftovers.mjs +117 -25
- package/main/liquid-glass.mjs +26 -0
- package/main/live-replies.mjs +10 -4
- package/main/main-went-red.mjs +330 -0
- package/main/main.mjs +199 -84
- package/main/memory-gate-history.mjs +36 -10
- package/main/memory-gate-server.mjs +62 -0
- package/main/memory-gate.mjs +14 -1
- package/main/menu.mjs +4 -2
- package/main/message-priority.mjs +2 -2
- package/main/mic.mjs +38 -0
- package/main/native/liquid-glass/build/liquid-glass.node +0 -0
- package/main/new-chats-watch.mjs +110 -0
- package/main/new-user-window.mjs +25 -0
- package/main/notify.mjs +24 -8
- package/main/phone-alerts.mjs +117 -0
- package/main/phone-inbox.mjs +625 -0
- package/main/phone-link.mjs +29 -9
- package/main/phone-uploads.mjs +476 -0
- package/main/picture-shape.mjs +114 -0
- package/main/quit-sequence.mjs +143 -0
- package/main/refresh-agent-models.mjs +15 -2
- package/main/relay-client.mjs +69 -1
- package/main/repeats.mjs +10 -4
- package/main/row-label.mjs +12 -41
- package/main/run-change.mjs +13 -0
- package/main/run-ledger.mjs +135 -0
- package/main/save-attachments.mjs +41 -0
- package/main/serve.mjs +45 -3
- package/main/settings.mjs +220 -44
- package/main/shell-path.mjs +185 -37
- package/main/ship-conflict-brief.mjs +119 -0
- package/main/ship-queue.mjs +115 -19
- package/main/source-updater.mjs +18 -10
- package/main/stage-attachment.mjs +68 -0
- package/main/stall-watch.mjs +50 -0
- package/main/store/home.mjs +87 -232
- package/main/store/paths.mjs +11 -0
- package/main/store/teammates-files.mjs +99 -0
- package/main/store/work-items.mjs +231 -13
- package/main/store.mjs +226 -52
- package/main/supervisor.mjs +3103 -341
- package/main/task-folders.mjs +430 -30
- package/main/task-terminals.mjs +110 -34
- package/main/team/index.mjs +114 -10
- package/main/team/memory-cloud.mjs +183 -1
- package/main/team/room-hours.mjs +48 -0
- package/main/team/room-setup.mjs +31 -0
- package/main/team/rooms-memory.mjs +454 -0
- package/main/team/rooms-shape.mjs +42 -0
- package/main/team/rooms-supabase.mjs +324 -0
- package/main/team/rooms.mjs +575 -0
- package/main/team/session.mjs +96 -26
- package/main/team/supabase-backend.mjs +180 -0
- package/main/team/sync.mjs +109 -1
- package/main/teammates.mjs +580 -0
- package/main/terminal-host.mjs +143 -0
- package/main/terminal-hosts.mjs +261 -0
- package/main/transcript-search.mjs +80 -0
- package/main/webview-guard.mjs +45 -0
- package/main/worktree-include.mjs +90 -219
- package/mcp/core/documents.mjs +3 -1
- package/mcp/core/repeats.mjs +88 -0
- package/mcp/core/teammates.mjs +222 -0
- package/mcp/core/work.mjs +76 -4
- package/mcp/stdio.mjs +24 -11
- package/mcp/tools.mjs +116 -8
- package/package.json +21 -5
- package/renderer/dist/assets/{abnfDiagram-YUKMZFIV-DPPuMWt-.js → abnfDiagram-YUKMZFIV-CoWB6YUK.js} +1 -1
- package/renderer/dist/assets/{arc-HmFfdseH.js → arc-Ch0NrCul.js} +1 -1
- package/renderer/dist/assets/{architectureDiagram-47P4ROYG-DxJ3zHrH.js → architectureDiagram-47P4ROYG-CXz9M5tQ.js} +1 -1
- package/renderer/dist/assets/{blockDiagram-E7TT5TSB-CbTXzOqQ.js → blockDiagram-E7TT5TSB-DchGUuHH.js} +1 -1
- package/renderer/dist/assets/{c4Diagram-CRA5TL53-X5ItFyLn.js → c4Diagram-CRA5TL53-Cip-zNyr.js} +1 -1
- package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
- package/renderer/dist/assets/{chunk-2Q5K7J3B-DC12Ho_L.js → chunk-2Q5K7J3B-BoXid_2r.js} +1 -1
- package/renderer/dist/assets/{chunk-5VM5RSS4-ClVtfPd0.js → chunk-5VM5RSS4-Cpez1rRz.js} +1 -1
- package/renderer/dist/assets/{chunk-7TKQ45FW-DgcOm0HT.js → chunk-7TKQ45FW-DKA84YkE.js} +1 -1
- package/renderer/dist/assets/{chunk-AW2ZBBNX-C01s4yoF.js → chunk-AW2ZBBNX-Dg-SumPG.js} +1 -1
- package/renderer/dist/assets/{chunk-FVRAUYC3-Bj0zxhR4.js → chunk-FVRAUYC3-06TCvPm1.js} +2 -2
- package/renderer/dist/assets/{chunk-HTAEGDNF-DWDeEhLU.js → chunk-HTAEGDNF-CMEBy1Td.js} +1 -1
- package/renderer/dist/assets/{chunk-JWPE2WC7-Dsqwv3eW.js → chunk-JWPE2WC7-BJ2ObmV-.js} +1 -1
- package/renderer/dist/assets/{chunk-KQW6MTUR-CE86ANL2.js → chunk-KQW6MTUR-CGWOYXUE.js} +1 -1
- package/renderer/dist/assets/{chunk-XXDRQBXY-CoNTASfs.js → chunk-XXDRQBXY-CiZp07XC.js} +1 -1
- package/renderer/dist/assets/{classDiagram-v2-K4WV4PDN-2wprLqtw.js → classDiagram-v2-K4WV4PDN-DJNIX719.js} +1 -1
- package/renderer/dist/assets/cornerTag-CPgvu-q3.js +1 -0
- package/renderer/dist/assets/cornerTag-oMfEjzCO.css +1 -0
- package/renderer/dist/assets/{cose-bilkent-JH36ORCC-enqMiYbn.js → cose-bilkent-JH36ORCC-CbkyZOUH.js} +1 -1
- package/renderer/dist/assets/{cynefin-EF2NZ3EQ-DHngLxvQ.js → cynefin-EF2NZ3EQ-78iOroZO.js} +1 -1
- package/renderer/dist/assets/{cynefinDiagram-3GCD6N5R-C0rZZIl3.js → cynefinDiagram-3GCD6N5R-GcCopMs7.js} +1 -1
- package/renderer/dist/assets/{dagre-W4DXFKR2-DQnaEWO7.js → dagre-W4DXFKR2-DHLlXLkz.js} +1 -1
- package/renderer/dist/assets/{diagram-2UJZ2QOL-CS8AlGPx.js → diagram-2UJZ2QOL-DuZSUHcH.js} +1 -1
- package/renderer/dist/assets/{diagram-OVF4WLC6-yiDMHIkY.js → diagram-OVF4WLC6-Ckxu3cqH.js} +1 -1
- package/renderer/dist/assets/{diagram-PFPMY2P6-XbQKzbBA.js → diagram-PFPMY2P6-CsTw9Ycn.js} +1 -1
- package/renderer/dist/assets/{diagram-UMYRVEAY-dhVvlfl_.js → diagram-UMYRVEAY-CTXd8qVg.js} +1 -1
- package/renderer/dist/assets/{diagram-YEKJPTXX-DZHe8SCf.js → diagram-YEKJPTXX-CJs1T_9m.js} +1 -1
- package/renderer/dist/assets/{diagram-ZIFT7M5P-B5h6aXVR.js → diagram-ZIFT7M5P-CvjI23mf.js} +1 -1
- package/renderer/dist/assets/{ebnfDiagram-VR2GEFS7-PR9ScDco.js → ebnfDiagram-VR2GEFS7-d_IeAzOJ.js} +1 -1
- package/renderer/dist/assets/{elk-IJKZMXRS-Cg-yQXPd.js → elk-IJKZMXRS-zo9KFP3N.js} +1 -1
- package/renderer/dist/assets/{erDiagram-O2IAPWRE-omOrJMxu.js → erDiagram-O2IAPWRE-DqfYf5Dt.js} +1 -1
- package/renderer/dist/assets/{flowDiagram-OXPTDLAJ-aOX2hzvT.js → flowDiagram-OXPTDLAJ-DAWsMLmo.js} +1 -1
- package/renderer/dist/assets/{ganttDiagram-R7TSEDQI-B1H3pWyI.js → ganttDiagram-R7TSEDQI-4ml_bhwR.js} +1 -1
- package/renderer/dist/assets/{gitGraphDiagram-XJZIOB7I-Cvw304HC.js → gitGraphDiagram-XJZIOB7I-B_WcqSId.js} +1 -1
- package/renderer/dist/assets/{infoDiagram-5W2HQ5XZ-_ZXcNViH.js → infoDiagram-5W2HQ5XZ-CI-jYqzc.js} +1 -1
- package/renderer/dist/assets/{ishikawaDiagram-K3B6WC7H-C7Mx_qQk.js → ishikawaDiagram-K3B6WC7H-B56VWRlV.js} +1 -1
- package/renderer/dist/assets/{journeyDiagram-COXZFDF6-fr62QoAt.js → journeyDiagram-COXZFDF6-D1-p09Lr.js} +1 -1
- package/renderer/dist/assets/{kanban-definition-P3RFRI5V-J8zsabCe.js → kanban-definition-P3RFRI5V-Cv3w7JoN.js} +1 -1
- package/renderer/dist/assets/{linear-BmNdEtIi.js → linear-C35V2Spi.js} +1 -1
- package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
- package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
- package/renderer/dist/assets/{mermaid.core-CnYzvFkz.js → mermaid.core-BzGT5Vo9.js} +4 -4
- package/renderer/dist/assets/{mindmap-definition-LZFPQGKD-Bfcxc7PM.js → mindmap-definition-LZFPQGKD-BZUfuymC.js} +1 -1
- package/renderer/dist/assets/{pegDiagram-BGZESJAR-CwUJOq6X.js → pegDiagram-BGZESJAR-DFh9XNGe.js} +1 -1
- package/renderer/dist/assets/{pieDiagram-CAPJLFHJ-BrwMmqfC.js → pieDiagram-CAPJLFHJ-C65tPVsy.js} +1 -1
- package/renderer/dist/assets/{product-name-CTKLYNWY.js → product-name-CSNW6d7s.js} +1 -1
- package/renderer/dist/assets/{quadrantDiagram-GDMTTRAM-BN4Stoj-.js → quadrantDiagram-GDMTTRAM-Cushj_nk.js} +1 -1
- package/renderer/dist/assets/{railroadDiagram-SM67HX2B-CPZ5bbeR.js → railroadDiagram-SM67HX2B-DZGebGx5.js} +1 -1
- package/renderer/dist/assets/{requirementDiagram-X7JNWC4A-CgRnimVe.js → requirementDiagram-X7JNWC4A-DpAB0MNo.js} +1 -1
- package/renderer/dist/assets/{sankeyDiagram-UM26HJRW-Bt2rej__.js → sankeyDiagram-UM26HJRW-D-29rHra.js} +1 -1
- package/renderer/dist/assets/{sequenceDiagram-ZO4K6R2Y-Dd5nKe9G.js → sequenceDiagram-ZO4K6R2Y-FfOQAZol.js} +1 -1
- package/renderer/dist/assets/{sizeCapture-INFHLROL-usyNyXyh.js → sizeCapture-INFHLROL-C_n6fDBY.js} +1 -1
- package/renderer/dist/assets/{stateDiagram-v2-TBUQTH76-CRzUBb0u.js → stateDiagram-v2-TBUQTH76-BZjenzJG.js} +1 -1
- package/renderer/dist/assets/{swimlanes-N4OXWK64-C1GJvMS0.js → swimlanes-N4OXWK64-DxaMU9vX.js} +1 -1
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
- package/renderer/dist/assets/{timeline-definition-YOQAKGHF-caeyPlcc.js → timeline-definition-YOQAKGHF-DTg5HJKz.js} +1 -1
- package/renderer/dist/assets/{usecaseDiagram-VIAY4XPW-BN_SV6PC.js → usecaseDiagram-VIAY4XPW-Bc3MZDsl.js} +1 -1
- package/renderer/dist/assets/{vennDiagram-BLWOH2XV-BLGuaUX-.js → vennDiagram-BLWOH2XV-GIVGk9Id.js} +1 -1
- package/renderer/dist/assets/{wardleyDiagram-YMQ3BMBF-Da0cJeNc.js → wardleyDiagram-YMQ3BMBF-NmJnbQOg.js} +1 -1
- package/renderer/dist/assets/{xychartDiagram-TAQBALBS-cGfUffMk.js → xychartDiagram-TAQBALBS-p-kfGfnW.js} +1 -1
- package/renderer/dist/corner-tag.html +3 -3
- package/renderer/dist/index.html +3 -3
- package/scripts/after-install.mjs +30 -8
- package/scripts/cloud-shim.pl +57 -0
- package/shared/account-names.d.mts +6 -0
- package/shared/account-names.mjs +15 -0
- package/shared/agent-hours.mjs +136 -0
- package/shared/agent-import.mjs +60 -34
- package/shared/agents.mjs +126 -522
- package/shared/analytics-events.mjs +12 -0
- package/shared/approval-card.mjs +36 -0
- package/shared/bridge-map.mjs +27 -1
- package/shared/claude-code-shape.mjs +6 -0
- package/shared/claude-usage.d.mts +1 -0
- package/shared/claude-usage.mjs +42 -1
- package/shared/cloud-hand-back.d.mts +7 -0
- package/shared/cloud-hand-back.mjs +20 -0
- package/shared/cloud-kit.d.mts +219 -0
- package/shared/cloud-kit.mjs +341 -0
- package/shared/cloud-login-name.mjs +4 -0
- package/shared/cloud-start.mjs +17 -0
- package/shared/cloud-versions.mjs +23 -0
- package/shared/codex-import.mjs +2 -2
- package/shared/codex-modes.d.mts +22 -9
- package/shared/codex-modes.mjs +67 -11
- package/shared/codex-usage.mjs +108 -2
- package/shared/corner-tag.d.mts +23 -1
- package/shared/corner-tag.mjs +284 -29
- package/shared/dead-run-trace.mjs +19 -2
- package/shared/engines.mjs +70 -309
- package/shared/feedback.d.mts +7 -0
- package/shared/feedback.mjs +25 -0
- package/shared/first-run-practice.mjs +17 -24
- package/shared/harness-definitions.mjs +8 -0
- package/shared/instruction-defaults.json +2 -2
- package/shared/key-caps.d.mts +14 -0
- package/shared/key-caps.mjs +68 -0
- package/shared/message-edits.d.mts +7 -0
- package/shared/message-edits.mjs +35 -0
- package/shared/move-thread.d.mts +24 -0
- package/shared/move-thread.mjs +112 -0
- package/shared/named-threads.mjs +72 -0
- package/shared/not-shipped.d.mts +22 -0
- package/shared/not-shipped.mjs +89 -0
- package/shared/notify-rules.d.mts +6 -0
- package/shared/notify-rules.mjs +36 -8
- package/shared/phone-files.mjs +28 -0
- package/shared/phone-settings.mjs +118 -0
- package/shared/phone-snapshot.d.mts +16 -0
- package/shared/phone-snapshot.mjs +348 -0
- package/shared/picture-formats.d.mts +1 -0
- package/shared/picture-formats.mjs +13 -0
- package/shared/rank.d.mts +2 -1
- package/shared/rank.mjs +32 -19
- package/shared/repeats.d.mts +8 -0
- package/shared/repeats.mjs +33 -2
- package/shared/room-cards.mjs +167 -0
- package/shared/room-links.d.mts +4 -0
- package/shared/room-links.mjs +21 -0
- package/shared/runs-on.d.mts +3 -3
- package/shared/runs-on.mjs +8 -5
- package/shared/shipped-note.d.mts +4 -0
- package/shared/shipped-note.mjs +6 -0
- package/shared/shortcuts.mjs +14 -6
- package/shared/spawn-trouble.d.mts +4 -0
- package/shared/spawn-trouble.mjs +443 -40
- package/shared/team-rules.mjs +9 -2
- package/shared/teammates.d.mts +19 -0
- package/shared/teammates.mjs +756 -0
- package/shared/terminal-focus.d.mts +1 -0
- package/shared/terminal-focus.mjs +13 -0
- package/shared/thread-cards.mjs +4 -1
- package/shared/usage.d.mts +9 -0
- package/shared/usage.mjs +49 -3
- package/shared/websites.d.mts +15 -0
- package/shared/websites.mjs +61 -0
- package/shared/work-items.mjs +127 -30
- package/shared/work-lines.mjs +92 -244
- package/renderer/dist/assets/channel-BlT2g9Sz.js +0 -1
- package/renderer/dist/assets/cornerTag-BLrxiDE5.css +0 -1
- package/renderer/dist/assets/cornerTag-BOT6u3Tn.js +0 -1
- package/renderer/dist/assets/main-ChNdnUvt.js +0 -1132
- package/renderer/dist/assets/main-nj0QR5kg.css +0 -32
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cy4Av2_p.js +0 -8
package/shared/engines.mjs
CHANGED
|
@@ -1,65 +1,23 @@
|
|
|
1
1
|
import { HARNESS_DEFINITIONS } from './harness-definitions.mjs';
|
|
2
|
-
//
|
|
2
|
+
// Which coding agent a task is handed to.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// 96.9 / 18.6 seconds, Claude Code 19.0 / 19.8 / 19.8 / 21.2. Both correct
|
|
7
|
-
// every run. Codex is NOT faster; it is slower on median and it wanders. That
|
|
8
|
-
// is written here rather than in a report because it is the reason this file
|
|
9
|
-
// must never make Codex the default for anybody, including her. Claude Code is
|
|
10
|
-
// the default and stays the default until she says otherwise. What this file
|
|
11
|
-
// buys is a SECOND ENGINE on a second subscription, which is the thing that
|
|
12
|
-
// actually went wrong for her, and a choice she makes per task.
|
|
4
|
+
// Claude Code is the default. Codex measured slower on median and wandered on
|
|
5
|
+
// the same tasks, so do not make it the default for anybody.
|
|
13
6
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
7
|
+
// Old rows may still carry `engine: "codex"`, because the work-item fold keeps
|
|
8
|
+
// every field it was given. So `engineFor` answers Claude Code unless the
|
|
9
|
+
// caller hands it `ENGINE_CHOICE_ENABLED`, and a caller that passes nothing
|
|
10
|
+
// gets the safe answer.
|
|
17
11
|
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// because "nothing here reads them"). The fold has no migration and drops
|
|
22
|
-
// nothing, so a row marked between 08-25 and 08-27 still carries
|
|
23
|
-
// `engine: "codex"` UNLESS a later line overwrote that one field — which is
|
|
24
|
-
// possible: the fold lets an equal-or-stronger write replace a field held by
|
|
25
|
-
// an earlier one (`beats`, the authority comparison near shared/work-items.mjs
|
|
26
|
-
// :494). Nothing has ever been written to clear it on purpose, so the honest
|
|
27
|
-
// statement is "probably still there", not "always will be".
|
|
28
|
-
//
|
|
29
|
-
// HOW MANY SUCH ROWS A STORE ACTUALLY HAS IS AN OPEN QUESTION, AND IT IS NOT
|
|
30
|
-
// ZERO UNTIL SOMEBODY LOOKS. It could not be measured from the machine this was
|
|
31
|
-
// written on: there is no ~/Zero there at all. On a real store it is one command,
|
|
32
|
-
// `grep -rho '"engine":"[a-z]*"' ~/Zero | sort | uniq -c`, and the answer
|
|
33
|
-
// belongs in this paragraph when someone runs it. The gate below costs nothing
|
|
34
|
-
// if the count turns out to be zero, and is the whole story if it is not.
|
|
35
|
-
//
|
|
36
|
-
// So the 08-25 resolver, restored unchanged, would have been a silent reversal
|
|
37
|
-
// of the decision to withhold Codex: those rows would start choosing Codex again the moment this
|
|
38
|
-
// module landed, with no picker, nothing in Settings, and no approval path
|
|
39
|
-
// built. The resolver is therefore CLOSED unless the caller hands it
|
|
40
|
-
// `ENGINE_CHOICE_ENABLED` below. A caller that passes nothing gets Claude
|
|
41
|
-
// Code, because the safe answer is the one that has to be the accident.
|
|
42
|
-
//
|
|
43
|
-
// This module holds the names and the one rule. Everything Codex-shaped —
|
|
44
|
-
// arguments, its event stream, where it writes its transcript — belongs in
|
|
45
|
-
// main/codex.mjs when that comes back, because the renderer must never import
|
|
46
|
-
// spawn code.
|
|
12
|
+
// This module holds the names and the one rule. Codex arguments and its event
|
|
13
|
+
// stream live in main/codex.mjs, because the renderer imports this file and
|
|
14
|
+
// must never import spawn code.
|
|
47
15
|
|
|
48
16
|
/**
|
|
49
|
-
* The engines, in the order the picker draws them.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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.
|
|
17
|
+
* The engines, in the order the picker draws them. Admitted harnesses only: a
|
|
18
|
+
* registered harness (OpenCode, say) still works through `harnessFor` by name
|
|
19
|
+
* but is not offered anywhere until it is admitted. The bar for admission is in
|
|
20
|
+
* docs/harnesses.md.
|
|
63
21
|
*/
|
|
64
22
|
export const ENGINES = HARNESS_DEFINITIONS
|
|
65
23
|
.filter(({ admitted }) => admitted !== false)
|
|
@@ -68,164 +26,58 @@ export const ENGINES = HARNESS_DEFINITIONS
|
|
|
68
26
|
export const ENGINE_IDS = ENGINES.map((e) => e.id);
|
|
69
27
|
|
|
70
28
|
/**
|
|
71
|
-
* What a task runs on when nobody has chosen.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* faster on median and never wandered, and it is the only one of the two that
|
|
75
|
-
* is WIRED to stop and ask her for permission mid-run.
|
|
76
|
-
*
|
|
77
|
-
* That second clause used to say Codex CANNOT ask, and it is now wrong twice
|
|
78
|
-
* over. It was true of `codex exec`, which is what the removed 08-25 build
|
|
79
|
-
* drove and what the sentence was written about. `codex app-server` raises real
|
|
80
|
-
* approval requests, measured 2026-09-04, main/codex-app-server.mjs answers
|
|
81
|
-
* them, main/codex-approvals.mjs draws them as the card she already knows, and
|
|
82
|
-
* the spool they land in is the one the other engine uses. This paragraph went
|
|
83
|
-
* on listing all four of those as missing for a whole slice after two of them
|
|
84
|
-
* had been built.
|
|
85
|
-
*
|
|
86
|
-
* THE PICKER IS BUILT NOW TOO, 2026-09-04: a word in the composer's sentence
|
|
87
|
-
* ("With Codex."), a row in Settings, and the engine named in the byline --
|
|
88
|
-
* every one of them drawn only where `Supervisor#engineChoices` says this Mac
|
|
89
|
-
* really offers a choice, which means the gate is open and the binary is there.
|
|
90
|
-
* That paragraph used to say the missing picker was why the default had not
|
|
91
|
-
* moved, and it was the last of four such claims to stop being true.
|
|
92
|
-
*
|
|
93
|
-
* SO THE DEFAULT IS HELD BY THE MEASUREMENT AT THE TOP OF THIS FILE AND BY
|
|
94
|
-
* NOTHING ELSE. Codex is slower on median and it wanders; Claude Code stays the
|
|
95
|
-
* default until she says otherwise, and now that there is a picker that is a
|
|
96
|
-
* decision rather than a consequence.
|
|
29
|
+
* What a task runs on when nobody has chosen. It is Claude Code for the speed
|
|
30
|
+
* reason at the top of this file, not because Codex lacks approvals or a
|
|
31
|
+
* picker: both exist now.
|
|
97
32
|
*/
|
|
98
33
|
export const DEFAULT_ENGINE = 'claude';
|
|
99
34
|
|
|
100
35
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
* can import the token. It moves the decision from data to source, and that is
|
|
110
|
-
* all it does. It is the second half, the picker and her consent, that makes
|
|
111
|
-
* the choice real.
|
|
112
|
-
*
|
|
113
|
-
* It is a Symbol, and that shape is the whole design:
|
|
114
|
-
*
|
|
115
|
-
* - A SYMBOL CANNOT ARRIVE FROM OUTSIDE THE PROCESS. Every input that could
|
|
116
|
-
* carry a stale `codex` is serialized: the ledger line off disk and
|
|
117
|
-
* `zero.config.json` are JSON, which has no way to spell a Symbol and drops
|
|
118
|
-
* it in silence, and an Electron IPC payload goes through the structured
|
|
119
|
-
* clone algorithm, which does not drop a Symbol but THROWS DataCloneError on
|
|
120
|
-
* one. Neither can express the gate. The louder of the two failures is the
|
|
121
|
-
* better one and we get it where it matters least; the point is that the
|
|
122
|
-
* untrusted data and the permission live in different type systems.
|
|
123
|
-
* - IT IS `Symbol`, NEVER `Symbol.for`. The global registry would let any code
|
|
124
|
-
* anywhere mint an equal one out of a string. This one exists only where
|
|
125
|
-
* somebody imported this module and named it.
|
|
126
|
-
* - IT IS NOT A BOOLEAN. `enabled: true` is the kind of thing a caller spreads
|
|
127
|
-
* in by accident from some other options object. This is not.
|
|
36
|
+
* The capability gate: passing this to `engineFor` says the call site meant to
|
|
37
|
+
* allow a second engine. It blocks serialized data only. Any module here can
|
|
38
|
+
* import it.
|
|
39
|
+
*
|
|
40
|
+
* It is a Symbol because nothing from outside the process can carry one: JSON
|
|
41
|
+
* drops it, and Electron's structured clone throws DataCloneError on it. It
|
|
42
|
+
* uses `Symbol`, not `Symbol.for`, so no string can mint an equal one, and it
|
|
43
|
+
* is not a boolean, so a spread options object cannot open it by accident.
|
|
128
44
|
*/
|
|
129
45
|
export const ENGINE_CHOICE_ENABLED = Symbol('the second engine is wired up in this build');
|
|
130
46
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
* one value in the app that can cause the Symbol above to be handed over
|
|
134
|
-
* (main/supervisor.mjs `_engineFor`, the only holder of it).
|
|
135
|
-
*
|
|
136
|
-
* THE OPT-IN IS A MOMENT AND NOT A FLAG, and that is the whole design of it.
|
|
137
|
-
*
|
|
138
|
-
* `engine` survived the 2026-08-27 removal on the work-item contract and the
|
|
139
|
-
* fold drops nothing, so her store may still hold rows marked `codex` between
|
|
140
|
-
* 08-25 and 08-27. How many is unknown; on her Mac it is one command,
|
|
141
|
-
* `grep -rho '"engine":"[a-z]*"' ~/Zero | sort | uniq -c`, and the answer
|
|
142
|
-
* belongs here when somebody runs it. A boolean opt-in would mean "every row
|
|
143
|
-
* that ever said codex is live from now on", and the first thing it would do is
|
|
144
|
-
* route an unknown number of those onto a second engine she had marked before
|
|
145
|
-
* there was any way to choose. That is the exact hazard the gate was built for,
|
|
146
|
-
* so an opt-in that walks into it is not one worth having. The picker built on
|
|
147
|
-
* 2026-09-04 does not soften this: an August row is still not a choice she is
|
|
148
|
-
* making now, and the byline draws it as the engine that will really run it.
|
|
149
|
-
*
|
|
150
|
-
* THOSE TWO MEANINGS ARE SEPARABLE, AND THE LEDGER ALREADY HOLDS WHAT
|
|
151
|
-
* SEPARATES THEM. The fold records `wrote[field] = { ts, source }` per field
|
|
152
|
-
* and stopped deleting it on the way out precisely because readers needed it
|
|
153
|
-
* (shared/work-items.mjs:324; shared/answers.mjs reads `wrote.answer.ts` the
|
|
154
|
-
* same way). So "Codex may be chosen FROM NOW ON" is a checkable statement
|
|
155
|
-
* rather than a hope, and `engineChoiceOnRowIsStale` below is the check.
|
|
156
|
-
*
|
|
157
|
-
* WHY NOT A BOOLEAN PLUS A REMEMBERED "first seen at". Because that would live
|
|
158
|
-
* in the app's own state rather than in her file: a fresh install, a state file
|
|
159
|
-
* she deleted, or a second Mac would each invent a different moment, and the
|
|
160
|
-
* one thing this value must be is the same on every machine reading the same
|
|
161
|
-
* store. A date the user typed is a fact about their decision; a date we noticed is a
|
|
162
|
-
* fact about a process.
|
|
163
|
-
*
|
|
164
|
-
* THE WORKSPACE DEFAULT (`config.engine`) IS TIME-SCOPED TOO, SINCE 2026-09-05,
|
|
165
|
-
* AND THIS PARAGRAPH USED TO SAY IT COULD NOT BE. Its words were: "JSON has no
|
|
166
|
-
* per-key write times, and `engine` sits in the same file as this, so opening
|
|
167
|
-
* that file to write the moment is the same act as reading the line above it."
|
|
168
|
-
* The first clause is true of JSON and irrelevant: the APP has write times, one
|
|
169
|
-
* module writes that key (`setWorkspaceSetting`, main/settings.mjs), and
|
|
170
|
-
* `saveConfig` takes a patch of several keys, so `engineAt` rides in the same
|
|
171
|
-
* write. Without it, closing the gate and reopening it with a NEWER moment
|
|
172
|
-
* re-armed a workspace default set weeks earlier -- every row that names
|
|
173
|
-
* nothing went straight back to Codex without her saying so again, which is the
|
|
174
|
-
* exact claim this value is supposed to make impossible, and the rollback
|
|
175
|
-
* lifecycle is the thing the gate is FOR.
|
|
176
|
-
*
|
|
177
|
-
* AND THE SECOND CLAUSE, READ CORRECTLY, IS WHY AN ABSENT STAMP MEANS HONOURED.
|
|
178
|
-
* `engineChoice` is hand-written into this file; a hand-written `engine` on the
|
|
179
|
-
* line above it was put there by the same hand in the same act, so the two ARE
|
|
180
|
-
* contemporaneous and there is nothing to be stale against. Only a default the
|
|
181
|
-
* app wrote can carry a stamp, and only a stamped default can go stale — which
|
|
182
|
-
* is also what keeps every config that exists today behaving exactly as it did.
|
|
183
|
-
* `engineDefaultIsStale` below is the check.
|
|
184
|
-
*
|
|
185
|
-
* IT IS PARSED STRICTLY, WHICH IS NOT THE SAME AS PARSED. `Date.parse` reads
|
|
186
|
-
* "1", "2026", "Sep 4 2026" and "2026/09/04" as real dates, so a bare parse
|
|
187
|
-
* would let `"engineChoice": "1"` open the second engine. The shape is checked
|
|
188
|
-
* first and everything else is off, because the safe answer is the one that has
|
|
189
|
-
* to be the accident.
|
|
190
|
-
*/
|
|
47
|
+
// The shape of a date somebody wrote down. `Date.parse` alone would accept "1"
|
|
48
|
+
// or "2026", and `"engineChoice": "1"` must not open the second engine.
|
|
191
49
|
const A_MOMENT = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
192
50
|
|
|
193
|
-
/**
|
|
194
|
-
* One strict parse, used by both moments in this file, so the two cannot come
|
|
195
|
-
* to disagree about what counts as a date somebody wrote down. */
|
|
196
51
|
function momentIn(written) {
|
|
197
52
|
if (typeof written !== 'string' || !A_MOMENT.test(written)) return null;
|
|
198
53
|
const at = Date.parse(written);
|
|
199
54
|
return Number.isFinite(at) ? at : null;
|
|
200
55
|
}
|
|
201
56
|
|
|
57
|
+
/**
|
|
58
|
+
* When the user opened the gate (`engineChoice` in zero.config.json), or null.
|
|
59
|
+
* It is a moment, not a flag, so an engine written on a row or as the default
|
|
60
|
+
* before it is not a choice made now and is ignored. It lives in the config
|
|
61
|
+
* rather than app state so every machine reading the same store agrees.
|
|
62
|
+
* main/supervisor.mjs `_engineFor` is the only place that turns it into the token.
|
|
63
|
+
*/
|
|
202
64
|
export function engineChoiceSince(config = {}) {
|
|
203
65
|
return momentIn(config?.engineChoice);
|
|
204
66
|
}
|
|
205
67
|
|
|
206
68
|
/**
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
* note above `engineChoiceSince`. It is the same strict parse for the same
|
|
211
|
-
* reason: a claim that cannot be read is not a claim, so it reads as no stamp,
|
|
212
|
-
* which is the hand-written case.
|
|
69
|
+
* When the app last wrote the workspace default (`engineAt`, written beside
|
|
70
|
+
* `engine` by `setWorkspaceSetting`), or null. Null means the default was typed
|
|
71
|
+
* into the file by hand, in the same act as `engineChoice`, so it is never stale.
|
|
213
72
|
*/
|
|
214
73
|
export function engineDefaultSince(config = {}) {
|
|
215
74
|
return momentIn(config?.engineAt);
|
|
216
75
|
}
|
|
217
76
|
|
|
218
77
|
/**
|
|
219
|
-
* Whether the workspace default
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
* False when nobody has opted in, because there is no moment to be stale
|
|
224
|
-
* against. False when the default is Claude Code, which asks for nothing the
|
|
225
|
-
* gate withholds — the same rule `engineChoiceOnRowIsStale` follows below, and
|
|
226
|
-
* for the same reason: discarding it could only ever move work TO the second
|
|
227
|
-
* engine, which is the one direction the gate exists to prevent. False when
|
|
228
|
-
* there is no stamp, which is a default typed beside the moment itself.
|
|
78
|
+
* Whether the workspace default was set before the gate's current moment.
|
|
79
|
+
* Never true for Claude Code: discarding that default could only move work onto
|
|
80
|
+
* the second engine.
|
|
229
81
|
*/
|
|
230
82
|
export function engineDefaultIsStale(config = {}) {
|
|
231
83
|
const since = engineChoiceSince(config);
|
|
@@ -236,33 +88,13 @@ export function engineDefaultIsStale(config = {}) {
|
|
|
236
88
|
}
|
|
237
89
|
|
|
238
90
|
/**
|
|
239
|
-
* Whether the engine named on this row
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
* against and the gate itself is what refuses then. False when the row names no
|
|
244
|
-
* engine, or names one nothing recognises, because neither is asking for
|
|
245
|
-
* anything and a warning on an ordinary row is noise on every row. True when
|
|
246
|
-
* the row names an engine and there is no `wrote.engine.ts` at or after the
|
|
247
|
-
* moment — which also covers an imported session and an old line with no
|
|
248
|
-
* `wrote` map at all, where there is simply no evidence she chose anything.
|
|
249
|
-
*
|
|
250
|
-
* AND FALSE FOR A ROW THAT NAMES CLAUDE CODE, WHICH THIS ASKED WITH `isEngine`
|
|
251
|
-
* AND GOT BACKWARDS. A row saying `engine: "claude"`, written before the moment,
|
|
252
|
-
* was discarded as stale — and `engineFor` then fell through to `config.engine`,
|
|
253
|
-
* which may be `codex`. So her explicit "run this on Claude Code" was read as no
|
|
254
|
-
* choice at all and answered with the second engine, which is the precise
|
|
255
|
-
* opposite of what staleness is for. `enginePicked` is the fix and it is the
|
|
256
|
-
* whole of it: naming the DEFAULT asks for nothing the gate withholds, so there
|
|
257
|
-
* is nothing to be stale about, and discarding it could only ever move work onto
|
|
258
|
-
* the engine the gate was built to hold back.
|
|
91
|
+
* Whether the engine named on this row was written before the gate's moment.
|
|
92
|
+
* A row with no `wrote.engine.ts` counts as stale, since there is no evidence
|
|
93
|
+
* of a choice. A row naming Claude Code is never stale: discarding it would let
|
|
94
|
+
* `engineFor` fall through to a Codex workspace default.
|
|
259
95
|
*
|
|
260
|
-
* `wrote.engine.source` is
|
|
261
|
-
*
|
|
262
|
-
* plain string and nothing checks it, so a source check would guard against an
|
|
263
|
-
* accident and never against a hostile worker, while silently refusing whatever
|
|
264
|
-
* a future picker happens to write. Who may write `engine` at all belongs to
|
|
265
|
-
* the slice that draws one.
|
|
96
|
+
* `wrote.engine.source` is not consulted, because a ledger line names its own
|
|
97
|
+
* author as an unchecked string.
|
|
266
98
|
*/
|
|
267
99
|
export function engineChoiceOnRowIsStale(item, config = {}) {
|
|
268
100
|
const since = engineChoiceSince(config);
|
|
@@ -281,34 +113,14 @@ export function engineLabel(id) {
|
|
|
281
113
|
}
|
|
282
114
|
|
|
283
115
|
/**
|
|
284
|
-
* Which engines this Mac can
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* UNGATED, AND THE INVARIANT IS THEREFORE SCOPED TO ROUTING. Say it plainly,
|
|
294
|
-
* because the file must not claim more than the code delivers: this function,
|
|
295
|
-
* `engineChoiceExists` and `enginePicked` will all happily describe a two-engine
|
|
296
|
-
* Mac without anyone holding the token. A future renderer could draw a picker
|
|
297
|
-
* out of them, and the gate would not stop it — she would be offered a choice
|
|
298
|
-
* that `engineFor` then refuses, which is its own kind of lie.
|
|
299
|
-
*
|
|
300
|
-
* That is a deliberate trade and not an oversight. These three answer "what is
|
|
301
|
-
* on this Mac" and "what did she pick"; they spawn nothing, and requiring a
|
|
302
|
-
* capability token to answer a question about the filesystem buys nothing real.
|
|
303
|
-
*
|
|
304
|
-
* AND THE SLICE THAT DREW A PICKER ANSWERED FOR IT, 2026-09-04. There is
|
|
305
|
-
* exactly one caller, `Supervisor#engineChoices`, and it asks
|
|
306
|
-
* `engineChoiceSince` FIRST -- the same expression `_engineFor` derives the gate
|
|
307
|
-
* token from -- so the offer and the routing are refused by the same fact and
|
|
308
|
-
* cannot come apart. A Mac with Codex installed and no moment written answers
|
|
309
|
-
* one engine here and Claude Code there. The renderer calls none of these: it
|
|
310
|
-
* is handed the list on the snapshot. tests/a-codex-row-from-august-still-runs-on-claude.test.mjs
|
|
311
|
-
* holds the caller list exactly, so the next one has to argue for itself here.
|
|
116
|
+
* Which engines this Mac can run, given `found` (id to boolean from the
|
|
117
|
+
* finders). Claude Code is listed even when not found, unless Codex was.
|
|
118
|
+
*
|
|
119
|
+
* This is ungated, like `engineChoiceExists` and `enginePicked`. Its one caller
|
|
120
|
+
* is `Supervisor#engineChoices`, which asks `engineChoiceSince` first, so the
|
|
121
|
+
* offer and the routing cannot disagree. The renderer gets the list on the
|
|
122
|
+
* snapshot. tests/a-codex-row-from-august-still-runs-on-claude.test.mjs pins the
|
|
123
|
+
* caller list.
|
|
312
124
|
*/
|
|
313
125
|
export function availableEngines(found = {}) {
|
|
314
126
|
const home = homeEngine(found);
|
|
@@ -316,13 +128,9 @@ export function availableEngines(found = {}) {
|
|
|
316
128
|
}
|
|
317
129
|
|
|
318
130
|
/**
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* `found.claude` has to be an explicit `false`. A caller that never asked
|
|
324
|
-
* (every call written before this, and every test config) leaves it undefined,
|
|
325
|
-
* and that reads as Claude Code being here, which is what it always meant.
|
|
131
|
+
* What runs on this Mac when nobody has chosen: Codex only when `found.claude`
|
|
132
|
+
* is explicitly false and Codex was found. Undefined means a caller that never
|
|
133
|
+
* asked, and reads as Claude Code being here.
|
|
326
134
|
*/
|
|
327
135
|
export function homeEngine(found = {}) {
|
|
328
136
|
return found.claude === false && found.codex ? 'codex' : DEFAULT_ENGINE;
|
|
@@ -334,37 +142,13 @@ export function engineChoiceExists(found = {}) {
|
|
|
334
142
|
}
|
|
335
143
|
|
|
336
144
|
/**
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
* the honest answer for any caller that has not been taught about the second
|
|
343
|
-
* engine — including every caller written before it comes back — is the engine
|
|
344
|
-
* she is running.
|
|
345
|
-
*
|
|
346
|
-
* Then her choice on the task, over the workspace default, which is hers to
|
|
347
|
-
* set in Settings, over Claude Code. An engine named on a row whose binary is
|
|
348
|
-
* not on this Mac falls back rather than dying: she may have chosen Codex on
|
|
349
|
-
* her laptop and be reading the row on a machine without it, and a session
|
|
350
|
-
* that never starts tells her nothing about why. That fallback is unchanged
|
|
351
|
-
* and the gate is IN ADDITION to it, not instead of it — the binary is a fact
|
|
352
|
-
* about this Mac, the gate is a fact about this build, and either one missing
|
|
353
|
-
* is enough to answer Claude Code.
|
|
354
|
-
*
|
|
355
|
-
* AND A CHOICE FROM BEFORE SHE OPENED THE GATE IS NOT A CHOICE SHE IS MAKING.
|
|
356
|
-
* When the config names the moment she opted in, a row whose `engine` predates
|
|
357
|
-
* it reads as no choice at all and the workspace default answers instead. That
|
|
358
|
-
* is `engineChoiceOnRowIsStale` above, and it is the whole of what makes the
|
|
359
|
-
* opt-in mean "Codex may be chosen from now on" rather than "every row that
|
|
360
|
-
* ever said codex is live". A caller that passes no such moment gets the old
|
|
361
|
-
* rule unchanged, which is why every assertion written before this one still
|
|
362
|
-
* holds -- but the single caller in the app that can open the gate derives the
|
|
363
|
-
* token FROM that same value, so in her app the two cannot come apart.
|
|
145
|
+
* Which engine one work item runs on. A Mac with only Codex runs Codex.
|
|
146
|
+
* Otherwise, without `enabled: ENGINE_CHOICE_ENABLED` it is Claude Code. With
|
|
147
|
+
* it, the row's engine wins over the workspace default, each ignored if written
|
|
148
|
+
* before the gate's moment. An engine whose binary is not on this Mac falls back
|
|
149
|
+
* to Claude Code, since the row may have been chosen on another machine.
|
|
364
150
|
*/
|
|
365
151
|
export function engineFor(item, { config = {}, found = {}, enabled } = {}) {
|
|
366
|
-
// ONE ENGINE ON THE MAC IS NOT A CHOICE, so the gate has nothing to guard.
|
|
367
|
-
// Without Claude Code, a row marked `claude` would only die on arrival.
|
|
368
152
|
if (homeEngine(found) !== DEFAULT_ENGINE) return homeEngine(found);
|
|
369
153
|
if (enabled !== ENGINE_CHOICE_ENABLED) return DEFAULT_ENGINE;
|
|
370
154
|
const wanted = isEngine(item?.engine) && !engineChoiceOnRowIsStale(item, config) ? item.engine : null;
|
|
@@ -375,43 +159,20 @@ export function engineFor(item, { config = {}, found = {}, enabled } = {}) {
|
|
|
375
159
|
}
|
|
376
160
|
|
|
377
161
|
/**
|
|
378
|
-
* Whether the
|
|
379
|
-
*
|
|
380
|
-
* rule the level and the model follow (renderer/src/compose-footer.ts).
|
|
162
|
+
* Whether the row names an engine other than the default. The composer's footer
|
|
163
|
+
* stays open on it, like the level and the model (renderer/src/compose-footer.ts).
|
|
381
164
|
*/
|
|
382
165
|
export function enginePicked(id) {
|
|
383
166
|
return isEngine(id) && id !== DEFAULT_ENGINE;
|
|
384
167
|
}
|
|
385
168
|
|
|
386
169
|
/**
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
* and `spawnPlan` then applied `item.model` without asking which engine wrote
|
|
394
|
-
* it. Measured 2026-09-05 by reading the resolved plan: a row saying `engine:
|
|
395
|
-
* "codex", model: "gpt-5.6-sol"` -- the slug her own config.toml names --
|
|
396
|
-
* produced `claude -p <brief> --model gpt-5.6-sol`, which Claude Code has never
|
|
397
|
-
* heard of. It dies on arrival and dies again on every retry.
|
|
398
|
-
*
|
|
399
|
-
* THE TWO FIELDS ARE ONE CHOICE AND THIS IS WHY. The composer's model list is
|
|
400
|
-
* scoped to the engine (`engineModelChoices`, renderer/src/models.ts) because
|
|
401
|
-
* the two vocabularies do not overlap at all: `opus` is an alias Claude Code
|
|
402
|
-
* resolves, `gpt-5.6-sol` is an exact slug Codex takes. So a model word means
|
|
403
|
-
* nothing without the engine beside it, and a row whose engine has changed
|
|
404
|
-
* under it has no model, rather than one that reads across.
|
|
405
|
-
*
|
|
406
|
-
* NULL IS NOT A STAND-IN AND THE CALLER MUST NOT TREAT IT AS ONE. This says
|
|
407
|
-
* "there is no model on this row for this run"; whether that means running on
|
|
408
|
-
* the workspace's own model or refusing the run outright is the caller's
|
|
409
|
-
* decision, and main/supervisor.mjs makes it -- it refuses, and says so on the
|
|
410
|
-
* row, for the reason `codexModelRefusal` gives on the other side.
|
|
411
|
-
*
|
|
412
|
-
* A ROW THAT NAMES NO ENGINE IS UNTOUCHED, which is every row she has ever
|
|
413
|
-
* written: there is no other harness in the sentence, so the model rides
|
|
414
|
-
* exactly as it did.
|
|
170
|
+
* The row's model for a run on `engine`, or null when the row names a different
|
|
171
|
+
* engine. Claude and Codex model names do not overlap, so a Codex slug must not
|
|
172
|
+
* reach `claude --model` when `engineFor` falls back to Claude Code. This is the
|
|
173
|
+
* mirror of `codexModelRefusal`. Null means "no model for this run", and
|
|
174
|
+
* main/supervisor.mjs decides what to do with it. A row naming no engine keeps
|
|
175
|
+
* its model.
|
|
415
176
|
*/
|
|
416
177
|
export function modelForEngine(item, engine) {
|
|
417
178
|
const model = typeof item?.model === 'string' && item.model ? item.model : null;
|
package/shared/feedback.d.mts
CHANGED
|
@@ -12,3 +12,10 @@ export function feedbackPayload(p?: {
|
|
|
12
12
|
files?: { name: string; type: string; data: string }[];
|
|
13
13
|
app?: { version?: string; os?: string };
|
|
14
14
|
}): { text: string; files: { name: string; type: string; data: string }[]; app: { version: string; os: string } };
|
|
15
|
+
|
|
16
|
+
export function sendFeedback(p: {
|
|
17
|
+
url: string | null | undefined;
|
|
18
|
+
payload: { text: string; files: { name: string; type: string; data: string }[]; app?: { version: string; os: string } };
|
|
19
|
+
fetchImpl?: (url: string, init: RequestInit) => Promise<{ ok: boolean }>;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
}): Promise<{ ok: boolean; error?: string }>;
|
package/shared/feedback.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// Words, files, or both, and never nothing. The size limit keeps one request
|
|
7
7
|
// well inside what the mail provider takes as attachments.
|
|
8
|
+
import { Name } from './product-name.mjs';
|
|
8
9
|
|
|
9
10
|
export const FEEDBACK_LIMITS = Object.freeze({
|
|
10
11
|
text: 20_000,
|
|
@@ -42,3 +43,27 @@ export function feedbackPayload({ text = '', files = [], app = {} } = {}) {
|
|
|
42
43
|
app: { version: String(app.version ?? ''), os: String(app.os ?? '') },
|
|
43
44
|
};
|
|
44
45
|
}
|
|
46
|
+
|
|
47
|
+
// Answers { ok: true } or { ok: false, error } with the error as a sentence.
|
|
48
|
+
// It never throws, so the card always has something to say. The Mac sends
|
|
49
|
+
// through it (main/feedback.mjs) and so does the phone website (cloud-bridge.ts).
|
|
50
|
+
export async function sendFeedback({ url, payload, fetchImpl = globalThis.fetch, timeoutMs = 60_000 }) {
|
|
51
|
+
if (!url) return { ok: false, error: `Feedback is not set up in this copy of ${Name}.` };
|
|
52
|
+
// Checked again here, off the bytes that would actually travel, since the
|
|
53
|
+
// card is not the last word on what leaves the device.
|
|
54
|
+
const sized = (payload?.files ?? []).map((f) => ({ size: Math.floor((String(f?.data ?? '').length * 3) / 4) }));
|
|
55
|
+
const check = checkFeedback({ text: payload?.text, files: sized });
|
|
56
|
+
if (!check.ok) return { ok: false, error: check.reason };
|
|
57
|
+
try {
|
|
58
|
+
const res = await fetchImpl(url, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'content-type': 'application/json' },
|
|
61
|
+
body: JSON.stringify(payload),
|
|
62
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
63
|
+
});
|
|
64
|
+
if (res?.ok) return { ok: true };
|
|
65
|
+
return { ok: false, error: 'It did not send. Try again in a moment.' };
|
|
66
|
+
} catch {
|
|
67
|
+
return { ok: false, error: 'It did not send. Check your connection and try again.' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -267,47 +267,40 @@ export const PRACTICE_ROWS = [
|
|
|
267
267
|
* off the screen by `walkRows` until the tour. Each `state` is the column it
|
|
268
268
|
* lands in, read off the row by the app's own rules and not by this file:
|
|
269
269
|
*
|
|
270
|
-
* * `needs`: an agent finished, or stopped on a question. Needs you. Only on
|
|
271
|
-
* the board, never in the tour, because the tour opens on "Your inbox is
|
|
272
|
-
* now empty" and these would make that untrue.
|
|
273
270
|
* * `working`: sent and not finished. In progress.
|
|
274
271
|
* * `scheduled`: set for tomorrow morning. Scheduled.
|
|
275
272
|
*
|
|
276
273
|
* None is finished, so "Everything you finished is kept here" stays true of
|
|
277
|
-
* the Closed tab.
|
|
274
|
+
* the Closed tab.
|
|
275
|
+
*
|
|
276
|
+
* AND NOT ONE OF THEM MAY EVER NEED YOU (2026-10-24). Two were `needs`, held
|
|
277
|
+
* back from the tour and let through from the board on, so pressing B put two
|
|
278
|
+
* rows into a Needs you the tour had just called empty. The same two pieces of
|
|
279
|
+
* work are still here in states that ask nothing of anybody, which keeps both
|
|
280
|
+
* rules: something in every column, and the inbox stays at zero. */
|
|
278
281
|
export const PRACTICE_BACKDROP = [
|
|
279
282
|
{
|
|
280
|
-
state: '
|
|
283
|
+
state: 'working',
|
|
281
284
|
kind: 'task',
|
|
282
|
-
title: '
|
|
285
|
+
title: 'Book the train for the offsite.',
|
|
283
286
|
body: 'Book train tickets for the team to the offsite on Thursday morning.',
|
|
284
287
|
trace: [
|
|
285
288
|
'[Read] Offsite plan.docx',
|
|
286
289
|
'[Read] Team contacts.xlsx',
|
|
287
290
|
'Twelve people, all leaving from the city on Thursday morning.',
|
|
288
|
-
'
|
|
291
|
+
'Comparing the 8:10 and the 9:10, which is the last one that still gets everybody there in time.',
|
|
289
292
|
],
|
|
290
|
-
result: 'Twelve seats on the 8:10 on Thursday, all in one carriage. The tickets are in the offsite folder.',
|
|
291
293
|
agoMs: 9 * 60_000,
|
|
292
294
|
},
|
|
293
295
|
{
|
|
294
|
-
state: '
|
|
295
|
-
kind: '
|
|
296
|
-
title: '
|
|
297
|
-
body: 'Send the welcome pack to the two new starters
|
|
298
|
-
trace: [
|
|
299
|
-
'[Read] Welcome pack.docx',
|
|
300
|
-
'[Read] Team calendar.ics',
|
|
301
|
-
'One starter begins on Monday and the other on Wednesday.',
|
|
302
|
-
],
|
|
303
|
-
result: [
|
|
304
|
-
'One new starter begins Monday and the other Wednesday. Sending both on Monday means nobody waits.',
|
|
305
|
-
'',
|
|
306
|
-
'## Options',
|
|
307
|
-
'1. Monday, for both (recommended)',
|
|
308
|
-
'2. On each person\'s first day',
|
|
309
|
-
].join('\n'),
|
|
296
|
+
state: 'scheduled',
|
|
297
|
+
kind: 'task',
|
|
298
|
+
title: 'Send the welcome pack to the two new starters.',
|
|
299
|
+
body: 'Send the welcome pack to the two new starters on Monday, before either of them begins.',
|
|
300
|
+
trace: [],
|
|
310
301
|
agoMs: 26 * 60_000,
|
|
302
|
+
// Monday morning, counted from when the practice project is made.
|
|
303
|
+
inMs: 66 * 3_600_000,
|
|
311
304
|
},
|
|
312
305
|
{
|
|
313
306
|
state: 'working',
|
|
@@ -13,6 +13,14 @@ export const HARNESS_DEFINITIONS = freeze([
|
|
|
13
13
|
installer: { shell: '/bin/bash', url: 'https://claude.ai/install.sh', pipe: 'bash' },
|
|
14
14
|
loginArgs: ['auth', 'login', '--claudeai'], statusArgs: ['auth', 'status', '--json'],
|
|
15
15
|
credentialFiles: ['.claude.json', '.credentials.json'],
|
|
16
|
+
// THE OLDEST CLAUDE CODE THE WORKER LINE BELOW RUNS ON. Measured 2026-10-15
|
|
17
|
+
// by running the app's own line against npm releases on a stand-in model:
|
|
18
|
+
// 2.1.71 (2026-03-06) is the first that takes `--permission-mode auto`,
|
|
19
|
+
// which every worker is started with; 2.1.70 and older die in under a
|
|
20
|
+
// second on it. Every other flag is older still (`--effort` arrived after
|
|
21
|
+
// 2.1.8, `--include-partial-messages` after 1.0.90). shared/spawn-trouble.mjs
|
|
22
|
+
// names it when an older one refuses the line.
|
|
23
|
+
minVersion: '2.1.71',
|
|
16
24
|
// HOW A WORKER IS STARTED, AS DATA. This was six hardcoded flag names in
|
|
17
25
|
// main/supervisor.mjs, which made the cheapest possible new engine — one
|
|
18
26
|
// that speaks this same stream-json and differs by a binary and the
|