@bongos/core 1.24.94 → 1.24.96

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.
Files changed (56) hide show
  1. package/.bongos-core.json +143 -43
  2. package/clients/bongos-client/README.md +1 -1
  3. package/clients/bongos-client/bongos-client.global.js +2 -0
  4. package/clients/bongos-client/index.cjs +2 -0
  5. package/clients/bongos-client/index.d.ts +2 -0
  6. package/clients/bongos-client/index.mjs +2 -0
  7. package/docs/adr/0405-each-character-has-one-colour-and-the-visor-wears-it.md +31 -0
  8. package/docs/adr/README.md +1 -0
  9. package/docs/api/openapi.json +49 -1
  10. package/docs/api-reference.md +3 -2
  11. package/docs/copy-inventory.md +12 -12
  12. package/docs/copy-registry.json +12 -12
  13. package/docs/design/characters/v2/artisan-visor.png +0 -0
  14. package/docs/design/characters/v2/artisan.png +0 -0
  15. package/docs/design/characters/v2/artist-visor.png +0 -0
  16. package/docs/design/characters/v2/artist.png +0 -0
  17. package/docs/design/characters/v2/draw-characters.js +465 -0
  18. package/docs/design/characters/v2/patron-visor.png +0 -0
  19. package/docs/design/characters/v2/patron.png +0 -0
  20. package/docs/design/characters/v2/sheet.html +114 -0
  21. package/docs/module-api-changelog.md +4 -0
  22. package/docs/page-inventory.json +2 -0
  23. package/docs/page-readings.json +35 -35
  24. package/modules/autonomy/db.js +48 -0
  25. package/modules/autonomy/routes/autonomy.js +15 -0
  26. package/modules/government/catalog.js +1 -1
  27. package/modules/hall-ui/public/characters/artisan-face.png +0 -0
  28. package/modules/hall-ui/public/characters/artisan-visor.png +0 -0
  29. package/modules/hall-ui/public/characters/artisan.png +0 -0
  30. package/modules/hall-ui/public/characters/artist-face.png +0 -0
  31. package/modules/hall-ui/public/characters/artist-visor.png +0 -0
  32. package/modules/hall-ui/public/characters/artist.png +0 -0
  33. package/modules/hall-ui/public/characters/patron-face.png +0 -0
  34. package/modules/hall-ui/public/characters/patron-visor.png +0 -0
  35. package/modules/hall-ui/public/characters/patron.png +0 -0
  36. package/modules/hall-ui/public/craft-mode.js +15 -4
  37. package/modules/hall-ui/public/home-city-draw.js +24 -2
  38. package/modules/hall-ui/public/home-city.css +15 -0
  39. package/modules/hall-ui/public/home-city.js +3 -1
  40. package/modules/hall-ui/public/home-runner.js +248 -0
  41. package/modules/hall-ui/public/index.html +8 -5
  42. package/modules/hall-ui/public/style.css +18 -5
  43. package/modules/hall-ui/public/visor.css +47 -5
  44. package/modules/hall-ui/public/visor.js +71 -5
  45. package/modules/hall-ui/records/characters.md +19 -0
  46. package/modules/hall-ui/records/home-runner.md +19 -0
  47. package/modules/hall-ui/records/visor.md +7 -0
  48. package/package-lock.json +2 -2
  49. package/package.json +1 -1
  50. package/release-notes.json +12 -0
  51. package/scripts/hall-preview/server.js +4 -0
  52. package/src/module-api.js +1 -1
  53. package/tests/autonomy_runner_goal_stats.mjs +92 -0
  54. package/tests/character_creation.mjs +44 -7
  55. package/tests/hall_visor.mjs +48 -2
  56. package/tests/home_runner.mjs +115 -0
@@ -12,7 +12,7 @@
12
12
  "surface": "builders",
13
13
  "title": "Studio",
14
14
  "states_read": ["pick","describe","make","waiting","result","result-own","failed","balance","balance-all","out"],
15
- "files_hash": "4d3ddcf70c72e2d8",
15
+ "files_hash": "bfd352bc483d41c2",
16
16
  "reading_hash": "d9e0e85bcee4aff4",
17
17
  "counts": {"lines":106,"placed":41,"shared":7,"unplaced":58},
18
18
  "omitted": {"data":17},
@@ -131,7 +131,7 @@
131
131
  "surface": "builders",
132
132
  "title": "Repo Atlas",
133
133
  "states_read": ["map","truth","detail","index","out"],
134
- "files_hash": "5ccfecad1f11842c",
134
+ "files_hash": "133220425bccce34",
135
135
  "reading_hash": "6934dead335b2507",
136
136
  "counts": {"lines":25,"placed":14,"shared":7,"unplaced":4},
137
137
  "omitted": {"data":5},
@@ -169,7 +169,7 @@
169
169
  "surface": "builders",
170
170
  "title": "Blockers",
171
171
  "states_read": ["default"],
172
- "files_hash": "3edd4c9a8287965e",
172
+ "files_hash": "a819c6030cf15a6e",
173
173
  "reading_hash": "60d73d854ced958a",
174
174
  "counts": {"lines":36,"placed":17,"shared":7,"unplaced":12},
175
175
  "omitted": {"data":51},
@@ -218,7 +218,7 @@
218
218
  "surface": "builders",
219
219
  "title": "Board Room",
220
220
  "states_read": ["room","out","sitting-meta","objection-form"],
