great-cto 3.70.0 → 3.71.1

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.
Files changed (37) hide show
  1. package/README.md +9 -1
  2. package/board/.claude-plugin/plugin.json +93 -1
  3. package/board/packages/board/lib/routes.mjs +14 -1
  4. package/board/packages/board/lib/work.mjs +34 -3
  5. package/board/packages/board/public/assets/admin-shell.css +63 -0
  6. package/board/packages/board/public/assets/admin-shell.js +39 -5
  7. package/board/packages/board/public/assets/work-view.js +320 -37
  8. package/board/packages/board/public/index.html +43 -1
  9. package/board/scripts/codex-pipeline.mjs +7 -1
  10. package/board/scripts/lib/claude-exec.mjs +49 -6
  11. package/board/scripts/lib/codex-exec.mjs +42 -18
  12. package/board/scripts/lib/codex-host-state.mjs +18 -2
  13. package/board/scripts/lib/codex-pipeline.mjs +44 -7
  14. package/board/scripts/lib/host-graph-bindings.mjs +135 -0
  15. package/board/scripts/lib/host-graph-events.mjs +157 -0
  16. package/board/scripts/lib/host-graph-journal.mjs +210 -0
  17. package/board/scripts/lib/host-graph-observer.mjs +61 -0
  18. package/board/scripts/lib/host-graph-runtime.mjs +38 -0
  19. package/board/scripts/work-task.mjs +17 -4
  20. package/dist/adapt.js +10 -21
  21. package/dist/codex.js +4 -15
  22. package/dist/companion.js +6 -122
  23. package/dist/installer.js +4 -72
  24. package/dist/main.js +51 -118
  25. package/dist/release-activation.mjs +561 -0
  26. package/dist/release-archive.mjs +165 -0
  27. package/dist/release-trust.mjs +514 -0
  28. package/dist/release-update.mjs +303 -0
  29. package/dist/self-upgrade.js +10 -39
  30. package/dist/update-check.js +1 -0
  31. package/dist/upgrade.js +5 -120
  32. package/package.json +1 -1
  33. package/board/packages/board/fixtures/beads-read-owner.mjs +0 -3
  34. package/board/packages/board/fixtures/fake-bd-controlled.mjs +0 -55
  35. package/board/packages/board/fixtures/fake-bd-fail.sh +0 -9
  36. package/board/packages/board/fixtures/fake-bd-slow.sh +0 -12
  37. package/board/packages/board/fixtures/held-receipt-git.mjs +0 -21
package/README.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # great-cto
2
2
 
