create-pmos 0.213.0 → 0.215.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/c3-ib-more-test.js +23 -4
- package/workspace/c3-sessions-panel-test.js +69 -11
- package/workspace/c3-work-test.js +2 -2
- package/workspace/guide/console3-sessions.md +33 -9
- package/workspace/guide/jira.md +13 -1
- package/workspace/guide/my-work.md +4 -1
- package/workspace/lib/day-run.js +30 -1
- package/workspace/lib/engine/boards.js +51 -7
- package/workspace/lib/jira-status.js +46 -1
- package/workspace/lib/my-work.js +1 -1
- package/workspace/lib/proactive-cli.js +8 -2
- package/workspace/lib/routes-my-work.js +44 -17
- package/workspace/lib/routes-pm-items.js +8 -0
- package/workspace/lib/transcript-read.js +93 -10
- package/workspace/lib/work-assigned.js +23 -11
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-demo-boards.js +12 -3
- package/workspace/public/pm/c3-ib-sprints.js +142 -39
- package/workspace/public/pm/c3-initiatives.css +18 -0
- package/workspace/public/pm/c3-session-conv.js +164 -0
- package/workspace/public/pm/c3-session-term.js +63 -16
- package/workspace/public/pm/c3-settings.js +1 -1
- package/workspace/public/pm/console3.css +62 -0
- package/workspace/public/pm/pm-work-view.js +37 -11
- package/workspace/public/pm/tab-term.js +9 -0
- package/workspace/server.js +32 -12
- package/workspace/skills/day-task.skill +1 -0
- package/workspace/skills/jira-sync.skill +9 -3
- package/workspace/test-files.json +1 -1
|
@@ -1689,6 +1689,66 @@
|
|
|
1689
1689
|
/* terminal sheet sizes, as the classic dock has them; the phone sheet is already full width */
|
|
1690
1690
|
.c3 .c3t-size{flex:none}
|
|
1691
1691
|
.c3 .c3t-size button{padding:4px 8px}
|
|
1692
|
+
/* the session sheet (c3-session-term.js): Conversation | Terminal, a list of running sessions at 2/3 and Whole, and the conversation (c3-session-conv.js) */
|
|
1693
|
+
.c3 .c3t-views button .ic{flex:none}
|
|
1694
|
+
.c3 .c3t-cv{display:grid;grid-template-columns:minmax(0,1fr);height:calc(100vh - 150px);min-height:240px;min-width:0}
|
|
1695
|
+
.c3 .c3t-cv-panes{display:flex;flex-direction:column;min-height:0;min-width:0}
|
|
1696
|
+
.c3 .c3t-cv-panes>[hidden]{display:none}
|
|
1697
|
+
.c3 .c3t-cv-panes .c3t-tw{flex:1 1 0;height:auto;min-height:0}
|
|
1698
|
+
.c3 .c3t-cv-list{display:none;border-right:1px solid var(--border);overflow:auto;padding:0 var(--sp-2) 0 0;margin-right:var(--sp-3);gap:2px;align-content:start}
|
|
1699
|
+
.c3 .c3t-cv-list .eyebrow{padding:var(--sp-2) 10px 4px;font:500 var(--fs-1) var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--muted-foreground)}
|
|
1700
|
+
.c3 .c3t-cv-list button{display:grid;gap:2px;text-align:left;border:0;background:none;padding:8px 10px;border-radius:8px;cursor:pointer;color:var(--foreground);font:inherit;font-size:var(--fs-3);width:100%;min-width:0}
|
|
1701
|
+
.c3 .c3t-cv-list button .nm,.c3 .c3t-cv-list button .sub{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1702
|
+
.c3 .c3t-cv-list button .sub{font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
|
|
1703
|
+
.c3 .c3t-cv-list button[aria-current="true"]{background:color-mix(in oklab,var(--acc) 12%,transparent)}
|
|
1704
|
+
.c3 .c3t-cv-list button[aria-current="true"] .nm{color:var(--acc)}
|
|
1705
|
+
.c3 .c3t-cv-main{display:flex;flex-direction:column;flex:1 1 0;min-height:0;min-width:0}
|
|
1706
|
+
.c3 .c3t-cv-meta{display:flex;gap:var(--sp-2);align-items:center;flex-wrap:wrap;padding-bottom:var(--sp-2);border-bottom:1px solid var(--border);font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
|
|
1707
|
+
.c3 .c3t-cv-meta .tg{--tc:var(--acc)}
|
|
1708
|
+
.c3 .c3t-cv-meta .tg[data-tone="muted"]{--tc:var(--muted-foreground)}
|
|
1709
|
+
.c3 .c3t-cv-meta .tg[data-tone="warning"]{--tc:var(--warning)}
|
|
1710
|
+
.c3 .c3t-cv-feed{flex:1 1 0;min-height:0;overflow:auto;padding:var(--sp-3) 2px}
|
|
1711
|
+
.c3 .c3t-cv-feed>.wrap{max-width:760px;margin:0 auto;display:flex;flex-direction:column;gap:var(--sp-2)}
|
|
1712
|
+
.c3 .c3t-cv .m{padding:8px 14px;border-radius:8px;max-width:85%;overflow-wrap:anywhere;font-size:var(--fs-3);line-height:1.5}
|
|
1713
|
+
.c3 .c3t-cv .m.me{align-self:flex-end;background:var(--acc);color:var(--on-accent);white-space:pre-wrap}
|
|
1714
|
+
.c3 .c3t-cv .m.bot{align-self:flex-start;background:var(--muted)}
|
|
1715
|
+
.c3 .c3t-cv .m.st{color:var(--muted-foreground);font:var(--fs-2) var(--mono);padding:0}
|
|
1716
|
+
.c3 .c3t-cv .m.bot .c3t-md{display:grid;gap:6px}
|
|
1717
|
+
.c3 .c3t-cv .m.bot .c3t-md>*{margin:0}
|
|
1718
|
+
.c3 .c3t-cv .m.bot ul,.c3 .c3t-cv .m.bot ol{padding-left:18px}
|
|
1719
|
+
.c3 .c3t-cv .m.bot code{font:var(--fs-2) var(--mono)}
|
|
1720
|
+
.c3 .c3t-cv .m.bot pre{margin:0;padding:8px 10px;border-radius:6px;background:var(--background);border:1px solid var(--border);overflow:auto;max-height:240px}
|
|
1721
|
+
.c3 .c3t-cv .who{display:flex;gap:6px;align-items:center;margin-top:var(--sp-2);font:500 var(--fs-1) var(--mono);color:var(--muted-foreground)}
|
|
1722
|
+
.c3 .c3t-cv .who.me{align-self:flex-end}
|
|
1723
|
+
.c3 .c3t-cv details.tools{align-self:flex-start;max-width:85%;font:var(--fs-2) var(--mono);color:var(--muted-foreground)}
|
|
1724
|
+
.c3 .c3t-cv details.tools summary{cursor:pointer;display:flex;gap:6px;align-items:center;list-style:none;padding:2px 0}
|
|
1725
|
+
.c3 .c3t-cv details.tools summary::-webkit-details-marker{display:none}
|
|
1726
|
+
.c3 .c3t-cv details.tools[open] summary .car{display:inline-flex;transform:rotate(90deg)}
|
|
1727
|
+
.c3 .c3t-cv details.tools ul{list-style:none;margin:4px 0 0;padding:0 0 0 18px;display:grid;gap:4px;border-left:2px solid var(--border)}
|
|
1728
|
+
.c3 .c3t-cv details.tools li{display:flex;gap:6px;align-items:baseline;min-width:0}
|
|
1729
|
+
.c3 .c3t-cv details.tools li b{color:var(--foreground);font-weight:500}
|
|
1730
|
+
.c3 .c3t-cv details.tools li span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
|
|
1731
|
+
.c3 .c3t-cv .live{align-self:flex-start;display:flex;gap:8px;align-items:center;font:var(--fs-2) var(--mono);color:var(--muted-foreground)}
|
|
1732
|
+
.c3 .c3t-cv .perm{border:1px solid var(--warning);border-radius:8px;padding:var(--sp-3);display:grid;gap:var(--sp-2);background:color-mix(in oklab,var(--warning) 6%,var(--card))}
|
|
1733
|
+
.c3 .c3t-cv .perm .q{font:var(--fs-2) var(--mono)}
|
|
1734
|
+
.c3 .c3t-cv .perm .c3t-tray{display:flex;gap:var(--sp-2);flex-wrap:wrap;align-items:center}
|
|
1735
|
+
.c3 .c3t-cv .none{margin:var(--sp-4) auto;max-width:420px;text-align:center;display:grid;gap:var(--sp-3);justify-items:center}
|
|
1736
|
+
.c3 .c3t-cv .none>.ic{width:28px;height:28px;color:var(--muted-foreground)}
|
|
1737
|
+
.c3 .c3t-cv .none .dim{font-size:var(--fs-2)}
|
|
1738
|
+
.c3 .c3t-cv-comp{border-top:1px solid var(--border);padding-top:var(--sp-2);display:grid;gap:6px}
|
|
1739
|
+
.c3 .c3t-cv-comp[hidden],.c3 .c3t-cv-comp .box>[hidden]{display:none}
|
|
1740
|
+
.c3 .c3t-cv-comp .box{display:flex;gap:var(--sp-2);align-items:flex-end;border:1px solid var(--border);border-radius:8px;padding:6px 6px 6px 12px;background:var(--card)}
|
|
1741
|
+
.c3 .c3t-cv-comp .box:focus-within{border-color:var(--ring)}
|
|
1742
|
+
.c3 .c3t-cv-comp textarea{flex:1;border:0;background:none;resize:none;font:inherit;font-size:var(--fs-3);color:var(--foreground);padding:5px 0;outline:none;min-width:0}
|
|
1743
|
+
.c3 .c3t-cv-comp .hint{display:flex;gap:var(--sp-2);justify-content:space-between;align-items:center;flex-wrap:wrap;font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
|
|
1744
|
+
.c3 .c3t-cv-comp .hint .chips2[hidden]{display:none}
|
|
1745
|
+
@media (min-width:761px){
|
|
1746
|
+
.c3:not(.ph) .sheet.c3t-ss.tsz-two-thirds .c3t-cv,.c3:not(.ph) .sheet.c3t-ss.tsz-whole .c3t-cv{grid-template-columns:220px minmax(0,1fr)}
|
|
1747
|
+
.c3:not(.ph) .sheet.c3t-ss.tsz-two-thirds .c3t-cv-list,.c3:not(.ph) .sheet.c3t-ss.tsz-whole .c3t-cv-list{display:grid}
|
|
1748
|
+
}
|
|
1749
|
+
.c3.ph .c3t-views .lb,.c3.ph .c3t-all .lb{display:none}
|
|
1750
|
+
.c3.ph .c3t-cv-comp .hint .kb{display:none}
|
|
1751
|
+
.c3.ph .c3t-cv .m,.c3.ph .c3t-cv details.tools{max-width:92%}
|
|
1692
1752
|
.c3.ph .c3t-size{display:none}
|
|
1693
1753
|
@media (min-width:761px){
|
|
1694
1754
|
.c3 .sheet.wide.tsz-half{width:50vw}
|
|
@@ -1891,6 +1951,8 @@
|
|
|
1891
1951
|
.c3 .wk-agent-sum{margin:0}
|
|
1892
1952
|
.c3 .wk-agent-body{--tc:var(--acc)}
|
|
1893
1953
|
.c3 .wk-agent-body .sit{white-space:pre-line}
|
|
1954
|
+
.c3 .wk-agent-editbox{display:grid;gap:var(--sp-2)}
|
|
1955
|
+
.c3 .wk-agent-edit{width:100%;box-sizing:border-box;font:inherit;font-size:var(--fs-2);line-height:1.5;padding:8px 10px;border:1px solid var(--border);background:var(--background);color:var(--foreground);border-radius:6px;resize:vertical}
|
|
1894
1956
|
.c3 .tag.wk-off{background:var(--accw);color:var(--acc)}
|
|
1895
1957
|
.c3 .tag.wk-nb{background:var(--muted);color:var(--foreground)}
|
|
1896
1958
|
.c3 .tag.wk-on{background:var(--acc);color:var(--background)}
|
|
@@ -33,7 +33,7 @@ import { renderCase } from './c3-case.js';
|
|
|
33
33
|
import { makeXp, attachExpand } from './c3-expand.js';
|
|
34
34
|
import { attachSwipe } from './c3-swipe.js';
|
|
35
35
|
import { toast } from './c3-toast.js';
|
|
36
|
-
import { openSheet } from './c3-sheet.js';
|
|
36
|
+
import { openSheet, closeSheet } from './c3-sheet.js';
|
|
37
37
|
import { c3btn } from './page-agent.js';
|
|
38
38
|
import { onBriefing, loadBriefing } from './shell-ticker.js';
|
|
39
39
|
import { reviewSection } from './c3-brief.js';
|
|
@@ -724,7 +724,9 @@ const AG = {
|
|
|
724
724
|
openSession: 'Open the session',
|
|
725
725
|
willDo: 'What the agent will do: ', change: 'Change what the agent does', askLabel: 'What the agent will do',
|
|
726
726
|
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',
|
|
727
|
-
noteLabel: 'Tell the agent what to change', openDraft: 'Open the draft',
|
|
727
|
+
noteLabel: 'Tell the agent what to change', openDraft: 'Open the draft', editDraft: 'Edit the draft', edited: 'Edited by you. Post sends your text.',
|
|
728
|
+
saveDraft: 'Save the draft', cancel: 'Cancel', savedDraft: (w) => `Saved. Post sends your text to ${w}.`, savedMine: 'Saved. The draft stays here.', bodyLabel: 'The draft',
|
|
729
|
+
doneIt: 'Mark it done', doneFx: 'Closes the item. Nothing is posted', doneToast: 'Done. Nothing was posted.', draftSheet: (k) => 'Draft for ' + k, stop: 'stop',
|
|
728
730
|
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.',
|
|
729
731
|
posting: 'Approved. A session posts exactly this text.', sentBack: 'Sent back with your note. The agent drafts it again.', retried: 'The agent tries again.',
|
|
730
732
|
needNote: 'Say what to change first: type a note, then send it back.', link: 'Open what was posted',
|
|
@@ -785,6 +787,7 @@ function agentCase(inst, item, L) {
|
|
|
785
787
|
const d = a.draft || {};
|
|
786
788
|
quote = d.body || ''; sit = AG.sitReady('', w);
|
|
787
789
|
if (d.summary) chg.push(['doc', d.summary, 'draft']);
|
|
790
|
+
if (d.edited_at) chg.push(['edit', AG.edited, '']);
|
|
788
791
|
if (a.revisions) chg.push(['reply', 'Redrafted after your note' + (a.revisions > 1 ? 's (' + a.revisions + ')' : ''), '']);
|
|
789
792
|
outbox = true; cta = 'Hold to post';
|
|
790
793
|
const note = el('input', { type: 'text', placeholder: AG.noteLabel, 'aria-label': AG.noteLabel, 'data-pm-agent-note-in': item.id, autocomplete: 'off' });
|
|
@@ -794,16 +797,18 @@ function agentCase(inst, item, L) {
|
|
|
794
797
|
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 }),
|
|
795
798
|
o('back', AG.back, [fx('reply', 'The agent drafts it again', 'ok')], back),
|
|
796
799
|
o('keep', AG.keepDraft, [fx('check', 'Nothing leaves PMOS', 'ok')], () => say({ text: isMine(item) ? AG.keptMine : AG.kept, timeout: 3000 })),
|
|
800
|
+
o('done', AG.doneIt, [fx('check', AG.doneFx, 'ok')], (r) => doneAct(inst, item, r, AG.doneToast)),
|
|
797
801
|
].filter(Boolean);
|
|
798
802
|
const noteBox = el('div', { class: 'form wk-agent-ask', 'data-pm-agent-note': item.id }, note); noteBox.hidden = inst.picks.get(item.id) !== 'back';
|
|
799
|
-
extras.push(el('div', { class: 'chips2' }, c3btn('chip2 lk', { 'data-pm-agent-draft': item.id }, [c3ic('eye', 'sm'), AG.openDraft], () => openDraft(item))
|
|
803
|
+
extras.push(el('div', { class: 'chips2' }, c3btn('chip2 lk', { 'data-pm-agent-draft': item.id }, [c3ic('eye', 'sm'), AG.openDraft], () => openDraft(item, inst)),
|
|
804
|
+
c3btn('chip2 lk', { 'data-pm-agent-edit': item.id }, [c3ic('edit', 'sm'), AG.editDraft], () => openDraft(item, inst, true))));
|
|
800
805
|
after = noteBox;
|
|
801
806
|
} else if (a.state === 'sending') {
|
|
802
807
|
sit = AG.sitSending(k);
|
|
803
808
|
} else if (a.state === 'sent') {
|
|
804
809
|
sit = AG.sitSent(a.sent && a.sent.at ? hhmm(a.sent.at) : '', w);
|
|
805
810
|
opts = L.opts.map((x) => ({ ...x }));
|
|
806
|
-
if (a.draft && a.draft.body) extras.push(el('div', { class: 'chips2' }, c3btn('chip2 lk', { 'data-pm-agent-draft': item.id }, [c3ic('eye', 'sm'), AG.openDraft], () => openDraft(item)),
|
|
811
|
+
if (a.draft && a.draft.body) extras.push(el('div', { class: 'chips2' }, c3btn('chip2 lk', { 'data-pm-agent-draft': item.id }, [c3ic('eye', 'sm'), AG.openDraft], () => openDraft(item, inst)),
|
|
807
812
|
a.sent && a.sent.link ? el('a', { class: 'chip2 lk', href: a.sent.link, target: '_blank', rel: 'noopener noreferrer' }, c3ic('out', 'sm'), AG.link) : null));
|
|
808
813
|
}
|
|
809
814
|
if (opts.length) opts[0].rec = 1;
|
|
@@ -817,14 +822,35 @@ function openAgentSession(id, item) {
|
|
|
817
822
|
try { window.dispatchEvent(new CustomEvent('pmos:open-session', { detail: { id, name: 'Job · ' + (item.key || item.title || '') } })); } catch { /* no event */ }
|
|
818
823
|
}
|
|
819
824
|
|
|
820
|
-
|
|
825
|
+
/* The agent's draft in a sheet. A draft that waits for the PM can be edited there (amendment 13): Save keeps the PM's text
|
|
826
|
+
* as the draft, so Post sends exactly that text. */
|
|
827
|
+
function openDraft(item, inst, editing) {
|
|
821
828
|
const a = item.agent || {}; const d = a.draft || {};
|
|
829
|
+
const canEdit = !!inst && a.state === 'ready';
|
|
830
|
+
const area = el('textarea', { class: 'wk-agent-edit', rows: '18', 'aria-label': AG.bodyLabel, 'data-pm-agent-edit-in': item.id });
|
|
831
|
+
area.value = d.body || '';
|
|
832
|
+
const save = async () => {
|
|
833
|
+
const v = area.value.trim();
|
|
834
|
+
if (!v) { say({ text: 'The draft cannot be empty. Send it back with a note instead.', timeout: 3500 }); area.focus(); return; }
|
|
835
|
+
const r = await pmPost('/api/my-work/decide', { item: item.id, action: 'edit', body: v });
|
|
836
|
+
if (!r || !r.ok) { say({ text: (r && r.error) || COPY.saveError, tone: 'error' }); return; }
|
|
837
|
+
closeSheet();
|
|
838
|
+
say({ text: isMine(item) ? AG.savedMine : AG.savedDraft(whereOf(item)), timeout: 4000 });
|
|
839
|
+
await load(inst);
|
|
840
|
+
};
|
|
841
|
+
const text = editing && canEdit
|
|
842
|
+
? el('div', { class: 'wk-agent-editbox' }, area, el('div', { class: 'chips2' },
|
|
843
|
+
button({ label: AG.saveDraft, variant: 'primary', size: 'sm', onClick: save, attrs: { 'data-pm-agent-save': item.id } }),
|
|
844
|
+
button({ label: AG.cancel, variant: 'outline', size: 'sm', onClick: () => openDraft(item, inst, false) })))
|
|
845
|
+
: el('div', { class: 'case wk-agent-body' }, el('p', { class: 'sit' }, d.body || ''));
|
|
822
846
|
const body = el('div', { class: 'wk-agent-sheet' },
|
|
823
|
-
el('div', { class: 'chips' }, el('span', { class: 'tag' }, a.state === 'sent' ? 'posted' : 'draft'), item.key ? el('span', { class: 'chip2' }, c3ic('org', 'sm'), (item.origin === 'jira' ? 'Jira ' : '') + item.key) : null),
|
|
847
|
+
el('div', { class: 'chips' }, el('span', { class: 'tag' }, a.state === 'sent' ? 'posted' : d.edited_at ? 'draft · edited by you' : 'draft'), item.key ? el('span', { class: 'chip2' }, c3ic('org', 'sm'), (item.origin === 'jira' ? 'Jira ' : '') + item.key) : null),
|
|
824
848
|
d.summary ? el('p', { class: 'dim wk-agent-sum' }, d.summary) : null,
|
|
825
|
-
|
|
849
|
+
text,
|
|
826
850
|
el('div', { class: 'fold' }, c3ic('info', 'sm'), ' ' + (a.state === 'sent' ? 'This is the text that was posted.' : 'Nothing is in ' + whereOf(item) + ' yet. Post it from the item, after you read it.')));
|
|
827
|
-
|
|
851
|
+
const actions = canEdit && !editing ? [c3btn('chip2 lk', { 'data-pm-agent-edit-sheet': item.id }, [c3ic('edit', 'sm'), AG.editDraft], () => openDraft(item, inst, true))] : null;
|
|
852
|
+
openSheet({ title: AG.draftSheet(item.key || item.title), icon: 'doc', body, actions });
|
|
853
|
+
if (editing && canEdit) { try { area.focus(); } catch { /* not focusable yet */ } }
|
|
828
854
|
}
|
|
829
855
|
|
|
830
856
|
/** The case file element of an item: c3-case.js plus what is Work's own (offline work, the shared actions, the steps, the agent tray, the edit fields). */
|
|
@@ -840,7 +866,7 @@ function caseFile(inst, item, lens) {
|
|
|
840
866
|
});
|
|
841
867
|
root.setAttribute('data-pm-case', item.id);
|
|
842
868
|
if (!item.mock_row) { // real items carry no confidence figure: never show a made-up percentage
|
|
843
|
-
root.querySelectorAll('.opt .rk').forEach((n) => { n.replaceChildren(c3ic('spark', 'sm'), 'Suggested'); n.removeAttribute('title'); });
|
|
869
|
+
root.querySelectorAll('.opt .rk').forEach((n) => { if (n.childNodes.length) n.replaceChildren(c3ic('spark', 'sm'), 'Suggested'); else n.remove(); n.removeAttribute('title'); });
|
|
844
870
|
root.querySelectorAll('.opt .conf').forEach((n) => n.remove());
|
|
845
871
|
}
|
|
846
872
|
const extras = [];
|
|
@@ -863,7 +889,7 @@ function caseFile(inst, item, lens) {
|
|
|
863
889
|
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'),
|
|
864
890
|
c3ib('Close the reply', 'x', { onClick: () => { inst.trays.delete(item.id); draw(inst); } }, 'sm')));
|
|
865
891
|
}
|
|
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')));
|
|
892
|
+
const showSteps = !(rs && rs.phase) && item.kind !== 'offline' && !(item.agent && !item.mock_row && item.agent.state !== 'sent' && !isMine(item) && !(item.steps || []).length) && (inst.planning.has(item.id) || (item.steps || []).length || (!agent && (!item.mock_row || item.src === 'You')));
|
|
867
893
|
if (showSteps) extras.push(stepsBlock(inst, item));
|
|
868
894
|
const anchor = root.querySelector('.c3ag') || root.querySelector('.sit');
|
|
869
895
|
if (anchor) anchor.after(...extras); else root.prepend(...extras);
|
|
@@ -1227,7 +1253,7 @@ function jobNewCase(inst, st, close) {
|
|
|
1227
1253
|
if (r) { say({ text: 'Job created. Add work to it by saying which job it serves.', timeout: 3000 }); close(); await load(inst); }
|
|
1228
1254
|
} });
|
|
1229
1255
|
if (!demo) {
|
|
1230
|
-
root.querySelectorAll('.opt .rk').forEach((n) => { n.replaceChildren(c3ic('spark', 'sm'), 'Suggested'); n.removeAttribute('title'); });
|
|
1256
|
+
root.querySelectorAll('.opt .rk').forEach((n) => { if (n.childNodes.length) n.replaceChildren(c3ic('spark', 'sm'), 'Suggested'); else n.remove(); n.removeAttribute('title'); });
|
|
1231
1257
|
root.querySelectorAll('.opt .conf').forEach((n) => n.remove());
|
|
1232
1258
|
}
|
|
1233
1259
|
root.querySelector('.sit').after(el('div', { class: 'c3jf' }, nameIn ? field('Job', nameIn) : null, ...['when', 'want', 'so'].map((k, i) => field(k === 'when' ? 'When' : k === 'want' ? 'I want' : 'So', inputs[i]))));
|
|
@@ -1098,6 +1098,15 @@ export async function typeIntoSession(id, text) {
|
|
|
1098
1098
|
return send({ t: 'input', id, data: '\r' });
|
|
1099
1099
|
}
|
|
1100
1100
|
|
|
1101
|
+
/** Keys typed into a session as they are, with no Enter after them: a menu choice ('1') or Esc ('\x1b') for a running agent.
|
|
1102
|
+
* The same frame and socket as typeIntoSession. Returns false when nothing was sent. */
|
|
1103
|
+
export async function pressKeys(id, data) {
|
|
1104
|
+
if (FIXTURE || !id || !data) return false;
|
|
1105
|
+
connect();
|
|
1106
|
+
for (let i = 0; i < 50 && !socketOpen(); i++) await new Promise((r) => setTimeout(r, 100));
|
|
1107
|
+
return send({ t: 'input', id, data });
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1101
1110
|
/**
|
|
1102
1111
|
* End a running session, by id — the `+ session` chooser's one destructive action.
|
|
1103
1112
|
*
|
package/workspace/server.js
CHANGED
|
@@ -2939,22 +2939,41 @@ function txFound(s, now = Date.now()) {
|
|
|
2939
2939
|
}
|
|
2940
2940
|
// console3's session conversation: the PM's prompts and the agent's words from the transcript (transcript-read.readConversation).
|
|
2941
2941
|
// Re-parsed only when the file moves; `stamp` lets the page skip a redraw when nothing changed. Read-only and local.
|
|
2942
|
+
// `state` says what the page can show: 'conv' (a conversation), 'shell' (a shell with no agent in it yet), 'unlinked' (an agent
|
|
2943
|
+
// whose record cannot be found or read), 'other' (an agent that keeps no Claude transcript). `ask` is Claude Code's permission
|
|
2944
|
+
// prompt while it is on the pane's screen (permissionAsk).
|
|
2942
2945
|
function sessionConversation(s) {
|
|
2943
|
-
if (s.agent !== 'claude' && s.agent !== 'shell') return { messages: [], missing: 'Only Claude sessions show a conversation here.' };
|
|
2946
|
+
if (s.agent !== 'claude' && s.agent !== 'shell') return { messages: [], state: 'other', missing: 'Only Claude sessions show a conversation here.' };
|
|
2944
2947
|
const found = txFound(s);
|
|
2945
2948
|
if (found.missing) {
|
|
2946
|
-
if (s.agent === 'shell' && /wrote no session record/.test(found.missing)) return { messages: [], missing: 'No agent is running in this shell yet.' };
|
|
2947
|
-
return { messages: [], missing: found.missing };
|
|
2949
|
+
if (s.agent === 'shell' && /wrote no session record/.test(found.missing)) return { messages: [], state: 'shell', missing: 'No agent is running in this shell yet.' };
|
|
2950
|
+
return { messages: [], state: 'unlinked', missing: found.missing };
|
|
2948
2951
|
}
|
|
2949
2952
|
let stamp = null;
|
|
2950
2953
|
try { const st = fs.statSync(found.file); stamp = `${st.size}:${st.mtimeMs}`; } catch {}
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2954
|
+
let c = s.conv;
|
|
2955
|
+
if (!(c && s.convStamp === stamp)) {
|
|
2956
|
+
c = transcript.readConversation(found.file, scrub, transcript.CONV_LIMIT, s.root);
|
|
2957
|
+
c.state = 'conv';
|
|
2958
|
+
c.stamp = stamp;
|
|
2959
|
+
s.conv = c; s.convStamp = stamp;
|
|
2960
|
+
}
|
|
2961
|
+
// whose turn comes from the harness read, which has its own re-read floor: taken on every call, so a turn that the floor
|
|
2962
|
+
// held back shows on the next poll even when the file has not moved since
|
|
2963
|
+
const h = transcriptHarness(s) || {};
|
|
2964
|
+
const out = { ...c, turn: h.turn || null, openCall: h.openCall ? h.openCall.verb : null, model: h.model || null };
|
|
2965
|
+
// the prompt is on the screen, not in the transcript: read it only while a tool call is open
|
|
2966
|
+
const ask = out.turn === 'tool' ? paneAsk(s) : null;
|
|
2967
|
+
return ask ? { ...out, ask } : out;
|
|
2968
|
+
}
|
|
2969
|
+
// The pane's visible screen, plain text, read at most once every 1.5s per session (a tmux capture is a blocking spawn).
|
|
2970
|
+
function paneAsk(s, now = Date.now()) {
|
|
2971
|
+
if (!s.tmux || !TMUX) return null;
|
|
2972
|
+
if (s.askAt && now - s.askAt < 1500) return s.ask || null;
|
|
2973
|
+
s.askAt = now;
|
|
2974
|
+
const r = tmuxCtl(['capture-pane', '-p', '-t', s.tmux]);
|
|
2975
|
+
s.ask = r.status === 0 && r.stdout ? transcript.permissionAsk(r.stdout, scrub) : null;
|
|
2976
|
+
return s.ask;
|
|
2958
2977
|
}
|
|
2959
2978
|
function transcriptHarness(s) {
|
|
2960
2979
|
if (s.state === 'exited') return { missing: { all: 'the session has exited' } };
|
|
@@ -6682,9 +6701,10 @@ async function handleRequest(req, res) {
|
|
|
6682
6701
|
if (p === '/api/sessions/conversation' && req.method === 'GET') {
|
|
6683
6702
|
const sess = sessions.get(String(url.searchParams.get('id') || ''));
|
|
6684
6703
|
if (!sess) return json(res, { ok: false, error: 'no such session' }, 404);
|
|
6685
|
-
if (sess.state === 'exited') return json(res, { ok: true, id: sess.id, messages: [], missing: 'The session has ended.', turn: null, stamp: null });
|
|
6704
|
+
if (sess.state === 'exited') return json(res, { ok: true, id: sess.id, agent: sess.agent, messages: [], state: 'ended', missing: 'The session has ended.', turn: null, stamp: null });
|
|
6686
6705
|
const c = sessionConversation(sess);
|
|
6687
|
-
return json(res, { ok: true, id: sess.id, messages: c.messages,
|
|
6706
|
+
return json(res, { ok: true, id: sess.id, agent: sess.agent, messages: c.messages, state: c.state || null, missing: c.missing || null, turn: c.turn || null,
|
|
6707
|
+
openCall: c.openCall || null, model: c.model || null, ask: c.ask || null, stamp: c.stamp || null });
|
|
6688
6708
|
}
|
|
6689
6709
|
if (p === '/api/sessions/transcript' && req.method === 'GET') {
|
|
6690
6710
|
const sess = sessions.get(String(url.searchParams.get('id') || ''));
|
|
@@ -36,6 +36,7 @@ Procedure:
|
|
|
36
36
|
4. If MODE is send:
|
|
37
37
|
a. The task is approved. Read the approved draft and its link in the output of `day show`. The approved draft is the only text you may send. Do not read it as a draft to improve.
|
|
38
38
|
A draft whose summary starts "The PM's own" was written by the PM in the Your day card. Post it exactly as written, as the PM, to the place the summary and the link name. Do not add a greeting, a signature or a line of your own.
|
|
39
|
+
If `day show` prints "edited by the PM", the PM changed the draft in Work. The body is the PM's text. Post it exactly as written, the same way.
|
|
39
40
|
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`.
|
|
40
41
|
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>"`.
|
|
41
42
|
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.
|
|
@@ -40,11 +40,17 @@ Procedure:
|
|
|
40
40
|
- `goal`: `{"text": "<the goal>", "jira": "<Jira's goal, or null>"}`. Always write the goal yourself from the sprint's tickets, in one sentence of at most 240 characters: what the sprint delivers when its tickets are done, naming the two or three largest outcomes. Do not copy Jira's sprint goal: it is often empty, too broad or incomplete. Put Jira's own goal in `"jira"` only as a reference, exactly as Jira writes it.
|
|
41
41
|
- `summary`: two to four plain sentences on what the team is working on: what is done, what is moving, what is blocked. For a closed sprint, say if the goal was met and what moved to the next sprint.
|
|
42
42
|
- `themes`: one entry per group of related tickets, at most 8, largest first: `{"title", "line", "tickets", "done", "blocked", "people", "initiative"}`. Group by initiative when the tickets belong to one (the initiative id from step 1); put tickets with no initiative in one theme such as "Fixes and upkeep" with `"initiative": null`. `line` is one sentence; `people` is the assignees' first names, comma-separated.
|
|
43
|
-
- For a closed sprint, also `closed`: `{"done", "total", "carried", "carriedTo", "points"}` (`points` as "31 of 38" when the tickets have points).
|
|
44
|
-
|
|
43
|
+
- For a closed sprint, also `closed`: `{"done", "total", "carried", "carriedTo", "points"}` (`points` as "31 of 38" when the tickets have points), `hit`: `"met"`, `"partly"` or `"missed"` (was the goal delivered), and `got`: one sentence on what got in the way, or leave it out when nothing did.
|
|
44
|
+
- For the active sprint, also `brief`, the sprint brief the PM reads first. Write it for a product manager: what matters, what is stuck, and what to do about it. PMOS counts the points, the flow, the velocity and the forecast itself, so do not repeat those as a list.
|
|
45
|
+
- `health`: `"ok"` (on track), `"warn"` (at risk) or `"bad"` (off track), from the share of points done against the share of the sprint gone, the blocked tickets and the velocity of the closed sprints.
|
|
46
|
+
- `verdict`: two or three sentences on where the sprint stands: on track or not, about how much will carry over at the team's usual pace, and which part of the work is exposed.
|
|
47
|
+
- `issues`: at most 6, worst first. One entry per blocked ticket (flagged, or a blocked or on-hold status) and per ticket that looks stuck (in progress or in review with no status change across reads, or a review queue that is building up), plus tickets with no estimate as one entry. Each is `{"sev": "bad" for blocked or "warn" for watch, "title", "keys", "initiative", "why", "by", "plan", "doneWhen"}`. `why` says why it matters for the goal. `plan` is two to four steps the PM can take to move the ticket to done: who to ask, the decision to make, the fallback if it does not work. `by` is when to act ("Decide today", "Check by Wed 15 Oct"). `doneWhen` is the outcome that closes it. Do not only report a problem: always propose the plan.
|
|
48
|
+
- `done`: at most 6 outcomes completed in the sprint, `{"title", "keys", "initiative", "line", "waiting"}`. Write the outcome, not the ticket title. Set `"waiting": true` when the ticket is done but not yet in production (a status such as "Ready for Prod" or a release that has not gone out).
|
|
49
|
+
- `asks`: at most 3 decisions or actions only the PM can take this week, each one sentence.
|
|
50
|
+
Use only what the tickets say. Never invent work, a date or a person. Save `{"sprints": [{"name", "id", "state", "goal", "summary", "themes", "closed", "hit", "got", "brief"}]}` in the scratch folder, run `pmos jira summary --from <the file>`, and delete the file.
|
|
45
51
|
5. Record the people in People. For each assignee and reporter other than the PM, write one sighting per issue (People adds each new person at once): {"source": "jira", "handle": {"kind": "jira", "value": "<their Jira display name>"}, "name": "<display name>", "interaction": {"date": "<the issue's last update, YYYY-MM-DD>", "kind": "Jira", "text": "<Assignee|Reporter> of <KEY>: <summary, at most 150 characters>", "pointer": "jira · <KEY> · <url>", "initiative": "<the initiative id>"}}. Put all of them in one file {"sightings": [...]} in the scratch folder, run `pmos people record --file <that file>` once, and delete the file. An issue with no assignee gives no assignee sighting.
|
|
46
52
|
6. Finish with the three sections of the output contract, one short line each:
|
|
47
|
-
- Checked: the initiative, the number of tickets read, and each change (status, sprint, blocked). If nothing changed, say "no change". Add how many people you recorded, how many issues the sprint read saved per sprint, and which sprints got a goal
|
|
53
|
+
- Checked: the initiative, the number of tickets read, and each change (status, sprint, blocked). If nothing changed, say "no change". Add how many people you recorded, how many issues the sprint read saved per sprint, and which sprints got a goal, summary and brief.
|
|
48
54
|
- Not reached: each initiative whose search failed, with the reason, and the sprint read if it failed.
|
|
49
55
|
- Needs you: tickets newly blocked, tickets the search did not return, and any refusal.
|
|
50
56
|
|
|
@@ -299,7 +299,7 @@
|
|
|
299
299
|
"pm-portfolio-inline-test.js": 15,
|
|
300
300
|
"settings-store.test.js": 1,
|
|
301
301
|
"c3-settings-test.js": 25,
|
|
302
|
-
"c3-sessions-panel-test.js":
|
|
302
|
+
"c3-sessions-panel-test.js": 70,
|
|
303
303
|
"c3-updates-test.js": 20,
|
|
304
304
|
"c3-quiet-test.js": 30,
|
|
305
305
|
"c3-phone-model.test.js": 1,
|