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/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,
|
|
@@ -145,6 +186,34 @@ const INTERRUPTED = [
|
|
|
145
186
|
/overloaded/i,
|
|
146
187
|
];
|
|
147
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
|
+
|
|
148
217
|
// A workspace the CLI refuses to run in. Distinct from the two above because
|
|
149
218
|
// the fix is a folder, not an account.
|
|
150
219
|
const WORKSPACE = [
|
|
@@ -173,26 +242,45 @@ const WORKSPACE = [
|
|
|
173
242
|
// login on a bigger plan runs this one. Codex's own words, off two dead runs on
|
|
174
243
|
// 2026-10-09 and 2026-10-11 (codex-cli 0.160.0, a free-plan login handed
|
|
175
244
|
// gpt-6.1-sol): "The 'gpt-6.1-sol' model is not supported when using Codex with
|
|
176
|
-
// a ChatGPT account." The
|
|
177
|
-
// by `codexModelRefusal
|
|
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.
|
|
178
248
|
const MODEL_NOT_ON_PLAN = [
|
|
179
249
|
/model is not supported when using codex with a chatgpt account/i,
|
|
180
250
|
/not a model the plan of the codex account/i,
|
|
251
|
+
/not a model the codex on this computer knows/i,
|
|
181
252
|
];
|
|
182
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
|
+
|
|
183
262
|
// WHICH OF THE THREE THINGS WENT WRONG, from whatever the tool printed.
|
|
184
263
|
// 'unknown' is a first-class answer, not a failure: an honest generic sentence
|
|
185
264
|
// beats a confident wrong one.
|
|
186
265
|
export function troubleCause(raw) {
|
|
187
266
|
const text = String(raw ?? '');
|
|
188
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';
|
|
189
272
|
// FIRST, because an organization's refusal mentions the subscription and
|
|
190
273
|
// reads as a signed-out login to the list below.
|
|
191
274
|
if (ORG_BLOCKED.some((re) => re.test(text))) return 'org-blocked';
|
|
192
275
|
// BEFORE SIGNED-OUT, because Codex's sentence says "ChatGPT account".
|
|
193
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';
|
|
194
279
|
if (SIGNED_OUT.some((re) => re.test(text))) return 'signed-out';
|
|
195
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';
|
|
196
284
|
if (WORKSPACE.some((re) => re.test(text))) return 'workspace';
|
|
197
285
|
// LAST ON PURPOSE, so every text that already had a name keeps it. A dropped
|
|
198
286
|
// connection is the weakest of the four claims: an account that is signed out
|
|
@@ -208,26 +296,112 @@ export function troubleCause(raw) {
|
|
|
208
296
|
// workspace policy prints, so the text alone cannot tell them apart. The
|
|
209
297
|
// login's own file can: `plan` is main/claude-plan.mjs's reading of it, and
|
|
210
298
|
// `ended` is its billing gone to "none". Anything else keeps its cause.
|
|
211
|
-
|
|
212
|
-
|
|
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())}`;
|
|
213
346
|
}
|
|
214
347
|
|
|
215
|
-
// WHEN THE LIMIT LETS GO,
|
|
348
|
+
// WHEN THE LIMIT LETS GO, IN THE CLOCK SHE IS LOOKING AT.
|
|
216
349
|
//
|
|
217
350
|
// carries the one thing she actually wants to know, and we were throwing it
|
|
218
|
-
// away and then telling her the vaguer "when the limit resets".
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
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());
|
|
227
368
|
}
|
|
228
369
|
|
|
229
370
|
const MONTHS = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'];
|
|
230
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
|
+
|
|
231
405
|
// THE SAME HOUR AS A MOMENT, so the fleet can wait exactly until it
|
|
232
406
|
// (a limit that reset at, say, 6:50pm could hold every agent until 7:03pm,
|
|
233
407
|
// because the app waited a fixed thirty minutes instead of reading this line).
|
|
@@ -245,15 +419,26 @@ export function limitResetMoment(raw, from = Date.now()) {
|
|
|
245
419
|
return new Date(Number(dated[3]), month, Number(dated[2]), h, Number(dated[5]), 0, 0).getTime();
|
|
246
420
|
}
|
|
247
421
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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;
|
|
257
442
|
}
|
|
258
443
|
|
|
259
444
|
// AN ACCOUNT THAT CANNOT BE USED AT ALL UNTIL A PERSON DOES SOMETHING.
|
|
@@ -265,19 +450,32 @@ export function needsHerHands(cause) {
|
|
|
265
450
|
// same person: one is her, in a terminal; one is an admin of a workspace she
|
|
266
451
|
// may not run. What they share is the only thing this question asks — trying
|
|
267
452
|
// again in thirty minutes cannot possibly help.
|
|
268
|
-
|
|
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';
|
|
269
458
|
}
|
|
270
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
|
+
|
|
271
466
|
// WHAT SHE READS WHEN NOT ONE AGENT CAN START ANYWHERE.
|
|
272
467
|
// Not "an error occurred". The subject of the sentence is the thing she cares
|
|
273
468
|
// about, which is her agents, and the reason follows it in plain words.
|
|
274
469
|
export function troubleSentence(cause) {
|
|
275
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.';
|
|
276
472
|
if (cause === 'org-blocked') return 'No agents can start. Your organization has turned Claude Code off.';
|
|
277
473
|
if (cause === 'subscription-ended') return 'No agents can start. Your Claude subscription has ended.';
|
|
278
474
|
if (cause === 'at-limit') return 'No agents can start. Your Claude plan is at its limit.';
|
|
279
475
|
if (cause === 'workspace') return 'No agents can start. Claude will not run in this folder.';
|
|
280
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.';
|
|
281
479
|
return 'No agents can start on this computer right now.';
|
|
282
480
|
}
|
|
283
481
|
|
|
@@ -288,11 +486,14 @@ export function troubleRemedy(cause) {
|
|
|
288
486
|
// ). "Sign in to Claude again" is a thing to go and work out; /login is a
|
|
289
487
|
// thing to type.
|
|
290
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.`;
|
|
291
490
|
if (cause === 'org-blocked') return 'Ask an admin of that Claude workspace to turn Claude Code back on.';
|
|
292
|
-
if (cause === 'subscription-ended') return 'Renew it at claude.ai
|
|
491
|
+
if (cause === 'subscription-ended') return 'Renew it at claude.ai and they pick straight up.';
|
|
293
492
|
if (cause === 'at-limit') return 'They start again on their own when it resets.';
|
|
294
493
|
if (cause === 'workspace') return 'Open the folder in Claude once and say yes to trusting it.';
|
|
295
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.`;
|
|
296
497
|
return `${Name} keeps trying on its own.`;
|
|
297
498
|
}
|
|
298
499
|
|
|
@@ -320,9 +521,14 @@ export function accountSentence(cause, dir) {
|
|
|
320
521
|
return 'Signed in, but its organization has turned Claude Code off. Only an admin of that workspace can turn it back on.';
|
|
321
522
|
}
|
|
322
523
|
if (cause === 'subscription-ended') return 'Signed in, but its Claude subscription has ended. Renew it at claude.ai to use this account again.';
|
|
323
|
-
if (cause === 'at-limit') return 'At its limit.
|
|
524
|
+
if (cause === 'at-limit') return 'At its limit. Agents start again on their own when it resets.';
|
|
324
525
|
if (cause === 'workspace') return 'Claude would not run in the folder this was asked to work in.';
|
|
325
526
|
if (cause === 'interrupted') return `Its runs keep getting cut off. ${Name} is trying them again.`;
|
|
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}.`;
|
|
326
532
|
// A cause we cannot name points at the account, not at the app (2026-10-11).
|
|
327
533
|
return 'Claude Code would not start on this account. Check that its plan is active at claude.ai.';
|
|
328
534
|
}
|
|
@@ -361,6 +567,9 @@ export function engineTroubleNote(cause, engineWord = 'Codex') {
|
|
|
361
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.`;
|
|
362
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.`;
|
|
363
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.`;
|
|
364
573
|
return `${engineWord} sessions are dying on arrival, so its tasks are not moving. ${Name} keeps trying.`;
|
|
365
574
|
}
|
|
366
575
|
|
|
@@ -473,9 +682,38 @@ const DEAD_RUN_OPENERS = {
|
|
|
473
682
|
'at-limit': 'is at its usage limit, so nothing has been done on this task.',
|
|
474
683
|
interrupted: 'was cut off in the middle, so nothing has been done on this task.',
|
|
475
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.',
|
|
476
694
|
unknown: 'stopped before it did anything, so this task has not been started.',
|
|
477
695
|
};
|
|
478
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
|
+
|
|
479
717
|
/**
|
|
480
718
|
* THE LINE IN A THREAD WHERE A RUN STOPPED, in our words.
|
|
481
719
|
*
|
|
@@ -494,6 +732,12 @@ const RUN_STOPPED = {
|
|
|
494
732
|
workspace: "This run stopped because it would not start in this project's folder.",
|
|
495
733
|
interrupted: 'This run was cut off in the middle.',
|
|
496
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.',
|
|
497
741
|
unknown: 'This run stopped before it finished.',
|
|
498
742
|
};
|
|
499
743
|
|
|
@@ -501,35 +745,135 @@ export function runStoppedLine(raw) {
|
|
|
501
745
|
return RUN_STOPPED[troubleCause(raw)] ?? RUN_STOPPED.unknown;
|
|
502
746
|
}
|
|
503
747
|
|
|
504
|
-
//
|
|
505
|
-
//
|
|
506
|
-
|
|
507
|
-
|
|
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)}`;
|
|
508
820
|
|
|
509
821
|
const again = runs > 1 ? ` This has happened ${runs} times on this task.` : '';
|
|
510
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)}` : '';
|
|
511
829
|
// THE TIME THE LIMIT PRINTED, when there is one. "when the limit resets"
|
|
512
830
|
// leaves her with a question; "at 6pm" is an answer, and it is the CLI's own
|
|
513
831
|
// word for it rather than anything we worked out.
|
|
514
832
|
const next = cause === 'signed-out'
|
|
515
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.`
|
|
516
836
|
: cause === 'org-blocked'
|
|
517
837
|
? ' Only an admin of that Claude workspace can turn it back on.'
|
|
518
838
|
: cause === 'subscription-ended'
|
|
519
|
-
? ' Renew it at claude.ai
|
|
839
|
+
? ' Renew it at claude.ai and it picks straight up.'
|
|
520
840
|
: cause === 'at-limit'
|
|
521
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.`
|
|
522
850
|
: cause === 'interrupted'
|
|
523
|
-
? ` ${Name} runs it again on its own.`
|
|
851
|
+
? ` ${Name} runs it again on its own${when}.`
|
|
524
852
|
: cause === 'model-not-on-plan'
|
|
525
|
-
? (
|
|
526
|
-
? ` ${
|
|
527
|
-
: ' Pick another model for this task
|
|
528
|
-
:
|
|
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}.`;
|
|
529
865
|
|
|
530
866
|
return `${opener}${again}${next}`;
|
|
531
867
|
}
|
|
532
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
|
+
|
|
533
877
|
/* ------------- a run that finished and still delivered nothing ------------ */
|
|
534
878
|
// WHAT GOES ON HER ROW WHEN THE RUN WAS FINE AND HER ANSWER STILL WENT NOWHERE.
|
|
535
879
|
//
|
|
@@ -558,7 +902,43 @@ export function deadRunSentence({ engineWord = 'The agent', cause = 'unknown', r
|
|
|
558
902
|
export function undeliveredAnswerSentence({ engineWord = 'The agent', attempts = 1 } = {}) {
|
|
559
903
|
const n = Math.max(1, Math.trunc(attempts ?? 1));
|
|
560
904
|
const runs = n === 1 ? 'once and finished' : `${n} times and each run finished`;
|
|
561
|
-
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);
|
|
562
942
|
}
|
|
563
943
|
|
|
564
944
|
// WHOSE SENTENCE IS ON THIS ROW, AND WHAT IT SAYS HAPPENED.
|
|
@@ -575,12 +955,20 @@ export function undeliveredAnswerSentence({ engineWord = 'The agent', attempts =
|
|
|
575
955
|
// So a result is asked whose it is. Null means somebody wrote a real answer
|
|
576
956
|
// there and the row is not stranded; a cause means the last word on that row is
|
|
577
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.
|
|
578
963
|
export function deadRunCause(text) {
|
|
579
964
|
const s = String(text ?? '');
|
|
580
965
|
if (!s.trim()) return null;
|
|
581
966
|
for (const [cause, opener] of Object.entries(DEAD_RUN_OPENERS)) {
|
|
582
967
|
if (s.includes(opener)) return cause;
|
|
583
968
|
}
|
|
969
|
+
for (const [cause, halves] of Object.entries(DEAD_RUN_HALVES)) {
|
|
970
|
+
if (halves.every((half) => s.includes(half))) return cause;
|
|
971
|
+
}
|
|
584
972
|
return null;
|
|
585
973
|
}
|
|
586
974
|
|
|
@@ -603,12 +991,17 @@ export function deadRunCause(text) {
|
|
|
603
991
|
// where the shape has no room for a sentence, which is most of them.
|
|
604
992
|
export function causeWord(cause) {
|
|
605
993
|
if (cause === 'signed-out') return 'a signed-out login';
|
|
994
|
+
if (cause === 'no-plan') return 'no plan to run on';
|
|
606
995
|
if (cause === 'org-blocked') return 'an organization that blocks Claude Code';
|
|
607
996
|
if (cause === 'subscription-ended') return 'an ended Claude subscription';
|
|
608
997
|
if (cause === 'at-limit') return 'your Claude limit';
|
|
609
998
|
if (cause === 'workspace') return 'a folder Claude will not run in';
|
|
610
999
|
if (cause === 'interrupted') return 'runs getting cut off';
|
|
611
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';
|
|
612
1005
|
return 'something we cannot name';
|
|
613
1006
|
}
|
|
614
1007
|
|
|
@@ -618,12 +1011,17 @@ export function causeWord(cause) {
|
|
|
618
1011
|
export function causeWhat(cause, count) {
|
|
619
1012
|
const n = Math.max(0, Math.trunc(count ?? 0));
|
|
620
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`;
|
|
621
1015
|
if (cause === 'org-blocked') return `${n} stopped by an organization that blocks Claude Code`;
|
|
622
1016
|
if (cause === 'subscription-ended') return `${n} stopped by an ended Claude subscription`;
|
|
623
1017
|
if (cause === 'at-limit') return `${n} waiting on your Claude limit`;
|
|
624
1018
|
if (cause === 'workspace') return `${n} in a folder Claude will not run in`;
|
|
625
1019
|
if (cause === 'interrupted') return `${n} cut off mid-run`;
|
|
626
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`;
|
|
627
1025
|
return `${n} stopped for a reason we cannot name`;
|
|
628
1026
|
}
|
|
629
1027
|
|
|
@@ -631,12 +1029,17 @@ export function causeWhat(cause, count) {
|
|
|
631
1029
|
// comes from the limit line the CLI printed, never from our own clock.
|
|
632
1030
|
export function causeNext(cause, resetsAt = null) {
|
|
633
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}.`;
|
|
634
1033
|
if (cause === 'org-blocked') return 'Ask an admin of that Claude workspace to turn Claude Code back on.';
|
|
635
|
-
if (cause === 'subscription-ended') return 'Renew it at claude.ai
|
|
1034
|
+
if (cause === 'subscription-ended') return 'Renew it at claude.ai and they pick straight up.';
|
|
636
1035
|
if (cause === 'at-limit') return resetsAt ? `They start again on their own at ${resetsAt}.` : 'They start again on their own when it resets.';
|
|
637
1036
|
if (cause === 'workspace') return 'Open the folder in Claude once and say yes to trusting it.';
|
|
638
1037
|
if (cause === 'interrupted') return `Nothing to do. ${Name} is trying them again.`;
|
|
639
|
-
if (cause === 'model-not-on-plan') return 'Pick another model on each
|
|
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.';
|
|
640
1043
|
return `${Name} keeps trying on its own.`;
|
|
641
1044
|
}
|
|
642
1045
|
|
|
@@ -665,7 +1068,7 @@ export function causeBreakdown(rows = [], resetsAt = null) {
|
|
|
665
1068
|
what: causeWhat(cause, count),
|
|
666
1069
|
next: causeNext(cause, cause === 'at-limit' ? resetsAt : null),
|
|
667
1070
|
act: causeAct(cause),
|
|
668
|
-
hers: needsHerHands(cause) || cause === 'workspace' || cause === 'model-not-on-plan',
|
|
1071
|
+
hers: needsHerHands(cause) || cause === 'workspace' || cause === 'model-not-on-plan' || cause === 'level-not-offered' || cause === 'network' || cause === 'open-elsewhere',
|
|
669
1072
|
}))
|
|
670
1073
|
.sort((a, b) => (b.hers - a.hers) || (b.count - a.count) || a.cause.localeCompare(b.cause));
|
|
671
1074
|
}
|