create-pmos 0.207.0 → 0.209.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.207.0",
3
+ "version": "0.209.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": {
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@b515557
1
+ kernel-source: pmos@dfd2453
2
2
  assembled: 2026-10-14
3
3
  mode: vendor
@@ -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-14T01:10:19Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-14T01:49:09Z"}
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-14T01:10:19Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-14T01:49:09Z"}
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-14T01:10:19Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-14T01:49:09Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -98,6 +98,11 @@ const writes = (p) => (window.__c3demoWrites || []).filter((w) => w.path === p |
98
98
  }
99
99
  /* ── the brief: everything the desktop Briefing counts waits here, and each row opens ── */
100
100
  ok('brief: the morning card counts what waits on you', await waitFor(page, () => /wait on you|waits on you/.test(document.querySelector('[data-c3ph-moment="morning"]')?.textContent || ''), null, 8000));
101
+ ok('brief: the morning shows the top three points of the Briefing deck', await waitFor(page, () => { const c = document.querySelector('[data-c3ph-moment="morning"]'); return !!c && c.querySelectorAll('[data-c3ph-point]').length === 3 && /things this morning/.test(c.textContent); }, null, 8000));
102
+ await page.click('[data-c3ph-moment="morning"] [data-c3ph-point]');
103
+ ok('brief: a point opens its details', await waitFor(page, () => !!document.querySelector('[data-shell="sheet"] [data-c3ph-need]'), null, 5000));
104
+ await page.keyboard.press('Escape');
105
+ await waitFor(page, () => !document.querySelector('[data-shell="sheet"] [data-c3ph-need]'), null, 3000);
101
106
  ok('brief: the briefing needs are in the waiting list', await waitFor(page, () => !!document.querySelector('[data-c3ph-all-open]'), null, 8000));
102
107
  if (await page.$('[data-c3ph-all-open]')) {
103
108
  await page.click('[data-c3ph-all-open]');
@@ -98,7 +98,9 @@ An item goes to the first view that fits these rules, in this order. Parked is S
98
98
 
99
99
  ### Let the agent draft an item
100
100
 
101
- Open an assigned item that shows **Agent can:**. The case file shows **What the agent will do**.
101
+ Open an item that shows **Agent can:**. The case file shows **What the agent will do**.
102
+
103
+ The offer is on Jira and WBS items with a suggestion, and on each item the morning brief suggests for you (for example a Slack reply or an email). On a brief item the offer is the reply or the note that the item asks for, and the draft goes back to the place the item came from, only when you post it.
102
104
 
103
105
  1. To change the instruction, select **Change what the agent does** and type a new one.
104
106
  2. Select **Let the agent draft this** and hold to confirm. This adds one job to today's day run, with the instruction.
@@ -15,7 +15,7 @@ The top card shows where you are in your day and why it is useful now. PMOS choo
15
15
 
16
16
  | Moment | When | What the card shows |
17
17
  |---|---|---|
18
- | Your day | Before noon, with no meeting in the next 30 minutes | The next meeting, the number of things that wait on you and how many are overdue, and **The brief of this meeting** |
18
+ | Your morning | Before noon, with no meeting in the next 30 minutes | Your briefing: the top three points of the desktop Briefing, each opens its details. Then how many things wait on you, how many drafts wait for your review, and the first meeting with **The brief of this meeting** |
19
19
  | Before you walk in | The next meeting starts in 30 minutes or less | What is prepared for it, the items to close first, each with **Done**, and **The brief of this meeting** |
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. |
@@ -15,6 +15,7 @@
15
15
  * newRun(date, now) · readRun(raw, date)
16
16
  * addTasks(run, brief) agent priorities (kind approve|auto) → waiting tasks
17
17
  * addBriefed(run, spec) one task the PM briefed in My work (initiative my-work, PRD R5)
18
+ * withdraw(run, taskId) a failed task taken out, so its item can be briefed again
18
19
  * addSend(run, spec, now) the PM's own text, written in the card, to post as written (today-act-prepare)
19
20
  * toStart(run, limit) the tasks to start now, each with its mode (do | revise | send)
20
21
  * started · deliver · decide · sent · fail · stop · sessionEnded · sessionStalled
@@ -283,6 +284,18 @@ function stop(run, taskId, reason, now) {
283
284
  });
284
285
  }