221
- "files_hash": "797b50d46b74f122",
221
+ "files_hash": "649b8241ac4ad473",
222
222
  "reading_hash": "0260b797b8dc1526",
223
223
  "counts": {"lines":52,"placed":21,"shared":7,"unplaced":24},
224
224
  "omitted": {"data":17},
@@ -283,7 +283,7 @@
283
283
  "surface": "builders",
284
284
  "title": "The city",
285
285
  "states_read": ["city","fullscreen","focus","pins","living","card","card-ask"],
286
- "files_hash": "d9dd949934a5b719",
286
+ "files_hash": "0367a0e57f2fcfb1",
287
287
  "reading_hash": "277096d903879a57",
288
288
  "counts": {"lines":26,"placed":9,"shared":6,"unplaced":11},
289
289
  "omitted": {"data":7},
@@ -322,7 +322,7 @@
322
322
  "surface": "builders",
323
323
  "title": "Collab",
324
324
  "states_read": ["board","out"],
325
- "files_hash": "6bd34870e07488b0",
325
+ "files_hash": "6e58d1336b59ed6d",
326
326
  "reading_hash": "e6452bf92231656f",
327
327
  "counts": {"lines":82,"placed":36,"shared":6,"unplaced":40},
328
328
  "omitted": {"data":23},
@@ -417,7 +417,7 @@
417
417
  "surface": "builders",
418
418
  "title": "Commands",
419
419
  "states_read": ["in","out"],
420
- "files_hash": "19b25bb84e65c93a",
420
+ "files_hash": "6c7abde80e1c7a74",
421
421
  "reading_hash": "ecc452689a8ee9d7",
422
422
  "counts": {"lines":71,"placed":49,"shared":7,"unplaced":15},
423
423
  "omitted": {"data":177},
@@ -501,7 +501,7 @@
501
501
  "surface": "builders",
502
502
  "title": "Copy desk",
503
503
  "states_read": ["queue","out","search","composer","closer","proposer"],
504
- "files_hash": "3598688fde8da13f",
504
+ "files_hash": "1f6a0864849d0263",
505
505
  "reading_hash": "3819ef6fa8f6fb7c",
506
506
  "counts": {"lines":72,"placed":36,"shared":7,"unplaced":29},
507
507
  "omitted": {"data":94},
@@ -586,7 +586,7 @@
586
586
  "surface": "builders",
587
587
  "title": "Deploy",
588
588
  "states_read": ["work","out"],
589
- "files_hash": "f66bb9f77832d5fe",
589
+ "files_hash": "b534665c0eb1a176",
590
590
  "reading_hash": "3770e8b2fa56bee1",
591
591
  "counts": {"lines":14,"placed":5,"shared":7,"unplaced":2},
592
592
  "omitted": {"data":5},
@@ -613,7 +613,7 @@
613
613
  "surface": "builders",
614
614
  "title": "The sky",
615
615
  "states_read": ["tour","sky","wells","ledger","full","full-back","out"],
616
- "files_hash": "f54ebe767edaf0c8",
616
+ "files_hash": "9de2854d844d990f",
617
617
  "reading_hash": "4a879397a84631e3",
618
618
  "counts": {"lines":50,"placed":25,"shared":6,"unplaced":19},
619
619
  "omitted": {"data":10},
@@ -676,7 +676,7 @@
676
676
  "surface": "builders",
677
677
  "title": "Goals",
678
678
  "states_read": ["list","filters","cards","detail","dependencies","out","uat","uat-panel","author"],
679
- "files_hash": "e0237363305a997e",
679
+ "files_hash": "83d51fe0a0cbdc81",
680
680
  "reading_hash": "13a5d07b3804b627",
681
681
  "counts": {"lines":187,"placed":61,"shared":7,"unplaced":119},
682
682
  "omitted": {"data":315},
@@ -876,7 +876,7 @@
876
876
  "surface": "builders",
877
877
  "title": "Government",
878
878
  "states_read": ["constitution","out","permissions","board","constitution-tab"],
879
- "files_hash": "cee3223b05313772",
879
+ "files_hash": "67e03165f6801e25",
880
880
  "reading_hash": "37c6878d60447fd6",
881
881
  "counts": {"lines":65,"placed":27,"shared":7,"unplaced":31},
882
882
  "omitted": {"data":276},
@@ -954,8 +954,8 @@
954
954
  "surface": "builders",
955
955
  "title": "Builders' Hall",
956
956
  "states_read": ["home","out","founding","genesis","decide","demo"],
957
- "files_hash": "78dcb0fa10127d8c",
958
- "reading_hash": "0ede747bdc35bbb5",
957
+ "files_hash": "36c7410d9c8aeae0",
958
+ "reading_hash": "45468116976bf674",
959
959
  "counts": {"lines":32,"placed":12,"shared":8,"unplaced":12},
960
960
  "omitted": {"data":5},
961
961
  "notes": ["founding: an action failed (page.focus: Timeout 6000ms exceeded.); read as it stood"],
@@ -977,7 +977,7 @@
977
977
  {"key":"L0015","section":"Needs your attention13","text":"Ideas awaiting triage","placement":"placed","file":"modules/hall-ui/public/builders.js","line":320,"string_id":"e181637c5a5e"},
978
978
  {"key":"L0016","section":"Needs your attention13","text":"Requires project {…} fix","placement":"unplaced"},
