@bongos/core 1.19.876 → 1.19.878

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 (47) hide show
  1. package/.bongos-core.json +110 -40
  2. package/.gitattributes +6 -2
  3. package/clients/bongos-client/README.md +1 -1
  4. package/clients/bongos-client/bongos-client.global.js +4 -0
  5. package/clients/bongos-client/index.cjs +4 -0
  6. package/clients/bongos-client/index.d.ts +7 -0
  7. package/clients/bongos-client/index.mjs +4 -0
  8. package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +12 -1
  9. package/docs/api/openapi.json +149 -3
  10. package/docs/api-reference.md +4 -2
  11. package/docs/copy-inventory.md +7 -6
  12. package/docs/copy-registry.json +16 -7
  13. package/docs/module-api-changelog.md +4 -0
  14. package/docs/recipes/autobongos-windows-host.md +163 -0
  15. package/docs/recipes/instance-deploy.md +29 -3
  16. package/modules/autonomy/cadence.js +276 -0
  17. package/modules/autonomy/db.js +47 -0
  18. package/modules/autonomy/digest.js +344 -0
  19. package/modules/autonomy/migrations/autonomy_002_runner_heartbeat.sql +93 -0
  20. package/modules/autonomy/routes/autonomy.js +69 -0
  21. package/modules/autonomy/runner-health.js +97 -0
  22. package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
  23. package/modules/hall-ui/public/gate.html +4 -1
  24. package/modules/hall-ui/public/gate.js +51 -2
  25. package/package-lock.json +2 -2
  26. package/package.json +1 -1
  27. package/scripts/gds/autobongos-digest.js +123 -0
  28. package/scripts/gds/autobongos-run.js +375 -11
  29. package/scripts/gds/autobongos-service.cmd +38 -0
  30. package/scripts/gds/autobongos-service.ps1 +135 -0
  31. package/scripts/gds/deploy-main-audit-gate.sh +90 -0
  32. package/scripts/gds/main-audit.js +26 -4
  33. package/scripts/gds/provision-core-upgrade.js +23 -3
  34. package/scripts/gds/run-unit-tests.js +16 -1
  35. package/src/module-api.js +1 -1
  36. package/tests/autobongos_cadence.mjs +602 -0
  37. package/tests/autonomy_digest.mjs +302 -0
  38. package/tests/autonomy_runner_health.mjs +135 -0
  39. package/tests/bump_version.mjs +12 -1
  40. package/tests/cli_package.mjs +11 -1
  41. package/tests/core_upgrade_runner.mjs +43 -1
  42. package/tests/deploy_audit_gate.mjs +154 -0
  43. package/tests/deploy_divergence_line.mjs +47 -1
  44. package/tests/helpers.mjs +61 -0
  45. package/tests/main_audit_db_roster.mjs +97 -0
  46. package/tests/publish_watch.mjs +9 -0
  47. package/tests/setup_device_flow_fallback.mjs +2 -1
@@ -254,7 +254,7 @@
254
254
  const el = document.getElementById('fence-state');
255
255
  el.innerHTML = `
256
256
  <div class="ov-foot" style="display:flex;align-items:center;gap:12px;flex-wrap:wrap;">
257
- <strong>${on ? 'Running' : 'Stopped'}</strong>
257
+ <span class="fact-pill ${on ? 'fact-pill--ok' : 'fact-pill--warn'}">${on ? 'Allowed to work' : 'Stopped'}</span>
258
258
  <span>${on ? 'The runner may claim and work allowlisted goals.' : escapeHtml(fence.paused_reason || 'The runner is doing nothing.')}</span>
259
259
  <button type="button" id="fence-toggle" class="btn">${on ? 'Stop the runner' : 'Start the runner'}</button>
260
260
  </div>`;
@@ -317,6 +317,54 @@
317
317
  }
318
318
  }
319
319
 