285
286
 
287
+ /** Take a failed task out of the run (c3-work-agent amendment 10): the PM stopped the agent on a brief item, so the item
288
+ * offers the draft again and a new press adds the task anew. */
289
+ function withdraw(run, taskId) {
290
+ if (!run) return { ok: false, error: 'no day run today' };
291
+ const t = findTask(run, taskId);
292
+ if (!t) return { ok: false, error: `no task ${taskId} in today's run` };
293
+ if (t.status !== 'failed') return { ok: false, error: `task ${taskId} is ${t.status} — only a stopped or failed task can be taken out` };
294
+ const next = clone(run);
295
+ next.tasks = next.tasks.filter((x) => x.id !== taskId);
296
+ return { ok: true, run: next };
297
+ }
298
+
286
299
  /** A session ended. A task it was working on without delivering, or sending without reporting, has failed. */
287
300
  function sessionEnded(run, sessionId, now) {
288
301
  if (!run) return { ok: false, error: 'no day run today' };
@@ -400,5 +413,5 @@ function checkDone(run, { found = 0, resolved = 0 } = {}, now) {
400
413
  }
401
414
 
402
415
  module.exports = { MAX_PARALLEL, MAX_REVISIONS, MAX_CHECK_LOG, MAX_INTERVAL_MIN, STATUSES, KINDS, ACTIONS, TASK_RE,
403
- newRun, readRun, findTask, modeOf, addTasks, addBriefed, addSend, toStart, started, deliver, decide, sent, fail, stop, sessionEnded, sessionStalled, stage, intervalMin,
416
+ newRun, readRun, findTask, modeOf, addTasks, addBriefed, addSend, toStart, started, deliver, decide, sent, fail, stop, withdraw, sessionEnded, sessionStalled, stage, intervalMin,
404
417
  checkDue, checkStarted, checkDone };
@@ -28,6 +28,7 @@ const myWork = require('./my-work.js');
28
28
  const boardFold = require('./board-fold.js');
29
29
  const offloop = require('./offloop.js');
30
30
  const dayRun = require('./day-run.js');
31
+ const assigned = require('./work-assigned.js');
31
32
  const { localDate } = require('./brief.js');
32
33
 
33
34
  const dirOf = (ctx) => path.join(ctx.runtimeDir, 'my-work');
@@ -121,6 +122,9 @@ async function handle(req, res, url, ctx) {
121
122
  return true;
122
123
  }
123
124
 
125
+ const bm = BRIEF_ITEM.exec(String((body && body.item) || ''));
126
+ if (bm && p !== '/api/my-work/refresh') { briefPress(ctx, res, p.slice('/api/my-work/'.length), body, bm[1], now); return true; }
127
+
124
128
  if (p === '/api/my-work/decide' || p === '/api/my-work/stop') { taskPress(ctx, res, p.slice('/api/my-work/'.length), body, now); return true; }
125
129
 
126
130
  // act
@@ -196,4 +200,44 @@ function taskPress(ctx, res, verb, body, now) {
196
200
  ctx.json(res, { ok: true, item: after });
197
201
  }
198
202
 
203
+ /* ── the agent on an item the morning brief suggests for the PM (c3-work-agent amendment 10) ──────────────────────
204
+ * Item `brief:<id>`; its task is `t-<id>` in today's day run, added as Your day's "Have the agent draft it" adds it. */
205
+ const BRIEF_ITEM = /^brief:([0-9a-f]{12})$/;
206
+
207
+ function briefPress(ctx, res, verb, body, id, now) {
208
+ const today = localDate(now);
209
+ let brief = null; try { brief = JSON.parse(fs.readFileSync(path.join(ctx.runtimeDir, 'briefs', today + '.json'), 'utf8')); } catch { brief = null; }
210
+ const x = brief && brief.date === today ? [...(brief.priorities || []), ...(brief.items || [])].find((y) => y && y.id === id) : null;
211
+ if (!x || x.owner !== 'pm') { ctx.json(res, { ok: false, error: 'no item of yours with that id in today\'s brief' }, 404); return; }
212
+ const taskId = 't-' + id;
213
+ const after = (task) => ctx.json(res, { ok: true, item: { id: 'brief:' + id, agent: assigned.briefAgent(x, ctx.readDayRun ? ctx.readDayRun(today) : null) }, task: task || null });
214
+ if (verb === 'act') {
215
+ if (body.action !== 'brief') { ctx.json(res, { ok: false, error: 'a brief item takes only action brief here — mark it done or set it aside from Work' }, 400); return; }
216
+ // the offer as shown ("Draft your reply: …") is not a change: the task then gets the full ask, written for the agent
217
+ const own = typeof body.instruction === 'string' && body.instruction.trim() !== assigned.briefSuggest(x) ? body.instruction.trim().slice(0, 500) : '';
218
+ const b = ctx.myWorkBrief(assigned.briefDraftSpec(x, own), String(body.ws || ''));
219
+ if (!b.ok) { ctx.json(res, { ok: false, error: b.error }, b.status || 500); return; }
220
+ after(b.task); return;
221
+ }
222
+ const run = ctx.readDayRun ? ctx.readDayRun(today) : null;
223
+ const t = run ? dayRun.findTask(run, taskId) : null;
224
+ if (!t) { ctx.json(res, { ok: false, error: 'no agent works on this item — let the agent draft it first' }, 409); return; }
225
+ let next = null;
226
+ if (verb === 'decide') {
227
+ const r = dayRun.decide(run, taskId, { action: body.action, note: body.note }, now);
228
+ if (!r.ok) { ctx.json(res, { ok: false, error: r.error }, 400); return; }
229
+ next = ctx.startDayTasks ? ctx.startDayTasks(r.run, now) : r.run;
230
+ } else {
231
+ // stop: only a task that has not delivered; its session ends and the task leaves the run, so the item offers the draft again
232
+ if (t.status !== 'waiting' && t.status !== 'working' && t.status !== 'failed') { ctx.json(res, { ok: false, error: `the agent's task is ${t.status} — there is nothing to stop` }, 409); return; }
233
+ if (t.session_id && t.status === 'working' && ctx.endSession) { try { ctx.endSession(t.session_id); } catch { /* the session already ended */ } }
234
+ const f = t.status === 'failed' ? { ok: true, run } : dayRun.stop(run, taskId, 'You stopped it.', now);
235
+ const w = f.ok ? dayRun.withdraw(f.run, taskId) : f;
236
+ if (!w.ok) { ctx.json(res, { ok: false, error: w.error }, 409); return; }
237
+ next = w.run;
238
+ }
239
+ if (!ctx.writeDayRun || !ctx.writeDayRun(next)) { ctx.json(res, { ok: false, error: 'could not save — nothing changed' }, 500); return; }
240
+ after(dayRun.findTask(next, taskId));
241
+ }
242
+
199
243
  module.exports = { prefixes: ['/api/my-work'], handle, listFor, listForAsync };
@@ -116,19 +116,23 @@ function readBrief(ctx, now) {
116
116
  if (!ctx.runtimeDir) return null;
117
117
  try { return JSON.parse(fs.readFileSync(path.join(ctx.runtimeDir, 'briefs', require('./brief.js').localDate(now.getTime()) + '.json'), 'utf8')); } catch { return null; }
118
118
  }
119
- function assignedFrom(myWork, brief, now) {
119
+ // today's day run, for the agent state of a brief item (c3-work-agent amendment 10). Best effort: no run, no task.
120
+ function readRun(ctx, now) {
121
+ try { return typeof ctx.readDayRun === 'function' ? ctx.readDayRun(require('./brief.js').localDate(now.getTime())) : null; } catch { return null; }
122
+ }
123
+ function assignedFrom(myWork, brief, now, run) {
120
124
  const todayIso = require('./brief.js').localDate(now.getTime());
121
- return [...assigned.fromMyWork(myWork && myWork.ok ? myWork.items : []), ...assigned.fromBrief(brief, todayIso)];
125
+ return [...assigned.fromMyWork(myWork && myWork.ok ? myWork.items : []), ...assigned.fromBrief(brief, todayIso, run)];
122
126
  }
123
127
  async function assignedAsync(ctx, now) {
124
128
  let mw = null;
125
129
  try { mw = await require('./routes-my-work.js').listForAsync(ctx, now.getTime()); } catch { mw = null; }
126
- return assignedFrom(mw, readBrief(ctx, now), now);
130
+ return assignedFrom(mw, readBrief(ctx, now), now, readRun(ctx, now));
127
131
  }
128
132
  function assignedSync(ctx, now) {
129
133
  let mw = null;
130
134
  try { mw = require('./routes-my-work.js').listFor(ctx, now.getTime()); } catch { mw = null; }
131
- return assignedFrom(mw, readBrief(ctx, now), now);
135
+ return assignedFrom(mw, readBrief(ctx, now), now, readRun(ctx, now));
132
136
  }
133
137
  const VIRTUAL_ID = /^(jira:|wbs:|brief:)/;
134
138
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  const TIER_PRIORITY = { P1: 'P1', P2: 'P2', P3: 'P3' };
16
16
  const AGENT = 'an agent';
17
- const SHOWN = ['origin', 'key', 'url', 'status', 'suggest', 'why', 'my_work_state', 'repo', 'initiative', 'agent'];
17
+ const SHOWN = ['origin', 'key', 'url', 'status', 'suggest', 'why', 'my_work_state', 'repo', 'initiative', 'agent', 'place'];
18
18
 
19
19
  /* The agent on a my-work item (initiative c3-work-agent): its state, read from lib/my-work.js stateOf.
20
20
  * suggest nothing started (the suggestion, when there is one, is the offer) queued · working · sending · failed the day task
@@ -56,7 +56,33 @@ function fromMyWork(items) {
56
56
  return out;
57
57
  }
58
58
 
59
- function fromBrief(brief, todayIso) {
59
+ /* An item the morning brief suggests for the PM (amendment 10): the agent drafts it on the PM's press, as Your day's "Have the
60
+ * agent draft it" does. Its task in today's day run is `t-<brief item id>`; the state is read from that task. */
61
+ const BRIEF_VERB = { reply: 'reply', review: 'review notes', decide: 'answer', 'sign-off': 'sign-off comment', read: 'summary', do: 'draft' };
62
+ const briefVerb = (x) => BRIEF_VERB[x.verb] || (x.reply_needed ? 'reply' : 'draft');
63
+ const briefWhat = (x) => String(x.todo || x.how || x.ask || x.title || '').trim();
64
+ /** The offer shown on the item: "Draft your reply: …". */
65
+ function briefSuggest(x) { return `Draft your ${briefVerb(x)}: ${briefWhat(x)}`.slice(0, 300); }
66
+ /** What lib/day-run.js addBriefed takes for brief item `x`; `instruction` is the PM's changed ask, or empty for the offer. */
67
+ function briefDraftSpec(x, instruction) {
68
+ const ask = String(instruction || '').trim() || `Draft the PM's ${briefVerb(x)} for this item: ${briefWhat(x)}`;
69
+ return { id: 't-' + x.id, item_id: x.id, title: x.title, source: x.source, link: x.link || null,
70
+ instruction: `${ask}${/[.!?]$/.test(ask) ? '' : '.'} Draft only. Send nothing: the PM approves it first.`,
71
+ context: { from: x.who || null, where: x.where || null, about: x.about || null, ask: x.ask || null, how: x.how || null, why_now: x.why_now || null } };
72
+ }
73
+ const BRIEF_STATE = { waiting: 'queued', working: 'working', sending: 'sending', failed: 'failed', review: 'ready', done: 'ready', sent: 'sent' };
74
+ function briefAgent(x, run) {
75
+ const t = run && Array.isArray(run.tasks) ? run.tasks.find((y) => y && y.id === 't-' + x.id) || null : null;
76
+ const base = { suggest: briefSuggest(x), instruction: null, draft: null, sent: null, error: null, session: null, asks: null, revisions: 0, started_at: null, decided_at: null };
77
+ if (!t) return { state: 'suggest', ...base };
78
+ return { ...base, state: BRIEF_STATE[t.status] || 'queued', instruction: t.instruction || null,
79
+ draft: t.draft ? { summary: t.draft.summary || null, body: t.draft.body || null } : null,
80
+ sent: t.sent ? { link: t.sent.link || null, at: t.sent.at || null } : null, error: t.error || null,
81
+ session: t.stalled_session ? String(t.stalled_session) : null, asks: t.asks || null, revisions: Array.isArray(t.revisions) ? t.revisions.length : 0,
82
+ started_at: t.started_at || null, decided_at: (t.decided && t.decided.at) || null };
83
+ }
84
+
85
+ function fromBrief(brief, todayIso, run = null) {
60
86
  if (!brief || brief.date !== todayIso) return [];
61
87
  const list = [...(Array.isArray(brief.priorities) ? brief.priorities : []), ...(Array.isArray(brief.items) ? brief.items : [])];
62
88
  const seen = new Set();
@@ -65,7 +91,8 @@ function fromBrief(brief, todayIso) {
65
91
  if (!x || !x.id || !x.title || x.owner !== 'pm' || x.worth_asking || seen.has(x.id)) continue;
66
92
  seen.add(x.id);
67
93
  out.push({ id: 'brief:' + x.id, title: String(x.title), source: 'brief', origin: 'brief', suggested: true, flagged: true,
68
- due: null, priority: null, waiting_on: null, job: null, why: x.why_now || null, initiative: x.initiative_id || null });
94
+ due: null, priority: null, waiting_on: null, job: null, why: x.why_now || null, initiative: x.initiative_id || null,
95
+ place: x.source || null, agent: briefAgent(x, run && run.date === todayIso ? run : null) });
69
96
  }
70
97
  return out;
71
98
  }