979
979
  {"key":"L0017","section":"footer","text":"Sign out","placement":"shared","file":"modules/hall-ui/public/shell.js","line":514,"string_id":"1883ad7f07fc"},
980
- {"key":"L0018","section":"Needs your attention13","text":"core 1.24.92","placement":"unplaced"},
980
+ {"key":"L0018","section":"Needs your attention13","text":"core 1.24.94","placement":"unplaced"},
981
981
  {"key":"L0019","section":"Needs your attention13","text":"Skip","placement":"unplaced"},
982
982
  {"key":"L0020","section":"Needs your attention13","text":"Built on","placement":"placed","file":"modules/hall-ui/public/welcome-boot.js","line":183,"string_id":"fef59346ffee"},
983
983
  {"key":"L0021","section":"Needs your attention13","text":"cloudbongos.com","placement":"unplaced"},
@@ -999,7 +999,7 @@
999
999
  "surface": "builders",
1000
1000
  "title": "Idea",
1001
1001
  "states_read": ["record","missing"],
1002
- "files_hash": "d657a33a67bc8f3b",
1002
+ "files_hash": "b19a972d3190b27c",
1003
1003
  "reading_hash": "f7d644748c87e426",
1004
1004
  "counts": {"lines":52,"placed":25,"shared":8,"unplaced":19},
1005
1005
  "omitted": {"data":25},
@@ -1064,7 +1064,7 @@
1064
1064
  "surface": "builders",
1065
1065
  "title": "Ideas",
1066
1066
  "states_read": ["compose","full","develop"],
1067
- "files_hash": "64316b241e5de273",
1067
+ "files_hash": "65f50755fa5d2c77",
1068
1068
  "reading_hash": "a62c05dcef3bf87f",
1069
1069
  "counts": {"lines":68,"placed":34,"shared":7,"unplaced":27},
1070
1070
  "omitted": {"data":118},
@@ -1145,7 +1145,7 @@
1145
1145
  "surface": "builders",
1146
1146
  "title": "Market Watch",
1147
1147
  "states_read": ["desk","settings","out"],
1148
- "files_hash": "c19fa516071448c1",
1148
+ "files_hash": "2685309696818881",
1149
1149
  "reading_hash": "30512dda207b5ea6",
1150
1150
  "counts": {"lines":23,"placed":11,"shared":7,"unplaced":5},
1151
1151
  "omitted": {"data":5},
@@ -1181,7 +1181,7 @@
1181
1181
  "surface": "builders",
1182
1182
  "title": "Market Watch",
1183
1183
  "states_read": ["dossier","out","propose","correct","filtered"],
1184
- "files_hash": "9f574f36d050ecb4",
1184
+ "files_hash": "4dd2ab01e84c217f",
1185
1185
  "reading_hash": "129bff28c4e4ed6d",
1186
1186
  "counts": {"lines":24,"placed":12,"shared":7,"unplaced":5},
1187
1187
  "omitted": {"data":5},
@@ -1218,7 +1218,7 @@
1218
1218
  "surface": "builders",
1219
1219
  "title": "Market Watch rules",
1220
1220
  "states_read": ["in","scoring","names","propose","out"],
1221
- "files_hash": "04757fe324f6331f",
1221
+ "files_hash": "526c2149de84f633",
1222
1222
  "reading_hash": "ca0c62e8dce51c8b",
1223
1223
  "counts": {"lines":36,"placed":12,"shared":7,"unplaced":17},
1224
1224
  "omitted": {"data":20},
@@ -1267,7 +1267,7 @@
1267
1267
  "surface": "builders",
1268
1268
  "title": "Modules",
1269
1269
  "states_read": ["browse","search-empty","lens","details","out"],
1270
- "files_hash": "bb880eb91b5bffa2",
1270
+ "files_hash": "574e1738477d342c",
1271
1271
  "reading_hash": "6a12e9306bba5622",
1272
1272
  "counts": {"lines":59,"placed":29,"shared":7,"unplaced":23},
1273
1273
  "omitted": {"data":107},
@@ -1339,7 +1339,7 @@
1339
1339
  "surface": "builders",
1340
1340
  "title": "Money",
1341
1341
  "states_read": ["money","hosting","out"],
1342
- "files_hash": "eab833a98ae63570",
1342
+ "files_hash": "816343bf158f836b",
1343
1343
  "reading_hash": "e355d5466b123e07",
1344
1344
  "counts": {"lines":83,"placed":28,"shared":7,"unplaced":48},
1345
1345
  "omitted": {"data":27},
@@ -1435,7 +1435,7 @@
1435
1435
  "surface": "builders",
1436
1436
  "title": "My agents",
1437
1437
  "states_read": ["desk","factory","saved","what-it-knows","speciality-walk","speciality-forms","nobody"],
1438
- "files_hash": "f9fd9939a04db900",
1438
+ "files_hash": "3e69046bdd44b756",
1439
1439
  "reading_hash": "5347e0496b38727d",
1440
1440
  "counts": {"lines":100,"placed":33,"shared":7,"unplaced":60},
1441
1441
  "omitted": {"data":38},
@@ -1548,7 +1548,7 @@
1548
1548
  "surface": "builders",
1549
1549
  "title": "Primer",
1550
1550
  "states_read": ["in","toc-open","jump","out"],
1551
- "files_hash": "bab434369863a9c0",
1551
+ "files_hash": "da0299600036bb6f",
1552
1552
  "reading_hash": "85790bd421e5c387",
