@bongos/core 1.20.80 → 1.20.81

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.
@@ -0,0 +1,156 @@
1
+ // modules/hall-ui/public/settings-autobongos.js — the "Your Autobongos runner"
2
+ // panel on /builders/settings (task 1004501).
3
+ //
4
+ // WHY THE PANEL EXISTS. The fence used to be one switch and one goal list for
5
+ // every runner on the instance. Owner ruling, 2026-10-01: each builder says which
6
+ // work THEIR runner takes, and a builder who has said nothing gets no autonomous
7
+ // work. This is where they say it.
8
+ //
9
+ // WHAT IT READS. GET /autonomy/fence answers with the caller's EFFECTIVE fence: the
10
+ // project allowlist narrowed to their picks, plus `scope` (what they picked, whether
11
+ // they may run a runner, whether the owner paused them) and `project` (the
12
+ // unnarrowed allowlist they choose from). One read, so the panel cannot disagree
13
+ // with what the builder's runner is told. The server decides every one of those
14
+ // facts; this page only draws them.
15
+ //
16
+ // WHAT IT WRITES. PUT /autonomy/me/goals with the whole list, on Save. The route
17
+ // refuses a goal that is not allowlisted (409), and it is behind `autonomy.run`, so
18
+ // a builder who may not run a runner is told so here instead of shown a form that
19
+ // would fail.
20
+ //
21
+ // Its own file on the settings-interaction.js precedent: settings.js sits against
22
+ // the 1500-line ratchet, and this panel shares nothing with the rest of the page.
23
+
24
+ (() => {
25
+ 'use strict';
26
+
27
+ const API = '/api/bongos';
28
+ const { escapeHtml, $ } = window.OTB;
29
+ const { emptyStateHtml } = window.OTBKit;
30
+ const toast = (msg) => window.OTB.toast(msg);
31
+ const api = window.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
32
+
33
+ const toAuth = () => window.location.assign(`${API}/auth/web/start`);
34
+
35
+ // The lines above the form: each one says why the runner might be idle, in the
36
+ // order the server checks them, so the first line a builder reads is the reason.
37
+ function noticesHtml(fence) {
38
+ const project = fence.project || {};
39
+ const scope = fence.scope || {};
40
+ const out = [];
41
+ if (project.enabled !== true) {
42
+ out.push(`The project's runner switch is off right now${project.paused_reason ? `: ${escapeHtml(project.paused_reason)}` : ''}. Your choices are kept and apply when it is back on.`);
43
+ }
44
+ if (scope.owner_paused) {
45
+ out.push(`The owner paused your runner${scope.owner_paused_reason ? `: ${escapeHtml(scope.owner_paused_reason)}` : ''}. Only the owner can resume it.`);
46
+ }
47
+ return out.map((t) => `<p class="prow__hint">${t}</p>`).join('');
48
+ }
49
+
50
+ // Goal names, by id. The fence carries only ids and the owner's note, so the
51
+ // names come from GET /goals — the list the Goals page draws for this same
52
+ // builder, so it names only goals they can already see. Best-effort: a failed
53
+ // read leaves the map empty and a row falls back to its number.
54
+ let goalNames = new Map();
55
+ async function loadGoalNames() {
56
+ try {
57
+ const res = await api.request('GET', `${API}/goals`);
58
+ if (res.ok && res.data && Array.isArray(res.data.goals)) {
59
+ goalNames = new Map(res.data.goals.map((g) => [Number(g.id), g.title]));
60
+ }
61
+ } catch (_) { /* names are a nicety; the picker works without them */ }
62
+ }
63
+ const goalLabel = (id) => goalNames.get(Number(id)) || `Goal ${id}`;
64
+
65
+ function render(fence) {
66
+ const body = $('#autobongos-body');
67
+ if (!body) return;
68
+ const scope = fence.scope || {};
69
+ if (scope.may_run !== true) {
70
+ body.innerHTML = emptyStateHtml('Your rank cannot run an Autobongos runner on this project. Ask the owner if you need one.');
71
+ return;
72
+ }
73
+ const goals = (fence.project && Array.isArray(fence.project.goals)) ? fence.project.goals : [];
74
+ if (!goals.length) {
75
+ body.innerHTML = noticesHtml(fence) + emptyStateHtml('The project has not allowed any goals for runners yet, so there is nothing to choose from.');
76
+ return;
77
+ }
78
+ const picked = new Set((scope.goals || []).map(Number));
79
+ body.innerHTML = `${noticesHtml(fence)}
80
+ <form id="autobongos-form">
81
+ <div class="prows">
82
+ ${goals.map((g) => {
83
+ const id = `autobongos-goal-${escapeHtml(String(g.goal_id))}`;
84
+ return `<label class="prow prow--pick" for="${id}">
85
+ <input type="checkbox" id="${id}" name="autobongos-goal" value="${escapeHtml(String(g.goal_id))}"${picked.has(Number(g.goal_id)) ? ' checked' : ''}>
86
+ <span class="prow__main">
87
+ <span class="prow__label">${escapeHtml(goalLabel(g.goal_id))}</span>
88
+ <span class="prow__desc">${escapeHtml([goalNames.has(Number(g.goal_id)) ? `goal ${g.goal_id}` : '', g.note || ''].filter(Boolean).join(' · '))}</span>
89
+ </span>
90
+ </label>`;
91
+ }).join('')}
92
+ </div>
93
+ <div class="settings-card">
94
+ <button type="submit" class="settings-btn" id="autobongos-save">Save</button>
95
+ <span class="settings-card__hint">${picked.size ? `Your runner works ${picked.size} goal${picked.size === 1 ? '' : 's'}.` : 'Your runner takes no work until you choose at least one goal.'}</span>
96
+ </div>
97
+ </form>`;
98
+ $('#autobongos-form').addEventListener('submit', (e) => {
99
+ e.preventDefault();
100
+ const ids = [...e.target.querySelectorAll('input[name="autobongos-goal"]:checked')].map((i) => Number(i.value));
101
+ save(ids);
102
+ });
103
+ }
104
+
105
+ async function load() {
106
+ const res = await api.request('GET', `${API}/autonomy/fence`);
107
+ if (res.status === 401) { toAuth(); return null; }
108
+ if (!res.ok) throw new Error(`fence GET → ${res.status}`);
109
+ return res.data;
110
+ }
111
+
112
+ async function save(goalIds) {
113
+ const status = $('#autobongos-status');
114
+ const btn = $('#autobongos-save');
115
+ if (status) status.textContent = 'Saving…';
116
+ if (btn) btn.disabled = true;
117
+ try {
118
+ const res = await api.request('PUT', `${API}/autonomy/me/goals`, { body: { goal_ids: goalIds } });
119
+ if (res.status === 401) { toAuth(); return; }
120
+ if (!res.ok) {
121
+ const env = res.data && res.data.error;
122
+ throw new Error((env && env.message) || `could not save (${res.status})`);
123
+ }
124
+ const msg = goalIds.length ? `Saved — your runner works ${goalIds.length} goal${goalIds.length === 1 ? '' : 's'}.` : 'Saved — your runner takes no work.';
125
+ if (status) status.textContent = msg;
126
+ toast(msg);
127
+ const fence = await load();
128
+ if (fence) render(fence);
129
+ } catch (err) {
130
+ console.error('[settings] save autobongos goals failed', err);
131
+ if (status) status.textContent = `Could not save: ${err.message}`;
132
+ toast('Could not save your runner’s goals. Try again.');
133
+ if (btn) btn.disabled = false;
134
+ }
135
+ }
136
+
137
+ async function setup() {
138
+ const section = $('#autobongos-scroll');
139
+ if (!section) return;
140
+ try {
141
+ const [fence] = await Promise.all([load(), loadGoalNames()]);
142
+ if (!fence) return;
143
+ render(fence);
144
+ section.hidden = false;
145
+ } catch (err) {
146
+ // Its own try/catch, like every other panel: a failing read shows this
147
+ // panel's own error rather than taking the settings page down with it.
148
+ console.error('[settings] failed to load autobongos scope', err);
149
+ section.hidden = false;
150
+ $('#autobongos-body').innerHTML =
151
+ '<p class="voices-empty">Your runner settings could not be loaded. Reload the page to try again.</p>';
152
+ }
153
+ }
154
+
155
+ setup();
156
+ })();
@@ -163,6 +163,23 @@
163
163
  <p class="section-note">Ending a session takes effect immediately; the token stops working on its very next request.</p>