@@ -77,7 +104,7 @@ function addable(virtuals, itemLines) {
77
104
  }
78
105
 
79
106
  function createdEvent(v) {
80
- const { id, source, agent, ...fields } = v; // the agent state is read on every list, never stored
107
+ const { id, source, agent, place, ...fields } = v; // the agent state (and a brief item's place) is read on every list, never stored
81
108
  return { event: 'created', id, fields, source };
82
109
  }
83
110
 
@@ -90,4 +117,4 @@ function overlay(item, byId) {
90
117
  return out;
91
118
  }
92
119
 
93
- module.exports = { fromMyWork, fromBrief, addable, createdEvent, overlay, agentOf, AGENT };
120
+ module.exports = { fromMyWork, fromBrief, addable, createdEvent, overlay, agentOf, briefAgent, briefSuggest, briefDraftSpec, AGENT };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.207.0",
3
+ "version": "0.209.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.207.0",
9
+ "version": "0.209.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@xterm/addon-fit": "^0.11.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.207.0",
3
+ "version": "0.209.0",
4
4
  "private": true,
5
5
  "description": "PMOS Terminal Workspace — local companion hosting real PTYs beside the library and gates (D62)",
6
6
  "license": "MIT",
@@ -58,3 +58,4 @@
58
58
  .c3 .c3ph-stats div{display:grid;gap:2px}
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
+ .c3 .c3ph-pts{display:grid;gap:2px}
@@ -8,7 +8,8 @@
8
8
  * actions and questions picked out so far (dest-notes.js partsOf), as the desktop note shows them