1553
1553
  "counts": {"lines":487,"placed":57,"shared":7,"unplaced":423},
1554
1554
  "omitted": {"data":18},
@@ -2048,7 +2048,7 @@
2048
2048
  "surface": "builders",
2049
2049
  "title": "Profile",
2050
2050
  "states_read": ["own","works","activity","yours","activity-artist","activity-artist-ideas","other","artist","artist-ideas","out"],
2051
- "files_hash": "a1e1acb3badc9e27",
2051
+ "files_hash": "88fc36c8238827bf",
2052
2052
  "reading_hash": "3ff8d703d17e8c2a",
2053
2053
  "counts": {"lines":232,"placed":84,"shared":7,"unplaced":141},
2054
2054
  "omitted": {"data":263},
@@ -2293,7 +2293,7 @@
2293
2293
  "surface": "builders",
2294
2294
  "title": "Project settings",
2295
2295
  "states_read": ["default"],
2296
- "files_hash": "bf4ce03972123088",
2296
+ "files_hash": "dec5d7d8359c913d",
2297
2297
  "reading_hash": "283df27d0d5bfc1b",
2298
2298
  "counts": {"lines":43,"placed":25,"shared":7,"unplaced":11},
2299
2299
  "omitted": {"data":33},
@@ -2349,7 +2349,7 @@
2349
2349
  "surface": "builders",
2350
2350
  "title": "Builders",
2351
2351
  "states_read": ["roster","artists","search","out"],
2352
- "files_hash": "c4f41160afeaf87f",
2352
+ "files_hash": "eada2a8ff8a184ab",
2353
2353
  "reading_hash": "665ac2e36339f5d3",
2354
2354
  "counts": {"lines":56,"placed":17,"shared":7,"unplaced":32},
2355
2355
  "omitted": {"data":137},
@@ -2418,7 +2418,7 @@
2418
2418
  "surface": "builders",
2419
2419
  "title": "Options",
2420
2420
  "states_read": ["account","access","preferences","install-reports","sound","update"],
2421
- "files_hash": "0f1849a1dab98d9a",
2421
+ "files_hash": "9ee8d40cdee36f53",
2422
2422
  "reading_hash": "898a13fda24c3c90",
2423
2423
  "counts": {"lines":162,"placed":104,"shared":7,"unplaced":51},
2424
2424
  "omitted": {"data":23},
@@ -2593,7 +2593,7 @@
2593
2593
  "surface": "builders",
2594
2594
  "title": "The street",
2595
2595
  "states_read": ["street","fullscreen","card","focus","here","inside-rules","inside-disputes","inside-people","inside-planning","inside-automation","inside-review","inside-release","inside-treasury","floor","amend","district","desk","desk-facts","desk-quiet","desk-people-builders","desk-people-tier","desk-disputes","desk-automation-goals","desk-release-core","desk-rules","desk-rules-category","desk-planning-priorities","desk-planning-ideas","desk-planning-blockers","pins","pins-pager","living","deck","deck-treasury","sitting","object","voted","admission","settled","blocker","blocker-more","report","report-more","core","core-more","recheck","recheck-more","idea","idea-more","queue","queue-more","automation","automation-more"],
2596
- "files_hash": "6768ffb4c12c21ac",
2596
+ "files_hash": "b034bee92146665b",
2597
2597
  "reading_hash": "e788e6bf4be8d68c",
2598
2598
  "counts": {"lines":325,"placed":54,"shared":6,"unplaced":265},
2599
2599
  "omitted": {"data":240},
@@ -2931,7 +2931,7 @@
2931
2931
  "surface": "builders",
2932
2932
  "title": "Studio",
2933
2933
  "states_read": ["home","out","flow","flow-pick","status","words-offer","words-edit","reviews","full","full-back","approvals","approvals-before","approvals-phone","approvals-note","approved","sent-back","approvals-page"],
2934
- "files_hash": "97c8fcdb1e58d08c",
2934
+ "files_hash": "c01cf7220bebfb79",
2935
2935
  "reading_hash": "c4e3b4188c1db755",
2936
2936
  "counts": {"lines":51,"placed":29,"shared":7,"unplaced":15},
2937
2937
  "omitted": {"data":23},
@@ -2995,7 +2995,7 @@
2995
2995
  "surface": "builders",
2996
2996
  "title": "Task",
2997
2997
  "states_read": ["record","from-goal","missing"],
2998
- "files_hash": "b839c7b452afbfbe",
2998
+ "files_hash": "91a9c833cdebb28b",
2999
2999
  "reading_hash": "8dd803b6934e6388",
3000
3000
  "counts": {"lines":151,"placed":46,"shared":9,"unplaced":96},
3001
3001
  "omitted": {"data":111},
@@ -3159,7 +3159,7 @@
3159
3159
  "surface": "builders",
3160
3160
  "title": "Your thinking",
3161
3161
  "states_read": ["tour","sky","list","simple","full-idea","full","full-list","full-back","out"],
3162
- "files_hash": "3cce7079079c7782",
3162
+ "files_hash": "96c460b71d47125f",
3163
3163
  "reading_hash": "b73f848b46b0f9b8",
3164
3164
  "counts": {"lines":50,"placed":25,"shared":6,"unplaced":19},
3165
3165
  "omitted": {"data":10},
@@ -3222,7 +3222,7 @@
3222
3222
  "surface": "builders",
3223
3223
  "title": "Tweak a page",
3224
3224
  "states_read": ["doc","typed","chrome","submitted","picking","out"],
