create-pmos 0.209.0 → 0.210.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 +2 -0
- package/workspace/lib/proactive-cli.js +8 -1
- package/workspace/lib/routes-my-work.js +49 -16
- package/workspace/lib/routes-pm-items.js +3 -0
- package/workspace/lib/work-assigned.js +37 -6
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/pm-work-view.js +12 -8
- package/workspace/skills/day-task.skill +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.210.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-14T01:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-14T01:55:32Z"}
|
|
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:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-14T01:55:32Z"}
|
|
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:
|
|
5
|
+
generated: {by: process:create-pmos, at: "2026-10-14T01:55:32Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -102,6 +102,8 @@ Open an item that shows **Agent can:**. The case file shows **What the agent wil
|
|
|
102
102
|
|
|
103
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.
|
|
104
104
|
|
|
105
|
+
An item you added yourself shows **Agent can draft a first version**. The agent drafts what the item asks for (a document, a plan or a recommendation), and the draft stays on the item. It has no place to post to: read it, keep it, or send it back with a note.
|
|
106
|
+
|
|
105
107
|
1. To change the instruction, select **Change what the agent does** and type a new one.
|
|
106
108
|
2. Select **Let the agent draft this** and hold to confirm. This adds one job to today's day run, with the instruction.
|
|
107
109
|
|
|
@@ -292,7 +292,14 @@ async function run(argv, { call, say, cwd = process.cwd() }) {
|
|
|
292
292
|
for (const o of Array.isArray(c.open) ? c.open : []) say(`open question: ${o}`);
|
|
293
293
|
if (c.initiative) say(`initiative: ${c.initiative}`);
|
|
294
294
|
say(`instruction from the PM (this is the ask): ${t.instruction}`);
|
|
295
|
-
} else say('(the item is no longer in today\'s brief)');
|
|
295
|
+
} else if (!t.instruction) say('(the item is no longer in today\'s brief)');
|
|
296
|
+
else {
|
|
297
|
+
// an item the PM added in Work (initiative c3-work-agent, amendment 11): the PM's item is the ask
|
|
298
|
+
const c = t.context || {};
|
|
299
|
+
say(`from Work: ${t.title}${c.due ? ' · due ' + c.due : ''}${c.from ? ' · with ' + c.from : ''}`);
|
|
300
|
+
}
|
|
301
|
+
// the PM's ask, as Work or Your day gave it, including a changed ask (c3-work-agent amendments 10 and 11)
|
|
302
|
+
if (t.instruction && !t.my_work) say(`instruction from the PM (this is the ask): ${t.instruction}`);
|
|
296
303
|
if (t.kind === 'approve') say('kind approve: deliver ONE draft; nothing goes out until the PM approves it.');
|
|
297
304
|
else say('kind auto: deliver ONE draft; nothing leaves this machine.');
|
|
298
305
|
if (t.draft) { say(`draft (${t.draft.at}): ${t.draft.summary}${t.draft.link ? ' · ' + t.draft.link : ''}`); say(t.draft.body); }
|
|
@@ -122,8 +122,8 @@ async function handle(req, res, url, ctx) {
|
|
|
122
122
|
return true;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
125
|
+
const agentItem = String((body && body.item) || '');
|
|
126
|
+
if ((BRIEF_ITEM.test(agentItem) || LOCAL_ITEM.test(agentItem)) && p !== '/api/my-work/refresh') { agentItemPress(ctx, res, p.slice('/api/my-work/'.length), body, agentItem, now); return true; }
|
|
127
127
|
|
|
128
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; }
|
|
129
129
|
|
|
@@ -200,29 +200,62 @@ function taskPress(ctx, res, verb, body, now) {
|
|
|
200
200
|
ctx.json(res, { ok: true, item: after });
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
/* ── the agent on an item the morning brief suggests for the PM (c3-work-agent amendment 10)
|
|
204
|
-
* Item `brief:<id
|
|
203
|
+
/* ── the agent on an item the morning brief suggests for the PM (c3-work-agent amendment 10) and on an item the PM added
|
|
204
|
+
* in Work (amendment 11). Item `brief:<id>`: its task is `t-<id>` in today's day run, added as Your day's "Have the agent
|
|
205
|
+
* draft it" adds it. Item `item-<hex>`: its task is `t-<hex>`, in the newest day run of the last 7 days that holds it. */
|
|
205
206
|
const BRIEF_ITEM = /^brief:([0-9a-f]{12})$/;
|
|
207
|
+
const LOCAL_ITEM = /^item-[0-9a-f]{12}$/;
|
|
206
208
|
|
|
207
|
-
|
|
209
|
+
/** The item the press names, as the agent sees it: its task id, the offer, the spec a press adds and its agent field. */
|
|
210
|
+
function targetOf(ctx, itemId, now) {
|
|
208
211
|
const today = localDate(now);
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
const bm = BRIEF_ITEM.exec(itemId);
|
|
213
|
+
if (bm) {
|
|
214
|
+
let brief = null; try { brief = JSON.parse(fs.readFileSync(path.join(ctx.runtimeDir, 'briefs', today + '.json'), 'utf8')); } catch { brief = null; }
|
|
215
|
+
const x = brief && brief.date === today ? [...(brief.priorities || []), ...(brief.items || [])].find((y) => y && y.id === bm[1]) : null;
|
|
216
|
+
if (!x || x.owner !== 'pm') return { error: 'no item of yours with that id in today\'s brief' };
|
|
217
|
+
const run = ctx.readDayRun ? ctx.readDayRun(today) : null;
|
|
218
|
+
return { postable: true, taskId: 't-' + x.id, suggest: assigned.briefSuggest(x), spec: (own) => assigned.briefDraftSpec(x, own), run, agent: (r) => assigned.briefAgent(x, r) };
|
|
219
|
+
}
|
|
220
|
+
// an item the PM added in Work (amendment 11): its draft is kept across days, so the newest run that holds its task decides
|
|
221
|
+
const pmItems = require('./pm-items.js');
|
|
222
|
+
const item = pmItems.fold(pmItems.readLines(path.join(ctx.runtimeDir, 'pm-items', 'items.jsonl'))).find((i) => i.id === itemId);
|
|
223
|
+
if (!item || !assigned.localOffer(item)) return { error: 'no open item of yours with that id' };
|
|
224
|
+
const taskId = assigned.localTaskId(item);
|
|
225
|
+
const run = assigned.runWith(assigned.recentRuns(ctx.readDayRun, now), taskId);
|
|
226
|
+
return { postable: false, taskId, suggest: assigned.localSuggest(item), spec: (own) => assigned.localDraftSpec(item, own), run, agent: (r) => assigned.localAgent(item, r) };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function agentItemPress(ctx, res, verb, body, itemId, now) {
|
|
230
|
+
const g = targetOf(ctx, itemId, now);
|
|
231
|
+
if (g.error) { ctx.json(res, { ok: false, error: g.error }, 404); return; }
|
|
232
|
+
const { taskId } = g;
|
|
233
|
+
const after = (run, task) => ctx.json(res, { ok: true, item: { id: itemId, agent: g.agent(run) }, task: task || null });
|
|
214
234
|
if (verb === 'act') {
|
|
215
|
-
if (body.action !== 'brief') { ctx.json(res, { ok: false, error: '
|
|
235
|
+
if (body.action !== 'brief') { ctx.json(res, { ok: false, error: 'this item takes only action brief here — mark it done or set it aside from Work' }, 400); return; }
|
|
236
|
+
const live = g.run && dayRun.findTask(g.run, taskId);
|
|
237
|
+
if (live && live.status !== 'failed' && g.run.date !== localDate(now)) { ctx.json(res, { ok: false, error: `the agent's task is ${live.status} — open the draft or stop it first` }, 409); return; }
|
|
216
238
|
// 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() !==
|
|
218
|
-
const b = ctx.myWorkBrief(
|
|
239
|
+
const own = typeof body.instruction === 'string' && body.instruction.trim() !== g.suggest ? body.instruction.trim().slice(0, 500) : '';
|
|
240
|
+
const b = ctx.myWorkBrief(g.spec(own), String(body.ws || ''));
|
|
219
241
|
if (!b.ok) { ctx.json(res, { ok: false, error: b.error }, b.status || 500); return; }
|
|
220
|
-
after(b.task); return;
|
|
242
|
+
after(b.run, b.task); return;
|
|
221
243
|
}
|
|
222
|
-
const run =
|
|
244
|
+
const run = g.run;
|
|
223
245
|
const t = run ? dayRun.findTask(run, taskId) : null;
|
|
224
246
|
if (!t) { ctx.json(res, { ok: false, error: 'no agent works on this item — let the agent draft it first' }, 409); return; }
|
|
225
247
|
let next = null;
|
|
248
|
+
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; }
|
|
249
|
+
if (verb === 'decide' && run.date !== localDate(now) && (body.action === 'revise' || body.action === 'retry')) {
|
|
250
|
+
// 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
|
|
251
|
+
const note = typeof body.note === 'string' ? body.note.trim().slice(0, 500) : '';
|
|
252
|
+
if (body.action === 'revise' && !note) { ctx.json(res, { ok: false, error: 'say what to change: a revise needs a note' }, 400); return; }
|
|
253
|
+
const prev = t.draft && t.draft.body ? String(t.draft.body).slice(0, 4000) : '';
|
|
254
|
+
const own = body.action === 'revise' ? `Draft this item again. The PM's note on your earlier draft: ${note}${prev ? `\n\nYour earlier draft, to change:\n${prev}\n` : ''}` : '';
|
|
255
|
+
const b = ctx.myWorkBrief(g.spec(own), String(body.ws || run.ws || ''));
|
|
256
|
+
if (!b.ok) { ctx.json(res, { ok: false, error: b.error }, b.status || 500); return; }
|
|
257
|
+
after(b.run, b.task); return;
|
|
258
|
+
}
|
|
226
259
|
if (verb === 'decide') {
|
|
227
260
|
const r = dayRun.decide(run, taskId, { action: body.action, note: body.note }, now);
|
|
228
261
|
if (!r.ok) { ctx.json(res, { ok: false, error: r.error }, 400); return; }
|
|
@@ -237,7 +270,7 @@ function briefPress(ctx, res, verb, body, id, now) {
|
|
|
237
270
|
next = w.run;
|
|
238
271
|
}
|
|
239
272
|
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));
|
|
273
|
+
after(next, dayRun.findTask(next, taskId));
|
|
241
274
|
}
|
|
242
275
|
|
|
243
276
|
module.exports = { prefixes: ['/api/my-work'], handle, listFor, listForAsync };
|
|
@@ -344,6 +344,9 @@ module.exports = {
|
|
|
344
344
|
const c = prepare(ctx);
|
|
345
345
|
const v = view(c.itemLines, c.jobLines, c.now, c.stepLines, c.focusLines, proofSources(ctx, c.itemLines), await assignedAsync(ctx, c.now));
|
|
346
346
|
v.resolves_titles = resolvesTitles(v.items, ctx, url);
|
|
347
|
+
// an item the PM added: the agent can draft a first version of it (c3-work-agent amendment 11)
|
|
348
|
+
const runs = assigned.recentRuns(ctx.readDayRun, c.now.getTime());
|
|
349
|
+
for (const it of v.items) if (!it.agent && assigned.localOffer(it)) it.agent = assigned.localAgent(it, assigned.runWith(runs, assigned.localTaskId(it)));
|
|
347
350
|
ctx.json(res, v);
|
|
348
351
|
return true;
|
|
349
352
|
}
|
|
@@ -70,17 +70,48 @@ function briefDraftSpec(x, instruction) {
|
|
|
70
70
|
instruction: `${ask}${/[.!?]$/.test(ask) ? '' : '.'} Draft only. Send nothing: the PM approves it first.`,
|
|
71
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
72
|
}
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
73
|
+
const TASK_STATE = { waiting: 'queued', working: 'working', sending: 'sending', failed: 'failed', review: 'ready', done: 'ready', sent: 'sent' };
|
|
74
|
+
/** The agent field of an item whose day task is `taskId` in `run` (or none yet: the offer `suggest`). */
|
|
75
|
+
function taskAgent(taskId, suggest, run) {
|
|
76
|
+
const t = run && Array.isArray(run.tasks) ? run.tasks.find((y) => y && y.id === taskId) || null : null;
|
|
77
|
+
const base = { suggest, instruction: null, draft: null, sent: null, error: null, session: null, asks: null, revisions: 0, started_at: null, decided_at: null };
|
|
77
78
|
if (!t) return { state: 'suggest', ...base };
|
|
78
|
-
return { ...base, state:
|
|
79
|
+
return { ...base, state: TASK_STATE[t.status] || 'queued', instruction: t.instruction || null,
|
|
79
80
|
draft: t.draft ? { summary: t.draft.summary || null, body: t.draft.body || null } : null,
|
|
80
81
|
sent: t.sent ? { link: t.sent.link || null, at: t.sent.at || null } : null, error: t.error || null,
|
|
81
82
|
session: t.stalled_session ? String(t.stalled_session) : null, asks: t.asks || null, revisions: Array.isArray(t.revisions) ? t.revisions.length : 0,
|
|
82
83
|
started_at: t.started_at || null, decided_at: (t.decided && t.decided.at) || null };
|
|
83
84
|
}
|
|
85
|
+
const briefAgent = (x, run) => taskAgent('t-' + x.id, briefSuggest(x), run);
|
|
86
|
+
|
|
87
|
+
/* An item the PM added in Work (amendment 11): the agent drafts a first version of what the item asks for, on the PM's
|
|
88
|
+
* press. Its task is `t-<hex>` for item `item-<hex>`; nothing goes anywhere, the draft stays in PMOS. */
|
|
89
|
+
const LOCAL_ID = /^item-([0-9a-f]{12})$/;
|
|
90
|
+
const localTaskId = (item) => { const m = LOCAL_ID.exec(String((item && item.id) || '')); return m ? 't-' + m[1] : null; };
|
|
91
|
+
/** Can the agent take this item? An open item the PM added, not offline work. */
|
|
92
|
+
const localOffer = (item) => !!(item && localTaskId(item) && (!item.source || item.source === 'local') && !item.done && item.kind !== 'offline' && String(item.title || '').trim());
|
|
93
|
+
function localSuggest(item) { return `Draft a first version: ${String(item.title).trim()}`.slice(0, 300); }
|
|
94
|
+
function localDraftSpec(item, instruction) {
|
|
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
|
+
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.`,
|
|
98
|
+
context: { about: item.title, due: item.due || null, from: item.person || null, ask: item.waiting_on ? 'waiting on ' + item.waiting_on : null } };
|
|
99
|
+
}
|
|
100
|
+
const localAgent = (item, run) => taskAgent(localTaskId(item), localSuggest(item), run);
|
|
101
|
+
/** The newest of `runs` (newest first) that holds task `taskId`, or null. An item the PM added keeps its draft across days. */
|
|
102
|
+
const runWith = (runs, taskId) => (runs || []).find((r) => r && Array.isArray(r.tasks) && r.tasks.some((t) => t && t.id === taskId)) || null;
|
|
103
|
+
/** The day runs of the last `days` days, newest first, through `readDayRun(date)`. */
|
|
104
|
+
function recentRuns(readDayRun, nowMs, days = 7) {
|
|
105
|
+
const out = [];
|
|
106
|
+
if (typeof readDayRun !== 'function') return out;
|
|
107
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
108
|
+
for (let i = 0; i < days; i++) {
|
|
109
|
+
const d = new Date(nowMs); d.setDate(d.getDate() - i);
|
|
110
|
+
let r = null; try { r = readDayRun(`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`); } catch { r = null; }
|
|
111
|
+
if (r) out.push(r);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
84
115
|
|
|
85
116
|
function fromBrief(brief, todayIso, run = null) {
|
|
86
117
|
if (!brief || brief.date !== todayIso) return [];
|
|
@@ -117,4 +148,4 @@ function overlay(item, byId) {
|
|
|
117
148
|
return out;
|
|
118
149
|
}
|
|
119
150
|
|
|
120
|
-
module.exports = { fromMyWork, fromBrief, addable, createdEvent, overlay, agentOf, briefAgent, briefSuggest, briefDraftSpec, AGENT };
|
|
151
|
+
module.exports = { fromMyWork, fromBrief, addable, createdEvent, overlay, agentOf, briefAgent, briefSuggest, briefDraftSpec, taskAgent, localTaskId, localOffer, localSuggest, localDraftSpec, localAgent, runWith, recentRuns, AGENT };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.210.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.210.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -706,11 +706,15 @@ const hhmm = (iso) => { const d = new Date(iso); return Number.isNaN(d.getTime()
|
|
|
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
707
|
// where a post goes: Jira for a ticket; for an item the morning brief suggests, the place it came from (amendment 10)
|
|
708
708
|
const PLACE = { mail: 'email', slack: 'Slack', jira: 'Jira', confluence: 'Confluence', github: 'GitHub', drive: 'Drive', figma: 'Figma', calendar: 'the calendar', wispr: 'Wispr' };
|
|
709
|
-
const
|
|
709
|
+
const isMine = (item) => item.source === 'local' && !item.origin; // an item the PM added in Work (amendment 11): no place to post to
|
|
710
|
+
const whereOf = (item) => (item.origin === 'jira' ? 'Jira' : isMine(item) ? 'anyone' : PLACE[item.place] || 'its source');
|
|
710
711
|
const AG = {
|
|
711
712
|
can: (t) => 'Agent can: ' + t, queued: 'Agent: queued', working: (ago) => 'Agent: working' + (ago ? ' · started ' + ago : ''), ready: 'Agent: draft ready',
|
|
712
713
|
sending: 'Agent: posting', sent: (w, at) => 'Posted to ' + w + (at ? ' ' + at : ''), failed: (asks) => (asks ? 'Agent: needs you' : 'Agent stopped'),
|
|
713
714
|
sitSuggest: (w) => `The agent can draft this for you. You read it here first. Nothing goes to ${w} until you post it.`,
|
|
715
|
+
canMine: 'Agent can draft a first version',
|
|
716
|
+
sitMine: 'The agent can draft a first version of this for you. You read it here. Nothing leaves PMOS.',
|
|
717
|
+
handedMine: 'Handed to the agent. Its draft stays here in PMOS.', keptMine: 'Kept. The draft stays on this item.',
|
|
714
718
|
sitQueued: 'Queued. The agent starts as soon as a session is free. You can close this page.',
|
|
715
719
|
sitWorking: 'The agent is working on it. You can close this page: the item keeps its state.',
|
|
716
720
|
sitReady: (at, w) => `Draft ready${at ? ' at ' + at : ''}. Nothing went to ${w}.`,
|
|
@@ -729,7 +733,7 @@ const AG = {
|
|
|
729
733
|
function agentChip(item) {
|
|
730
734
|
const a = item.agent; if (!a || item.mock_row) return null;
|
|
731
735
|
const st = (cls, ic, text, spin) => el('span', { class: cls + ' wk-agent-st', 'data-pm-agent-state': a.state, 'data-pm-suggest': item.id, title: text }, spin ? el('span', { class: 'c3spin' }, c3ic(ic, 'sm')) : c3ic(ic, 'sm'), el('span', { class: 'wk-agent-tx' }, text));
|
|
732
|
-
if (a.state === 'suggest') return st('chip2', 'spark', AG.can(a.instruction || a.suggest || ''));
|
|
736
|
+
if (a.state === 'suggest') return st('chip2', 'spark', isMine(item) ? AG.canMine : AG.can(a.instruction || a.suggest || ''));
|
|
733
737
|
if (a.state === 'queued') return st('st', 'clock', AG.queued);
|
|
734
738
|
if (a.state === 'working') return st('st c3strip', 'spark', AG.working(minsAgo(a.started_at)), true);
|
|
735
739
|
if (a.state === 'ready') return st('st ok', 'check', AG.ready);
|
|
@@ -758,9 +762,9 @@ function agentCase(inst, item, L) {
|
|
|
758
762
|
const strip = (spin, ic, text) => el('div', { class: 'c3ag', 'data-pm-agent-strip': item.id }, el('div', { class: 'fold a', role: 'status' }, spin ? el('span', { class: 'c3spin' }, c3ic(ic, 'sm')) : c3ic(ic, 'sm'), ' ' + text + ' ',
|
|
759
763
|
c3btn('chip2 lk', { 'data-pm-agent-stop': item.id }, [c3ic('stop', 'sm'), AG.stop], () => agentPress(inst, '/api/my-work/stop', { item: item.id }, AG.stopped))));
|
|
760
764
|
if (a.state === 'suggest' || a.state === 'failed') {
|
|
761
|
-
sit = a.state === 'failed' ? AG.sitFailed(a.error, a.asks) : AG.sitSuggest(w);
|
|
765
|
+
sit = a.state === 'failed' ? AG.sitFailed(a.error, a.asks) : isMine(item) ? AG.sitMine : AG.sitSuggest(w);
|
|
762
766
|
if (a.state === 'failed' && a.session) extras.push(el('div', { class: 'chips2' }, c3btn('chip2 lk', { 'data-pm-agent-session': a.session }, [c3ic('term', 'sm'), AG.openSession], () => openAgentSession(a.session, item))));
|
|
763
|
-
const brief = () => agentPress(inst, '/api/my-work/act', { action: 'brief', item: item.id, instruction: ask(), ws: inst.ctx.ws }, AG.handed(w));
|
|
767
|
+
const brief = () => agentPress(inst, '/api/my-work/act', { action: 'brief', item: item.id, instruction: ask(), ws: inst.ctx.ws }, isMine(item) ? AG.handedMine : AG.handed(w));
|
|
764
768
|
const retry = () => agentPress(inst, '/api/my-work/decide', { item: item.id, action: 'retry' }, AG.retried);
|
|
765
769
|
opts = [o('agent', a.state === 'failed' ? AG.tryAgain : AG.draftIt, [fx('spark', 'Drafts it here in PMOS', 'ok'), fx('lock', 'Nothing goes to ' + w, 'ok')], a.state === 'failed' ? retry : brief), ...own];
|
|
766
770
|
cta = 'Slide to let the agent draft';
|
|
@@ -787,10 +791,10 @@ function agentCase(inst, item, L) {
|
|
|
787
791
|
const back = () => { const v = note.value.trim(); if (!v) { say({ text: AG.needNote, timeout: 3500 }); note.focus(); return; } agentPress(inst, '/api/my-work/decide', { item: item.id, action: 'revise', note: v }, AG.sentBack); };
|
|
788
792
|
note.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.isComposing) back(); });
|
|
789
793
|
opts = [
|
|
790
|
-
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 }),
|
|
794
|
+
isMine(item) ? 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 }),
|
|
791
795
|
o('back', AG.back, [fx('reply', 'The agent drafts it again', 'ok')], back),
|
|
792
|
-
o('keep', AG.keepDraft, [fx('check', 'Nothing leaves PMOS', 'ok')], () => say({ text: AG.kept, timeout: 3000 })),
|
|
793
|
-
];
|
|
796
|
+
o('keep', AG.keepDraft, [fx('check', 'Nothing leaves PMOS', 'ok')], () => say({ text: isMine(item) ? AG.keptMine : AG.kept, timeout: 3000 })),
|
|
797
|
+
].filter(Boolean);
|
|
794
798
|
const noteBox = el('div', { class: 'form wk-agent-ask', 'data-pm-agent-note': item.id }, note); noteBox.hidden = inst.picks.get(item.id) !== 'back';
|
|
795
799
|
extras.push(el('div', { class: 'chips2' }, c3btn('chip2 lk', { 'data-pm-agent-draft': item.id }, [c3ic('eye', 'sm'), AG.openDraft], () => openDraft(item))));
|
|
796
800
|
after = noteBox;
|
|
@@ -859,7 +863,7 @@ function caseFile(inst, item, lens) {
|
|
|
859
863
|
c3ib('Speak is off on this line: nothing on this page records. Type the reply here.', 'mic', { 'data-off': '', onclick: () => say({ text: 'The microphone needs the app. Nothing was recorded: type the reply here.', timeout: 4200 }) }, 'sm'),
|
|
860
864
|
c3ib('Close the reply', 'x', { onClick: () => { inst.trays.delete(item.id); draw(inst); } }, 'sm')));
|
|
861
865
|
}
|
|
862
|
-
const showSteps = !(rs && rs.phase) && item.kind !== 'offline' && !(item.agent && !item.mock_row && item.agent.state !== 'sent') && (inst.planning.has(item.id) || (item.steps || []).length || (!agent && (!item.mock_row || item.src === 'You')));
|
|
866
|
+
const showSteps = !(rs && rs.phase) && item.kind !== 'offline' && !(item.agent && !item.mock_row && item.agent.state !== 'sent' && !isMine(item)) && (inst.planning.has(item.id) || (item.steps || []).length || (!agent && (!item.mock_row || item.src === 'You')));
|
|
863
867
|
if (showSteps) extras.push(stepsBlock(inst, item));
|
|
864
868
|
const anchor = root.querySelector('.c3ag') || root.querySelector('.sit');
|
|
865
869
|
if (anchor) anchor.after(...extras); else root.prepend(...extras);
|
|
@@ -22,9 +22,9 @@ Rules that always hold:
|
|
|
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
23
|
|
|
24
24
|
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 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>"`.
|
|
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
26
|
2. If MODE is do:
|
|
27
|
-
a. Read the sources the task names, read-only. Start from the link of the task. 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.
|
|
27
|
+
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
28
|
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
29
|
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
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.
|