9
9
  * before a meeting: when it starts, who, what is prepared, the "before you go" items with Done
10
10
  * just ended: tell PMOS what came out of it a free gap: the own items that fit, with Done and Later today
11
- * morning: the next meeting and how much waits
11
+ * morning ("Your morning"): the briefing, the top three cards of the desktop Briefing's deck (GET /api/briefing cards), each
12
+ * opening its details; how much waits; the drafts waiting for review; then the first meeting with its brief
12
13
  * working hours, and a free gap: "Finished for you", the agent's drafts of today (GET /api/phone-day finished), each opens
13
14
  * to read (c3-phone-detail.js openDraft); approving one waits for the desk
14
15
  * evening (recap, from 17:00): how today went (GET /api/phone-day recap), what moved, what is still open, one thing tonight
@@ -31,7 +32,7 @@
31
32
  * There is no way to the desktop pages on a phone.
32
33
  * The moment is worked out again every minute; when it changes, a short banner says so. ?at=HH:MM sets the clock in demo mode only. */
33
34
  import { el } from './dom.js';
34
- import { button, iconButton } from './primitives.js';
35
+ import { button, iconButton, ensureComponentsCss } from './primitives.js';
35
36
  import { pmPost, pmGet } from './http.js';
36
37
  import { ensureC3Css, c3ic } from './pm-work-view.js';
