@bongos/core 1.25.32 → 1.25.33

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/.bongos-core.json CHANGED
@@ -2,22 +2,22 @@
2
2
  "artifact": "bongos-core",
3
3
  "manifest_schema": 1,
4
4
  "generator": "scripts/bongos/package-core.js",
5
- "core_version": "1.25.32",
6
- "core_contract": "1.25.32",
7
- "source_commit": "229f85ce4b90f085b848b35622dbcc128b0057ce",
5
+ "core_version": "1.25.33",
6
+ "core_contract": "1.25.33",
7
+ "source_commit": "4b5c8d701067bdfbff18b430afc31b42d93bc473",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-11-14T14:21:44.964Z",
9
+ "built_at": "2026-11-14T14:37:07.493Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 659,
13
13
  "agent_docs_stubbed": 34,
14
- "functional_verbatim": 4091,
14
+ "functional_verbatim": 4092,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 4785,
20
- "tree_sha256": "16aaec3eb64ea6c85c1525e28d61a133c757d674794144c34b1c3d3d621a8073",
19
+ "file_count": 4786,
20
+ "tree_sha256": "98b5b7a87754177055b0767a010c3c7376170c614a91d857e51705d09be541ad",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2542,12 +2542,12 @@
2542
2542
  {
2543
2543
  "path": "docs/copy-inventory.md",
2544
2544
  "mode": "0000644",
2545
- "sha256": "3988b264c3287e008eac0bc904071f1e1da6a4c3a08032509b7a0b6d8aa0ad83"
2545
+ "sha256": "1d0c71ce2c00fb0f2c6d0ef9718f2deae87bfedfda15e0fd907d2d74258f5d14"
2546
2546
  },
2547
2547
  {
2548
2548
  "path": "docs/copy-registry.json",
2549
2549
  "mode": "0000644",
2550
- "sha256": "8a25633c4c0dc902f09c90a3637a8fe87c021e6149cc7211584469dc6a2e6fb1"
2550
+ "sha256": "dbd60962cf08e2cdec6a2e59a7d89acaf106ebf163234d8e92fb58a33d3ae2ca"
2551
2551
  },
