agentbox-app 0.1.12 → 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/acp.mjs +296 -0
- package/main/agent-homes.mjs +158 -0
- package/main/agent-sessions.mjs +311 -260
- package/main/agent-updates.mjs +9 -22
- package/main/agents.mjs +179 -466
- package/main/analytics.mjs +42 -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-bin.mjs +45 -311
- package/main/claude-input.mjs +57 -7
- package/main/claude-launch-env.mjs +54 -0
- package/main/claude-models.mjs +39 -7
- package/main/claude-plan.mjs +64 -16
- package/main/claude-session-open.mjs +28 -0
- package/main/claude-usage-process.mjs +40 -0
- package/main/claude-usage.mjs +94 -59
- 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-bin.mjs +51 -249
- package/main/codex-models.mjs +20 -2
- package/main/codex-session.mjs +246 -506
- package/main/codex-threads-worker.mjs +12 -0
- package/main/codex-threads.mjs +112 -11
- package/main/codex-usage-file.mjs +5 -3
- package/main/codex-usage.mjs +56 -7
- package/main/codex-watch.mjs +83 -5
- package/main/codex.mjs +95 -363
- package/main/config.mjs +37 -5
- package/main/corner-tag.mjs +810 -0
- package/main/crash-report.mjs +26 -3
- package/main/dev-window.mjs +1 -1
- package/main/engine-bin.mjs +545 -0
- package/main/engine-setup.mjs +25 -24
- package/main/feedback.mjs +14 -31
- 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-stream.mjs +208 -0
- package/main/harnesses/claude.mjs +105 -0
- package/main/harnesses/codex.mjs +72 -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 +96 -0
- package/main/ipc.mjs +520 -135
- package/main/leftovers.mjs +117 -25
- package/main/liquid-glass.mjs +26 -0
- package/main/live-replies.mjs +10 -4
- package/main/local-app-frames.mjs +82 -0
- package/main/main-went-red.mjs +330 -0
- package/main/main.mjs +248 -37
- package/main/memory-gate-history.mjs +85 -20
- package/main/memory-gate-server.mjs +129 -11
- package/main/memory-gate.mjs +62 -8
- package/main/memory-log.mjs +124 -0
- 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 +64 -5
- package/main/opencode-server.mjs +271 -0
- package/main/opencode.mjs +287 -0
- package/main/phone-alerts.mjs +117 -0
- package/main/phone-inbox.mjs +625 -0
- package/main/phone-link.mjs +571 -0
- 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-address.mjs +28 -0
- package/main/relay-client.mjs +314 -0
- package/main/repeats.mjs +10 -4
- package/main/row-label.mjs +18 -47
- package/main/run-change.mjs +13 -0
- package/main/run-ledger.mjs +135 -0
- package/main/runs-on.mjs +32 -35
- package/main/save-attachments.mjs +41 -0
- package/main/serve.mjs +155 -9
- package/main/settings.mjs +364 -50
- package/main/shell-path.mjs +234 -0
- package/main/ship-conflict-brief.mjs +119 -0
- package/main/ship-queue.mjs +134 -20
- package/main/sign-in-files.mjs +3 -7
- package/main/source-updater.mjs +45 -11
- package/main/stage-attachment.mjs +68 -0
- package/main/stall-watch.mjs +50 -0
- package/main/start-hold.mjs +73 -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 +227 -53
- package/main/supervisor.mjs +3865 -699
- package/main/task-commands.mjs +13 -17
- package/main/task-folders-offthread.mjs +6 -1
- package/main/task-folders.mjs +897 -105
- package/main/task-terminals.mjs +147 -41
- 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/updater.mjs +71 -9
- package/main/webview-guard.mjs +45 -0
- package/main/worktree-include.mjs +365 -0
- 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 +30 -5
- package/renderer/dist/assets/abnfDiagram-YUKMZFIV-CoWB6YUK.js +1 -0
- package/renderer/dist/assets/arc-Ch0NrCul.js +1 -0
- package/renderer/dist/assets/architectureDiagram-47P4ROYG-CXz9M5tQ.js +36 -0
- package/renderer/dist/assets/blockDiagram-E7TT5TSB-DchGUuHH.js +140 -0
- package/renderer/dist/assets/c4Diagram-CRA5TL53-Cip-zNyr.js +38 -0
- package/renderer/dist/assets/channel-BQuQSL--.js +1 -0
- package/renderer/dist/assets/chunk-2Q5K7J3B-BoXid_2r.js +1 -0
- package/renderer/dist/assets/chunk-5VM5RSS4-Cpez1rRz.js +15 -0
- package/renderer/dist/assets/chunk-7TKQ45FW-DKA84YkE.js +1 -0
- package/renderer/dist/assets/chunk-AW2ZBBNX-Dg-SumPG.js +88 -0
- package/renderer/dist/assets/chunk-FVRAUYC3-06TCvPm1.js +2 -0
- package/renderer/dist/assets/chunk-HTAEGDNF-CMEBy1Td.js +1 -0
- package/renderer/dist/assets/chunk-JWPE2WC7-BJ2ObmV-.js +1 -0
- package/renderer/dist/assets/chunk-KQW6MTUR-CGWOYXUE.js +1 -0
- package/renderer/dist/assets/chunk-XXDRQBXY-CiZp07XC.js +1 -0
- package/renderer/dist/assets/classDiagram-v2-K4WV4PDN-DJNIX719.js +217 -0
- 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-CbkyZOUH.js +1 -0
- package/renderer/dist/assets/cynefin-EF2NZ3EQ-78iOroZO.js +141 -0
- package/renderer/dist/assets/cynefinDiagram-3GCD6N5R-GcCopMs7.js +62 -0
- package/renderer/dist/assets/cytoscape.esm-C5JX3QLV.js +331 -0
- package/renderer/dist/assets/dagre-W4DXFKR2-DHLlXLkz.js +4 -0
- package/renderer/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/renderer/dist/assets/diagram-2UJZ2QOL-DuZSUHcH.js +200 -0
- package/renderer/dist/assets/diagram-OVF4WLC6-Ckxu3cqH.js +24 -0
- package/renderer/dist/assets/diagram-PFPMY2P6-CsTw9Ycn.js +30 -0
- package/renderer/dist/assets/diagram-UMYRVEAY-CTXd8qVg.js +24 -0
- package/renderer/dist/assets/diagram-YEKJPTXX-CJs1T_9m.js +41 -0
- package/renderer/dist/assets/diagram-ZIFT7M5P-CvjI23mf.js +3 -0
- package/renderer/dist/assets/ebnfDiagram-VR2GEFS7-d_IeAzOJ.js +1 -0
- package/renderer/dist/assets/elk-IJKZMXRS-zo9KFP3N.js +27 -0
- package/renderer/dist/assets/erDiagram-O2IAPWRE-DqfYf5Dt.js +99 -0
- package/renderer/dist/assets/flowDiagram-OXPTDLAJ-DAWsMLmo.js +213 -0
- package/renderer/dist/assets/ganttDiagram-R7TSEDQI-4ml_bhwR.js +292 -0
- package/renderer/dist/assets/gitGraphDiagram-XJZIOB7I-B_WcqSId.js +106 -0
- package/renderer/dist/assets/graph-DGbsTEsI.js +1 -0
- package/renderer/dist/assets/infoDiagram-5W2HQ5XZ-CI-jYqzc.js +2 -0
- package/renderer/dist/assets/init-Gi6I4Gst.js +1 -0
- package/renderer/dist/assets/ishikawaDiagram-K3B6WC7H-B56VWRlV.js +70 -0
- package/renderer/dist/assets/journeyDiagram-COXZFDF6-D1-p09Lr.js +139 -0
- package/renderer/dist/assets/kanban-definition-P3RFRI5V-Cv3w7JoN.js +89 -0
- package/renderer/dist/assets/katex-C5jXJg4s.js +257 -0
- package/renderer/dist/assets/linear-C35V2Spi.js +1 -0
- package/renderer/dist/assets/main-BcoBiGwv.css +32 -0
- package/renderer/dist/assets/main-CLnIOulD.js +1191 -0
- package/renderer/dist/assets/mermaid.core-BzGT5Vo9.js +314 -0
- package/renderer/dist/assets/mindmap-definition-LZFPQGKD-BZUfuymC.js +96 -0
- package/renderer/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/renderer/dist/assets/pegDiagram-BGZESJAR-DFh9XNGe.js +1 -0
- package/renderer/dist/assets/pieDiagram-CAPJLFHJ-C65tPVsy.js +39 -0
- package/renderer/dist/assets/product-name-CSNW6d7s.js +40 -0
- package/renderer/dist/assets/quadrantDiagram-GDMTTRAM-Cushj_nk.js +7 -0
- package/renderer/dist/assets/railroadDiagram-SM67HX2B-DZGebGx5.js +1 -0
- package/renderer/dist/assets/requirementDiagram-X7JNWC4A-DpAB0MNo.js +84 -0
- package/renderer/dist/assets/sankeyDiagram-UM26HJRW-D-29rHra.js +40 -0
- package/renderer/dist/assets/sequenceDiagram-ZO4K6R2Y-FfOQAZol.js +169 -0
- package/renderer/dist/assets/sizeCapture-INFHLROL-C_n6fDBY.js +1 -0
- package/renderer/dist/assets/stateDiagram-v2-TBUQTH76-BZjenzJG.js +291 -0
- package/renderer/dist/assets/swimlanes-N4OXWK64-DxaMU9vX.js +1 -0
- package/renderer/dist/assets/swimlanesDiagram-K3J5GTZL-Cb2kM6rr.js +8 -0
- package/renderer/dist/assets/timeline-definition-YOQAKGHF-DTg5HJKz.js +120 -0
- package/renderer/dist/assets/usecaseDiagram-VIAY4XPW-Bc3MZDsl.js +370 -0
- package/renderer/dist/assets/vennDiagram-BLWOH2XV-GIVGk9Id.js +34 -0
- package/renderer/dist/assets/wardleyDiagram-YMQ3BMBF-NmJnbQOg.js +78 -0
- package/renderer/dist/assets/xychartDiagram-TAQBALBS-p-kfGfnW.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 +30 -8
- package/scripts/approval-server.sh +24 -5
- package/scripts/before-publish.mjs +12 -10
- 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 +62 -34
- package/shared/agents.d.mts +1 -0
- 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 +45 -1
- package/shared/claude-code-shape.mjs +6 -0
- package/shared/claude-commands.generated.mjs +6 -4
- package/shared/claude-models.generated.mjs +6 -4
- 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 +36 -0
- package/shared/corner-tag.mjs +396 -0
- package/shared/dead-run-trace.mjs +19 -2
- package/shared/engines.mjs +76 -300
- 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 +111 -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 +31 -0
- package/shared/notify-rules.mjs +55 -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/provider-commands.mjs +10 -2
- package/shared/rank.d.mts +4 -1
- package/shared/rank.mjs +82 -19
- package/shared/relay-address.mjs +6 -0
- package/shared/relay-crypto.mjs +165 -0
- 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 -13
- package/shared/runs-on.mjs +31 -52
- package/shared/shipped-note.d.mts +4 -0
- package/shared/shipped-note.mjs +6 -0
- package/shared/shortcuts.mjs +24 -10
- package/shared/spawn-trouble.d.mts +4 -0
- package/shared/spawn-trouble.mjs +484 -30
- 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/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/thread-cards.mjs +4 -1
- package/shared/usage.d.mts +12 -0
- package/shared/usage.mjs +56 -3
- package/shared/websites.d.mts +15 -0
- package/shared/websites.mjs +61 -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 +150 -29
- package/shared/work-lines.mjs +93 -245
- package/shared/xterm-invalid-range.d.mts +2 -0
- package/shared/xterm-invalid-range.mjs +58 -0
- package/renderer/dist/assets/index-ChKsQkRw.js +0 -1169
- package/renderer/dist/assets/index-ClBXnq3B.css +0 -32
package/shared/spawn-trouble.mjs
CHANGED
|
@@ -61,12 +61,53 @@
|
|
|
61
61
|
// to her subscription and never to a key (CLAUDE.md), so passing that advice on
|
|
62
62
|
// would be this app telling her to do the one thing it refuses to do.
|
|
63
63
|
import { NAME, Name } from '../shared/product-name.mjs';
|
|
64
|
+
import { harnessDefinition } from './harness-definitions.mjs';
|
|
65
|
+
import { instantIn, resetDay, wallIn } from './usage.mjs';
|
|
66
|
+
|
|
67
|
+
// A CLAUDE CODE TOO OLD FOR THE LINE AGENTBOX STARTS IT WITH. Its argument
|
|
68
|
+
// parser refuses the line before a single request, in one of two shapes,
|
|
69
|
+
// measured on npm releases 2.1.70 back to 1.0.90 (2026-10-15):
|
|
70
|
+
//
|
|
71
|
+
// error: option '--permission-mode <mode>' argument 'auto' is invalid. ...
|
|
72
|
+
// error: unknown option '--effort'
|
|
73
|
+
//
|
|
74
|
+
// ONLY FLAGS THE APP ITSELF SENDS, and only the mode value it sends. Somebody's
|
|
75
|
+
// own session arguments can carry a typo, and an update does not fix a typo,
|
|
76
|
+
// so an unknown flag of theirs stays 'unknown'. Anchored to the start of a
|
|
77
|
+
// line because that is where the CLI prints it, and an agent quoting the same
|
|
78
|
+
// words from some other program's output is not this.
|
|
79
|
+
const CLAUDE = harnessDefinition('claude');
|
|
80
|
+
const OUR_FLAGS = [
|
|
81
|
+
...JSON.stringify(CLAUDE.run).match(/--[A-Za-z-]+/g),
|
|
82
|
+
'--allowedTools', '--disallowedTools', '--permission-mode', '--mcp-config', '--permission-prompt-tool', '--settings', '--model',
|
|
83
|
+
];
|
|
84
|
+
const TOO_OLD = [
|
|
85
|
+
new RegExp(`^error: unknown option '(${OUR_FLAGS.join('|')})'`, 'm'),
|
|
86
|
+
/^error: option '--permission-mode <mode>' argument 'auto' is invalid/m,
|
|
87
|
+
];
|
|
64
88
|
const ORG_BLOCKED = [
|
|
65
89
|
/organi[sz]ation has disabled/i,
|
|
66
90
|
/disabled for (your|this) organi[sz]ation/i,
|
|
67
91
|
/ask your admin/i,
|
|
68
92
|
];
|
|
69
93
|
|
|
94
|
+
// A CODEX SIGNED IN WITH AN API KEY. Our own line, said before the worker
|
|
95
|
+
// starts (main/codex-account.mjs `codexKeyRefusal`), because Codex reads the
|
|
96
|
+
// key out of auth.json past the worker scrub and bills every call to it.
|
|
97
|
+
// Before SIGNED_OUT, whose /invalid api key/ is a different event.
|
|
98
|
+
const API_KEY = [/signed in with an api key/i];
|
|
99
|
+
|
|
100
|
+
// A CODEX config.toml CODEX WILL NOT READ. Codex's own words, off real runs of
|
|
101
|
+
// codex-cli 0.160.0 on 2026-10-15: the app-server refusing `config/read` for a
|
|
102
|
+
// quote left open or an old `profile = "..."` line, and the terminal's
|
|
103
|
+
// "Error loading config.toml". Not benched like a login: fixing the file
|
|
104
|
+
// changes no sign-in file, so a benched account would never come back.
|
|
105
|
+
const CODEX_CONFIG = [
|
|
106
|
+
/refused config\/read: failed to (read configuration layers|resolve feature override)/i,
|
|
107
|
+
/error loading config\.toml/i,
|
|
108
|
+
/config\.toml:\d+:\d+: /i,
|
|
109
|
+
];
|
|
110
|
+
|
|
70
111
|
const SIGNED_OUT = [
|
|
71
112
|
/oauth/i,
|
|
72
113
|
/session expired/i,
|
|
@@ -133,7 +174,11 @@ const INTERRUPTED = [
|
|
|
133
174
|
/connection (closed|lost|dropped)/i,
|
|
134
175
|
/response stopped arriving/i,
|
|
135
176
|
/may be incomplete/i,
|
|
136
|
-
/econnreset|enotfound|etimedout|econnrefused/i,
|
|
177
|
+
/econnreset|enotfound|etimedout|econnrefused|enetunreach|ehostunreach|eai_again/i,
|
|
178
|
+
// Codex also prefixes non-network failures with "stream disconnected".
|
|
179
|
+
// Match the request failure itself so incomplete responses keep their limit.
|
|
180
|
+
/error sending request for url/i,
|
|
181
|
+
/failed to send websocket request/i,
|
|
137
182
|
/unable to connect/i,
|
|
138
183
|
/can.{0,3}t reach the api/i,
|
|
139
184
|
/timed out/i,
|
|
@@ -141,6 +186,34 @@ const INTERRUPTED = [
|
|
|
141
186
|
/overloaded/i,
|
|
142
187
|
];
|
|
143
188
|
|
|
189
|
+
// A NETWORK THAT WILL NOT LET A SECURE CONNECTION THROUGH (2026-10-15).
|
|
190
|
+
//
|
|
191
|
+
// A company network that inspects TLS swaps in its own certificate, and a
|
|
192
|
+
// proxy can ask for a login. Neither clears by waiting, which is what made
|
|
193
|
+
// 'interrupted' the wrong home for them: the real Claude Code 2.1.301 behind a
|
|
194
|
+
// self-signed proxy died in 371 ms with "API Error: Unable to connect to API:
|
|
195
|
+
// Self-signed certificate detected (DEPTH_ZERO_SELF_SIGNED_CERT) ...", "unable
|
|
196
|
+
// to connect" filed it as a dropped connection, and the screen said there was
|
|
197
|
+
// nothing to do. Node's own spellings, Codex's (rustls and OpenSSL) and a
|
|
198
|
+
// proxy's 407 are here too. A REFUSED connection is not: "Connection refused
|
|
199
|
+
// — a firewall or proxy may be blocking it (ECONNREFUSED)" is also what an
|
|
200
|
+
// offline laptop prints.
|
|
201
|
+
//
|
|
202
|
+
// The fix is a setting the person's terminal already has, and since
|
|
203
|
+
// main/shell-path.mjs the app reads it from their shell profile at launch, so
|
|
204
|
+
// the sentence says to put it there and reopen the app.
|
|
205
|
+
const NETWORK = [
|
|
206
|
+
/self[- ]signed certificate/i,
|
|
207
|
+
/unable to get local issuer certificate/i,
|
|
208
|
+
/unable to verify the first certificate/i,
|
|
209
|
+
/\b(DEPTH_ZERO_SELF_SIGNED_CERT|SELF_SIGNED_CERT_IN_CHAIN|UNABLE_TO_GET_ISSUER_CERT_LOCALLY|UNABLE_TO_VERIFY_LEAF_SIGNATURE)\b/,
|
|
210
|
+
/tls_untrusted_ca/i,
|
|
211
|
+
/invalid peer certificate/i,
|
|
212
|
+
/certificate verify failed/i,
|
|
213
|
+
/proxy authentication required/i,
|
|
214
|
+
];
|
|
215
|
+
const NETWORK_FIX = 'export HTTPS_PROXY and NODE_EXTRA_CA_CERTS in your shell profile';
|
|
216
|
+
|
|
144
217
|
// A workspace the CLI refuses to run in. Distinct from the two above because
|
|
145
218
|
// the fix is a folder, not an account.
|
|
146
219
|
const WORKSPACE = [
|
|
@@ -164,17 +237,50 @@ const WORKSPACE = [
|
|
|
164
237
|
/skip-git-repo-check/i,
|
|
165
238
|
];
|
|
166
239
|
|
|
240
|
+
// A MODEL THE LOGIN'S PLAN DOES NOT INCLUDE. Not the account being out and not
|
|
241
|
+
// the model being unknown: the same login runs every other model, and another
|
|
242
|
+
// login on a bigger plan runs this one. Codex's own words, off two dead runs on
|
|
243
|
+
// 2026-10-09 and 2026-10-11 (codex-cli 0.160.0, a free-plan login handed
|
|
244
|
+
// gpt-6.1-sol): "The 'gpt-6.1-sol' model is not supported when using Codex with
|
|
245
|
+
// a ChatGPT account." The other two are ours, thrown before the thread starts
|
|
246
|
+
// by `codexModelRefusal`: this login's list lacks it, or no login's list here
|
|
247
|
+
// has it, which is what a paid plan that has ended looks like.
|
|
248
|
+
const MODEL_NOT_ON_PLAN = [
|
|
249
|
+
/model is not supported when using codex with a chatgpt account/i,
|
|
250
|
+
/not a model the plan of the codex account/i,
|
|
251
|
+
/not a model the codex on this computer knows/i,
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
// Ours too, from `codexEffortRefusal`.
|
|
255
|
+
const LEVEL_NOT_OFFERED = [/not a level that model offers/i];
|
|
256
|
+
|
|
257
|
+
// Codex lets one process write to a conversation at a time, and refuses our
|
|
258
|
+
// resume while the Codex app or a terminal has it open. codex-cli 0.163.0:
|
|
259
|
+
// "thread <id> already has an active writer".
|
|
260
|
+
const OPEN_ELSEWHERE = [/already has an active writer/i];
|
|
261
|
+
|
|
167
262
|
// WHICH OF THE THREE THINGS WENT WRONG, from whatever the tool printed.
|
|
168
263
|
// 'unknown' is a first-class answer, not a failure: an honest generic sentence
|
|
169
264
|
// beats a confident wrong one.
|
|
170
265
|
export function troubleCause(raw) {
|
|
171
266
|
const text = String(raw ?? '');
|
|
172
267
|
if (!text.trim()) return 'unknown';
|
|
268
|
+
// The CLI's own argument check, before any account is asked anything.
|
|
269
|
+
if (TOO_OLD.some((re) => re.test(text))) return 'too-old';
|
|
270
|
+
if (CODEX_CONFIG.some((re) => re.test(text))) return 'codex-config';
|
|
271
|
+
if (API_KEY.some((re) => re.test(text))) return 'api-key';
|
|
173
272
|
// FIRST, because an organization's refusal mentions the subscription and
|
|
174
273
|
// reads as a signed-out login to the list below.
|
|
175
274
|
if (ORG_BLOCKED.some((re) => re.test(text))) return 'org-blocked';
|
|
275
|
+
// BEFORE SIGNED-OUT, because Codex's sentence says "ChatGPT account".
|
|
276
|
+
if (MODEL_NOT_ON_PLAN.some((re) => re.test(text))) return 'model-not-on-plan';
|
|
277
|
+
if (LEVEL_NOT_OFFERED.some((re) => re.test(text))) return 'level-not-offered';
|
|
278
|
+
if (OPEN_ELSEWHERE.some((re) => re.test(text))) return 'open-elsewhere';
|
|
176
279
|
if (SIGNED_OUT.some((re) => re.test(text))) return 'signed-out';
|
|
177
280
|
if (AT_LIMIT.some((re) => re.test(text))) return 'at-limit';
|
|
281
|
+
// BEFORE WORKSPACE, whose /untrusted/ would read a certificate as a folder,
|
|
282
|
+
// and before INTERRUPTED, whose "unable to connect" is in Claude Code's line.
|
|
283
|
+
if (NETWORK.some((re) => re.test(text))) return 'network';
|
|
178
284
|
if (WORKSPACE.some((re) => re.test(text))) return 'workspace';
|
|
179
285
|
// LAST ON PURPOSE, so every text that already had a name keeps it. A dropped
|
|
180
286
|
// connection is the weakest of the four claims: an account that is signed out
|
|
@@ -184,22 +290,118 @@ export function troubleCause(raw) {
|
|
|
184
290
|
return 'unknown';
|
|
185
291
|
}
|
|
186
292
|
|
|
187
|
-
//
|
|
293
|
+
// A SUBSCRIPTION THAT HAS ENDED IS REFUSED IN AN ADMIN'S WORDS. Claude Code
|
|
294
|
+
// 2.1.293 told a lapsed personal Max login "Your organization has disabled
|
|
295
|
+
// Claude subscription access for Claude Code" (2026-10-11), the same line a
|
|
296
|
+
// workspace policy prints, so the text alone cannot tell them apart. The
|
|
297
|
+
// login's own file can: `plan` is main/claude-plan.mjs's reading of it, and
|
|
298
|
+
// `ended` is its billing gone to "none". Anything else keeps its cause.
|
|
299
|
+
// A LOGIN THAT RAN OUT AND ONE NOBODY EVER MADE REFUSE IN THE SAME WORDS, and
|
|
300
|
+
// the second is what somebody who skipped setup meets: "Sign that account back
|
|
301
|
+
// in" about an account they never had. `everSignedIn` is the caller's reading
|
|
302
|
+
// of the login's own files; false only where that reading is proof.
|
|
303
|
+
export function refineCause(cause, plan, { everSignedIn = true } = {}) {
|
|
304
|
+
if (cause === 'org-blocked' && plan?.ended) return 'subscription-ended';
|
|
305
|
+
if (cause === 'signed-out' && !everSignedIn) return 'no-plan';
|
|
306
|
+
return cause;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// THE CLOCK A LIMIT LINE NAMES, AND THE ZONE IT NAMED IT IN.
|
|
310
|
+
//
|
|
311
|
+
// The zone used to be dropped on the floor, on the assumption that the CLI
|
|
312
|
+
// prints the clock the Mac is already set to. A run on the cloud prints the
|
|
313
|
+
// cloud machine's: "resets 12:10am (UTC)" reached a Mac on Pacific time
|
|
314
|
+
// (2026-10-16), and both the hour on the row and the moment the fleet waited
|
|
315
|
+
// for were seven hours out. `offset` is minutes east of UTC, and null when
|
|
316
|
+
// the line printed no zone at all — which is still most lines, and still
|
|
317
|
+
// means local. This reads any offset, and reads it for the sentence as well
|
|
318
|
+
// as for the bench, which the first fix for that incident did not.
|
|
319
|
+
//
|
|
320
|
+
// Both wordings are real: the CLI's own line is "resets 6pm" and the API's is
|
|
321
|
+
// "Your weekly limit resets at 4pm", so the "at" is optional.
|
|
322
|
+
function resetClock(raw) {
|
|
323
|
+
const m = /resets\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*(?:\(\s*(?:UTC|GMT)\s*(?:([+-])(\d{1,2})(?::?(\d{2}))?)?\s*\))?/i
|
|
324
|
+
.exec(String(raw ?? ''));
|
|
325
|
+
if (!m) return null;
|
|
326
|
+
const zoned = /\(\s*(?:UTC|GMT)/i.test(m[0]);
|
|
327
|
+
return {
|
|
328
|
+
h: (Number(m[1]) % 12) + (m[3].toLowerCase() === 'pm' ? 12 : 0),
|
|
329
|
+
min: Number(m[2] ?? 0),
|
|
330
|
+
offset: zoned ? (m[4] === '-' ? -1 : 1) * (Number(m[5] ?? 0) * 60 + Number(m[6] ?? 0)) : null,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// The CLI's own wording for a clock time, so a converted hour reads exactly
|
|
335
|
+
// the way a quoted one always has.
|
|
336
|
+
function clockWords(h, min) {
|
|
337
|
+
return `${h % 12 || 12}${min ? `:${String(min).padStart(2, '0')}` : ''}${h < 12 ? 'am' : 'pm'}`;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// "3:15pm" today, "Wed 2:30pm" this week, "Wed Oct 28 1:05pm" further out,
|
|
341
|
+
// the same day words the account meters use.
|
|
342
|
+
function momentWords(ms, from) {
|
|
343
|
+
const d = new Date(ms);
|
|
344
|
+
const day = resetDay(ms, from);
|
|
345
|
+
return `${day ? `${day} ` : ''}${clockWords(d.getHours(), d.getMinutes())}`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// WHEN THE LIMIT LETS GO, IN THE CLOCK SHE IS LOOKING AT.
|
|
188
349
|
//
|
|
189
350
|
// carries the one thing she actually wants to know, and we were throwing it
|
|
190
|
-
// away and then telling her the vaguer "when the limit resets".
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
351
|
+
// away and then telling her the vaguer "when the limit resets". A line that
|
|
352
|
+
// names its own zone is converted to this Mac's clock rather than quoted, so
|
|
353
|
+
// the hour on the row is an hour she can act on; a line that names none is
|
|
354
|
+
// already local and is quoted as it stands. Null when there is no time to
|
|
355
|
+
// quote, and then the sentence falls back to the wording that has always been
|
|
356
|
+
// there.
|
|
357
|
+
export function limitResetsAt(raw, from = Date.now()) {
|
|
358
|
+
const clock = resetClock(raw);
|
|
359
|
+
if (!clock) {
|
|
360
|
+
// A reset on another day ("resets Oct 22 at 4pm", Codex's "try again at
|
|
361
|
+
// Oct 28th, 2026 1:05 PM") has no bare hour to quote.
|
|
362
|
+
const moment = limitResetMoment(raw, from);
|
|
363
|
+
return moment == null ? null : momentWords(moment, from);
|
|
364
|
+
}
|
|
365
|
+
if (clock.offset == null) return clockWords(clock.h, clock.min);
|
|
366
|
+
const at = new Date(limitResetMoment(raw, from));
|
|
367
|
+
return clockWords(at.getHours(), at.getMinutes());
|
|
199
368
|
}
|
|
200
369
|
|
|
201
370
|
const MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
|
202
371
|
|
|
372
|
+
const CLAUDE_DAY = /resets\s+([a-z]{3})[a-z]*\.?\s+(\d{1,2})\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*(?:\(\s*([^)]+?)\s*\))?/i;
|
|
373
|
+
const UTC_ZONE = /^(?:UTC|GMT)\s*(?:([+-])(\d{1,2})(?::?(\d{2}))?)?$/i;
|
|
374
|
+
|
|
375
|
+
// "UTC", "GMT+2" or a place such as "America/Los_Angeles". A zone this
|
|
376
|
+
// computer cannot name, or none, is its own clock.
|
|
377
|
+
function zoneOf(word) {
|
|
378
|
+
const utc = UTC_ZONE.exec(word ?? '');
|
|
379
|
+
if (utc) return { offset: (utc[1] === '-' ? -1 : 1) * (Number(utc[2] ?? 0) * 60 + Number(utc[3] ?? 0)), name: null };
|
|
380
|
+
return { offset: null, name: word && wallIn(Date.now(), word) ? word : null };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// The moment a dated Claude Code reset names, or null. The line has no year,
|
|
384
|
+
// so it is the year in that zone at `from`, or the next one when that puts the
|
|
385
|
+
// reset more than a day behind (December reading a January date).
|
|
386
|
+
function claudeResetDay(raw, from) {
|
|
387
|
+
const m = CLAUDE_DAY.exec(String(raw ?? ''));
|
|
388
|
+
if (!m) return null;
|
|
389
|
+
const month = MONTHS.indexOf(m[1].toLowerCase());
|
|
390
|
+
if (month < 0) return null;
|
|
391
|
+
const day = Number(m[2]);
|
|
392
|
+
const h = (Number(m[3]) % 12) + (m[5].toLowerCase() === 'pm' ? 12 : 0);
|
|
393
|
+
const min = Number(m[4] ?? 0);
|
|
394
|
+
const zone = zoneOf(m[6]);
|
|
395
|
+
const at = (year) => zone.offset != null
|
|
396
|
+
? Date.UTC(year, month, day, h, min) - zone.offset * 60_000
|
|
397
|
+
: instantIn(year, month, day, h, min, zone.name);
|
|
398
|
+
const year = zone.offset != null
|
|
399
|
+
? new Date(from + zone.offset * 60_000).getUTCFullYear()
|
|
400
|
+
: (wallIn(from, zone.name)?.year ?? new Date(from).getFullYear());
|
|
401
|
+
const moment = at(year);
|
|
402
|
+
return moment < from - 86_400_000 ? at(year + 1) : moment;
|
|
403
|
+
}
|
|
404
|
+
|
|
203
405
|
// THE SAME HOUR AS A MOMENT, so the fleet can wait exactly until it
|
|
204
406
|
// (a limit that reset at, say, 6:50pm could hold every agent until 7:03pm,
|
|
205
407
|
// because the app waited a fixed thirty minutes instead of reading this line).
|
|
@@ -217,15 +419,26 @@ export function limitResetMoment(raw, from = Date.now()) {
|
|
|
217
419
|
return new Date(Number(dated[3]), month, Number(dated[2]), h, Number(dated[5]), 0, 0).getTime();
|
|
218
420
|
}
|
|
219
421
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
if (
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
422
|
+
// Claude Code names the day when the reset is not today: "resets Oct 22 at
|
|
423
|
+
// 4pm (America/Los_Angeles)".
|
|
424
|
+
const datedReset = claudeResetDay(raw, from);
|
|
425
|
+
if (datedReset != null) return datedReset;
|
|
426
|
+
const clock = resetClock(raw);
|
|
427
|
+
if (!clock) return null;
|
|
428
|
+
if (clock.offset == null) {
|
|
429
|
+
const d = new Date(from);
|
|
430
|
+
d.setHours(clock.h, clock.min, 0, 0);
|
|
431
|
+
if (d.getTime() < from - 60_000) d.setDate(d.getDate() + 1);
|
|
432
|
+
return d.getTime();
|
|
433
|
+
}
|
|
434
|
+
// The named zone's own calendar day is not this Mac's, so the candidate is
|
|
435
|
+
// built in UTC and then walked to the one occurrence that sits in the next
|
|
436
|
+
// twenty-four hours. The grace is for the two clocks disagreeing by seconds.
|
|
437
|
+
const asked = new Date(from);
|
|
438
|
+
let t = Date.UTC(asked.getUTCFullYear(), asked.getUTCMonth(), asked.getUTCDate(), clock.h, clock.min) - clock.offset * 60_000;
|
|
439
|
+
while (t < from - 60_000) t += 86_400_000;
|
|
440
|
+
while (t - from >= 86_400_000) t -= 86_400_000;
|
|
441
|
+
return t;
|
|
229
442
|
}
|
|
230
443
|
|
|
231
444
|
// AN ACCOUNT THAT CANNOT BE USED AT ALL UNTIL A PERSON DOES SOMETHING.
|
|
@@ -237,18 +450,32 @@ export function needsHerHands(cause) {
|
|
|
237
450
|
// same person: one is her, in a terminal; one is an admin of a workspace she
|
|
238
451
|
// may not run. What they share is the only thing this question asks — trying
|
|
239
452
|
// again in thirty minutes cannot possibly help.
|
|
240
|
-
|
|
453
|
+
// An out-of-date Claude Code is hers too: no retry updates it, and every run
|
|
454
|
+
// until she does spends nothing but a reply's tries.
|
|
455
|
+
// A Codex signed in with a key is hers as well, and `codex login` rewrites
|
|
456
|
+
// auth.json, which is what brings a benched account back.
|
|
457
|
+
return cause === 'signed-out' || cause === 'no-plan' || cause === 'org-blocked' || cause === 'subscription-ended' || cause === 'too-old' || cause === 'api-key';
|
|
241
458
|
}
|
|
242
459
|
|
|
460
|
+
// THE ONE COMMAND THAT UPDATES IT, WHEREVER IT CAME FROM. `claude update`
|
|
461
|
+
// updates a native install itself and, on a Homebrew one, prints the brew line
|
|
462
|
+
// to run instead (read in 2.1.80's own code), so it is the right first thing
|
|
463
|
+
// to type whatever she installed it with.
|
|
464
|
+
const UPDATE = `run claude update in a terminal (${Name} needs version ${CLAUDE.minVersion} or newer)`;
|
|
465
|
+
|
|
243
466
|
// WHAT SHE READS WHEN NOT ONE AGENT CAN START ANYWHERE.
|
|
244
467
|
// Not "an error occurred". The subject of the sentence is the thing she cares
|
|
245
468
|
// about, which is her agents, and the reason follows it in plain words.
|
|
246
469
|
export function troubleSentence(cause) {
|
|
247
470
|
if (cause === 'signed-out') return 'No agents can start. Your Claude login has run out.';
|
|
471
|
+
if (cause === 'no-plan') return 'No agents can start. No AI plan is set up on this computer.';
|
|
248
472
|
if (cause === 'org-blocked') return 'No agents can start. Your organization has turned Claude Code off.';
|
|
473
|
+
if (cause === 'subscription-ended') return 'No agents can start. Your Claude subscription has ended.';
|
|
249
474
|
if (cause === 'at-limit') return 'No agents can start. Your Claude plan is at its limit.';
|
|
250
475
|
if (cause === 'workspace') return 'No agents can start. Claude will not run in this folder.';
|
|
251
476
|
if (cause === 'interrupted') return 'No agents can start. The connection to Claude keeps dropping.';
|
|
477
|
+
if (cause === 'network') return 'No agents can start. This network will not let them connect securely.';
|
|
478
|
+
if (cause === 'too-old') return 'No agents can start. Claude Code on this computer is too old.';
|
|
252
479
|
return 'No agents can start on this computer right now.';
|
|
253
480
|
}
|
|
254
481
|
|
|
@@ -259,10 +486,14 @@ export function troubleRemedy(cause) {
|
|
|
259
486
|
// ). "Sign in to Claude again" is a thing to go and work out; /login is a
|
|
260
487
|
// thing to type.
|
|
261
488
|
if (cause === 'signed-out') return 'Open Claude in a terminal, type /login, and they pick straight back up.';
|
|
489
|
+
if (cause === 'no-plan') return `Set one up ${SET_UP_A_PLAN} and they pick straight up.`;
|
|
262
490
|
if (cause === 'org-blocked') return 'Ask an admin of that Claude workspace to turn Claude Code back on.';
|
|
491
|
+
if (cause === 'subscription-ended') return 'Renew it at claude.ai and they pick straight up.';
|
|
263
492
|
if (cause === 'at-limit') return 'They start again on their own when it resets.';
|
|
264
493
|
if (cause === 'workspace') return 'Open the folder in Claude once and say yes to trusting it.';
|
|
265
494
|
if (cause === 'interrupted') return `Nothing to do. ${Name} tries them again on its own.`;
|
|
495
|
+
if (cause === 'network') return `If your company uses a proxy or its own certificate, ${NETWORK_FIX} and reopen ${Name}.`;
|
|
496
|
+
if (cause === 'too-old') return `To update it, ${UPDATE}. They start again on their own.`;
|
|
266
497
|
return `${Name} keeps trying on its own.`;
|
|
267
498
|
}
|
|
268
499
|
|
|
@@ -289,10 +520,17 @@ export function accountSentence(cause, dir) {
|
|
|
289
520
|
if (cause === 'org-blocked') {
|
|
290
521
|
return 'Signed in, but its organization has turned Claude Code off. Only an admin of that workspace can turn it back on.';
|
|
291
522
|
}
|
|
292
|
-
if (cause === '
|
|
523
|
+
if (cause === 'subscription-ended') return 'Signed in, but its Claude subscription has ended. Renew it at claude.ai to use this account again.';
|
|
524
|
+
if (cause === 'at-limit') return 'At its limit. Agents start again on their own when it resets.';
|
|
293
525
|
if (cause === 'workspace') return 'Claude would not run in the folder this was asked to work in.';
|
|
294
526
|
if (cause === 'interrupted') return `Its runs keep getting cut off. ${Name} is trying them again.`;
|
|
295
|
-
|
|
527
|
+
// NOT THE ACCOUNT, and said so, because this is the page where she would
|
|
528
|
+
// otherwise go and sign it in again.
|
|
529
|
+
if (cause === 'network') return `The account is fine; this network will not let it connect securely. Set your company's proxy or certificate in your shell profile and reopen ${Name}.`;
|
|
530
|
+
// Not this account either: every account here runs the same Claude Code.
|
|
531
|
+
if (cause === 'too-old') return `Claude Code on this computer is too old. To update it, ${UPDATE}.`;
|
|
532
|
+
// A cause we cannot name points at the account, not at the app (2026-10-11).
|
|
533
|
+
return 'Claude Code would not start on this account. Check that its plan is active at claude.ai.';
|
|
296
534
|
}
|
|
297
535
|
|
|
298
536
|
/* ------------------ the same fact about a whole ENGINE -------------------- */
|
|
@@ -329,6 +567,9 @@ export function engineTroubleNote(cause, engineWord = 'Codex') {
|
|
|
329
567
|
if (cause === 'at-limit') return `${engineWord} is at its usage limit, so its tasks are waiting. They start again on their own when it resets.`;
|
|
330
568
|
if (cause === 'workspace') return `${engineWord} would not run in the folder a task asked to work in. Open that folder in ${engineWord} once and allow it.`;
|
|
331
569
|
if (cause === 'interrupted') return `${engineWord} runs keep getting cut off. ${Name} is trying them again on its own.`;
|
|
570
|
+
if (cause === 'network') return `${engineWord} cannot connect securely through this network, so its tasks are not moving. Set your company's proxy or certificate in your shell profile and reopen ${Name}.`;
|
|
571
|
+
if (cause === 'api-key') return `${engineWord} is signed in with an API key, which ${Name} never bills, so nothing runs on it. Open a terminal, run codex login, and sign in with ChatGPT.`;
|
|
572
|
+
if (cause === 'codex-config') return `${engineWord} could not read its config.toml, so nothing runs on it. Run codex in a terminal to see the line it points at, and fix it.`;
|
|
332
573
|
return `${engineWord} sessions are dying on arrival, so its tasks are not moving. ${Name} keeps trying.`;
|
|
333
574
|
}
|
|
334
575
|
|
|
@@ -437,11 +678,42 @@ const DEAD_RUN_OPENERS = {
|
|
|
437
678
|
workspace: "would not start in this project's folder, so nothing has been done here.",
|
|
438
679
|
'signed-out': 'could not sign in, so nothing has been done on this task.',
|
|
439
680
|
'org-blocked': 'is switched off for that account by its organization, so nothing has been done.',
|
|
681
|
+
'subscription-ended': "cannot run because that account's Claude subscription has ended, so nothing has been done.",
|
|
440
682
|
'at-limit': 'is at its usage limit, so nothing has been done on this task.',
|
|
441
683
|
interrupted: 'was cut off in the middle, so nothing has been done on this task.',
|
|
684
|
+
'model-not-on-plan': "could not use this task's model on that account's plan, so nothing has been done.",
|
|
685
|
+
'level-not-offered': "could not run this task's model at the thinking level it asks for, so nothing has been done.",
|
|
686
|
+
network: 'could not connect securely through this network, so nothing has been done on this task.',
|
|
687
|
+
'too-old': `on this computer is older than ${Name} needs, so nothing has been done on this task.`,
|
|
688
|
+
'api-key': `is signed in with an API key, which ${Name} never bills, so nothing has been done.`,
|
|
689
|
+
'codex-config': 'could not read its config.toml, so nothing has been done on this task.',
|
|
690
|
+
'open-elsewhere': 'has this conversation open in another window, so nothing has been done here.',
|
|
691
|
+
// THE ONE OPENER WITH NO ENGINE IN FRONT OF IT: somebody who has set nothing
|
|
692
|
+
// up has never heard of Codex, and the plan screens never say the word.
|
|
693
|
+
'no-plan': 'No AI plan is set up on this computer, so nothing has been done on this task.',
|
|
442
694
|
unknown: 'stopped before it did anything, so this task has not been started.',
|
|
443
695
|
};
|
|
444
696
|
|
|
697
|
+
// WHERE A PLAN GETS SET UP: the bar across the top of the inbox (`PlanBar`),
|
|
698
|
+
// which is the only way back into setup. Said once, for the four places that
|
|
699
|
+
// point at it.
|
|
700
|
+
const SET_UP_A_PLAN = 'at the top of your inbox';
|
|
701
|
+
|
|
702
|
+
// The same, for a run that used a tool before it died, so "nothing has been
|
|
703
|
+
// done" would be false. Kept out of DEAD_RUN_OPENERS so `deadRunCause` does
|
|
704
|
+
// not count the row among those nothing has started on. A cause missing here
|
|
705
|
+
// can only stop a run before its first tool, so on a run that worked it is a
|
|
706
|
+
// misreading and gets the plain line.
|
|
707
|
+
const WORKED_OPENERS = {
|
|
708
|
+
'signed-out': 'was signed out partway through, so this task is not finished.',
|
|
709
|
+
'org-blocked': 'was switched off for that account partway through, so this task is not finished.',
|
|
710
|
+
'subscription-ended': "stopped partway through because that account's subscription ended.",
|
|
711
|
+
'at-limit': 'hit its usage limit partway through, so this task is not finished.',
|
|
712
|
+
interrupted: 'was cut off partway through, so this task is not finished.',
|
|
713
|
+
network: 'lost its secure connection partway through, so this task is not finished.',
|
|
714
|
+
unknown: 'stopped partway through and did not finish this task.',
|
|
715
|
+
};
|
|
716
|
+
|
|
445
717
|
/**
|
|
446
718
|
* THE LINE IN A THREAD WHERE A RUN STOPPED, in our words.
|
|
447
719
|
*
|
|
@@ -453,10 +725,19 @@ const DEAD_RUN_OPENERS = {
|
|
|
453
725
|
*/
|
|
454
726
|
const RUN_STOPPED = {
|
|
455
727
|
'signed-out': 'This run stopped because the account was signed out.',
|
|
456
|
-
|
|
728
|
+
// Same refusal text for an admin's block and an ended subscription, and this
|
|
729
|
+
// line has only the text, so it says what is true of both.
|
|
730
|
+
'org-blocked': 'This run stopped because Claude Code will not run on that account.',
|
|
457
731
|
'at-limit': 'This run stopped because the account hit its usage limit.',
|
|
458
732
|
workspace: "This run stopped because it would not start in this project's folder.",
|
|
459
733
|
interrupted: 'This run was cut off in the middle.',
|
|
734
|
+
'model-not-on-plan': "This run stopped because that account's plan does not include the model.",
|
|
735
|
+
'level-not-offered': 'This run stopped because the model does not offer that thinking level.',
|
|
736
|
+
network: 'This run stopped because this network would not let it connect securely.',
|
|
737
|
+
'too-old': `This run stopped because Claude Code on this computer is older than ${Name} needs.`,
|
|
738
|
+
'api-key': 'This run stopped because Codex is signed in with an API key.',
|
|
739
|
+
'codex-config': 'This run stopped because Codex could not read its config.toml.',
|
|
740
|
+
'open-elsewhere': 'This run stopped because the conversation is open in another Codex window.',
|
|
460
741
|
unknown: 'This run stopped before it finished.',
|
|
461
742
|
};
|
|
462
743
|
|
|
@@ -464,27 +745,135 @@ export function runStoppedLine(raw) {
|
|
|
464
745
|
return RUN_STOPPED[troubleCause(raw)] ?? RUN_STOPPED.unknown;
|
|
465
746
|
}
|
|
466
747
|
|
|
467
|
-
|
|
468
|
-
|
|
748
|
+
// A cause we cannot name still has a likeliest culprit, and it is the agent's
|
|
749
|
+
// plan, not the app (2026-10-19). Engines with no plan to point at get none.
|
|
750
|
+
const UNNAMED_CHECK = {
|
|
751
|
+
Codex: 'Check that your ChatGPT plan is active at chatgpt.com, or run codex in a terminal to see what it says. ',
|
|
752
|
+
'Claude Code': 'Check that your Claude plan is active at claude.ai, or run claude in a terminal to see what it says. ',
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
function dayWords(ms) {
|
|
756
|
+
return new Date(ms).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// What to do before replying to a thread the app has stopped trying on, in the
|
|
760
|
+
// same words `deadRunSentence` uses for each cause. Null when the reply is all.
|
|
761
|
+
function fixBeforeReplying({ cause, engineWord, resetsAt, model, planEnded }) {
|
|
762
|
+
if (cause === 'signed-out') return 'Sign that account back in.';
|
|
763
|
+
if (cause === 'no-plan') return `Set up a plan ${SET_UP_A_PLAN}.`;
|
|
764
|
+
if (cause === 'org-blocked') return 'Only an admin of that Claude workspace can turn it back on.';
|
|
765
|
+
if (cause === 'subscription-ended') return 'Renew it at claude.ai.';
|
|
766
|
+
if (cause === 'at-limit') return resetsAt ? `The limit resets at ${resetsAt}.` : 'Wait for the limit to reset.';
|
|
767
|
+
if (cause === 'too-old') return `To update it, ${UPDATE}.`;
|
|
768
|
+
if (cause === 'api-key') return 'Run codex login in a terminal to sign in with your ChatGPT plan.';
|
|
769
|
+
if (cause === 'codex-config') return 'Run codex in a terminal to see the line it points at and fix it.';
|
|
770
|
+
if (cause === 'open-elsewhere') return 'Close it there.';
|
|
771
|
+
if (cause === 'model-not-on-plan') {
|
|
772
|
+
return planEnded
|
|
773
|
+
? `That account's ChatGPT subscription ended on ${dayWords(planEnded)}, and the free plan does not include ${model || 'this model'}. Renew it at chatgpt.com, or pick another model for this task.`
|
|
774
|
+
: 'Pick another model for this task.';
|
|
775
|
+
}
|
|
776
|
+
if (cause === 'level-not-offered') return 'Pick another thinking level for this task.';
|
|
777
|
+
if (cause === 'network') return `If your company uses a proxy or its own certificate, ${NETWORK_FIX} and reopen ${Name}.`;
|
|
778
|
+
if (cause === 'workspace') return `Open this project's folder in ${engineWord} once and say yes to trusting it.`;
|
|
779
|
+
if (cause === 'unknown') return UNNAMED_CHECK[engineWord]?.trim() || null;
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// NO SENTENCE HERE OFFERS ANOTHER ACCOUNT (w-dac993a554). There used to be an
|
|
784
|
+
// `elsewhere` flag: another signed-in login had room, or had the model, so the
|
|
785
|
+
// line promised the move instead of the wait. Nothing moves work between
|
|
786
|
+
// accounts any more, so a limit is named with the hour it resets and a model no
|
|
787
|
+
// plan includes is answered with another model.
|
|
788
|
+
//
|
|
789
|
+
// `planEnded` is when the account's paid ChatGPT plan ended (main/codex-account.mjs
|
|
790
|
+
// `codexPlanEnded`), which is why its list lacks `model`.
|
|
791
|
+
//
|
|
792
|
+
// `gaveUp` is the app having stopped carrying a reply (`settleDeliveryAndSay`).
|
|
793
|
+
// Nothing restarts the thread until somebody replies or resumes it, so the
|
|
794
|
+
// sentence asks for that and promises nothing of its own.
|
|
795
|
+
// `account` is the login this run was spending, already in words
|
|
796
|
+
// (shared/account-names.mjs). It is null on a Mac with one login, where naming
|
|
797
|
+
// it tells nobody anything, and then every sentence here is the one it has
|
|
798
|
+
// always been. There is no `instead`: nothing takes the task to another login
|
|
799
|
+
// (w-dac993a554), so a limit is named and waited out.
|
|
800
|
+
//
|
|
801
|
+
// `partway` is a run that was picking up a thread an earlier run worked on: it
|
|
802
|
+
// may have done nothing itself, but the task has been started. A cause with no
|
|
803
|
+
// partway words stopped this run before it began, and keeps its own opener.
|
|
804
|
+
//
|
|
805
|
+
// `retryAt` is when the rest ladder starts it again (`restingUntil` in
|
|
806
|
+
// main/supervisor.mjs), named only on the causes that wait for nothing else.
|
|
807
|
+
export function deadRunSentence({ engineWord = 'The agent', cause = 'unknown', runs = 1, resetsAt = null, worked: usedATool = false, partway = false, model = null, planEnded = null, gaveUp = false, account = null, retryAt = null, now = Date.now() } = {}) {
|
|
808
|
+
if (usedATool && !WORKED_OPENERS[cause]) cause = 'unknown';
|
|
809
|
+
const worked = usedATool || (partway && !!WORKED_OPENERS[cause]);
|
|
810
|
+
// A LIMIT IS A FACT ABOUT ONE LOGIN, so that is the one cause whose opener
|
|
811
|
+
// takes the name. The others are about the tool or the task.
|
|
812
|
+
const limitOpener = cause === 'at-limit' && account
|
|
813
|
+
? (worked
|
|
814
|
+
? `hit its usage limit on ${account} partway through, so this task is not finished.`
|
|
815
|
+
: `is at its usage limit on ${account}, so nothing has been done on this task.`)
|
|
816
|
+
: null;
|
|
817
|
+
const opener = cause === 'no-plan'
|
|
818
|
+
? DEAD_RUN_OPENERS['no-plan']
|
|
819
|
+
: `${engineWord} ${limitOpener ?? (worked ? WORKED_OPENERS[cause] ?? WORKED_OPENERS.unknown : DEAD_RUN_OPENERS[cause] ?? DEAD_RUN_OPENERS.unknown)}`;
|
|
469
820
|
|
|
470
821
|
const again = runs > 1 ? ` This has happened ${runs} times on this task.` : '';
|
|
471
822
|
|
|
823
|
+
if (gaveUp) {
|
|
824
|
+
const fix = fixBeforeReplying({ cause, engineWord, resetsAt, model, planEnded });
|
|
825
|
+
return `${opener}${again} ${fix ? `${fix} Then reply` : 'Reply'} here to start it again.`;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const when = retryAt > now ? ` at ${momentWords(retryAt, now)}` : '';
|
|
472
829
|
// THE TIME THE LIMIT PRINTED, when there is one. "when the limit resets"
|
|
473
830
|
// leaves her with a question; "at 6pm" is an answer, and it is the CLI's own
|
|
474
831
|
// word for it rather than anything we worked out.
|
|
475
832
|
const next = cause === 'signed-out'
|
|
476
833
|
? ' Sign that account back in and it picks straight up.'
|
|
834
|
+
: cause === 'no-plan'
|
|
835
|
+
? ` Set one up ${SET_UP_A_PLAN} and it picks straight up.`
|
|
477
836
|
: cause === 'org-blocked'
|
|
478
837
|
? ' Only an admin of that Claude workspace can turn it back on.'
|
|
838
|
+
: cause === 'subscription-ended'
|
|
839
|
+
? ' Renew it at claude.ai and it picks straight up.'
|
|
479
840
|
: cause === 'at-limit'
|
|
480
841
|
? (resetsAt ? ` It starts again on its own at ${resetsAt}.` : ' It starts again on its own when the limit resets.')
|
|
842
|
+
: cause === 'too-old'
|
|
843
|
+
? ` To update it, ${UPDATE}, and it starts again on its own.`
|
|
844
|
+
: cause === 'api-key'
|
|
845
|
+
? ' Run codex login in a terminal to sign in with your ChatGPT plan, and it picks straight up.'
|
|
846
|
+
: cause === 'codex-config'
|
|
847
|
+
? ` Run codex in a terminal to see the line it points at and fix it; ${Name} tries again on its own.`
|
|
848
|
+
: cause === 'open-elsewhere'
|
|
849
|
+
? ` Close it there and ${Name} tries again on its own.`
|
|
481
850
|
: cause === 'interrupted'
|
|
482
|
-
? ` ${Name} runs it again on its own.`
|
|
483
|
-
:
|
|
851
|
+
? ` ${Name} runs it again on its own${when}.`
|
|
852
|
+
: cause === 'model-not-on-plan'
|
|
853
|
+
? (planEnded
|
|
854
|
+
? ` That account's ChatGPT subscription ended on ${dayWords(planEnded)}, and the free plan does not include ${model || 'this model'}. Renew it at chatgpt.com, or pick another model for this task.`
|
|
855
|
+
: ' Pick another model for this task.')
|
|
856
|
+
: cause === 'level-not-offered'
|
|
857
|
+
? ' Pick another thinking level for this task and it will run.'
|
|
858
|
+
: cause === 'network'
|
|
859
|
+
? ` It will keep failing until this is fixed: if your company uses a proxy or its own certificate, ${NETWORK_FIX} and reopen ${Name}.`
|
|
860
|
+
: worked
|
|
861
|
+
? ` ${Name} tries it again on its own${when}.`
|
|
862
|
+
: cause === 'workspace'
|
|
863
|
+
? ` ${Name} keeps trying, and it will keep failing the same way until this is fixed.`
|
|
864
|
+
: ` ${UNNAMED_CHECK[engineWord] ?? ''}${Name} tries again on its own${when}.`;
|
|
484
865
|
|
|
485
866
|
return `${opener}${again}${next}`;
|
|
486
867
|
}
|
|
487
868
|
|
|
869
|
+
// Whether `deadRunSentence` tells her the app runs the task again by itself.
|
|
870
|
+
// The fresh-work pass keeps a row in the queue on exactly these, so a row is
|
|
871
|
+
// never promised a retry and then read as an answer waiting on her.
|
|
872
|
+
export function deadRunPromisesARetry({ cause = 'unknown', gaveUp = false } = {}) {
|
|
873
|
+
if (gaveUp) return false;
|
|
874
|
+
return cause !== 'org-blocked' && cause !== 'level-not-offered' && cause !== 'model-not-on-plan';
|
|
875
|
+
}
|
|
876
|
+
|
|
488
877
|
/* ------------- a run that finished and still delivered nothing ------------ */
|
|
489
878
|
// WHAT GOES ON HER ROW WHEN THE RUN WAS FINE AND HER ANSWER STILL WENT NOWHERE.
|
|
490
879
|
//
|
|
@@ -513,7 +902,43 @@ export function deadRunSentence({ engineWord = 'The agent', cause = 'unknown', r
|
|
|
513
902
|
export function undeliveredAnswerSentence({ engineWord = 'The agent', attempts = 1 } = {}) {
|
|
514
903
|
const n = Math.max(1, Math.trunc(attempts ?? 1));
|
|
515
904
|
const runs = n === 1 ? 'once and finished' : `${n} times and each run finished`;
|
|
516
|
-
return
|
|
905
|
+
return `${UNDELIVERED} ${engineWord} ran ${runs} without writing anything back on the row, so ${NAME} has stopped carrying it. Reply again and it goes to a fresh session.`;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const UNDELIVERED = 'Your reply did not reach this task.';
|
|
909
|
+
|
|
910
|
+
// A THREAD THE APP STOPPED STARTING (`_noteHowTheRunStarted` in
|
|
911
|
+
// main/supervisor.mjs). `tries` is how many times it has tried again on its
|
|
912
|
+
// own since, and `next` when it tries again, null once it has stopped trying.
|
|
913
|
+
const STOPPED_STARTING = `${Name} stopped starting this thread`;
|
|
914
|
+
const clock = (t) => new Date(t).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' }).toLowerCase().replace(/\s/g, '');
|
|
915
|
+
|
|
916
|
+
export function stoppedStartingSentence({ starts = 6, minutes = 10, tries = 0, triedAt = null, said = null, next = null } = {}) {
|
|
917
|
+
const what = tries === 0
|
|
918
|
+
? `It was started ${starts} times in ${minutes} minutes, and each run ended before its agent began.`
|
|
919
|
+
: tries === 1
|
|
920
|
+
? `It tried again on its own${triedAt ? ` at ${clock(triedAt)}` : ''}, and that run ended before its agent began too.`
|
|
921
|
+
: `It tried again on its own ${tries === 2 ? 'twice' : `${tries} times`}, and each run ended before its agent began.`;
|
|
922
|
+
const last = said ? ` The last one said "${said.slice(0, 300)}".` : '';
|
|
923
|
+
const then = next ? ` It tries again on its own at ${clock(next)}. Reply here to try again now.` : ' Reply here to try again.';
|
|
924
|
+
return `${STOPPED_STARTING}. ${what}${last}${then}`;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// THE APP'S OWN WORD THAT A RUN GOT NOWHERE: a dead-run sentence, from a run
|
|
928
|
+
// that used a tool or one that did not, the reply that never landed, or a
|
|
929
|
+
// thread it stopped starting. On a thread carrying a reply, each is written
|
|
930
|
+
// only once nothing will carry the reply on its own, so that thread waits on a
|
|
931
|
+
// person (`gaveUpOnTheAnswer`, renderer/src/list-rules.ts).
|
|
932
|
+
const DEAD_RUN_HALVES = {
|
|
933
|
+
'at-limit': ['is at its usage limit', 'so nothing has been done on this task.'],
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
export function saysARunGotNowhere(text) {
|
|
937
|
+
const s = String(text ?? '');
|
|
938
|
+
if (!s.trim()) return false;
|
|
939
|
+
if (deadRunCause(s)) return true;
|
|
940
|
+
if (Object.values(WORKED_OPENERS).some((opener) => s.includes(opener))) return true;
|
|
941
|
+
return s.startsWith(UNDELIVERED) || s.startsWith(STOPPED_STARTING);
|
|
517
942
|
}
|
|
518
943
|
|
|
519
944
|
// WHOSE SENTENCE IS ON THIS ROW, AND WHAT IT SAYS HAPPENED.
|
|
@@ -530,12 +955,20 @@ export function undeliveredAnswerSentence({ engineWord = 'The agent', attempts =
|
|
|
530
955
|
// So a result is asked whose it is. Null means somebody wrote a real answer
|
|
531
956
|
// there and the row is not stranded; a cause means the last word on that row is
|
|
532
957
|
// still the app saying nothing ran.
|
|
958
|
+
//
|
|
959
|
+
// A NAMED LOGIN SITS INSIDE ONE OPENER ("is at its usage limit on
|
|
960
|
+
// alex@acme.com, so nothing has been done"), so that one is read back by its
|
|
961
|
+
// two halves. Without this a row stopped counting as one nothing had run on
|
|
962
|
+
// the moment it said which account was spent.
|
|
533
963
|
export function deadRunCause(text) {
|
|
534
964
|
const s = String(text ?? '');
|
|
535
965
|
if (!s.trim()) return null;
|
|
536
966
|
for (const [cause, opener] of Object.entries(DEAD_RUN_OPENERS)) {
|
|
537
967
|
if (s.includes(opener)) return cause;
|
|
538
968
|
}
|
|
969
|
+
for (const [cause, halves] of Object.entries(DEAD_RUN_HALVES)) {
|
|
970
|
+
if (halves.every((half) => s.includes(half))) return cause;
|
|
971
|
+
}
|
|
539
972
|
return null;
|
|
540
973
|
}
|
|
541
974
|
|
|
@@ -558,10 +991,17 @@ export function deadRunCause(text) {
|
|
|
558
991
|
// where the shape has no room for a sentence, which is most of them.
|
|
559
992
|
export function causeWord(cause) {
|
|
560
993
|
if (cause === 'signed-out') return 'a signed-out login';
|
|
994
|
+
if (cause === 'no-plan') return 'no plan to run on';
|
|
561
995
|
if (cause === 'org-blocked') return 'an organization that blocks Claude Code';
|
|
996
|
+
if (cause === 'subscription-ended') return 'an ended Claude subscription';
|
|
562
997
|
if (cause === 'at-limit') return 'your Claude limit';
|
|
563
998
|
if (cause === 'workspace') return 'a folder Claude will not run in';
|
|
564
999
|
if (cause === 'interrupted') return 'runs getting cut off';
|
|
1000
|
+
if (cause === 'model-not-on-plan') return "a model the account's plan does not include";
|
|
1001
|
+
if (cause === 'level-not-offered') return 'a thinking level its model does not offer';
|
|
1002
|
+
if (cause === 'network') return 'a network that blocks secure connections';
|
|
1003
|
+
if (cause === 'too-old') return 'an out-of-date Claude Code';
|
|
1004
|
+
if (cause === 'open-elsewhere') return 'a conversation open in another Codex window';
|
|
565
1005
|
return 'something we cannot name';
|
|
566
1006
|
}
|
|
567
1007
|
|
|
@@ -571,10 +1011,17 @@ export function causeWord(cause) {
|
|
|
571
1011
|
export function causeWhat(cause, count) {
|
|
572
1012
|
const n = Math.max(0, Math.trunc(count ?? 0));
|
|
573
1013
|
if (cause === 'signed-out') return `${n} stopped by a signed-out Claude login`;
|
|
1014
|
+
if (cause === 'no-plan') return `${n} waiting on a plan to run on`;
|
|
574
1015
|
if (cause === 'org-blocked') return `${n} stopped by an organization that blocks Claude Code`;
|
|
1016
|
+
if (cause === 'subscription-ended') return `${n} stopped by an ended Claude subscription`;
|
|
575
1017
|
if (cause === 'at-limit') return `${n} waiting on your Claude limit`;
|
|
576
1018
|
if (cause === 'workspace') return `${n} in a folder Claude will not run in`;
|
|
577
1019
|
if (cause === 'interrupted') return `${n} cut off mid-run`;
|
|
1020
|
+
if (cause === 'model-not-on-plan') return `${n} asking for a model no signed-in Codex plan includes`;
|
|
1021
|
+
if (cause === 'level-not-offered') return `${n} asking for a thinking level its model does not offer`;
|
|
1022
|
+
if (cause === 'network') return `${n} stopped by a network that blocks secure connections`;
|
|
1023
|
+
if (cause === 'too-old') return `${n} stopped by an out-of-date Claude Code`;
|
|
1024
|
+
if (cause === 'open-elsewhere') return `${n} open in another Codex window`;
|
|
578
1025
|
return `${n} stopped for a reason we cannot name`;
|
|
579
1026
|
}
|
|
580
1027
|
|
|
@@ -582,10 +1029,17 @@ export function causeWhat(cause, count) {
|
|
|
582
1029
|
// comes from the limit line the CLI printed, never from our own clock.
|
|
583
1030
|
export function causeNext(cause, resetsAt = null) {
|
|
584
1031
|
if (cause === 'signed-out') return 'Type /login in a terminal and they pick straight up.';
|
|
1032
|
+
if (cause === 'no-plan') return `Set one up ${SET_UP_A_PLAN}.`;
|
|
585
1033
|
if (cause === 'org-blocked') return 'Ask an admin of that Claude workspace to turn Claude Code back on.';
|
|
1034
|
+
if (cause === 'subscription-ended') return 'Renew it at claude.ai and they pick straight up.';
|
|
586
1035
|
if (cause === 'at-limit') return resetsAt ? `They start again on their own at ${resetsAt}.` : 'They start again on their own when it resets.';
|
|
587
1036
|
if (cause === 'workspace') return 'Open the folder in Claude once and say yes to trusting it.';
|
|
588
1037
|
if (cause === 'interrupted') return `Nothing to do. ${Name} is trying them again.`;
|
|
1038
|
+
if (cause === 'model-not-on-plan') return 'Pick another model on each.';
|
|
1039
|
+
if (cause === 'level-not-offered') return 'Pick another thinking level on each.';
|
|
1040
|
+
if (cause === 'network') return troubleRemedy('network');
|
|
1041
|
+
if (cause === 'too-old') return troubleRemedy('too-old');
|
|
1042
|
+
if (cause === 'open-elsewhere') return 'Close them in the other Codex window.';
|
|
589
1043
|
return `${Name} keeps trying on its own.`;
|
|
590
1044
|
}
|
|
591
1045
|
|
|
@@ -614,7 +1068,7 @@ export function causeBreakdown(rows = [], resetsAt = null) {
|
|
|
614
1068
|
what: causeWhat(cause, count),
|
|
615
1069
|
next: causeNext(cause, cause === 'at-limit' ? resetsAt : null),
|
|
616
1070
|
act: causeAct(cause),
|
|
617
|
-
hers: needsHerHands(cause) || cause === 'workspace',
|
|
1071
|
+
hers: needsHerHands(cause) || cause === 'workspace' || cause === 'model-not-on-plan' || cause === 'level-not-offered' || cause === 'network' || cause === 'open-elsewhere',
|
|
618
1072
|
}))
|
|
619
1073
|
.sort((a, b) => (b.hers - a.hers) || (b.count - a.count) || a.cause.localeCompare(b.cause));
|
|
620
1074
|
}
|