37
38
  import { onBriefPage, loadBriefPage } from './c3-brief-data.js';
@@ -66,7 +67,7 @@ export function phoneWanted(store) {
66
67
  }
67
68
 
68
69
  export function mountPhone(root, ctx = {}) {
69
- ensureC3Css(); ensureCss();
70
+ ensureC3Css(); ensureComponentsCss(); ensureCss();
70
71
  const demo = !!ctx.demo;
71
72
  const q = new URLSearchParams(location.search);
72
73
  const at = demo && /^\d{1,2}:\d{2}$/.test(q.get('at') || '') ? q.get('at').split(':').map(Number) : null;
@@ -220,6 +221,15 @@ export function mountPhone(root, ctx = {}) {
220
221
  return out;
221
222
  }
222
223
  const decisions = () => waitRows().filter((r) => !r.pm).length;
224
+ /* the top points of the desktop Briefing (GET /api/briefing cards, the deck), at most three, each opening its details */
225
+ function briefPoints() {
226
+ const cards = (S.briefing && Array.isArray(S.briefing.cards) ? S.briefing.cards : []).filter((c) => c && c.headline);
227
+ return cards.slice(0, 3).map((c, i) => {
228
+ const v = cardView(c);
229
+ return { id: String(c.key || 'card' + i), pm: false, decide: !!(c.move && c.move.options), headline: v.headline,
230
+ label: v.tag + (v.deadline && v.deadline.text ? ' · ' + v.deadline.text : ''), why: c.why && c.why.text, url: c.url, urlLabel: c.url_label, facts: v.facts };
231
+ });
232
+ }
223
233
  const needOpts = { onDone: (r) => itemDone(r), onLater: (r) => itemLater(r, 'later-today') };
224
234
  const focus = (m) => import('./c3-focus.js').then((f) => f.openFocus({ event: { id: m.id, start: m.start, at: m.start, end: m.end || null, title: m.title }, date: String(m.start || '').slice(0, 10), phone: true }))
225
235
  .catch(() => toast('Focus did not open.'));
@@ -316,11 +326,18 @@ export function mountPhone(root, ctx = {}) {
316
326
  if (mo.kind === 'recap') return recapCard();
317
327
  if (mo.kind === 'night') return nightCard();
318
328
  if (mo.kind === 'morning') {
329
+ /* the morning is the briefing (mockup version 7, "Your morning"): the top points of the desktop Briefing's deck, then the first meeting */
319
330
  const w = waitRows(); const late = w.filter((r) => /overdue|late/i.test(r.label)).length;
320
- return card(true, { 'data-c3ph-moment': 'morning' }, why('Your day'), big(mo.next ? mo.next.title + ' at ' + hm(mo.next.start) : 'No meetings today'),
321
- line([mo.next ? 'First up, ' + inText((Date.parse(mo.next.start) - now().getTime()) / 60000) + '.' : null,
322
- w.length ? w.length + (w.length === 1 ? ' thing waits on you' : ' things wait on you') + (late ? ', ' + late + ' overdue.' : '.') : 'Nothing waits on you.'].filter(Boolean).join(' ')),
323
- mo.next ? acts(btn('The brief of this meeting', 'outline', 'calendar', { 'data-c3ph-meeting-open': mo.next.id }, () => meetingSheet(mo.next))) : null);
331
+ const pts = briefPoints();
332
+ const rv = S.briefing && Array.isArray(S.briefing.review) ? S.briefing.review.length : 0;
333
+ return card(true, { 'data-c3ph-moment': 'morning', 'data-c3ph-brief-points': String(pts.length) }, why('Your morning'),
334
+ big(!S.briefing ? 'Reading your briefing' : pts.length ? (pts.length === 1 ? 'One thing this morning' : ['', '', 'Two', 'Three'][pts.length] + ' things this morning') : 'Nothing pressing this morning'),
335
+ pts.length ? el('div', { class: 'c3ph-pts' }, ...pts.map((r) => row({ title: r.headline, meta: [r.label], compact: true, onOpen: () => openNeed(r, needOpts), openLabel: 'Open ' + r.headline, attrs: { 'data-c3ph-point': r.id } }))) : null,
336
+ line([w.length ? w.length + (w.length === 1 ? ' thing waits on you' : ' things wait on you') + (late ? ', ' + late + ' overdue.' : '.') : 'Nothing waits on you.',
337
+ rv ? (rv === 1 ? '1 draft waits' : rv + ' drafts wait') + ' for your review on your computer.' : null].filter(Boolean).join(' ')),
338
+ mo.next ? el('div', { class: 'c3ph-fit', 'data-c3ph-first': mo.next.id }, el('b', null, 'First up: ' + mo.next.title + ' at ' + hm(mo.next.start)),
339
+ line(inText((Date.parse(mo.next.start) - now().getTime()) / 60000).replace(/^in/, 'In') + (who(mo.next) ? ' · ' + who(mo.next) : '')),
340
+ acts(btn('The brief of this meeting', 'outline', 'calendar', { 'data-c3ph-meeting-open': mo.next.id }, () => meetingSheet(mo.next)))) : line('No meetings today.'));
324
341
  }
325
342
  return recapCard();
326
343
  }
@@ -696,7 +696,7 @@ function startRun(inst, item, intent) {
696
696
  }
697
697
 
698
698
 
699
- /* ── the agent on a Jira or WBS item (initiative c3-work-agent, planning/prd/c3-work-agent-prd.md) ───────────────────
699
+ /* ── the agent on a Jira, WBS or morning-brief item (initiative c3-work-agent, planning/prd/c3-work-agent-prd.md) ────────
700
700
  * item.agent = { state: suggest|queued|working|ready|sending|sent|failed, suggest, instruction, draft: { summary, body }, sent, error, … }
701
701
  * from lib/work-assigned.js. The suggestion is an option the PM picks; the day task does the work; nothing goes outward
702
702
  * until the PM holds Post (POST /api/my-work/decide approve). Stop ends a task that has not delivered (POST /api/my-work/stop). */
@@ -704,7 +704,9 @@ const AGENT_BUSY = ['queued', 'working', 'sending'];
704
704
  const AGENT_POLL_MS = 15000;
705
705
  const hhmm = (iso) => { const d = new Date(iso); return Number.isNaN(d.getTime()) ? '' : String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); };
