create-pmos 0.156.0 → 0.158.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-briefing-test.js +11 -0
- package/workspace/guide/briefing.md +2 -0
- package/workspace/guide/notes.md +7 -1
- package/workspace/lib/routes-notes.js +70 -11
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/console3.css +43 -0
- package/workspace/server.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.158.0",
|
|
4
4
|
"description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
package/payload/KIT-VERSION
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Eval calibration log"
|
|
4
4
|
description: "Append-only record of every PM override of an evaluator verdict."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-12T05:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-12T05:54:55Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Health budgets"
|
|
4
4
|
description: "Closed registry of health-budget anchors (D59), edited only by the PM."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-12T05:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-12T05:54:55Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Reference
|
|
3
3
|
title: "Discoveries and friction log"
|
|
4
4
|
description: "Append-only record of what runs discovered and where the process rubbed."
|
|
5
|
-
generated: {by: process:create-pmos, at: "2026-10-12T05:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-12T05:54:55Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -118,6 +118,17 @@ process.on('unhandledRejection', (e) => { console.error(e); finish(1); });
|
|
|
118
118
|
const M = await open({ viewport: { width: 400, height: 900 } });
|
|
119
119
|
ok('R6 at 400 wide the Briefing does not scroll sideways', M.up && await M.page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1 && document.getElementById('c3-root').scrollWidth <= innerWidth + 1), 'scrollWidth');
|
|
120
120
|
ok('R6 at 400 wide the stance and Needs you are on the page, with no day-part switch', await M.page.evaluate(() => !!document.querySelector('.stance') && document.querySelectorAll('[data-pg-dp]').length === 0 && !!document.querySelector('[data-c3pg="needs"]')));
|
|
121
|
+
/* c3-phone-layout: the five tabs share the width, the tool buttons fit on one row, and an avatar keeps its round look */
|
|
122
|
+
const ph = await M.page.evaluate(() => {
|
|
123
|
+
const W = innerWidth;
|
|
124
|
+
const tabs = [...document.querySelectorAll('.c3tabs [data-c3-tab]')].map((b) => b.getBoundingClientRect());
|
|
125
|
+
const tools = [...document.querySelectorAll('.c3top .tools > .ib, .c3top .tools > .psess, .c3top .tools > .c3pg-bell, .c3top .tools > .c3pg-menuwrap')].map((b) => Math.round(b.getBoundingClientRect().top));
|
|
126
|
+
const av = [...document.querySelectorAll('.nrow .av')].map((a) => a.getBoundingClientRect()).filter((r) => r.width);
|
|
127
|
+
return { tabs: tabs.length, inside: tabs.every((r) => r.left >= 0 && r.right <= W + 1 && r.height >= 44), rows: new Set(tools).size, toolsN: tools.length, av: av.length, round: av.every((r) => Math.abs(r.width - r.height) < 1) };
|
|
128
|
+
});
|
|
129
|
+
ok('phone: the five tabs are all on screen and each is a 44px target', ph.tabs === 5 && ph.inside, JSON.stringify(ph));
|
|
130
|
+
ok('phone: the tool buttons sit on one row', ph.toolsN >= 4 && ph.rows === 1, JSON.stringify(ph));
|
|
131
|
+
ok('phone: a person avatar is as wide as it is tall', ph.round, JSON.stringify(ph));
|
|
121
132
|
ok('0 no page error (400 wide)', M.errors.length === 0, M.errors.join(' | '));
|
|
122
133
|
await M.ctx.close();
|
|
123
134
|
return finish();
|
|
@@ -80,3 +80,5 @@ An item has its actions in one place only. On the Briefing, Today and Portfolio
|
|
|
80
80
|
## On a phone
|
|
81
81
|
|
|
82
82
|
At 400 pixels wide the Briefing is one column (greeting, cards, Needs you, Today, Portfolio pulse) with no sideways scroll. Each button in an opened row is 44 pixels high.
|
|
83
|
+
|
|
84
|
+
On a phone, every console page has the same top. The page title has Record at its right. The sunrise line is under it, then the tool buttons in one row. The five tabs share the width, and each tab shows an icon over its name, so Initiatives and Reports are always on screen. A count shows at the top right of its tab's icon. A person in a row shows as a small round letter, and you can tap anywhere in the 44-pixel square around it. A line that has a sentence and a due date wraps like normal text.
|
package/workspace/guide/notes.md
CHANGED
|
@@ -123,12 +123,18 @@ To set up Qwen3-ASR, Parakeet, speech detection and speakers, open the settings
|
|
|
123
123
|
|
|
124
124
|
The kept audio holds your microphone and the meeting apart until the final pass is done. When **Keep the audio for replay** is off, both are deleted after the final pass.
|
|
125
125
|
|
|
126
|
+
When PMOS restarts (for example after you install a new version) while a note waits for its final pass, its summary or Wispr Flow's notes, PMOS starts that work again when you next open PMOS:
|
|
127
|
+
|
|
128
|
+
- A final pass that was queued or running is queued again. When it can no longer run, for example because the audio is gone, the note says why, and your agent session writes the notes from the live transcript.
|
|
129
|
+
- A summary that was started and never written is started again.
|
|
130
|
+
- A Wispr Flow fetch continues on its own schedule. When its 20 minutes have passed, it stops and says so. Press **Fetch Wispr Flow notes now** to try again.
|
|
131
|
+
|
|
126
132
|
## Wispr Flow notes
|
|
127
133
|
|
|
128
134
|
When you also record the meeting with Wispr Flow, PMOS fetches Wispr Flow's notes and shows them under **Wispr Flow notes**, below yours.
|
|
129
135
|
|
|
130
136
|
1. Five minutes after **Stop**, PMOS starts an agent session that looks for this meeting in Wispr Flow, by its time and title.
|
|
131
|
-
2. When Wispr Flow has no notes yet, PMOS asks again every two minutes, for up to 20 minutes.
|
|
137
|
+
2. When Wispr Flow has no notes yet, PMOS asks again every two minutes, for up to 20 minutes. Each try starts a new agent session, and the previous one is closed when it is idle.
|
|
132
138
|
3. When it finds them, the summary shows in the note, with **Open in Wispr Flow**. Your agent session also writes your notes again with Wispr Flow's notes added, and the Overview says **merged with Wispr Flow's notes**. What both say is written once, what only one of them has is added, and where they differ the transcript decides. An item that only Wispr Flow has shows **Said (Wispr Flow):** and no meeting time. This runs once for a note. When Wispr Flow's notes were found before your agent session wrote the notes, they are merged at once.
|
|
133
139
|
4. When the fetch failed or Wispr Flow had no notes, the **Wispr Flow notes** section says so and shows **Fetch Wispr Flow notes now**. Press it: your agent session looks again at once, and every two minutes for up to 20 minutes from the press. It works when the automatic fetch is switched off.
|
|
134
140
|
|
|
@@ -549,15 +549,13 @@ function askWispr(ctx, id) {
|
|
|
549
549
|
const h = notes.wisprHandoff(dir, { id, until: stopped });
|
|
550
550
|
if (!h.ok) return;
|
|
551
551
|
const tries = (w0.tries || 0) + 1;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
session = s.id;
|
|
560
|
-
}
|
|
552
|
+
// every try is a fresh session: a try typed into the last one went nowhere when that session sat on a prompt. The last
|
|
553
|
+
// one is ended first when it is idle (server.js endSupersededRuns; a session still working or waiting on the PM stays).
|
|
554
|
+
if (typeof ctx.endSupersededRuns === 'function') { try { ctx.endSupersededRuns('notes-wispr:' + id); } catch { /* best effort */ } }
|
|
555
|
+
const ws = cur.note.ws ? ctx.getWs(cur.note.ws) : null;
|
|
556
|
+
const s = ctx.spawnSession({ name: 'notes: Wispr Flow', agent: 'claude', ws: ws && ws.id === cur.note.ws ? ws.id : undefined, origin: 'notes-wispr:' + id, initialInput: h.prompt + '\r' });
|
|
557
|
+
if (!s || s.state === 'exited') { notes.setWispr(dir, { id, patch: { state: 'failed', reason: (s && s.note) || 'could not start a claude session', tries } }); return; }
|
|
558
|
+
const session = s.id;
|
|
561
559
|
notes.setWispr(dir, { id, patch: { state: 'asking', tries, session, asked_at: new Date().toISOString() } });
|
|
562
560
|
wisprLater(ctx, id, wisprRetry(ctx), () => checkWispr(ctx, id));
|
|
563
561
|
}
|
|
@@ -586,7 +584,8 @@ function fetchWisprNow(ctx, id) {
|
|
|
586
584
|
if (listening(cur.note)) return { ok: false, status: 409, error: 'stop listening first' };
|
|
587
585
|
if (typeof ctx.spawnSession !== 'function') return { ok: false, status: 409, error: 'this workspace cannot start sessions' };
|
|
588
586
|
const w = cur.note.wispr;
|
|
589
|
-
|
|
587
|
+
// a check with its timer in this process is under way; one without (PMOS restarted) is stuck, so it starts again
|
|
588
|
+
if (w && (w.state === 'waiting' || w.state === 'asking') && wisprTimers.has(id)) return { ok: true, wispr: w };
|
|
590
589
|
const now = new Date().toISOString();
|
|
591
590
|
notes.setWispr(dir, { id, patch: { state: 'waiting', tries: 0, reason: '', manual_at: now, stopped_at: (w && w.stopped_at) || now, due_at: now } });
|
|
592
591
|
askWispr(ctx, id);
|
|
@@ -607,12 +606,71 @@ function mergeWispr(ctx, id) {
|
|
|
607
606
|
return !!(r && r.ok && r.started);
|
|
608
607
|
}
|
|
609
608
|
|
|
609
|
+
/* AFTER A RESTART. The final pass queue, the Wispr Flow timers and a headless summary run live in this process. A restart
|
|
610
|
+
* (an update installs a new version and starts PMOS again) dropped them, and their notes stayed "queued", "waiting" or
|
|
611
|
+
* "writing" for good. The first notes request after a start picks them up (resumeStuck), once per runtime directory:
|
|
612
|
+
* - a final pass that was queued or running is queued again; when it can no longer run, it is marked failed with the
|
|
613
|
+
* reason, and the agent writes the notes from the live transcript, as Stop does without an engine;
|
|
614
|
+
* - a summary run that was started, wrote nothing and is no longer a session is started again;
|
|
615
|
+
* - a Wispr Flow check that was waiting or asking carries on from its own clock, or ends as failed once its deadline
|
|
616
|
+
* after Stop is past (the PM can press Fetch to try again). */
|
|
617
|
+
let resumedFor = null;
|
|
618
|
+
function resumeStuck(ctx) {
|
|
619
|
+
const dir = ctx.runtimeDir;
|
|
620
|
+
if (resumedFor === dir) return [];
|
|
621
|
+
resumedFor = dir;
|
|
622
|
+
const done = [];
|
|
623
|
+
const l = notes.list(dir);
|
|
624
|
+
if (!l.ok) return done;
|
|
625
|
+
let live = null;
|
|
626
|
+
const liveIds = () => {
|
|
627
|
+
if (live) return live;
|
|
628
|
+
try { live = new Set((typeof ctx.sessionList === 'function' ? ctx.sessionList() : []).filter((x) => x.state !== 'exited').map((x) => x.id)); } catch { live = new Set(); }
|
|
629
|
+
return live;
|
|
630
|
+
};
|
|
631
|
+
const wsFor = (n) => { const w = n.ws ? ctx.getWs(n.ws) : null; return w && w.id === n.ws ? w : null; };
|
|
632
|
+
for (const row of l.notes) {
|
|
633
|
+
const cur = notes.read(dir, row.id);
|
|
634
|
+
if (!cur.ok || cur.note.private || listening(cur.note)) continue;
|
|
635
|
+
const n = cur.note;
|
|
636
|
+
const fst = n.final_state && n.final_state.state;
|
|
637
|
+
if ((fst === 'queued' || fst === 'running') && !(finalRunner && finalRunner.state(n.id))) {
|
|
638
|
+
const q = queueFinal(ctx, n.id);
|
|
639
|
+
if (q.queued) done.push({ id: n.id, final: 'queued' });
|
|
640
|
+
else {
|
|
641
|
+
notes.setFinalState(dir, { id: n.id, state: { state: 'failed', reason: 'PMOS restarted before the final pass ended, and it cannot run again: ' + q.reason } });
|
|
642
|
+
const a = n.agent;
|
|
643
|
+
if (!a || (!a.imported_at && !a.started)) { startAgent(ctx, dir, n.id, wsFor(n)); done.push({ id: n.id, final: 'failed', agent: 'started' }); }
|
|
644
|
+
else done.push({ id: n.id, final: 'failed' });
|
|
645
|
+
}
|
|
646
|
+
} else if (n.agent && n.agent.started && !n.agent.imported_at && !(n.agent.out && fs.existsSync(n.agent.out)) && n.agent.session && !liveIds().has(n.agent.session)) {
|
|
647
|
+
startAgent(ctx, dir, n.id, wsFor(n));
|
|
648
|
+
done.push({ id: n.id, agent: 'started' });
|
|
649
|
+
}
|
|
650
|
+
const w = n.wispr;
|
|
651
|
+
if (w && (w.state === 'waiting' || w.state === 'asking') && !wisprTimers.has(n.id)) {
|
|
652
|
+
const stopped = Math.max(Date.parse(w.stopped_at) || 0, Date.parse(w.manual_at) || 0);
|
|
653
|
+
if (Date.now() - stopped >= wisprDeadline(ctx)) {
|
|
654
|
+
notes.setWispr(dir, { id: n.id, patch: { state: 'failed', reason: 'PMOS restarted while it waited for Wispr Flow, and the ' + Math.round(wisprDeadline(ctx) / 60000) + ' minutes after Stop are past. Press Fetch to try again.' } });
|
|
655
|
+
done.push({ id: n.id, wispr: 'failed' });
|
|
656
|
+
} else {
|
|
657
|
+
const id = n.id;
|
|
658
|
+
if (w.state === 'waiting') wisprLater(ctx, id, Math.max(0, (Date.parse(w.due_at) || 0) - Date.now()), () => askWispr(ctx, id));
|
|
659
|
+
else wisprLater(ctx, id, 0, () => checkWispr(ctx, id));
|
|
660
|
+
done.push({ id, wispr: w.state });
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return done;
|
|
665
|
+
}
|
|
666
|
+
|
|
610
667
|
const send = (ctx, res, r) => ctx.json(res, r, r.ok ? 200 : (r.status || 400));
|
|
611
668
|
|
|
612
669
|
async function handle(req, res, url, ctx) {
|
|
613
670
|
const p = url.pathname;
|
|
614
671
|
const dir = ctx.runtimeDir;
|
|
615
672
|
if (!dir) { ctx.json(res, { ok: false, error: 'the notes store is not available' }, 500); return true; }
|
|
673
|
+
if (resumedFor !== dir) { try { resumeStuck(ctx); } catch { /* a note that cannot be picked up stays as it was */ } }
|
|
616
674
|
if (p === '/api/notes' || p === '/api/notes/one') {
|
|
617
675
|
if (req.method !== 'GET') { ctx.json(res, { ok: false, error: 'GET only' }, 405); return true; }
|
|
618
676
|
if (p === '/api/notes') { const l = notes.list(dir, { resumeMs: resumeMsOf(ctx) }); if (l.ok) sweepExpired(ctx, l.notes); send(ctx, res, l); return true; }
|
|
@@ -991,5 +1049,6 @@ function startAgent(ctx, dir, id, w) {
|
|
|
991
1049
|
return { ok: true, started, session: s ? s.id : null, reason, handoff: h.handoff, out: h.out, withheld: h.withheld };
|
|
992
1050
|
}
|
|
993
1051
|
|
|
994
|
-
module.exports = { prefixes, handle, enrich, runMinute, bulletsState, queueFinal, askWispr, checkWispr, fetchWisprNow, mergeWispr,
|
|
1052
|
+
module.exports = { prefixes, handle, enrich, runMinute, bulletsState, queueFinal, askWispr, checkWispr, fetchWisprNow, mergeWispr, resumeStuck,
|
|
1053
|
+
__resetResume: () => { resumedFor = null; },
|
|
995
1054
|
__idle: () => Promise.all([transcriber ? transcriber.idle() : null, finalRunner ? finalRunner.idle() : null, ...chains.values()]) };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.158.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pmos-workspace",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.158.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -2172,3 +2172,46 @@
|
|
|
2172
2172
|
.c3 .cal3-bar i.p{background:var(--acc);opacity:.55}
|
|
2173
2173
|
.c3 .cal3-bar i.t{background:var(--border)}
|
|
2174
2174
|
.c3 .cal3-counts{gap:10px;font-size:var(--fs-1)}
|
|
2175
|
+
|
|
2176
|
+
/* ── Phone layout pass (initiative c3-phone-layout): at 760px and under, each console3 page fits a 390px screen ─────────────────
|
|
2177
|
+
* Layout only; the desktop is unchanged. The header puts Record beside the title and the tool buttons on one row; the five tabs
|
|
2178
|
+
* share the width, each an icon over its word; rows keep their chips, dates and avatars legible; switch rows wrap as whole groups. */
|
|
2179
|
+
@media (max-width:760px){
|
|
2180
|
+
/* header: title and Record on the first line, the sun line, then the tool buttons; the demo pill last */
|
|
2181
|
+
.c3.c3pg .c3top{gap:8px 6px;justify-content:flex-start}
|
|
2182
|
+
.c3.c3pg .c3top .tools{display:contents}
|
|
2183
|
+
.c3.c3pg .c3top>div:first-child{order:0;flex:1 1 auto;min-width:0}
|
|
2184
|
+
.c3.c3pg .c3top .c3rec{order:1;margin-left:auto}
|
|
2185
|
+
.c3.c3pg .c3top .c3pg-daylight{order:2;flex:1 0 100%}
|
|
2186
|
+
.c3.c3pg .c3top .tools>*{order:3}
|
|
2187
|
+
.c3.c3pg .c3top .tools>.c3rec{order:1}
|
|
2188
|
+
.c3.c3pg[data-c3-demo] .c3top .c3pg-demo{order:4;flex:1 0 100%;justify-content:center}
|
|
2189
|
+
/* tabs: five equal columns, an icon over a short word, the count on the icon's shoulder; nothing hides off the right */
|
|
2190
|
+
.c3.c3pg .c3tabs{gap:0;overflow-x:visible}
|
|
2191
|
+
.c3.c3pg .c3tabs button{position:relative;flex:1 1 0;min-width:0;flex-direction:column;justify-content:center;gap:3px;padding:6px 2px;font-size:var(--fs-1);line-height:1.1}
|
|
2192
|
+
.c3.c3pg .c3tabs button>svg{flex:none}
|
|
2193
|
+
.c3.c3pg .c3tabs .bdg{position:absolute;top:3px;left:calc(50% + 6px)}
|
|
2194
|
+
/* an avatar keeps its round 22px look; its 44px tap area is the transparent box around it */
|
|
2195
|
+
.c3.ph .nrow .av,.c3.ph .drow .av{width:44px;height:44px;min-height:44px;flex:none;border:0;background:radial-gradient(circle closest-side,var(--muted) 0 calc(100% - 12px),var(--border) calc(100% - 12px) calc(100% - 11px),transparent calc(100% - 10.5px))}
|
|
2196
|
+
/* a chip that holds a sentence and its date wraps as one line of text instead of two squeezed columns */
|
|
2197
|
+
.c3 .nrow .chips .chip2:has(>.dim){display:block;min-width:0;max-width:100%}
|
|
2198
|
+
.c3 .nrow .chips .chip2:has(>.dim)>svg{display:inline-block;vertical-align:-3px;margin-right:4px}
|
|
2199
|
+
.c3 .nrow .chips .chip2:has(>.dim)>span{display:inline}
|
|
2200
|
+
.c3 .nrow .chips .chip2>.dim{white-space:nowrap}
|
|
2201
|
+
.c3 .wrow .chips .jc{max-width:100%;white-space:normal;overflow:visible}
|
|
2202
|
+
/* a row's trailing button keeps its icon centred */
|
|
2203
|
+
.c3 .nrow .acts .ib{justify-content:center}
|
|
2204
|
+
/* a card head wraps its note under the title rather than squeeze both */
|
|
2205
|
+
.c3 .sect>.sh2,.c3 .brief .sect .sh2{flex-wrap:wrap}
|
|
2206
|
+
.c3 .sect>.sh2>h2,.c3 .brief .sect .sh2>h2{flex:0 1 auto;min-width:0}
|
|
2207
|
+
.c3 .card>h3{flex-wrap:wrap}
|
|
2208
|
+
/* switch rows (Calendar/Notes, Day to Yr): each switch stays in one piece and the row wraps between them */
|
|
2209
|
+
.c3 .calbar .lens{flex-wrap:wrap;gap:6px}
|
|
2210
|
+
.c3 .calbar .lens>.seg{flex-wrap:nowrap}
|
|
2211
|
+
/* Work's lead line: Open Briefing sits on its own line under the sentence */
|
|
2212
|
+
.c3 .lh .wk-min .pill{display:flex;width:max-content;margin-top:6px}
|
|
2213
|
+
/* Settings: search takes the row, the workspace picker the next */
|
|
2214
|
+
.c3.c3pg .c3set .st-hd .tools{width:100%}
|
|
2215
|
+
.c3.c3pg .c3set .st-hd input[type=text]{flex:1 1 100%;width:auto}
|
|
2216
|
+
.c3.c3pg .c3set .st-hd select{flex:1 1 0;min-width:0}
|
|
2217
|
+
}
|
package/workspace/server.js
CHANGED
|
@@ -8800,7 +8800,7 @@ async function handleRequest(req, res) {
|
|
|
8800
8800
|
// pm-os-absorb Runs 2–5 — the planning layer's routes, one file per run (lib/route-modules.js). Last, so none shadows a route above.
|
|
8801
8801
|
// meeting-notes Run 1: the Notes routes keep notes in the per-user runtime directory and start the agent-summary session.
|
|
8802
8802
|
if (await routeModules.dispatch(req, res, url, { json, readBody, getWs, invalidateNeedsAgg, boardCache, consoleState, stateDirFor, sessionList, initiativeIndex,
|
|
8803
|
-
runtimeDir: STATE, spawnSession, jevModelOpts, sendToSession,
|
|
8803
|
+
runtimeDir: STATE, spawnSession, jevModelOpts, sendToSession, endSupersededRuns,
|
|
8804
8804
|
// workspace-headless-jobs R6: is this session a headless run that is still working?
|
|
8805
8805
|
headlessBusy: (id) => { const x = sessions.get(String(id || '')); return !!(x && x.headless && x.child); },
|
|
8806
8806
|
// initiative my-work: every registered repository, the day runs, and the two presses that start sessions
|