create-pmos 0.160.0 → 0.161.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-sessions-panel-test.js +24 -0
- package/workspace/guide/console3-sessions.md +21 -3
- package/workspace/guide/layout.md +1 -1
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/c3-dock.js +11 -4
- package/workspace/public/pm/c3-new-session.js +94 -0
- package/workspace/public/pm/c3-shell.js +3 -1
- package/workspace/public/pm/console3.css +8 -0
- package/workspace/public/pm/sessions-panel.js +7 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.161.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-12T07:44:14Z"}
|
|
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-12T07:44:14Z"}
|
|
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-12T07:44:14Z"}
|
|
6
6
|
status: stable
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -121,6 +121,30 @@ function startShell(name) {
|
|
|
121
121
|
ok('D3 End ends the session', !!(await until(gone, 8000)));
|
|
122
122
|
ok('D4 its row leaves Started by me', await waitFor(page, (s) => !document.querySelector(s), sel));
|
|
123
123
|
|
|
124
|
+
/* ── F: New session starts on console3 and opens its terminal here (c3-new-session; it went to the classic app before) ── */
|
|
125
|
+
const before = new Set(((await get('/api/sessions')).sessions || []).map((s) => s.id));
|
|
126
|
+
if (!(await page.isVisible('[data-c3t="new"]').catch(() => false))) await page.click('[data-c3pg="agent-button"]');
|
|
127
|
+
ok('F1 Started by me has a New button', await waitFor(page, () => !!document.querySelector('[data-c3t="new"]')));
|
|
128
|
+
await page.click('[data-c3t="new"]');
|
|
129
|
+
ok('F2 New opens the new-session sheet on this page', await waitFor(page, () => !!document.querySelector('[data-c3ns="form"]')) && page.url() === url0, page.url());
|
|
130
|
+
await page.fill('[data-c3ns="name"]', 'c3 new shell');
|
|
131
|
+
await page.selectOption('[data-c3ns="agent"]', 'shell');
|
|
132
|
+
await page.click('[data-c3ns="start"]');
|
|
133
|
+
const made = await until(async () => ((await get('/api/sessions')).sessions || []).find((s) => !before.has(s.id) && s.name === 'c3 new shell'), 10000);
|
|
134
|
+
ok('F3 Start starts the session on the server', !!made);
|
|
135
|
+
ok('F4 its terminal opens in a sheet on console3', !!made && await waitFor(page, (i) => !!document.querySelector(`[data-c3-session-term="${i}"] .xterm`), made.id) && page.url() === url0, page.url());
|
|
136
|
+
await page.click('[data-shell="sheet"] [aria-label="Close"]');
|
|
137
|
+
await waitFor(page, () => !document.querySelector('[data-c3-session-term]'));
|
|
138
|
+
await page.evaluate(() => window.dispatchEvent(new CustomEvent('pmos:open-session', { detail: {} })));
|
|
139
|
+
ok('F5 an open-session request with no session opens the new-session sheet, not the classic app', await waitFor(page, () => !!document.querySelector('[data-c3ns="form"]')) && page.url() === url0, page.url());
|
|
140
|
+
await page.click('[data-shell="sheet"] [aria-label="Close"]');
|
|
141
|
+
await waitFor(page, () => !document.querySelector('[data-c3ns="form"]'));
|
|
142
|
+
if (await page.isVisible('[data-c3pg="dock-new"]').catch(() => false)) {
|
|
143
|
+
await page.click('[data-c3pg="dock-new"]');
|
|
144
|
+
ok('F6 the dock\'s + opens the new-session sheet on this page', await waitFor(page, () => !!document.querySelector('[data-c3ns="form"]')) && page.url() === url0, page.url());
|
|
145
|
+
await page.click('[data-shell="sheet"] [aria-label="Close"]');
|
|
146
|
+
}
|
|
147
|
+
|
|
124
148
|
ok('E1 the page threw no error', errors.length === 0, errors.join(' | '));
|
|
125
149
|
return finish();
|
|
126
150
|
})().catch((e) => { console.error(e); finish(1); });
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: Guide
|
|
3
|
-
title: "Sessions in console3: open, read and end your sessions"
|
|
4
|
-
description: "How the Sessions panel in console3 groups your sessions, how to open a session's terminal without leaving console3, and how to end a session."
|
|
3
|
+
title: "Sessions in console3: start, open, read and end your sessions"
|
|
4
|
+
description: "How the Sessions panel in console3 groups your sessions, how to start a session, how to open a session's terminal without leaving console3, and how to end a session."
|
|
5
5
|
timestamp: 2026-10-12
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Sessions in console3: open, read and end your sessions
|
|
8
|
+
# Sessions in console3: start, open, read and end your sessions
|
|
9
9
|
|
|
10
10
|
Press the **Sessions** button in the console3 header to open the panel. It is the bolt icon with no label. Point at it to see its name. When a session needs you, a count shows on it. It shows your sessions in four groups: **On a card**, **Started by me**, **Past (7 days)** and **Automatic**.
|
|
11
11
|
|
|
@@ -13,6 +13,24 @@ Press the **Sessions** button in the console3 header to open the panel. It is th
|
|
|
13
13
|
|
|
14
14
|
The dock is open on the Briefing. On the other tabs it is folded away, and no rail of buttons is left at the right edge. To open the dock again, press Ctrl J (⌘J on a Mac), or use **Open the dock** in the **More** menu.
|
|
15
15
|
|
|
16
|
+
## Start a session
|
|
17
|
+
|
|
18
|
+
You start a session in console3. You do not need the classic app.
|
|
19
|
+
|
|
20
|
+
1. Do one of these steps:
|
|
21
|
+
- Press **+** in the header of the dock.
|
|
22
|
+
- Press **+ session** on a branch that has no session.
|
|
23
|
+
- Press **New** next to **Started by me** in the Sessions panel.
|
|
24
|
+
2. The **New session** sheet opens. Type a name, or leave it empty to use `Shell` or `Claude`.
|
|
25
|
+
3. Select the **Agent**: Shell or Claude. A Claude session uses the profile, the launch flags and the tier from Settings.
|
|
26
|
+
4. Select where the session runs in **Runs on**:
|
|
27
|
+
- **Automatic**: the server selects the tree.
|
|
28
|
+
- A listed tree: the session runs in that working tree.
|
|
29
|
+
- **New branch**: type the branch name. The session runs in its own working copy of the new branch.
|
|
30
|
+
5. Press **Start**. The session starts, and its terminal opens in a sheet on the same page.
|
|
31
|
+
|
|
32
|
+
If the server cannot start the session, the sheet shows the reason.
|
|
33
|
+
|
|
16
34
|
## Read a row
|
|
17
35
|
|
|
18
36
|
Each row under **Started by me** shows the state of the session, how long ago it started, and its tree. The state comes from the workspace server, for example `working` or `quiet`.
|
|
@@ -7,7 +7,7 @@ timestamp: 2026-10-12
|
|
|
7
7
|
|
|
8
8
|
# Guide: layout
|
|
9
9
|
|
|
10
|
-
This page describes the classic workspace, at `/classic.html`. The address `/` opens the console (see [the Briefing](briefing.md)). The console's More menu has **Open the classic app
|
|
10
|
+
This page describes the classic workspace, at `/classic.html`. The address `/` opens the console (see [the Briefing](briefing.md)). The console's More menu has **Open the classic app**. You can start a session in the console too (see [Sessions in console3](console3-sessions.md)).
|
|
11
11
|
|
|
12
12
|
On a desktop screen the workspace is one frame. It has one top bar, the place on screen under it, the detail when you open one, and the agent dock at the right. There is no left sidebar.
|
|
13
13
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmos-workspace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.161.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.161.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@xterm/addon-fit": "^0.11.0",
|
package/workspace/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* pm/c3-dock.js — the console3 dock on /c3.html: the mockup's repository, branch and session tree (console3.html renderDock, line 1957).
|
|
2
|
-
* Header: the repository select,
|
|
2
|
+
* Header: the repository select, the New session button (c3-new-session.js) and the fold button. Tree: one .br per working tree of the repository, its branch on
|
|
3
3
|
* top and the sessions that run in that tree under it, each with a state glyph, a provider icon and a name. Below the tree: the
|
|
4
4
|
* selected session's detail (what it is, where it runs, how long, the output tail, the Message box) and a scratchpad.
|
|
5
5
|
* Real data only:
|
|
@@ -17,6 +17,10 @@ import { c3ic } from './pm-work-view.js';
|
|
|
17
17
|
import { ic, TIPS } from './c3-terminal-icons.js';
|
|
18
18
|
import { source, needsYou } from './c3-terminal-data.js';
|
|
19
19
|
import { toast } from './c3-toast.js';
|
|
20
|
+
import { button } from './primitives.js';
|
|
21
|
+
|
|
22
|
+
/* a primitives button (keyboard, role) carrying the dock's own look and children */
|
|
23
|
+
const c3btn = (attrs, onClick, ...kids) => { const b = button({ variant: 'ghost', attrs, onClick }); b.replaceChildren(...kids); return b; };
|
|
20
24
|
|
|
21
25
|
/* the mockup's SM (line 1953): glyph and class by state. The server's `shown` words map onto them. */
|
|
22
26
|
const SM = { working: ['●', 'w'], needs: ['◆', 'n'], quiet: ['◼', ''], blocked: ['◇', 'b'], exited: ['○', ''] };
|
|
@@ -50,13 +54,15 @@ export function createDock(opts) {
|
|
|
50
54
|
const side = el('aside', { class: 'c3dock dock', 'aria-label': 'Sessions', 'data-c3pg': 'dock' },
|
|
51
55
|
el('div', { class: 'dk-h' }, c3ic('repo', 'sm'), sel,
|
|
52
56
|
listBtn,
|
|
53
|
-
|
|
57
|
+
c3btn({ class: 'ib sm', title: 'New session', 'aria-label': 'New session', 'data-c3pg': 'dock-new' }, () => newSession(), c3ic('plus', 'sm')),
|
|
54
58
|
el('button', { type: 'button', class: 'ib sm', title: 'Fold the dock (Ctrl J)', 'aria-label': 'Fold the dock', onclick: () => o.setOpen && o.setOpen(false) }, c3ic('fold'))),
|
|
55
59
|
body);
|
|
56
60
|
|
|
57
61
|
const withEnd = (s) => (S.ended.has(s.id) ? { ...s, shown: 'exited', label: 'ended' } : s);
|
|
58
62
|
const mine = () => S.sessions.filter((s) => s.ws === S.ws && !s.automatic).map(withEnd);
|
|
59
63
|
const inTree = (dir) => mine().filter((s) => s.tree === dir).sort((a, b) => (a.shown === 'exited') - (b.shown === 'exited') || String(b.created).localeCompare(String(a.created)));
|
|
64
|
+
/* a new session: the sheet asks name, agent and where it runs, starts it over the terminal connection and opens its terminal here (c3-new-session) */
|
|
65
|
+
const newSession = (on) => import('./c3-new-session.js').then((m) => m.openNewSession({ ws: S.ws, trees: (S.trees || []).filter((t) => t.ws === S.ws || t.main), on, onStarted: (id) => { S.sel = id; refresh(); } }));
|
|
60
66
|
const pick = (id) => { S.sel = id; S.selTree = null; S.confirm = null; drawTree(); drawDetail(); if (o.setOpen) o.setOpen(true); };
|
|
61
67
|
|
|
62
68
|
function row(s) {
|
|
@@ -73,7 +79,7 @@ export function createDock(opts) {
|
|
|
73
79
|
const ss = inTree(t.dir);
|
|
74
80
|
const body = ss.length ? ss.slice(0, 4).map(row)
|
|
75
81
|
: [t.main ? el('span', { class: 'dim mono', style: 'font-size:var(--fs-1);padding:2px 6px' }, 'no session')
|
|
76
|
-
:
|
|
82
|
+
: c3btn({ class: 'chip2 lk', title: 'Start a session on this branch', 'data-c3pg': 'dock-new-here' }, () => newSession('ws:' + t.ws), c3ic('plus', 'sm'), 'session')];
|
|
77
83
|
if (ss.length > 4) body.push(el('span', { class: 'dim mono', style: 'font-size:var(--fs-1);padding:2px 6px' }, '+' + (ss.length - 4) + ' more'));
|
|
78
84
|
return el('div', { class: 'br', 'data-c3-tree': t.dir }, el('button', { type: 'button', class: 'bn', 'data-c3-branch': t.dir, 'aria-current': String(!!S.sel && ss.some((x) => x.id === S.sel)), title: ss.length ? 'Open the session on this branch' : 'This branch has no session', onclick: () => { if (ss[0]) pick((ss.find((x) => x.shown !== 'exited') || ss[0]).id); else { S.sel = null; S.selTree = t.dir; drawTree(); drawDetail(); } },
|
|
79
85
|
style: 'border:0;background:none;text-align:left;width:100%;cursor:pointer' }, c3ic('branch', 'sm'), t.branch || '(detached)'), el('div', { class: 'ss' }, ...body));
|
|
@@ -88,7 +94,7 @@ export function createDock(opts) {
|
|
|
88
94
|
|
|
89
95
|
async function drawDetail() {
|
|
90
96
|
const s = mine().find((x) => x.id === S.sel);
|
|
91
|
-
if (!s) { const bt = S.selTree && S.trees && S.trees.find((x) => x.dir === S.selTree); detail.replaceChildren(el('p', { class: 'dim mono dk-empty', 'data-c3pg': 'dock-empty' }, bt ? (bt.branch || 'This branch') + ' has no session.
|
|
97
|
+
if (!s) { const bt = S.selTree && S.trees && S.trees.find((x) => x.dir === S.selTree); detail.replaceChildren(el('p', { class: 'dim mono dk-empty', 'data-c3pg': 'dock-empty' }, bt ? (bt.branch || 'This branch') + ' has no session. Press + session to start one.' : mine().length ? 'Pick a session above.' : 'No session runs in this repository.')); return; }
|
|
92
98
|
const t = S.trees && S.trees.find((x) => x.dir === s.tree);
|
|
93
99
|
const started = ago(Date.now() - Date.parse(s.created));
|
|
94
100
|
const last = Number.isFinite(s.lastOutputAt) ? ago(Date.now() - s.lastOutputAt) : null;
|
|
@@ -169,6 +175,7 @@ export function createDock(opts) {
|
|
|
169
175
|
start() { document.addEventListener('keydown', onKey); drawTree(); refresh(); timer = setInterval(refresh, 15000); if (source().subscribe) S.unsub = source().subscribe(refresh); },
|
|
170
176
|
stop() { if (timer) clearInterval(timer); if (S.unsub) S.unsub(); document.removeEventListener('keydown', onKey); },
|
|
171
177
|
refresh,
|
|
178
|
+
newSession,
|
|
172
179
|
};
|
|
173
180
|
return api;
|
|
174
181
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* pm/c3-new-session.js — start a session from console3, in a sheet, and open its terminal in the console (c3-new-session).
|
|
2
|
+
* openNewSession({ ws, trees, initiative, on, onStarted }) on: 'auto' (default), 'ws:<id>' (that tree) or 'new'
|
|
3
|
+
* Before this, every "start a session" press on console3 went to the classic app (/classic.html), because the page had no way
|
|
4
|
+
* to start one. A session starts over the terminal connection, so this uses the same request as the classic dock's new-session
|
|
5
|
+
* sheet (tab-term.js requestSpawn) and then opens the terminal with c3-session-term.js. Nothing opens unless the server says the
|
|
6
|
+
* session started; a refusal stays in the sheet as the server's sentence.
|
|
7
|
+
* Fields: name (empty: Shell or Claude, numbered when taken), agent (the Settings default first), and where it runs:
|
|
8
|
+
* Automatic the server's start policy picks the tree (the initiative's own working copy, or the main tree)
|
|
9
|
+
* a listed tree that working tree as it is
|
|
10
|
+
* New branch a new branch in its own working copy
|
|
11
|
+
* A Claude session takes the server's default profile and the Settings launch flags and tier, as the classic dock does. */
|
|
12
|
+
import { el } from './dom.js';
|
|
13
|
+
import { openSheet } from './c3-sheet.js';
|
|
14
|
+
import { button } from './primitives.js';
|
|
15
|
+
import { toast } from './c3-toast.js';
|
|
16
|
+
import { pmGet } from './http.js';
|
|
17
|
+
import { sessionDefaults, adoptProfileDefault, CLIS } from './dock.js';
|
|
18
|
+
|
|
19
|
+
const AGENT = { shell: 'Shell', claude: 'Claude' };
|
|
20
|
+
|
|
21
|
+
/** A free name: `base`, else `base 2`, `base 3`, … among the sessions that have not ended. Pure, for Node. */
|
|
22
|
+
export function freeName(base, sessions) {
|
|
23
|
+
const taken = new Set((sessions || []).filter((s) => s && s.state !== 'exited' && s.shown !== 'exited').map((s) => s.name));
|
|
24
|
+
if (!taken.has(base)) return base;
|
|
25
|
+
for (let i = 2; ; i += 1) if (!taken.has(base + ' ' + i)) return base + ' ' + i;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** What requestSpawn is sent, from the form. Pure, for Node. Returns { error } when the form is not complete. */
|
|
29
|
+
export function spawnOpts(f, d) {
|
|
30
|
+
const claude = f.agent === 'claude';
|
|
31
|
+
const common = {
|
|
32
|
+
name: f.name, agent: f.agent, initiative: f.initiative || null,
|
|
33
|
+
profile: claude ? (f.profile === undefined ? null : f.profile) : null,
|
|
34
|
+
flags: claude && d && Array.isArray(d.flags) && d.flags.length ? d.flags.map((x) => ({ flag: x.flag, ...(x.value ? { value: x.value } : {}) })) : undefined,
|
|
35
|
+
tier: claude && d && d.tier && d.tier !== 'auto' ? d.tier : null,
|
|
36
|
+
workType: 'general',
|
|
37
|
+
};
|
|
38
|
+
if (f.on === 'new') {
|
|
39
|
+
const b = String(f.branch || '').trim();
|
|
40
|
+
if (!b) return { error: 'Type a name for the new branch.' };
|
|
41
|
+
return { ...common, ws: f.ws, branch: b, createBranch: true, isolate: true };
|
|
42
|
+
}
|
|
43
|
+
if (f.on && f.on.startsWith('ws:')) return { ...common, ws: f.on.slice(3), branch: null, createBranch: false, isolate: null };
|
|
44
|
+
return { ...common, ws: f.ws, start: true };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function openNewSession(opts) {
|
|
48
|
+
const o = opts || {};
|
|
49
|
+
const d = sessionDefaults();
|
|
50
|
+
const trees = (o.trees || []).filter((t) => t && !t.missing && t.ws);
|
|
51
|
+
const F = { agent: CLIS.includes(d.cli) ? d.cli : 'shell', on: o.on && (o.on === 'new' || trees.some((t) => 'ws:' + t.ws === o.on)) ? o.on : 'auto', busy: false };
|
|
52
|
+
|
|
53
|
+
const name = el('input', { type: 'text', 'aria-label': 'Name', placeholder: 'Empty: Shell or Claude', 'data-c3ns': 'name' });
|
|
54
|
+
const agent = el('select', { 'aria-label': 'Agent', 'data-c3ns': 'agent', onchange: (e) => { F.agent = e.target.value; } },
|
|
55
|
+
...CLIS.map((c) => el('option', { value: c, selected: c === F.agent }, AGENT[c] || c)));
|
|
56
|
+
const branch = el('input', { type: 'text', 'aria-label': 'New branch', placeholder: 'feature/…', 'data-c3ns': 'branch' });
|
|
57
|
+
const branchRow = el('label', { class: 'c3ns-f', hidden: F.on !== 'new' }, el('span', { class: 'dim mono' }, 'New branch'), branch);
|
|
58
|
+
const where = el('select', { 'aria-label': 'Runs on', 'data-c3ns': 'on', onchange: (e) => { F.on = e.target.value; branchRow.hidden = F.on !== 'new'; } },
|
|
59
|
+
el('option', { value: 'auto', selected: F.on === 'auto' }, o.initiative ? 'Automatic: the working copy of initiative/' + o.initiative : 'Automatic: the main working tree'),
|
|
60
|
+
...trees.map((t) => el('option', { value: 'ws:' + t.ws, selected: F.on === 'ws:' + t.ws }, (t.main ? 'Main tree' : '⑂') + ' ' + (t.branch || 'detached'))),
|
|
61
|
+
el('option', { value: 'new', selected: F.on === 'new' }, 'New branch'));
|
|
62
|
+
const err = el('p', { class: 'c3ns-err', role: 'alert', 'data-c3ns': 'error' });
|
|
63
|
+
const go = button({ label: 'Start', variant: 'primary', size: 'sm', onClick: () => start(), attrs: { 'data-c3ns': 'start' } });
|
|
64
|
+
const body = el('div', { class: 'c3ns', 'data-c3ns': 'form' },
|
|
65
|
+
el('label', { class: 'c3ns-f' }, el('span', { class: 'dim mono' }, 'Name'), name),
|
|
66
|
+
el('label', { class: 'c3ns-f' }, el('span', { class: 'dim mono' }, 'Agent'), agent),
|
|
67
|
+
el('label', { class: 'c3ns-f' }, el('span', { class: 'dim mono' }, 'Runs on'), where),
|
|
68
|
+
branchRow, err, el('div', { class: 'c3ns-go' }, go));
|
|
69
|
+
const sheet = openSheet({ title: o.initiative ? 'New session for ' + o.initiative : 'New session', icon: 'term', body });
|
|
70
|
+
requestAnimationFrame(() => name.focus());
|
|
71
|
+
|
|
72
|
+
async function start() {
|
|
73
|
+
if (F.busy) return;
|
|
74
|
+
if (!o.ws) { err.textContent = 'Pick a repository first.'; return; }
|
|
75
|
+
F.busy = true; go.disabled = true; err.textContent = '';
|
|
76
|
+
const sessions = await pmGet('/api/sessions').then((r) => (r && Array.isArray(r.sessions) ? r.sessions : []), () => []);
|
|
77
|
+
const base = (AGENT[F.agent] || 'Shell') + (o.initiative ? ' · ' + o.initiative : '');
|
|
78
|
+
// the default profile is the server's; this browser's copy can be old, so a Claude start reads it first
|
|
79
|
+
const profile = F.agent === 'claude' ? adoptProfileDefault(await pmGet('/api/profiles').catch(() => null)) : null;
|
|
80
|
+
const req = spawnOpts({ name: name.value.trim() || freeName(base, sessions), agent: F.agent, initiative: o.initiative, profile, on: F.on, branch: branch.value, ws: o.ws }, d);
|
|
81
|
+
if (req.error) { err.textContent = req.error; F.busy = false; go.disabled = false; return; }
|
|
82
|
+
const term = await import('./tab-term.js').catch(() => null);
|
|
83
|
+
const res = term ? await term.requestSpawn(req) : { ok: false, error: 'The terminal connection could not be loaded on this page.' };
|
|
84
|
+
F.busy = false; go.disabled = false;
|
|
85
|
+
if (!res || !res.ok) { err.textContent = (res && res.error) || 'The session was not started.'; return; }
|
|
86
|
+
sheet.close();
|
|
87
|
+
if (o.onStarted) o.onStarted(res.id);
|
|
88
|
+
const m = await import('./c3-session-term.js');
|
|
89
|
+
m.openSessionTerm({ id: res.id, name: req.name });
|
|
90
|
+
// a session that shares its working tree with a running one is told so: their edits land in the same files
|
|
91
|
+
if (res.notice && /already running/.test(res.notice)) toast({ text: res.notice, timeout: 12000 });
|
|
92
|
+
}
|
|
93
|
+
return sheet;
|
|
94
|
+
}
|
|
@@ -425,7 +425,9 @@ function dock() {
|
|
|
425
425
|
});
|
|
426
426
|
S.dockEl = S.dock.side;
|
|
427
427
|
// a session opens in its own terminal sheet on this page (c3-session-term.js), not in the main app
|
|
428
|
-
|
|
428
|
+
// no id means "start one": the new-session sheet opens on this page (c3-new-session.js), not the classic app
|
|
429
|
+
window.addEventListener('pmos:open-session', (e) => { const id = e.detail && e.detail.id; if (!id) { S.dock.newSession(); return; } import('./c3-session-term.js').then((m) => m.openSessionTerm({ id, name: e.detail.name })); });
|
|
430
|
+
window.addEventListener('pmos:new-session', (e) => { e.preventDefault(); S.dock.newSession(e.detail && e.detail.on); });
|
|
429
431
|
return [S.dock.side];
|
|
430
432
|
}
|
|
431
433
|
/* The dock is open on the Briefing and folded away on the other tabs. A folded dock leaves no rail (PM, 2026-10-12): the header's Sessions button, Ctrl J and the More menu reach it. */
|
|
@@ -2218,3 +2218,11 @@
|
|
|
2218
2218
|
/* the dock's note under the branch list and its empty detail (c3-dock.js) */
|
|
2219
2219
|
.c3dock .dk-more { font-size: var(--fs-1); padding: 4px; }
|
|
2220
2220
|
.c3dock .dk-empty { font-size: var(--fs-2); }
|
|
2221
|
+
|
|
2222
|
+
/* the new-session sheet (c3-new-session.js) */
|
|
2223
|
+
.c3ns { display: flex; flex-direction: column; gap: var(--sp-3); }
|
|
2224
|
+
.c3ns-f { display: flex; flex-direction: column; gap: var(--sp-1); }
|
|
2225
|
+
.c3ns-f input, .c3ns-f select { width: 100%; }
|
|
2226
|
+
.c3ns-err { color: var(--destructive); font-size: var(--fs-2); margin: 0; }
|
|
2227
|
+
.c3ns-err:empty { display: none; }
|
|
2228
|
+
.c3ns-go { display: flex; justify-content: flex-end; }
|
|
@@ -130,10 +130,15 @@ function draw(host, st) {
|
|
|
130
130
|
: none('None running. A result appears on its card.')));
|
|
131
131
|
/* Started by me */
|
|
132
132
|
const mine = g.startedByMe || [];
|
|
133
|
-
|
|
133
|
+
// console3 answers pmos:new-session with its new-session sheet; a page that does not (the classic app) opens its own dock sheet
|
|
134
|
+
const add = button({ variant: 'ghost', attrs: { class: 'chip2 lk c3t-new', 'data-c3t': 'new', title: 'Start a new session' }, onClick: () => {
|
|
135
|
+
const ev = new CustomEvent('pmos:new-session', { cancelable: true, detail: {} });
|
|
136
|
+
if (window.dispatchEvent(ev)) Promise.all([import('./dock-sessions.js'), import('./board-read.js')]).then(([ds, br]) => ds.openNewSessionSheet(br.currentProduct(), { initiative: null })).catch(() => {});
|
|
137
|
+
} });
|
|
138
|
+
add.replaceChildren(ic('plus', 'sm'), 'New');
|
|
134
139
|
root.append(section('term', 'Started by me', mine.length, add, mine.length
|
|
135
140
|
? el('ul', { class: 'list', 'data-c3t': 'mine' }, mine.map((s) => { const c = cards[s.id]; return row(st, s, c, { sub: liveWord(s, c) + ' · ' + ago(s.startedAt) + ' · main tree', body: [plain(s, c) ? null : cardHost(st, s, c || { state: 'working', title: 'Working', nextMoves: [], claims: [], steps: [] }), blankPanel(st, s, c)].filter(Boolean) }); }))
|
|
136
|
-
: none('None.
|
|
141
|
+
: none('None. Press New to start one.')));
|
|
137
142
|
/* Past */
|
|
138
143
|
const past = g.past || [];
|
|
139
144
|
root.append(section('clock', 'Past (7 days)', past.length, null, past.length
|