create-pmos 0.187.0 → 0.188.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.187.0",
3
+ "version": "0.188.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@74de0bc
1
+ kernel-source: pmos@856b541
2
2
  assembled: 2026-10-13
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-13T05:30:39Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T06:02:00Z"}
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-13T05:30:39Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T06:02:00Z"}
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-13T05:30:39Z"}
5
+ generated: {by: process:create-pmos, at: "2026-10-13T06:02:00Z"}
6
6
  status: stable
7
7
  ---
8
8
 
@@ -112,6 +112,7 @@ The row chip then shows the agent's state:
112
112
  | **Agent: draft ready** | The draft waits for you. |
113
113
  | **Agent: posting** | You approved the draft. A session posts exactly that text. |
114
114
  | **Posted to** the item | The draft went out. Select **Open what was posted** to see it. |
115
+ | **Agent: needs you** | The agent asked you something, or waits for your OK to run a step, in its session. The case file gives the question. Select **Open the session** and answer it there: the job carries on. |
115
116
  | **Agent stopped** | The job failed or you stopped it. The case file gives the reason. Select **Try again** to run it again. |
116
117
 
117
118
  The page reads the state again every 15 seconds while an agent is queued, working or posting.
@@ -243,7 +243,7 @@ function decide(run, taskId, op, now) {
243
243
  const stalled = !!t.stalled_session;
244
244
  // the PM's own text is sent again as it is: a retry never asks an agent to draft over it
245
245
  t.status = t.draft && t.draft.by === 'pm' ? 'sending' : 'waiting'; keepSession(t); t.error = null; t.decided = { action: 'retry', at };
246
- if (stalled) { t.prev_session = null; delete t.stalled_session; delete t.stalled_status; }
246
+ if (stalled) { t.prev_session = null; delete t.stalled_session; delete t.stalled_status; delete t.asks; }
247
247
  return null;
248
248
  });
249
249
  }
@@ -303,14 +303,14 @@ function sessionEnded(run, sessionId, now) {
303
303
  function revive(t) {
304
304
  if (t.status !== 'failed' || !t.stalled_session || !t.stalled_status) return;
305
305
  t.status = t.stalled_status; t.session_id = t.stalled_session; t.error = null;
306
- delete t.stalled_session; delete t.stalled_status; delete t.failed_at;
306
+ delete t.stalled_session; delete t.stalled_status; delete t.failed_at; delete t.asks;
307
307
  }
308
308
 
309
309
  /**
310
310
  * A task session is open but has printed nothing for a long time and delivered nothing: it waits on a question, or its
311
311
  * agent stopped without reporting. The task fails with `why`, which frees its place, and a retry starts a NEW session.
312
312
  */
313
- function sessionStalled(run, sessionId, why, now) {
313
+ function sessionStalled(run, sessionId, why, now, { asks = null } = {}) {
314
314
  if (!run) return { ok: false, error: 'no day run today' };
315
315
  const next = clone(run);
316
316
  let changed = false;
@@ -318,6 +318,7 @@ function sessionStalled(run, sessionId, why, now) {
318
318
  if (!sessionId || t.session_id !== sessionId || (t.status !== 'working' && t.status !== 'sending')) continue;
319
319
  t.stalled_status = t.status; t.stalled_session = sessionId; t.session_id = null; t.prev_session = null;
320
320
  t.status = 'failed'; t.error = text(why).slice(0, 240) || 'the session stopped without a draft'; t.failed_at = iso(now); changed = true;
321
+ if (asks) t.asks = asks; else delete t.asks;
321
322
  }
322
323
  return { ok: true, run: next, changed };
323
324
  }
@@ -195,7 +195,8 @@ function stateOf(item, presses, runs) {
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
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,
198
- sent: t.sent || null, started_at: t.started_at || null, revisions: Array.isArray(t.revisions) ? t.revisions.length : 0 } : null;
198
+ sent: t.sent || null, started_at: t.started_at || null, revisions: Array.isArray(t.revisions) ? t.revisions.length : 0,
199
+ session: t.stalled_session || null, asks: t.asks || null } : null;
199
200
  const base = { task, instruction: last.instruction, decided_at: last.at };
200
201
  if (!t) return { state: 'agent', sub: 'waiting', ...base };
201
202
  if (t.status === 'review') return { state: 'ready', sub: 'review', ...base };
@@ -33,7 +33,7 @@ function agentOf(x) {
33
33
  state, suggest: x.suggest || null, instruction: x.instruction || null,
34
34
  draft: t && (t.draft || t.body) ? { summary: t.draft || null, body: t.body || null } : null,
35
35
  sent: t && t.sent ? { link: t.sent.link || null, at: t.sent.at || null } : null,
36
- error: t ? t.error || null : null, revisions: t ? t.revisions || 0 : 0, started_at: t ? t.started_at || null : null, decided_at: x.decided_at || null,
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,
37
37
  };
38
38
  }