3225
- "files_hash": "6fc3e45b7cc82f9f",
3225
+ "files_hash": "f52e3d547a72e46c",
3226
3226
  "reading_hash": "f521f8d9a7147557",
3227
3227
  "counts": {"lines":26,"placed":18,"shared":2,"unplaced":6},
3228
3228
  "omitted": {"data":19},
@@ -3261,7 +3261,7 @@
3261
3261
  "surface": "builders",
3262
3262
  "title": "Watch",
3263
3263
  "states_read": ["board","out","operations","security","sessions","autobongos"],
3264
- "files_hash": "4e769b3c76d5f3b8",
3264
+ "files_hash": "8c900aaef19935e9",
3265
3265
  "reading_hash": "1961a0c06b9d8477",
3266
3266
  "counts": {"lines":72,"placed":33,"shared":7,"unplaced":32},
3267
3267
  "omitted": {"data":52},
@@ -3346,7 +3346,7 @@
3346
3346
  "surface": "builders",
3347
3347
  "title": "Tasks",
3348
3348
  "states_read": ["ready","in-progress","backlog","completed","filters-open","out","founding"],
3349
- "files_hash": "cb9ac65fd6baed84",
3349
+ "files_hash": "932f03e5ce99e576",
3350
3350
  "reading_hash": "e9b412d1ec59ceee",
3351
3351
  "counts": {"lines":122,"placed":49,"shared":7,"unplaced":66},
3352
3352
  "omitted": {"data":323},
@@ -429,6 +429,53 @@ async function removeRunner({ builderId, host }) {
429
429
  return r ? { host: r.host, session_id: r.session_id ? String(r.session_id) : null } : null;
430
430
  }
431
431
 
432
+ // ── one goal, seen from the builder's runner (task 1005243) ──────────────────
433
+ //
434
+ // readRunnerGoalStats — what the game's runner card says about ONE goal: how
435
+ // many tasks there the builder's runner has shipped, the tokens those sessions
436
+ // spent, their average length, and how many tasks are still to build. All of it
437
+ // is the ledger's answer: a runner's claim carries `autonomous` (set by the
438
+ // server from the claiming session, core_295), a shipped claim is outcome
439
+ // 'shipped', and a session's tokens and length are what ship.js uploaded to
440
+ // session_records. SCOPED TO ONE BUILDER, like the heartbeat read: nobody reads
441
+ // another builder's runner.
442
+ //
443
+ // ITS COST. `mine` starts from the caller's claims through core_263's
444
+ // idx_claims_builder_history (builder_id, claimed_at), so it reads one builder's
445
+ // claims, never the table; session_records goes through idx_session_records_builder,
446
+ // and the tasks count through the goal's own rows. The card asks only while it is
447
+ // open (home-runner.js, every 15 s while the tab is visible).
448
+ async function readRunnerGoalStats(builderId, goalId) {
449
+ const { rows } = await pool.query(
450
+ `WITH mine AS (
451
+ SELECT DISTINCT c.task_id
452
+ FROM claims c JOIN tasks t ON t.id = c.task_id
453
+ WHERE c.builder_id = $1 AND c.autonomous AND c.outcome = 'shipped' AND t.goal_id = $2
454
+ ), spent AS (
455
+ SELECT COALESCE(SUM(sr.total_tokens), 0)::bigint AS tokens,
456
+ AVG(sr.duration_seconds) FILTER (WHERE sr.duration_seconds > 0) AS avg_s
457
+ FROM session_records sr
458
+ WHERE sr.builder_id = $1 AND sr.task_ids && ARRAY(SELECT task_id FROM mine)
459
+ )
460
+ SELECT (SELECT COUNT(*) FROM mine)::int AS tasks_done,
461
+ spent.tokens, spent.avg_s,
462
+ -- what is left is told only for a goal the project opened to runners, so
463
+ -- the read is no window onto a goal's size the caller could not send to
464
+ CASE WHEN EXISTS (SELECT 1 FROM autonomy_allowed_goals WHERE goal_id = $2)
465
+ THEN (SELECT COUNT(*) FROM tasks WHERE goal_id = $2 AND status IN ('ready', 'active'))::int END AS tasks_left
466
+ FROM spent`,
467
+ [builderId, goalId]
468
+ );
469
+ const r = rows[0] || {};
470
+ const avg = r.avg_s == null ? null : Math.round(Number(r.avg_s));
471
+ return {
472
+ tasks_done: Number(r.tasks_done) || 0,
473
+ tokens: Number(r.tokens) || 0,
474
+ avg_task_seconds: Number.isFinite(avg) && avg > 0 ? avg : null,
475
+ tasks_left: r.tasks_left == null ? null : Number(r.tasks_left) || 0,
476
+ };
477
+ }
478
+
432
479
  module.exports = {
433
480
  recordAutonomyRun,
434
481
  lastAutonomyHead,
@@ -449,4 +496,5 @@ module.exports = {
449
496
  setBuilderGoals,
450
497
  setBuilderPause,
451
498
  readAllRunnerScopes,
499
+ readRunnerGoalStats,
452
500
  };
@@ -376,6 +376,21 @@ module.exports = function buildAutonomyRouter() {
376
376
  }
377
377
  });
378
378
 