706
706
  const minsAgo = (iso) => { const t = Date.parse(iso); if (!Number.isFinite(t)) return ''; const m = Math.max(0, Math.round((Date.now() - t) / 60000)); return m < 1 ? 'just now' : m < 60 ? m + ' min ago' : Math.floor(m / 60) + ' h ago'; };
707
- const whereOf = (item) => (item.origin === 'jira' ? 'Jira' : 'its source');
707
+ // where a post goes: Jira for a ticket; for an item the morning brief suggests, the place it came from (amendment 10)
708
+ const PLACE = { mail: 'email', slack: 'Slack', jira: 'Jira', confluence: 'Confluence', github: 'GitHub', drive: 'Drive', figma: 'Figma', calendar: 'the calendar', wispr: 'Wispr' };
709
+ const whereOf = (item) => (item.origin === 'jira' ? 'Jira' : PLACE[item.place] || 'its source');
708
710
  const AG = {
709
711
  can: (t) => 'Agent can: ' + t, queued: 'Agent: queued', working: (ago) => 'Agent: working' + (ago ? ' · started ' + ago : ''), ready: 'Agent: draft ready',
710
712
  sending: 'Agent: posting', sent: (w, at) => 'Posted to ' + w + (at ? ' ' + at : ''), failed: (asks) => (asks ? 'Agent: needs you' : 'Agent stopped'),
@@ -747,7 +749,7 @@ async function agentPress(inst, path, body, text) {
747
749
  }
748
750
 
749
751
  function agentCase(inst, item, L) {
750
- const a = item.agent; const w = whereOf(item); const k = item.key || item.title;
752
+ const a = item.agent; const w = whereOf(item); const k = item.key || (item.origin === 'brief' ? w : item.title);
751
753
  const o = (id, l, f, run, extra) => ({ id, l, rec: 0, fx: f, run, ...(extra || {}) });
752
754
  const own = L.opts.slice(0, 2).map((x) => ({ ...x, rec: 0 }));
753
755
  const ask = () => inst.asks.get(item.id) || a.instruction || a.suggest || '';
@@ -8474,11 +8474,8 @@ async function handleRequest(req, res) {
8474
8474
  const x = brief ? [...(brief.priorities || []), ...(brief.worth_asking || [])].find((y) => y.id === String(body.item || '')) : null;
8475
8475
  if (!x) return json(res, { ok: false, error: 'no item with that id in today\'s brief' }, 404);
8476
8476
  if (x.owner !== 'pm') return json(res, { ok: false, error: 'the agent already does this job: approve or revise its draft' }, 409);
8477
- const verb = { reply: 'reply', review: 'review notes', decide: 'answer', 'sign-off': 'sign-off comment', read: 'summary', do: 'draft' }[x.verb] || (x.reply_needed ? 'reply' : 'draft');
8478
- const spec = { id: 't-' + x.id, item_id: x.id, title: x.title, source: x.source, link: x.link || null,
8479
- instruction: `Draft the PM's ${verb} for this item: ${x.todo || x.how || x.ask || x.title}. Draft only. Send nothing: the PM approves it first.`,
8480
- context: { from: x.who || null, where: x.where || null, about: x.about || null, ask: x.ask || null, how: x.how || null, why_now: x.why_now || null } };
8481
- const r = myWorkBrief(spec, body.ws);
8477
+ // the same ask as Work's "Let the agent draft this" on this item (lib/work-assigned.js, c3-work-agent amendment 10)
8478
+ const r = myWorkBrief(require('./lib/work-assigned.js').briefDraftSpec(x, ''), body.ws);
8482
8479
  if (!r.ok) return json(res, { ok: false, error: r.error }, r.status || 400);
8483
8480
  return json(res, { ok: true, task: r.task });
8484
8481
  }