39
39
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pmos-workspace",
3
- "version": "0.187.0",
3
+ "version": "0.188.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pmos-workspace",
9
- "version": "0.187.0",
9
+ "version": "0.188.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.187.0",
3
+ "version": "0.188.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",
@@ -1583,6 +1583,7 @@
1583
1583
  .c3 .raw{background:var(--muted);border-radius:8px;padding:var(--sp-2) var(--sp-3);font:var(--fs-1)/1.6 var(--mono);color:var(--muted-foreground);overflow-wrap:anywhere;white-space:pre-wrap}
1584
1584
  .c3 .c3t-card .draft{background:var(--muted);border-radius:10px;padding:var(--sp-3);font-size:var(--fs-3);white-space:pre-wrap;overflow-wrap:anywhere}
1585
1585
  .c3 .c3t-tray{display:flex;gap:var(--sp-2);flex-wrap:wrap;align-items:center}
1586
+ .c3 .c3t-grow{flex:1}
1586
1587
  .c3 .c3t-tray input,.c3 .c3t-panel input{flex:1;min-width:120px;border:1px solid var(--border);border-radius:99px;padding:6px 12px;background:var(--card);color:var(--foreground);font:inherit;font-size:var(--fs-2)}
1587
1588
  .c3 .c3t-tray input[readonly],.c3 .c3t-panel input[readonly]{opacity:.6;cursor:not-allowed}
1588
1589
  .c3 .c3t-card .pill.rec{background:var(--acc);color:var(--on-accent);border-color:var(--acc)}
@@ -1884,6 +1885,8 @@
1884
1885
  .c3 .wk-agent-ask input{flex:1;min-width:0;font:inherit;font-size:var(--fs-2);padding:4px 8px;border:1px solid var(--border);background:var(--background);color:var(--foreground);border-radius:6px}
1885
1886
  .c3 .wk-agent-ask[hidden]{display:none}
1886
1887
  .c3 .wk-agent-sheet{display:grid;gap:var(--sp-3)}