164
164
  </section>
165
165
 
166
+ <!-- YOUR AUTOBONGOS RUNNER — which goals your own unattended runner
167
+ works (task 1004501). Rendered by settings-autobongos.js against
168
+ GET /autonomy/fence (your effective fence) and PUT
169
+ /autonomy/me/goals. Picking nothing means your runner takes no
170
+ work. The project allowlist and the master switch stay the
171
+ owner's, on the Gate page. -->
172
+ <section class="scroll" id="autobongos-scroll" aria-labelledby="autobongos-h" data-group="access" hidden>
173
+ <h2 id="autobongos-h" class="scroll__h">Your Autobongos runner</h2>
174
+ <p class="scroll__lede">
175
+ Choose which goals your own runner works while you are away. You can
176
+ only choose from the goals the project allows. If you choose none, your
177
+ runner takes no work.
178
+ </p>
179
+ <div id="autobongos-body" aria-live="polite"></div>
180
+ <p class="settings-status" id="autobongos-status" role="status" aria-live="polite"></p>
181
+ </section>
182
+
166
183
  <!-- IMAGE KEY — per-builder Gemini key (task 1013 / ADR 0073).
167
184
  GET/PUT/DELETE /api/bongos/me/art-key/own. Write-only (set/replace/
168
185
  remove); the full key is never shown again after save (masked,
@@ -425,5 +442,7 @@
425
442
  <script src="/builders/settings-render.js?v=2026-09-16-render-levels"></script>
426
443
  <!-- task 1004296: Settings → Software update. Own file on the same precedent. -->
427
444
  <script src="/builders/settings-software-update.js?v=2026-09-26-update"></script>
445
+ <!-- task 1004501: Settings → Your Autobongos runner. Own file on the same precedent. -->
446
+ <script src="/builders/settings-autobongos.js?v=2026-10-01-autobongos"></script>
428
447
  </body>
429
448
  </html>
@@ -1066,6 +1066,7 @@
1066
1066
  'privacy': 'account-hub-scroll',
1067
1067
  'disciplines': 'craft-scroll',
1068
1068
  'cli': 'cli-scroll',
1069
+ 'autobongos': 'autobongos-scroll',
1069
1070
  'art-key': 'art-key',
1070
1071
  'interaction': 'interaction-scroll',
1071
1072
  'wandering': 'wandering-scroll',
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.20.80",
3
+ "version": "1.20.81",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.20.80",
9
+ "version": "1.20.81",
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.20.80",
3
+ "version": "1.20.81",
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",
@@ -8625,5 +8625,11 @@
8625
8625
  "id": "1003907",
8626
8626
  "text": "Autobongos has now been proven against the live project: it shipped work by itself with nobody watching, refused every kind of work it is not allowed to touch, and recovered from restarts and network failures on its own. The o"
8627
8627
  }
8628
+ ],
8629
+ "1.20.81": [
8630
+ {
8631
+ "id": "1004501",
8632
+ "text": "Each builder now chooses which goals their own Autobongos runner works, in Settings, from the goals the project allows. A builder who chooses nothing gets no unattended work. Who may run a runner at all is now a project permis"
8633
+ }
8628
8634
  ]
8629
8635
  }
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.20.80'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.20.81'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');