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
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// MAIN GOES RED ON GITHUB AND ONE TASK SAYS WHICH TESTS (w-3836800002).
|
|
2
|
+
//
|
|
3
|
+
// Main was red on GitHub from 2026-10-09 to 2026-10-13 and nobody noticed. The
|
|
4
|
+
// `tests` workflow takes about five minutes, which is after the agent that
|
|
5
|
+
// shipped has already ended its turn, and `scripts/ship.mjs` signs off by
|
|
6
|
+
// printing "check the run" to a terminal nobody is reading. So the one gate
|
|
7
|
+
// that runs the WHOLE suite on both machines had no reader at all.
|
|
8
|
+
//
|
|
9
|
+
// The app ships the branch (main/ship-queue.mjs), so the app is the thing still
|
|
10
|
+
// running when that cross appears. After every successful ship it waits for
|
|
11
|
+
// that commit's run and files ONE task when main turns red: which tests, which
|
|
12
|
+
// machine each failed on, and the commit that did it.
|
|
13
|
+
//
|
|
14
|
+
// NOTHING BLOCKS ON THIS. No ship waits for it, no agent is held, and every
|
|
15
|
+
// failure here is silent: a machine without `gh`, or signed out of it, or
|
|
16
|
+
// without access to the repository, does nothing at all. A watcher that
|
|
17
|
+
// interrupted shipping to report on shipping would be worse than the gap it
|
|
18
|
+
// closes.
|
|
19
|
+
//
|
|
20
|
+
// ONE BREAKAGE IS ONE ROW. While main stays red, further ships file nothing:
|
|
21
|
+
// a row per red commit would be a dozen rows about one mistake. The green
|
|
22
|
+
// again lands on that same row as a note, so the row that told them reads as
|
|
23
|
+
// finished where they are already looking.
|
|
24
|
+
|
|
25
|
+
import { execFile } from 'node:child_process';
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
|
|
29
|
+
/** How often to ask GitHub, and how long before giving up on a run. */
|
|
30
|
+
export const POLL_MS = 60 * 1000;
|
|
31
|
+
export const GIVE_UP_MS = 30 * 60 * 1000;
|
|
32
|
+
|
|
33
|
+
const STATE = 'main-tests.json';
|
|
34
|
+
const WORKFLOW = 'tests';
|
|
35
|
+
const LOG_BYTES = 16 * 1024 * 1024;
|
|
36
|
+
|
|
37
|
+
/** `gh`, with its own words back and never a throw. */
|
|
38
|
+
export function runGh(args, { cwd, timeout = 60 * 1000 } = {}) {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
execFile('gh', args, { cwd, encoding: 'utf8', timeout, maxBuffer: LOG_BYTES }, (error, stdout, stderr) => {
|
|
41
|
+
if (error) resolve({ code: error.code ?? 1, out: `${stdout ?? ''}${stderr ?? ''}${error.message ?? ''}` });
|
|
42
|
+
else resolve({ code: 0, out: String(stdout ?? '') });
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The run for the commit that was just shipped, or null while GitHub has not
|
|
49
|
+
* made one yet.
|
|
50
|
+
*
|
|
51
|
+
* `scripts/ship.mjs` reports the SHORT sha and GitHub answers with the full
|
|
52
|
+
* forty, so either may be the prefix of the other.
|
|
53
|
+
*/
|
|
54
|
+
export function runFor(runs, sha) {
|
|
55
|
+
const want = String(sha ?? '').trim().toLowerCase();
|
|
56
|
+
if (!want) return null;
|
|
57
|
+
return (runs ?? []).find((run) => {
|
|
58
|
+
const head = String(run?.headSha ?? '').toLowerCase();
|
|
59
|
+
return head && (head.startsWith(want) || want.startsWith(head));
|
|
60
|
+
}) ?? null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* What that run says: `waiting`, `passing`, `failing`, or `ignore`.
|
|
65
|
+
*
|
|
66
|
+
* `cancelled` is IGNORED because a second push to main cancels the first
|
|
67
|
+
* (the `concurrency` block in .github/workflows/tests.yml), so a cancelled run
|
|
68
|
+
* is not a verdict on anything; the newer push's own watch carries it.
|
|
69
|
+
* `startup_failure` is ignored because it is a GitHub ACCOUNT problem rather
|
|
70
|
+
* than a test: for three weeks every run here ended that way, and the answer
|
|
71
|
+
* was a payment method, not a commit (the head of that workflow file says so).
|
|
72
|
+
* A row per push during a spell like that is noise with no fix in it.
|
|
73
|
+
*/
|
|
74
|
+
export function verdict(run) {
|
|
75
|
+
if (!run || run.status !== 'completed') return 'waiting';
|
|
76
|
+
const how = String(run.conclusion ?? '');
|
|
77
|
+
if (how === 'success') return 'passing';
|
|
78
|
+
if (how === 'failure' || how === 'timed_out') return 'failing';
|
|
79
|
+
return 'ignore';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Which machine a job ran on, by the label the workflow asked for. */
|
|
83
|
+
export function machineOf(job) {
|
|
84
|
+
const labels = (job?.labels ?? []).join(' ').toLowerCase();
|
|
85
|
+
const name = String(job?.name ?? '');
|
|
86
|
+
if (/ubuntu|linux/.test(`${labels} ${name}`)) return 'Linux';
|
|
87
|
+
if (/macos/.test(`${labels} ${name}`)) return 'macOS';
|
|
88
|
+
if (/windows/.test(`${labels} ${name}`)) return 'Windows';
|
|
89
|
+
return name || 'that machine';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const ANSI = /\u001b\[[0-9;]*m/g;
|
|
93
|
+
// A job log is one timestamped line per line of output, and GitHub's own
|
|
94
|
+
// lines carry a kind in front of them.
|
|
95
|
+
const STAMP = /^\S+Z\s/;
|
|
96
|
+
const GITHUB_SAYS = /^##\[[a-z]+\]/;
|
|
97
|
+
const clean = (raw) => String(raw).replace(ANSI, '').replace(STAMP, '').replace(GITHUB_SAYS, '').trim();
|
|
98
|
+
// Vitest's own `FAIL tests/x.test.mjs > suite > case`, and ONLY at the start
|
|
99
|
+
// of the line. A LINE VITEST QUOTED IS NOT THE RUNNER SPEAKING: the ship queue
|
|
100
|
+
// learned that three times over (w-560647d4db). A failing text assertion
|
|
101
|
+
// prints the file's own source as a diff (`+`/`-`) and as a code frame
|
|
102
|
+
// (`34| ...`), and this repo's test files are sentences, so plenty of them
|
|
103
|
+
// carry "failed" or "FAIL" inside a name. Anchoring at FAIL rules all of that
|
|
104
|
+
// out, and the tick vitest puts on a file it ran green rules out the rest.
|
|
105
|
+
const NAMES_A_RED_TEST = /^FAIL\s+(\S+?\.(?:test|spec)\.[a-z]+)(?:\s|$)/;
|
|
106
|
+
|
|
107
|
+
/** Every test file the runner printed a FAIL for, once each, in order. */
|
|
108
|
+
export function failedTestsIn(log) {
|
|
109
|
+
const found = [];
|
|
110
|
+
for (const raw of String(log ?? '').split('\n')) {
|
|
111
|
+
const name = NAMES_A_RED_TEST.exec(clean(raw))?.[1];
|
|
112
|
+
if (name && !found.includes(name)) found.push(name);
|
|
113
|
+
}
|
|
114
|
+
return found;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// VITEST EXITS 1 FOR THINGS THAT ARE NOT A FAILING TEST, and none of them
|
|
118
|
+
// print FAIL: an error thrown after a file's tests have passed, a collection
|
|
119
|
+
// that never started, a worker that died. The row used to stop at "names no
|
|
120
|
+
// failing test" with the log it had already fetched left unread.
|
|
121
|
+
//
|
|
122
|
+
// A banner is matched along with the rule vitest draws it in, because a line
|
|
123
|
+
// vitest QUOTED is not the runner speaking. The plural heading over the whole
|
|
124
|
+
// section is not one of these, and the three lines of advice are about
|
|
125
|
+
// unhandled errors in general rather than about this run.
|
|
126
|
+
const UNHANDLED = /^⎯+\s*Unhandled (?:Error|Rejection)\s*⎯*$/;
|
|
127
|
+
const NAMES_A_BREAKAGE = /^(?:No test files found|FATAL ERROR:)/;
|
|
128
|
+
const ADVICE = /^(?:Vitest caught|This might cause|Serialized Error)/;
|
|
129
|
+
const SAID_AT_MOST = 5;
|
|
130
|
+
|
|
131
|
+
/** What the runner itself said, for a row that can name no failing test. */
|
|
132
|
+
export function whatTheRunnerSaid(log) {
|
|
133
|
+
const lines = String(log ?? '').split('\n').map(clean);
|
|
134
|
+
const said = [];
|
|
135
|
+
const keep = (line) => {
|
|
136
|
+
if (line && !ADVICE.test(line) && !said.includes(line)) said.push(line);
|
|
137
|
+
};
|
|
138
|
+
lines.forEach((line, i) => {
|
|
139
|
+
if (UNHANDLED.test(line)) keep(lines.slice(i + 1, i + 8).find((l) => l && !ADVICE.test(l)));
|
|
140
|
+
else if (NAMES_A_BREAKAGE.test(line)) keep(line);
|
|
141
|
+
});
|
|
142
|
+
return said.slice(0, SAID_AT_MOST);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const shortSha = (sha) => String(sha ?? '').slice(0, 7);
|
|
146
|
+
const NOTHING_NAMED = 'nothing in its log names a failing test';
|
|
147
|
+
// A log `gh` could not hand over says nothing about the run, and a row that
|
|
148
|
+
// reported it as "names no failing test" would be making that up.
|
|
149
|
+
const NOT_READ = 'its log could not be read from here';
|
|
150
|
+
const unread = (machines) => machines.length > 0 && machines.every((m) => m.read === false);
|
|
151
|
+
/** The first thing any machine said, cut to something a title can hold. */
|
|
152
|
+
const firstWords = (machines, max) => {
|
|
153
|
+
const said = machines.flatMap((m) => m.said ?? []).find(Boolean);
|
|
154
|
+
if (!said) return '';
|
|
155
|
+
return said.length > max ? `${said.slice(0, max - 1).trimEnd()}…` : said;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** The row's title: the one test, or the count and the machines. */
|
|
159
|
+
export function redTitle(machines) {
|
|
160
|
+
const names = [...new Set(machines.flatMap((m) => m.tests))];
|
|
161
|
+
if (names.length === 1) return `main went red on GitHub: ${names[0]}`;
|
|
162
|
+
if (!names.length) {
|
|
163
|
+
const said = firstWords(machines, 58);
|
|
164
|
+
if (said) return `main went red on GitHub: ${said}`;
|
|
165
|
+
if (unread(machines)) return `main went red on GitHub, and ${NOT_READ}`;
|
|
166
|
+
return 'main went red on GitHub, and its log names no failing test';
|
|
167
|
+
}
|
|
168
|
+
const where = machines.filter((m) => m.tests.length).map((m) => m.machine);
|
|
169
|
+
return `main went red on GitHub: ${names.length} tests, on ${where.join(' and ')}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The row's Context: what a reader gets before they open anything. */
|
|
173
|
+
export function redContext({ sha, machines }) {
|
|
174
|
+
const names = [...new Set(machines.flatMap((m) => m.tests))];
|
|
175
|
+
const where = machines.filter((m) => m.tests.length).map((m) => m.machine);
|
|
176
|
+
if (!names.length) {
|
|
177
|
+
const said = firstWords(machines, 110);
|
|
178
|
+
let how = NOTHING_NAMED;
|
|
179
|
+
if (said) how = `no test failed and the runner said "${said}"`;
|
|
180
|
+
else if (unread(machines)) how = NOT_READ;
|
|
181
|
+
return `main's tests run went red on GitHub at ${shortSha(sha)}, and ${how}. The run itself is in the task.`;
|
|
182
|
+
}
|
|
183
|
+
return [
|
|
184
|
+
`main's tests run went red on GitHub at ${shortSha(sha)}, on ${where.join(' and ')}.`,
|
|
185
|
+
`${names.length === 1 ? 'The failing test' : `The ${names.length} failing tests`}: ${names.join(', ')}.`,
|
|
186
|
+
'Which machine each failed on, and the run, are in the task.',
|
|
187
|
+
].join(' ');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** The whole row: what failed, where, and the commit that did it. */
|
|
191
|
+
export function redRow({ sha, subject, url, machines }) {
|
|
192
|
+
return {
|
|
193
|
+
title: redTitle(machines),
|
|
194
|
+
problem: redContext({ sha, machines }),
|
|
195
|
+
body: [
|
|
196
|
+
"main's `tests` run went red on GitHub, at this commit:",
|
|
197
|
+
'',
|
|
198
|
+
` ${shortSha(sha)} ${subject || 'no title'}`,
|
|
199
|
+
'',
|
|
200
|
+
...machines.flatMap(({ machine, tests, said, read }) => [
|
|
201
|
+
`${machine}:`,
|
|
202
|
+
...(tests.length ? tests.map((t) => `- ${t}`) : [
|
|
203
|
+
`- ${read === false ? NOT_READ : NOTHING_NAMED}${said?.length ? ', and the runner said:' : ''}`,
|
|
204
|
+
...(said ?? []).map((line) => ` ${line}`),
|
|
205
|
+
]),
|
|
206
|
+
'',
|
|
207
|
+
]),
|
|
208
|
+
...(url ? [`The whole run: ${url}`, ''] : []),
|
|
209
|
+
'That run finishes about five minutes after the agent that shipped has',
|
|
210
|
+
'already ended, so nobody is watching it: main was red here from',
|
|
211
|
+
'2026-10-09 to 2026-10-13 and nothing said so. This is the only row filed',
|
|
212
|
+
'while main stays red, and a note lands here when it goes green again.',
|
|
213
|
+
].join('\n'),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** What that row hears when main comes back. */
|
|
218
|
+
export function greenAgain({ sha, subject }) {
|
|
219
|
+
return `main's tests are green again on GitHub, as of ${shortSha(sha)}${subject ? ` (${subject})` : ''}.`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const sleep = (ms) => new Promise((resolve) => { setTimeout(resolve, ms).unref?.(); });
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* The watch itself: one per successful ship, and never anything a person waits
|
|
226
|
+
* on.
|
|
227
|
+
*
|
|
228
|
+
* WHETHER MAIN IS KNOWN RED LIVES IN THE APP'S OWN DATA FOLDER, beside the
|
|
229
|
+
* ship settings, because it is a fact about this Mac's watching rather than
|
|
230
|
+
* about anyone's work. With no file, main counts as fine, so the FIRST red run
|
|
231
|
+
* this ever sees files a row: requiring a green first would have kept it silent
|
|
232
|
+
* through exactly the four days that put this here.
|
|
233
|
+
*/
|
|
234
|
+
export class MainTests {
|
|
235
|
+
constructor({ store, userDir, gh = runGh, wait = sleep, now = Date.now, log = console } = {}) {
|
|
236
|
+
Object.assign(this, { store, userDir, gh, wait, now, log });
|
|
237
|
+
this.stopped = false;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
stop() { this.stopped = true; }
|
|
241
|
+
|
|
242
|
+
stateFile() { return path.join(this.userDir, STATE); }
|
|
243
|
+
|
|
244
|
+
read() {
|
|
245
|
+
try {
|
|
246
|
+
const raw = JSON.parse(fs.readFileSync(this.stateFile(), 'utf8'));
|
|
247
|
+
return raw && typeof raw === 'object' ? raw : {};
|
|
248
|
+
} catch { return {}; }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
write(state) {
|
|
252
|
+
try {
|
|
253
|
+
const tmp = `${this.stateFile()}.${process.pid}.tmp`;
|
|
254
|
+
fs.writeFileSync(tmp, `${JSON.stringify(state)}\n`);
|
|
255
|
+
fs.renameSync(tmp, this.stateFile());
|
|
256
|
+
} catch (e) { this.log.warn?.(`main-tests: could not record where main stands: ${e.message}`); }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** gh's JSON, or null if this machine cannot ask. */
|
|
260
|
+
async ask(args, cwd) {
|
|
261
|
+
const { code, out } = await this.gh(args, { cwd });
|
|
262
|
+
if (code !== 0) return null;
|
|
263
|
+
try { return JSON.parse(out); } catch { return null; }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async watch({ product, sha } = {}) {
|
|
267
|
+
const cwd = product?.repoPath;
|
|
268
|
+
if (!cwd || !sha || !fs.existsSync(cwd)) return null;
|
|
269
|
+
const until = this.now() + GIVE_UP_MS;
|
|
270
|
+
while (!this.stopped) {
|
|
271
|
+
const runs = await this.ask(
|
|
272
|
+
['run', 'list', '--branch', 'main', '--workflow', WORKFLOW, '-L', '20',
|
|
273
|
+
'--json', 'databaseId,headSha,status,conclusion,displayTitle,url'],
|
|
274
|
+
cwd,
|
|
275
|
+
);
|
|
276
|
+
// No gh, signed out, or no access to this repository: nothing, quietly.
|
|
277
|
+
if (!runs) return null;
|
|
278
|
+
const run = runFor(runs, sha);
|
|
279
|
+
const says = verdict(run);
|
|
280
|
+
if (says === 'ignore') return null;
|
|
281
|
+
if (says === 'passing') return this.wentGreen(product, run);
|
|
282
|
+
if (says === 'failing') return this.wentRed(product, run, cwd);
|
|
283
|
+
if (this.now() >= until) {
|
|
284
|
+
this.log.info?.(`main-tests: ${shortSha(sha)}'s run has not finished in half an hour; leaving it.`);
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
await this.wait(POLL_MS);
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** One row, and only while main was not already known red. */
|
|
293
|
+
async wentRed(product, run, cwd) {
|
|
294
|
+
const state = this.read();
|
|
295
|
+
if (state.red) return null;
|
|
296
|
+
const jobs = (await this.ask(['api', `repos/{owner}/{repo}/actions/runs/${run.databaseId}/jobs`, '--paginate'], cwd))?.jobs ?? [];
|
|
297
|
+
const machines = [];
|
|
298
|
+
for (const job of jobs) {
|
|
299
|
+
if (String(job?.conclusion ?? '') !== 'failure') continue;
|
|
300
|
+
const { code, out } = await this.gh(['api', `repos/{owner}/{repo}/actions/jobs/${job.id}/logs`], { cwd });
|
|
301
|
+
const read = code === 0;
|
|
302
|
+
const tests = read ? failedTestsIn(out) : [];
|
|
303
|
+
machines.push({ machine: machineOf(job), tests, read, said: read && !tests.length ? whatTheRunnerSaid(out) : [] });
|
|
304
|
+
}
|
|
305
|
+
const row = redRow({ sha: run.headSha, subject: run.displayTitle, url: run.url, machines });
|
|
306
|
+
let filed = null;
|
|
307
|
+
try {
|
|
308
|
+
filed = this.store.fileItem(product.slug, { ...row, kind: 'task' });
|
|
309
|
+
} catch (e) {
|
|
310
|
+
this.log.warn?.(`main-tests: could not file the red main: ${e.message}`);
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
this.write({ ...state, red: { product: product.slug, id: filed.id, sha: run.headSha } });
|
|
314
|
+
this.log.info?.(`main-tests: main went red at ${shortSha(run.headSha)}; filed ${filed.id}.`);
|
|
315
|
+
return filed;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** The green again, on the row that reported the red. */
|
|
319
|
+
wentGreen(product, run) {
|
|
320
|
+
const state = this.read();
|
|
321
|
+
if (!state.red) return null;
|
|
322
|
+
try {
|
|
323
|
+
this.store.noteItem(state.red.product, state.red.id, greenAgain({ sha: run.headSha, subject: run.displayTitle }));
|
|
324
|
+
} catch (e) {
|
|
325
|
+
this.log.warn?.(`main-tests: could not say main is green again: ${e.message}`);
|
|
326
|
+
}
|
|
327
|
+
this.write({ ...state, red: null });
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|