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 +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/guide/my-work.md +3 -1
- package/workspace/lib/day-run.js +10 -3
- package/workspace/lib/my-work.js +1 -1
- package/workspace/lib/proactive-cli.js +6 -4
- package/workspace/lib/routes-my-work.js +2 -1
- package/workspace/lib/work-assigned.js +3 -3
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/pm-work-view.js +19 -9
- package/workspace/server.js +10 -3
- package/workspace/skills/day-task.skill +5 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "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": {
|
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-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-
|
|
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-
|
|
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
|
|
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
|
|
package/workspace/lib/day-run.js
CHANGED
|
@@ -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
|
-
|
|
208
|
-
|
|
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 };
|
package/workspace/lib/my-work.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
9
|
+
"version": "0.227.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -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
|
-
|
|
749
|
-
if (a.state === '
|
|
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
|
-
|
|
797
|
-
|
|
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
|
-
|
|
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);
|
package/workspace/server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|