create-pmos 0.165.0 → 0.167.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/notes.md +1 -0
- package/workspace/guide/validate-with-typesafe.md +1 -1
- package/workspace/lib/typesafe-settings.js +15 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-notes.js +19 -1
- package/workspace/public/pm/c3-settings.js +12 -2
- package/workspace/server.js +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.167.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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-12T14:00:51Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-12T14:00:51Z"}
|
|
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-
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-12T14:00:51Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
package/workspace/guide/notes.md
CHANGED
|
@@ -284,6 +284,7 @@ To take notes for a calendar event, open the event's detail on the Agenda and pr
|
|
|
284
284
|
- In the settings, **Mark private** stops listening at once and deletes the note's audio. A private note is not recorded again, and nothing from it goes to Jev or to an agent. **Mark not private** undoes it.
|
|
285
285
|
- **Summary by agent** starts an agent session in the selected repository that writes a summary into the note. Lines that look like a secret are removed first.
|
|
286
286
|
- **Delete note** in the **⋯** menu, or **Delete** on a row of **All notes**, removes the note, its transcript, its summaries and its audio from this machine after you confirm. The note's summary goes from the calendar and the initiative. You cannot undo it. Deleting a note that is not being taken does not stop the one that is.
|
|
287
|
+
- On the console3 page, open the note in **Calendar › Notes** and press the bin next to **Copy as Markdown** in its head. PMOS asks first: **Delete note** deletes it, **Keep it** keeps it. A note that is being recorded stops without keeping its audio, then it is deleted. The next note in the list opens.
|
|
287
288
|
|
|
288
289
|
## What stays on your machine
|
|
289
290
|
|
|
@@ -112,7 +112,7 @@ The workspace reads the TypeSafe key only on this machine. After you save the ke
|
|
|
112
112
|
If you have no TypeSafe key, a model on your computer can answer the same questions. Two models are supported. Both use the same interface as Jev.
|
|
113
113
|
|
|
114
114
|
- **Winnow**: in a `winnow-inference` folder, run `python3 scripts/serve.py`. It uses port 8091. Winnow-12B needs about 16 GB of memory.
|
|
115
|
-
- **Laya**:
|
|
115
|
+
- **Laya**: in Settings, press **Set up Laya**. A terminal named Set up Laya opens with the commands typed in. Read them and press Enter. The first time, they install Laya into the PMOS data folder. Then Laya starts and uses port 8000. Laya is small and runs on a laptop. Laya stops when you close that terminal or restart the computer. Press **Set up Laya** again to start it.
|
|
116
116
|
|
|
117
117
|
1. Start the model.
|
|
118
118
|
2. Open Settings, then **Privacy and data**. Under **Local model if Jev is missing**, press **Winnow** or **Laya**.
|
|
@@ -90,6 +90,20 @@ function status(stateDir, env = null) {
|
|
|
90
90
|
return { present: false, source: null, local: null };
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
/** The commands that install Laya into its own folder under the runtime directory (once) and start it on loopback.
|
|
94
|
+
* The workspace types them into a terminal and never runs them itself: the PM reads them and presses Enter (as D91's
|
|
95
|
+
* Whisper setup does). A folder of its own avoids the system Python, which newer Macs refuse to install into. */
|
|
96
|
+
function layaSetupCommand(stateDir, platform = process.platform) {
|
|
97
|
+
const dir = path.join(stateDir, 'laya');
|
|
98
|
+
const port = typesafe.BACKENDS.laya.port;
|
|
99
|
+
if (platform === 'win32') {
|
|
100
|
+
return `if (-not (Test-Path "${dir}\\Scripts\\laya-serve.exe")) { python -m venv "${dir}"; & "${dir}\\Scripts\\python" -m pip install "laya[serve]" }; `
|
|
101
|
+
+ `$env:LAYA_HOST="127.0.0.1"; $env:LAYA_PORT="${port}"; $env:LAYA_DEVICE="cpu"; $env:LAYA_PRELOAD="1"; & "${dir}\\Scripts\\laya-serve"`;
|
|
102
|
+
}
|
|
103
|
+
return `[ -x "${dir}/bin/laya-serve" ] || { python3 -m venv "${dir}" && "${dir}/bin/python" -m pip install "laya[serve]"; } && `
|
|
104
|
+
+ `LAYA_HOST=127.0.0.1 LAYA_PORT=${port} LAYA_DEVICE=cpu LAYA_PRELOAD=1 "${dir}/bin/laya-serve"`;
|
|
105
|
+
}
|
|
106
|
+
|
|
93
107
|
/** Save or clear the local fallback server. `backend` is 'winnow' or 'laya'; `port` defaults to that server's own. */
|
|
94
108
|
function setLocal(stateDir, { backend, port } = {}) {
|
|
95
109
|
const cur = read(stateDir);
|
|
@@ -163,4 +177,4 @@ function isExcluded(exclude, rel) {
|
|
|
163
177
|
});
|
|
164
178
|
}
|
|
165
179
|
|
|
166
|
-
module.exports = { adoptEnv, status, getKey, setKey, clearKey, setLocal, getRepo, setRepo, isExcluded, ENV_NAME, FILE };
|
|
180
|
+
module.exports = { adoptEnv, status, getKey, setKey, clearKey, setLocal, layaSetupCommand, getRepo, setRepo, isExcluded, ENV_NAME, FILE };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.167.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.167.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* tag, recording or paused, "no meeting", and a dot for a conflict.
|
|
9
9
|
* the head the note's meeting, then what it is about (the Portfolio, a board, or an initiative on its board) with one picker
|
|
10
10
|
* (aboutMenu: /api/boards and each board's Now and next initiatives), the mockup the PM approved on 2026-10-12.
|
|
11
|
+
* Copy as Markdown, and Delete (asked first; remove), as the main app's Notes has.
|
|
11
12
|
* the note the Calendar stage's layout (calendar-views.js stageNode, the mockup the PM approved on 2026-10-12): a head with
|
|
12
13
|
* Before, During and After, then the stage's cards.
|
|
13
14
|
* Before the meeting context the note kept (meeting.context): agenda (an item heard is ticked), what it is for, to be ready,
|
|
@@ -35,6 +36,7 @@ import * as dock from './note-dock.js';
|
|
|
35
36
|
import * as V from './view-notes.js';
|
|
36
37
|
import { makeTask } from './notes-task.js';
|
|
37
38
|
import { screenStrip } from './notes-screen-view.js';
|
|
39
|
+
import { askConfirm } from './dialogs.js';
|
|
38
40
|
|
|
39
41
|
const PHASES = [['before', 'clip', 'Before'], ['during', 'mic', 'During'], ['after', 'notes', 'After']];
|
|
40
42
|
const startOf = (n) => (n.meeting && n.meeting.start) || n.recorded_from || n.created_at || n.updated_at;
|
|
@@ -199,7 +201,8 @@ function noteCol(s) {
|
|
|
199
201
|
PHASES.map(([k, ic, l]) => btn('sgb', { 'data-cal3-phase': k, 'aria-pressed': String(ph === k), title: l }, [c3ic(ic), el('span', null, l)], () => { s.phase = k; draw(s); })))),
|
|
200
202
|
el('div', { class: 'chips2 dim cal3-sm' },
|
|
201
203
|
[dayOf(startOf(n)), len].filter(Boolean).join(' · '),
|
|
202
|
-
btn('ib sm', { title: 'Copy as Markdown (with the meeting context)', 'aria-label': 'Copy as Markdown', 'data-cal3-notes': 'copy' }, c3ic('clip'), () => copy(n))
|
|
204
|
+
btn('ib sm', { title: 'Copy as Markdown (with the meeting context)', 'aria-label': 'Copy as Markdown', 'data-cal3-notes': 'copy' }, c3ic('clip'), () => copy(n)),
|
|
205
|
+
btn('ib sm', { title: 'Delete this note', 'aria-label': 'Delete note', 'data-cal3-notes': 'delete' }, c3ic('trash'), () => remove(s, n))),
|
|
203
206
|
links(s, n, ev));
|
|
204
207
|
const body = ph === 'before' ? before(s, n) : ph === 'during' ? during(s, n, mode) : after(s, n, mode);
|
|
205
208
|
return el('div', { class: 'cal3-ncol', 'data-cal3-notes': 'note', 'data-cal3-note': n.id, 'data-cal3-stage': ph }, head, ...[].concat(body));
|
|
@@ -444,6 +447,21 @@ async function copy(n) {
|
|
|
444
447
|
catch { toast('The clipboard was not available.'); }
|
|
445
448
|
}
|
|
446
449
|
|
|
450
|
+
/* Delete: asked first, as the main app's Notes asks (view-notes.js deleteNote). A note being recorded stops without keeping its
|
|
451
|
+
* audio, then the note, its transcript, audio and screenshots go (POST /api/notes/delete). The next note in the list opens. */
|
|
452
|
+
async function remove(s, n) {
|
|
453
|
+
const name = n.title || 'Untitled note';
|
|
454
|
+
const sure = await askConfirm({ title: 'Delete "' + name + '"?', body: 'The note, its transcript, its summaries and its kept audio are deleted from this machine. Its summary goes from the calendar and the initiative. This cannot be undone.', confirmLabel: 'Delete note', cancelLabel: 'Keep it', danger: true });
|
|
455
|
+
if (!sure) return;
|
|
456
|
+
if (!(await V.discardRecording({ state: {}, setState: () => {} }, n.id))) { toast('"' + name + '" was not deleted.'); return; }
|
|
457
|
+
dock.forget(n.id);
|
|
458
|
+
s.rows = (s.rows || []).filter((x) => x.id !== n.id);
|
|
459
|
+
s.open = null; s.note = null; s.phase = null; s.shots = []; s.rec = null; s.audio = null;
|
|
460
|
+
toast('"' + name + '" was deleted.');
|
|
461
|
+
refreshNotes();
|
|
462
|
+
read(s);
|
|
463
|
+
}
|
|
464
|
+
|
|
447
465
|
/* "Show in the calendar": the Calendar switch back to the grid, on the meeting's day, with the meeting selected */
|
|
448
466
|
function showMeeting(s, ev) {
|
|
449
467
|
const ui = s.ui; if (!ui) return;
|
|
@@ -618,14 +618,24 @@ export function mountSettings(host, ctx = {}) {
|
|
|
618
618
|
M.localTest = r && r.ok ? { ok: true, text: 'Answered (' + (r.model || r.local) + ').' } : { ok: false, text: (r && r.error) || 'No answer.' };
|
|
619
619
|
draw();
|
|
620
620
|
};
|
|
621
|
+
const setup = async () => {
|
|
622
|
+
M.localTest = { busy: true }; draw();
|
|
623
|
+
const r = await pmPost('/api/typesafe/local/setup', {});
|
|
624
|
+
if (r && r.ok) {
|
|
625
|
+
M.typesafe = r.key; delete M.err['typesafe.local'];
|
|
626
|
+
M.localTest = { ok: !!r.started, text: r.started ? 'A terminal named Set up Laya opened with the commands typed in. Press Enter there, wait until Laya says it is running, then press Test.' : 'No terminal opened (' + (r.reason || 'unknown') + '). Run this in a terminal: ' + r.command };
|
|
627
|
+
} else { M.localTest = null; M.err['typesafe.local'] = (r && r.error) || 'Not started.'; }
|
|
628
|
+
draw();
|
|
629
|
+
};
|
|
621
630
|
const lt = M.localTest;
|
|
622
631
|
const note = !loc ? 'Off. With no TypeSafe key, Jev features stay off.'
|
|
623
632
|
: (t.source === 'local' ? 'No TypeSafe key, so ' + loc.name + ' answers every Jev question.' : 'TypeSafe answers first; ' + loc.name + ' answers when it fails.')
|
|
624
633
|
+ ' PMOS calls 127.0.0.1:' + loc.port + ' only. Nothing leaves this computer.' + (lt && !lt.busy ? ' ' + lt.text : '');
|
|
625
|
-
return frame({ key: 'typesafe.local', label: 'Local model if Jev is missing', help: '
|
|
634
|
+
return frame({ key: 'typesafe.local', label: 'Local model if Jev is missing', help: 'Press Set up Laya to install and start Laya in a terminal, or start Winnow (winnow-inference, port 8091) yourself and pick it here.', scope: 'computer',
|
|
626
635
|
err: M.err['typesafe.local'], note,
|
|
627
636
|
control: t ? el('div', { class: 'iacts' }, seg('Local model', [['off', 'Off'], ['winnow', 'Winnow'], ['laya', 'Laya']], cur, pick),
|
|
628
|
-
|
|
637
|
+
btn('Set up Laya', { variant: 'ghost', disabled: !!(lt && lt.busy), onclick: setup, attrs: { 'data-c3set-laya-setup': '' } }),
|
|
638
|
+
loc ? btn(lt && lt.busy ? 'Working' : 'Test', { variant: 'ghost', disabled: !!(lt && lt.busy), onclick: test }) : null) : tag('Reading') });
|
|
629
639
|
}
|
|
630
640
|
|
|
631
641
|
function appearanceSec() {
|
package/workspace/server.js
CHANGED
|
@@ -7976,6 +7976,17 @@ async function handleRequest(req, res) {
|
|
|
7976
7976
|
if (!out.ok) return json(res, { ok: false, error: out.error }, /choose|port/.test(out.error) ? 400 : 500);
|
|
7977
7977
|
return json(res, { ok: true, key: tsSettings.status(STATE) });
|
|
7978
7978
|
}
|
|
7979
|
+
/* Set up Laya: picks Laya as the local model and opens a terminal with the install-and-start commands typed in, not run.
|
|
7980
|
+
* The PM reads them and presses Enter; Laya then runs in that terminal on 127.0.0.1:8000 (D95). */
|
|
7981
|
+
if (p === '/api/typesafe/local/setup' && req.method === 'POST') {
|
|
7982
|
+
const saved = tsSettings.setLocal(STATE, { backend: 'laya' });
|
|
7983
|
+
if (!saved.ok) return json(res, { ok: false, error: saved.error }, 500);
|
|
7984
|
+
const command = tsSettings.layaSetupCommand(STATE);
|
|
7985
|
+
const home = homeWorkspace();
|
|
7986
|
+
const s = spawnSession({ name: 'Set up Laya', agent: 'shell', ws: home ? home.id : undefined, origin: 'laya-setup', initialInput: command });
|
|
7987
|
+
const started = !!s && s.state !== 'exited';
|
|
7988
|
+
return json(res, { ok: true, started, session: s ? s.id : null, command, key: tsSettings.status(STATE), reason: started ? '' : ((s && s.note) || 'could not start a terminal') });
|
|
7989
|
+
}
|
|
7979
7990
|
if (p === '/api/typesafe/local/test' && req.method === 'POST') {
|
|
7980
7991
|
if (!tsSettings.status(STATE).local) return json(res, { ok: false, error: 'No local model is set.' }, 409);
|
|
7981
7992
|
const r = await tsClient.ask({ key: tsClient.LOCAL_KEY, state: 'PMOS is checking that the local model answers.', retries: 0,
|