1888
+ .c3 .wk-agent-st{display:inline-flex;align-items:center;gap:4px;max-width:100%;min-width:0}
1889
+ .c3 .wk-agent-tx{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
1887
1890
  .c3 .wk-agent-will{font-size:var(--fs-2)}
1888
1891
  .c3 .wk-agent-sum{margin:0}
1889
1892
  .c3 .wk-agent-body{--tc:var(--acc)}
@@ -707,14 +707,15 @@ const minsAgo = (iso) => { const t = Date.parse(iso); if (!Number.isFinite(t)) r
707
707
  const whereOf = (item) => (item.origin === 'jira' ? 'Jira' : 'its source');
708
708
  const AG = {
709
709
  can: (t) => 'Agent can: ' + t, queued: 'Agent: queued', working: (ago) => 'Agent: working' + (ago ? ' · started ' + ago : ''), ready: 'Agent: draft ready',
710
- sending: 'Agent: posting', sent: (w, at) => 'Posted to ' + w + (at ? ' ' + at : ''), failed: (why) => 'Agent stopped' + (why ? ': ' + why : ''),
710
+ sending: 'Agent: posting', sent: (w, at) => 'Posted to ' + w + (at ? ' ' + at : ''), failed: (asks) => (asks ? 'Agent: needs you' : 'Agent stopped'),
711
711
  sitSuggest: (w) => `The agent can draft this for you. You read it here first. Nothing goes to ${w} until you post it.`,
712
712
  sitQueued: 'Queued. The agent starts as soon as a session is free. You can close this page.',
713
713
  sitWorking: 'The agent is working on it. You can close this page: the item keeps its state.',
714
714
  sitReady: (at, w) => `Draft ready${at ? ' at ' + at : ''}. Nothing went to ${w}.`,
715
715
  sitSending: (k) => `Posting to ${k}. A session does exactly the step you approved and nothing else.`,
716
716
  sitSent: (at, w) => `You posted it to ${w}${at ? ' at ' + at : ''}.`,
717
- sitFailed: (why) => `The agent stopped${why ? ': ' + why : '.'} Nothing was sent.`,
717
+ sitFailed: (why, asks) => (asks && why ? why.charAt(0).toUpperCase() + why.slice(1) + ' Nothing was sent.' : `The agent stopped${why ? ': ' + why : '.'} Nothing was sent.`),
718
+ openSession: 'Open the session',
718
719
  willDo: 'What the agent will do: ', change: 'Change what the agent does', askLabel: 'What the agent will do',
719
720
  draftIt: 'Let the agent draft this', tryAgain: 'Try again', post: (k) => 'Post it to ' + k, back: 'Send it back with a note', keepDraft: 'Keep it as a draft',
720
721
  noteLabel: 'Tell the agent what to change', openDraft: 'Open the draft', draftSheet: (k) => 'Draft for ' + k, stop: 'stop',
@@ -725,14 +726,14 @@ const AG = {
725
726
 
726
727
  function agentChip(item) {
727
728
  const a = item.agent; if (!a || item.mock_row) return null;
728
- const st = (cls, ic, text, spin) => el('span', { class: cls, 'data-pm-agent-state': a.state, 'data-pm-suggest': item.id }, spin ? el('span', { class: 'c3spin' }, c3ic(ic, 'sm')) : c3ic(ic, 'sm'), text);
729
+ 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));
729
730
  if (a.state === 'suggest') return st('chip2', 'spark', AG.can(a.instruction || a.suggest || ''));
730
731
  if (a.state === 'queued') return st('st', 'clock', AG.queued);
731
732
  if (a.state === 'working') return st('st c3strip', 'spark', AG.working(minsAgo(a.started_at)), true);
732
733
  if (a.state === 'ready') return st('st ok', 'check', AG.ready);
733
734
  if (a.state === 'sending') return st('st c3strip', 'out', AG.sending, true);
734
735
  if (a.state === 'sent') return st('st ok', 'out', AG.sent(whereOf(item), a.sent && a.sent.at ? hhmm(a.sent.at) : ''));
735
- if (a.state === 'failed') return st('tag warn', 'alert', AG.failed(a.error));
736
+ if (a.state === 'failed') return st('tag warn', 'alert', AG.failed(a.asks));
736
737
  return null;
737
738
  }
738
739
 
@@ -755,7 +756,8 @@ function agentCase(inst, item, L) {
755
756
  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 + ' ',
756
757
  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))));
757
758
  if (a.state === 'suggest' || a.state === 'failed') {
758
- sit = a.state === 'failed' ? AG.sitFailed(a.error) : AG.sitSuggest(w);
759
+ sit = a.state === 'failed' ? AG.sitFailed(a.error, a.asks) : AG.sitSuggest(w);
760
+ 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))));
759
761
  const brief = () => agentPress(inst, '/api/my-work/act', { action: 'brief', item: item.id, instruction: ask(), ws: inst.ctx.ws }, AG.handed(w));
760
762
  const retry = () => agentPress(inst, '/api/my-work/decide', { item: item.id, action: 'retry' }, AG.retried);
761
763
  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];
@@ -805,6 +807,10 @@ function agentCase(inst, item, L) {
805
807
  return { model, run };
806
808
  }
807
809
 