379
+ // ONE GOAL, SEEN FROM THE CALLER'S RUNNER (task 1005243): the game's runner
380
+ // card on Home — tasks it shipped there, the tokens they took, the average
381
+ // task length and what is left, so the card can say roughly how long the rest
382
+ // takes. Read-only and the caller's own, behind `autonomy.run` like the picks.
383
+ router.get('/autonomy/me/goals/:goalId/stats', auth.requireBuilder, auth.requirePermission('autonomy.run'), async (req, res) => {
384
+ const goalId = Number(req.params.goalId);
385
+ if (!Number.isSafeInteger(goalId) || goalId <= 0) return res.fail('goal_id_invalid', { status: 400, message: 'goal id must be a positive integer' });
386
+ try {
387
+ res.json({ goal_id: goalId, ...(await db.readRunnerGoalStats(req.builder.id, goalId)) });
388
+ } catch (err) {
389
+ log.error('[gds] GET /autonomy/me/goals/:goalId/stats', err);
390
+ res.fail('runner_stats_unreadable', { status: 503, message: 'could not read the runner stats' });
391
+ }
392
+ });
393
+
379
394
  // The owner pauses or resumes ONE builder's runner without throwing the master
380
395
  // switch. Same atom as the kill switch: stopping a machine is the same act
381
396
  // whether it is everyone's or one person's, and the paused builder must not be
@@ -128,7 +128,7 @@ const PERMISSIONS = [
128
128
  // "runner operators" should be able to compose one. It can never widen the fence
129
129
  // — the project allowlist and the kill switch stay behind autonomy.fence.manage —
130
130
  // so delegating it delegates only "whose machine may work the allowed goals".
131
- { key: 'autonomy.run', system: false, floor: 'metic', guards: 'PUT /autonomy/me/goals (a builder\'s own runner scope); also decides whether GET /autonomy/fence answers enabled for the caller' },
131
+ { key: 'autonomy.run', system: false, floor: 'metic', guards: 'PUT /autonomy/me/goals (a builder\'s own runner scope) and GET /autonomy/me/goals/:goalId/stats (its numbers on one goal); also decides whether GET /autonomy/fence answers enabled for the caller' },
132
132
  // BV1.R21 (task 1003608, goal 1000086). ADR 0250 §6 amends ADR 0157 for exactly
133
133
  // this key: `version.create` returns to the Archon floor.
134
134
  //
@@ -830,8 +830,12 @@
830
830
  // clears the pick and signs out, and the next sign-in finds none and asks again.
831
831
  // Identity and view only — rank still gates every verb on the server (ADR 0016).
832
832
  //
833
- // The sprites are the owner's art (Builder → Artisan, Artist → Artist, Ruler →
834
- // Patron; the Governor is the spare), one file per character under characters/.
833
+ // The sprites are the second look (task 1005218): 64x64 pixel art, two animals in one
834
+ // body (Artisan a ram on a lizard, Artist an ibis on an octopus, Patron a falcon on a
835
+ // lion), each with one colour of its own (style.css --char-*). Three files per character
836
+ // under characters/: <id>.png the whole figure, <id>-face.png the head (the chip),
837
+ // <id>-visor.png the head wearing the visor (the visor's corner). All are DRAWN by
838
+ // docs/design/characters/v2/draw-characters.js --hall, never edited by hand.
835
839
  var account; // undefined: not told; null: none picked; else the character
836
840
  var creating = false; // the preselected door while character creation is open
837
841
  var CHAR_KEY = 'hall-character'; // localStorage: the last held character, so the first paint is right
@@ -841,9 +845,14 @@
841
845
  patron: 'Watch the project, propose, and with a seat decide.',
842
846
  };
843
847
  // Only ever one of the three names reaches the URL (valid() is the allowlist).
844
- function spriteSrc(id) { return '/builders/characters/' + (valid(id) || 'artisan') + '.png' + (SCRIPT_V ? '?v=' + SCRIPT_V : ''); }
848
+ function pictureSrc(id, part) { return '/builders/characters/' + (valid(id) || 'artisan') + (part ? '-' + part : '') + '.png' + (SCRIPT_V ? '?v=' + SCRIPT_V : ''); }
849
+ function spriteSrc(id) { return pictureSrc(id, ''); }
850
+ // The head alone, and the head wearing the visor (what the visor shows of you).
851
+ function faceSrc(id) { return pictureSrc(id, 'face'); }
852
+ function portraitSrc(id) { return pictureSrc(id, 'visor'); }
845
853
  function spriteHtml(id, cls) {
846
- return '<img class="craft-sprite ' + cls + '" src="' + spriteSrc(id) + '" alt="" width="250" height="290" decoding="async">';
854
+ var face = cls === 'craft-sprite--chip';
855
+ return '<img class="craft-sprite ' + cls + '" src="' + (face ? faceSrc(id) : spriteSrc(id)) + '" alt="" width="' + (face ? 32 : 64) + '" height="' + (face ? 32 : 64) + '" decoding="async">';
847
856
  }
