create-pmos 0.226.0 → 0.227.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.226.0",
3
+ "version": "0.227.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@2164aa0
1
+ kernel-source: pmos@87765df
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-14T06:56:55Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-14T07:02:16Z"}
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-14T06:56:55Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-14T07:02:16Z"}
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-14T06:56:55Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-14T07:02:16Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -134,7 +134,9 @@ Or choose one option:
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
 
137
- The agent only drafts. It never posts to Jira until you approve the draft.
137
+ Some work needs an action in another system, for example creating an exploration in GA4. The agent does not try that action while it drafts. The draft names the action as its step, and the card shows **Step when you approve**. Then the first option is **Do it**, in place of **Post it to**, also on an item you added. Hold to confirm. A new job session starts with Claude in Chrome and permission to use the browser tools only, does that one step, and reports what it made. Chrome must be open with the Claude extension, and you must be signed in to the system.
138
+
139
+ The agent only drafts. It never posts to Jira or acts in another system until you approve the draft.
138
140
 
139
141
  ### Defer and flag
140
142
 
@@ -204,14 +204,21 @@ function deliver(run, taskId, d, now) {
204
204
  if (!body) return 'a draft needs a body';
205
205
  if (body.length > 8000) return 'the body is over 8000 characters';
206
206
  if (b.link !== undefined && b.link !== null && b.link !== '' && !isUrl(b.link)) return 'the link must be an http(s) address of at most 500 characters';
207
- t.draft = { summary, body, link: b.link || null, at: iso(now) };
208
- logTurn(t, { who: 'agent', kind: 'draft', at: iso(now), text: summary, body });
207
+ // the one step in another system that approval triggers (amendment 17), such as "Create the cohort exploration in GA4"
208
+ const step = text(b.step);
209
+ if (step.length > 240) return 'the step is over 240 characters';
210
+ if (step && t.kind !== 'approve') return 'only a task the PM approves can name a step';
211
+ t.draft = { summary, body, link: b.link || null, at: iso(now), ...(step ? { step } : {}) };
212
+ logTurn(t, { who: 'agent', kind: 'draft', at: iso(now), text: summary, body, ...(step ? { step } : {}) });
209
213
  t.status = t.kind === 'auto' ? 'done' : 'review';
210
214
  t.error = null;
211
215
  return null;
212
216
  });
213
217
  }
214
218
 
219
+ /** The step in another system that a send of task `t` does (amendment 17), or null when the send posts the draft. */
220
+ const stepOf = (t) => (t && t.draft && typeof t.draft.step === 'string' && t.draft.step.trim() ? t.draft.step.trim() : null);
221
+
215
222
  /** The task leaves its session: `prev_session` keeps its id, so the next step can run in the same session while it is open. */