2552
2552
  {
2553
2553
  "path": "docs/design/apex-pass-2-direction.md",
@@ -3102,7 +3102,7 @@
3102
3102
  {
3103
3103
  "path": "docs/module-api-changelog.md",
3104
3104
  "mode": "0000644",
3105
- "sha256": "b7a5a08b27c2c3a25ba91434a45cdd7e8d3a480403cffc68d02bea3bf8bb4c0c"
3105
+ "sha256": "33db3faa1834a96618182e25a012b6cc98ca87236d346953c987fa6c1f1c7997"
3106
3106
  },
3107
3107
  {
3108
3108
  "path": "docs/modules-contract.md",
@@ -3187,7 +3187,7 @@
3187
3187
  {
3188
3188
  "path": "docs/page-readings.json",
3189
3189
  "mode": "0000644",
3190
- "sha256": "d1f2904c04abff3a17f6d0ff1515036f2d33e334ef4e4b63775e10ff56e30491"
3190
+ "sha256": "a19d45806423cb1f44f40f6bc6f5cc130db29e4ccefe8d85af8967fb68334d63"
3191
3191
  },
3192
3192
  {
3193
3193
  "path": "docs/project-context.template.md",
@@ -4752,7 +4752,7 @@
4752
4752
  {
4753
4753
  "path": "modules/autonomy/runner-health.js",
4754
4754
  "mode": "0000644",
4755
- "sha256": "c25bb637534ba41b9a71e3d34321415ff0edff7dae4edac40736436cd4e2bad2"
4755
+ "sha256": "dfab3899749342a6e1f0684d0f4b1aab8c42550024b8e555e8cc1413e7d05e67"
4756
4756
  },
4757
4757
  {
4758
4758
  "path": "modules/autonomy/server-schedule.js",
@@ -6662,7 +6662,7 @@
6662
6662
  {
6663
6663
  "path": "modules/hall-ui/public/gate.js",
6664
6664
  "mode": "0000644",
6665
- "sha256": "9b166e3028e034fc98bcff582c7e588f1bb0b2e7d82459c5a7ee1cac5f64269f"
6665
+ "sha256": "3d1e7683f27eb30c11647e89a3ae6138c9d2e459606deb6a334d7231490f5576"
6666
6666
  },
6667
6667
  {
6668
6668
  "path": "modules/hall-ui/public/genesis-home.js",
@@ -7337,7 +7337,7 @@
7337
7337
  {
7338
7338
  "path": "modules/hall-ui/public/runner-button.js",
7339
7339
  "mode": "0000644",
7340
- "sha256": "f083be214ca3ab6e6868c43a5d67a99ab904c9e8939e4f1566df4f1fe5b0c562"
7340
+ "sha256": "83f065223dbf6903bc0426adc92459fc486bc46b877537aa2944b8d71aade49d"
7341
7341
  },
7342
7342
  {
7343
7343
  "path": "modules/hall-ui/public/sessions.css",
@@ -12517,12 +12517,12 @@
12517
12517
  {
12518
12518
  "path": "package-lock.json",
12519
12519
  "mode": "0000644",
12520
- "sha256": "c67d732a8b17d6770f86652bc20ba6b052afd0b65de35bea9e897d531ea8847b"
12520
+ "sha256": "1855476688c40e5b761ea79b3ad14f908dc4ca9204de3d2c1eed4972e669e625"
12521
12521
  },
12522
12522
  {
12523
12523
  "path": "package.json",
12524
12524
  "mode": "0000644",
12525
- "sha256": "3d1af59f70de777b9e2b383222f288d301dd2ce7c31fe52f2488033454950c18"
12525
+ "sha256": "cbfcba84dcf5ffd88672a1e50168bc45df2c385babf24f21ae0f6f401fb9f6a4"
12526
12526
  },
12527
12527
  {
12528
12528
  "path": "public-docs/index.html",
@@ -12542,7 +12542,7 @@
12542
12542
  {
12543
12543
  "path": "release-notes.json",
12544
12544
  "mode": "0000644",
12545
- "sha256": "b3f123f8b328674e373abac3a487b23889dda6e0118da2e8888589f026e20c00"
12545
+ "sha256": "5cb90b68209691dda5d1d1834ee42c3cec56272605119bedb2465a0ac1302abb"
12546
12546
  },
12547
12547
  {
12548
12548
  "path": "scripts/bongos/CLAUDE.md",
@@ -12682,7 +12682,7 @@
12682
12682
  {
12683
12683
  "path": "scripts/bongos/autobongos-host.js",
12684
12684
  "mode": "0000755",
12685
- "sha256": "5fc39d1b1c98c23ef7cc9549de376ed2c9751575934af550194cb29a5a5f7a4d"
12685
+ "sha256": "92caf5cebcaa06eb71b0bc514f3616fc7b29bc5e04e8b1c2ee4011849dfb2042"
12686
12686
  },
12687
12687
  {
12688
12688
  "path": "scripts/bongos/autobongos-loop.js",
@@ -12692,7 +12692,7 @@
12692
12692
  {
12693
12693
  "path": "scripts/bongos/autobongos-run.js",
12694
12694
  "mode": "0000644",
12695
- "sha256": "b051621396ec639d2503beec1c992be84e3fefc22d959a7124698da7e0a7a0fe"
12695
+ "sha256": "e366b9995bef2f1d6313634f483a182b288272ed40af3496c4d0a4ad0655e9d6"
12696
12696
  },
12697
12697
  {
12698
12698
  "path": "scripts/bongos/autobongos-service.cmd",
@@ -16622,7 +16622,7 @@
16622
16622
  {
16623
16623
  "path": "src/module-api.js",
16624
16624
  "mode": "0000644",
16625
- "sha256": "130d7b45a6fd3579b7863b1901969d8dde97ae473ca53b198efe88b91ddb90e0"
16625
+ "sha256": "ae2ad3a3e6b66f1e03369e0b40df1417f17519792ccd2a5cd90bf509cb7b3a51"
16626
16626
  },
16627
16627
  {
16628
16628
  "path": "src/module-loader/catalog.js",
@@ -17179,6 +17179,11 @@
17179
17179
  "mode": "0000644",
17180
17180
  "sha256": "3c55fd843bb0e8e5c9eaf3e8be4d32fe9a46eba077eaaf30a45ad7bea959d27f"
17181
17181
  },
17182
+ {
17183
+ "path": "tests/autobongos_cannot_fetch.mjs",
17184
+ "mode": "0000644",
17185
+ "sha256": "64d6f7783afb4e692653983fab4c67b61a59abee8d2e2279807605f64df6b4ef"
17186
+ },
17182
17187
  {
17183
17188
  "path": "tests/autobongos_fence.mjs",
17184
17189
  "mode": "0000644",
@@ -91,7 +91,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
91
91
  | `af615fa9f404` | certain | (checked {…}) | `modules/hall-ui/public/deploy.js:516` |
92
92
  | `e2c5af6b2815` | certain | (the desktop app, or Claude Code in a terminal). | `modules/hall-ui/public/setup-gate.js:33` |
93
93
  | `c7aecadc8133` | certain | (with {…}) | `modules/hall-ui/public/goals-render.js:594` |
94
- | `c81e3a97b531` | certain | + Add goal | `modules/hall-ui/public/gate.js:217` |
94
+ | `c81e3a97b531` | certain | + Add goal | `modules/hall-ui/public/gate.js:221` |
95
95
  | `27d9721f358f` | certain | + Invite | `modules/hall-ui/public/goals-render.js:1213` |
96
96
  | `74815c8882ba` | certain | + send a new scout | `modules/hall-ui/public/market-watch.html:51` |
97
97
  | `646fab1157fe` | certain | + {…} more project{…} not publicly listed. | `modules/hall-ui/public/profile.js:395` |
@@ -151,7 +151,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
151
151
  | `524188fe0078` | certain | Active sessions | `modules/hall-ui/public/settings.html:190` |
152
152
  | `e4da002ae135` | certain | Add ${…} | `modules/hall-ui/public/ai-studio.js:567` |
153
153
  | `e271eaeba3ba` | certain | Add a card | `modules/hall-ui/public/settings.html:218` |
154
- | `1bdd78d8e7c5` | certain | Add a device | `modules/hall-ui/public/gate.js:188` |
154
+ | `1bdd78d8e7c5` | certain | Add a device | `modules/hall-ui/public/gate.js:192` |
155
155
  | `1bdd78d8e7c5` | certain | Add a device | `modules/hall-ui/public/settings-autobongos.js:211` |
156
156
  | `ff277763b4ce` | certain | Add a device, press Run on its card, and it builds the goals you choose below while you are away. Any Windows | `modules/hall-ui/public/settings.html:233` |
157
157
  | `32509514479f` | certain | Add a done-when criterion | `modules/hall-ui/public/goal-author.js:38` |
@@ -305,7 +305,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
305
305
  | `aa3ba7d28a76` | certain | Checking your key… | `modules/hall-ui/public/settings.js:794` |
306
306
  | `834f8c6f3b72` | certain | Choose a builder… | `modules/hall-ui/public/goals-render.js:1249` |
307
307
  | `b3d0e2af5176` | certain | Choose a goal first. | `modules/hall-ui/public/task.js:1023` |
308
- | `89ddfeda9566` | certain | Choose goals | `modules/hall-ui/public/gate.js:186` |
308
+ | `89ddfeda9566` | certain | Choose goals | `modules/hall-ui/public/gate.js:190` |
309
309
  | `6bf684ebc1c3` | certain | Choose the skills for {…} | `modules/hall-ui/public/speciality-tools.js:199` |
310
310
  | `6c88cdf417f1` | certain | Choose which sound plays for each build event — one when Claude waits for your input, another when a task ship | `modules/hall-ui/public/settings.html:368` |
311
311
  | `7b984c01b6a8` | certain | Choose your character | `modules/hall-ui/public/craft-mode.js:910` |
@@ -485,7 +485,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
485
485
  | `64f6af887b28` | certain | Drag to go back in time | `modules/hall-ui/public/visor.js:577` |
486
486
  | `39f897eaff3c` | certain | Drop sketches, pictures or a PDF here, paste a picture, | `modules/hall-ui/public/ai-studio.js:180` |
487
487
  | `67f09099c2d3` | certain | Duplicate — should be said once, in one place | `modules/hall-ui/public/copy-desk.html:93` |
488
- | `1de61111bc00` | certain | Each builder's runner | `modules/hall-ui/public/gate.js:418` |
488
+ | `1de61111bc00` | certain | Each builder's runner | `modules/hall-ui/public/gate.js:422` |
489
489
  | `1f3b826a05d4` | certain | Each month is recorded spend ({…} over these {…} months) plus payouts and fees out of the project account. Rea | `modules/hall-ui/public/money.js:126` |
490
490
  | `3a6916fa79d4` | certain | Each one links back to this page. | `modules/hall-ui/public/lightbulb.js:103` |
491
491
  | `3f7d82a0d9b1` | certain | Each was filed when a ship changed copy or visuals. No new ones arrive; answer these and the door goes. | `modules/hall-ui/public/studio.html:197` |
@@ -498,7 +498,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
498
498
  | `8e82771e5b63` | certain | Ending a session takes effect immediately; the token stops working on its very next request. | `modules/hall-ui/public/settings.html:202` |
499
499
  | `58d381535285` | certain | Engagement reports | `modules/hall-ui/public/watch.html:129` |
500
500
  | `6fd9b1bf8d97` | certain | Engineering work is open on this page ({…}), so code may change around your words. You can keep writing. | `modules/hall-ui/public/tweak-editor.js:304` |
501
- | `690609c8e9a9` | certain | Enter a goal number. | `modules/hall-ui/public/gate.js:323` |
501
+ | `690609c8e9a9` | certain | Enter a goal number. | `modules/hall-ui/public/gate.js:327` |
502
502
  | `8eabe3d69bf3` | certain | Enter files the idea. Shift and Enter starts a new line. | `modules/hall-ui/public/home-idea-box.js:55` |
503
503
  | `4f776bc33d5f` | certain | Enter the builders’ hall | `modules/hall-ui/public/genesis-home.js:421` |
504
504
  | `1825c9c467d8` | certain | Enter the id of the task this one should wait for. | `modules/hall-ui/public/task.js:965` |
@@ -577,15 +577,15 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
577
577
  | `9e70797b7e97` | certain | Goal id | `modules/hall-ui/public/goals-render.js:852` |
578
578
  | `529460e82058` | certain | Goal invitations to you | `modules/hall-ui/public/goal-inbox.js:149` |
579
579
  | `3807521c5db7` | certain | Goal map | `modules/hall-ui/public/goals.html:70` |
580
- | `ed1966d824a5` | certain | Goal number | `modules/hall-ui/public/gate.js:209` |
580
+ | `ed1966d824a5` | certain | Goal number | `modules/hall-ui/public/gate.js:213` |
581
581
  | `e2a9e33abe60` | certain | Goal tasks | `modules/hall-ui/public/goals.html:155` |
582
- | `ec88b946ad16` | certain | Goal {…} | `modules/hall-ui/public/gate.js:202` |
583
- | `6f0deb0b667d` | certain | Goal {…} added. | `modules/hall-ui/public/gate.js:326` |
584
- | `8a421ae268e6` | certain | Goal {…} removed. | `modules/hall-ui/public/gate.js:350` |
582
+ | `ec88b946ad16` | certain | Goal {…} | `modules/hall-ui/public/gate.js:206` |
583
+ | `6f0deb0b667d` | certain | Goal {…} added. | `modules/hall-ui/public/gate.js:330` |
584
+ | `8a421ae268e6` | certain | Goal {…} removed. | `modules/hall-ui/public/gate.js:354` |
585
585
  | `e0bc28d10785` | certain | Goals could not be loaded. Refresh to try again. | `modules/hall-ui/public/goals-page.js:875` |
586
586
  | `4517f2253d0f` | certain | Goals couldn’t be loaded just now. Refresh to try again. | `modules/hall-ui/public/goals.js:142` |
587
587
  | `6a85997bda21` | certain | Goals created | `modules/hall-ui/public/profile.html:152` |
588
- | `6bbd703b5aad` | certain | Goals it can work on | `modules/hall-ui/public/gate.js:218` |
588
+ | `6bbd703b5aad` | certain | Goals it can work on | `modules/hall-ui/public/gate.js:222` |
589
589
  | `bba4caf837e9` | certain | Goals — {{worldName}} | `modules/hall-ui/public/goals.html:6` |
590
590
  | `bba4caf837e9` | certain | Goals — {{worldName}} | `modules/hall-ui/public/goals.html:6` |
591
591
  | `d0f76a295d09` | certain | Good afternoon. | `modules/hall-ui/public/studio.js:1059` |
@@ -822,7 +822,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
822
822
  | `b5839a34590c` | certain | My agents — {{worldName}} | `modules/hall-ui/public/my-agents.html:6` |
823
823
  | `b5839a34590c` | certain | My agents — {{worldName}} | `modules/hall-ui/public/my-agents.html:6` |
824
824
  | `39afdc03594a` | certain | My balance | `modules/hall-ui/public/ai-studio.js:340` |
825
- | `3d0d554cd684` | certain | Name (optional) | `modules/hall-ui/public/gate.js:209` |
825
+ | `3d0d554cd684` | certain | Name (optional) | `modules/hall-ui/public/gate.js:213` |
826
826
  | `ae84ebe1d735` | certain | Name cannot be blank. | `modules/hall-ui/public/settings.js:458` |
827
827
  | `31e91ee6ce03` | certain | Name must be {…} characters or fewer. | `modules/hall-ui/public/settings.js:463` |
828
828
  | `00461d24cd51` | certain | Name this card (optional) | `modules/hall-ui/public/settings.html:217` |
@@ -984,7 +984,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
984
984
  | `fa818ee444b4` | certain | Only this goal's owner or a manager (or an Archon) may do this. | `modules/hall-ui/public/goals-render.js:1363` |
985
985
  | `fa818ee444b4` | certain | Only this goal's owner or a manager (or an Archon) may do this. | `modules/hall-ui/public/goals-render.js:1379` |
986
986
  | `544011db314a` | certain | Only this goal's owner or a manager (or an Archon) may invite. | `modules/hall-ui/public/goals-render.js:1311` |
987
- | `42bb75e4297c` | certain | Open Settings | `modules/hall-ui/public/gate.js:187` |
987
+ | `42bb75e4297c` | certain | Open Settings | `modules/hall-ui/public/gate.js:191` |
988
988
  | `5f3e0b179839` | certain | Open in Sessions | `modules/hall-ui/public/task.js:491` |
989
989
  | `9f55e4674ebb` | certain | Open in the copy desk | `modules/hall-ui/public/studio.js:253` |
990
990
  | `03197f066323` | certain | Open it in the tweak editor | `modules/hall-ui/public/paintbrush.js:440` |
@@ -1125,7 +1125,7 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1125
1125
  | `58af483d7a84` | certain | Release task {…} | `modules/hall-ui/public/goals-render.js:374` |
1126
1126
  | `c49f9b9f8864` | certain | Remake or tweak | `modules/hall-ui/public/studio.js:216` |
1127
1127
  | `65e3399a8fbf` | certain | Remove filter | `modules/hall-ui/public/hall-kit.js:311` |
1128
- | `12c3328b937c` | certain | Remove goal {…} | `modules/hall-ui/public/gate.js:202` |
1128
+ | `12c3328b937c` | certain | Remove goal {…} | `modules/hall-ui/public/gate.js:206` |
1129
1129
  | `1c755c04e705` | certain | Remove this line | `modules/hall-ui/public/paintbrush.js:129` |
1130
1130
  | `20882d6ffdd0` | certain | Removed {…} from builder {…}. | `modules/hall-ui/public/government.js:411` |
1131
1131
  | `9be75ebd8d67` | certain | Replay the sky from the first day | `modules/hall-ui/public/goal-map.html:127` |
@@ -1167,8 +1167,8 @@ Identical copy within one surface. Sometimes right (a repeated button), sometime
1167
1167
  | `c56d9a6451a7` | certain | Rewrite them in the tweak editor | `modules/hall-ui/public/paintbrush.js:248` |
1168
1168
  | `12af2af5acf2` | certain | Role it sits under | `modules/hall-ui/public/speciality-tools.js:614` |
1169
1169
  | `4c1e8ab8e1de` | certain | Run this command instead (same folder). When it asks for the token, click Copy token and paste it there; nothi | `modules/hall-ui/public/settings.js:652` |
1170
- | `48e5f1d19e90` | certain | Runner allowed to work | `modules/hall-ui/public/gate.js:267` |
1171
- | `41730c113dd5` | certain | Runners work only the goals above, and only while the switch is on. Each builder picks which of them their own | `modules/hall-ui/public/gate.js:267` |
1170
+ | `48e5f1d19e90` | certain | Runner allowed to work | `modules/hall-ui/public/gate.js:271` |
1171
+ | `41730c113dd5` | certain | Runners work only the goals above, and only while the switch is on. Each builder picks which of them their own | `modules/hall-ui/public/gate.js:271` |
1172
1172
  | `6fb37ca50950` | certain | Runs use this key, so the project's Anthropic account pays for them. It is stored encrypted and never shown ag | `modules/hall-ui/public/market-watch.html:170` |
1173
1173
  | `b6596905f845` | certain | Said more than once | `modules/hall-ui/public/copy-desk.html:56` |
1174
1174
  | `ce046dcbd8e1` | certain | Save as PDF | `modules/hall-ui/public/modules.html:112` |
@@ -1136,7 +1136,7 @@
1136
1136
  "text": "Add a device",
1137
1137
  "occurrences": 2,
1138
1138
  "at": [
1139
- "modules/hall-ui/public/gate.js:188",
1139
+ "modules/hall-ui/public/gate.js:192",
1140
1140
  "modules/hall-ui/public/settings-autobongos.js:211"
1141
1141
  ]
1142
1142
  },
@@ -3238,7 +3238,7 @@
3238
3238
  "surface": "builders-hall",
3239
3239
  "text": "+ Add goal",
3240
3240
  "file": "modules/hall-ui/public/gate.js",
3241
- "line": 217,
3241
+ "line": 221,
3242
3242
  "origin": "js-markup",
3243
3243
  "confidence": "certain"
3244
3244
  },
@@ -3778,7 +3778,7 @@
3778
3778
  "surface": "builders-hall",
3779
3779
  "text": "Add a device",
3780
3780
  "file": "modules/hall-ui/public/gate.js",
3781
- "line": 188,
3781
+ "line": 192,
3782
3782
  "origin": "js-markup",
3783
3783
  "confidence": "certain"
3784
3784
  },
@@ -5164,7 +5164,7 @@
5164
5164
  "surface": "builders-hall",
5165
5165
  "text": "Choose goals",
5166
5166
  "file": "modules/hall-ui/public/gate.js",
5167
- "line": 186,
5167
+ "line": 190,
5168
5168
  "origin": "js-markup",
5169
5169
  "confidence": "certain"
5170
5170
  },
@@ -6784,7 +6784,7 @@
6784
6784
  "surface": "builders-hall",
6785
6785
  "text": "Each builder's runner",
6786
6786
  "file": "modules/hall-ui/public/gate.js",
6787
- "line": 418,
6787
+ "line": 422,
6788
6788
  "origin": "js-markup",
6789
6789
  "confidence": "certain"
6790
6790
  },
@@ -6901,7 +6901,7 @@
6901
6901
  "surface": "builders-hall",
6902
6902
  "text": "Enter a goal number.",
6903
6903
  "file": "modules/hall-ui/public/gate.js",
6904
- "line": 323,
6904
+ "line": 327,
6905
6905
  "origin": "js-notice",
6906
6906
  "confidence": "certain"
6907
6907
  },
@@ -7612,7 +7612,7 @@
7612
7612
  "surface": "builders-hall",
7613
7613
  "text": "Goal number",
7614
7614
  "file": "modules/hall-ui/public/gate.js",
7615
- "line": 209,
7615
+ "line": 213,
7616
7616
  "origin": "js-markup",
7617
7617
  "confidence": "certain"
7618
7618
  },
@@ -7630,7 +7630,7 @@
7630
7630
  "surface": "builders-hall",
7631
7631
  "text": "Goal {…}",
7632
7632
  "file": "modules/hall-ui/public/gate.js",
7633
- "line": 202,
7633
+ "line": 206,
7634
7634
  "origin": "js-markup",
7635
7635
  "confidence": "certain"
7636
7636
  },
@@ -7639,7 +7639,7 @@
7639
7639
  "surface": "builders-hall",
7640
7640
  "text": "Goal {…} added.",
7641
7641
  "file": "modules/hall-ui/public/gate.js",
7642
- "line": 326,
7642
+ "line": 330,
7643
7643
  "origin": "js-notice",
7644
7644
  "confidence": "certain"
7645
7645
  },
@@ -7648,7 +7648,7 @@
7648
7648
  "surface": "builders-hall",
7649
7649
  "text": "Goal {…} removed.",
7650
7650
  "file": "modules/hall-ui/public/gate.js",
7651
- "line": 350,
7651
+ "line": 354,
7652
7652
  "origin": "js-notice",
7653
7653
  "confidence": "certain"
7654
7654
  },
@@ -7684,7 +7684,7 @@
7684
7684
  "surface": "builders-hall",
7685
7685
  "text": "Goals it can work on",
7686
7686
  "file": "modules/hall-ui/public/gate.js",
7687
- "line": 218,
7687
+ "line": 222,
7688
7688
  "origin": "js-markup",
7689
7689
  "confidence": "certain"
7690
7690
  },
