create-pmos 0.219.0 → 0.221.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-phone-test.js +1 -0
- package/workspace/guide/my-work.md +2 -2
- package/workspace/guide/phone-companion.md +2 -2
- package/workspace/lib/routes-phone-day.js +4 -2
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-demo-phone.js +1 -1
- package/workspace/public/pm/c3-doc-sheet.js +43 -5
- package/workspace/public/pm/c3-phone-detail.js +4 -2
- package/workspace/public/pm/c3-phone.css +5 -0
- package/workspace/public/pm/pm-work-view.js +38 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.221.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-14T05:13:19Z"}
|
|
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-14T05:13:19Z"}
|
|
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-14T05:13:19Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -236,6 +236,7 @@ const writes = (p) => (window.__c3demoWrites || []).filter((w) => w.path === p |
|
|
|
236
236
|
ok('day: working hours show what the agent finished for you', await waitFor(page, () => !!document.querySelector('[data-c3ph-moment] [data-c3ph-fin="t-0000000000a1"]'), null, 8000));
|
|
237
237
|
await page.click('[data-c3ph-fin-open="t-0000000000a1"]');
|
|
238
238
|
ok('day: Read it opens the draft, and approving it waits for the desk', await waitFor(page, () => { const d = document.querySelector('[data-c3ph-draft="t-0000000000a1"]'); return !!d && /31 Oct/.test(d.textContent) && /on your computer/.test(d.textContent); }, null, 5000));
|
|
239
|
+
ok('day: the draft reads as a document, not raw markdown (headings, bold, a list)', await page.evaluate(() => { const m = document.querySelector('[data-c3ph-draft="t-0000000000a1"] .ds-md'); return !!m && !!m.querySelector('h1') && !!m.querySelector('h2') && !!m.querySelector('strong') && m.querySelectorAll('li').length === 2 && !/(^|\s)(#|\*\*)/.test(m.textContent); }));
|
|
239
240
|
await page.close();
|
|
240
241
|
page = await open(pctx, '/c3.html?demo=1&at=17:40');
|
|
241
242
|
ok('day: 17:40 is the evening recap, with the evening icon', (await moment(page)) === 'recap' && await page.evaluate(() => document.querySelector('[data-c3ph-part]')?.dataset.c3phPart === 'evening'), await moment(page));
|
|
@@ -121,7 +121,7 @@ The row chip then shows the agent's state:
|
|
|
121
121
|
|
|
122
122
|
The page reads the state again every 15 seconds while an agent is queued, working or posting.
|
|
123
123
|
|
|
124
|
-
When the draft is ready, the case file shows it as a document card, with its headings, lists and tables. Select **Open** to read
|
|
124
|
+
When the draft is ready, the case file shows it as a document card, with its headings, lists and tables. Select **Open** to read it in the document sheet, the same sheet as your other documents: **Read**, **Edit**, **Proposals** (send it back with a note) and **Edit log** (the conversation with the agent). Select **Copy** to copy the text.
|
|
125
125
|
|
|
126
126
|
Under the draft is the conversation with the agent: your ask, each draft, each note you sent back and each edit. Select **View round** on an earlier draft to read that version. The conversation stays on the item on the next days too.
|
|
127
127
|
|
|
@@ -130,7 +130,7 @@ To ask the agent for changes, type in the box under the conversation and press E
|
|
|
130
130
|
Or choose one option:
|
|
131
131
|
|
|
132
132
|
- **Post it to** the ticket posts the exact text. Nothing goes out until you hold to confirm.
|
|
133
|
-
- **Edit the draft** opens the
|
|
133
|
+
- **Edit the draft** opens the draft in the document sheet's editor. Select **Save**. The card then says **Edited by you**, and **Post it to** the ticket posts exactly your text.
|
|
134
134
|
- **Keep it as a draft** leaves the draft as it is.
|
|
135
135
|
- **Mark it done** closes the item. Nothing is posted.
|
|
136
136
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
type: Guide
|
|
3
3
|
title: "Guide: the phone companion"
|
|
4
4
|
description: "At phone width the console shows a companion: one moment of your day, your items one at a time, Tell PMOS and Record."
|
|
5
|
-
timestamp: 2026-10-
|
|
5
|
+
timestamp: 2026-10-14
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Guide: the phone companion
|
|
@@ -20,7 +20,7 @@ The top card shows where you are in your day and why it is useful now. PMOS choo
|
|
|
20
20
|
| In the meeting | A meeting is in progress | **Record this meeting?** with **Record** and **Not this one**. Recording opens focus (see below). |
|
|
21
21
|
| Just ended | A meeting ended less than 20 minutes ago | **What came out of it?** If you recorded the meeting, the card shows the summary, the decisions, the actions and the open questions from its notes, the same notes as on the Notes page. While PMOS writes the notes, the card says so. If you did not record it, **Tell PMOS** puts the meeting name in the bar so you can type the result. |
|
|
22
22
|
| Free time | 15 minutes or more before the next meeting | The items that fit in the time: overdue items first, then items due today, at most two. Each has **Done** and **Later today**. Under them, what the agent finished for you. |
|
|
23
|
-
| Finished for you | After noon, before 17:00, with no meeting near | The drafts that the agent finished today, newest first. **Read it** shows the draft. Approve a draft, or send it back with a note, on your computer: nothing is sent from the phone. Under them, the items that fit. |
|
|
23
|
+
| Finished for you | After noon, before 17:00, with no meeting near | The drafts that the agent finished today, newest first. **Read it** shows the draft as a document, with its headings, lists and tables, the same as on your computer. Approve a draft, or send it back with a note, on your computer: nothing is sent from the phone. Under them, the items that fit. |
|
|
24
24
|
| Evening recap | From 17:00 | How today went: the meetings, the decisions in their notes, the items you marked done and the agent's drafts. Then what moved, what is still open, and one thing for tonight with **Done** and **Tomorrow**. |
|
|
25
25
|
| End of day | From 20:00, and before 05:00 | Today in one line, the meetings of the next working day (tap one to see its brief), and how many items carry over |
|
|
26
26
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* READ ONLY, and built only from what PMOS already holds. Nothing is invented. The companion reads it for three moments:
|
|
7
7
|
* finished working hours, "Finished for you": the agent's tasks of today's day run (goals/<date>.run.json, lib/day-run.js) that have a
|
|
8
|
-
* draft. { id, title, summary, body, at, status, link, source }; status 'review' (waits for the PM's approval at the desk),
|
|
8
|
+
* draft. { id, title, summary, body, at, status, link, source }; body keeps its lines (markdown, at most 8000 characters); status 'review' (waits for the PM's approval at the desk),
|
|
9
9
|
* 'done' (an auto task) or 'sent'. Newest first, at most 5.
|
|
10
10
|
* recap evening: how the day went. meetings = today's meetings that have ended; decided = the decisions in the final notes of today's
|
|
11
11
|
* meetings; done = the PM's own items marked done today; finished = the agent's drafts of today. moved = at most 5 rows of
|
|
@@ -21,6 +21,8 @@ const { todayAgenda, notesAdapter } = require('./routes-brief-page.js');
|
|
|
21
21
|
const pad = (n) => String(n).padStart(2, '0');
|
|
22
22
|
const localDate = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
23
23
|
const clip = (s, n) => { const t = String(s || '').replace(/\s+/g, ' ').trim(); return t.length > n ? t.slice(0, n - 1) + '…' : t; };
|
|
24
|
+
// the draft's text keeps its lines: the companion draws it as a document (markdown, c3-md.js)
|
|
25
|
+
const clipDoc = (s, n) => { const t = String(s || '').replace(/\r\n?/g, '\n').replace(/\n{3,}/g, '\n\n').trim(); return t.length > n ? t.slice(0, n - 1) + '…' : t; };
|
|
24
26
|
const WD = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
25
27
|
const MO = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
26
28
|
const FINISHED = ['review', 'done', 'sent'];
|
|
@@ -36,7 +38,7 @@ function nextWorkday(now) {
|
|
|
36
38
|
function finishedOf(run) {
|
|
37
39
|
const tasks = run && Array.isArray(run.tasks) ? run.tasks : [];
|
|
38
40
|
return tasks.filter((t) => t && FINISHED.includes(t.status) && t.draft && t.draft.summary)
|
|
39
|
-
.map((t) => ({ id: t.id, title: clip(t.title, 200), summary: clip(t.draft.summary, 300), body:
|
|
41
|
+
.map((t) => ({ id: t.id, title: clip(t.title, 200), summary: clip(t.draft.summary, 300), body: clipDoc(t.draft.body, 8000), at: t.draft.at || null, status: t.status, link: t.draft.link || t.link || null, source: t.source || null }))
|
|
40
42
|
.sort((a, b) => String(b.at || '').localeCompare(String(a.at || '')));
|
|
41
43
|
}
|
|
42
44
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.221.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.221.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -35,7 +35,7 @@ const at = (d, hh, mm) => new Date(2026, 9, d, hh, mm).toISOString();
|
|
|
35
35
|
export const demoDay = () => ({
|
|
36
36
|
ok: true, date: '2026-10-08',
|
|
37
37
|
finished: [
|
|
38
|
-
{ id: 't-0000000000a1', title: 'Reply to Priya on the Checkout v2 date', summary: 'A reply that moves v2 to 31 Oct and keeps beta on 30 Oct, with the reason in one line.', body: '
|
|
38
|
+
{ id: 't-0000000000a1', title: 'Reply to Priya on the Checkout v2 date', summary: 'A reply that moves v2 to 31 Oct and keeps beta on 30 Oct, with the reason in one line.', body: '# Reply to Priya\n\nHi Priya,\n\n**Checkout v2 moves to 31 Oct**: the sandbox keys arrive two days late.\n\n## What stays\n\n- Beta stays on 30 Oct for the two partners already set up.\n- The `checkout_v2` flag stays off until then.', at: at(8, 12, 52), status: 'review', link: null, source: 'my-work' },
|
|
39
39
|
{ id: 't-0000000000a2', title: 'Weekly status for leadership', summary: 'Green on Checkout v2 with the new date; amber on Returns, where the interview slipped a week.', body: '', at: at(8, 12, 40), status: 'done', link: null, source: 'brief' },
|
|
40
40
|
],
|
|
41
41
|
recap: { meetings: 5, decided: 2, done: 1, finished: 2,
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
*
|
|
32
32
|
* Two options for a report, which is a document with no stored file yet (report-families-view.js):
|
|
33
33
|
* generated = { text, why, health } Read shows the generated text; Edit and Proposals say why they need a stored document; Health shows `health`.
|
|
34
|
+
* An agent's draft on a Work item (initiative c3-work-agent, amendment 16) is a generated document with three more hooks:
|
|
35
|
+
* generated.save(text, why) → { ok, error } Edit works: Save hands the text to the caller (the reason is optional) instead of the edit route.
|
|
36
|
+
* generated.ask(note) → { ok, error } Proposals is the note that sends the draft back to the agent.
|
|
37
|
+
* generated.log = [{ who, what, at, text }] Edit log lists these turns (the conversation with the agent).
|
|
34
38
|
* secExtra(name) a node under each section in Read: the report's claims and where they come from.
|
|
35
39
|
* Pure parts (Node-testable): splitDoc, joinDoc, changedSections, wordDiffTokens, byName, dayName, parseFront, setFront, composeSections,
|
|
36
40
|
* copyKindOf, citesIn, outlineOf, hoodOf. */
|
|
@@ -738,7 +742,7 @@ export function openDocSheet({ ws, doc, mode = 'read', prompt = '', relations, o
|
|
|
738
742
|
|
|
739
743
|
/* ── Edit ── */
|
|
740
744
|
function startEdit() {
|
|
741
|
-
if (generated || frozen() || S.text == null) { S.ed = null; return; }
|
|
745
|
+
if ((generated && !generated.save) || frozen() || S.text == null) { S.ed = null; return; }
|
|
742
746
|
S.ed = { text: S.text, base: splitDoc(S.text), front: {}, kind: 'rich', why: '', whyBad: false, node: null, ta: null, range: null, link: false };
|
|
743
747
|
}
|
|
744
748
|
function edNode() {
|
|
@@ -785,7 +789,7 @@ export function openDocSheet({ ws, doc, mode = 'read', prompt = '', relations, o
|
|
|
785
789
|
const s = ui.body.querySelector('[data-doc-savesum]'); if (s) s.replaceChildren(...saveSum(ch).filter(Boolean));
|
|
786
790
|
}
|
|
787
791
|
function saveSum(ch) {
|
|
788
|
-
if (!ch.length) return [c3ic('info', 'sm'), 'No changes yet. Health checks the document again after you save.'];
|
|
792
|
+
if (!ch.length) return [c3ic('info', 'sm'), generated ? 'No changes yet.' : 'No changes yet. Health checks the document again after you save.'];
|
|
789
793
|
const cl = [...new Set(ch.map((c) => clashOf(c.name)).filter(Boolean))];
|
|
790
794
|
return [c3ic('edit', 'sm'), plural(ch.length, 'section') + ' changed: ' + ch.map((c) => c.name).join(', ') + '.',
|
|
791
795
|
cl.length ? el('span', { class: 'ds-warn' }, ' ' + plural(cl.length, 'open proposal') + ' also change' + (cl.length === 1 ? 's' : '') + ' them; accepting it after your save needs a new draft.') : null];
|
|
@@ -835,7 +839,7 @@ export function openDocSheet({ ws, doc, mode = 'read', prompt = '', relations, o
|
|
|
835
839
|
drawEdit();
|
|
836
840
|
}
|
|
837
841
|
function editBody() {
|
|
838
|
-
if (generated) return el('div', { class: 'fold w', role: 'note', 'data-doc-nostore': 'edit' }, c3ic('lock', 'sm'), generated.why);
|
|
842
|
+
if (generated && !generated.save) return el('div', { class: 'fold w', role: 'note', 'data-doc-nostore': 'edit' }, c3ic('lock', 'sm'), generated.why);
|
|
839
843
|
if (frozen()) return el('div', { class: 'ds', 'data-doc-frozen': '' }, el('div', { class: 'fold w', role: 'status' }, c3ic('lock', 'sm'), 'Issued reports are frozen. Start the next cycle to change it.'),
|
|
840
844
|
el('div', { class: 'ds-acts' }, btn('ds-btn pri', { 'data-doc-next': '' }, [c3ic('plus', 'sm'), 'Start the next cycle'], () => nextCycle())));
|
|
841
845
|
if (S.text == null) return readBody();
|
|
@@ -845,12 +849,12 @@ export function openDocSheet({ ws, doc, mode = 'read', prompt = '', relations, o
|
|
|
845
849
|
let area;
|
|
846
850
|
if (E.kind === 'rich') { if (!E.node) E.node = edNode(); area = E.node; E.ta = null; }
|
|
847
851
|
else { E.ta = E.ta || el('textarea', { class: 'ds-src', id: 'dsrc', 'aria-label': 'Markdown source', spellcheck: 'false', 'data-doc-mdsrc': '', onInput: () => { clearTimeout(E.t); E.t = setTimeout(mark, 250); } }, E.text); area = E.ta; E.node = null; }
|
|
848
|
-
const why = el('input', { type: 'text', id: 'dwhy', class: E.whyBad ? 'bad' : '', placeholder: 'Why are you changing it? Kept in the edit log.', 'aria-label': 'Why', 'data-doc-why': '', value: E.why,
|
|
852
|
+
const why = el('input', { type: 'text', id: 'dwhy', class: E.whyBad ? 'bad' : '', placeholder: generated ? 'What did you change? Optional.' : 'Why are you changing it? Kept in the edit log.', 'aria-label': 'Why', 'data-doc-why': '', value: E.why,
|
|
849
853
|
onInput: (e) => { E.why = e.target.value; if (E.whyBad && E.why.trim()) { E.whyBad = false; e.target.classList.remove('bad'); const a = ui.body.querySelector('[data-doc-whybad]'); if (a) a.remove(); } } });
|
|
850
854
|
const save = el('div', { class: 'ds-save' }, el('div', { class: 'sum', 'data-doc-savesum': '' }, saveSum([])), why,
|
|
851
855
|
E.whyBad ? el('div', { class: 'fold w', role: 'alert', 'data-doc-whybad': '' }, c3ic('alert', 'sm'), 'Say why the document changes. The reason is kept in the edit log.') : null,
|
|
852
856
|
S.err ? el('div', { class: 'fold w', role: 'alert' }, c3ic('alert', 'sm'), S.err) : null,
|
|
853
|
-
el('div', { class: 'row' }, el('span', { class: 'ds-mono' }, c3ic('user', 'sm'), ' Saved by you' + (nextV(doc.v) ? ' as ' + nextV(doc.v) : '')),
|
|
857
|
+
el('div', { class: 'row' }, el('span', { class: 'ds-mono' }, c3ic('user', 'sm'), generated ? ' Saved by you as the draft' : ' Saved by you' + (nextV(doc.v) ? ' as ' + nextV(doc.v) : '')),
|
|
854
858
|
el('span', { class: 'ds-acts' }, btn('ds-btn', { 'data-doc-cancel': '' }, 'Cancel', () => { S.ed = null; S.err = ''; go('read'); }),
|
|
855
859
|
btn('ds-btn pri', { 'data-doc-save': '' }, [c3ic('check', 'sm'), 'Save' + (nextV(doc.v) ? ' as ' + nextV(doc.v) : '')], () => saveDoc()))));
|
|
856
860
|
return el('div', { class: 'ds', 'data-doc-edit': '' }, staleBanner(), propsBlock(secs, true), area, save);
|
|
@@ -860,6 +864,7 @@ export function openDocSheet({ ws, doc, mode = 'read', prompt = '', relations, o
|
|
|
860
864
|
const E = S.ed; if (!E || S.busy) return;
|
|
861
865
|
const after = draftText(); const ch = changedSections(S.text, after);
|
|
862
866
|
if (after === S.text || !ch.length) { say('Nothing changed.'); S.ed = null; S.err = ''; go('read'); return; }
|
|
867
|
+
if (generated) { await saveGenerated(after, E.why.trim()); return; }
|
|
863
868
|
if (!E.why.trim()) { E.whyBad = true; E.text = after; if (E.kind === 'rich') { E.base = splitDoc(after); E.node = null; } else if (E.ta) E.ta.value = after; drawEdit(); const w = ui.body.querySelector('#dwhy'); if (w) w.focus(); return; }
|
|
864
869
|
const before = S.text; S.busy = true;
|
|
865
870
|
const r = await pmPost('/api/doc-sheet/edit', { ...base, text: after, why: E.why.trim() }).catch(() => null);
|
|
@@ -874,6 +879,15 @@ export function openDocSheet({ ws, doc, mode = 'read', prompt = '', relations, o
|
|
|
874
879
|
});
|
|
875
880
|
}
|
|
876
881
|
|
|
882
|
+
async function saveGenerated(after, why) {
|
|
883
|
+
const E = S.ed; S.busy = true;
|
|
884
|
+
const r = await Promise.resolve(generated.save(after, why)).catch((e) => ({ ok: false, error: e && e.message }));
|
|
885
|
+
S.busy = false;
|
|
886
|
+
if (!r || !r.ok) { S.err = (r && r.error) || 'The draft was not saved.'; E.text = after; if (E.kind === 'rich') { E.base = splitDoc(after); E.node = null; } drawEdit(); return; }
|
|
887
|
+
S.text = after; generated.text = after; S.err = ''; S.ed = null; S.changed = true; go('read');
|
|
888
|
+
say(generated.saved || 'Saved as the draft.');
|
|
889
|
+
}
|
|
890
|
+
|
|
877
891
|
/* ── Proposals ── */
|
|
878
892
|
function askBox() {
|
|
879
893
|
if (frozen()) return el('div', { class: 'fold w', role: 'status', 'data-doc-frozen': '' }, c3ic('lock', 'sm'), 'Issued reports are frozen. Start the next cycle to ask for a change.');
|
|
@@ -966,6 +980,7 @@ export function openDocSheet({ ws, doc, mode = 'read', prompt = '', relations, o
|
|
|
966
980
|
S.rejFor = null; S.rejWhy = ''; S.log = null; await loadProps(); draw(); say('Rejected. The reason is in the edit log.');
|
|
967
981
|
}
|
|
968
982
|
function propsBody() {
|
|
983
|
+
if (generated && generated.ask) return askGenerated();
|
|
969
984
|
if (generated) return el('div', { class: 'fold w', role: 'note', 'data-doc-nostore': 'ask' }, c3ic('lock', 'sm'), generated.why);
|
|
970
985
|
const op = openProps(); const closed = (S.props || []).filter((p) => p.state !== 'proposed');
|
|
971
986
|
const list = S.props === null ? el('div', { class: 'fold', role: 'status' }, c3ic('clock', 'sm'), 'Reading the proposals.')
|
|
@@ -1054,7 +1069,30 @@ export function openDocSheet({ ws, doc, mode = 'read', prompt = '', relations, o
|
|
|
1054
1069
|
if (!j || !j.ok) { say((j && j.error) || 'The reason was not added.'); return; }
|
|
1055
1070
|
S.whyFor = null; await loadLog(); draw(); say('Reason added. The revision itself is unchanged.');
|
|
1056
1071
|
}
|
|
1072
|
+
/* a draft on a Work item: the note sends it back to the agent, which drafts it again on the item */
|
|
1073
|
+
function askGenerated() {
|
|
1074
|
+
const ta = el('textarea', { id: 'dpr', placeholder: 'Tell the agent what to change', 'aria-label': 'Note to the agent', 'data-doc-prompt': '', onInput: (e) => { S.prompt = e.target.value; } }, S.prompt);
|
|
1075
|
+
const send = async () => {
|
|
1076
|
+
const v = String(S.prompt || '').trim(); if (!v) { say('Tell the agent what to change'); ta.focus(); return; }
|
|
1077
|
+
S.busy = true; draw();
|
|
1078
|
+
const r = await Promise.resolve(generated.ask(v)).catch((e) => ({ ok: false, error: e && e.message }));
|
|
1079
|
+
S.busy = false;
|
|
1080
|
+
if (!r || !r.ok) { S.err = (r && r.error) || 'The draft was not sent back.'; draw(); return; }
|
|
1081
|
+
S.prompt = ''; S.err = ''; ui.close();
|
|
1082
|
+
};
|
|
1083
|
+
return el('div', { class: 'ds', 'data-doc-proposals': '' }, el('div', { class: 'ds-ask' }, el('label', { class: 'ds-h', for: 'dpr' }, c3ic('spark', 'sm'), 'Send it back to the agent'), ta,
|
|
1084
|
+
el('div', { class: 'ds-acts' }, btn('ds-btn ag', { 'data-doc-ask': '', disabled: S.busy, 'aria-disabled': S.busy ? 'true' : null }, [c3ic('reply', 'sm'), 'Send to the agent'], send)),
|
|
1085
|
+
S.err ? el('div', { class: 'fold w', role: 'alert' }, c3ic('alert', 'sm'), S.err) : null,
|
|
1086
|
+
el('p', { class: 'ds-note', 'data-doc-ask-why': '' }, generated.askWhy || 'The agent drafts it again with your note. Nothing is sent anywhere.')));
|
|
1087
|
+
}
|
|
1057
1088
|
function logBody() {
|
|
1089
|
+
if (generated && Array.isArray(generated.log)) {
|
|
1090
|
+
return el('div', { class: 'ds', 'data-doc-log': '' }, el('h3', { class: 'ds-h' }, 'Conversation with the agent · ' + generated.log.length),
|
|
1091
|
+
el('ul', { class: 'ds-closed hx', 'data-doc-genlog': '' }, generated.log.map((t, i) => el('li', { 'data-doc-genturn': String(i) },
|
|
1092
|
+
el('span', { class: 'chip2' + (t.who === 'agent' ? ' ds-ag' : '') }, c3ic(t.who === 'agent' ? 'spark' : 'user', 'sm'), t.who === 'agent' ? 'Agent' : 'You'),
|
|
1093
|
+
el('span', null, el('b', null, t.what), t.text ? el('small', null, short(t.text)) : null),
|
|
1094
|
+
el('span', { class: 'w' }, t.at ? whenOf(t.at) : '')))));
|
|
1095
|
+
}
|
|
1058
1096
|
if (generated) return el('div', { class: 'fold', 'data-doc-nostore': 'hist' }, c3ic('info', 'sm'), generated.why);
|
|
1059
1097
|
if (!S.log) return el('div', { class: 'fold', role: 'status' }, c3ic('clock', 'sm'), 'Reading the edit log.');
|
|
1060
1098
|
const F = [['all', 'All'], ['people', 'People'], ['agent', 'Agent'], ['outside', 'Outside PMOS']];
|
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
* Done and Later today when the row is the PM's own Work item. opts: { onDone(r), onLater(r) }
|
|
8
8
|
* openAll(rows, opts) every row that waits, as a list; a press opens that row (openNeed). opts as openNeed.
|
|
9
9
|
* openDraft(t) a draft the agent finished today (GET /api/phone-day finished): what it is, its summary, its text and its
|
|
10
|
-
* link.
|
|
10
|
+
* link. The text is drawn as a document (c3-md.js, as the desk's Work sheet and the document sheet draw it):
|
|
11
|
+
* headings, lists, tables, bold. Approving or revising it waits for the desk, where the whole draft and its sources are.
|
|
11
12
|
* Nothing here writes: the buttons call back into c3-phone.js, which owns the writes and their Undo. */
|
|
12
13
|
import { el } from './dom.js';
|
|
13
14
|
import { button, row } from './primitives.js';
|
|
14
15
|
import { pmGet } from './http.js';
|
|
15
16
|
import { c3ic } from './pm-work-view.js';
|
|
16
17
|
import { openSheet, closeSheet } from './c3-sheet.js';
|
|
18
|
+
import { parseMd, toDom } from './c3-md.js';
|
|
17
19
|
|
|
18
20
|
const p2 = (n) => String(n).padStart(2, '0');
|
|
19
21
|
const hm = (iso) => { const d = new Date(iso); return p2(d.getHours()) + ':' + p2(d.getMinutes()); };
|
|
@@ -88,7 +90,7 @@ export function openDraft(t) {
|
|
|
88
90
|
const body = el('div', { class: 'c3ph-sheetbody', 'data-c3ph-draft': t.id },
|
|
89
91
|
el('span', { class: 'c3ph-step' }, (t.status === 'review' ? 'Waits for you' : t.status === 'sent' ? 'Sent' : 'Finished').toUpperCase() + (t.at ? ' · ' + hm(t.at) : '')),
|
|
90
92
|
line(t.summary),
|
|
91
|
-
|
|
93
|
+
t.body ? el('div', { class: 'ds-md wk-agent-md c3ph-md' }, toDom(parseMd(String(t.body)), document)) : null,
|
|
92
94
|
t.link ? el('div', { class: 'chips2' }, link(t.link, 'Open the source')) : null,
|
|
93
95
|
line(t.status === 'review' ? 'Approve it, or send it back with a note, on your computer. Nothing is sent from the phone.' : 'Nothing more is needed from you.'));
|
|
94
96
|
return openSheet({ title: t.title, icon: 'file-text', body });
|
|
@@ -59,3 +59,8 @@
|
|
|
59
59
|
.c3 .c3ph-stats b{font-size:20px;font-variant-numeric:tabular-nums}
|
|
60
60
|
.c3 .c3ph-stats span{font-size:11px;color:var(--muted-foreground);line-height:1.25}
|
|
61
61
|
.c3 .c3ph-pts{display:grid;gap:2px}
|
|
62
|
+
/* the agent's draft in its sheet, drawn as a document (c3-md.js): its headings, lists and tables, at the phone's reading size */
|
|
63
|
+
.c3 .c3ph-md{max-width:none;padding-top:8px;border-top:1px solid var(--border);font-size:var(--fs-3);line-height:1.55}
|
|
64
|
+
.c3 .c3ph-md h1,.c3 .c3ph-md h2{font-size:var(--fs-4);margin:12px 0 4px}
|
|
65
|
+
.c3 .c3ph-md p{margin:6px 0}
|
|
66
|
+
.c3 .c3ph-md .tw{max-width:100%}
|
|
@@ -34,7 +34,7 @@ import { parseMd, toDom } from './c3-md.js';
|
|
|
34
34
|
import { makeXp, attachExpand } from './c3-expand.js';
|
|
35
35
|
import { attachSwipe } from './c3-swipe.js';
|
|
36
36
|
import { toast } from './c3-toast.js';
|
|
37
|
-
import { openSheet
|
|
37
|
+
import { openSheet } from './c3-sheet.js';
|
|
38
38
|
import { c3btn } from './page-agent.js';
|
|
39
39
|
import { onBriefing, loadBriefing } from './shell-ticker.js';
|
|
40
40
|
import { reviewSection } from './c3-brief.js';
|
|
@@ -729,9 +729,9 @@ const AG = {
|
|
|
729
729
|
orDecide: 'Or decide', conv: 'Conversation with the agent', show: 'Show', hide: 'Hide', showAll: 'Show all', showLess: 'Show less', open: 'Open', copy: 'Copy', copied: 'Copied.',
|
|
730
730
|
send: 'Send to the agent', replyWhat: (w) => `The agent drafts it again. ${w ? `Nothing goes to ${w} or to anyone.` : 'Nothing leaves PMOS.'}`,
|
|
731
731
|
theDraft: 'the draft above', beingRedrafted: 'being redrafted',
|
|
732
|
-
round: (n) => 'Agent draft, round ' + n, yours: 'Edited by you', viewRound: (n) => 'View round ' + n, viewEdit: 'View your edit',
|
|
732
|
+
round: (n) => 'Agent draft, round ' + n, yours: 'Edited by you', viewRound: (n) => 'View round ' + n, viewEdit: 'View your edit',
|
|
733
733
|
noteLabel: 'Tell the agent what to change', openDraft: 'Open the draft', editDraft: 'Edit the draft', edited: 'Edited by you. Post sends your text.',
|
|
734
|
-
|
|
734
|
+
savedDraft: (w) => `Saved. Post sends your text to ${w}.`, savedMine: 'Saved. The draft stays here.',
|
|
735
735
|
doneIt: 'Mark it done', doneFx: 'Closes the item. Nothing is posted', doneToast: 'Done. Nothing was posted.', draftSheet: (k) => 'Draft for ' + k, stop: 'stop',
|
|
736
736
|
handed: (w) => `Handed to the agent. Nothing goes to ${w} until you post it.`, stopped: 'Stopped. Nothing was kept.', kept: 'Kept as a draft. Post it when you are ready.',
|
|
737
737
|
posting: 'Approved. A session posts exactly this text.', sentBack: 'Sent back with your note. The agent drafts it again.', retried: 'The agent tries again.',
|
|
@@ -903,35 +903,41 @@ function openAgentSession(id, item) {
|
|
|
903
903
|
/* The agent's draft drawn the way a document is (c3-md.js, as the document sheet draws it): headings, lists, tables, bold. */
|
|
904
904
|
const draftMd = (text) => el('div', { class: 'ds-md wk-agent-md' }, toDom(parseMd(String(text || '')), document));
|
|
905
905
|
|
|
906
|
-
/* The agent's draft in
|
|
907
|
-
* as the draft, so Post sends exactly that text
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
const
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
906
|
+
/* The agent's draft opens in the document sheet, the one every other document opens in (amendment 16): Read with its outline,
|
|
907
|
+
* Edit (Save keeps the PM's text as the draft, so Post sends exactly that text: amendment 13), Proposals (a note sends the draft
|
|
908
|
+
* back to the agent), Edit log (the conversation with the agent) and Health. An earlier version (ver) opens read-only. */
|
|
909
|
+
async function openDraft(item, inst, editing, ver) {
|
|
910
|
+
const a = item.agent || {}; const d = a.draft || {};
|
|
911
|
+
const live = !!inst && a.state === 'ready' && !ver;
|
|
912
|
+
const w = whereOf(item); const mine = isMine(item);
|
|
913
|
+
const what = { ask: mine ? 'added the item' : 'asked', draft: 'drafted it', note: 'sent it back', edit: 'edited the draft', post: 'posted it' };
|
|
914
|
+
const log = turnsOf(item).map((t) => ({ who: t.who === 'agent' ? 'agent' : 'pm', what: (t.body ? t.label + ': ' : '') + (what[t.kind] || t.kind), at: t.at || null, text: t.kind === 'draft' ? t.text : t.kind === 'note' || t.kind === 'ask' ? t.text : '' }));
|
|
915
|
+
const why = ver ? 'This is an earlier version. The draft on the item is the newest one.'
|
|
916
|
+
: a.state === 'sent' ? 'This is the text that was posted. It is kept on the item, not as a file.'
|
|
917
|
+
: 'This draft is kept on the item, not as a file. ' + (mine ? 'It stays here in PMOS.' : 'Nothing is in ' + w + ' yet. Post it from the item, after you read it.');
|
|
918
|
+
const generated = { text: ver ? ver.body || '' : d.body || '', why, log, health: { passed: 0, total: 0, checks: [], err: 'Health checks run on stored documents. This draft is kept on the item.' } };
|
|
919
|
+
if (live) {
|
|
920
|
+
generated.save = async (text) => {
|
|
921
|
+
const v = String(text || '').trim();
|
|
922
|
+
if (!v) return { ok: false, error: 'The draft cannot be empty. Send it back with a note instead.' };
|
|
923
|
+
const r = await pmPost('/api/my-work/decide', { item: item.id, action: 'edit', body: v });
|
|
924
|
+
if (r && r.ok) load(inst);
|
|
925
|
+
return r || { ok: false, error: COPY.saveError };
|
|
926
|
+
};
|
|
927
|
+
generated.saved = mine ? AG.savedMine : AG.savedDraft(w);
|
|
928
|
+
generated.ask = async (note) => {
|
|
929
|
+
const r = await pmPost('/api/my-work/decide', { item: item.id, action: 'revise', note });
|
|
930
|
+
if (r && r.ok) { say({ text: AG.sentBack, timeout: 3500 }); load(inst); }
|
|
931
|
+
return r || { ok: false, error: COPY.saveError };
|
|
932
|
+
};
|
|
933
|
+
generated.askWhy = 'The agent drafts it again with your note, on the item. ' + AG.replyWhat(mine ? '' : w);
|
|
934
|
+
}
|
|
935
|
+
const title = (ver ? ver.label + (ver.at ? ' · ' + hhmm(ver.at) : '') + ' · ' : '') + (mine ? 'Draft: ' + (item.title || '') : AG.draftSheet(item.key || item.title));
|
|
936
|
+
const relations = () => el('ul', { class: 'ds-rel', 'data-pm-agent-rel': item.id }, el('li', null, c3ic(item.url ? 'out' : 'doc', 'sm'),
|
|
937
|
+
item.url ? el('a', { class: 'ds-rlk', href: item.url, target: '_blank', rel: 'noopener noreferrer' }, item.key || item.title, el('small', null, 'the item this draft is for')) : el('span', { class: 'ds-rlk' }, item.title || '', el('small', null, 'the item this draft is for'))));
|
|
938
|
+
const { openDocSheet } = await import('./c3-doc-sheet.js');
|
|
939
|
+
return openDocSheet({ ws: inst && inst.ctx ? inst.ctx.ws : '', doc: { path: '', name: title, ty: 'Draft', v: ver ? '' : (d.edited_at ? 'edited by you' : ''), st: a.state === 'sent' ? 'issued' : 'draft', ini: '', iniName: mine ? 'Work' : w },
|
|
940
|
+
mode: editing && live ? 'edit' : 'read', generated, relations });
|
|
935
941
|
}
|
|
936
942
|
|
|
937
943
|
/** The case file element of an item: c3-case.js plus what is Work's own (offline work, the shared actions, the steps, the agent tray, the edit fields). */
|