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 +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 +1 -0
- package/workspace/lib/day-run.js +4 -3
- package/workspace/lib/my-work.js +2 -1
- package/workspace/lib/work-assigned.js +1 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/console3.css +3 -0
- package/workspace/public/pm/pm-work-view.js +11 -5
- package/workspace/public/pm/run-card.js +12 -3
- package/workspace/public/pm/sessions-panel.js +2 -2
- package/workspace/server.js +18 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "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": {
|
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-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-
|
|
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-
|
|
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.
|
package/workspace/lib/day-run.js
CHANGED
|
@@ -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
|
}
|
package/workspace/lib/my-work.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
9
|
+
"version": "0.188.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -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: (
|
|
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.
|
|
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
|
-
|
|
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
|
}
|
package/workspace/server.js
CHANGED
|
@@ -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)
|
|
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);
|