320
+ // The runner's liveness (task 1003905). Rendered ABOVE the switch on purpose:
321
+ // "is it running at all" is the first question, and an owner who has just
322
+ // flipped the switch on wants to see the machine notice.
323
+ //
324
+ // The verdict is the SERVER's — modules/autonomy/runner-health.js decides what
325
+ // dead means and the route attaches it — so this page and any future alert
326
+ // cannot drift into two definitions of the same word.
327
+ // This page's OWN class vocabulary (style.css `.fact-pill--{ok,warn,danger}`),
328
+ // the same one renderRow spends for a PR's verdict. The first cut reached for
329
+ // inline `--color-text-*` tokens from a different design system: they do not
330
+ // exist here, resolved to inherited grey, and drew a dead runner in exactly the
331
+ // same colour as a healthy one. Named classes cannot fail that way silently —
332
+ // a missing class is visible in the stylesheet, a missing var is not.
333
+ const HEALTH_CLASS = { alive: 'fact-pill--ok', late: 'fact-pill--warn', dead: 'fact-pill--danger', never_seen: 'fact-pill--info' };
334
+
335
+ async function loadRunners() {
336
+ const el = document.getElementById('fence-runner');
337
+ if (!el) return;
338
+ let resp;
339
+ try {
340
+ resp = await fetchJson('/autonomy/runners');
341
+ } catch (err) {
342
+ if (err && err.kind === '401') return;
343
+ el.innerHTML = emptyStateHtml('Could not read the runner\u2019s check-ins just now.');
344
+ return;
345
+ }
346
+ const runners = resp.runners || [];
347
+ if (!runners.length) {
348
+ // never_seen, and the server said so rather than this page guessing. Not an
349
+ // alarm: nobody has set the host up yet.
350
+ el.innerHTML = `<p class="ov-foot">${escapeHtml(resp.none ? resp.none.detail : 'No runner has ever checked in.')}</p>`;
351
+ return;
352
+ }
353
+ el.innerHTML = runners.map((r) => {
354
+ const h = r.health || {};
355
+ const cls = HEALTH_CLASS[h.state] || 'fact-pill--info';
356
+ // NOT the word "running": the switch below already uses it for a different
357
+ // thing (allowed to work), and the two lines sit one above the other. A
358
+ // reader should never have to work out which sense is meant.
359
+ const label = { dead: 'Not checking in', late: 'Quiet', alive: 'Checking in' }[h.state] || 'Unknown';
360
+ return `<p class="ov-foot" style="display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;">
361
+ <span class="fact-pill ${cls}">${escapeHtml(label)}</span>
362
+ <span>${escapeHtml(h.detail || '')}</span>
363
+ <span class="ov-fact">last check-in ${escapeHtml(h.age || 'unknown')} ago</span>
364
+ </p>`;
365
+ }).join('');
366
+ }
367
+
320
368
  async function loadFence() {
321
369
  let fence;
322
370
  try {
@@ -356,7 +404,8 @@
356
404
  document.getElementById('gate-body').hidden = false;
357
405
  if (rank === 'archon') {
358
406
  document.getElementById('fence-panel').hidden = false;
359
- await loadFence();
407
+ // Independent reads, so one round trip rather than two.
408
+ await Promise.all([loadRunners(), loadFence()]);
360
409
  }
361
410
  await load();
362
411
  }
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.876",
3
+ "version": "1.19.878",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.876",
9
+ "version": "1.19.878",
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.19.876",
3
+ "version": "1.19.878",
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",
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // scripts/gds/autobongos-digest.js — read the night without opening a log
5
+ // (task 1003906, ADR 0307).
6
+ //
7
+ // node scripts/gds/autobongos-digest.js # since midnight, local
8
+ // node scripts/gds/autobongos-digest.js --since 24h # a rolling window
9
+ // node scripts/gds/autobongos-digest.js --json # the structured shape
10
+ // node scripts/gds/autobongos-digest.js --all # the whole log
11
+ //
12
+ // The decision logic is modules/autonomy/digest.js and has no I/O; this half
13
+ // finds the two files it reads and prints the result. Exit 0 always, including on
14
+ // an empty night: a digest is a report, and a non-zero exit would make a cron
15
+ // wrapper treat "nothing happened" as a failure.
16
+
17
+ const fs = require('node:fs');
18
+ const path = require('node:path');
19
+
20
+ const digest = require('../../modules/autonomy/digest.js');
21
+
22
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
23
+
24
+ function arg(flag, argv) {
25
+ const i = argv.indexOf(flag);
26
+ return i >= 0 ? argv[i + 1] : null;
27
+ }
28
+
29
+ function logPath() {
30
+ if (process.env.AUTOBONGOS_LOG_FILE) return process.env.AUTOBONGOS_LOG_FILE;
31
+ try { return require('../../src/instance-config.js').configPath('autobongos-runs.jsonl'); }
32
+ catch (_) { return path.join(REPO_ROOT, 'autobongos-runs.jsonl'); }
33
+ }
34
+
35
+ function gaugePath() {
36
+ if (process.env.AUTOBONGOS_GAUGE_FILE) return process.env.AUTOBONGOS_GAUGE_FILE;
37
+ try { return require('../../src/instance-config.js').configPath('autobongos-gauge.json'); }
38
+ catch (_) { return path.join(REPO_ROOT, 'autobongos-gauge.json'); }
39
+ }
40
+
41
+ // readRows — an append-only JSONL, and a single unparseable line must not cost
42
+ // the whole night. A JSONL written by a process that can be killed mid-write has
43
+ // a torn last line more often than never.
44
+ function readRows(file) {
45
+ let text;
46
+ try { text = fs.readFileSync(file, 'utf8'); }
47
+ catch (_) { return { rows: [], missing: true, skipped: 0 }; }
48
+ const rows = [];
49
+ let skipped = 0;
50
+ for (const line of text.split('\n')) {
51
+ const t = line.trim();
52
+ if (!t) continue;
53
+ try { rows.push(JSON.parse(t)); } catch (_) { skipped += 1; }
54
+ }
55
+ return { rows, missing: false, skipped };
56
+ }
57
+
58
+ function readGaugeFile(file) {
59
+ try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
60
+ catch (_) { return null; }
61
+ }
62
+
63
+ // sinceMs — `--since 24h|90m|3d`, or midnight LOCAL by default.
64
+ //
65
+ // Local rather than UTC because the question is "what happened last night" and
66
+ // night is a local idea; a UTC midnight cut would split an owner's evening in
67
+ // half depending on where they live.
68
+ function sinceMs(spec, now = new Date()) {
69
+ if (!spec) {
70
+ const d = new Date(now);
71
+ d.setHours(0, 0, 0, 0);
72
+ return d.getTime();
73
+ }
74
+ const m = /^(\d+(?:\.\d+)?)\s*([hmd])$/i.exec(String(spec).trim());
75
+ if (!m) return null;
76
+ const mult = { h: 3600e3, m: 60e3, d: 86400e3 }[m[2].toLowerCase()];
77
+ return now.getTime() - Number(m[1]) * mult;
78
+ }
79
+
80
+ function main(argv) {
81
+ const all = argv.includes('--all');
82
+ const asJson = argv.includes('--json');
83
+ const now = new Date();
84
+ const from = all ? null : sinceMs(arg('--since', argv), now);
85
+ if (!all && from === null) {
86
+ console.error('autobongos-digest: --since wants a span like 24h, 90m or 3d.');
87
+ return 2;
88
+ }
89
+
90
+ const lf = logPath();
91
+ const { rows, missing, skipped } = readRows(lf);
92
+ const inWindow = all ? rows : rows.filter((r) => {
93
+ const t = Date.parse(r && r.at);
94
+ return Number.isFinite(t) ? t >= from : true; // an undated row is kept, not silently dropped
95
+ });
96
+
97
+ const d = digest.buildDigest(inWindow, {
98
+ gauge: readGaugeFile(gaugePath()),
99
+ nowEpochS: Math.floor(now.getTime() / 1000),
100
+ sinceMs: all ? null : from,
101
+ untilMs: now.getTime(),
102
+ });
103
+
104
+ if (asJson) { console.log(JSON.stringify(d, null, 2)); return 0; }
105
+
106
+ console.log('');
107
+ console.log(digest.renderDigest(d));
108
+ // Both of these are about the DIGEST's own reliability, so they sit outside the
109
+ // five questions rather than being folded into them.
110
+ if (missing) {
111
+ console.log('');
112
+ console.log(` No run log at ${lf} — the runner has not written one here yet.`);
113
+ }
114
+ if (skipped) console.log(` (${skipped} unreadable line(s) in the log were skipped.)`);
115
+ console.log('');
116
+ return 0;
117
+ }
118
+
119
+ if (require.main === module) {
120
+ process.exit(main(process.argv.slice(2)));
121
+ }
122
+
123
+ module.exports = { main, sinceMs, readRows, logPath, gaugePath };