848
857
  function heldCharacter() {
849
858
  if (typeof account === 'string') return account;
@@ -1061,6 +1070,8 @@
1061
1070
  character: function () { return account; },
1062
1071
  changeCharacter: changeCharacter,
1063
1072
  spriteSrc: spriteSrc,
1073
+ faceSrc: faceSrc,
1074
+ portraitSrc: portraitSrc,
1064
1075
  // task 1005148: the visor's two reads — the /me the gate saw, and a tool by id.
1065
1076
  me: function () { return lastMe; },
1066
1077
  // A tool opens only for the character whose tool it is (task 1005207): the
@@ -297,6 +297,8 @@
297
297
  const a = it.a, x0 = a.x, y0 = a.y, x1 = x0 + FOOT, y1 = y0 + FOOT, base = 4;
298
298
  const d = doneAt(a, t), built = Math.max(2, (d.build + d.art) * m.px), clad = d.art * m.px;
299
299
  const site = sites[a.id], who = site ? m.people[site.builder] : null, agent = !!(site && (site.agent || (who && who.agent)));
300
+ // your runner sent here (task 1005243): its marker stands in for the agent's face
301
+ const mine = latest && o.runner && o.runner.goal === a.id ? o.runner.state : null;
300
302
  const wait = latest ? (m.waiting[a.id] || 0) : 0;
301
303
  const g = [];
302
304
  g.push(...box(x0 - 0.12, y0 - 0.12, x1 + 0.12, y1 + 0.12, 0, base, I.plinth));
@@ -330,12 +332,13 @@
330
332
  const hx = mx + 1.8;
331
333
  g.push(line(P(hx, my, ct), P(hx, my, top - 6), I.steel, 1));
332
334
  g.push(node('g', { class: 'hc-slab' }, box(hx - 0.3, my - 0.2, hx + 0.3, my + 0.2, top - 38, 3, I.slab)));
333
- if (who) over.push({ k: 'face', p: P(mx + 0.9, my, ct + 30), who, agent, a });
335
+ if (who && !mine) over.push({ k: 'face', p: P(mx + 0.9, my, ct + 30), who, agent, a });
334
336
  }
337
+ if (mine) over.push({ k: 'runner', p: site ? P(x0 + 1.4, y0 + 0.5, top + 86) : P((x0 + x1) / 2, (y0 + y1) / 2, top + 40), state: mine, a });
335
338
  if (wait) over.push({ k: 'dot', p: P((x0 + x1) / 2, (y0 + y1) / 2, top + (site ? 12 : 22)), a, n: wait });
336
339
  const shipped = Math.round(d.build + d.art), byArt = Math.round(d.art);
337
340
  const tip = `${shipped} shipped${byArt ? ` · ${byArt} by artists` : ''}${who ? ` · ${agent ? `${who.name}'s agent` : who.name} on site` : ''} · click to open the goal`;
338
- out.push(node('a', { href: `/goals#/goal/${encodeURIComponent(a.id)}`, class: 'hc-tower', 'aria-label': `${a.title}: ${shipped} shipped. Open the goal.`, 'data-tip': a.title, 'data-tip-body': tip }, g));
341
+ out.push(node('a', { href: `/goals#/goal/${encodeURIComponent(a.id)}`, class: 'hc-tower' + (o.picked === a.id ? ' hc-tower--picked' : ''), 'data-goal': a.id, 'aria-label': `${a.title}: ${shipped} shipped. Open the goal.`, 'data-tip': a.title, 'data-tip-body': tip }, g));
339
342
  // the cloud at its foot, over this tower and under the next one forward
340
343
  if (cloudy) out.push(puff(x1 - 0.5, y1 - 0.5, 62));
341
344
  });
@@ -344,6 +347,7 @@
344
347
 