810
+ function openAgentSession(id, item) {
811
+ try { window.dispatchEvent(new CustomEvent('pmos:open-session', { detail: { id, name: 'Job · ' + (item.key || item.title || '') } })); } catch { /* no event */ }
812
+ }
813
+
808
814
  function openDraft(item) {
809
815
  const a = item.agent || {}; const d = a.draft || {};
810
816
  const body = el('div', { class: 'wk-agent-sheet' },
@@ -103,6 +103,11 @@ function pausedBox(ctx, card) {
103
103
  }
104
104
  // a session that has not started may be at the CLI's trust-the-folder question, where a typed Enter would answer it
105
105
  const NOSTART = 'Open the session to see what it waits for: typing into it now could answer a question you have not read.';
106
+ function flipBtn(ui, ctx) {
107
+ const b = button({ variant: 'ghost', onClick: () => { ui.flip = true; ctx.redraw(); }, attrs: { class: 'ib', 'aria-label': 'Flip to the case file', title: 'Case file' } });
108
+ b.replaceChildren(ic('flip', 'sm'));
109
+ return b;
110
+ }
106
111
  export function openBtn(ctx) {
107
112
  const b = button({ label: 'Open the session', onClick: () => ctx.openSession && ctx.openSession(ctx.id), attrs: { class: 'pill c3t-open', 'data-open-session': '', title: 'Open the session: its real terminal, on this page' } });
108
113
  b.prepend(ic('term', 'sm'));
@@ -210,10 +215,12 @@ function draw(host, ctx) {
210
215
  else {
211
216
  kids.push(el('h3', null, done && card.result.headline ? card.result.headline : (ctx.name || card.title)));
212
217
  const strip = stripNode(card, ctx.observed); if (strip) kids.push(strip);
218
+ // every box but the question and the permission carries Open the session; a card with none of them gets its own below
219
+ let opened = false;
213
220
  if (perms.length) perms.forEach((p) => kids.push(permissionBox(ctx, p)));
214
221
  else if (card.state === 'asking' && card.ask) kids.push(questionBox(ctx, card));
215
- else if (card.state === 'paused') kids.push(pausedBox(ctx, card));
216
- else if (ui.hidden !== card.state + ':' + ((card.failure && card.failure.code) || '')) { const f = failBox(ctx, card, ui); if (f) kids.push(f); }
222
+ else if (card.state === 'paused') { kids.push(pausedBox(ctx, card)); opened = true; }
223
+ else if (ui.hidden !== card.state + ':' + ((card.failure && card.failure.code) || '')) { const f = failBox(ctx, card, ui); if (f) { kids.push(f); opened = true; } }
217
224
  if (card.state === 'permission' && !perms.length) kids.push(dim('Waiting for your permission. Open the session to see it.'));
218
225
  if (done) {
219
226
  kids.push(...resultNodes(card.result, { ui, redraw: ctx.redraw, fileBody: ctx.fileBody }).filter(Boolean));
@@ -225,7 +232,9 @@ function draw(host, ctx) {
225
232
  if (ui.voice) kids.push(voiceNode(ctx, ui));
226
233
  else if (ui.tray) kids.push(trayNode(ctx, card, ui));
227
234
  if (!done && !ui.tray && !ui.voice && !perms.length && card.state !== 'asking' && !['working', 'queued'].includes(card.state)) { /* a failure box carries its own moves */ }
228
- if (!done && !ctx.readonly) kids.push(el('div', { class: 'c3t-tray' }, el('span', { style: 'flex:1' }), el('button', { type: 'button', class: 'ib', 'aria-label': 'Flip to the case file', title: 'Case file', onclick: () => { ui.flip = true; ctx.redraw(); } }, ic('flip', 'sm'))));
235
+ // a starting or working run shows Open the session on its face: a read-only card (a job PMOS started) has no case file to flip to
236
+ if (!done && (!ctx.readonly || (!opened && ctx.openSession && !ctx.ended))) kids.push(el('div', { class: 'c3t-tray' }, !opened && ctx.openSession && !ctx.ended ? openBtn(ctx) : null, el('span', { class: 'c3t-grow' }),
237
+ ctx.readonly ? null : flipBtn(ui, ctx)));
229
238
  }
230
239
  const art = el('article', { class: 'c3t-card' + (['working', 'queued'].includes(card.state) ? ' busy' : '') + (ui.flash ? ' flash' : ''), 'data-run-card': card.state || '', 'data-run-id': ctx.id, 'aria-label': ctx.name || ctx.id }, ...kids);
231
240
  host.replaceChildren(el('div', { class: 'c3 c3e' }, art));
@@ -39,7 +39,7 @@ function outcomeOf(card, meta) {
39
39
  function cardHost(st, s, card, extra) {
40
40
  const host = el('div', { 'data-run-host': s.id });
41
41
  const ctx = { id: s.id, name: s.name, card, observed: st.data.demo && st.data.demo.observed ? st.data.demo.observed[s.id] : null, origin: s.origin === 'manual' ? (s.cardId ? 'on a card' : 'started by you') : s.origin, src: st.src,
42
- refresh: st.refresh, openSession: st.openSession, glossary: st.data.demo && st.data.demo.glossary, fileBody: st.data.demo && st.data.demo.fileBody, accepted: false, ...(extra || {}) };
42
+ refresh: st.refresh, openSession: st.openSession, ended: s.endedAt != null, glossary: st.data.demo && st.data.demo.glossary, fileBody: st.data.demo && st.data.demo.fileBody, accepted: false, ...(extra || {}) };
43
43
  mountRunCard(host, ctx);
44
44
  return host;
45
45
  }
@@ -172,7 +172,7 @@ function draw(host, st) {
172
172
  const running = auto.filter((s) => s.endedAt == null).length;
173
173
  const sw = el('button', { type: 'button', class: 'c3t-sw', role: 'switch', 'aria-checked': String(st.showAuto), 'aria-label': 'Show automatic sessions', 'data-sessions-auto': '', onclick: () => { st.showAuto = !st.showAuto; st.redraw(); } }, el('i'));
174
174
  root.append(section('bolt', 'Automatic', auto.length, el('span', { class: 'chips2' }, dim(auto.length + ' today' + (running ? ', ' + running + ' running' : '')), sw), st.showAuto
175
- ? (auto.length ? el('ul', { class: 'list', 'data-c3t': 'auto' }, auto.map((s) => { const c = cards[s.id]; return row(st, s, c, { sub: s.origin + ' · ' + (s.endedAt == null ? 'running' : 'ended') + ' · ' + ago(s.startedAt) + ' · ' + outcomeOf(c), body: c ? [cardHost(st, s, c, { readonly: true })] : [dim('No card yet.')] }); })) : none('None.'))
175
+ ? (auto.length ? el('ul', { class: 'list', 'data-c3t': 'auto' }, auto.map((s) => { const c = cards[s.id]; return row(st, s, c, { sub: s.origin + ' · ' + (s.endedAt == null ? 'running' : 'ended') + ' · ' + ago(s.startedAt) + ' · ' + outcomeOf(c), body: c ? [cardHost(st, s, c, { readonly: true })] : [dim('No card yet.'), s.endedAt == null ? el('div', { class: 'c3t-tray' }, openBtn({ id: s.id, openSession: st.openSession })) : null].filter(Boolean) }); })) : none('None.'))
176
176
  : el('div', { class: 'dim', style: 'font-size:var(--fs-2)' }, 'Hidden. They run, write their result onto a card and clean up. Switch on to see them.')));
177
177
  host.append(root);
178
178
  }
@@ -4135,10 +4135,27 @@ function stallQuietTasks(run, now) {
4135
4135
  if ((t.status !== 'working' && t.status !== 'sending') || !t.session_id) continue;
4136
4136
  const s = sessions.get(t.session_id);
4137
4137
  if (!s || s.state === 'exited') continue;
4138
- if (now - Math.max(s.lastOutputAt || 0, Date.parse(t.started_at || 0) || 0) >= TASK_IDLE_MS) run = dayRunLib.sessionStalled(run, t.session_id, TASK_STALLED_WHY, now).run;
4138
+ if (now - Math.max(s.lastOutputAt || 0, Date.parse(t.started_at || 0) || 0) < TASK_IDLE_MS) continue;
4139
+ const q = taskStallReason(s);
4140
+ run = dayRunLib.sessionStalled(run, t.session_id, q.why, now, { asks: q.asks }).run;
4139
4141
  }
4140
4142
  return run;
4141
4143
  }
4144
+ // Why a quiet task session is quiet, from its transcript: a tool call that waits on the PM's OK, a turn that ended with
4145
+ // a question for the PM, or (no transcript answer) only that it printed nothing. The session stays open in every case.
4146
+ function taskStallReason(s) {
4147
+ let hx = null; try { hx = transcriptHarness(s); } catch { hx = null; }
4148
+ const shown = sessionState.presented('quiet', hx && hx.turn);
4149
+ const said = String(((harnessOf(s) || {}).lastWords) || '').replace(/\s+/g, ' ').trim().slice(0, 140);
4150
+ if (shown === 'blocked') {
4151
+ const verb = hx && hx.openCall && hx.openCall.verb ? String(hx.openCall.verb).slice(0, 60) : '';
4152
+ return { asks: 'ok', why: `the agent waits for your OK${verb ? ' to ' + verb : ''} in its session. Open the session and answer it: the job carries on from there.` };
4153
+ }
4154
+ if (shown === 'needs') return { asks: 'answer', why: `the agent stopped to ask you something in its session${said ? ': "' + said + '"' : ''}. Open the session and answer it: the job carries on from there.` };
4155
+ // no transcript at all: the agent never began its turn, which is what a CLI held at its trust-the-folder or sign-in screen looks like
4156
+ if (hx && hx.missing && hx.missing.all && s.agent === 'claude') return { asks: 'start', why: 'the session has printed nothing for five minutes and shows no sign that it began the job: Claude Code may be waiting for you to trust the folder or to sign in. Open the session to see it.' };
4157
+ return { asks: null, why: TASK_STALLED_WHY };
4158
+ }
4142
4159
  // The skill's own body is the prompt, as fireSchedule hands it over; the line after it names this run's task and mode.
4143
4160
  function daySkillPrompt(run, skillId, wsId) {
4144
4161
  const w = getWs(wsId || run.ws);