216
223
  function keepSession(t) {
217
224
  if (t.session_id) t.prev_session = t.session_id;
@@ -451,5 +458,5 @@ function checkDone(run, { found = 0, resolved = 0 } = {}, now) {
451
458
  }
452
459
 
453
460
  module.exports = { MAX_PARALLEL, MAX_CHECK_LOG, MAX_INTERVAL_MIN, STATUSES, KINDS, ACTIONS, TASK_RE,
454
- newRun, readRun, findTask, modeOf, addTasks, addBriefed, addSend, toStart, started, deliver, decide, edit, carry, sent, fail, stop, withdraw, sessionEnded, sessionStalled, stage, intervalMin,
461
+ newRun, readRun, findTask, modeOf, stepOf, addTasks, addBriefed, addSend, toStart, started, deliver, decide, edit, carry, sent, fail, stop, withdraw, sessionEnded, sessionStalled, stage, intervalMin,
455
462
  checkDue, checkStarted, checkDone };
@@ -194,7 +194,7 @@ function stateOf(item, presses, runs) {
194
194
  if (!last || last.action === 'reopen') return { state: 'brief', sub: null, task: null, instruction: null };
195
195
  if (last.action === 'handle') return { state: 'yours', sub: null, task: null, instruction: null, decided_at: last.at };
196
196
  const t = taskOf(runs, last);
197
- const task = t ? { id: t.id, status: t.status, error: t.error || null, draft: t.draft ? t.draft.summary : null, body: t.draft ? t.draft.body || null : null, edited_at: t.draft ? t.draft.edited_at || null : null,
197
+ const task = t ? { id: t.id, status: t.status, error: t.error || null, draft: t.draft ? t.draft.summary : null, body: t.draft ? t.draft.body || null : null, edited_at: t.draft ? t.draft.edited_at || null : null, step: t.draft ? t.draft.step || null : null,
198
198
  sent: t.sent || null, started_at: t.started_at || null, revisions: Array.isArray(t.revisions) ? t.revisions.length : 0,
199
199
  session: t.stalled_session || null, asks: t.asks || null, log: Array.isArray(t.log) ? t.log : null } : null;
200
200
  const base = { task, instruction: last.instruction, decided_at: last.at };
@@ -20,7 +20,7 @@
20
20
  * pmos proactive goals [list] | add TEXT [--initiative ID] | done N | drop N | reopen N | remove N | plan [--repo PATH]
21
21
  * pmos proactive day [--repo PATH] the day run: its stage, its tasks and the daytime check
22
22
  * pmos proactive day show TASK one task with its item, draft and revision notes (what day-task reads)
23
- * pmos proactive day deliver TASK --summary S --body-file F [--link URL]
23
+ * pmos proactive day deliver TASK --summary S --body-file F [--link URL] [--step S]
24
24
  * pmos proactive day sent TASK [--link URL] | day fail TASK --reason R
25
25
  * pmos proactive day approve|revise|retry TASK [--note N] | day check
26
26
  *
@@ -233,7 +233,7 @@ async function run(argv, { call, say, cwd = process.cwd() }) {
233
233
  }
234
234
  if (verb === 'day') {
235
235
  const sub = pos[0] || 'status';
236
- const usage = 'usage: pmos proactive day [status] | show TASK | deliver TASK --summary S --body-file F [--link URL] | sent TASK [--link URL] | fail TASK --reason R | approve|revise|retry TASK [--note N] | check';
236
+ const usage = 'usage: pmos proactive day [status] | show TASK | deliver TASK --summary S --body-file F [--link URL] [--step S] | sent TASK [--link URL] | fail TASK --reason R | approve|revise|retry TASK [--note N] | check';
237
237
  const wsOfRepo = async () => {
238
238
  let root; try { root = execFileSync('git', ['rev-parse', '--show-toplevel'], { cwd: flags.repo ? path.resolve(cwd, flags.repo) : cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim(); } catch { return null; }
239
239
  const wl = await call('GET', '/api/workspaces');
@@ -307,6 +307,8 @@ async function run(argv, { call, say, cwd = process.cwd() }) {
307
307
  say(`draft (${t.draft.at}): ${t.draft.summary}${t.draft.link ? ' · ' + t.draft.link : ''}`);
308
308
  // the PM edited the draft (c3-work-agent amendment 13): the body below is the PM's text, to post as it is
309
309
  if (t.draft.edited_at) say(`edited by the PM (${t.draft.edited_at}): the text below is the PM's own. Post it exactly as written.`);
310
+ // the one step in another system that the PM approves (c3-work-agent amendment 17); a send does this step, not a post
311
+ if (t.draft.step) say(`step: ${t.draft.step}${mode === 'send' ? ' — approved by the PM. Do exactly this step with Claude in Chrome, and nothing else.' : ' — runs only after the PM approves it.'}`);
310
312
  say(t.draft.body);
311
313
  }
312
314
  t.revisions.forEach((rv, i) => say(`revision note ${i + 1} (${rv.at}): ${rv.note}`));
@@ -316,9 +318,9 @@ async function run(argv, { call, say, cwd = process.cwd() }) {
316
318
  return 0;
317
319
  }
318
320
  if (sub === 'deliver') {
319
- if (!flags.summary || !flags['body-file']) return fail('usage: pmos proactive day deliver TASK --summary S --body-file F [--link URL]');
321
+ if (!flags.summary || !flags['body-file']) return fail('usage: pmos proactive day deliver TASK --summary S --body-file F [--link URL] [--step S]');
320
322
  let body; try { body = fs.readFileSync(path.resolve(cwd, flags['body-file']), 'utf8'); } catch (e) { return fail(`could not read ${flags['body-file']} — ${e.message}`); }
321
- return post('deliver', { task: t.id, summary: flags.summary, body, ...(flags.link ? { link: flags.link } : {}) }, (r) => `delivered — ${t.id} is now ${r.task.status}`);
323
+ return post('deliver', { task: t.id, summary: flags.summary, body, ...(flags.link ? { link: flags.link } : {}), ...(flags.step ? { step: flags.step } : {}) }, (r) => `delivered — ${t.id} is now ${r.task.status}`);
322
324
  }
323
325
  if (sub === 'sent') return post('sent', { task: t.id, ...(flags.link ? { link: flags.link } : {}) }, () => `sent — ${t.id}`);
324
326
  if (sub === 'fail') {
@@ -272,7 +272,8 @@ function agentItemPress(ctx, res, verb, body, itemId, now) {
272
272
  const t = run ? dayRun.findTask(run, taskId) : null;
273
273
  if (!t) { ctx.json(res, { ok: false, error: 'no agent works on this item — let the agent draft it first' }, 409); return; }
274
274
  let next = null;
275
- if (verb === 'decide' && body.action === 'approve' && !g.postable) { ctx.json(res, { ok: false, error: 'an item you added has no place to post to — keep the draft or send it back' }, 400); return; }
275
+ // an item the PM added has no place to post to; a draft that names a step in another system (amendment 17) can still be approved
276
+ if (verb === 'decide' && body.action === 'approve' && !g.postable && !dayRun.stepOf(t)) { ctx.json(res, { ok: false, error: 'an item you added has no place to post to — keep the draft or send it back' }, 400); return; }
276
277
  if (verb === 'decide' && run.date !== localDate(now) && (body.action === 'revise' || body.action === 'retry')) {
277
278
  // a draft from an earlier day: the job CLI reads today's run only, so the new round is a task of today's run
278
279
  const note = typeof body.note === 'string' ? body.note.trim().slice(0, 500) : '';
@@ -31,7 +31,7 @@ function agentOf(x) {
31
31
  if (!state) return null;
32
32
  return {
33
33
  state, suggest: x.suggest || null, instruction: x.instruction || null,
34
- draft: t && (t.draft || t.body) ? { summary: t.draft || null, body: t.body || null, edited_at: t.edited_at || null } : null,
34
+ draft: t && (t.draft || t.body) ? { summary: t.draft || null, body: t.body || null, edited_at: t.edited_at || null, step: t.step || null } : null,
35
35
  sent: t && t.sent ? { link: t.sent.link || null, at: t.sent.at || null } : null,
36
36
  error: t ? t.error || null : null, session: t && t.session ? String(t.session) : null, asks: t ? t.asks || null : null, revisions: t ? t.revisions || 0 : 0, started_at: t ? t.started_at || null : null, decided_at: x.decided_at || null, log: t ? t.log || null : null,
37
37
  };
@@ -77,7 +77,7 @@ function taskAgent(taskId, suggest, run) {
77
77
  const base = { suggest, instruction: null, draft: null, sent: null, error: null, session: null, asks: null, revisions: 0, started_at: null, decided_at: null, log: null };
78
78
  if (!t) return { state: 'suggest', ...base };
79
79
  return { ...base, state: TASK_STATE[t.status] || 'queued', instruction: t.instruction || null,
80
- draft: t.draft ? { summary: t.draft.summary || null, body: t.draft.body || null, edited_at: t.draft.edited_at || null } : null,
80
+ draft: t.draft ? { summary: t.draft.summary || null, body: t.draft.body || null, edited_at: t.draft.edited_at || null, step: t.draft.step || null } : null,
81
81
  sent: t.sent ? { link: t.sent.link || null, at: t.sent.at || null } : null, error: t.error || null,
82
82
  session: t.stalled_session ? String(t.stalled_session) : null, asks: t.asks || null, revisions: Array.isArray(t.revisions) ? t.revisions.length : 0,
83
83
  started_at: t.started_at || null, decided_at: (t.decided && t.decided.at) || null, log: Array.isArray(t.log) ? t.log : null };
@@ -94,7 +94,7 @@ function localSuggest(item) { return `Draft a first version: ${String(item.title
94
94
  function localDraftSpec(item, instruction) {
95
95
  const ask = String(instruction || '').trim() || `Draft a first version of the work this item of the PM's asks for: ${String(item.title).trim()}`;
96
96
  return { id: localTaskId(item), item_id: null, title: String(item.title).slice(0, 250), source: 'work', link: null,
97
- instruction: `${ask}${/[.!?]$/.test(ask) ? '' : '.'} Use what this repository and your connected tools hold. Deliver ONE draft (a document, a plan or a recommendation) for the PM to read. Draft only. Send nothing to anyone.`,
97
+ instruction: `${ask}${/[.!?]$/.test(ask) ? '' : '.'} Use what this repository and your connected tools hold. Deliver ONE draft (a document, a plan or a recommendation) for the PM to read. Draft only. Send nothing to anyone. If the ask needs one action in another system, such as creating a report in GA4, name that action as the draft's step: it runs only after the PM approves it.`,
98
98
  context: { about: item.title, due: item.due || null, from: item.person || null, ask: item.waiting_on ? 'waiting on ' + item.waiting_on : null } };
99
99
  }
100
100
  const localAgent = (item, run) => taskAgent(localTaskId(item), localSuggest(item), run);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.226.0",
3
+ "version": "0.227.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.226.0",
9
+ "version": "0.227.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.226.0",
3
+ "version": "0.227.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",
@@ -736,6 +736,11 @@ const AG = {
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.',
738
738
  needNote: 'Say what to change first: type a note, then send it back.', link: 'Open what was posted',
739
+ // a draft that names one step in another system (amendment 17): approving it starts a session with Claude in Chrome
740
+ doIt: (s) => 'Do it: ' + s, stepLine: 'Step when you approve: ', stepFx1: 'Outward: this one step, as you', stepFx2: 'A new session with Chrome does only this',
741
+ doing: 'Approved. A session with Chrome does exactly this step.', sitStepReady: (at) => `Draft ready${at ? ' at ' + at : ''}. The step has not run.`,
742
+ sitStepping: 'Doing the step you approved. A session with Chrome does exactly that step and nothing else.',
743
+ stepping: 'Agent: doing the step', stepDone: 'Agent: step done', sitStepDone: (at) => `The agent did the step you approved${at ? ' at ' + at : ''}.`, stepLink: 'Open what it made',
739
744
  };
740
745
 
741
746
  function agentChip(item) {
@@ -745,8 +750,9 @@ function agentChip(item) {
745
750
  if (a.state === 'queued') return st('st', 'clock', AG.queued);
746
751
  if (a.state === 'working') return st('st c3strip', 'spark', AG.working(minsAgo(a.started_at)), true);
747
752
  if (a.state === 'ready') return st('st ok', 'check', AG.ready);
748
- if (a.state === 'sending') return st('st c3strip', 'out', AG.sending, true);
749
- if (a.state === 'sent') return st('st ok', 'out', AG.sent(whereOf(item), a.sent && a.sent.at ? hhmm(a.sent.at) : ''));
753
+ const step = a.draft && a.draft.step;
754
+ if (a.state === 'sending') return st('st c3strip', 'out', step ? AG.stepping : AG.sending, true);
755
+ if (a.state === 'sent') return st('st ok', 'out', step ? AG.stepDone : AG.sent(whereOf(item), a.sent && a.sent.at ? hhmm(a.sent.at) : ''));
750
756
  if (a.state === 'failed') return st('tag warn', 'alert', AG.failed(a.asks));
751
757
  return null;
752
758
  }
@@ -793,26 +799,29 @@ function agentCase(inst, item, L) {
793
799
  extras.push(a.state === 'queued' ? strip(false, 'clock', 'Queued' + (a.decided_at ? ' at ' + hhmm(a.decided_at) : ''))
794
800
  : strip(true, 'spark', (redraft ? 'Redrafting with your note' : 'Working') + (a.started_at ? ' · started ' + minsAgo(a.started_at) : '')));
795
801
  } else if (a.state === 'ready') {
796
- sit = AG.sitReady('', w);
797
- outbox = true; cta = 'Hold to post';
802
+ const step = a.draft && a.draft.step;
803
+ sit = step ? AG.sitStepReady('') : AG.sitReady('', w);
804
+ outbox = true; cta = step ? 'Hold to do it' : 'Hold to post';
798
805
  const mine = isMine(item);
799
806
  opts = [
800
- mine ? null : o('post', AG.post(k), [fx('lock', 'Outward: exactly this text', 'warn'), fx('out', 'Goes to ' + w + ' as you', 'warn')], () => agentPress(inst, '/api/my-work/decide', { item: item.id, action: 'approve' }, AG.posting), { outward: true }),
807
+ // a step in another system replaces the post: the approved send does the step (amendment 17)
808
+ step ? o('step', AG.doIt(step), [fx('lock', AG.stepFx1, 'warn'), fx('out', AG.stepFx2, 'warn')], () => agentPress(inst, '/api/my-work/decide', { item: item.id, action: 'approve' }, AG.doing), { outward: true }) : null,
809
+ mine || step ? null : o('post', AG.post(k), [fx('lock', 'Outward: exactly this text', 'warn'), fx('out', 'Goes to ' + w + ' as you', 'warn')], () => agentPress(inst, '/api/my-work/decide', { item: item.id, action: 'approve' }, AG.posting), { outward: true }),
801
810
  mine ? o('done', AG.doneIt, [fx('check', AG.doneFx, 'ok')], (r) => doneAct(inst, item, r, AG.doneToast)) : null,
802
- o('edit', AG.editDraft, [fx('edit', mine ? 'Your version replaces the draft here' : 'Post then sends your version', 'ok')], () => openDraft(item, inst, true)),
811
+ o('edit', AG.editDraft, [fx('edit', step ? 'The step then uses your version' : mine ? 'Your version replaces the draft here' : 'Post then sends your version', 'ok')], () => openDraft(item, inst, true)),
803
812
  o('keep', AG.keepDraft, [fx('check', 'Nothing leaves PMOS', 'ok')], () => say({ text: mine ? AG.keptMine : AG.kept, timeout: 3000 })),
804
813
  mine ? null : o('done', AG.doneIt, [fx('check', AG.doneFx, 'ok')], (r) => doneAct(inst, item, r, AG.doneToast)),
805
814
  ].filter(Boolean);
806
815
  extras.push(draftCard(inst, item, k, false), conversation(inst, item, false, w));
807
816
  before = el('div', { class: 'wk-conv-h', 'data-pm-agent-decide': item.id }, el('span', { class: 'dim mono' }, c3ic('check', 'sm'), AG.orDecide));
808
817
  } else if (a.state === 'sending') {
809
- sit = AG.sitSending(k);
818
+ sit = a.draft && a.draft.step ? AG.sitStepping : AG.sitSending(k);
810
819
  if (a.draft && a.draft.body) extras.push(draftCard(inst, item, k, false), conversation(inst, item, false));
811
820
  } else if (a.state === 'sent') {
812
- sit = AG.sitSent(a.sent && a.sent.at ? hhmm(a.sent.at) : '', w);
821
+ sit = a.draft && a.draft.step ? AG.sitStepDone(a.sent && a.sent.at ? hhmm(a.sent.at) : '') : AG.sitSent(a.sent && a.sent.at ? hhmm(a.sent.at) : '', w);
813
822
  opts = L.opts.map((x) => ({ ...x }));
814
823
  if (a.draft && a.draft.body) extras.push(draftCard(inst, item, k, false), conversation(inst, item, false));
815
- if (a.sent && a.sent.link) extras.push(el('div', { class: 'chips2' }, el('a', { class: 'chip2 lk', href: a.sent.link, target: '_blank', rel: 'noopener noreferrer' }, c3ic('out', 'sm'), AG.link)));
824
+ if (a.sent && a.sent.link) extras.push(el('div', { class: 'chips2' }, el('a', { class: 'chip2 lk', href: a.sent.link, target: '_blank', rel: 'noopener noreferrer' }, c3ic('out', 'sm'), a.draft && a.draft.step ? AG.stepLink : AG.link)));
816
825
  }
817
826
  if (opts.length) opts[0].rec = 1;
818
827
  const model = { id: 'task:' + item.id, sit, quote, chg, conf: 0, cta, opts: opts.map((x) => ({ id: x.id, l: x.l, rec: x.rec, fx: x.fx, outward: !!x.outward })), why: '', outbox,
@@ -854,6 +863,7 @@ function draftCard(inst, item, k, busy) {
854
863
  el('span', { class: 'chip2', 'data-pm-agent-version': item.id }, c3ic(cur.kind === 'edit' ? 'edit' : 'spark', 'sm'), cur.label + (cur.at ? ' · ' + hhmm(cur.at) : ''))),
855
864
  el('div', { class: 'chips2' }, c3btn('chip2 lk', { 'data-pm-agent-draft': item.id }, [c3ic('eye', 'sm'), AG.open], () => openDraft(item, inst)),
856
865
  c3btn('chip2 lk', { 'data-pm-agent-copy': item.id }, [c3ic('copy', 'sm'), AG.copy], () => copyText(d.body)))),
866
+ d.step ? el('div', { class: 'dim mono wk-agent-will', 'data-pm-agent-step': item.id }, c3ic('out', 'sm'), ' ' + AG.stepLine + d.step) : null,
857
867
  body,
858
868
  long ? c3btn('chip2 lk', { 'data-pm-agent-all': item.id, 'aria-expanded': String(all) }, [c3ic(all ? 'zout' : 'zin', 'sm'), all ? AG.showLess : AG.showAll],
859
869
  () => { if (all) inst.editing.delete('all:' + item.id); else inst.editing.add('all:' + item.id); draw(inst); }) : null);
@@ -4342,6 +4342,9 @@ function taskWsId(run, t) {
4342
4342
  } catch { return null; }
4343
4343
  }
4344
4344
  /** Start one task session; a start that fails marks the task failed with the reason. Returns the new run. */
4345
+ /** The launch flags of a session that does an approved step (amendment 17): Claude in Chrome, and an allow rule for its
4346
+ * browser tools only. Nothing else is allowed beyond what the PM's own settings allow. */
4347
+ const STEP_FLAGS = [{ flag: '--chrome' }, { flag: '--allowedTools', value: 'mcp__claude-in-chrome' }];
4345
4348
  function startDayTask(run, t, now) {
4346
4349
  // a job runs in its own repository's folder, so it reads that repository's files and records (one day, many repositories)
4347
4350
  const sk = daySkillPrompt(run, 'day-task', taskWsId(run, t));
@@ -4349,14 +4352,18 @@ function startDayTask(run, t, now) {
4349
4352
  const line = `This run: /day-task ${t.id} ${t.mode} — read the task first with: pmos proactive day show ${t.id}`;
4350
4353
  // The session that did the task's last step is used again while it is open: it has the skill and the task in its
4351
4354
  // context already, so only this run's line is typed into it, and the PM keeps one session per job.
4352
- const prev = t.prev_session && sessions.get(t.prev_session);
4355
+ // An approved step in another system (c3-work-agent amendment 17) runs in a NEW session that starts with Claude in Chrome
4356
+ // and an allow rule for the browser tools only (STEP_FLAGS), because the PM approved that step in Work. The earlier
4357
+ // session is not used again: it started without that rule, and no line typed into it can stand in for the PM's consent.
4358
+ const step = t.mode === 'send' && dayRunLib.stepOf(t);
4359
+ const prev = !step && t.prev_session && sessions.get(t.prev_session);
4353
4360
  if (!sk.error && prev && prev.state !== 'exited' && prev.pty) {
4354
4361
  try { firstInput.deliver(prev, line + '\r'); s = prev; } catch { s = null; }
4355
4362
  }
4356
4363
  if (!sk.error && !s) {
4357
4364
  const prompt = `${sk.prompt}\n\n${line}`;
4358
4365
  s = spawnSession({ name: jobSessionName(t.title), agent: sk.task.agent || 'claude', ws: sk.w ? sk.w.id : run.ws, origin: 'day-task', initialInput: prompt + '\r',
4359
- modelAsk: { scheduleTier: null, skillTier: sk.task.tier || null, skill: sk.task.skill } });
4366
+ modelAsk: { scheduleTier: null, skillTier: sk.task.tier || null, skill: sk.task.skill }, ...(step ? { launchFlags: STEP_FLAGS } : {}) });
4360
4367
  }
4361
4368
  const live = s && s.state !== 'exited';
4362
4369
  const st = dayRunLib.started(run, t.id, live ? s.id : null, now);
@@ -8626,7 +8633,7 @@ async function handleRequest(req, res) {
8626
8633
  }
8627
8634
  const id = String(body.task || '');
8628
8635
  if (!dayRunLib.findTask(run, id)) return json(res, { ok: false, error: `no task ${id || '(none given)'} in today's run` }, 404);
8629
- const r = verb === 'deliver' ? dayRunLib.deliver(run, id, { summary: body.summary, body: body.body, link: body.link }, now)
8636
+ const r = verb === 'deliver' ? dayRunLib.deliver(run, id, { summary: body.summary, body: body.body, link: body.link, step: body.step }, now)
8630
8637
  : verb === 'sent' ? dayRunLib.sent(run, id, { link: body.link }, now)
8631
8638
  : verb === 'fail' ? dayRunLib.fail(run, id, body.reason, now)
8632
8639
  : dayRunLib.decide(run, id, { action: body.action, note: body.note }, now);
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: day-task
3
3
  label: "Day task"
4
- description: "Do one task of the PM's day for one task id and one mode (do, revise or send). In do and revise it prepares ONE draft and sends nothing. In send it carries out the one outward step of the draft the PM approved."
4
+ description: "Do one task of the PM's day for one task id and one mode (do, revise or send). In do and revise it prepares ONE draft and sends nothing. In send it carries out the one outward step of the draft the PM approved, or the one step in another system that the draft names."
5
5
  run: agent
6
6
  agent: claude
7
7
  output:
@@ -20,14 +20,15 @@ Rules that always hold:
20
20
  5. NOTHING INVENTED. If a fact is not in a source you read, leave it out or write "unverified".
21
21
  6. NEVER APPROVE FOR THE PM. Do not run `pmos proactive day approve`, `revise` or `retry`. Those commands are the PM's.
22
22
  7. YOU CANNOT FINISH WITHOUT A REPORT. End every run with exactly one of `day deliver` (do, revise), `day sent` (send) or `day fail`.
23
+ 8. A STEP IN ANOTHER SYSTEM WAITS FOR APPROVAL. If the ask needs an action in another system (for example "create the cohort exploration in GA4"), do not try it in do or revise mode. Name it as the draft's step with `--step`. The PM approves the step in Work, and then a new session with Claude in Chrome does that one step.
23
24
 
24
25
  Procedure:
25
- 1. Run `pmos proactive day show TASK`. It prints the task: the title, the kind (approve or auto), the source, the link, the goal, the item context (where, who, about, ask, how), the draft so far and the notes of the PM for each revision. Read all of it. A task from My work, from Work (an item the PM added, or an item of the brief the PM gave to the agent) or from Your day also prints "instruction from the PM (this is the ask)". The PM read the ticket and wrote that instruction. Do what it says and nothing more, and read the item context as background. The rules above still hold: one draft, nothing sent, no pull request, no commit. If the instruction asks for one of those, draft it and say in the summary which step approval would trigger. If the command says the task does not exist, or its status does not fit the mode, stop and run `pmos proactive day fail TASK --reason "<one sentence>"`.
26
+ 1. Run `pmos proactive day show TASK`. It prints the task: the title, the kind (approve or auto), the source, the link, the goal, the item context (where, who, about, ask, how), the draft so far and the notes of the PM for each revision. Read all of it. A task from My work, from Work (an item the PM added, or an item of the brief the PM gave to the agent) or from Your day also prints "instruction from the PM (this is the ask)". The PM read the ticket and wrote that instruction. Do what it says and nothing more, and read the item context as background. The rules above still hold: one draft, nothing sent, no pull request, no commit. If the instruction asks for one of those, draft it and say in the summary which step approval would trigger. If it asks for an action in another system, such as creating a report, a dashboard or an exploration, put that action in `--step` (rule 8). If the command says the task does not exist, or its status does not fit the mode, stop and run `pmos proactive day fail TASK --reason "<one sentence>"`.
26
27
  2. If MODE is do:
27
28
  a. Read the sources the task names, read-only. Start from the link of the task. A task with no link (an item the PM added in Work) starts from the instruction, this repository and the sources it names. Read the conversation, the issue, the page or the file itself, and what it points to. Use `pmos proactive settings` to see which sources are on. If a source is off or you cannot reach it, say so in the summary.
28
29
  b. Do the work locally. A reply, a comment, a summary, a description or a plan is a draft in a file in the scratch folder of this session. Write the text exactly as it would be sent.
29
30
  c. Check the draft against the item: does it answer the ask, is it for the right person and place, is it in the voice and the length the place expects. Fix it.
30
- d. Submit it. Run `pmos proactive day deliver TASK --summary "<at most 240 characters: what you did and where the draft would go>" --body-file <the draft file> [--link <URL of the thread, issue, page or file the draft is for>]`. The body is at most 8000 characters. The summary names the one outward step that approval will trigger, for example "Post this reply in the thread". For a task of kind auto, the summary says what you made and where it is.
31
+ d. Submit it. Run `pmos proactive day deliver TASK --summary "<at most 240 characters: what you did and where the draft would go>" --body-file <the draft file> [--link <URL of the thread, issue, page or file the draft is for>] [--step "<at most 240 characters: the one action in another system that approval triggers>"]`. Use `--step` only for an action in another system that is not a post of the draft text. Start it with a verb and name the system, for example "Create the 'New users by week' cohort exploration in GA4 property Acme Web". The body then gives the exact settings the step uses, so the PM can check them before approving. The body is at most 8000 characters. The summary names the one outward step that approval will trigger, for example "Post this reply in the thread". For a task of kind auto, the summary says what you made and where it is.
31
32
  e. If the workspace refuses the draft, read the reason, fix the draft and submit once more. Do not work around a refusal.
32
33
  3. If MODE is revise:
33
34
  a. Read the draft so far and the LAST note of the PM in the output of `day show`. The note says what to change. Earlier notes still hold unless the last one cancels them.
@@ -40,6 +41,7 @@ Procedure:
40
41
  b. Do the one outward step the summary names, with the connector that fits, to the place the link names. Examples: post the approved reply in the linked Slack thread, add the approved comment to the linked Jira issue, reply to the linked mail thread. If the summary names more than one step, do only the first and report the rest with `day fail`.
41
42
  c. If the place does not match the link, the thread is closed, or the connector is not available, do not try another route. Run `pmos proactive day fail TASK --reason "<what stopped it>"`.
42
43
  d. When it is done, run `pmos proactive day sent TASK --link <URL of what you posted or sent>`. Leave out `--link` only if the connector returns none.
44
+ e. If `day show` prints a `step:` line, the PM approved that step, not a post. Do that one step in the system it names, with the Claude in Chrome tools (`mcp__claude-in-chrome__*`), and use the settings in the approved body. Do not post the body anywhere. Do not do a second action, and do not change or delete anything that already exists. If Chrome is not connected, the PM is not signed in to the system, or the page does not match the step, run `day fail` with what stopped it. When the step is done, run `day sent` with the URL of what you made.
43
45
  5. If you cannot produce a draft or do the step for any reason, run `pmos proactive day fail TASK --reason "<one sentence, at most 240 characters>"`. A true failure helps the PM more than a weak draft.
44
46
  6. Finish with the output contract.
45
47