@@ -9817,7 +9817,7 @@
9817
9817
  "surface": "builders-hall",
9818
9818
  "text": "Name (optional)",
9819
9819
  "file": "modules/hall-ui/public/gate.js",
9820
- "line": 209,
9820
+ "line": 213,
9821
9821
  "origin": "js-markup",
9822
9822
  "confidence": "certain"
9823
9823
  },
@@ -11275,7 +11275,7 @@
11275
11275
  "surface": "builders-hall",
11276
11276
  "text": "Open Settings",
11277
11277
  "file": "modules/hall-ui/public/gate.js",
11278
- "line": 187,
11278
+ "line": 191,
11279
11279
  "origin": "js-markup",
11280
11280
  "confidence": "certain"
11281
11281
  },
@@ -12544,7 +12544,7 @@
12544
12544
  "surface": "builders-hall",
12545
12545
  "text": "Remove goal {…}",
12546
12546
  "file": "modules/hall-ui/public/gate.js",
12547
- "line": 202,
12547
+ "line": 206,
12548
12548
  "origin": "js-markup",
12549
12549
  "confidence": "certain"
12550
12550
  },
@@ -12922,7 +12922,7 @@
12922
12922
  "surface": "builders-hall",
12923
12923
  "text": "Runner allowed to work",
