@bongos/core 1.24.75 → 1.24.77
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 +101 -41
- package/.claude/skills/blocker-review/SKILL.md +10 -0
- package/clients/bongos-client/index.d.ts +1 -1
- package/docs/adr/0307-autobongos-a-deterministic-supervisor-and-agentic-workers.md +30 -0
- package/docs/api/openapi.json +5 -1
- package/docs/api-reference.md +1 -1
- package/docs/copy-inventory.md +43 -35
- package/docs/copy-registry.json +129 -57
- package/docs/module-api-changelog.md +4 -2
- package/docs/packs/engineer.md +2 -2
- package/docs/page-inventory.json +2 -0
- package/docs/page-readings.json +8 -8
- package/modules/autonomy/gauge.js +25 -1
- package/modules/hall-ui/public/deploy.html +1 -1
- package/modules/hall-ui/public/deploy.js +56 -5
- package/modules/hall-ui/public/home-city.js +7 -2
- package/modules/hall-ui/public/home-idea-box.css +33 -0
- package/modules/hall-ui/public/home-idea-box.js +106 -0
- package/modules/hall-ui/public/index.html +2 -0
- package/modules/hall-ui/public/project-settings.css +15 -0
- package/modules/hall-ui/records/home-city.md +21 -0
- package/modules/ideas/routes/blockers.js +47 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +19 -1
- package/scripts/bongos/autobongos-bootstrap.ps1 +169 -0
- package/scripts/bongos/autobongos-bootstrap.sh +151 -0
- package/scripts/bongos/autobongos-host.js +869 -0
- package/scripts/bongos/autobongos-run.js +7 -1
- package/scripts/bongos/autonomy-gauge.js +11 -3
- package/scripts/bongos/blocker-recheck.js +111 -0
- package/scripts/bongos/cli-lib.js +5 -1
- package/scripts/bongos/government-walk.js +1 -1
- package/scripts/gds/autobongos-bootstrap.ps1 +8 -0
- package/scripts/gds/autobongos-bootstrap.sh +8 -0
- package/scripts/gds/autobongos-host.js +7 -0
- package/scripts/gds/blocker-recheck.js +7 -0
- package/scripts/hall-preview/server.js +1 -0
- package/src/instance-config.js +22 -3
- package/src/module-api.js +1 -1
- package/tests/autobongos_host.mjs +619 -0
- package/tests/autobongos_loop.mjs +11 -0
- package/tests/autonomy_gauge.mjs +43 -0
- package/tests/cli_sessions.mjs +16 -0
- package/tests/deploy_page_projects.mjs +49 -0
- package/tests/government_agent_asks.mjs +92 -2
- package/tests/home_idea_box.mjs +178 -0
|
@@ -90,6 +90,6 @@
|
|
|
90
90
|
<script src="/builders/hall-ask.js"></script>
|
|
91
91
|
<!-- the in-place update window a hosted project's hall opens (task 1005041) -->
|
|
92
92
|
<script src="/builders/deploy-handoff.js?v=2026-11-03-handoff"></script>
|
|
93
|
-
<script src="/builders/deploy.js?v=2026-11-
|
|
93
|
+
<script src="/builders/deploy.js?v=2026-11-09-status"></script>
|
|
94
94
|
</body>
|
|
95
95
|
</html>
|
|
@@ -426,12 +426,63 @@
|
|
|
426
426
|
Reconnecting sets up <strong>lasting</strong> access: the hub keeps a key of its own for this repository, so this change and every later upgrade are saved to GitHub without asking you again.</p>`;
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
+
// ---- the plain status line (task 1005012) ----------------------------------
|
|
430
|
+
//
|
|
431
|
+
// The owner opened this page to put merged work live and found neither a button nor a
|
|
432
|
+
// sentence: the box was already on the newest version — it had moved there by itself on
|
|
433
|
+
// its update rule — and the page said only "nothing to deploy". So the box LEADS with one
|
|
434
|
+
// line in words, up to date or not, and says how the next version reaches it. Every
|
|
435
|
+
// fact in it is the server's: `behind`, the served version and the update rule. The page
|
|
436
|
+
// still compares no versions; it only words what it was told.
|
|
437
|
+
//
|
|
438
|
+
// How the next version arrives depends on the rule alone. On "Stay put" nothing arrives
|
|
439
|
+
// by itself, so saying it would is the one sentence here that could be false.
|
|
440
|
+
const AUTO_RULES = new Set(['patch', 'minor']);
|
|
441
|
+
function nextVersionWords(p) {
|
|
442
|
+
if (AUTO_RULES.has(p.update_channel)) {
|
|
443
|
+
return 'When a newer version is published, it arrives on its own under the update rule below — nothing needs pressing.';
|
|
444
|
+
}
|
|
445
|
+
if (p.update_channel === 'pinned') {
|
|
446
|
+
return 'The update rule below is Stay put, so a newer version waits until you change the update rule.';
|
|
447
|
+
}
|
|
448
|
+
return 'When a newer version is published, a deploy button appears here.';
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Merged is not live: a merge reaches this project only inside a published version. On
|
|
452
|
+
// the hall's own view the npm-release module lists where each piece of work has got to,
|
|
453
|
+
// so the line points at it when it is there, and says nothing about a list that is not.
|
|
454
|
+
function waitingWorkWords() {
|
|
455
|
+
const mods = window.__MODULES__;
|
|
456
|
+
const listed = view.isSelf && !!(mods && mods.enabled && mods.enabled['npm-release']);
|
|
457
|
+
return `Work merged after that is not live until a published version carries it${
|
|
458
|
+
listed ? ' — “Where is the work” on this page shows where each piece has got to' : ''}.`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function upToDateHtml(p) {
|
|
462
|
+
const v = escapeHtml(String(p.served_version));
|
|
463
|
+
return `
|
|
464
|
+
<div class="dp-status dp-status--ok" role="status">
|
|
465
|
+
<p class="ov-row__sub"><strong>Up to date.</strong> This project runs core ${v}, the newest version
|
|
466
|
+
it can take. Every piece of merged work inside core ${v} is live. There is nothing to deploy.</p>
|
|
467
|
+
<p class="ov-row__sub">${escapeHtml(nextVersionWords(p))} ${escapeHtml(waitingWorkWords())}</p>
|
|
468
|
+
</div>`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Behind: the one primary action on the page. It is a PERSON'S button and stays one —
|
|
472
|
+
// the project's law is that going live waits for a person, so pressing it opens the
|
|
473
|
+
// check and the confirm (ADR 0293 D4), and nothing restarts until "Move to …" is pressed.
|
|
474
|
+
// The id is the step-one id it always was, so the two-step wiring is unchanged.
|
|
429
475
|
function stepOneHtml(p) {
|
|
476
|
+
const to = escapeHtml(String(p.recommended_version));
|
|
477
|
+
const auto = AUTO_RULES.has(p.update_channel)
|
|
478
|
+
? ' It would also arrive on its own under the update rule below; this does it now.' : '';
|
|
430
479
|
return `
|
|
431
|
-
<
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
480
|
+
<div class="dp-status dp-status--behind" role="status">
|
|
481
|
+
<p class="ov-row__sub"><strong>Not up to date.</strong> Core <strong>${to}</strong> is published; this project still runs ${escapeHtml(String(p.served_version))}.${auto}</p>
|
|
482
|
+
<div class="ps-controls">
|
|
483
|
+
<button type="button" class="btn btn--primary" id="dp-preview-${escapeHtml(String(p.instance_id))}">Deploy core ${to} now</button>
|
|
484
|
+
</div>
|
|
485
|
+
<p class="ov-row__sub dp-status__note">You see what it brings in and confirm before anything restarts.</p>
|
|
435
486
|
</div>`;
|
|
436
487
|
}
|
|
437
488
|
|
|
@@ -613,7 +664,7 @@
|
|
|
613
664
|
if (moving.has(p.instance_id)) action = movingHtml(p);
|
|
614
665
|
else if (p.open_intent) action = busyHtml(p);
|
|
615
666
|
else if (p.reading === 'no_answer') action = '';
|
|
616
|
-
else if (!p.behind) action =
|
|
667
|
+
else if (!p.behind) action = upToDateHtml(p);
|
|
617
668
|
else action = step === 'confirm' ? stepTwoHtml(p, isSelf) : stepOneHtml(p);
|
|
618
669
|
if (!moving.has(p.instance_id)) action += settledHtml(p);
|
|
619
670
|
return `
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
const SKY_LABEL = 'The project\'s ideas as a night sky over the city\'s real skyline, with this week\'s ideators ranked beside it';
|
|
58
58
|
const CITY_LABEL = 'The project as a city: one tower per working area, as tall as what has shipped there, with the builders of the week beside it';
|
|
59
59
|
|
|
60
|
-
let host = null, model = null, viewerId = null, t = 0, target = 0, raf = null, run = null, lastWeek = -1, shown = false, gallery = null;
|
|
60
|
+
let host = null, model = null, viewerId = null, t = 0, target = 0, raf = null, run = null, lastWeek = -1, shown = false, gallery = null, ideaBox = null;
|
|
61
61
|
const rungs = new Map();
|
|
62
62
|
|
|
63
63
|
function mode() {
|
|
@@ -292,11 +292,15 @@
|
|
|
292
292
|
|
|
293
293
|
// ---- the artist's Home (task 1005060): in Artistry the same section is the gallery,
|
|
294
294
|
// home-gallery.js, the bench in the museum the city's art district opens onto ----
|
|
295
|
+
// In Ideating, the idea box (home-idea-box.js) opens Home above the sky, and shows even
|
|
296
|
+
// when the sky could not be read: filing an idea needs no picture of the others.
|
|
295
297
|
function craftHome() {
|
|
296
298
|
const art = !!gallery && document.documentElement.dataset.craft === 'artistry';
|
|
299
|
+
const idea = !!ideaBox && craft() === 'ideating';
|
|
297
300
|
host.classList.toggle('hc--gallery', art);
|
|
298
301
|
if (gallery) gallery.show(art);
|
|
299
|
-
|
|
302
|
+
if (ideaBox) ideaBox.show(idea);
|
|
303
|
+
host.hidden = !(art || idea || shown);
|
|
300
304
|
}
|
|
301
305
|
|
|
302
306
|
// ---- the governor's Home is the street; tests swap the hop for a recorder ----
|
|
@@ -338,6 +342,7 @@
|
|
|
338
342
|
wireTip();
|
|
339
343
|
wireDoors();
|
|
340
344
|
if (window.OTBHomeGallery) gallery = window.OTBHomeGallery.attach(host);
|
|
345
|
+
if (window.OTBHomeIdeaBox) ideaBox = window.OTBHomeIdeaBox.attach(host);
|
|
341
346
|
document.addEventListener('otb:craft', craftHome);
|
|
342
347
|
craftHome();
|
|
343
348
|
new MutationObserver(() => { if (model) drawScene(); }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/* modules/hall-ui/public/home-idea-box.css — the idea box that opens the ideator's Home
|
|
2
|
+
(home-idea-box.js; record modules/hall-ui/records/home-city.md § "The idea box").
|
|
3
|
+
It sits at the top of the city's section, over the sky. Every colour is a hall token
|
|
4
|
+
or a mix of one; the tokens carry both modes, so no rule here is written per mode.
|
|
5
|
+
Every type size and radius is a --ds-* scale token (DESIGN.md, the Fifteen Rule), and
|
|
6
|
+
the narrow layout comes from wrapping rather than a breakpoint, so the sheet adds no
|
|
7
|
+
typed-in value to the design counts. No :root, no motion. */
|
|
8
|
+
|
|
9
|
+
.hib { padding: clamp(16px, 4vw, 28px) clamp(16px, 4vw, 28px) clamp(14px, 3vw, 20px); display: grid; gap: 10px; }
|
|
10
|
+
.hib[hidden] { display: none; }
|
|
11
|
+
.hib-h { margin: 0; font: 600 var(--ds-text-4xl)/1.25 var(--font-display); color: var(--ink); }
|
|
12
|
+
.hib-h label { cursor: text; }
|
|
13
|
+
/* The box takes the row; on a narrow screen the button wraps under it */
|
|
14
|
+
.hib-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start; }
|
|
15
|
+
.hib .hib-title {
|
|
16
|
+
width: 100%; min-width: 0; box-sizing: border-box;
|
|
17
|
+
font: 400 var(--ds-text-xl)/1.4 var(--font-body); color: var(--ink);
|
|
18
|
+
background: var(--bg-card); border: var(--line) solid var(--rule-soft); border-radius: var(--radius-sm);
|
|
19
|
+
}
|
|
20
|
+
.hib textarea.hib-title {
|
|
21
|
+
flex: 1 1 16rem; width: auto; min-height: 48px; max-height: 40vh; padding: 12px 20px;
|
|
22
|
+
font-size: var(--ds-text-2xl); line-height: 22px;
|
|
23
|
+
border-radius: var(--ds-radius-panel); resize: none; overflow-y: auto;
|
|
24
|
+
}
|
|
25
|
+
.hib .hib-title::placeholder { color: var(--ink-faint); }
|
|
26
|
+
.hib .hib-title:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; border-color: transparent; }
|
|
27
|
+
.hib-file { min-height: 48px; padding: 0 22px; font-size: var(--ds-text-lg); flex-shrink: 0; }
|
|
28
|
+
.hib-said { margin: 0; min-height: 1.4em; font: 400 var(--ds-text-md)/1.4 var(--font-body); color: var(--ink-soft); }
|
|
29
|
+
.hib-said--err { color: var(--danger); }
|
|
30
|
+
.hib-said a { color: var(--ink); }
|
|
31
|
+
|
|
32
|
+
/* The setup guide is about building on your own machine; in Ideating, Home opens on the box */
|
|
33
|
+
html[data-craft="ideating"] #welcome-scroll { display: none; }
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// modules/hall-ui/public/home-idea-box.js — the ideator's Home opens on the idea box
|
|
2
|
+
// (goal 1000095, the owner's "work-first hall", 2026-11-04; record
|
|
3
|
+
// modules/hall-ui/records/home-city.md § "The idea box").
|
|
4
|
+
//
|
|
5
|
+
// In Ideating, the first thing on Home is a place to type an idea and file it; the sky
|
|
6
|
+
// and the ladder of ideators stay under it, secondary. It is ONE box and a button
|
|
7
|
+
// (owner, 2026-11-05: "only one box"): Enter files, Shift+Enter starts a new line, and
|
|
8
|
+
// the box grows with what is typed. The first line is the idea's title, any lines after
|
|
9
|
+
// it its details. Filing is POST /inbox as the sky's quick composer files
|
|
10
|
+
// (sky-composer.js): { title, body_md, kind: 'feature' }. A failed filing keeps the
|
|
11
|
+
// words where they were and says why; a filed one says where it went and links to
|
|
12
|
+
// Your thinking, where every thought the builder filed is listed.
|
|
13
|
+
//
|
|
14
|
+
// It is not a hall widget of its own (Home's stats zone holds one): home-city.js
|
|
15
|
+
// attaches it at the top of its section and shows it when the craft is Ideating, the
|
|
16
|
+
// way it shows the gallery in Artistry. It reads nothing; it writes one thing.
|
|
17
|
+
(function () {
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
var API = '/api/bongos';
|
|
21
|
+
|
|
22
|
+
// ---- pure: what the line under the box says (node-testable) ----
|
|
23
|
+
function explain(res) {
|
|
24
|
+
var err = res && res.data && res.data.error;
|
|
25
|
+
var code = err && (typeof err === 'object' ? err.code : err);
|
|
26
|
+
var details = (err && typeof err === 'object' && err.details) || {};
|
|
27
|
+
if (details.reason) return 'Not filed: ' + details.reason + '. Your idea is still here.';
|
|
28
|
+
if (res && (res.status === 401 || res.status === 403)) return 'Not filed: your session may have expired. Reload and try again.';
|
|
29
|
+
if (code) return 'Not filed: ' + String(code).replace(/_/g, ' ') + '. Your idea is still here.';
|
|
30
|
+
return 'Not filed' + (res && res.status ? ' (' + res.status + ')' : '') + '. Nothing was saved; your idea is still here.';
|
|
31
|
+
}
|
|
32
|
+
// the first non-empty line is the title; whatever follows it is the details
|
|
33
|
+
function body(text) {
|
|
34
|
+
var lines = String(text || '').replace(/\r\n?/g, '\n').split('\n');
|
|
35
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
36
|
+
return { title: (lines.shift() || '').trim(), body_md: lines.join('\n').trim(), kind: 'feature' };
|
|
37
|
+
}
|
|
38
|
+
var Lib = { explain: explain, body: body };
|
|
39
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = Lib;
|
|
40
|
+
if (typeof document === 'undefined') return;
|
|
41
|
+
|
|
42
|
+
function attach(host) {
|
|
43
|
+
var api = null, filing = false;
|
|
44
|
+
var root = document.createElement('form');
|
|
45
|
+
root.className = 'hib';
|
|
46
|
+
root.setAttribute('novalidate', '');
|
|
47
|
+
root.setAttribute('aria-labelledby', 'hib-h');
|
|
48
|
+
root.hidden = true;
|
|
49
|
+
root.innerHTML =
|
|
50
|
+
'<h2 class="hib-h" id="hib-h"><label for="hib-title">What is your idea?</label></h2>' +
|
|
51
|
+
'<div class="hib-row">' +
|
|
52
|
+
'<textarea id="hib-title" class="hib-title" rows="1" placeholder="One line is enough" aria-describedby="hib-keys"></textarea>' +
|
|
53
|
+
'<button type="submit" class="btn hib-file">File idea</button>' +
|
|
54
|
+
'</div>' +
|
|
55
|
+
'<p class="sr-only" id="hib-keys">Enter files the idea. Shift and Enter starts a new line.</p>' +
|
|
56
|
+
'<p class="hib-said" role="status" aria-live="polite"></p>';
|
|
57
|
+
host.insertBefore(root, host.firstChild);
|
|
58
|
+
var title = root.querySelector('.hib-title');
|
|
59
|
+
var said = root.querySelector('.hib-said'), btn = root.querySelector('.hib-file');
|
|
60
|
+
|
|
61
|
+
// the box is as tall as what is in it
|
|
62
|
+
function fit() { title.style.height = 'auto'; title.style.height = title.scrollHeight + 'px'; }
|
|
63
|
+
title.addEventListener('input', fit);
|
|
64
|
+
// Enter files, Shift+Enter is a new line; a key that is still composing a character is left alone
|
|
65
|
+
title.addEventListener('keydown', function (e) {
|
|
66
|
+
if (e.key !== 'Enter' || e.shiftKey || e.isComposing) return;
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
if (typeof root.requestSubmit === 'function') root.requestSubmit(); else root.dispatchEvent(new Event('submit', { cancelable: true }));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
function say(text, link) {
|
|
72
|
+
said.textContent = text;
|
|
73
|
+
said.classList.toggle('hib-said--err', !link && !!text);
|
|
74
|
+
if (link) {
|
|
75
|
+
var a = document.createElement('a');
|
|
76
|
+
a.href = '/thinking';
|
|
77
|
+
a.textContent = 'See it in Your thinking';
|
|
78
|
+
said.append(' ', a);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
root.addEventListener('submit', function (e) {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
if (filing) return;
|
|
85
|
+
var b = body(title.value);
|
|
86
|
+
if (!b.title) { say('Type your idea first.'); title.focus(); return; }
|
|
87
|
+
api = api || (window.BongosClient && window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false }));
|
|
88
|
+
if (!api) { say(explain(null)); return; }
|
|
89
|
+
filing = true; btn.disabled = true; say('');
|
|
90
|
+
api.request('POST', API + '/inbox', { body: b }).catch(function () { return null; }).then(function (res) {
|
|
91
|
+
filing = false; btn.disabled = false;
|
|
92
|
+
if (!res || !res.ok || !res.data || !res.data.idea) { say(explain(res)); return; }
|
|
93
|
+
title.value = ''; fit();
|
|
94
|
+
say(res.data.routed_task ? 'Filed, and it is already a task.' : 'Filed. It waits for the next triage.', true);
|
|
95
|
+
title.focus();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
// shown in Ideating
|
|
101
|
+
show: function (on) { root.hidden = !on; },
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
window.OTBHomeIdeaBox = { attach: attach, lib: Lib };
|
|
106
|
+
})();
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
<link rel="stylesheet" href="/builders/hall-ask.css">
|
|
24
24
|
<link rel="stylesheet" href="/builders/home-city.css">
|
|
25
25
|
<link rel="stylesheet" href="/builders/home-gallery.css">
|
|
26
|
+
<link rel="stylesheet" href="/builders/home-idea-box.css">
|
|
26
27
|
</head>
|
|
27
28
|
<body data-page="home">
|
|
28
29
|
|
|
@@ -133,6 +134,7 @@
|
|
|
133
134
|
<script src="/builders/city-draw.js" defer></script>
|
|
134
135
|
<script src="/builders/home-city-draw.js" defer></script>
|
|
135
136
|
<script src="/builders/home-gallery.js" defer></script>
|
|
137
|
+
<script src="/builders/home-idea-box.js" defer></script>
|
|
136
138
|
<script src="/builders/home-city.js" defer></script>
|
|
137
139
|
<!-- task 1003455: the shared blocker-row renderer (window.OTBBlockersLib), read by
|
|
138
140
|
both goal-inbox.js's attention-inbox slice below and the /blockers page.
|
|
@@ -212,6 +212,21 @@
|
|
|
212
212
|
}
|
|
213
213
|
.dp-settled .ps-controls { margin: 0.4rem 0 0.2rem; }
|
|
214
214
|
|
|
215
|
+
/* The box's plain status line (task 1005012): up to date, or not and here is the button.
|
|
216
|
+
The same left-mark grammar as the settled states — ok when nothing waits, the accent
|
|
217
|
+
when a published version is waiting for a person's press. */
|
|
218
|
+
.dp-status { margin: 0.4rem 0 0; }
|
|
219
|
+
.dp-status--ok {
|
|
220
|
+
border-left: 3px solid var(--ok);
|
|
221
|
+
padding-left: 0.7rem;
|
|
222
|
+
}
|
|
223
|
+
.dp-status--behind {
|
|
224
|
+
border-left: 3px solid var(--accent);
|
|
225
|
+
padding-left: 0.7rem;
|
|
226
|
+
}
|
|
227
|
+
.dp-status .ps-controls { margin: 0.5rem 0 0.3rem; }
|
|
228
|
+
.dp-status__note { color: var(--ink-soft); }
|
|
229
|
+
|
|
215
230
|
/* The project's update rule (task 1004445) — a quiet setting under the move, set apart by
|
|
216
231
|
a hairline so it does not read as part of whichever state sits above it. */
|
|
217
232
|
.dp-rule {
|
|
@@ -175,6 +175,27 @@ leaderboard.
|
|
|
175
175
|
- Pinned in `tests/home_studio.mjs` and `tests/home_studio_widget.mjs` (real browser, empty states included); harness
|
|
176
176
|
fixtures `copy-desk__studio.json` and `home__studio__works.json`.
|
|
177
177
|
|
|
178
|
+
## The idea box — the ideator's Home opens on it (task 1005078)
|
|
179
|
+
|
|
180
|
+
The owner's work-first hall (2026-11-04: "the hall is for doing work, not looking at stuff"), goal 1000095. In
|
|
181
|
+
Ideating, the first thing on Home is a place to type an idea and file it; the sky and the ladder stay under it.
|
|
182
|
+
|
|
183
|
+
- **One section, still.** `home-idea-box.js` is not a hall widget (Home's stats zone holds one): `home-city.js`
|
|
184
|
+
attaches it at the top of its section, the gallery's way, and `craftHome()` shows it in Ideating only. It shows
|
|
185
|
+
even when `GET /home/city` failed, because filing needs no picture of the other ideas.
|
|
186
|
+
- **One box and a button** (owner, 2026-11-05: "the idea submission should only have one box"). Enter files and
|
|
187
|
+
Shift+Enter starts a new line (owner, same day); the box grows with what is typed. The first non-empty line is
|
|
188
|
+
the title and any lines after it are `body_md`. No kind or goal picker: it files `kind: 'feature'` through
|
|
189
|
+
`POST /inbox`, the sky composer's quick filing, and triage routes it. The top bar's + idea button is unchanged.
|
|
190
|
+
- **What it says.** A refusal keeps the words and names the reason (`details.reason`, else the code); a filing
|
|
191
|
+
clears the box and links to Your thinking. It reads nothing.
|
|
192
|
+
- **The setup guide hides in Ideating** (`#welcome-scroll`, `home-idea-box.css`): it is about building on your
|
|
193
|
+
own machine and pushed the box below the fold. Every other craft still shows it.
|
|
194
|
+
- **Preview.** `scripts/hall-preview/server.js` answers `POST /inbox` with the canned `inbox.post.json`, so the
|
|
195
|
+
filed state can be seen. Pinned by `tests/home_idea_box.mjs` (8 cases, a real browser).
|
|
196
|
+
- **Sign-off.** The owner saw it in the preview at 1440 dark and said "Looks right, ship it" (2026-11-05), then
|
|
197
|
+
asked for Enter / Shift+Enter, which was added before the ship.
|
|
198
|
+
|
|
178
199
|
## The governor's Home is the street (task 1005064)
|
|
179
200
|
|
|
180
201
|
The owner, 2026-11-04, after the four Homes went live: "when you tab between the pages the home screen
|
|
@@ -79,6 +79,28 @@ function askRequired(req, body) {
|
|
|
79
79
|
return !TEMPLATED_SOURCES.includes(body.source);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// CHECKED, NOT INFERRED (task 1005120). A blocker spends the owner's attention, the
|
|
83
|
+
// scarcest thing a project has. One project's blocker asked its owner to "apply to
|
|
84
|
+
// Plaid for OAuth" for days after the project's own shipped work found there was
|
|
85
|
+
// nothing to apply for: it had been filed on an inference (one bank worked, one did
|
|
86
|
+
// not) when the page that settled it was one click away. So the same filings that
|
|
87
|
+
// owe an ask (an AI session, by hand) also owe `checked` — one sentence naming what
|
|
88
|
+
// the session opened or ran that showed only a person can do this. It is written into
|
|
89
|
+
// the blocker's body, so whoever re-reads the blocker later (the daily review re-checks
|
|
90
|
+
// before asking again) can see what the claim rested on and test it against what has
|
|
91
|
+
// shipped since. The floor is a length, because a length is all a server can judge;
|
|
92
|
+
// it stops "probably needed", which is the shape the hunch takes.
|
|
93
|
+
const CHECKED_MIN = 30;
|
|
94
|
+
const CHECKED_REQUIRED_MESSAGE = 'An AI session files a blocker only after checking it really needs a person. '
|
|
95
|
+
+ `Add "checked": one sentence (${CHECKED_MIN} characters or more) saying what you opened or ran that showed it, `
|
|
96
|
+
+ 'for example the settings page you read or the command whose output you saw. If you have not checked, check first.';
|
|
97
|
+
// The line names WHO checked, so a blocker is tracked back to its filer — the person a
|
|
98
|
+
// re-check asks first — without a new column on the core-owned table.
|
|
99
|
+
function checkedLine(checked, builder) {
|
|
100
|
+
const who = builder && builder.github_login ? ` (by @${builder.github_login})` : '';
|
|
101
|
+
return `**Checked before filing${who}:** ${String(checked).trim()}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
82
104
|
// The `ideas.fileBlocker` port's writer: POST /blockers' shape and length rules, for a caller
|
|
83
105
|
// inside the server, which has no request to validate. A refusal throws with code BAD_BLOCKER.
|
|
84
106
|
function fileBlockerFromServer({ title, bodyMd = '', source, sourceRef } = {}) {
|
|
@@ -145,6 +167,8 @@ module.exports = function buildBlockersRouter() {
|
|
|
145
167
|
// person answers (task 1004703, see THE ASK above); a slop ask files nothing
|
|
146
168
|
// (400 `ask_refused`). An AI session filing by hand (a CLI session, any `source`
|
|
147
169
|
// but a templated script's) must send one: without it, 400 `ask_required` (task 1004704).
|
|
170
|
+
// The same filing also sends `checked` — what it opened or ran that showed a person
|
|
171
|
+
// is needed — or 400 `checked_required`; it is appended to body_md (task 1005120).
|
|
148
172
|
// → 201 { blocker, ask } (ask: the stored card, or null).
|
|
149
173
|
router.post('/blockers', api.requireBuilder, async (req, res) => {
|
|
150
174
|
if (validateOrRespond(req, res, {
|
|
@@ -153,6 +177,7 @@ module.exports = function buildBlockersRouter() {
|
|
|
153
177
|
source: { type: 'string', maxLength: LIMITS.SOURCE },
|
|
154
178
|
source_ref: { type: 'string', maxLength: LIMITS.SOURCE_REF },
|
|
155
179
|
ask: { type: 'object' },
|
|
180
|
+
checked: { type: 'string', maxLength: LIMITS.TITLE },
|
|
156
181
|
})) return;
|
|
157
182
|
const body = req.body || {};
|
|
158
183
|
let verdict = null;
|
|
@@ -177,10 +202,31 @@ module.exports = function buildBlockersRouter() {
|
|
|
177
202
|
return res.fail('ask_refused', { status: 400, message: gov.refusalLine(verdict.errors), details: { errors: verdict.errors } });
|
|
178
203
|
}
|
|
179
204
|
}
|
|
205
|
+
// After the ask, so a filing with no card hears about the card first (task 1005120).
|
|
206
|
+
const checked = typeof body.checked === 'string' ? body.checked.trim() : '';
|
|
207
|
+
if (askRequired(req, body) && checked.length < CHECKED_MIN) {
|
|
208
|
+
return res.fail('checked_required', {
|
|
209
|
+
status: 400,
|
|
210
|
+
message: CHECKED_REQUIRED_MESSAGE,
|
|
211
|
+
details: { field: 'checked', min_length: CHECKED_MIN },
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const bodyMd = checked
|
|
215
|
+
? [String(body.body_md ?? '').trim(), checkedLine(checked, req.builder)].filter(Boolean).join('\n\n')
|
|
216
|
+
: (body.body_md ?? '');
|
|
217
|
+
// body_md was validated on its own; the check line is added after, so the whole is
|
|
218
|
+
// held to the same cap rather than quietly growing past it.
|
|
219
|
+
if (bodyMd.length > LIMITS.DESCRIPTION) {
|
|
220
|
+
return res.fail('body_too_long', {
|
|
221
|
+
status: 400,
|
|
222
|
+
message: `The blocker's text and its "checked" line come to ${bodyMd.length} characters; ${LIMITS.DESCRIPTION} at most. Shorten the body.`,
|
|
223
|
+
details: { field: 'body_md', max_length: LIMITS.DESCRIPTION },
|
|
224
|
+
});
|
|
225
|
+
}
|
|
180
226
|
try {
|
|
181
227
|
const blocker = await blockers.createBlocker({
|
|
182
228
|
title: body.title,
|
|
183
|
-
bodyMd
|
|
229
|
+
bodyMd,
|
|
184
230
|
source: body.source ?? 'manual',
|
|
185
231
|
sourceRef: body.source_ref ?? null,
|
|
186
232
|
});
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.77",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.77",
|
|
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.24.
|
|
3
|
+
"version": "1.24.77",
|
|
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",
|
package/release-notes.json
CHANGED
|
@@ -11470,10 +11470,28 @@
|
|
|
11470
11470
|
"text": "The hall has a game mode: once it is on, every page opens full screen with no sidebar, and Escape brings the menus back when you need them."
|
|
11471
11471
|
}
|
|
11472
11472
|
],
|
|
11473
|
-
"1.24.
|
|
11473
|
+
"1.24.76": [
|
|
11474
|
+
{
|
|
11475
|
+
"id": "1005078",
|
|
11476
|
+
"text": "In Ideating mode, Home now opens on a single box to type and file an idea — press Enter to file it, Shift+Enter for a new line."
|
|
11477
|
+
},
|
|
11478
|
+
{
|
|
11479
|
+
"id": "1005120",
|
|
11480
|
+
"text": "Blockers now cost a real check to file: an AI session must say what it looked at that showed a person is needed, and that note stays on the blocker. The daily blocker review now re-checks each one against what has shipped sinc"
|
|
11481
|
+
},
|
|
11482
|
+
{
|
|
11483
|
+
"id": "1005012",
|
|
11484
|
+
"text": "The Deploy page now says in one plain line whether the project is up to date. When it is, it says merged work in that version is live and that the next version arrives on its own (or waits, if the project is set to stay put)."
|
|
11485
|
+
},
|
|
11474
11486
|
{
|
|
11475
11487
|
"id": "1005121",
|
|
11476
11488
|
"text": "Closing a project-setup stage is now one step: if the founder alone decides, the stage closes and the project moves on at once; if others must vote, the answer says who and what to do next instead of falsely reporting success."
|
|
11477
11489
|
}
|
|
11490
|
+
],
|
|
11491
|
+
"1.24.77": [
|
|
11492
|
+
{
|
|
11493
|
+
"id": "1005095",
|
|
11494
|
+
"text": "The Autobongos runner can now be set up on any Windows PC or Mac, including a laptop, with no administrator step. One program installs it, starts it whenever you sign in to the device, keeps it running, updates it by itself, a"
|
|
11495
|
+
}
|
|
11478
11496
|
]
|
|
11479
11497
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# scripts/bongos/autobongos-bootstrap.ps1 -- the first lines of Autobongos setup
|
|
2
|
+
# on a Windows PC (tasks 1005095, 1005094; goal 1000139).
|
|
3
|
+
#
|
|
4
|
+
# autobongos-bootstrap.ps1 -Code abr_... -ApiBase https://... -Repo https://github.com/owner/name
|
|
5
|
+
#
|
|
6
|
+
# and, as the hall hands it out, with no file on the PC at all:
|
|
7
|
+
#
|
|
8
|
+
# & ([scriptblock]::Create((irm 'https://<project>/api/bongos/autonomy/runner-setup.ps1'))) -Code '...' -ApiBase '...' -Repo '...'
|
|
9
|
+
#
|
|
10
|
+
# WHY THIS IS POWERSHELL AND SO SHORT. Everything that sets a runner up is one
|
|
11
|
+
# Node program (autobongos-host.js), the same on Windows and Mac. But Node cannot
|
|
12
|
+
# install Node, and the program lives in a copy of the project that does not exist
|
|
13
|
+
# yet. So this does only what must happen before Node can take over: find or
|
|
14
|
+
# install git, Node and Claude Code, make the runner ITS OWN copy of the project,
|
|
15
|
+
# and hand over. The hall serves this file and Settings puts the one-time code in
|
|
16
|
+
# the command, so the person copies one line.
|
|
17
|
+
#
|
|
18
|
+
# NO ADMINISTRATOR SHELL. It runs as the person, in an ordinary PowerShell window.
|
|
19
|
+
# winget may show Windows' own "allow this app to make changes" prompt while it
|
|
20
|
+
# installs git or Node; that is the installer asking, not this script.
|
|
21
|
+
#
|
|
22
|
+
# EVERY STOP IS A SENTENCE. A failure it cannot fix itself ends with what
|
|
23
|
+
# happened and what to do, never a stack trace. Safe to run again: each step
|
|
24
|
+
# checks before it acts, and a copy that already exists is brought current
|
|
25
|
+
# instead of cloned twice.
|
|
26
|
+
#
|
|
27
|
+
# IT NEVER CALLS exit WHEN IT WAS PASTED (task 1005094). Run as a script block --
|
|
28
|
+
# which is how the pasted line runs it -- `exit` ends the PowerShell WINDOW, so
|
|
29
|
+
# the person would see their window vanish instead of the sentence telling them
|
|
30
|
+
# what to do. Every stop therefore unwinds to the bottom of this file, which
|
|
31
|
+
# calls `exit` only when this is running as a file.
|
|
32
|
+
|
|
33
|
+
[CmdletBinding()]
|
|
34
|
+
param(
|
|
35
|
+
[string]$Code,
|
|
36
|
+
[string]$ApiBase,
|
|
37
|
+
[string]$Repo,
|
|
38
|
+
# Where the runner keeps its own files. One folder, so removing the runner is
|
|
39
|
+
# deleting it. $HOME, not $env:USERPROFILE: it is the same folder on Windows,
|
|
40
|
+
# and it also exists where this file is only ever tested (PowerShell on Linux
|
|
41
|
+
# has no USERPROFILE, and a null default stops the script before its first line).
|
|
42
|
+
[string]$RunnerHome = (Join-Path $HOME '.cloudbongos-runner'),
|
|
43
|
+
# Say what would be done and stop before changing anything.
|
|
44
|
+
[switch]$DryRun
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
$ErrorActionPreference = 'Stop'
|
|
48
|
+
$StopMark = 'autobongos-setup-stopped'
|
|
49
|
+
|
|
50
|
+
function Stop-Setup([string]$What, [string]$Fix) {
|
|
51
|
+
Write-Host ''
|
|
52
|
+
Write-Host "Autobongos setup stopped here: $What" -ForegroundColor Yellow
|
|
53
|
+
Write-Host "What to do: $Fix"
|
|
54
|
+
throw $StopMark
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function Say([string]$Text) { Write-Host $Text }
|
|
58
|
+
|
|
59
|
+
# A fresh install changes PATH in the registry, not in this window.
|
|
60
|
+
function Update-PathFromRegistry {
|
|
61
|
+
$machine = [Environment]::GetEnvironmentVariable('Path', 'Machine')
|
|
62
|
+
$user = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
63
|
+
$env:Path = (@($machine, $user) | Where-Object { $_ }) -join ';'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function Test-Tool([string]$Name) { return [bool](Get-Command $Name -ErrorAction SilentlyContinue) }
|
|
67
|
+
|
|
68
|
+
# Find a tool, or install it with winget and look again.
|
|
69
|
+
function Assert-Tool([string]$Name, [string]$WingetId, [string]$Url) {
|
|
70
|
+
if (Test-Tool $Name) { Say " found $Name"; return }
|
|
71
|
+
if ($DryRun) { Say " would install $Name ($WingetId)"; return }
|
|
72
|
+
if (-not (Test-Tool 'winget')) {
|
|
73
|
+
Stop-Setup "$Name is not installed, and this PC has no winget to install it with." "Install $Name from $Url, open a new PowerShell window, and paste the setup command again."
|
|
74
|
+
}
|
|
75
|
+
Say " installing $Name (Windows may ask you to allow it)..."
|
|
76
|
+
& winget install --id $WingetId -e --source winget --accept-package-agreements --accept-source-agreements --silent | Out-Null
|
|
77
|
+
Update-PathFromRegistry
|
|
78
|
+
if (-not (Test-Tool $Name)) {
|
|
79
|
+
Stop-Setup "$Name did not install." "Install $Name from $Url, open a new PowerShell window, and paste the setup command again."
|
|
80
|
+
}
|
|
81
|
+
Say " installed $Name"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# The whole setup, in the open (not a function): what node and git print must
|
|
85
|
+
# reach the person as it happens, and node must keep the keyboard for the Claude
|
|
86
|
+
# sign-in. A stop it can explain throws $StopMark and lands in the catch below.
|
|
87
|
+
$SetupExit = 1
|
|
88
|
+
try {
|
|
89
|
+
if (-not $Code -or -not $ApiBase -or -not $Repo) {
|
|
90
|
+
Stop-Setup 'the setup command is incomplete.' 'Copy the whole command again from Settings > Your Autobongos runner.'
|
|
91
|
+
}
|
|
92
|
+
if ($Repo -notmatch '^https://[A-Za-z0-9.-]+/[A-Za-z0-9._/-]+$') {
|
|
93
|
+
Stop-Setup 'the project address in the command does not look right.' 'Copy the whole command again from Settings > Your Autobongos runner.'
|
|
94
|
+
}
|
|
95
|
+
# The one-time code is sent to this address and the runner's login comes back
|
|
96
|
+
# from it, so it must be https. Plain http is for this machine alone (a
|
|
97
|
+
# developer's own instance); autobongos-host.js applies the same rule again.
|
|
98
|
+
if ($ApiBase -notmatch '^https://[A-Za-z0-9.-]+(:\d+)?/?$' -and $ApiBase -notmatch '^http://(localhost|127\.0\.0\.1)(:\d+)?/?$') {
|
|
99
|
+
Stop-Setup "the project's address in the command is not a secure (https) address." 'Copy the whole command again from Settings > Your Autobongos runner.'
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Say 'Setting this PC up as an Autobongos runner.'
|
|
103
|
+
Say 'It gets its own copy of the project and its own sign-in; your own work on this PC is not touched.'
|
|
104
|
+
Say ''
|
|
105
|
+
Say 'Checking the three programs it needs...'
|
|
106
|
+
Assert-Tool 'git' 'Git.Git' 'https://git-scm.com/download/win'
|
|
107
|
+
Assert-Tool 'node' 'OpenJS.NodeJS.LTS' 'https://nodejs.org'
|
|
108
|
+
|
|
109
|
+
if (Test-Tool 'claude') {
|
|
110
|
+
Say ' found claude'
|
|
111
|
+
} elseif ($DryRun) {
|
|
112
|
+
Say ' would install Claude Code'
|
|
113
|
+
} else {
|
|
114
|
+
Say ' installing Claude Code...'
|
|
115
|
+
# npm.cmd, not npm: PowerShell blocks npm.ps1 by default (task 1005033).
|
|
116
|
+
& npm.cmd install -g '@anthropic-ai/claude-code' | Out-Null
|
|
117
|
+
Update-PathFromRegistry
|
|
118
|
+
if (-not (Test-Tool 'claude')) {
|
|
119
|
+
Stop-Setup 'Claude Code did not install.' 'Install it from https://claude.com/claude-code, open a new PowerShell window, and paste the setup command again.'
|
|
120
|
+
}
|
|
121
|
+
Say ' installed Claude Code'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
$RepoDir = Join-Path $RunnerHome 'repo'
|
|
125
|
+
Say ''
|
|
126
|
+
if ($DryRun) {
|
|
127
|
+
Say "Would make the runner's copy of the project in $RepoDir and hand over to autobongos-host.js."
|
|
128
|
+
$SetupExit = 0
|
|
129
|
+
} else {
|
|
130
|
+
|
|
131
|
+
New-Item -ItemType Directory -Force -Path $RunnerHome | Out-Null
|
|
132
|
+
if (Test-Path (Join-Path $RepoDir '.git')) {
|
|
133
|
+
Say "The runner's copy is already here; bringing it up to date..."
|
|
134
|
+
& git -C $RepoDir fetch --quiet origin main
|
|
135
|
+
if ($LASTEXITCODE -ne 0) {
|
|
136
|
+
Stop-Setup "this PC could not fetch the project's code from GitHub." 'Sign in to GitHub when Windows asks, then paste the setup command again.'
|
|
137
|
+
}
|
|
138
|
+
& git -C $RepoDir reset --quiet --hard origin/main
|
|
139
|
+
} else {
|
|
140
|
+
Say "Making the runner's own copy of the project (GitHub may ask you to sign in)..."
|
|
141
|
+
& git clone --quiet $Repo $RepoDir
|
|
142
|
+
if ($LASTEXITCODE -ne 0) {
|
|
143
|
+
Stop-Setup 'this PC could not copy the project from GitHub.' 'Sign in to GitHub in the window that opens (use an account that can see the project), then paste the setup command again.'
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
$HostJs = Join-Path $RepoDir 'scripts\bongos\autobongos-host.js'
|
|
148
|
+
if (-not (Test-Path $HostJs)) {
|
|
149
|
+
Stop-Setup "the project's copy has no runner program in it." 'The project is on an older version. Ask the project owner to update it, then try again.'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
$env:AUTOBONGOS_HOME = $RunnerHome
|
|
153
|
+
& node $HostJs setup --code $Code --api-base $ApiBase --mark-runner-copy
|
|
154
|
+
$SetupExit = $LASTEXITCODE
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
if ("$($_.Exception.Message)" -ne $StopMark) {
|
|
159
|
+
Write-Host ''
|
|
160
|
+
Write-Host 'Autobongos setup stopped on something unexpected.' -ForegroundColor Yellow
|
|
161
|
+
Write-Host 'What to do: paste the setup command again. If it stops here twice, tell the project owner.'
|
|
162
|
+
}
|
|
163
|
+
$SetupExit = 1
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# As a FILE, report the result the way a script does. PASTED (a script block has
|
|
167
|
+
# no file of its own), leave the window open and only record the result.
|
|
168
|
+
if ($MyInvocation.MyCommand.Path) { exit $SetupExit }
|
|
169
|
+
$global:LASTEXITCODE = $SetupExit
|