create-pmos 0.211.0 → 0.212.0
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/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/c3-settings-test.js +53 -2
- package/workspace/guide/data-freshness.md +35 -8
- package/workspace/guide/index.md +1 -1
- package/workspace/lib/driver-run.js +4 -3
- package/workspace/lib/freshness.js +2 -0
- package/workspace/lib/refresh-run.js +300 -0
- package/workspace/lib/refresh.js +464 -0
- package/workspace/lib/report-finder.js +22 -1
- package/workspace/lib/route-modules.js +1 -0
- package/workspace/lib/routes-refresh.js +93 -0
- package/workspace/lib/settings-store.js +0 -2
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-quiet.css +6 -1
- package/workspace/public/pm/c3-settings.css +11 -0
- package/workspace/public/pm/c3-settings.js +233 -9
- package/workspace/replaced-tests.json +11 -0
- package/workspace/server.js +101 -11
- package/workspace/test-lists/data-refresh-settings.json +5 -0
package/workspace/server.js
CHANGED
|
@@ -79,6 +79,9 @@ const validateLib = require('./lib/validate.js');
|
|
|
79
79
|
const pageAgent = require('./lib/page-agent.js'); // page-starts-agent — the fixed prompts a page press may start
|
|
80
80
|
const scanRunLib = require('./lib/scan-run.js'); // horizon-scan — the watch list suggest run and the scheduled scans, in the 60-second tick
|
|
81
81
|
const reportFinderLib = require('./lib/report-finder.js'); // report-workspace — the weekly look for the reports the PM already sends
|
|
82
|
+
const refreshLib = require('./lib/refresh.js'); // data-refresh-settings — the sources, their schedules and the refresh log
|
|
83
|
+
const refreshRunLib = require('./lib/refresh-run.js'); // data-refresh-settings — the engine bound to this server (created by the tick below)
|
|
84
|
+
let refreshRun = null;
|
|
82
85
|
const driverRunLib = require('./lib/driver-run.js'); // initiative-driver — wakes, the unblock sweep and the runs, in the 60-second tick
|
|
83
86
|
const headlessLib = require('./lib/headless.js'); // workspace-headless-jobs — which jobs run as `claude -p`, and with which tools
|
|
84
87
|
const liveNotes = require('./lib/live-notes.js'); // jev-live-notes — reads and appends a repository's live-notes ledger; no network
|
|
@@ -1945,7 +1948,7 @@ let kitBusy = null; // workspace-kit-update: the one kit or app update
|
|
|
1945
1948
|
const autoSync = require('./lib/auto-sync').createAutoSync({
|
|
1946
1949
|
roots: () => [...new Set([...workspaces.values()].map((w) => w.root))],
|
|
1947
1950
|
busy: syncing,
|
|
1948
|
-
onDone: (root) => boardCache.invalidate(root),
|
|
1951
|
+
onDone: (root, out) => { boardCache.invalidate(root); try { if (refreshRun) refreshRun.codeRun('sync', { trigger: 'change', result: out && out.ok ? 'done' : 'failed', got: out && out.ok ? (out.message || 'synced') : null, error: out && !out.ok ? out.message || 'the sync failed' : null }); } catch { /* the log is not the sync's business */ } },
|
|
1949
1952
|
label: `${os.hostname()} (auto-sync)`,
|
|
1950
1953
|
});
|
|
1951
1954
|
// workspace-data-freshness (R2 to R4): the facts cache is kept in the runtime state folder, and ONE change detector per
|
|
@@ -1958,7 +1961,13 @@ const freshness = require('./lib/freshness.js').create({
|
|
|
1958
1961
|
workspaces: () => [...workspaces.values()],
|
|
1959
1962
|
schedules: () => readSchedules(),
|
|
1960
1963
|
factsStatus: (root) => factsAsync.status(root),
|
|
1961
|
-
onChange: (w) => {
|
|
1964
|
+
onChange: (w) => {
|
|
1965
|
+
// data-refresh-settings: the repository source is the rebuild on change; switched off in Data refresh, a change rebuilds nothing.
|
|
1966
|
+
if (refreshRun && !refreshRun.jobGate('repo').ok) return;
|
|
1967
|
+
boardCache.board(w.root, { refreshNow: true }); factsAsync.refresh(w.root);
|
|
1968
|
+
if (refreshRun) refreshRun.codeRun('repo', { trigger: 'change', ws: w.id, got: 'a change in the repository' });
|
|
1969
|
+
},
|
|
1970
|
+
scheduleOf: (skill) => (refreshRun && refreshRun.managedSkill(skill) ? refreshRun.scheduleOfSkill(skill) : undefined),
|
|
1962
1971
|
pollMs: Math.max(250, Number(process.env.PMOS_FRESH_POLL_MS) || 10000),
|
|
1963
1972
|
stableMs: Math.max(0, Number(process.env.PMOS_FRESH_STABLE_MS) || 3000),
|
|
1964
1973
|
});
|
|
@@ -2621,7 +2630,62 @@ const coreDocsTick = () => {
|
|
|
2621
2630
|
proSettings, inQuiet: (now, quiet) => proactiveLib.inQuietHours(now, quiet), isLead: (w) => isPlanNeedsTree(w) }, Number.isFinite(t) ? { now: t } : {});
|
|
2622
2631
|
} catch (e) { warnWriteFail('core documents tick', e); }
|
|
2623
2632
|
};
|
|
2624
|
-
|
|
2633
|
+
// data-refresh-settings: the refresh engine. It starts the skill sources that are due (calendar, Jira, work assigned to you, tracker,
|
|
2634
|
+
// the morning brief), writes the refresh log, and gates the job ticks below: a job that is off, or whose input is still running, waits.
|
|
2635
|
+
const REFRESH_BOOT = Date.now();
|
|
2636
|
+
refreshRun = refreshRunLib.create({
|
|
2637
|
+
stateDir: STATE,
|
|
2638
|
+
now: () => { const t = process.env.PMOS_REFRESH_NOW ? Date.parse(process.env.PMOS_REFRESH_NOW) : NaN; return Number.isFinite(t) ? t + (Date.now() - REFRESH_BOOT) : Date.now(); },
|
|
2639
|
+
homeWs: () => { const h = homeWorkspace(); return h ? h.id : null; },
|
|
2640
|
+
proactive: () => proSettings(),
|
|
2641
|
+
schedules: { read: readSchedules, write: writeSchedules },
|
|
2642
|
+
startSkill: (wsId, skill, name, origin) => startSkillSession(wsId, skill, name, origin),
|
|
2643
|
+
sessionAlive: (id) => { const x = sessions.get(String(id || '')); return !!x && x.state !== 'exited'; },
|
|
2644
|
+
freshRows: (wsIdArg) => { const w = getWs(wsIdArg || ''); return w ? freshness.sources({ id: w.id, root: w.root, repoStateDir: (() => { try { return stateDirFor(w); } catch { return null; } })() }) : []; },
|
|
2645
|
+
copyPaths: (id, wsIdArg) => {
|
|
2646
|
+
const w = getWs(wsIdArg || '');
|
|
2647
|
+
if (id === 'mywork') return [path.join(STATE, 'my-work', 'read.json')];
|
|
2648
|
+
if (id === 'jira' && w) { let d = null; try { d = stateDirFor(w); } catch { d = null; } return d ? [path.join(d, 'tracker', 'jira')] : []; }
|
|
2649
|
+
return [];
|
|
2650
|
+
},
|
|
2651
|
+
tell: ({ source, label, error, ws }) => {
|
|
2652
|
+
const w = getWs(ws || '') || homeWorkspace(); const nf = w ? needsFileFor(w) : null; if (!nf) return;
|
|
2653
|
+
let needs = []; try { needs = JSON.parse(fs.readFileSync(nf, 'utf8')); } catch { needs = []; }
|
|
2654
|
+
needs.push({ run_id: `refresh-${source}-${Date.now()}`, kind: 'refresh', initiative_id: label, question: `${label} could not refresh: ${String(error).slice(0, 300)} See Settings, Data refresh.` });
|
|
2655
|
+
fs.writeFileSync(nf, JSON.stringify(needs)); invalidateNeedsAgg(w.id);
|
|
2656
|
+
},
|
|
2657
|
+
setDayCheckEvery: (every) => {
|
|
2658
|
+
let raw = {}; try { raw = JSON.parse(fs.readFileSync(PRO_SETTINGS(), 'utf8')); } catch { raw = {}; }
|
|
2659
|
+
const cur = proSettings();
|
|
2660
|
+
const n = proactiveLib.normalizeSettings({ ...raw, dayCheck: { ...cur.dayCheck, every: Number(every) } });
|
|
2661
|
+
if (!n.ok) throw new Error(n.error);
|
|
2662
|
+
fs.mkdirSync(STATE, { recursive: true }); fs.writeFileSync(PRO_SETTINGS(), JSON.stringify(n.settings) + '\n');
|
|
2663
|
+
},
|
|
2664
|
+
setRepoWatch: (on) => { for (const w of workspaces.values()) freshness.setEnabled(w.root, on); },
|
|
2665
|
+
jobs: {
|
|
2666
|
+
daycheck: () => {
|
|
2667
|
+
const date = briefLib.localDate(Date.now()); const run = readDayRun(date);
|
|
2668
|
+
if (!run) return { ok: false, error: 'There is no plan for today yet. Press Prepare my day first; the day check reads that plan.' };
|
|
2669
|
+
const c = startDayCheck(run, 'pressed', Date.now());
|
|
2670
|
+
if (!c.ok) return { ok: false, error: c.error };
|
|
2671
|
+
writeDayRun(c.run); return { ok: true, started: 'session', session: c.session };
|
|
2672
|
+
},
|
|
2673
|
+
scan: (wsIdArg) => { const w = getWs(wsIdArg || '') || homeWorkspace(); if (!w) return { ok: false, error: 'no workspace' }; const r = scanner.startNow(w, {}, { by: 'pm' }); return r.ok ? { ok: true } : { ok: false, status: r.status, error: r.error }; },
|
|
2674
|
+
coredocs: () => { let m; try { m = require('./lib/routes-core-docs.js'); } catch { return { ok: false, error: 'core documents are not installed' }; }
|
|
2675
|
+
m.tick({ getWs, stateDirFor, workspaceList: () => [...workspaces.values()], runtimeDir: STATE, selfRoot: EMBEDDED_SELF ? SELF_ROOT : null, initiativeIndex,
|
|
2676
|
+
spawnSession, stopSession: (id) => { const x = sessions.get(String(id || '')); if (x && x.child) stopHeadless(x); }, invalidateNeedsAgg,
|
|
2677
|
+
proSettings, inQuiet: () => false, isLead: (w) => isPlanNeedsTree(w) }, { force: true });
|
|
2678
|
+
return { ok: true }; },
|
|
2679
|
+
driver: () => { const r = driver.tick({ sweepNow: true }); return r && r.off ? { ok: false, error: 'The initiative driver is switched off on this computer.' } : { ok: true }; },
|
|
2680
|
+
finder: () => reportFinder.runNow(),
|
|
2681
|
+
signals: () => { const t0 = Date.now(); const r = proactiveScan({ manual: true }); refreshRun.codeRun('signals', { trigger: 'pressed', took_ms: Date.now() - t0, got: r && r.ok ? `${r.appended || 0} new signals` : null, result: r && r.ok ? 'done' : 'failed', error: r && r.ok ? null : (r && r.error) || 'the scan failed' }); return { ok: true, started: 'done' }; },
|
|
2682
|
+
repo: () => { for (const w of workspaces.values()) { boardCache.board(w.root, { refreshNow: true }); factsAsync.refresh(w.root, { force: true }); } refreshRun.codeRun('repo', { trigger: 'pressed', got: 'board and initiative facts rebuilt' }); return { ok: true, started: 'rebuild' }; },
|
|
2683
|
+
sync: () => { autoSync.tick().then((out) => { const ran = (out || []).filter(Boolean); refreshRun.codeRun('sync', { trigger: 'pressed', result: ran.every((x) => x.ok) ? 'done' : 'failed', got: ran.length ? `${ran.length} repositories synced` : 'nothing to sync', error: ran.filter((x) => !x.ok).map((x) => x.message).join('; ') || null }); }).catch(() => {}); return { ok: true, started: 'sync' }; },
|
|
2684
|
+
},
|
|
2685
|
+
warn: (what, e) => warnWriteFail(what, e),
|
|
2686
|
+
});
|
|
2687
|
+
const gated = (id, fn) => () => { let g = { ok: true }; try { g = refreshRun.jobGate(id); } catch { g = { ok: true }; } if (g.ok) fn(); };
|
|
2688
|
+
try { const _tick = setInterval(() => { checkDueSchedules(); proactiveTick(); try { prepareTick({ manual: false }); } catch { /* the next tick tries again */ } try { dayTick(); } catch (e) { warnWriteFail('day run tick', e); } try { refreshRun.tick(); } catch (e) { warnWriteFail('refresh tick', e); } gated('sync', () => autoSync.tick().catch(() => {}))(); dashboardScheduleTick(); gated('driver', driverTick)(); gated('scan', scanTick)(); gated('finder', reportFinderTick)(); gated('coredocs', coreDocsTick)(); reportDesignTick(); }, TICK_MS); if (_tick.unref) _tick.unref(); } catch {}
|
|
2625
2689
|
|
|
2626
2690
|
// ── Sessions: real PTYs, owned by the server, with disk-backed scrollback + metadata. ──
|
|
2627
2691
|
/** @type {Map<string, {id,ws,name,agent,cwd,state,note,created,pty,buf,cols,rows,clients:Set}>} */
|
|
@@ -3408,6 +3472,7 @@ function attachPty(s, { seed = false } = {}) {
|
|
|
3408
3472
|
: `exited (code ${exitCode})`;
|
|
3409
3473
|
appendBuf(s, `\r\n\x1b[38;5;244m[${s.note}]\x1b[0m\r\n`);
|
|
3410
3474
|
persist(s); pushSessions();
|
|
3475
|
+
noteSessionEnd(s);
|
|
3411
3476
|
});
|
|
3412
3477
|
return p;
|
|
3413
3478
|
}
|
|
@@ -3583,6 +3648,7 @@ function startHeadless(base, workdir, kind, launchArgs, env, initialInput, addDi
|
|
|
3583
3648
|
persist(s); pushSessions();
|
|
3584
3649
|
// page-starts-agent: a caller that started this run from the page reads its result here (a proposal, never a write).
|
|
3585
3650
|
if (typeof onDone === 'function') { try { onDone({ result: r, code, signal, timedOut, stopped: !!s.stoppedByPm, note: s.note }); } catch (e) { warnWriteFail('headless result', e); } }
|
|
3651
|
+
noteSessionEnd(s);
|
|
3586
3652
|
});
|
|
3587
3653
|
child.stdin.on('error', () => {});
|
|
3588
3654
|
child.stdin.end(prompt);
|
|
@@ -3603,7 +3669,15 @@ function killHeadless(child, sig) {
|
|
|
3603
3669
|
try { process.kill(-child.pid, sig); } catch { try { child.kill(sig); } catch {} }
|
|
3604
3670
|
}
|
|
3605
3671
|
|
|
3606
|
-
|
|
3672
|
+
// data-refresh-settings: every session passes the refresh engine on its way out, so a background refresh writes its start line
|
|
3673
|
+
// (lib/refresh-run.js started: only an origin it knows is logged). The end is noted where a session exits (noteSessionEnd).
|
|
3674
|
+
function spawnSession(opts) {
|
|
3675
|
+
const s = spawnSessionInner(opts);
|
|
3676
|
+
try { if (s && refreshRun) refreshRun.started(s); } catch (e) { warnWriteFail('refresh log (start)', e); }
|
|
3677
|
+
return s;
|
|
3678
|
+
}
|
|
3679
|
+
function noteSessionEnd(s) { try { if (refreshRun) refreshRun.ended(s); } catch (e) { warnWriteFail('refresh log (end)', e); } }
|
|
3680
|
+
function spawnSessionInner({ name, agent, cwd, ws, origin, initialInput, profile, initiative, modelAsk, planItem, workType, autoDelete, launchFlags, addDirs, onDone, scratch, extraEnv, writePaths }) {
|
|
3607
3681
|
const id = nextSessionId();
|
|
3608
3682
|
/* pm-os-absorb Run 1 — every AGENT session gets a token, `<id>.<8 hex>`, in its environment as PMOS_SESSION, and
|
|
3609
3683
|
* PMOS_BIN, this workspace's own bin/pmos.js. `pmos item` run inside it is then attributed `agent:<token>` (the token
|
|
@@ -3790,6 +3864,7 @@ function spawnSession({ name, agent, cwd, ws, origin, initialInput, profile, ini
|
|
|
3790
3864
|
: `exited (code ${exitCode})`;
|
|
3791
3865
|
appendBuf(s, `\r\n\x1b[38;5;244m[${s.note}]\x1b[0m\r\n`);
|
|
3792
3866
|
persist(s); pushSessions();
|
|
3867
|
+
noteSessionEnd(s);
|
|
3793
3868
|
});
|
|
3794
3869
|
feed(s);
|
|
3795
3870
|
persist(s); pushSessions(); pushWorkspaces();
|
|
@@ -4345,7 +4420,7 @@ function dayTick() {
|
|
|
4345
4420
|
if (a.ok) run = a.run;
|
|
4346
4421
|
run = startDayTasks(run, now);
|
|
4347
4422
|
const due = dayRunLib.checkDue(run, { settings: proSettings(), now, events: brief ? brief.calendar || [] : [] });
|
|
4348
|
-
if (due.due) { const c = startDayCheck(run, due.reason, now); if (c.ok) run = c.run; }
|
|
4423
|
+
if (due.due && refreshRun.jobGate('daycheck').ok) { const c = startDayCheck(run, due.reason, now); if (c.ok) run = c.run; }
|
|
4349
4424
|
run.checks.next = due.next;
|
|
4350
4425
|
if (JSON.stringify(run) !== before) writeDayRun(run);
|
|
4351
4426
|
}
|
|
@@ -4364,13 +4439,15 @@ function myWorkRead(wsId) {
|
|
|
4364
4439
|
return { ok: true, session: s.id };
|
|
4365
4440
|
}
|
|
4366
4441
|
/** Refresh now (workspace-data-freshness R7): start skill `skillId` in a session of repository `wsId`, as myWorkRead does. */
|
|
4367
|
-
function startSkillSession(wsId, skillId, name) {
|
|
4442
|
+
function startSkillSession(wsId, skillId, name, origin) {
|
|
4368
4443
|
const w = getWs(wsId);
|
|
4369
4444
|
if (!w) return { ok: false, status: 400, error: 'no workspace' };
|
|
4370
4445
|
const task = taskById(skillId, w.root);
|
|
4371
4446
|
if (!task || task.kind !== 'agent' || !task.prompt) return { ok: false, status: 409, error: `the ${skillId} skill is not installed for this repository` };
|
|
4372
4447
|
const prompt = skillStore.retargetFiling(task.prompt, filingContract(w));
|
|
4373
|
-
|
|
4448
|
+
// data-refresh-settings: a refresh of a source runs as `refresh:<source>`, so the refresh log records it whoever pressed it.
|
|
4449
|
+
const src = refreshLib.SKILL_OF.get(skillId);
|
|
4450
|
+
const s = spawnSession({ name, agent: task.agent || 'claude', ws: w.id, origin: origin || (src ? 'refresh:' + src : 'source-refresh'), initialInput: prompt + '\r',
|
|
4374
4451
|
modelAsk: { scheduleTier: null, skillTier: task.tier || null, skill: task.skill } });
|
|
4375
4452
|
if (!s || s.state === 'exited') return { ok: false, status: 500, error: (s && s.note) || 'the refresh session could not start' };
|
|
4376
4453
|
return { ok: true, session: s.id };
|
|
@@ -4593,7 +4670,12 @@ function proactiveTick() {
|
|
|
4593
4670
|
const now = Date.now();
|
|
4594
4671
|
if (now - proLastScan < PRO_SCAN_EVERY_MS) return;
|
|
4595
4672
|
proLastScan = now;
|
|
4596
|
-
|
|
4673
|
+
if (refreshRun.jobGate('signals').ok) {
|
|
4674
|
+
try {
|
|
4675
|
+
const r = proactiveScan({ manual: false });
|
|
4676
|
+
if (r && !r.skipped) refreshRun.codeRun('signals', { trigger: 'schedule', took_ms: Date.now() - now, got: `${r.appended || 0} new signals` });
|
|
4677
|
+
} catch (e) { warnWriteFail('proactive scan', e); refreshRun.codeRun('signals', { trigger: 'schedule', result: 'failed', error: String((e && e.message) || e) }); }
|
|
4678
|
+
}
|
|
4597
4679
|
try { learnWeekly({ manual: false }); } catch (e) { warnWriteFail('proactive weekly review', e); }
|
|
4598
4680
|
}
|
|
4599
4681
|
|
|
@@ -6662,6 +6744,8 @@ async function handleRequest(req, res) {
|
|
|
6662
6744
|
const list = readSchedules(); const made = []; const kept = [];
|
|
6663
6745
|
for (const def of set) {
|
|
6664
6746
|
if (!taskById(def.skill, w.root)) { kept.push({ skill: def.skill, why: 'the skill is not installed' }); continue; }
|
|
6747
|
+
// data-refresh-settings (R3): a refresh skill is scheduled in Settings › Data refresh. The set turns its source on there.
|
|
6748
|
+
if (refreshRun.managedSkill(def.skill)) { const turned = refreshRun.ensureOn(refreshRun.managedSkill(def.skill), w.id); kept.push({ skill: def.skill, why: turned ? 'turned on in Settings, Data refresh' : 'already on in Settings, Data refresh', refresh: refreshRun.managedSkill(def.skill) }); continue; }
|
|
6665
6749
|
if (list.some((x) => x.ws === w.id && x.skill === def.skill)) { kept.push({ skill: def.skill, why: 'already scheduled' }); continue; }
|
|
6666
6750
|
const norm = scheduleLogic.normalizeSchedule({ ...def, task: def.skill });
|
|
6667
6751
|
if (!norm.ok) { kept.push({ skill: def.skill, why: norm.error }); continue; }
|
|
@@ -8384,9 +8468,11 @@ async function handleRequest(req, res) {
|
|
|
8384
8468
|
if (!taskById('morning-brief', w.root)) return json(res, { ok: false, error: 'the morning-brief skill is not installed for this repository' }, 409);
|
|
8385
8469
|
const norm = scheduleLogic.normalizeSchedule({ ...SEED_SETS.brief[0], task: 'morning-brief' });
|
|
8386
8470
|
if (!norm.ok) return json(res, { ok: false, error: norm.error }, 500);
|
|
8387
|
-
|
|
8388
|
-
sched
|
|
8471
|
+
// data-refresh-settings (R3): the brief's schedule lives in Settings › Data refresh. The entry kept here only carries this run.
|
|
8472
|
+
sched = { id: 'sc-' + Date.now().toString(36) + 'g', ws: w.id, ...norm.schedule, profile: null, lastRun: null, lastStatus: null, enabled: false, endedReason: 'Moved to Settings › Data refresh' };
|
|
8473
|
+
sched.nextRun = null;
|
|
8389
8474
|
const list = readSchedules(); list.unshift(sched); writeSchedules(list); seeded = true;
|
|
8475
|
+
try { refreshRun.ensureOn('brief', w.id); } catch (e) { warnWriteFail('refresh settings (brief)', e); }
|
|
8390
8476
|
}
|
|
8391
8477
|
const result = fireSchedule(sched);
|
|
8392
8478
|
// A session that could not start is recorded as already ended, so the page says why instead of waiting on a read.
|
|
@@ -8634,6 +8720,8 @@ async function handleRequest(req, res) {
|
|
|
8634
8720
|
// A brief for a day the PM pressed "Prepare my day": its agent priorities become tasks and start (G13), and a merge
|
|
8635
8721
|
// closes the check that sent it (G22).
|
|
8636
8722
|
try { dayAfterBrief(out.date, merged); } catch (e) { warnWriteFail('day run after the brief', e); }
|
|
8723
|
+
// data-refresh-settings: a full brief ends the morning brief's refresh run (its session stays open after it submits).
|
|
8724
|
+
if (body.merge !== true) { try { refreshRun.briefDone(`${(out.items || []).length} items`); } catch (e) { warnWriteFail('refresh log (brief)', e); } }
|
|
8637
8725
|
return json(res, { ok: true, brief: out, ...(merged ? { merged } : {}), ...(remembered ? { remembered } : {}), ...(memoryError ? { memory_error: memoryError } : {}) });
|
|
8638
8726
|
}
|
|
8639
8727
|
if (req.method === 'GET') {
|
|
@@ -9082,7 +9170,9 @@ async function handleRequest(req, res) {
|
|
|
9082
9170
|
// c3-initiative-boards: the PMOS checkout this server is embedded in, whose default board keeps its derived ladder
|
|
9083
9171
|
selfRoot: EMBEDDED_SELF ? SELF_ROOT : null,
|
|
9084
9172
|
// horizon-scan-portfolio: Scan now, Stop and an approved research proposal start and end runs in the scanner; scanClock fixes its clock in the suites
|
|
9085
|
-
scanner, scanClock
|
|
9173
|
+
scanner, scanClock,
|
|
9174
|
+
// data-refresh-settings: Settings › Data refresh (lib/routes-refresh.js)
|
|
9175
|
+
refreshRun })) return;
|
|
9086
9176
|
res.writeHead(404); res.end('not found');
|
|
9087
9177
|
}
|
|
9088
9178
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "initiative data-refresh-settings: the refresh engine (lib/refresh.js) and its routes on a real server (lib/routes-refresh.js, lib/refresh-run.js).",
|
|
3
|
+
"modules": ["refresh.test.js", "refresh-routes.test.js"],
|
|
4
|
+
"seconds": { "refresh.test.js": 2, "refresh-routes.test.js": 25 }
|
|
5
|
+
}
|