@bongos/core 1.24.95 → 1.25.1
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 +97 -57
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +6 -0
- package/clients/bongos-client/index.cjs +6 -0
- package/clients/bongos-client/index.d.ts +9 -0
- package/clients/bongos-client/index.mjs +6 -0
- package/docs/api/openapi.json +179 -3
- package/docs/api-reference.md +6 -3
- package/docs/architecture.md +3 -1
- package/docs/copy-inventory.md +7 -7
- package/docs/copy-registry.json +7 -7
- package/docs/module-api-changelog.md +6 -0
- package/docs/page-inventory.json +2 -0
- package/docs/page-readings.json +6 -6
- package/migrations/core_298_card_step_up.sql +16 -0
- package/modules/autonomy/db.js +48 -0
- package/modules/autonomy/routes/autonomy.js +15 -0
- package/modules/economy/module.json +1 -1
- package/modules/economy/processor-stripe.js +292 -0
- package/modules/economy/routes/balances.js +4 -3
- package/modules/economy/routes/project-account.js +12 -1
- package/modules/economy/tests/processor_stripe.mjs +135 -0
- package/modules/government/catalog.js +1 -1
- package/modules/government/routes/government.js +2 -2
- package/modules/hall-ui/public/home-city-draw.js +24 -2
- package/modules/hall-ui/public/home-city.css +15 -0
- package/modules/hall-ui/public/home-city.js +3 -1
- package/modules/hall-ui/public/home-runner.js +248 -0
- package/modules/hall-ui/public/index.html +8 -5
- package/modules/hall-ui/public/visor.css +27 -0
- package/modules/hall-ui/public/visor.js +65 -2
- package/modules/hall-ui/records/home-runner.md +19 -0
- package/modules/hall-ui/records/visor.md +4 -0
- package/modules/render-deploy/routes/act.js +1 -1
- package/modules/render-deploy/routes/app.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +17 -0
- package/scripts/bongos/fitness.js +1 -1
- package/scripts/bongos/government-walk.js +2 -2
- package/scripts/hall-preview/server.js +4 -0
- package/src/bongos/passkeys.js +30 -0
- package/src/bongos/routes/auth.js +2 -2
- package/src/bongos/routes/builders.js +2 -1
- package/src/bongos/routes/module-payouts.js +4 -2
- package/src/bongos/routes/my-cash-outs.js +5 -3
- package/src/bongos/routes/my-passkeys.js +70 -0
- package/src/bongos/routes/payout-approvals.js +5 -3
- package/src/bongos/step-up.js +42 -0
- package/src/module-api.js +5 -1
- package/tests/autonomy_runner_goal_stats.mjs +92 -0
- package/tests/government_act_alone_buildings.mjs +2 -1
- package/tests/government_treasury.mjs +2 -1
- package/tests/hall_visor.mjs +20 -0
- package/tests/home_runner.mjs +115 -0
- package/tests/module_api.mjs +1 -0
- package/tests/my_passkeys.mjs +47 -1
- package/tests/rank_change_broadcast_port.mjs +2 -0
|
@@ -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
|
|
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
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// modules/hall-ui/public/home-runner.js — your Autobongos runner on Home's city
|
|
2
|
+
// (task 1005243, goal 1000142).
|
|
3
|
+
//
|
|
4
|
+
// WHY. Owner direction, 2026-11-12: running your runner should feel like putting a
|
|
5
|
+
// builder on a building in a base-building game. In game mode you tap a tower (a
|
|
6
|
+
// goal), the visor offers "Send runner here", and the tower then carries your
|
|
7
|
+
// runner's marker; tap it again and the visor shows what it has done there and
|
|
8
|
+
// "Recall runner". The owner chose that a runner works ONE tower at a time, so
|
|
9
|
+
// sending it sets your goal picks to that one goal.
|
|
10
|
+
//
|
|
11
|
+
// THE RULES IT KEEPS.
|
|
12
|
+
// - ADR 0403: the scene is the view and the visor is the control. The tower only
|
|
13
|
+
// gets picked; every button is on the glass (OTBVisor.focus).
|
|
14
|
+
// - The server decides who may run a runner: GET /autonomy/fence answers
|
|
15
|
+
// scope.may_run, and only then is a tower pickable. Signed out, a rank without
|
|
16
|
+
// `autonomy.run`, or classic mode: towers stay plain links and nothing is new.
|
|
17
|
+
// - Every write is a route Settings already uses: PUT /autonomy/me/goals and
|
|
18
|
+
// POST /autonomy/runners/:host/run. The numbers come from GET
|
|
19
|
+
// /autonomy/me/goals/:id/stats (the ledger: runner claims shipped there, the
|
|
20
|
+
// tokens their sessions took, the tasks still ready).
|
|
21
|
+
// - The device's face (working, resting until, off…) is runner-button.js's
|
|
22
|
+
// buttonState, the same decision the Settings card draws.
|
|
23
|
+
//
|
|
24
|
+
// The decision (cardFor, stateOf) is PURE and exported for node tests; the wiring
|
|
25
|
+
// below it only runs in a browser.
|
|
26
|
+
(function (root) {
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const RB = () => (root && root.OTBRunnerButton) || (typeof require === 'function' ? require('./runner-button.js') : null);
|
|
30
|
+
|
|
31
|
+
// ---------- pure ----------
|
|
32
|
+
|
|
33
|
+
// The device that speaks for "your runner": one that is working, else one that is
|
|
34
|
+
// switched on, else the most recently seen (the server's order).
|
|
35
|
+
function leadDevice(runners) {
|
|
36
|
+
const list = Array.isArray(runners) ? runners.filter(Boolean) : [];
|
|
37
|
+
return list.find((r) => r.health && r.health.workingTaskId) ||
|
|
38
|
+
list.find((r) => r.run_requested !== false) || list[0] || null;
|
|
39
|
+
}
|
|
40
|
+
function anyOn(runners) { return (Array.isArray(runners) ? runners : []).some((r) => r && r.run_requested !== false); }
|
|
41
|
+
|
|
42
|
+
// The goal the runner is sent to: exactly one pick. Several picks (made in Settings)
|
|
43
|
+
// is not one tower, so no tower carries the marker until the builder sends it.
|
|
44
|
+
function assignedGoal(fence) {
|
|
45
|
+
const g = fence && fence.scope && Array.isArray(fence.scope.goals) ? fence.scope.goals : [];
|
|
46
|
+
return g.length === 1 ? String(g[0]) : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The marker's state, from the device's face: working | resting | starting | ready | off.
|
|
50
|
+
function stateOf({ runners, fence, nowMs } = {}) {
|
|
51
|
+
const d = leadDevice(runners);
|
|
52
|
+
if (!d || !anyOn(runners)) return 'off';
|
|
53
|
+
const b = RB().buttonState({ runner: d, fence, nowMs });
|
|
54
|
+
if (d.health && d.health.workingTaskId && b.status === 'Running') return 'working';
|
|
55
|
+
if (/^Resting/.test(b.status)) return 'resting';
|
|
56
|
+
if (b.status === 'Starting…' || b.status === 'Updating…') return 'starting';
|
|
57
|
+
if (b.status === 'Running' || b.status === 'Ready') return 'ready';
|
|
58
|
+
return 'off';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 1.2M tokens, 340k tokens, 900 tokens.
|
|
62
|
+
function tokensText(n) {
|
|
63
|
+
const v = Number(n) || 0;
|
|
64
|
+
if (v >= 1e6) return `${(v / 1e6).toFixed(v >= 1e7 ? 0 : 1).replace(/\.0$/, '')}M tokens`;
|
|
65
|
+
if (v >= 1e3) return `${Math.round(v / 1e3)}k tokens`;
|
|
66
|
+
return `${Math.round(v)} tokens`;
|
|
67
|
+
}
|
|
68
|
+
// "about 40 min", "about 3 h", "about 2 days": an estimate, so rounded like one.
|
|
69
|
+
function aboutText(seconds) {
|
|
70
|
+
const s = Number(seconds);
|
|
71
|
+
if (!Number.isFinite(s) || s <= 0) return '';
|
|
72
|
+
const min = s / 60;
|
|
73
|
+
if (min < 60) return `about ${Math.max(5, Math.round(min / 5) * 5)} min`;
|
|
74
|
+
const h = min / 60;
|
|
75
|
+
if (h < 36) return `about ${Math.round(h)} h`;
|
|
76
|
+
return `about ${Math.round(h / 24)} days`;
|
|
77
|
+
}
|
|
78
|
+
function plural(n, one, many) { return `${n} ${n === 1 ? one : many}`; }
|
|
79
|
+
|
|
80
|
+
// What the visor's card says for one tower. Returns null when the viewer has no
|
|
81
|
+
// runner to send (the tower then stays a plain link), else
|
|
82
|
+
// { title, status: { text, tone }, lines: [string], lead: { label, act } | null }
|
|
83
|
+
// act: 'send' | 'recall' | 'setup' | 'open'.
|
|
84
|
+
function cardFor({ goal, runners, fence, stats, nowMs = Date.now(), titleOf } = {}) {
|
|
85
|
+
if (!goal || !fence || !fence.scope || fence.scope.may_run !== true) return null;
|
|
86
|
+
const id = String(goal.id);
|
|
87
|
+
const title = goal.title || `Goal ${id}`;
|
|
88
|
+
const nameOf = (g) => (typeof titleOf === 'function' && titleOf(g)) || `goal ${g}`;
|
|
89
|
+
const list = Array.isArray(runners) ? runners : [];
|
|
90
|
+
const st = stats || null;
|
|
91
|
+
const done = st ? Number(st.tasks_done) || 0 : 0;
|
|
92
|
+
const left = st ? Number(st.tasks_left) || 0 : null;
|
|
93
|
+
const eta = st && left ? aboutText(left * (Number(st.avg_task_seconds) || 0)) : '';
|
|
94
|
+
const leftLine = left == null ? null : left === 0 ? 'Nothing ready to build here right now.' : `${plural(left, 'task', 'tasks')} ready${eta ? ` · ${eta} for your runner` : ''}`;
|
|
95
|
+
const doneLine = st && done ? `${plural(done, 'task', 'tasks')} done here · ${tokensText(st.tokens)}` : null;
|
|
96
|
+
|
|
97
|
+
if (!list.length) {
|
|
98
|
+
return { title, status: { text: 'No runner yet', tone: 'neutral' },
|
|
99
|
+
lines: ['Set it up on your PC once, then send it to any tower.'], lead: { label: 'Set up a runner', act: 'setup' } };
|
|
100
|
+
}
|
|
101
|
+
const allowed = fence.project && Array.isArray(fence.project.goals) ? fence.project.goals.map(String) : [];
|
|
102
|
+
if (!allowed.includes(id)) {
|
|
103
|
+
return { title, status: { text: 'Closed to runners', tone: 'neutral' },
|
|
104
|
+
lines: ['The project has not opened this goal to runners yet.'], lead: { label: 'Open the goal', act: 'open' } };
|
|
105
|
+
}
|
|
106
|
+
const here = assignedGoal(fence) === id;
|
|
107
|
+
const on = anyOn(list);
|
|
108
|
+
if (here && on) {
|
|
109
|
+
const b = RB().buttonState({ runner: leadDevice(list), fence, nowMs });
|
|
110
|
+
const face = b.status ? `${b.status}${b.countdown ? ` · ${b.countdown}` : ''}` : 'Off';
|
|
111
|
+
return { title, status: { text: face, tone: b.tone || 'neutral' },
|
|
112
|
+
lines: [b.sub, doneLine, leftLine].filter(Boolean), lead: { label: 'Recall runner', act: 'recall' } };
|
|
113
|
+
}
|
|
114
|
+
const where = on && assignedGoal(fence) ? `Your runner is at ${nameOf(assignedGoal(fence))}` :
|
|
115
|
+
on ? 'Your runner is working on several goals' : 'Your runner is resting';
|
|
116
|
+
return { title, status: { text: here ? 'Recalled' : where, tone: 'neutral' },
|
|
117
|
+
lines: [doneLine, leftLine].filter(Boolean), lead: { label: 'Send runner here', act: 'send' } };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const api = { cardFor, stateOf, assignedGoal, leadDevice, tokensText, aboutText };
|
|
121
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
122
|
+
if (!root || typeof document === 'undefined') return;
|
|
123
|
+
root.OTBHomeRunnerLib = api;
|
|
124
|
+
|
|
125
|
+
// ---------- the wiring (browser only) ----------
|
|
126
|
+
const API = '/api/bongos';
|
|
127
|
+
const POLL_MS = 15000;
|
|
128
|
+
let fence = null, runners = [], picked = null, stats = null, busy = false, error = '', timer = null;
|
|
129
|
+
let client = null;
|
|
130
|
+
function http() {
|
|
131
|
+
if (!client && root.BongosClient) client = root.BongosClient.createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false });
|
|
132
|
+
return client;
|
|
133
|
+
}
|
|
134
|
+
async function req(method, path, body) {
|
|
135
|
+
const c = http();
|
|
136
|
+
if (!c) return null;
|
|
137
|
+
try { return await c.request(method, API + path, body ? { body } : undefined); } catch (_) { return null; }
|
|
138
|
+
}
|
|
139
|
+
function game() { return document.documentElement.dataset.game === 'on'; }
|
|
140
|
+
function mayRun() { return !!(fence && fence.scope && fence.scope.may_run === true); }
|
|
141
|
+
function active() { return game() && mayRun() && !!root.OTBVisor && typeof root.OTBVisor.focus === 'function'; }
|
|
142
|
+
function towerOf(id) { return document.querySelector(`.home-city .hc-tower[data-goal="${CSS.escape(String(id))}"]`); }
|
|
143
|
+
function titleOf(id) { const t = towerOf(id); return t ? t.getAttribute('data-tip') : null; }
|
|
144
|
+
|
|
145
|
+
// What home-city.js draws: the marker on the assigned tower, and the picked tower.
|
|
146
|
+
function marker() {
|
|
147
|
+
if (!active()) return null;
|
|
148
|
+
const goal = assignedGoal(fence);
|
|
149
|
+
return goal ? { goal, state: stateOf({ runners, fence, nowMs: Date.now() }) } : null;
|
|
150
|
+
}
|
|
151
|
+
function changed() { document.dispatchEvent(new CustomEvent('otb:home-runner')); }
|
|
152
|
+
|
|
153
|
+
async function readState() {
|
|
154
|
+
const [f, r] = await Promise.all([req('GET', '/autonomy/fence'), req('GET', '/autonomy/runners')]);
|
|
155
|
+
fence = f && f.ok && f.data ? f.data : null;
|
|
156
|
+
runners = r && r.ok && r.data && Array.isArray(r.data.runners) ? r.data.runners : [];
|
|
157
|
+
}
|
|
158
|
+
async function readStats() {
|
|
159
|
+
if (!picked) return;
|
|
160
|
+
const s = await req('GET', `/autonomy/me/goals/${encodeURIComponent(picked)}/stats`);
|
|
161
|
+
stats = s && s.ok && s.data ? s.data : null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function show() {
|
|
165
|
+
const V = root.OTBVisor;
|
|
166
|
+
if (!V || typeof V.focus !== 'function') return;
|
|
167
|
+
if (!picked || !active()) { V.focus(null); return; }
|
|
168
|
+
const card = cardFor({ goal: { id: picked, title: titleOf(picked) }, runners, fence, stats, nowMs: Date.now(), titleOf });
|
|
169
|
+
if (!card) { V.focus(null); return; }
|
|
170
|
+
const lead = card.lead ? { label: busy ? (card.lead.act === 'recall' ? 'Recalling…' : 'Sending…') : card.lead.label, busy, run: () => act(card.lead.act) } : null;
|
|
171
|
+
V.focus({ key: `runner:${picked}`, title: card.title, status: card.status, lines: error ? [error].concat(card.lines) : card.lines, lead, onClose: unpick });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function act(what) {
|
|
175
|
+
if (busy || !picked) return;
|
|
176
|
+
if (what === 'open') { root.location.assign(`/goals#/goal/${encodeURIComponent(picked)}`); return; }
|
|
177
|
+
if (what === 'setup') { root.location.assign('/settings#autobongos'); return; }
|
|
178
|
+
busy = true; error = ''; show();
|
|
179
|
+
let ok = true;
|
|
180
|
+
if (what === 'send') {
|
|
181
|
+
const g = await req('PUT', '/autonomy/me/goals', { goal_ids: [Number(picked)] });
|
|
182
|
+
ok = !!(g && g.ok);
|
|
183
|
+
if (ok) {
|
|
184
|
+
const outs = await Promise.all(runners.filter((r) => r.run_requested === false)
|
|
185
|
+
.map((r) => req('POST', `/autonomy/runners/${encodeURIComponent(r.host)}/run`, { run: true })));
|
|
186
|
+
ok = outs.every((o) => o && o.ok);
|
|
187
|
+
}
|
|
188
|
+
} else if (what === 'recall') {
|
|
189
|
+
const outs = await Promise.all(runners.filter((r) => r.run_requested !== false)
|
|
190
|
+
.map((r) => req('POST', `/autonomy/runners/${encodeURIComponent(r.host)}/run`, { run: false })));
|
|
191
|
+
ok = outs.every((o) => o && o.ok);
|
|
192
|
+
}
|
|
193
|
+
if (!ok) error = what === 'send' ? 'Could not send your runner. Try again.' : 'Could not recall your runner. Try again.';
|
|
194
|
+
await readState();
|
|
195
|
+
await readStats();
|
|
196
|
+
busy = false;
|
|
197
|
+
show();
|
|
198
|
+
changed();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function pick(id) {
|
|
202
|
+
picked = String(id); stats = null; error = '';
|
|
203
|
+
show(); changed();
|
|
204
|
+
readStats().then(() => { if (picked === String(id)) show(); });
|
|
205
|
+
}
|
|
206
|
+
// The visor's card closed (Escape, its close mark, or a tap on open ground).
|
|
207
|
+
function unpick() {
|
|
208
|
+
if (!picked) return;
|
|
209
|
+
picked = null; stats = null; error = '';
|
|
210
|
+
changed();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// A tap on a tower in game mode picks it instead of leaving the page; a tap on open
|
|
214
|
+
// ground lets the pick go. Capture, so it runs before the tower's own link.
|
|
215
|
+
function onClick(e) {
|
|
216
|
+
if (!active()) return;
|
|
217
|
+
const city = e.target.closest && e.target.closest('.home-city .hc-svg');
|
|
218
|
+
if (!city) return;
|
|
219
|
+
const t = e.target.closest('.hc-tower[data-goal], .hc-runner[data-goal]');
|
|
220
|
+
if (!t) { if (picked && !e.target.closest('a')) { root.OTBVisor.focus(null); unpick(); } return; }
|
|
221
|
+
e.preventDefault();
|
|
222
|
+
const id = t.getAttribute('data-goal');
|
|
223
|
+
if (picked === id) return;
|
|
224
|
+
pick(id);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function refresh() {
|
|
228
|
+
await readState();
|
|
229
|
+
if (picked) await readStats();
|
|
230
|
+
if (picked) show();
|
|
231
|
+
changed();
|
|
232
|
+
}
|
|
233
|
+
function poll() {
|
|
234
|
+
clearTimeout(timer);
|
|
235
|
+
timer = setTimeout(async () => {
|
|
236
|
+
if (!document.hidden && game() && mayRun() && runners.length) await refresh();
|
|
237
|
+
poll();
|
|
238
|
+
}, POLL_MS);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function start() {
|
|
242
|
+
document.addEventListener('click', onClick, true);
|
|
243
|
+
refresh().then(poll);
|
|
244
|
+
}
|
|
245
|
+
root.OTBHomeRunner = { marker, picked: () => (active() ? picked : null), refresh };
|
|
246
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
|
|
247
|
+
else start();
|
|
248
|
+
})(typeof window !== 'undefined' ? window : null);
|
|
@@ -19,11 +19,11 @@
|
|
|
19
19
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
20
20
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
21
21
|
<link rel="stylesheet" href="/builders/style.css?v=2026-11-11-helm4d">
|
|
22
|
-
<link rel="stylesheet" href="/builders/visor.css?v=2026-11-
|
|
22
|
+
<link rel="stylesheet" href="/builders/visor.css?v=2026-11-12-runner">
|
|
23
23
|
<link rel="stylesheet" href="/builders/welcome-boot.css?v=2026-11-07-brand-mark">
|
|
24
24
|
<link rel="stylesheet" href="/builders/shell-game.css?v=2026-11-11-view2">
|
|
25
25
|
<link rel="stylesheet" href="/builders/hall-ask.css">
|
|
26
|
-
<link rel="stylesheet" href="/builders/home-city.css?v=2026-11-
|
|
26
|
+
<link rel="stylesheet" href="/builders/home-city.css?v=2026-11-12-runner">
|
|
27
27
|
<link rel="stylesheet" href="/builders/home-gallery.css">
|
|
28
28
|
<link rel="stylesheet" href="/builders/home-idea-box.css">
|
|
29
29
|
</head>
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<script src="/builders/shell.js?v=2026-11-11-view2" defer></script>
|
|
117
117
|
<script src="/builders/hall-dialog.js" defer></script>
|
|
118
118
|
<!-- task 1005148: the visor (glass over the scene, game mode only; ADR 0399), after the shell and hall-dialog.js -->
|
|
119
|
-
<script src="/builders/visor.js?v=2026-11-
|
|
119
|
+
<script src="/builders/visor.js?v=2026-11-12-runner" defer></script>
|
|
120
120
|
<!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
|
|
121
121
|
<script src="/builders/palette.js" defer></script>
|
|
122
122
|
<!-- task 2166: the shared component kit (window.OTBKit) — pager/ledger/rail.
|
|
@@ -138,10 +138,13 @@
|
|
|
138
138
|
<script src="/builders/city-draw.js" defer></script>
|
|
139
139
|
<!-- task 1005196: the hour of the city's sky (sets the --sky-now-* tokens the city spends) -->
|
|
140
140
|
<script src="/builders/sky-time.js?v=2026-11-11-helm4" defer></script>
|
|
141
|
-
<script src="/builders/home-city-draw.js?v=2026-11-
|
|
141
|
+
<script src="/builders/home-city-draw.js?v=2026-11-12-runner" defer></script>
|
|
142
142
|
<script src="/builders/home-gallery.js" defer></script>
|
|
143
143
|
<script src="/builders/home-idea-box.js" defer></script>
|
|
144
|
-
|
|
144
|
+
<!-- task 1005243: your runner on Home's towers (game mode; the visor carries its card and button) -->
|
|
145
|
+
<script src="/builders/runner-button.js" defer></script>
|
|
146
|
+
<script src="/builders/home-runner.js" defer></script>
|
|
147
|
+
<script src="/builders/home-city.js?v=2026-11-12-runner" defer></script>
|
|
145
148
|
<!-- task 1003455: the shared blocker-row renderer (window.OTBBlockersLib), read by
|
|
146
149
|
both goal-inbox.js's attention-inbox slice below and the /blockers page.
|
|
147
150
|
Loaded BEFORE goal-inbox.js, which calls it while rendering. -->
|
|
@@ -169,5 +169,32 @@ html[data-game="on"] .visor:not([hidden]) {
|
|
|
169
169
|
.visor__row--hint:hover { color: var(--ink-soft); }
|
|
170
170
|
.visor__sheet .visor__key { color: var(--ink-soft); background: none; }
|
|
171
171
|
|
|
172
|
+
/* the card for what the scene picked (task 1005243): Home's tower and your runner on it. Centred
|
|
173
|
+
over the accent button it hands its one action to, the night panel between two strong
|
|
174
|
+
hairlines like the sheet, never wider than a short paragraph. The words are the scene's facts;
|
|
175
|
+
the status dot is the hall's status vocabulary (--ok / --warn / --info), never a colour of its own. */
|
|
176
|
+
.visor__focus {
|
|
177
|
+
position: absolute; left: 50%; bottom: calc(var(--visor-inset-bottom) + 72px); transform: translateX(-50%);
|
|
178
|
+
pointer-events: auto; box-sizing: border-box; width: min(20rem, calc(100vw - 2 * var(--ds-space-gutter-narrow)));
|
|
179
|
+
padding: var(--ds-space-xs) var(--ds-space-sm) var(--ds-space-sm); background: var(--bg-card);
|
|
180
|
+
border: 0 solid var(--rule); border-left-width: var(--line-strong); border-right-width: var(--line-strong);
|
|
181
|
+
}
|
|
182
|
+
.visor__focus-head { display: flex; align-items: center; justify-content: space-between; gap: var(--ds-space-xs); }
|
|
183
|
+
.visor__focus-title {
|
|
184
|
+
margin: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
185
|
+
font-family: var(--name-face); font-weight: var(--name-weight); text-transform: var(--name-case);
|
|
186
|
+
font-size: var(--ds-text-xl); letter-spacing: var(--name-space); color: var(--ink);
|
|
187
|
+
}
|
|
188
|
+
.visor__focus-status { display: flex; align-items: center; gap: var(--ds-space-xs); margin: 2px 0 0; font-size: var(--ds-text-md); color: var(--ink); }
|
|
189
|
+
.visor__focus-status i { width: 10px; height: 10px; flex: none; background: var(--rule); }
|
|
190
|
+
.visor__focus-status--ok i { background: var(--ok); }
|
|
191
|
+
.visor__focus-status--warn i { background: var(--warn); }
|
|
192
|
+
.visor__focus-status--info i { background: var(--info); }
|
|
193
|
+
.visor__focus-status--danger i { background: var(--danger); }
|
|
194
|
+
.visor__focus-lines { list-style: none; margin: var(--ds-space-xs) 0 0; padding: 0; color: var(--ink-soft); font-size: var(--ds-text-sm); line-height: 1.45; }
|
|
195
|
+
.visor__focus-lines li + li { margin-top: 2px; }
|
|
196
|
+
.visor__focus .visor__key { color: var(--ink-soft); background: none; }
|
|
197
|
+
.visor__main--lead[aria-busy="true"] { cursor: progress; background: var(--accent-lift); }
|
|
198
|
+
|
|
172
199
|
/* a phone (640px and narrower): the rules live in style.css’s shared phone block, so the
|
|
173
200
|
visor adds no breakpoint of its own (the design-count ratchet). */
|
|
@@ -151,6 +151,64 @@
|
|
|
151
151
|
render();
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
// ---------- what the scene has picked (task 1005243) ----------
|
|
155
|
+
// A scene that picks something (Home's tower) hands the visor a small card about it:
|
|
156
|
+
// a title, one status, a few lines, and at most one action. The action becomes the
|
|
157
|
+
// accent button while the card is up (ADR 0403 §4: a scene's lead action reaches the
|
|
158
|
+
// accent button); the character's own main action is back when the card closes.
|
|
159
|
+
// The scene paints the pick; the visor paints the words and the button.
|
|
160
|
+
var focus = null;
|
|
161
|
+
function setFocus(f) {
|
|
162
|
+
var was = focus;
|
|
163
|
+
if (!f || typeof f !== 'object' || !f.title) focus = null;
|
|
164
|
+
else {
|
|
165
|
+
var lead = f.lead && f.lead.label && typeof f.lead.run === 'function' ? { label: String(f.lead.label), run: f.lead.run, busy: !!f.lead.busy } : null;
|
|
166
|
+
focus = {
|
|
167
|
+
key: String(f.key || f.title), title: String(f.title),
|
|
168
|
+
status: f.status && f.status.text ? { text: String(f.status.text), tone: String(f.status.tone || 'neutral') } : null,
|
|
169
|
+
lines: (Array.isArray(f.lines) ? f.lines : []).filter(Boolean).map(String).slice(0, 4),
|
|
170
|
+
lead: lead, onClose: typeof f.onClose === 'function' ? f.onClose : null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (focus && !was) document.addEventListener('keydown', onFocusKey, true);
|
|
174
|
+
if (!focus && was) document.removeEventListener('keydown', onFocusKey, true);
|
|
175
|
+
render();
|
|
176
|
+
}
|
|
177
|
+
// Closed by the person (Escape or the close mark): the scene is told, so it unpicks.
|
|
178
|
+
function dropFocus() {
|
|
179
|
+
var f = focus;
|
|
180
|
+
if (!f) return;
|
|
181
|
+
setFocus(null);
|
|
182
|
+
if (f.onClose) f.onClose();
|
|
183
|
+
}
|
|
184
|
+
// Escape closes the card before it reaches the options menu, but never while the sheet
|
|
185
|
+
// is open: the sheet's own capture listener takes that press.
|
|
186
|
+
function onFocusKey(e) {
|
|
187
|
+
if (!focus || sheet || e.key !== 'Escape' || !shown()) return;
|
|
188
|
+
e.preventDefault();
|
|
189
|
+
e.stopPropagation();
|
|
190
|
+
dropFocus();
|
|
191
|
+
}
|
|
192
|
+
var TONES = { ok: 1, warn: 1, info: 1, neutral: 1, danger: 1 };
|
|
193
|
+
function focusHtml() {
|
|
194
|
+
if (!focus) return '';
|
|
195
|
+
var st = focus.status;
|
|
196
|
+
return '<div class="visor__focus" role="group" aria-label="' + esc(focus.title) + '" aria-live="polite" data-visor-focus>' +
|
|
197
|
+
'<div class="visor__focus-head"><p class="visor__focus-title">' + esc(focus.title) + '</p>' +
|
|
198
|
+
'<button class="visor__close" type="button" data-visor-unfocus aria-label="Close">' + keyHtml('Esc') + '</button></div>' +
|
|
199
|
+
(st ? '<p class="visor__focus-status visor__focus-status--' + (TONES[st.tone] ? st.tone : 'neutral') + '"><i aria-hidden="true"></i>' + esc(st.text) + '</p>' : '') +
|
|
200
|
+
(focus.lines.length ? '<ul class="visor__focus-lines">' + focus.lines.map(function (l) { return '<li>' + esc(l) + '</li>'; }).join('') + '</ul>' : '') +
|
|
201
|
+
'</div>';
|
|
202
|
+
}
|
|
203
|
+
function mainHtml(main) {
|
|
204
|
+
if (focus && focus.lead) {
|
|
205
|
+
return '<button class="visor__main visor__main--lead" type="button" data-visor-lead' + (focus.lead.busy ? ' aria-busy="true"' : '') + '>' +
|
|
206
|
+
'<span>' + esc(focus.lead.label) + '</span></button>';
|
|
207
|
+
}
|
|
208
|
+
return '<a class="visor__main" data-visor-act="' + esc(main.id) + '" ' + actionAttrs(main) + '>' +
|
|
209
|
+
'<span>' + esc(main.label) + '</span>' + keyHtml(main.key) + '</a>';
|
|
210
|
+
}
|
|
211
|
+
|
|
154
212
|
// ---------- drawing ----------
|
|
155
213
|
var root = null, sheet = null, returnFocus = null;
|
|
156
214
|
var NS = 'http://www.w3.org/2000/svg';
|
|
@@ -197,14 +255,14 @@
|
|
|
197
255
|
return frameSvg() +
|
|
198
256
|
(scene.place ? '<p class="visor__place">' + esc(scene.place) + '</p>' : '') +
|
|
199
257
|
progressHtml() +
|
|
258
|
+
focusHtml() +
|
|
200
259
|
'<div class="visor__foot">' +
|
|
201
260
|
'<div class="visor__me">' +
|
|
202
261
|
(sprite ? '<img class="visor__sprite" src="' + esc(sprite) + '" alt="" width="32" height="32" decoding="async">' : '') +
|
|
203
262
|
'<span class="visor__who"><span class="visor__name">' + esc(NAMES[id]) + '</span>' +
|
|
204
263
|
'<span class="visor__credits" data-visor-credits>' + creditsHtml(n) + '</span></span>' +
|
|
205
264
|
'</div>' +
|
|
206
|
-
|
|
207
|
-
'<span>' + esc(main.label) + '</span>' + keyHtml(main.key) + '</a>' +
|
|
265
|
+
mainHtml(main) +
|
|
208
266
|
'<div class="visor__hints">' + scene.actions.map(function (a, i) { return a.lead ? placeHintHtml(a, i) : ''; }).join('') + acts.slice(1).map(hintHtml).join('') +
|
|
209
267
|
'<button class="visor__hint visor__hint--sheet" type="button" data-visor-sheet aria-expanded="false" aria-controls="visor-sheet">' +
|
|
210
268
|
'<span class="visor__dots" aria-hidden="true"></span>' + keyHtml(SHEET_KEY) + '<span>Actions</span></button>' +
|
|
@@ -328,6 +386,8 @@
|
|
|
328
386
|
var t = e.target && e.target.closest ? e.target : null;
|
|
329
387
|
if (!t) return;
|
|
330
388
|
if (t.closest('[data-visor-close]')) { closeSheet(true); return; }
|
|
389
|
+
if (t.closest('[data-visor-unfocus]')) { dropFocus(); return; }
|
|
390
|
+
if (t.closest('[data-visor-lead]')) { if (focus && focus.lead && !focus.lead.busy) { closeSheet(false); focus.lead.run(); } return; }
|
|
331
391
|
if (t.closest('[data-visor-sheet]')) { openSheet(); return; }
|
|
332
392
|
var sc = t.closest('[data-visor-scene]');
|
|
333
393
|
if (sc && scene) { var a = scene.actions[Number(sc.getAttribute('data-visor-scene'))]; closeSheet(false); if (a) a.run(); return; }
|
|
@@ -389,6 +449,9 @@
|
|
|
389
449
|
|
|
390
450
|
window.OTBVisor = {
|
|
391
451
|
scene: registerScene,
|
|
452
|
+
// a scene's pick: OTBVisor.focus({ title, status, lines, lead: { label, run }, onClose }) or focus(null)
|
|
453
|
+
focus: setFocus,
|
|
454
|
+
focused: function () { return focus ? focus.key : null; },
|
|
392
455
|
render: render,
|
|
393
456
|
open: openSheet,
|
|
394
457
|
close: function () { closeSheet(true); },
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Your runner on Home's towers — send it, see it, recall it (task 1005243, goal 1000142)
|
|
2
|
+
|
|
3
|
+
Covers: `home-runner.js` (new), the tower pick and runner marker in `home-city-draw.js` / `home-city.css`, the visor's card (`visor.js` `OTBVisor.focus`, `visor.css` `.visor__focus*`), and the read `GET /api/bongos/autonomy/me/goals/:goalId/stats` (`modules/autonomy`). Game mode only.
|
|
4
|
+
|
|
5
|
+
## What it settled
|
|
6
|
+
|
|
7
|
+
- **The owner's brief (2026-11-12):** putting your Autobongos runner to work should feel like putting a builder on a building in a base-building game, and stay uncluttered. In game mode you tap a tower on Home (a tower is a goal), the visor offers **Send runner here**, and the tower then carries your runner. Tap it again for what it has done there and **Recall runner**.
|
|
8
|
+
- **One tower at a time (owner, 2026-11-12).** Sending sets your goal picks to that one goal (`PUT /autonomy/me/goals`) and switches on any of your devices that are stopped (`POST /autonomy/runners/:host/run`). Recall switches them off and leaves the pick. Several picks made in Settings are "working on several goals": no tower carries the marker until you send it somewhere.
|
|
9
|
+
- **ADR 0403 kept:** the scene only gets picked (the tower is lit and edged in the accent); the words and the one button are the visor's. `OTBVisor.focus({ title, status, lines, lead: { label, run }, onClose })` is the general mechanism: a scene's pick puts a small card on the glass and its action takes the accent button; Escape (or the card's Esc mark, or a tap on open ground) closes it before the options menu sees the press, and the character's own main action comes back. This answers ADR 0403 §4's open question for one action.
|
|
10
|
+
- **Who sees it:** only a viewer the server says may run a runner (`GET /autonomy/fence` → `scope.may_run`, the `autonomy.run` atom). Signed out, without the atom, or in classic mode a tower stays the plain link to its goal and nothing is new.
|
|
11
|
+
- **The marker** is the agent's robot face in the accent colour (yours, not anyone's agent), ringed by its state: working (accent ring that turns, CSS only, stilled by the hall's reduced-motion rule), starting, resting (dotted info ring), ready (soft ring), recalled (faded). It replaces the agent face `/home/city` would draw on that tower. Tapping it picks its tower.
|
|
12
|
+
- **The numbers are the ledger's**, never the runner's own report: tasks the caller's runner shipped in that goal (`claims.autonomous` + outcome `shipped`), the tokens their sessions took (`session_records.total_tokens`), their average length, and the goal's `ready`/`active` tasks. Time left is that count × the average and is shown only when there is an average ("about 3 h"); with no runner history there it promises no time. The device's face (Running, Resting until 12:31 · 42 min, Device is off…) is `runner-button.js` `buttonState`, the same decision the Settings card draws.
|
|
13
|
+
- **Not yet:** the percent of your Claude usage limit. That number lives only on the runner's PC; task 1005244 has the runner report it in its check-in.
|
|
14
|
+
|
|
15
|
+
## Looking at it
|
|
16
|
+
|
|
17
|
+
`hall-preview-fixture` in `.claude/launch.json` (port 4602, `--fixture-me`; the plain preview borrows the live `/me`, whose character may not be the Artisan). Set `localStorage hall-game=on`, `OTBCraftMode.setCharacter('artisan')`, and edit `autonomy__fence.json` / `autonomy__runners.json` to a project that allows goals (the checked-in fence has the switch off and no goals, which renders "Closed to runners" — a real state). The harness reads fixtures per request, so `OTBHomeRunner.refresh()` picks a change up without a restart. Fixtures added: `autonomy__me__goals__stats.json`, `autonomy__me__goals.put.json`.
|
|
18
|
+
|
|
19
|
+
Tests: `tests/home_runner.mjs` (the card and marker decisions, run for real), `tests/autonomy_runner_goal_stats.mjs` (the read and route), `tests/hall_visor.mjs` (the card on the glass, its button, Escape order).
|
|
@@ -42,6 +42,10 @@ The glass was redrawn to the owner's mock-up "Cloud city: in the visor, minimal"
|
|
|
42
42
|
- **Scenes.** The top arc, the notch and the place sit inside the top 64px (`--visor-arc`; the segments under them take the inset to 88px, `--visor-inset-top`), so no scene's top row is struck through. Home no longer shifts its greeting row left (there is no pill top-right to clear). The city's door rises above the foot row at every width, because the accent button is centred where the door stood. The sky's `visor-on` offsets are unchanged.
|
|
43
43
|
- **Looking at it.** The stand-in scene in `tests/hall_visor.mjs` over the hall's real sheets; put a bright gradient behind it to check midday.
|
|
44
44
|
|
|
45
|
+
## A scene's pick on the glass (task 1005243)
|
|
46
|
+
|
|
47
|
+
`OTBVisor.focus({ key, title, status: { text, tone }, lines, lead: { label, run }, onClose })` puts a small card above the accent button (the night panel between two strong hairlines, like the sheet) and hands `lead` the accent button while it is up; `focus(null)` takes both away. Escape closes it in capture before the options menu, but never while the sheet is open. First user: Home's towers and your runner (`records/home-runner.md`).
|
|
48
|
+
|
|
45
49
|
## What it settled (task 1005148)
|
|
46
50
|
|
|
47
51
|
- **The first look (2026-11-10, five mock-up rounds; replaced by the minimal visor above).** A quiet layout with "hairline instruments" from the helmet round: thin curved visor lines across the top, bottom and both sides, a small centre cross, and a compass of nine slanted cells along the top line with the one salmon marker under its centre. No tracker or radar (tried and dropped), no clipped corners: the pills stay round (DESIGN.md "pill or nothing"). No glow (DESIGN.md's rejected near-black-plus-neon HUD). The frame is one SVG in a 1000×1000 box stretched over the stage with non-scaling strokes, so it is a hairline at every size.
|
|
@@ -26,7 +26,7 @@ const log = api.logger('render-deploy');
|
|
|
26
26
|
module.exports = function buildRenderDeployActRouter({ makeClient = defaultMakeClient, pool } = {}) {
|
|
27
27
|
const router = express.Router();
|
|
28
28
|
|
|
29
|
-
router.post('/render-deploy/act', api.requireBuilder, api.requirePermission('core.pin.move'), api.requireActAlone('release'), async (req, res) => {
|
|
29
|
+
router.post('/render-deploy/act', api.requireBuilder, api.requirePermission('core.pin.move'), api.requireCardStepUp, api.requireActAlone('release'), async (req, res) => {
|
|
30
30
|
const db = pool || api.pool;
|
|
31
31
|
// Shape only, and strict: an undeclared field is refused. `confirm` and `expectLiveDeployId`
|
|
32
32
|
// are left to the checks below so each gets its own plain refusal rather than a schema one.
|
|
@@ -35,7 +35,7 @@ module.exports = function buildRenderDeployAppRouter({ makeClient = defaultMakeC
|
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
router.put('/render-deploy/app', api.requireBuilder, api.requirePermission('core.pin.move'), async (req, res) => {
|
|
38
|
+
router.put('/render-deploy/app', api.requireBuilder, api.requirePermission('core.pin.move'), api.requireCardStepUp, async (req, res) => {
|
|
39
39
|
if (api.validateOrRespond(req, res, {
|
|
40
40
|
ownerId: { type: 'string', maxLength: 80 },
|
|
41
41
|
serviceId: { type: 'string', maxLength: 80 },
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.25.1",
|
|
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.
|
|
3
|
+
"version": "1.25.1",
|
|
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
|
@@ -11683,5 +11683,22 @@
|
|
|
11683
11683
|
"id": "1005218",
|
|
11684
11684
|
"text": "The three characters have a new look: space-faring aliens made of two animals each, drawn at a larger size. Each has its own colour, and in the game the visor now takes your character's colour and shows your character wearing"
|
|
11685
11685
|
}
|
|
11686
|
+
],
|
|
11687
|
+
"1.24.96": [
|
|
11688
|
+
{
|
|
11689
|
+
"id": "1005243",
|
|
11690
|
+
"text": "In game mode, tap a goal tower on Home to send your Autobongos runner there with one visor button; the tower then shows your runner at work, and tapping it shows tasks done, tokens used, time left and a Recall button."
|
|
11691
|
+
}
|
|
11692
|
+
],
|
|
11693
|
+
"1.25.0": [],
|
|
11694
|
+
"1.25.1": [
|
|
11695
|
+
{
|
|
11696
|
+
"id": "1004747",
|
|
11697
|
+
"text": "Sensitive actions (rank changes, deploys, signing sessions out, treasury payouts) can now require a tap of your hardware card within the last 10 minutes. Nobody is required to yet; an Archon switch comes next."
|
|
11698
|
+
},
|
|
11699
|
+
{
|
|
11700
|
+
"id": "1005040",
|
|
11701
|
+
"text": "Added the Stripe Connect test-mode payment adapter (economy.processor port). Every payout, cash-out, balance and ledger-check call now has a processor to talk to once a test key is set; nothing is live."
|
|
11702
|
+
}
|
|
11686
11703
|
]
|
|
11687
11704
|
}
|
|
@@ -619,7 +619,7 @@ const KERNEL_FILES = [
|
|
|
619
619
|
// by the doorway, so leaving it off reads as a kernel->non-kernel leak. It imports
|
|
620
620
|
// only module-seams.js and logger.js (both on this roster) and reaches the
|
|
621
621
|
// government module through its port, never by require.
|
|
622
|
-
'src/bongos/act-alone.js',
|
|
622
|
+
'src/bongos/act-alone.js', 'src/bongos/step-up.js', 'src/bongos/passkeys.js', // trust gate + its store (task 1004747, ADR 0382)
|
|
623
623
|
];
|
|
624
624
|
|
|
625
625
|
// Kernel files exempt from the no-module-keys scan (Check 10) because naming a
|
|
@@ -687,13 +687,13 @@ async function runWalk() {
|
|
|
687
687
|
|
|
688
688
|
// The session: whoever the request says it is, from this project's people.
|
|
689
689
|
const asHeader = 'x-walk-builder';
|
|
690
|
-
const sessionHeader = 'x-walk-session';
|
|
690
|
+
const sessionHeader = 'x-walk-session'; req('src/bongos/passkeys').stepUpStatus = async () => ({ required: false, fresh: false }); // task 1004747: no card is required in the walk
|
|
691
691
|
const fakeBuilder = (r, res, nextFn) => {
|
|
692
692
|
const b = store.builders.get(String(r.headers[asHeader] || ''));
|
|
693
693
|
if (!b || b.status !== 'active') return res.status(401).json({ error: { code: 'not_signed_in' } });
|
|
694
694
|
r.builder = { id: Number(b.id), rank: b.rank, github_login: b.github_login, display_name: b.display_name };
|
|
695
695
|
// an AI session's CLI token, or a person in the hall (task 1004704's line)
|
|
696
|
-
r.gdsSession = { source: r.headers[sessionHeader] === 'cli' ? 'cli' : 'web' };
|
|
696
|
+
r.gdsSession = { source: r.headers[sessionHeader] === 'cli' ? 'cli' : 'web', session_id: '1' }; // session_id: the step-up gate (task 1004747) needs one
|
|
697
697
|
return nextFn();
|
|
698
698
|
};
|
|
699
699
|
api.requireBuilder = fakeBuilder;
|
|
@@ -112,6 +112,8 @@ let liveWhy = null;
|
|
|
112
112
|
// me__recent-ships.json. Query strings are ignored when matching. Id-bearing
|
|
113
113
|
// paths fall back to the generic names below so one fixture serves any id.
|
|
114
114
|
const FIXTURE_FALLBACKS = [
|
|
115
|
+
// Home's runner card (task 1005243): one goal's runner numbers, the same for every goal
|
|
116
|
+
[/^autonomy\/me\/goals\/\d+\/stats$/, 'autonomy__me__goals__stats'],
|
|
115
117
|
[/^goals\/\d+\/conflicts$/, 'goals__conflicts'],
|
|
116
118
|
[/^goals\/\d+\/dependency-forest$/, 'goals__dependency-forest'],
|
|
117
119
|
[/^goals\/\d+\/requests$/, 'goals__requests'],
|
|
@@ -221,6 +223,8 @@ const CANNED_WRITES = [
|
|
|
221
223
|
['POST', /^auth\/runner-code\/issue$/, 'auth__runner-code__issue.post'],
|
|
222
224
|
['POST', /^me\/sessions\/revoke$/, 'me__sessions__revoke.post'],
|
|
223
225
|
['DELETE', /^autonomy\/runners\/[^/]+$/, 'autonomy__runners.delete'],
|
|
226
|
+
// Home's runner card (task 1005243): Send runner here writes the one pick.
|
|
227
|
+
['PUT', /^autonomy\/me\/goals$/, 'autonomy__me__goals.put'],
|
|
224
228
|
];
|
|
225
229
|
|
|
226
230
|
// A DATE THAT ROTS CANNOT DEMO AN AGE SIGNAL (task 1004041). The help-request
|