create-pmos 0.253.0 → 0.255.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 +2 -2
- 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/payload/state/log.md +1 -1
- package/workspace/briefing-premium-products-test.js +164 -0
- package/workspace/briefing-premium-sources-test.js +153 -0
- package/workspace/guide/briefing-premium.md +29 -2
- package/workspace/lib/briefing-premium-agent.js +1 -1
- package/workspace/lib/briefing-premium-products.js +121 -0
- package/workspace/lib/briefing-premium-sources.js +140 -0
- package/workspace/lib/briefing-premium.js +97 -20
- package/workspace/lib/routes-briefing-premium.js +39 -5
- package/workspace/lib/settings-store.js +3 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/premium.css +15 -0
- package/workspace/public/pm/premium.js +53 -7
- package/workspace/public/premium.html +1 -0
- package/workspace/server.js +1 -1
- package/workspace/test-files.json +4 -0
- package/workspace/test-lists/briefing-premium.json +4 -0
|
@@ -8,8 +8,9 @@ const ICON = {
|
|
|
8
8
|
meeting: '<rect x="4" y="5" width="16" height="15" rx="3"/><path d="M4 10h16M9 3v4M15 3v4"/>',
|
|
9
9
|
merge: '<circle cx="6" cy="6" r="2"/><circle cx="6" cy="18" r="2"/><circle cx="18" cy="9" r="2"/><path d="M6 8v8M18 11c0 4-6 3-12 5"/>',
|
|
10
10
|
run: '<path d="M13 3 5 14h6l-1 7 8-11h-6z"/>',
|
|
11
|
+
digest: '<path d="M5 7h14M5 12h14M5 17h9"/>',
|
|
11
12
|
};
|
|
12
|
-
const SRC = { meeting: 'Calendar', merge: 'Merges', run: 'Agent runs' };
|
|
13
|
+
const SRC = { meeting: 'Calendar', merge: 'Merges', run: 'Agent runs', digest: 'Digests' };
|
|
13
14
|
let M = null; // the model
|
|
14
15
|
const on = new Set(Object.keys(SRC));
|
|
15
16
|
const pinned = new Set();
|
|
@@ -42,7 +43,7 @@ function drawRows() {
|
|
|
42
43
|
const keys = rows.map((r) => r.key), off = (M.list.putOff || []).map((x) => x.key); // a row put off is not done: it leaves both counts and is named below the list
|
|
43
44
|
const base = (M.pending ? keys : baseline(keys)).filter((k) => !off.includes(k)), done = base.filter((k) => !keys.includes(k)).length, total = base.length;
|
|
44
45
|
const order = rows.slice().sort((a, b) => (pinned.has(b.key) - pinned.has(a.key)) || (a.n - b.n));
|
|
45
|
-
L.innerHTML = order.map((r, i) => `<div class="rr${i < 3 ? ' lead' : ''}${i === 0 ? ' first' : ''}" data-key="${esc(r.key)}"${linkOf(r) ? ` data-link="${esc(linkOf(r))}"` : ''}><button class="rhd" aria-haspopup="dialog"><span class="rn">${STAMP(i + 1)}</span><span><span class="rt">${esc(r.title)}${i === 0 ? '<em class="tgf">Do this first</em>' : ''}</span><span class="rs">${esc(r.subject)}</span><span class="sug"><span>${esc(r.prepared)}</span></span><span class="skip">${esc(r.skip)}</span></span></button><button class="rpin" data-pin="${esc(r.key)}" aria-pressed="${pinned.has(r.key)}" aria-label="Pin ${esc(r.title)} to the top" title="Pin to the top"><svg class="i" viewBox="0 0 24 24"><path d="M9 4h6l-1 6 3 3H7l3-3zM12 13v7"/></svg></button></div>`).join('')
|
|
46
|
+
L.innerHTML = order.map((r, i) => `<div class="rr${i < 3 ? ' lead' : ''}${i === 0 ? ' first' : ''}" data-key="${esc(r.key)}"${linkOf(r) ? ` data-link="${esc(linkOf(r))}"` : ''}><button class="rhd" aria-haspopup="dialog"><span class="rn">${STAMP(i + 1)}</span><span><span class="rt">${esc(r.title)}${i === 0 ? '<em class="tgf">Do this first</em>' : ''}</span><span class="rs">${r.product ? `<b class="pn">${esc(r.product)}</b>` : ''}${esc(r.subject)}</span><span class="sug"><span>${esc(r.prepared)}</span></span><span class="skip">${esc(r.skip)}</span></span></button><button class="rpin" data-pin="${esc(r.key)}" aria-pressed="${pinned.has(r.key)}" aria-label="Pin ${esc(r.title)} to the top" title="Pin to the top"><svg class="i" viewBox="0 0 24 24"><path d="M9 4h6l-1 6 3 3H7l3-3zM12 13v7"/></svg></button></div>`).join('')
|
|
46
47
|
+ (M.list.more ? `<p class="note">${M.list.more} more ${M.list.more === 1 ? 'row is' : 'rows are'} not shown here.</p>` : '');
|
|
47
48
|
const n = M.list.count;
|
|
48
49
|
$('#movesCap').innerHTML = M.pending ? `${n} ${n === 1 ? 'row' : 'rows'} so far · still reading` : n ? `<b>${done} of ${total}</b> done · ${n} ${n === 1 ? 'row' : 'rows'} left` : 'All clear';
|
|
@@ -66,11 +67,11 @@ $('#rank').addEventListener('click', (e) => {
|
|
|
66
67
|
const linkOf = (r) => ((r.from || []).find((x) => /^meeting:/.test(x)) || '').replace(/^meeting:/, '');
|
|
67
68
|
function evHtml(e, i) {
|
|
68
69
|
const lnk = e.kind === 'meeting' && (M.list.rows || []).some((r) => linkOf(r) === e.title) ? e.title : '';
|
|
69
|
-
return `<button class="ev src-${e.kind}"${lnk ? ` data-link="${esc(lnk)}"` : ''} data-i="${i}" style="animation:rise .6s var(--ease) both"><time>${esc(e.t)}</time><span class="gl-t"><svg class="i" viewBox="0 0 24 24">${ICON[e.kind]}</svg></span><span><b>${esc(e.title)}</b><span class="d">${esc(e.sub)}</span></span></button>`;
|
|
70
|
+
return `<button class="ev src-${e.kind}"${lnk ? ` data-link="${esc(lnk)}"` : ''} data-i="${i}" style="animation:rise .6s var(--ease) both"><time>${esc(e.t)}</time><span class="gl-t"><svg class="i" viewBox="0 0 24 24">${ICON[e.kind]}</svg></span><span><b>${esc(e.title)}</b><span class="d">${esc(e.sub)}${e.product ? ' · ' + esc(e.product) : ''}</span></span></button>`;
|
|
70
71
|
}
|
|
71
72
|
const ALL = () => M.today.ahead.concat(M.today.earlier);
|
|
72
73
|
function drawChips() {
|
|
73
|
-
$('#srcChips').innerHTML = Object.entries(SRC).map(([k, n]) => `<button class="fchip" data-k="${k}" aria-pressed="${on.has(k)}">${n}<em>${M.today.counts[k] || 0}</em></button>`).join('');
|
|
74
|
+
$('#srcChips').innerHTML = Object.entries(SRC).filter(([k]) => k !== 'digest' || M.today.counts.digest).map(([k, n]) => `<button class="fchip" data-k="${k}" aria-pressed="${on.has(k)}">${n}<em>${M.today.counts[k] || 0}</em></button>`).join('');
|
|
74
75
|
}
|
|
75
76
|
function drawTl() {
|
|
76
77
|
const T = M.today, all = ALL(), ahead = T.ahead.filter((e) => on.has(e.kind)), early = T.earlier.filter((e) => on.has(e.kind));
|
|
@@ -126,6 +127,7 @@ function cardHtml(r) {
|
|
|
126
127
|
return `${head}<p class="ct">Return this run: why</p><div class="seg2" id="retWhy">${RET_REASONS.map((x) => `<button data-ret="${esc(x)}" aria-pressed="${RET[r.key] === x}">${esc(x)}</button>`).join('')}</div>${err}<div class="cact"><button class="cta" id="recBtn" data-rec="return" disabled>${CHECK}<span>${esc(d.action)}</span></button></div></div>`;
|
|
127
128
|
}
|
|
128
129
|
if (d.opts.length) return `${head}${opts}${d.nodef ? `<p class="nodef">${esc(d.nodef)}</p>` : ''}<div class="cact" id="cardAct"><button class="cta" disabled title="${esc(d.hold || 'Nothing here can be recorded yet.')}">${o ? esc(d.prim[o.k]) : 'Your call: choose one'}</button></div>${d.hold ? holdNote(d.hold) : ''}</div>`;
|
|
130
|
+
if (d.link) return `${head}<div class="cact"><a class="cta" id="srcLink" href="${esc(d.link)}" target="_blank" rel="noopener noreferrer">${esc(d.linkLabel || 'Open the source')}</a></div></div>`;
|
|
129
131
|
return `${head}${d.nodef ? `<p class="nodef">${esc(d.nodef)}</p>` : ''}${d.action ? `<div class="cact"><button class="cta" disabled title="${esc(d.hold || 'Nothing here can be recorded.')}">${CHECK}${esc(d.action)}</button></div>${d.hold ? holdNote(d.hold) : ''}` : ''}</div>`;
|
|
130
132
|
}
|
|
131
133
|
function paneHtml(r) {
|
|
@@ -180,7 +182,7 @@ $('#dBody').addEventListener('click', (e) => {
|
|
|
180
182
|
const w = openR.dialog.write, why = ($('#why') || {}).value || '', key = openR.key;
|
|
181
183
|
WHY[key] = why;
|
|
182
184
|
if (w.kind === 'decision') write('decide', { key, choice: SEL[key] || openR.dialog.sel, why }, async (res) => { delete WHY[key]; toast('Recorded in pm-decisions.md.'); closeDlg(); await load(); });
|
|
183
|
-
else write('return', { run: w.run, reason: RET[key] }, async () => { toast('Returned to the agent. It counts as a correction.'); closeDlg(); await load(); });
|
|
185
|
+
else write('return', { run: w.run, reason: RET[key], ...(w.ws ? { ws: w.ws } : {}) }, async () => { toast('Returned to the agent. It counts as a correction.'); closeDlg(); await load(); });
|
|
184
186
|
}
|
|
185
187
|
});
|
|
186
188
|
$('#dBody').addEventListener('input', (e) => { if (e.target.id === 'why' && openR) { WHY[openR.key] = e.target.value; ctaState(openR); } });
|
|
@@ -197,6 +199,50 @@ function openEvent(e) {
|
|
|
197
199
|
['pointerleave', 'focusout'].forEach((ev) => document.addEventListener(ev, (e) => { const t = e.target.closest && e.target.closest('[data-link]'); if (t) lk(t, false); }, true));
|
|
198
200
|
}
|
|
199
201
|
|
|
202
|
+
|
|
203
|
+
/* ── the products the Briefing reads (initiative briefing-premium-products) ──────────────────────────────────────────────────────────── */
|
|
204
|
+
const wsParam = () => new URLSearchParams(location.search).get('ws');
|
|
205
|
+
function drawPick() {
|
|
206
|
+
const w = $('#pkw'), P = M.products, one = wsParam();
|
|
207
|
+
if (!w) return;
|
|
208
|
+
if (one) { w.hidden = false; $('#pick').hidden = true; $('#pickMenu').hidden = true; let a = $('#pickAll'); if (!a) { a = document.createElement('a'); a.id = 'pickAll'; a.className = 'fchip'; a.href = '/premium.html'; a.textContent = 'All picked products'; w.appendChild(a); } return; }
|
|
209
|
+
if ($('#pickAll')) $('#pickAll').remove();
|
|
210
|
+
$('#pick').hidden = false;
|
|
211
|
+
if (!P) { w.hidden = true; return; }
|
|
212
|
+
w.hidden = false;
|
|
213
|
+
const n = P.available.filter((x) => x.on).length;
|
|
214
|
+
$('#pickN').textContent = n + ' of ' + P.available.length;
|
|
215
|
+
$('#pickMenu').innerHTML = P.available.length
|
|
216
|
+
? P.available.map((x) => `<button role="menuitemcheckbox" aria-checked="${x.on}" data-ws="${esc(x.ws)}"><i class="bx"></i><span>${esc(x.name)}</span>${x.home ? '<em>home</em>' : ''}</button>`).join('') + '<button role="menuitem" data-reset="1" class="pkreset"><span>Back to the default</span><em>all but home</em></button>'
|
|
217
|
+
: '<p class="note pkempty">No product workspace is registered.</p>';
|
|
218
|
+
}
|
|
219
|
+
function closePick() { $('#pickMenu').hidden = true; $('#pick').setAttribute('aria-expanded', 'false'); }
|
|
220
|
+
$('#pick').addEventListener('click', (e) => {
|
|
221
|
+
e.stopPropagation();
|
|
222
|
+
const m = $('#pickMenu'), open = m.hidden;
|
|
223
|
+
m.hidden = !open; $('#pick').setAttribute('aria-expanded', open);
|
|
224
|
+
if (open) { const f = $('button', m); if (f) f.focus(); }
|
|
225
|
+
});
|
|
226
|
+
document.addEventListener('click', (e) => { if (!e.target.closest('#pkw')) closePick(); });
|
|
227
|
+
$('#pkw').addEventListener('keydown', (e) => {
|
|
228
|
+
if (e.key === 'Escape') { closePick(); $('#pick').focus(); return; }
|
|
229
|
+
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;
|
|
230
|
+
const L = $$('#pickMenu button'), i = L.indexOf(document.activeElement); if (i < 0) return;
|
|
231
|
+
e.preventDefault(); L[(i + (e.key === 'ArrowDown' ? 1 : L.length - 1)) % L.length].focus();
|
|
232
|
+
});
|
|
233
|
+
$('#pickMenu').addEventListener('click', async (e) => {
|
|
234
|
+
const b = e.target.closest('button'); if (!b || !M || !M.products) return;
|
|
235
|
+
const on = M.products.available.filter((x) => x.on).map((x) => x.ws);
|
|
236
|
+
let body;
|
|
237
|
+
if (b.dataset.reset) body = { key: 'ws.briefing.include', reset: true };
|
|
238
|
+
else { const ws = b.dataset.ws, next = on.includes(ws) ? on.filter((x) => x !== ws) : on.concat(ws); body = { key: 'ws.briefing.include', value: next }; }
|
|
239
|
+
const res = await pmPost('/api/settings', body);
|
|
240
|
+
if (!res || res.ok !== true) { toast((res && res.error) || 'The choice was not saved.'); return; }
|
|
241
|
+
toast(b.dataset.reset ? 'Back to the default: every product except the home one.' : 'Saved. The Briefing now reads the products you ticked.');
|
|
242
|
+
await load();
|
|
243
|
+
const again = b.dataset.ws ? $(`#pickMenu [data-ws="${CSS.escape(b.dataset.ws)}"]`) : null; if (again) again.focus();
|
|
244
|
+
});
|
|
245
|
+
|
|
200
246
|
/* ── load ───────────────────────────────────────────────────────────────────────────────────────────────── */
|
|
201
247
|
let readTimer = null;
|
|
202
248
|
const ago = (ms) => { const m = Math.round(ms / 60000); return m < 1 ? 'just now' : m < 90 ? m + ' min ago' : Math.round(m / 60) + ' h ago'; };
|
|
@@ -207,7 +253,7 @@ async function load() {
|
|
|
207
253
|
clearInterval(readTimer);
|
|
208
254
|
if (r && r.state === 'reading') {
|
|
209
255
|
// the first read has no end I can promise: say how long it has run, and ask again
|
|
210
|
-
const t0 = Date.now() - (r.elapsed_ms || 0), tick = () => { $('#brief').textContent = 'Reading the
|
|
256
|
+
const t0 = Date.now() - (r.elapsed_ms || 0), tick = () => { $('#brief').textContent = 'Reading the products for the first time. ' + Math.round((Date.now() - t0) / 1000) + ' s so far. This page shows nothing until it has read, rather than guess.'; };
|
|
211
257
|
tick(); readTimer = setInterval(tick, 1000); setTimeout(load, 3000);
|
|
212
258
|
return;
|
|
213
259
|
}
|
|
@@ -216,7 +262,7 @@ async function load() {
|
|
|
216
262
|
$('#brief').textContent = 'The Briefing could not read the repository, so it shows nothing rather than guess.' + (r && r.error ? ' The reason: ' + r.error : status ? ' (the server answered ' + status + ')' : ' The server did not answer.');
|
|
217
263
|
return;
|
|
218
264
|
}
|
|
219
|
-
M = r.model; drawHero(); drawRows(); drawChips(); drawTl(); drawDock(); onScroll();
|
|
265
|
+
M = r.model; drawHero(); drawRows(); drawChips(); drawTl(); drawDock(); drawPick(); onScroll();
|
|
220
266
|
const age = Date.now() - r.as_of;
|
|
221
267
|
if (age > 5 * 60000) $('#snap').textContent += ' · read ' + ago(age) + (r.refreshing ? ', refreshing' : '');
|
|
222
268
|
if (r.error) $('#snap').textContent += ' · the latest read failed: ' + r.error.slice(0, 120);
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
<header class="top rise" style="--d:0">
|
|
36
36
|
<h1 class="vh">Briefing</h1><span class="glass name" aria-hidden="true">Briefing</span>
|
|
37
37
|
<div class="tools tools-top"><span class="snap" id="snap">Reading the repository</span>
|
|
38
|
+
<span class="pkw" id="pkw" hidden><button class="fchip" id="pick" aria-haspopup="menu" aria-expanded="false" aria-controls="pickMenu">Products<em id="pickN"></em></button><div class="crmenu pkmenu" id="pickMenu" role="menu" hidden></div></span>
|
|
38
39
|
<button class="glass round" id="theme" aria-label="Switch light or dark"><svg class="i" viewBox="0 0 24 24"><path d="M20 14.5A8 8 0 0 1 9.5 4 8 8 0 1 0 20 14.5z"/></svg></button></div>
|
|
39
40
|
</header>
|
|
40
41
|
<section class="hero">
|
package/workspace/server.js
CHANGED
|
@@ -9360,7 +9360,7 @@ async function handleRequest(req, res) {
|
|
|
9360
9360
|
// 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.
|
|
9361
9361
|
// meeting-notes Run 1: the Notes routes keep notes in the per-user runtime directory and start the agent-summary session.
|
|
9362
9362
|
if (await routeModules.dispatch(req, res, url, { json, readBody, getWs, invalidateNeedsAgg, boardCache, consoleState, stateDirFor, sessionList, initiativeIndex,
|
|
9363
|
-
runtimeDir: STATE, spawnSession, jevModelOpts, sendToSession, endSupersededRuns, proSettings,
|
|
9363
|
+
runtimeDir: STATE, homeRoot: HOME_ENABLED ? SELF_ROOT : null, spawnSession, jevModelOpts, sendToSession, endSupersededRuns, proSettings,
|
|
9364
9364
|
// report-workspace: the schedule store a recurring report registers its draft-only schedule in, a skill's body, and the stop of a headless run
|
|
9365
9365
|
scheduleStore: { read: readSchedules, write: writeSchedules },
|
|
9366
9366
|
// report-structure-gaps: Draft it now on a report with a saved structure starts the draft session the schedule would, and the row reads its state
|
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
"briefing-premium-work.test.js",
|
|
42
42
|
"briefing-premium-board.test.js",
|
|
43
43
|
"briefing-premium-initiative.test.js",
|
|
44
|
+
"briefing-premium-products.test.js",
|
|
45
|
+
"briefing-premium-sources.test.js",
|
|
44
46
|
"calendar-events.test.js",
|
|
45
47
|
"calendar-supersede.test.js",
|
|
46
48
|
"tracker-read.test.js",
|
|
@@ -200,6 +202,8 @@
|
|
|
200
202
|
"briefing-premium-work.test.js": 1,
|
|
201
203
|
"briefing-premium-board.test.js": 1,
|
|
202
204
|
"briefing-premium-initiative.test.js": 1,
|
|
205
|
+
"briefing-premium-products.test.js": 1,
|
|
206
|
+
"briefing-premium-sources.test.js": 1,
|
|
203
207
|
"calendar-events.test.js": 1,
|
|
204
208
|
"calendar-supersede.test.js": 1,
|
|
205
209
|
"tracker-read.test.js": 1,
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
"briefing-premium-work-test.js",
|
|
6
6
|
"briefing-premium-board-test.js",
|
|
7
7
|
"briefing-premium-initiative-test.js",
|
|
8
|
+
"briefing-premium-products-test.js",
|
|
9
|
+
"briefing-premium-sources-test.js",
|
|
8
10
|
"briefing-premium-agent-server.test.js"
|
|
9
11
|
],
|
|
10
12
|
"seconds": {
|
|
@@ -12,6 +14,8 @@
|
|
|
12
14
|
"briefing-premium-work-test.js": 80,
|
|
13
15
|
"briefing-premium-board-test.js": 120,
|
|
14
16
|
"briefing-premium-initiative-test.js": 120,
|
|
17
|
+
"briefing-premium-products-test.js": 90,
|
|
18
|
+
"briefing-premium-sources-test.js": 90,
|
|
15
19
|
"briefing-premium-agent-server.test.js": 40
|
|
16
20
|
}
|
|
17
21
|
}
|