create-pmos 0.147.0 → 0.149.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/guide/ask-the-agent.md +46 -0
- package/workspace/guide/console-calendar.md +13 -4
- package/workspace/guide/index.md +1 -0
- package/workspace/guide/notes.md +1 -1
- package/workspace/lib/calendar-events.js +11 -1
- package/workspace/lib/calendar-layers.js +90 -3
- package/workspace/lib/headless.js +6 -2
- package/workspace/lib/page-agent.js +35 -0
- package/workspace/lib/routes-calendar-views.js +9 -4
- package/workspace/lib/routes-doc-sheet.js +86 -4
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-calendar.js +63 -7
- package/workspace/public/pm/c3-demo-cal4.js +15 -1
- package/workspace/public/pm/c3-demo-data.js +2 -2
- package/workspace/public/pm/c3-doc-sheet.js +36 -7
- package/workspace/public/pm/c3-lifecycle-panels.js +8 -5
- package/workspace/public/pm/c3-notes.js +47 -9
- package/workspace/public/pm/console3.css +7 -0
- package/workspace/public/pm/lifecycle-lens.js +9 -5
- package/workspace/public/pm/note-dock.js +5 -8
- package/workspace/public/pm/page-agent.js +25 -0
- package/workspace/public/pm/report-families-view.js +11 -9
- package/workspace/server.js +20 -3
- package/workspace/test-lists/c3-meeting-card.json +5 -0
- package/workspace/test-lists/page-starts-agent.json +5 -0
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* What a press does:
|
|
9
9
|
* - a section row opens IN PLACE (c3-expand): why it is here, the claims with their label and a Traced / Needs your input mark, the sources as
|
|
10
|
-
* jump chips (they click the Initiatives, Calendar, Work or Briefing tab), and "Fix with the agent"
|
|
10
|
+
* jump chips (they click the Initiatives, Calendar, Work or Briefing tab), and "Fix with the agent": it opens the report as a document on Ask the
|
|
11
|
+
* agent with the fix written in, so the agent starts from the page and its draft is accepted only by the PM's slide (page-starts-agent).
|
|
11
12
|
* - a swipe right approves a section and a swipe left cuts it (c3-swipe). In DEMO the decision is kept on the page, with an Undo toast. In REAL mode
|
|
12
13
|
* no route stores a section decision, so the swipe is off and the legend and the row say why.
|
|
13
14
|
* - the Send row opens the case file (c3-case) with the slide. DEMO: the slide marks the report Sent and adds it to the Sent card (nothing leaves).
|
|
@@ -27,10 +28,11 @@ import { openSheet } from './c3-sheet.js';
|
|
|
27
28
|
import { toast } from './c3-toast.js';
|
|
28
29
|
import { openDocSheet, splitDoc } from './c3-doc-sheet.js';
|
|
29
30
|
import { forecastChecksCard } from './c3-misc-panels.js';
|
|
31
|
+
import { c3btn } from './page-agent.js';
|
|
30
32
|
|
|
31
33
|
const insts = new WeakMap();
|
|
32
34
|
const FAM_ICON = { project: 'tree', product: 'target', communications: 'out', periodic: 'cal' };
|
|
33
|
-
const
|
|
35
|
+
const fixPrompt = (s) => (s.st === 'stale' ? 'Bring the section “' + s.h + '” up to date with its sources.' : 'Fix the section “' + s.h + '”: give each claim a label (fact, cited or reported) and its source or ticket key.');
|
|
34
36
|
const NO_STORE = 'Approving or cutting a section is not recorded here yet: no record stores a section decision.';
|
|
35
37
|
const dim = (t) => el('span', { class: 'dim mono', style: 'font-size:var(--fs-2)' }, t);
|
|
36
38
|
const rx = () => (typeof window !== 'undefined' && window.__c3demo && window.__c3repix) || null;
|
|
@@ -156,7 +158,7 @@ function claimRow(c) {
|
|
|
156
158
|
return el('li', { class: bad ? 'bad' : L[1], 'data-claim': '' }, c3ic(bad ? 'alert' : L[0], 'sm'), el('span', null, c.text + ' ', el('b', { class: 'dim', style: 'font-weight:500' }, traced ? 'Traced' : 'Needs your input')),
|
|
157
159
|
el('span', { class: 'tag' }, (c.label || 'unlabelled') + (c.source ? ' · ' + c.source : '')));
|
|
158
160
|
}
|
|
159
|
-
function detail(s) {
|
|
161
|
+
function detail(s, onFix) {
|
|
160
162
|
const claims = s.claims && s.claims.length ? s.claims.map(claimRow) : [el('li', { class: s.st === 'ok' ? 'ok' : s.st === 'stale' ? 'warn' : 'warn' }, c3ic(STATE[s.st][2], 'sm'), el('span', null, STATE[s.st][1] + (s.st === 'stale' ? ': a source is out of date. Refresh it before this goes out.' : s.st === 'check' ? ': a person decides this one.' : ': every number has a source.')), el('span', { class: 'tag' }, 'claims check'))];
|
|
161
163
|
const chips = s.src.length ? s.src.map((q) => (q[2] ? el('button', { type: 'button', class: 'chip2 lk', 'data-a': 'rsrc', 'data-k': q[2], onClick: (e) => { e.stopPropagation(); jump(q[2]); } }, c3ic(q[0], 'sm'), q[1], c3ic('next', 'sm'))
|
|
162
164
|
: el('span', { class: 'chip2' }, c3ic(q[0], 'sm'), q[1]))) : [el('span', { class: 'dim', style: 'font-size:var(--fs-2)' }, s.by === 'You' ? 'Your judgement. No source to trace.' : 'No source is recorded for this section.')];
|
|
@@ -164,15 +166,15 @@ function detail(s) {
|
|
|
164
166
|
s.why ? el('div', { class: 'dim', style: 'font-size:var(--fs-2)' }, c3ic('info', 'sm'), ' Why it is here: ' + s.why) : null,
|
|
165
167
|
el('div', { class: 'dim', style: 'font-size:var(--fs-2)' }, 'Claims check'), el('ul', { class: 'chgl' }, claims),
|
|
166
168
|
el('div', { class: 'dim', style: 'font-size:var(--fs-2)' }, 'Where it comes from'), el('div', { class: 'chips2', style: 'flex-wrap:wrap' }, chips),
|
|
167
|
-
s.st !== 'ok' ? el('div', { class: 'chips2'
|
|
169
|
+
s.st !== 'ok' ? el('div', { class: 'chips2' }, c3btn('chip2 lk', { 'data-rp-act': 'fix' }, [c3ic('spark', 'sm'), 'Fix with the agent'], (e) => { e.stopPropagation(); if (onFix) onFix(s); })) : null);
|
|
168
170
|
}
|
|
169
171
|
|
|
170
|
-
function sectionRow(inst, ctx, fam, key, s) {
|
|
172
|
+
function sectionRow(inst, ctx, fam, key, s, onFix) {
|
|
171
173
|
const DEMO = !!rx();
|
|
172
174
|
const k = key + '|' + s.id; const dst = decOf(inst, key, s.id);
|
|
173
175
|
const cc = STATE[s.st]; const o = ORIG[s.by] || ORIG.Agent;
|
|
174
176
|
const li = el('li', { class: 'nrow' + (dst === 'cut' ? ' cutr' : ''), 'data-row': '', 'data-section': s.id, 'data-rs': k, tabindex: '0', style: dst === 'cut' ? 'opacity:.5' : null });
|
|
175
|
-
const xp = el('div', { class: 'xp' }, el('div', { class: 'xi' }, detail(s)));
|
|
177
|
+
const xp = el('div', { class: 'xp' }, el('div', { class: 'xi' }, detail(s, onFix)));
|
|
176
178
|
li.append(el('span', { class: 'ki', style: 'color:' + (dst === 'ok' ? 'var(--success)' : cc[0]) }, c3ic(dst === 'ok' ? 'check' : dst === 'cut' ? 'x' : cc[2])),
|
|
177
179
|
el('div', { class: 'nb' }, el('span', { class: 'tt' }, s.h), el('div', { class: 'dim', style: 'font-size:var(--fs-2);margin-top:2px' }, s.t),
|
|
178
180
|
el('div', { class: 'chips' }, el('span', { class: 'chip2', style: 'color:' + o[1] }, c3ic(o[0], 'sm'), o[2]), el('span', { class: 'chip2', style: 'color:' + cc[0] }, c3ic(cc[2], 'sm'), cc[1]),
|
|
@@ -237,7 +239,7 @@ function secExtraOf(secs, getSheet) {
|
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
/** Open a report (or a communications message) as a document. path/name name it; secs are its sections; mode is the segment to start on. */
|
|
240
|
-
async function openReportDoc(inst, ctx, { fam, path, name, aud, repName, secs, mode }) {
|
|
242
|
+
async function openReportDoc(inst, ctx, { fam, path, name, aud, repName, secs, mode, prompt }) {
|
|
241
243
|
const ws = (ctx && ctx.ws) || '';
|
|
242
244
|
let text = null;
|
|
243
245
|
try { const r = await fetch('/api/doc?ws=' + encodeURIComponent(ws) + '&path=' + encodeURIComponent(path)); if (r.ok) text = await r.text(); } catch { /* no stored document */ }
|
|
@@ -246,7 +248,7 @@ async function openReportDoc(inst, ctx, { fam, path, name, aud, repName, secs, m
|
|
|
246
248
|
if (stored) inst.docText[path] = text;
|
|
247
249
|
let sheet = null; const getSheet = () => sheet;
|
|
248
250
|
const doc = { path, name, ty: 'Report', v: stored ? 'v1' : '', st: 'draft', ini: '', iniName: 'Portfolio' };
|
|
249
|
-
sheet = openDocSheet({ ws, doc, mode: mode || 'read', relations: reportRelations(inst, secs, repName || name, getSheet), secExtra: secExtraOf(secs, getSheet),
|
|
251
|
+
sheet = openDocSheet({ ws, doc, mode: mode || 'read', prompt: prompt || '', relations: reportRelations(inst, secs, repName || name, getSheet), secExtra: secExtraOf(secs, getSheet),
|
|
250
252
|
generated: stored ? null : { text: mdOf(secs), why: NO_STORE_WHY, health: generatedHealth(secs) },
|
|
251
253
|
onChanged: async () => { try { const r = await fetch('/api/doc?ws=' + encodeURIComponent(ws) + '&path=' + encodeURIComponent(path)); if (r.ok) { inst.docText[path] = await r.text(); draw(inst, ctx); } } catch { /* the rows keep what they show */ } } });
|
|
252
254
|
void aud; void fam;
|
|
@@ -457,7 +459,7 @@ function draw(inst, ctx) {
|
|
|
457
459
|
const doc = el('section', { class: 'card sect', 'data-rp-section': 'family', 'aria-label': fam.name },
|
|
458
460
|
el('div', { class: 'sh2' }, el('h2', null, c3ic('doc', 'sm'), repName + ' ', R ? el('span', { class: 'chip2' }, c3ic('user', 'sm'), audName) : el('span', { class: 'bdg g' }, String(secs.length)), R ? null : dim(vr.name)),
|
|
459
461
|
el('span', { class: 'chips2' }, el('button', { type: 'button', class: 'ib sm', 'data-a': 'docopen', title: 'Open as a document to edit by hand or ask the agent', 'aria-label': 'Open as a document to edit by hand or ask the agent', onClick: () => openReportDoc(inst, ctx, { fam, path: pathOf(inst, fam), name: repName + ' for ' + audName, aud: audName, repName, secs: secs.filter((s) => decOf(inst, key, s.id) !== 'cut') }) }, c3ic('notes', 'sm')))),
|
|
460
|
-
el('ul', { class: 'list' }, secs.length ? [...secs.map((s) => sectionRow(inst, ctx, fam, key, s)), sendRow(inst, ctx, fam, key, secs, q, audName, repName, vr)] : el('li', { class: 'fold' }, c3ic('check', 'sm'), 'This family has no sections.')),
|
|
462
|
+
el('ul', { class: 'list' }, secs.length ? [...secs.map((s) => sectionRow(inst, ctx, fam, key, s, (x) => openReportDoc(inst, ctx, { fam, path: pathOf(inst, fam), name: repName + ' for ' + audName, aud: audName, repName, secs: secs.filter((q) => decOf(inst, key, q.id) !== 'cut'), mode: 'ask', prompt: fixPrompt(x) }))), sendRow(inst, ctx, fam, key, secs, q, audName, repName, vr)] : el('li', { class: 'fold' }, c3ic('check', 'sm'), 'This family has no sections.')),
|
|
461
463
|
el('div', { class: 'pgl' }, R ? [el('span', null, 'swipe right: approve'), el('span', null, 'swipe left: cut'), el('span', null, 'tap: where it comes from')] : [el('span', null, 'tap: where it comes from'), el('span', null, 'approve and cut: off, no record stores them')]),
|
|
462
464
|
notes.length ? el('div', { 'data-rp-section': 'check-notes', style: 'display:grid;gap:var(--sp-2)' }, notes) : null);
|
|
463
465
|
const rf = (ctx && ctx.reportFacts) || {};
|
package/workspace/server.js
CHANGED
|
@@ -76,6 +76,7 @@ const jevRoute = require('./lib/jev-route.js'); // prompt-model-routing —
|
|
|
76
76
|
const jevClaims = require('./lib/jev-claims.js'); // D80 — the bridge between the claims ledger and the one TypeSafe module
|
|
77
77
|
const jevCategorize = require('./lib/jev-categorize.js'); // D82 — the bridge between the document categorizer and the one TypeSafe module
|
|
78
78
|
const validateLib = require('./lib/validate.js');
|
|
79
|
+
const pageAgent = require('./lib/page-agent.js'); // page-starts-agent — the fixed prompts a page press may start
|
|
79
80
|
const headlessLib = require('./lib/headless.js'); // workspace-headless-jobs — which jobs run as `claude -p`, and with which tools
|
|
80
81
|
const liveNotes = require('./lib/live-notes.js'); // jev-live-notes — reads and appends a repository's live-notes ledger; no network
|
|
81
82
|
const jevLive = require('./lib/jev-live.js'); // jev-live-notes Run 7 (D85) — the live view's one bridge to Jev, behind three gates
|
|
@@ -3374,7 +3375,7 @@ function sendToSession(id, text) {
|
|
|
3374
3375
|
|
|
3375
3376
|
/* A headless run (workspace-headless-jobs). The row is an ordinary session record, so every caller that checks a
|
|
3376
3377
|
* session's state keeps working; it has no pty and no tmux, and `child` is the process. The log is read-only. */
|
|
3377
|
-
function startHeadless(base, workdir, kind, launchArgs, env, initialInput, addDirs) {
|
|
3378
|
+
function startHeadless(base, workdir, kind, launchArgs, env, initialInput, addDirs, onDone) {
|
|
3378
3379
|
const prompt = safeFirstInput(String(initialInput).replace(/\r+$/, ''));
|
|
3379
3380
|
const args = headlessLib.argsFor(kind, { modelArgs: launchArgs, addDirs: Array.isArray(addDirs) ? addDirs : [] });
|
|
3380
3381
|
const s = { ...base, cwd: workdir, state: 'working', pty: null, tmux: null, headless: kind, child: null,
|
|
@@ -3425,6 +3426,8 @@ function startHeadless(base, workdir, kind, launchArgs, env, initialInput, addDi
|
|
|
3425
3426
|
: `exited (code ${code})`;
|
|
3426
3427
|
if (s.note !== 'exited (code 0)') say(`\x1b[38;5;203m[${s.note}]\x1b[0m\n`);
|
|
3427
3428
|
persist(s); pushSessions();
|
|
3429
|
+
// page-starts-agent: a caller that started this run from the page reads its result here (a proposal, never a write).
|
|
3430
|
+
if (typeof onDone === 'function') { try { onDone({ result: r, code, signal, timedOut, stopped: !!s.stoppedByPm, note: s.note }); } catch (e) { warnWriteFail('headless result', e); } }
|
|
3428
3431
|
});
|
|
3429
3432
|
child.stdin.on('error', () => {});
|
|
3430
3433
|
child.stdin.end(prompt);
|
|
@@ -3445,7 +3448,7 @@ function killHeadless(child, sig) {
|
|
|
3445
3448
|
try { process.kill(-child.pid, sig); } catch { try { child.kill(sig); } catch {} }
|
|
3446
3449
|
}
|
|
3447
3450
|
|
|
3448
|
-
function spawnSession({ name, agent, cwd, ws, origin, initialInput, profile, initiative, modelAsk, planItem, workType, autoDelete, launchFlags, addDirs }) {
|
|
3451
|
+
function spawnSession({ name, agent, cwd, ws, origin, initialInput, profile, initiative, modelAsk, planItem, workType, autoDelete, launchFlags, addDirs, onDone }) {
|
|
3449
3452
|
const id = nextSessionId();
|
|
3450
3453
|
/* pm-os-absorb Run 1 — every AGENT session gets a token, `<id>.<8 hex>`, in its environment as PMOS_SESSION, and
|
|
3451
3454
|
* PMOS_BIN, this workspace's own bin/pmos.js. `pmos item` run inside it is then attributed `agent:<token>` (the token
|
|
@@ -3558,7 +3561,7 @@ function spawnSession({ name, agent, cwd, ws, origin, initialInput, profile, ini
|
|
|
3558
3561
|
// ── the headless path (workspace-headless-jobs): a background job that needs no human runs as `claude -p`, with
|
|
3559
3562
|
// no pane, the same environment and model, a fixed tool list and the prompt on stdin (lib/headless.js says which).
|
|
3560
3563
|
const hkind = initialInput ? headlessLib.kindFor({ agent, origin: tag, skill: modelAsk && modelAsk.skill }) : null;
|
|
3561
|
-
if (hkind) return startHeadless(base, workdir, hkind, argv, penv, initialInput, addDirs);
|
|
3564
|
+
if (hkind) return startHeadless(base, workdir, hkind, argv, penv, initialInput, addDirs, onDone);
|
|
3562
3565
|
|
|
3563
3566
|
// ── the tmux path: the process outlives this server, and we re-attach to it live (R7) ──
|
|
3564
3567
|
if (TMUX) {
|
|
@@ -7561,6 +7564,20 @@ async function handleRequest(req, res) {
|
|
|
7561
7564
|
kitBusy = 'app';
|
|
7562
7565
|
return json(res, { ok: true, ws: w.id, from: APP_VERSION, to: body.version, log: r.log });
|
|
7563
7566
|
}
|
|
7567
|
+
/* ── page-starts-agent: a console3 press starts an agent session (a draft, a spike, an early gate), shown in Sessions ── */
|
|
7568
|
+
if (p === '/api/agent/start' && req.method === 'POST') {
|
|
7569
|
+
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
7570
|
+
const g = pmGuard.requirePagePress(req, body);
|
|
7571
|
+
if (!g.ok) return json(res, { ok: false, error: g.error }, g.status);
|
|
7572
|
+
const w = getWs(body.ws);
|
|
7573
|
+
if (!w) return json(res, { ok: false, error: 'no workspace' }, 404);
|
|
7574
|
+
const job = pageAgent.jobFor(body);
|
|
7575
|
+
if (!job.ok) return json(res, job, 400);
|
|
7576
|
+
const started = startWork({ initiative: job.initiative || undefined, ws: w.id, agent: 'claude', name: job.name, origin: 'page-agent:' + job.task, initialInput: job.prompt + '\r' });
|
|
7577
|
+
if (!started.ok) return json(res, { ok: false, error: started.error }, 409);
|
|
7578
|
+
if (started.session.state === 'exited') return json(res, { ok: false, error: started.session.note || 'the agent could not start', session: { id: started.session.id } }, 409);
|
|
7579
|
+
return json(res, { ok: true, session: { id: started.session.id, name: started.session.name }, notice: started.notice || null });
|
|
7580
|
+
}
|
|
7564
7581
|
/* ── next-step actions: prepare is read-only; go starts exactly what was shown; review records the PM's word ── */
|
|
7565
7582
|
if (p === '/api/next-step/prepare' && req.method === 'POST') {
|
|
7566
7583
|
let body = {}; try { body = JSON.parse((await readBody(req)) || '{}'); } catch { return json(res, { ok: false, error: 'invalid JSON body' }, 400); }
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Initiative page-starts-agent: Ask the agent, Fix with the agent, drafts, spikes and early gates start the agent from the console3 page (lib/page-agent.js, the doc-sheet ask route), against a stub claude.",
|
|
3
|
+
"modules": ["page-starts-agent.test.js"],
|
|
4
|
+
"seconds": { "page-starts-agent.test.js": 20 }
|
|
5
|
+
}
|