12924
12924
  "file": "modules/hall-ui/public/gate.js",
12925
- "line": 267,
12925
+ "line": 271,
12926
12926
  "origin": "js-markup",
12927
12927
  "confidence": "certain"
12928
12928
  },
@@ -12931,7 +12931,7 @@
12931
12931
  "surface": "builders-hall",
12932
12932
  "text": "Runners work only the goals above, and only while the switch is on. Each builder picks which of them their own runner takes, and you can pause any one builder's runner below. All of it lives on the server, so a runner's PC can read it but cannot change it.",
12933
12933
  "file": "modules/hall-ui/public/gate.js",
12934
- "line": 267,
12934
+ "line": 271,
12935
12935
  "origin": "js-markup",
12936
12936
  "confidence": "certain"
12937
12937
  },
@@ -3502,5 +3502,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
3502
3502
  landed since 1.25.30 with no explicit bump. run 43602884082. (task 1002620)
3503
3503
  1.25.32 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3504
3504
  landed since 1.25.31 with no explicit bump. run 43604349792. (task 1002620)
3505
+ 1.25.33 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3506
+ landed since 1.25.32 with no explicit bump. run 43605667657. (task 1002620)
3505
3507
  ---------------------------------------------------------------------------
3506
3508
  ```
@@ -954,7 +954,7 @@
954
954
  "surface": "builders",
955
955
  "title": "Builders' Hall",
956
956
  "states_read": ["home","out","founding","genesis","decide","demo","votes"],
957
- "files_hash": "cc83670f9478672c",
957
+ "files_hash": "0f2d40cf76c32eb9",
958
958
  "reading_hash": "c5f4986bd90e2a2a",
959
959
  "counts": {"lines":29,"placed":8,"shared":8,"unplaced":13},
960
960
  "omitted": {"data":4},
@@ -2415,7 +2415,7 @@
2415
2415
  "surface": "builders",
2416
2416
  "title": "Options",
2417
2417
  "states_read": ["account","access","preferences","install-reports","sound","update"],
2418
- "files_hash": "f62327c29506ae22",
2418
+ "files_hash": "23bd832511bb89db",
2419
2419
  "reading_hash": "a6d3194ed1b751ca",
2420
2420
  "counts": {"lines":163,"placed":104,"shared":7,"unplaced":52},
2421
2421
  "omitted": {"data":23},
@@ -3259,7 +3259,7 @@
3259
3259
  "surface": "builders",
3260
3260
  "title": "Watch",
3261
3261
  "states_read": ["board","out","operations","security","sessions","autobongos"],
3262
- "files_hash": "80347c5c722c6c97",
3262
+ "files_hash": "78a1c17cb7e1331f",
3263
3263
  "reading_hash": "1961a0c06b9d8477",
3264
3264
  "counts": {"lines":72,"placed":33,"shared":7,"unplaced":32},
3265
3265
  "omitted": {"data":52},
@@ -30,6 +30,9 @@
30
30
  // it carries the fix as `remedy`. The runner decides the grace and says
31
31
  // `config_idle:<code>` on its heartbeat; only a code config-idle.js knows is
32
32
  // honoured, so the owner's kill switch and pause can never read as an alarm.
33
+ // · cannot_fetch — alive, but it has failed to reach the project several times
34
+ // in a row (task 1005202), so it is pinned to the code it has. NOT ok, and it
35
+ // carries the fix as `remedy`, like idle_config.
33
36
  // · stopped — alive, and its builder pressed Stop on this device (task
34
37
  // 1005094). Not an alarm and not "running": the device is doing what it was
35
38
  // told, and its card shows Run. Only a runner that is still checking in can
@@ -56,6 +59,11 @@ const RESTART_S = 10 * 60;
56
59
 
57
60
  const configIdle = require('./config-idle.js');
58
61
 
62
+ // The fix for `cannot_fetch` (task 1005202). Seen on a real Mac: the GitHub
63
+ // account git was using could not see the project. The runner's own log names
64
+ // that account; this is the step to take on the device.
65
+ const CANNOT_FETCH_REMEDY = 'On that device, git cannot reach this project on GitHub — usually it is signed in to a GitHub account that cannot see it. Run gh auth switch there to pick one that can (or gh auth login to add it); the runner\'s log names the account it is using. It picks up new code by itself once it can.';
66
+
59
67
  const SHA_RE = /^[0-9a-f]{40}$/;
60
68
  const sha = (v) => (typeof v === 'string' && SHA_RE.test(v) ? v : null);
61
69
 
@@ -141,6 +149,17 @@ function describeRunner(heartbeat, { nowMs = Date.now(), aliveS = ALIVE_S, lateS
141
149
  holdUntil: Number.isFinite(holdMs) && holdMs > nowMs ? new Date(holdMs).toISOString() : null,
142
150
  };
143
151
 
152
+ // Talking, but it cannot reach the project to fetch new code (task 1005202), so
153
+ // it works forever on the code it has. NOT ok, and it carries the fix. Only
154
+ // while it is still checking in: past that, silence is the truth.
155
+ if (heartbeat.last_event === 'cannot_fetch' && age <= aliveS) {
156
+ return {
157
+ state: 'cannot_fetch', ok: false, ageS: age, age: humanAge(age), ...ident,
158
+ why: 'it cannot fetch new code from GitHub', remedy: CANNOT_FETCH_REMEDY,
159
+ detail: `running old code${where} — it cannot fetch new code from GitHub`,
160
+ };
161
+ }
162
+
144
163
  if (heartbeat.last_event === 'upgrade_exit') {
145
164
  if (age <= RESTART_S) {
146
165
  return {
@@ -99,8 +99,10 @@
99
99
  // stopped (task 1005094): checking in, and its builder pressed Stop on that
100
100
  // device in Settings. Connected, not an alarm, and not the one to describe
101
101
  // while another device is working.
102
- const HEALTH_RANK = { alive: 0, late: 1, idle_config: 2, restarting: 3, stopped: 4, dead: 5, never_seen: 6 };
103
- const HEALTH_DOT = { alive: 'ok', late: 'ok', idle_config: 'warn', restarting: 'info', stopped: 'info', dead: 'danger', never_seen: 'info' };
102
+ // cannot_fetch (task 1005202): talking, but stuck on old code because it cannot
103
+ // reach the project on GitHub. A warning with its fix, like idle_config.
104
+ const HEALTH_RANK = { alive: 0, late: 1, idle_config: 2, cannot_fetch: 2, restarting: 3, stopped: 4, dead: 5, never_seen: 6 };
105
+ const HEALTH_DOT = { alive: 'ok', late: 'ok', idle_config: 'warn', cannot_fetch: 'warn', restarting: 'info', stopped: 'info', dead: 'danger', never_seen: 'info' };
104
106
 
105
107
  // The panel is drawn from both reads at once; each is kept so a write that only
106
108
  // reloads one of them redraws with the other's last answer.
@@ -124,7 +126,7 @@
124
126
  // `project`, and then there is no second layer to check.
125
127
  function readiness(runner, fence, mine) {
126
128
  const h = (runner && runner.health) || {};
127
- const connected = ['alive', 'late', 'restarting', 'idle_config', 'stopped'].includes(h.state);
129
+ const connected = ['alive', 'late', 'restarting', 'idle_config', 'cannot_fetch', 'stopped'].includes(h.state);
128
130
  const goals = (fence && fence.goals) || [];
129
131
  const items = [];
130
132
  if (!runner) {
@@ -138,7 +140,9 @@
138
140
  }
139
141
  if (!connected) items.push({ key: 'claude', state: 'wait', text: 'Claude signed in' });
140
142
  else if (h.account) items.push({ key: 'claude', state: 'ok', text: 'Claude signed in' });
141
- if (connected && h.code && h.code.behind === true) {
143
+ if (h.state === 'cannot_fetch') {
144
+ items.push({ key: 'code', state: 'todo', text: 'Cannot fetch new code', hint: h.remedy || '' });
145
+ } else if (connected && h.code && h.code.behind === true) {
142
146
  items.push({ key: 'code', state: 'wait', text: 'Updating to the latest code', hint: 'It does this by itself.' });
143
147
  } else if (connected && h.code) {
144
148
  items.push({ key: 'code', state: 'ok', text: 'Up to date' });
@@ -388,7 +392,7 @@
388
392
  : 'has picked no goals, so their runner takes no work';
389
393
  const beats = (b.runners || []).map((r) => {
390
394
  const h = r.health || {};
391
- const label = { dead: 'not checking in', late: 'quiet', alive: 'checking in', restarting: 'restarting', stopped: 'stopped on that device', idle_config: `idle on a setting — ${h.remedy || 'see Settings'}` }[h.state] || 'unknown';
395
+ const label = { dead: 'not checking in', late: 'quiet', alive: 'checking in', restarting: 'restarting', stopped: 'stopped on that device', idle_config: `idle on a setting — ${h.remedy || 'see Settings'}`, cannot_fetch: 'stuck on old code — it cannot fetch from GitHub' }[h.state] || 'unknown';
392
396
  return `<span class="ov-fact"><span class="rn-dot rn-dot--sm rn-dot--${HEALTH_DOT[h.state] || 'info'}" aria-hidden="true"></span> ${escapeHtml(h.host || r.host || '')} ${escapeHtml(label)}</span>`;
393
397
  }).join('');
394
398
  const paused = b.owner_paused === true;
@@ -126,6 +126,13 @@
126
126
  sub: `It is on, but ${hold}.` };
127
127
  }
128
128
 
129
+ // Checking in, but pinned to the code it has: it cannot reach the project on
130
+ // GitHub (task 1005202). Said the way "off or asleep" is, with the fix under it.
131
+ if (h.state === 'cannot_fetch') {
132
+ return { status: 'Stuck on old code', act: 'stop', label: 'Stop', tone: 'warn', busy: false, alt: null,
133
+ sub: `It is on, but it cannot fetch new code from GitHub. ${h.remedy || ''}`.trim() };
134
+ }
135
+
129
136
  if (h.state === 'idle_config') {
130
137
  return { status: 'Ready', act: 'stop', label: 'Stop', tone: 'warn', busy: false, alt: null,
131
138
  sub: `It is on, but idle because ${h.why || 'of a setting'}. ${h.remedy || ''}`.trim() };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.25.32",
3
+ "version": "1.25.33",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.25.32",
9
+ "version": "1.25.33",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.25.32",
3
+ "version": "1.25.33",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -11967,5 +11967,11 @@
11967
11967
  "id": "1005308",
11968
11968
  "text": "The old circuit-city painting is gone. The welcome screen and the game's start screens now open on the city in the clouds, coloured for the time of day: deep night with lit windows, a warm dawn, a bright midday."
11969
11969
  }
11970
+ ],
11971
+ "1.25.33": [
11972
+ {
11973
+ "id": "1005202",
11974
+ "text": "A runner that cannot download new code from GitHub now shows \"Stuck on old code\" on its card, with the fix, instead of looking healthy."
11975
+ }
11970
11976
  ]
11971
11977
  }
@@ -308,18 +308,28 @@ function git(args, opts = {}) {
308
308
  }
309
309
 
310
310
  // refreshCheckout — the .cmd's fetch + reset, on the runner's own copy only.
311
- function refreshCheckout(log = () => {}) {
312
- assertRunnerCopy();
313
- const f = git(['fetch', '--quiet', 'origin', 'main']);
314
- if (f.status !== 0) {
315
- // The log is the ONLY place this is said (the card reads check-ins, not fetches), so it
316
- // carries the remedy too — a fetch that fails for a credential reason fails every loop,
317
- // and the runner then works forever on the code it happens to have (task 1005096).
311
+ //
312
+ // Straight fetch failures are counted in `counter` and handed to the supervisor
313
+ // through `env` (AUTOBONGOS_FETCH_FAILURES), which seeds its own count, so its
314
+ // check-in can say `cannot_fetch` and the device's card stops reading healthy
315
+ // (task 1005202). The collaborators are injectable for the test only.
316
+ function refreshCheckout(log = () => {}, {
317
+ git: gitFn = git, assert = assertRunnerCopy, remedy = githubRemedy, counter = null, env = null,
318
+ } = {}) {
319
+ assert();
320
+ const f = gitFn(['fetch', '--quiet', 'origin', 'main']);
321
+ const fetched = f.status === 0;
322
+ if (counter) counter.fetchFailures = fetched ? 0 : (counter.fetchFailures || 0) + 1;
323
+ if (counter && env) env.AUTOBONGOS_FETCH_FAILURES = String(counter.fetchFailures);
324
+ if (!fetched) {
325
+ // A fetch that fails for a credential reason fails every loop, and the runner then
326
+ // works forever on the code it happens to have (task 1005096), so the log carries
327
+ // the remedy, and the count above carries the fact to the card.
318
328
  log(`git fetch failed — running the code already here (${String(f.stderr || '').trim().slice(0, 200)})`);
319
- log(` if that keeps happening, it is not transient: ${githubRemedy()}`);
329
+ log(` if that keeps happening, it is not transient: ${remedy()}`);
320
330
  return false;
321
331
  }
322
- const r = git(['reset', '--quiet', '--hard', 'origin/main']);
332
+ const r = gitFn(['reset', '--quiet', '--hard', 'origin/main']);
323
333
  if (r.status !== 0) { log(`git reset failed (${String(r.stderr || '').trim().slice(0, 200)})`); return false; }
324
334
  return true;
325
335
  }
@@ -821,6 +831,7 @@ async function runCommand({ paths = hostPaths() } = {}) {
821
831
  const log = makeLogger(paths, { echo: !!process.stdout.isTTY });
822
832
  const env = supervisorEnv(process.env, paths, dirName);
823
833
  const state = { child: null, stopping: false };
834
+ const fetchCounter = { fetchFailures: 0 }; // straight failed fetches, handed to the supervisor (task 1005202)
824
835
  const keepAwake = makeKeepAwake();
825
836
  const selfSha = fileSha(__filename);
826
837
  writeLock(paths);
@@ -836,7 +847,7 @@ async function runCommand({ paths = hostPaths() } = {}) {
836
847
  rotateLog(paths);
837
848
  const out = await runLoop({}, {
838
849
  log, shouldStop: () => state.stopping,
839
- refresh: () => refreshCheckout(log),
850
+ refresh: () => refreshCheckout(log, { counter: fetchCounter, env }),
840
851
  ensureDeps: () => ensureDeps(paths, log),
841
852
  spawnSupervisor: () => spawnSupervisorReal({ paths, env, log, keepAwake, state }),
842
853
  selfChanged: () => fileSha(__filename) !== selfSha,
@@ -924,5 +935,5 @@ module.exports = {
924
935
  windowsRunArgv, windowsRunCommand, windowsStartSpawn, legacyBootTaskInstalled, launchdPlist,
925
936
  stopRunner, stopReport, exchangeCode, claudeState, waitForCheckIn,
926
937
  signIn, cleanBase, install, removeStartEntry, installedState, status, digestArgv,
927
- githubRemedy, GITHUB_REMEDY_GENERIC,
938
+ githubRemedy, GITHUB_REMEDY_GENERIC, refreshCheckout,
928
939
  };
@@ -511,8 +511,19 @@ const MAX_CLAIM_ATTEMPTS = 5;
511
511
  // guess that is wrong costs one wasted spawn per half hour, not one per wake.
512
512
  const LIMIT_UNKNOWN_RESET_MS = 30 * 60 * 1000;
513
513
 
514
+ // How many straight failures to reach origin before the check-in says
515
+ // `cannot_fetch` (task 1005202). A blip fails once or twice and clears; a
516
+ // credential fault (the active GitHub account cannot see the project) fails
517
+ // every time, forever, and the runner then works on whatever code it has. The
518
+ // polls inside a wait are a minute apart, so this is a few minutes of failing.
519
+ const CANNOT_FETCH_AFTER = 3;
520
+
521
+ // fetchFailures starts from the host wrapper's own count (AUTOBONGOS_FETCH_FAILURES,
522
+ // set by autobongos-host.js), so a restart does not reset the card to healthy.
514
523
  function newRunnerState() {
515
- return { setAside: new Map(), queueGate: null, limitUntil: null, diskHead: null, mainHead: null, landSweepAt: null };
524
+ const seeded = Number.parseInt(process.env.AUTOBONGOS_FETCH_FAILURES, 10);
525
+ return { setAside: new Map(), queueGate: null, limitUntil: null, diskHead: null, mainHead: null, landSweepAt: null,
526
+ fetchFailures: Number.isInteger(seeded) && seeded > 0 ? seeded : 0 };
516
527
  }
517
528
  const RUNNER_STATE = newRunnerState();
518
529
 
@@ -920,9 +931,23 @@ function heartbeatIdentity(deps = {}) {
920
931
  return out;
921
932
  }
922
933
 
934
+ // cannotFetchEvent — what a beat says once the runner has failed to reach the
935
+ // project CANNOT_FETCH_AFTER times in a row (task 1005202). It replaces the idle
936
+ // words ("waiting", "loop", nothing), which would otherwise read as healthy. It
937
+ // never hides a restart, a setting holding the runner, or the task in its hands:
938
+ // each of those is its own truth about this minute.
939
+ function cannotFetchEvent(fields = {}, state = RUNNER_STATE) {
940
+ if (!state || !((state.fetchFailures || 0) >= CANNOT_FETCH_AFTER)) return fields.last_event;
941
+ if (fields.working_task_id) return fields.last_event;
942
+ const ev = fields.last_event;
943
+ if (ev === 'upgrade_exit' || (typeof ev === 'string' && ev.startsWith('config_idle:'))) return ev;
944
+ return 'cannot_fetch';
945
+ }
946
+
923
947
  async function postHeartbeat(fields = {}, deps = {}) {
924
948
  try {
925
949
  const api = deps.api || await (require('./cli-lib').cliClient)();
950
+ const lastEvent = cannotFetchEvent(fields, deps.state || RUNNER_STATE);
926
951
  await api.autonomy.postAutonomyHeartbeat({
927
952
  body: {
928
953
  host: os.hostname(),
@@ -930,6 +955,7 @@ async function postHeartbeat(fields = {}, deps = {}) {
930
955
  started_at: RUNNER_STARTED_AT,
931
956
  ...heartbeatIdentity(deps),
932
957
  ...fields,
958
+ ...(lastEvent ? { last_event: lastEvent } : {}),
933
959
  },
934
960
  });
935
961
  return true;
@@ -1165,11 +1191,17 @@ async function refreshCheckout(deps = {}) {
1165
1191
  // The head of origin/main, or null. `git ls-remote` rather than `fetch`: it moves
1166
1192
  // no refs and writes nothing, so polling it every few minutes cannot interfere
1167
1193
  // with a worker building in a tree beside it.
1194
+ //
1195
+ // Every call also counts toward `fetchFailures` (task 1005202): a success clears
1196
+ // it, anything else adds one, so the heartbeat can say the runner cannot reach
1197
+ // the project instead of reading healthy on code it can never update.
1168
1198
  async function runFetchHead(deps = {}) {
1169
1199
  const runCmd = deps.run || run;
1170
- const r = await runCmd('git', ['ls-remote', 'origin', 'refs/heads/main']);
1171
- if (!r.ok) return null;
1172
- const m = /^([0-9a-f]{40})/.exec(String(r.stdout).trim());
1200
+ const st = deps.state || RUNNER_STATE;
1201
+ let r = null;
1202
+ try { r = await runCmd('git', ['ls-remote', 'origin', 'refs/heads/main']); } catch (_) { r = null; }
1203
+ const m = r && r.ok ? /^([0-9a-f]{40})/.exec(String(r.stdout).trim()) : null;
1204
+ st.fetchFailures = m ? 0 : (st.fetchFailures || 0) + 1;
1173
1205
  return m ? m[1] : null;
1174
1206
  }
1175
1207
 
@@ -1395,6 +1427,6 @@ module.exports = {
1395
1427
  worktreeName, readFence, gradeLedgerPath, gradeCapReader, reconcileLeftoverClaims, pollSignals, waitOrJump, forever, CLAIM_PREFIX, codeDrifted, loadedHead, UPGRADE_EXIT_CODE, MIN_UPTIME_BEFORE_UPGRADE_MS,
1396
1428
  heartbeatPath, writeHeartbeat, readHeartbeat, pidAlive, anotherRunnerIsAlive, HEARTBEAT_STALE_MS,
1397
1429
  isRunnerTree, RUNNER_TREE_RE, postHeartbeat, claudeAccountLabel, RUNNER_STARTED_AT, failureReason,
1398
- newRunnerState, loadedFiles, supervisorTouched, refreshCheckout, LIMIT_UNKNOWN_RESET_MS, QUEUE_GATED_EXIT, CLAIM_SET_ASIDE_MS, MAX_CLAIM_ATTEMPTS, owedTaskIds,
1430
+ newRunnerState, runFetchHead, cannotFetchEvent, CANNOT_FETCH_AFTER, loadedFiles, supervisorTouched, refreshCheckout, LIMIT_UNKNOWN_RESET_MS, QUEUE_GATED_EXIT, CLAIM_SET_ASIDE_MS, MAX_CLAIM_ATTEMPTS, owedTaskIds,
1399
1431
  readRunLogRows, recheckPendingLands, LAND_SWEEP_MS,
1400
1432
  };
package/src/module-api.js CHANGED
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
81
81
  // MAJOR (see allowBoxScope below): passes the request through untouched.
82
82
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
83
83
 
84
- const CORE_VERSION = '1.25.32'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
84
+ const CORE_VERSION = '1.25.33'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
85
85
 
86
86
  // A namespaced logger so a module's log lines are attributable + consistent.
87
87
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -0,0 +1,123 @@
1
+ // tests/autobongos_cannot_fetch.mjs — a runner that can never fetch says so on
2
+ // its card (task 1005202).
3
+ //
4
+ // Found on a real Mac: two GitHub accounts were signed in, the active one could
5
+ // not see the private project, and every `git fetch` 404'd. The runner checked in
6
+ // as healthy and worked on the code it happened to clone, forever; only its own
7
+ // log said otherwise. These pin the signal end to end: the host and the
8
+ // supervisor count straight fetch failures, past CANNOT_FETCH_AFTER the check-in
9
+ // says `cannot_fetch`, the verdict is not ok and carries the remedy, and the
10
+ // device's button and checklist stop reading healthy. One good fetch clears it.
11
+ //
12
+ // DB-free, no network. Run: node --test tests/autobongos_cannot_fetch.mjs
13
+
14
+ import { test } from 'node:test';
15
+ import assert from 'node:assert/strict';
16
+ import path from 'node:path';
17
+ import { createRequire } from 'node:module';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
21
+ const req = createRequire(import.meta.url);
22
+ const runner = req(path.join(ROOT, 'scripts/bongos/autobongos-run.js'));
23
+ const host = req(path.join(ROOT, 'scripts/bongos/autobongos-host.js'));
24
+ const health = req(path.join(ROOT, 'modules/autonomy/runner-health.js'));
25
+ const RB = req(path.join(ROOT, 'modules/hall-ui/public/runner-button.js'));
26
+
27
+ const A = 'a'.repeat(40);
28
+ const NOW = Date.parse('2026-11-14T12:00:00.000Z');
29
+ const agoS = (s) => new Date(NOW - s * 1000).toISOString();
30
+
31
+ const failing = async () => ({ ok: false, stdout: '', stderr: 'remote: Repository not found.' });
32
+ const working = async () => ({ ok: true, stdout: `${A}\trefs/heads/main\n` });
33
+
34
+ function recorder() {
35
+ const bodies = [];
36
+ const api = { autonomy: { postAutonomyHeartbeat: async ({ body }) => { bodies.push(body); return { ok: true }; } } };
37
+ return { bodies, api };
38
+ }
39
+
40
+ test('the supervisor counts straight failures to reach main, and one success clears the count', async () => {
41
+ const state = runner.newRunnerState();
42
+ assert.equal(state.fetchFailures, 0);
43
+ for (let i = 1; i <= 3; i++) {
44
+ assert.equal(await runner.runFetchHead({ run: failing, state }), null);
45
+ assert.equal(state.fetchFailures, i);
46
+ }
47
+ await runner.runFetchHead({ run: async () => { throw new Error('spawn git ENOENT'); }, state });
48
+ assert.equal(state.fetchFailures, 4, 'a git that cannot even start is a failure too');
49
+ assert.equal(await runner.runFetchHead({ run: working, state }), A);
50
+ assert.equal(state.fetchFailures, 0);
51
+ });
52
+
53
+ test('past the threshold the check-in says cannot_fetch instead of a healthy idle', async () => {
54
+ const { bodies, api } = recorder();
55
+ const state = runner.newRunnerState();
56
+ const deps = { api, state, claudeAccountLabel: () => null };
57
+ for (let i = 0; i < runner.CANNOT_FETCH_AFTER - 1; i++) await runner.runFetchHead({ run: failing, state });
58
+ await runner.postHeartbeat({ last_event: 'waiting' }, deps);
59
+ assert.equal(bodies.at(-1).last_event, 'waiting', 'a blip below the threshold is not an alarm');
60
+
61
+ await runner.runFetchHead({ run: failing, state });
62
+ for (const ev of ['waiting', 'loop', 'nothing_claimable', undefined]) {
63
+ await runner.postHeartbeat(ev ? { last_event: ev } : {}, deps);
64
+ assert.equal(bodies.at(-1).last_event, 'cannot_fetch', String(ev));
65
+ }
66
+ // What it must never hide: a restart, a setting holding it, or the task in its hands.
67
+ await runner.postHeartbeat({ last_event: 'upgrade_exit' }, deps);
68
+ assert.equal(bodies.at(-1).last_event, 'upgrade_exit');
69
+ await runner.postHeartbeat({ last_event: 'config_idle:no_goals' }, deps);
70
+ assert.equal(bodies.at(-1).last_event, 'config_idle:no_goals');
71
+ await runner.postHeartbeat({ last_event: 'claimed', working_task_id: 7 }, deps);
72
+ assert.equal(bodies.at(-1).last_event, 'claimed');
73
+
74
+ await runner.runFetchHead({ run: working, state });
75
+ await runner.postHeartbeat({ last_event: 'waiting' }, deps);
76
+ assert.equal(bodies.at(-1).last_event, 'waiting', 'one good fetch and the card reads healthy again');
77
+ });
78
+
79
+ test('the host counts its own failed fetches and hands the count to the supervisor it starts', () => {
80
+ const env = {};
81
+ const counter = { fetchFailures: 0 };
82
+ const bad = () => ({ status: 128, stdout: '', stderr: 'fatal: repository not found' });
83
+ const good = () => ({ status: 0, stdout: '', stderr: '' });
84
+ const opts = { git: bad, assert: () => {}, remedy: () => 'switch account', counter, env };
85
+ assert.equal(host.refreshCheckout(() => {}, opts), false);
86
+ assert.equal(host.refreshCheckout(() => {}, opts), false);
87
+ assert.equal(counter.fetchFailures, 2);
88
+ assert.equal(env.AUTOBONGOS_FETCH_FAILURES, '2');
89
+ // The supervisor it starts begins from that count, so the card does not wait
90
+ // for a fresh run of failures after every restart.
91
+ const saved = process.env.AUTOBONGOS_FETCH_FAILURES;
92
+ process.env.AUTOBONGOS_FETCH_FAILURES = env.AUTOBONGOS_FETCH_FAILURES;
93
+ try { assert.equal(runner.newRunnerState().fetchFailures, 2); } finally {
94
+ if (saved === undefined) delete process.env.AUTOBONGOS_FETCH_FAILURES; else process.env.AUTOBONGOS_FETCH_FAILURES = saved;
95
+ }
96
+ assert.equal(host.refreshCheckout(() => {}, { ...opts, git: good }), true);
97
+ assert.equal(counter.fetchFailures, 0);
98
+ assert.equal(env.AUTOBONGOS_FETCH_FAILURES, '0');
99
+ });
100
+
101
+ test('the verdict is cannot_fetch: not ok, and it carries the remedy', () => {
102
+ const d = health.describeRunner({ host: 'mac-mini', mode: 'full', last_event: 'cannot_fetch', last_seen_at: agoS(30) }, { nowMs: NOW });
103
+ assert.equal(d.state, 'cannot_fetch');
104
+ assert.equal(d.ok, false, 'a runner pinned to old code is not healthy');
105
+ assert.match(d.detail, /mac-mini/);
106
+ assert.match(d.remedy, /gh auth switch/);
107
+ // Silence still wins: a runner nobody has heard from cannot vouch for anything.
108
+ assert.equal(health.describeRunner({ host: 'mac-mini', last_event: 'cannot_fetch', last_seen_at: agoS(3 * 3600) }, { nowMs: NOW }).state, 'dead');
109
+ });
110
+
111
+ test("the device's button says it is stuck on old code, and names the fix", () => {
112
+ const fence = { enabled: true, scope: { goals: [1] }, project: { enabled: true, goals: [{ goal_id: 1 }] } };
113
+ const runnerRow = {
114
+ host: 'mac-mini', run_requested: true, run_changed_at: agoS(3600), last_seen_at: agoS(30),
115
+ health: { state: 'cannot_fetch', host: 'mac-mini', age: '30s', remedy: 'Run gh auth switch on it.' },
116
+ };
117
+ const b = RB.buttonState({ runner: runnerRow, fence, nowMs: NOW });
118
+ assert.equal(b.status, 'Stuck on old code');
119
+ assert.equal(b.tone, 'warn');
120
+ assert.equal(b.act, 'stop');
121
+ assert.match(b.sub, /cannot fetch new code/);
122
+ assert.match(b.sub, /gh auth switch/);
123
+ });