3
- > One command install for the [great_cto](https://github.com/avelikiy/great_cto) Claude Code plugin.
3
+ > CLI for the [great_cto](https://github.com/avelikiy/great_cto) engineering workspace, controlled runs and verified release updates.
4
+
5
+ **Verified distribution boundary:** ADR-045 replaces mutable installation and update
6
+ paths with signed artifact admission. This implementation refuses `init`, native
7
+ plugin refresh and automatic companion installation before mutation. The plain
8
+ npm self-update adapter requires an independently configured verifier and exact
9
+ signed release assets. See [verified updates](../../docs/reference/verified-updates.md).
10
+ The installation walkthrough below describes the legacy 3.70.1 flow; it is not
11
+ an activation route for this implementation.
4
12
 
5
13
  ```bash
6
14
  npx great-cto init
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "great-cto",
3
3
  "description": "You already have the agent. This is everything around it. great_cto runs Claude Code as a pipeline of 72 specialist agents \u2014 an independent model checks each stage before the next builds on it, spending caps refuse rather than warn, and three decisions stay yours: what gets built, how, and whether it ships.",
4
- "version": "3.70.0",
4
+ "version": "3.71.1",
5
5
  "author": {
6
6
  "name": "Alexander Velikiy",
7
7
  "url": "https://hashnode.com/@Greatcto"
@@ -88,6 +88,16 @@
88
88
  "timeout": 10
89
89
  }
90
90
  ]
91
+ },
92
+ {
93
+ "matcher": "",
94
+ "hooks": [
95
+ {
96
+ "type": "command",
97
+ "timeout": 5,
98
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
99
+ }
100
+ ]
91
101
  }
92
102
  ],
93
103
  "PreCompact": [
@@ -120,6 +130,16 @@
120
130
  "statusMessage": "Loading orchestrator contract..."
121
131
  }
122
132
  ]
133
+ },
134
+ {
135
+ "matcher": "",
136
+ "hooks": [
137
+ {
138
+ "type": "command",
139
+ "timeout": 5,
140
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
141
+ }
142
+ ]
123
143
  }
124
144
  ],
125
145
  "SubagentStop": [
@@ -138,6 +158,16 @@
138
158
  "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/turn-snapshot.mjs\" 2>/dev/null || true"
139
159
  }
140
160
  ]
161
+ },
162
+ {
163
+ "matcher": "",
164
+ "hooks": [
165
+ {
166
+ "type": "command",
167
+ "timeout": 5,
168
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
169
+ }
170
+ ]
141
171
  }
142
172
  ],
143
173
  "PreToolUse": [
@@ -208,6 +238,16 @@
208
238
  "statusMessage": "Checking recorded lessons..."
209
239
  }
210
240
  ]
241
+ },
242
+ {
243
+ "matcher": "",
244
+ "hooks": [
245
+ {
246
+ "type": "command",
247
+ "timeout": 5,
248
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
249
+ }
250
+ ]
211
251
  }
212
252
  ],
213
253
  "PostToolUse": [
@@ -293,6 +333,16 @@
293
333
  "statusMessage": "Computing next pipeline stage..."
294
334
  }
295
335
  ]
336
+ },
337
+ {
338
+ "matcher": "",
339
+ "hooks": [
340
+ {
341
+ "type": "command",
342
+ "timeout": 5,
343
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
344
+ }
345
+ ]
296
346
  }
297
347
  ],
298
348
  "SessionEnd": [
@@ -311,6 +361,16 @@
311
361
  "timeout": 5
312
362
  }
313
363
  ]
364
+ },
365
+ {
366
+ "matcher": "",
367
+ "hooks": [
368
+ {
369
+ "type": "command",
370
+ "timeout": 5,
371
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
372
+ }
373
+ ]
314
374
  }
315
375
  ],
316
376
  "UserPromptSubmit": [
@@ -338,6 +398,16 @@
338
398
  "timeout": 5
339
399
  }
340
400
  ]
401
+ },
402
+ {
403
+ "matcher": "",
404
+ "hooks": [
405
+ {
406
+ "type": "command",
407
+ "timeout": 5,
408
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
409
+ }
410
+ ]
341
411
  }
342
412
  ],
343
413
  "PermissionDenied": [
@@ -384,6 +454,16 @@
384
454
  "timeout": 5
385
455
  }
386
456
  ]
457
+ },
458
+ {
459
+ "matcher": "",
460
+ "hooks": [
461
+ {
462
+ "type": "command",
463
+ "timeout": 5,
464
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
465
+ }
466
+ ]
387
467
  }
388
468
  ],
389
469
  "Notification": [
@@ -397,6 +477,18 @@
397
477
  }
398
478
  ]
399
479
  }
480
+ ],
481
+ "PostToolUseFailure": [
482
+ {
483
+ "matcher": "",
484
+ "hooks": [
485
+ {
486
+ "type": "command",
487
+ "timeout": 5,
488
+ "command": "_gd=\"$PWD\"; while [ -n \"$_gd\" ] && { [ \"$_gd\" = \"$HOME\" ] || [ ! -f \"$_gd/.great_cto/PROJECT.md\" ]; }; do _gd=\"${_gd%/*}\"; done; [ -n \"$_gd\" ] && cd \"$_gd\"; [ -d \"${GREAT_CTO_GRAPH_BINDINGS_DIR:-$HOME/.great_cto/graph-bindings}\" ] || exit 0; _pd=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -n \"$_pd\" ] || exit 0; GREAT_CTO_HOST=claude-code node \"$_pd/scripts/hooks/host-graph-observer.mjs\""
489
+ }
490
+ ]
491
+ }
400
492
  ]
401
493
  },
402
494
  "homepage": "https://greatcto.systems",
@@ -45,7 +45,7 @@ import { readVerdicts } from './verdicts.mjs';
45
45
  import { parseAgentBudgets, upsertAgentBudget, removeAgentBudget } from '../../../scripts/lib/agent-budget.mjs';
46
46
  import { resolveSecondOpinion, SECOND_OPINION_PROVIDERS } from '../../../scripts/lib/second-opinion.mjs';
47
47
  import { detectCodex } from '../../../scripts/lib/codex-exec.mjs';
48
- import { getWork } from './work.mjs';
48
+ import { getWork, getHostGraphPage } from './work.mjs';
49
49
  let publicationPreviewBusy = false;
50
50
  let publicationWriteBusy = false;
51
51
  import { publicationTickets, localPublicationOrigin } from './publication-tickets.mjs';
@@ -237,6 +237,19 @@ async function dispatch(req, res, url, cwd) {
237
237
  return true;
238
238
  }
239
239
 
240
+ if (pathname === '/api/host-graph') {
241
+ if (requestedProject && resolveProjectInfo(requestedProject).resolved === 'fallback') {
242
+ res.writeHead(404, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify({ error: 'Unknown project' })); return true;
243
+ }
244
+ if (req.method !== 'GET') { res.writeHead(405, { Allow: 'GET' }); res.end(); return true; }
245
+ try {
246
+ const graph = getHostGraphPage(cwd, { host: url.searchParams.get('host'), sessionId: url.searchParams.get('session'),
247
+ runId: url.searchParams.get('run'), cursor: url.searchParams.get('cursor') });
248
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify(graph));
249
+ } catch { res.writeHead(404, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify({ error: 'Bound graph page is unavailable' })); }
250
+ return true;
251
+ }
252
+
240
253
  // New read model refuses project fallback rather than relabeling another project.
241
254
  if (pathname === '/api/work') {
242
255
  if (requestedProject && resolveProjectInfo(requestedProject).resolved === 'fallback') {
@@ -10,6 +10,8 @@ import { measureWork, compareWork } from '../../../scripts/lib/work-metrics.mjs'
10
10
  import { readSessionStatus } from '../../../scripts/lib/session-status.mjs';
11
11
  import { agentActivity } from './agent-activity.mjs';
12
12
  import { taskInspector } from './task-inspector.mjs';
13
+ import { graphBinding, projectHostObservations } from '../../../scripts/lib/host-graph-observer.mjs';
14
+ import { nativeGraphObservations, readNativeGraphPage } from '../../../scripts/lib/host-graph-bindings.mjs';
13
15
 
14
16
  const text = v => typeof v === 'string' ? v : null;
15
17
  const date = v => typeof v === 'string' && Number.isFinite(Date.parse(v)) ? v : null;
@@ -30,7 +32,7 @@ function executionAttempts(run) {
30
32
  }));
31
33
  const truncated = attempts.length > 200;
32
34
  const graph = run?.executionGraph || { state: 'unavailable', complete: false, nodes: [], edges: [], snapshots: [], reason: 'No linked controller-recorded graph is available.' };
33
- return { state: nodes.length ? 'recorded' : 'unknown', nodes, topology: graph.state === 'recorded' ? 'recorded' : 'unknown', graph, truncated,
35
+ return { state: nodes.length ? 'recorded' : 'unknown', nodes, observations: run?.hostObservations || [], observationHealth: run?.hostObservationHealth || null, omittedObservationAttempts: run?.hostObservationOmittedAttempts || 0, topology: graph.state === 'recorded' ? 'recorded' : 'unknown', graph, truncated,
34
36
  partial: !run || truncated || nodes.length !== window.length,
35
37
  reason: !run ? 'Stable attempt observations are unavailable without a single explicitly linked controlled run.'
36
38
  : !nodes.length ? 'No usable attempt observations are recorded.' : null };
@@ -63,7 +65,7 @@ function evidenceSections(entry) {
63
65
  }
64
66
 
65
67
  export function projectWork({ projectId, issues = [], codex = { state: 'absent', runs: [] },
66
- tasks = [], sessions = [], sources = [], activity = { state: 'none', events: [] }, observedAt = new Date().toISOString() }) {
68
+ tasks = [], sessions = [], nativeGraphs = [], sources = [], activity = { state: 'none', events: [] }, observedAt = new Date().toISOString() }) {
67
69
  const entries = [];
68
70
  for (const r of codex.runs || []) {
69
71
  // Completion of a controller run is not proof of the user's acceptance criteria.
@@ -139,6 +141,20 @@ export function projectWork({ projectId, issues = [], codex = { state: 'absent',
139
141
  command: approveCommand || (enabled ? `great-cto resume --task ${t.taskId} --host ${t.host} --revision ${t.revision}` : null),
140
142
  });
141
143
  }
144
+ for (const observed of nativeGraphs) {
145
+ const linked = entries.filter(e => e.taskId && tasks.some(t => t.taskId === e.taskId && t.links.sessions.includes(observed.sessionId) && t.host === observed.host));
146
+ if (linked.length === 1) {
147
+ const entry = linked[0]; entry.execution ||= executionAttempts(null);
148
+ if (entry.execution.graph?.source !== 'host-observation' && entry.execution.topology !== 'recorded') {
149
+ entry.execution.graph = observed.graph; entry.execution.topology = 'observed';
150
+ } else (entry.execution.observations ||= []).push(observed.graph);
151
+ } else if (!linked.length) entries.push({ key: `native:${observed.host}:${observed.sessionId}`, kind: 'native', taskId: null,
152
+ runId: null, issueIds: [], title: `Observed ${observed.host} session`, goal: null, acceptance: [],
153
+ host: observed.host, phase: 'unknown', nativeState: 'observed', terminal: false, updatedAt: null,
154
+ outcome: null, reason: 'Enrolled native observations; task acceptance and execution authority are not established.',
155
+ decisions: [], capabilities: [], command: null, evidence: [], release: null,
156
+ execution: { state: observed.graph.state, nodes: [], topology: 'observed', graph: observed.graph, partial: true, observations: [] } });
157
+ }
142
158
  for (const entry of entries) entry.evidenceSections = evidenceSections(entry);
143
159
  entries.sort((a, b) => (a.terminal - b.terminal) || (b.decisions.length - a.decisions.length)
144
160
  || (Date.parse(b.updatedAt || '') || 0) - (Date.parse(a.updatedAt || '') || 0) || a.key.localeCompare(b.key));
@@ -193,8 +209,23 @@ export function getWork(cwd) {
193
209
  sessions = readSessionStatus(cwd);
194
210
  source('claude_sessions', unreadable ? 'degraded' : 'current', unreadable ? `${unreadable} session state file(s) could not be read` : null);
195
211
  } catch { source('claude_sessions', 'unavailable', 'Cannot read Claude session status'); }
212
+ let nativeGraphs = [];
213
+ try { nativeGraphs = nativeGraphObservations(cwd); source('native_graphs', nativeGraphs.some(r => r.graph.health === 'degraded') ? 'degraded' : 'current', null); }
214
+ catch { source('native_graphs', 'unavailable', 'Native graph enrollment is unreadable'); }
196
215
  let canonical = cwd;
197
216
  try { canonical = realpathSync(cwd); } catch { /* source health already reports inaccessible data */ }
198
217
  const projectId = 'project:' + createHash('sha256').update(canonical).digest('hex');
199
- return { ...projectWork({ projectId, issues, codex, tasks, sessions, sources, activity, observedAt }), projectName: basename(cwd) };
218
+ return { ...projectWork({ projectId, issues, codex, tasks, sessions, nativeGraphs, sources, activity, observedAt }), projectName: basename(cwd) };
219
+ }
220
+
221
+ /** Read-only pagination, with exact enrollment or controlled attempt membership. */
222
+ export function getHostGraphPage(root, { host, sessionId, runId = null, cursor = null }) {
223
+ if (!['codex', 'claude-code'].includes(host)) throw Error('invalid graph host');
224
+ if (!runId || runId === sessionId) return readNativeGraphPage({ root, host, sessionId, cursor });
225
+ const listing = listCodexRuns({ root });
226
+ if (listing.state === 'degraded') throw Error('controlled membership is unreadable');
227
+ const run = listing.runs.find(r => r.id === runId);
228
+ const binding = run?.hostObservationBindings.find(b => b.sessionId === sessionId && b.host === host);
229
+ if (!binding) throw Error('controlled observation does not belong to this project');
230
+ return projectHostObservations(graphBinding({ root, runId, sessionId, attemptId: binding.attemptId, host }), { cursor });
200
231
  }
@@ -50,3 +50,66 @@
50
50
  #panel-inbox .inbox-page{max-width:1500px}.nav-item:focus-visible,.admin-subtab:focus-visible,.knowledge-file:focus-visible,.work-filter:focus-visible,.work-tab:focus-visible,.work-task-row:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
51
51
  @media(max-width:1100px){.knowledge-layout,.work-cockpit{grid-template-columns:230px minmax(0,1fr)}.admin-metrics{grid-template-columns:1fr 1fr}.admin-metrics section{border-bottom:1px solid var(--border)}}
52
52
  @media(max-width:768px){.admin-page,.work-page{padding:16px!important}.knowledge-layout,.work-cockpit{grid-template-columns:minmax(0,1fr)}#knowledge-index{border-right:0;border-bottom:1px solid var(--border);max-height:270px}.work-task-list{border-right:0;border-bottom:1px solid var(--border);max-height:240px;overflow:auto}#knowledge-document,.work-detail{padding:18px}.admin-page-heading{align-items:flex-start}.admin-metrics section{padding:15px 10px}.admin-metrics h2{font-size:23px}.admin-doc-heading{align-items:flex-start;flex-direction:column}#admin-subnav{padding:10px 16px}.admin-table{min-width:530px}.work-tabs{gap:4px}}
53
+
54
+ /* Canvas focus: the selected task owns the content area, not a nested master list. */
55
+ .canvas-task .work-page{max-width:none!important;padding:18px 24px!important}
56
+ .work-list-only .work-task-list{border:1px solid var(--border);border-radius:6px;background:var(--bg-card)}
57
+ .work-list-only .work-task-row{padding:16px 18px;display:grid;grid-template-columns:minmax(180px,2fr) minmax(130px,1fr) minmax(110px,1fr);gap:16px;align-items:center}
58
+ .work-list-only .work-task-row:last-child{border-bottom:0}
59
+ .work-task-header{display:block;margin:0 0 16px;min-width:0}
60
+ .work-task-header h2{font-size:23px;line-height:1.3;letter-spacing:-.5px;margin:12px 0 8px;overflow-wrap:anywhere}
61
+ .work-task-header p{margin:8px 0;font-size:12px;color:var(--text2)}
62
+ .work-task-header button{min-height:40px;padding:8px 12px;border:1px solid var(--border-strong);border-radius:5px;background:var(--bg-card);color:var(--text);font:inherit;cursor:pointer;flex-shrink:0}
63
+ .work-detail-only,.work-workspace{min-width:0}
64
+ .work-detail-only .work-detail{padding:0}
65
+ .canvas-task .work-tabs{margin-bottom:16px}
66
+ .work-execution-flat{padding:0!important;border:0!important;background:transparent!important}
67
+ .work-execution-flat>.execution-graph{padding:0;border:0;margin:12px 0}
68
+ .execution-graph-toolbar{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-bottom:10px}
69
+ .execution-graph-toolbar h3{font-size:15px;margin:0}
70
+ .execution-graph-toolbar .execution-graph-tools{margin:0;gap:6px}
71
+ .execution-graph-tools .gate-btn{width:auto;flex:0 0 auto;min-height:40px;padding:7px 12px;font-size:12px;border:1px solid var(--border-strong);border-radius:5px;background:var(--bg-card);color:var(--text)}
72
+ .canvas-execution .execution-graph-canvas{height:clamp(360px,calc(100dvh - 340px),850px);max-height:none;border:1px solid var(--border-strong);border-radius:6px;overscroll-behavior:contain;background:var(--bg-page);touch-action:pan-x pan-y}
73
+ .canvas-task .execution-node-detail{margin:8px 0;padding:10px}
74
+ .work-execution-flat>details{margin-top:16px;border-top:1px solid var(--border);padding-top:12px}
75
+ .work-execution-flat>details>summary{min-height:40px;cursor:pointer}
76
+ .work-switcher,.work-node-inspector{color:var(--text);background:var(--bg-card);border:1px solid var(--border-strong);border-radius:8px;padding:20px;box-shadow:0 8px 32px rgb(0 0 0 / .18);max-width:calc(100vw - 24px);max-height:calc(100dvh - 32px);overflow:auto;overflow-wrap:anywhere}
77
+ .work-switcher{width:590px}
78
+ .work-switcher::backdrop{background:rgb(0 0 0 / .35)}
79
+ .work-switcher input{display:block;box-sizing:border-box;width:100%;padding:10px;margin:10px 0;border:1px solid var(--border-strong);background:var(--bg-page);color:var(--text);font:inherit;border-radius:5px}
80
+ .work-switcher button,.work-node-inspector button{min-height:40px;padding:8px 12px;background:var(--bg-card);color:var(--text);border:1px solid var(--border-strong);border-radius:5px;font:inherit;cursor:pointer}
81
+ .work-switcher [data-work-select]{display:block;width:100%;text-align:left;margin-top:8px}
82
+ .work-switcher h2,.work-node-inspector h2{font-size:18px;margin:0 0 12px}
83
+ .work-node-inspector{position:fixed;inset:260px 14px 14px auto;margin:0;width:360px;max-height:calc(100dvh - 274px);z-index:25}
84
+ .work-node-inspector .execution-node-detail{border:0;padding:0}
85
+ .work-node-inspector .execution-node-detail summary{overflow-wrap:anywhere;line-height:1.6}
86
+ .work-node-inspector dd code{white-space:normal;overflow-wrap:anywhere}
87
+ .work-node-inspector [data-graph-close]{float:right}
88
+ .canvas-nav-toggle{display:none;align-items:center;justify-content:center;min-width:40px;min-height:40px;border:1px solid var(--border-strong);border-radius:5px;background:var(--bg-card);color:var(--text);cursor:pointer}
89
+ @media(min-width:769px){
90
+ .canvas-execution .canvas-nav-toggle{display:inline-flex}
91
+ .canvas-execution:not(.canvas-navigation-expanded) .sidebar{width:64px;overflow:visible}
92
+ .canvas-execution:not(.canvas-navigation-expanded) .sidebar-brand{justify-content:center;padding:14px 10px}
93
+ .canvas-execution:not(.canvas-navigation-expanded) .brand-name,.canvas-execution:not(.canvas-navigation-expanded) #build-version,.canvas-execution:not(.canvas-navigation-expanded) .sw-meta,.canvas-execution:not(.canvas-navigation-expanded) .proj-switch>.chev,.canvas-execution:not(.canvas-navigation-expanded) .nav-label,.canvas-execution:not(.canvas-navigation-expanded) .sidebar-footer #live-label,.canvas-execution:not(.canvas-navigation-expanded) .sidebar .nav-section,.canvas-execution:not(.canvas-navigation-expanded) #nav-views-label,.canvas-execution:not(.canvas-navigation-expanded) .nav-views{display:none!important}
94
+ .canvas-execution:not(.canvas-navigation-expanded) .proj-switch{justify-content:center;padding:14px 8px}
95
+ .canvas-execution:not(.canvas-navigation-expanded) .proj-menu{left:54px;width:320px;top:0}
96
+ .canvas-execution:not(.canvas-navigation-expanded) #nav-tablist{padding:8px}
97
+ .canvas-execution:not(.canvas-navigation-expanded) #nav-tablist .nav-item{justify-content:center;position:relative;min-height:44px;padding:10px;overflow:visible}
98
+ .canvas-execution:not(.canvas-navigation-expanded) #nav-tablist .count{display:none}
99
+ .canvas-execution:not(.canvas-navigation-expanded) #nav-tablist .nav-item:is(:hover,:focus-visible)::after{content:attr(data-nav-label);position:absolute;left:50px;top:4px;z-index:50;white-space:nowrap;padding:8px 12px;border:1px solid var(--border-strong);border-radius:5px;background:var(--bg-card);color:var(--text);box-shadow:0 3px 12px rgb(0 0 0 / .12);pointer-events:none}
100
+ .canvas-execution:not(.canvas-navigation-expanded) .sidebar-footer{justify-content:center;padding:12px 8px}
101
+ }
102
+ @media(max-width:768px){
103
+ .canvas-task .work-page{padding:16px!important}
104
+ .work-task-header h2{font-size:20px}
105
+ .work-list-only .work-task-row{display:block;line-height:1.6}
106
+ .work-list-only .work-task-row small{margin-top:6px}
107
+ .work-node-inspector{inset:auto 12px 12px 12px;width:auto;max-height:65dvh}
108
+ .canvas-execution .execution-graph-canvas{height:55dvh;min-height:300px}
109
+ .canvas-execution .topbar .search-box{display:none}
110
+ .execution-graph-tools .gate-btn{min-height:44px}
111
+ }
112
+
113
+ .work-task-navigation{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.work-task-navigation>.work-meta{margin-right:auto}.canvas-task .topbar .btn-new{display:none}
114
+
115
+ .work-page .execution-graph-tools .gate-btn{background:var(--bg-card);color:var(--text);width:auto;min-height:40px}.work-node-inspector header{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px}.work-node-inspector [data-dialog-close]{flex-shrink:0}
@@ -3,6 +3,8 @@
3
3
  const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
4
4
  const aliases = { docs:'knowledge', usage:'insights/consumption', ledger:'insights/ledger', budgets:'insights/ledger', dashboard:'insights/ledger', fleet:'settings/executors', agents:'settings/executors', harness:'settings/runtime', skills:'settings/capabilities', history:'work/completed', inbox:'decisions', notifications:'settings/general', share:'settings/general' };
5
5
  function route(input) {
6
+ const taskRoute = window.GctoWork?.parseRoute?.(input);
7
+ if (taskRoute) return { canonical:taskRoute.canonical, panel:'work', primary:'work', workRoute:true };
6
8
  if (/^(?:agents|fleet)\/[a-z0-9-]+$/i.test(input)) return {canonical:input,panel:'agents',primary:'settings'};
7
9
  const canonical = aliases[input] || input;
8
10
  const panels = { work:'work','work/completed':'history',decisions:'inbox',knowledge:'knowledge',insights:'admin-outcomes','insights/outcomes':'admin-outcomes','insights/consumption':'usage','insights/ledger':'budgets',settings:'agents','settings/executors':'agents','settings/runtime':'harness','settings/capabilities':'skills','settings/general':'settings' };
@@ -76,10 +78,31 @@
76
78
  });
77
79
  } catch(error) {if(current(seq,project)&&docTicket===request) panel('knowledge-document').textContent='Document could not be read: '+error.message;}
78
80
  }
81
+ let expandedNavigation = false;
82
+ function setWorkView(view = {}) {
83
+ const detail = !!view.detail && selectedRoute.split('/')[0].split('?')[0] === 'work';
84
+ const execution = detail && view.tab === 'execution';
85
+ document.body.classList.toggle('canvas-task', detail);
86
+ document.body.classList.toggle('canvas-execution', execution);
87
+ document.body.classList.toggle('canvas-navigation-expanded', execution && expandedNavigation);
88
+ const toggle = panel('canvas-nav-toggle');
89
+ if (toggle) { toggle.hidden = !execution; toggle.setAttribute('aria-expanded', String(expandedNavigation)); toggle.setAttribute('aria-label', expandedNavigation ? 'Compact navigation' : 'Expand navigation'); toggle.title = expandedNavigation ? 'Compact navigation' : 'Expand navigation'; }
90
+ const composer = panel('work-compose')?.closest('.admin-compose');
91
+ if (composer) composer.hidden = detail;
92
+ const heading = document.querySelector('.work-page > h1');
93
+ if (heading) heading.hidden = detail;
94
+ }
95
+ function navigateWork(canonical) {
96
+ const parsed = window.GctoWork?.parseRoute?.(canonical);
97
+ if (!parsed) return;
98
+ const hash = '#/' + parsed.canonical;
99
+ if (location.hash !== hash) history.pushState(null, '', hash);
100
+ window.switchTab(parsed.canonical);
101
+ }
79
102
  function setup() {
80
103
  const nav=panel('nav-tablist'), existing={};
81
104
  document.querySelectorAll('.nav-item[data-tab]').forEach(n=>existing[n.dataset.tab]=n.querySelector('svg')?.outerHTML||'');
82
- nav.innerHTML=['work','decisions','knowledge','insights','settings'].map(key=>`<button type="button" class="nav-item${key==='work'?' active':''}" role="tab" aria-selected="${key==='work'}" data-tab="${key}" tabindex="0" onkeydown="navTabKeydown(event,this)">${existing[key]||existing[key==='knowledge'?'skills':key==='insights'?'usage':'harness']||''}<span class="nav-label">${key[0].toUpperCase()+key.slice(1)}</span>${key==='decisions'?'<span class="count" id="nav-inbox-count">0</span>':''}</button>`).join('');
105
+ nav.innerHTML=['work','decisions','knowledge','insights','settings'].map(key=>`<button type="button" class="nav-item${key==='work'?' active':''}" role="tab" aria-selected="${key==='work'}" data-tab="${key}" aria-label="${key[0].toUpperCase()+key.slice(1)}" data-nav-label="${key[0].toUpperCase()+key.slice(1)}" tabindex="0" onkeydown="navTabKeydown(event,this)">${existing[key]||existing[key==='knowledge'?'skills':key==='insights'?'usage':'harness']||''}<span class="nav-label">${key[0].toUpperCase()+key.slice(1)}</span>${key==='decisions'?'<span class="count" id="nav-inbox-count">0</span>':''}</button>`).join('');
83
106
  nav.querySelectorAll('[data-tab]').forEach(n=>n.addEventListener('click',()=>window.switchTab(n.dataset.tab)));
84
107
  const tools=panel('tools-nav');tools.hidden=true;
85
108
  const workspace=document.querySelector('.workspace');
@@ -88,6 +111,14 @@
88
111
  panel('knowledge-reload').addEventListener('click',()=>{generation++;loadKnowledge();});
89
112
  const insights=document.createElement('div');insights.id='panel-admin-outcomes';insights.className='panel';insights.setAttribute('role','tabpanel');insights.innerHTML='<div class="admin-page"><header class="admin-page-heading"><div><h1>Insights</h1><p>Recorded outcomes are separate from token consumption.</p></div><button type="button" class="gate-btn" id="outcomes-reload">Reload</button></header><div id="admin-outcomes-body" aria-live="polite"></div></div>';workspace.append(insights);
90
113
  panel('outcomes-reload').addEventListener('click',loadTaskOutcomes);
114
+ const toggle = document.createElement('button');
115
+ toggle.id = 'canvas-nav-toggle'; toggle.type = 'button'; toggle.className = 'canvas-nav-toggle'; toggle.hidden = true;
116
+ toggle.setAttribute('aria-controls', 'nav-tablist'); toggle.innerHTML = '<span aria-hidden="true">☰</span>';
117
+ workspace.querySelector('.topbar').prepend(toggle);
118
+ toggle.addEventListener('click', () => { expandedNavigation = !expandedNavigation; setWorkView({detail:true, tab:'execution'}); });
119
+ const projectSwitch = panel('proj-switch');
120
+ projectSwitch.setAttribute('role','button'); projectSwitch.tabIndex = 0; projectSwitch.setAttribute('aria-label','Choose project');
121
+ projectSwitch.addEventListener('keydown', event => { if (event.target === projectSwitch && ['Enter',' '].includes(event.key)) { event.preventDefault(); toggleProjMenu(event); } });
91
122
  const original=window.switchTab;
92
123
  window.switchTab=function(id,el){
93
124
  const agentLink=String(id).match(/^(?:agents|fleet)\/([a-z0-9-]+)$/i);
@@ -98,16 +129,19 @@
98
129
  open(0);return;
99
130
  }
100
131
  const r=route(id);
101
- if(!r) {bar.hidden=true;return original(id,el);}
132
+ if(!r) {window.GctoWork.leaveView?.();setWorkView({detail:false});bar.hidden=true;return original(id,el);}
102
133
  selectedRoute=r.canonical;
134
+ if (r.workRoute) { window.GctoWork.navigateRoute(r.canonical); const input = panel('search-input'); if(input) input.value = window.GctoWork.parseRoute(r.canonical).search; }
135
+ else { window.GctoWork.leaveView?.(); setWorkView({detail:false}); }
103
136
  const old={inbox:'decisions',budgets:'ledger',agents:'fleet','admin-outcomes':'admin-outcomes',knowledge:'knowledge'}[r.panel]||r.panel;
104
- original(old); // Keep established loaders and mutation guards.
137
+ if (r.workRoute && typeof currentTab === 'string' && currentTab === 'work') { closeMenu(false); window.GctoWork.refresh(); }
138
+ else original(old); // Keep established loaders and mutation guards; task inspection adds no view POST.
105
139
  document.querySelectorAll('.nav-item[data-tab]').forEach(n=>{const active=n.closest('#nav-tablist')&&n.dataset.tab===r.primary;n.classList.toggle('active',!!active);n.setAttribute('aria-selected',String(!!active));});
106
140
  panel('crumb-here').textContent=r.primary[0].toUpperCase()+r.primary.slice(1);
107
141
  const items=r.primary==='settings'?[['settings/executors','Executors'],['settings/runtime','Runtime'],['settings/capabilities','Capabilities'],['settings/general','General']]:r.primary==='insights'?[['insights/outcomes','Outcomes'],['insights/consumption','Consumption'],['insights/ledger','Ledger & budgets']]:[];
108
142
  bar.hidden=!items.length;bar.innerHTML=items.map(([key,label])=>`<button type="button" class="admin-subtab" data-route="${key}" aria-pressed="${r.canonical===key||(r.canonical===r.primary&&key===items[0][0])}">${label}</button>`).join('');
109
143
  bar.querySelectorAll('[data-route]').forEach(b=>b.onclick=()=>window.switchTab(b.dataset.route));
110
- history.replaceState(null,'','#/'+r.canonical);
144
+ history.replaceState(history.state,'','#/'+r.canonical);
111
145
  if(r.primary==='knowledge')loadKnowledge();
112
146
  if(r.panel==='admin-outcomes')loadTaskOutcomes();
113
147
  // Restore a working route after projection errors; an error is not an empty list.
@@ -117,6 +151,6 @@
117
151
  composer.querySelector('button[type="submit"]').textContent='Copy launch command';
118
152
  window.switchTab((location.hash||'#/work').replace(/^#\/?/,''));
119
153
  }
120
- window.GctoAdmin={route,outcomes,reset,openDocument};
154
+ window.GctoAdmin={route,outcomes,reset,openDocument,setWorkView,navigateWork};
121
155
  if(typeof document!=='undefined') setup();
122
156
  })();