345
348
  over.forEach((v, n) => {
346
349
  if (v.k === 'face') { out.push(avatarNode(v.p, v.who, v.agent, v.a, I, n, node, line, circle)); return; }
350
+ if (v.k === 'runner') { out.push(runnerNode(v.p, v.state, v.a, I, node, line, circle)); return; }
347
351
  const cx = v.p[0] - 30, cy = v.p[1];
348
352
  out.push(node('g', { class: 'hc-wait', 'data-tip': v.a.title, 'data-tip-body': `${v.n} waiting on you` }, [
349
353
  circle(cx, cy, 11, { fill: 'var(--accent)', stroke: 'var(--bg-card)', 'stroke-width': 2.5 }),
@@ -607,6 +611,24 @@
607
611
  return node('g', { class: 'hc-face-pin', 'data-tip': agent ? `${who.name}'s agent` : who.name, 'data-tip-body': a.title }, g);
608
612
  }
609
613
 
614
+ // Your Autobongos runner on the tower it was sent to (task 1005243): the agent's robot face,
615
+ // ringed in the state it is in. Working wears the accent ring that turns (CSS, under the
616
+ // Kill Switch); resting a dashed ring; starting the info ring; ready a soft ring; off no ring
617
+ // and a faded face. The words for each are the visor's card, never this mark alone.
618
+ // The same words the visor's card puts on the device's face (runner-button.js buttonState).
619
+ const RUNNER_WORDS = { working: 'Running', resting: 'Resting', starting: 'Starting…', ready: 'Ready', off: 'Recalled' };
620
+ function runnerNode(p, state, a, I, node, line, circle) {
621
+ const x = p[0], y = p[1];
622
+ const st = RUNNER_WORDS[state] ? state : 'off';
623
+ return node('g', { class: `hc-runner hc-runner--${st}`, 'data-goal': a.id, 'data-tip': 'Your runner', 'data-tip-body': `${RUNNER_WORDS[st]} · ${a.title}` }, [
624
+ line([x, y + 18], [x, y + 32], I.steel, 1),
625
+ circle(x, y, 21, { class: 'hc-runner__ring', fill: 'none', 'stroke-width': 2.5 }),
626
+ circle(x, y, 16, { class: 'hc-runner__face', fill: I.avAgent, stroke: 'var(--bg-card)', 'stroke-width': 3 }),
627
+ node('rect', { class: 'hc-runner__ink', x: r1(x - 7), y: r1(y - 5), width: 14, height: 11, rx: 3, fill: 'none', stroke: I.avInk, 'stroke-width': 1.5 }),
628
+ circle(x - 3, y + 0.5, 1.5, { class: 'hc-runner__eye', fill: I.avInk }), circle(x + 3, y + 0.5, 1.5, { class: 'hc-runner__eye', fill: I.avInk }),
629
+ ]);
630
+ }
631
+
610
632
  function initial(name) { const s = String(name || '').trim(); return s ? s[0].toUpperCase() : '?'; }
611
633
  // a stable spread for an idea's place in the sky (FNV-1a over its id)
612
634
  function hash(s) { let h = 2166136261; for (const c of String(s)) { h ^= c.charCodeAt(0); h = Math.imul(h, 16777619); } return Math.abs(h); }
@@ -38,6 +38,20 @@ html[data-game="on"] body[data-page="home"] .page-head--hall .page-head__h { wid
38
38
  .hc-svg a:focus { outline: none; }
39
39
  .hc-svg a:focus-visible .hc-face { filter: brightness(1.35); }
40
40
  .hc-svg .hc-tower:hover .hc-face { filter: brightness(1.3); }
41
+ /* game mode, your runner (task 1005243): the tower picked for the visor's card is lit and edged
42
+ in the accent; your runner's marker is the agent's face in a ring that says its state */
43
+ .hc-svg .hc-tower--picked .hc-face { filter: brightness(1.2); stroke: var(--accent); stroke-width: 2px; }
44
+ .hc-svg .hc-runner { cursor: pointer; }
45
+ .hc-svg .hc-runner__ring { stroke: var(--ink-soft); stroke-opacity: .5; stroke-width: 3.5px; }
46
+ /* yours, not anyone's agent: the accent face, the dark ink on it */
47
+ .hc-svg .hc-runner__face { fill: var(--accent); }
48
+ .hc-svg .hc-runner__ink { stroke: var(--accent-on); }
49
+ .hc-svg .hc-runner__eye { fill: var(--accent-on); }
50
+ .hc-svg .hc-runner--working .hc-runner__ring { stroke: var(--accent); stroke-opacity: 1; stroke-dasharray: 22 10; transform-box: fill-box; transform-origin: center; animation: hc-turn 6s linear infinite; }
51
+ .hc-svg .hc-runner--starting .hc-runner__ring { stroke: var(--info); stroke-opacity: 1; stroke-dasharray: 4 6; }
52
+ .hc-svg .hc-runner--resting .hc-runner__ring { stroke: var(--info); stroke-opacity: .9; stroke-dasharray: 2 5; }
53
+ .hc-svg .hc-runner--off { opacity: .55; }
54
+ .hc-svg .hc-runner--off .hc-runner__ring { stroke-opacity: 0; }
41
55
  /* the core and the museum stay quiet on the builder's Home so the new buildings lead;
42
56
  each comes back to full colour under the pointer or the keyboard */
43
57
  .hc-svg .hc-quiet { opacity: .62; filter: saturate(.3); transition: opacity .3s, filter .3s; }
@@ -110,6 +124,7 @@ html[data-theme="dark"] .home-city.hc-is-sky { background: linear-gradient(color
110
124
 
111
125
  /* live, and nothing more: a crane's slab climbs, this week's ideas glow (20s+) */
112
126
  @keyframes hc-lift { 0%, 12% { transform: translateY(0); } 55%, 70% { transform: translateY(-24px); } 100% { transform: translateY(0); } }
127
+ @keyframes hc-turn { to { transform: rotate(360deg); } }
113
128
  @keyframes hc-glow { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
114
129
  @keyframes hc-live { 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ok) 55%, transparent); } 100% { box-shadow: 0 0 0 8px transparent; } }
115
130
  .hc-slab { animation: hc-lift 20s ease-in-out infinite; }
@@ -104,7 +104,8 @@
104
104
  const svg = host.querySelector('.hc-svg');
105
105
  const sky = craft() === 'ideating';
106
106
  const draw = sky ? Draw.skySvg : Draw.citySvg;
107
- const out = draw(model, atLive(t) ? model.W - 1 : t, { mode: mode(), gov: window.GovCity || null });
107
+ const R = window.OTBHomeRunner; // your runner on its tower, and the tower picked for it (home-runner.js)
108
+ const out = draw(model, atLive(t) ? model.W - 1 : t, { mode: mode(), gov: window.GovCity || null, runner: R ? R.marker() : null, picked: R ? R.picked() : null });
108
109
  host.classList.toggle('hc-is-sky', sky);
109
110
  svg.setAttribute('aria-label', sky ? SKY_LABEL : CITY_LABEL);
110
111
  host.querySelector('#home-city-h').textContent = sky ? 'The project\'s ideas this week' : 'The project this week';
@@ -354,6 +355,7 @@
354
355
  new MutationObserver(() => { if (model) drawScene(); }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
355
356
  document.addEventListener('otb:craft', () => { if (model) { drawScene(); drawLadder(); } });
356
357
  window.addEventListener('resize', () => { if (model) drawLadder(); });
358
+ document.addEventListener('otb:home-runner', () => { if (model) drawScene(); });
357
359
  registerScene();
358
360
  load();
359
361
  }