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.
- package/README.md +9 -1
- package/board/.claude-plugin/plugin.json +93 -1
- package/board/packages/board/lib/routes.mjs +14 -1
- package/board/packages/board/lib/work.mjs +34 -3
- package/board/packages/board/public/assets/admin-shell.css +63 -0
- package/board/packages/board/public/assets/admin-shell.js +39 -5
- package/board/packages/board/public/assets/work-view.js +320 -37
- package/board/packages/board/public/index.html +43 -1
- package/board/scripts/codex-pipeline.mjs +7 -1
- package/board/scripts/lib/claude-exec.mjs +49 -6
- package/board/scripts/lib/codex-exec.mjs +42 -18
- package/board/scripts/lib/codex-host-state.mjs +18 -2
- package/board/scripts/lib/codex-pipeline.mjs +44 -7
- package/board/scripts/lib/host-graph-bindings.mjs +135 -0
- package/board/scripts/lib/host-graph-events.mjs +157 -0
- package/board/scripts/lib/host-graph-journal.mjs +210 -0
- package/board/scripts/lib/host-graph-observer.mjs +61 -0
- package/board/scripts/lib/host-graph-runtime.mjs +38 -0
- package/board/scripts/work-task.mjs +17 -4
- package/dist/adapt.js +10 -21
- package/dist/codex.js +4 -15
- package/dist/companion.js +6 -122
- package/dist/installer.js +4 -72
- package/dist/main.js +51 -118
- package/dist/release-activation.mjs +561 -0
- package/dist/release-archive.mjs +165 -0
- package/dist/release-trust.mjs +514 -0
- package/dist/release-update.mjs +303 -0
- package/dist/self-upgrade.js +10 -39
- package/dist/update-check.js +1 -0
- package/dist/upgrade.js +5 -120
- package/package.json +1 -1
- package/board/packages/board/fixtures/beads-read-owner.mjs +0 -3
- package/board/packages/board/fixtures/fake-bd-controlled.mjs +0 -55
- package/board/packages/board/fixtures/fake-bd-fail.sh +0 -9
- package/board/packages/board/fixtures/fake-bd-slow.sh +0 -12
- 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
|
-
>
|
|
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.
|
|
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 => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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
|
-
|
|
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(
|
|
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
|
})();
|