@jmanuelcorral/openteam 0.1.41 → 0.1.42
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.es.md +1 -1
- package/README.md +1 -1
- package/dist/cli.js +419 -36
- package/dist/commands/graph.d.ts +8 -0
- package/dist/commands/graph.d.ts.map +1 -1
- package/dist/config/schema.d.ts +18 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/console/assets.d.ts +9 -1
- package/dist/console/assets.d.ts.map +1 -1
- package/dist/console/opencodeClient.d.ts +6 -0
- package/dist/console/opencodeClient.d.ts.map +1 -1
- package/dist/console/protocol.d.ts +8 -0
- package/dist/console/protocol.d.ts.map +1 -1
- package/dist/console/render.d.ts +25 -0
- package/dist/console/render.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/orchestrator/worktreeAdapter.d.ts +39 -0
- package/dist/orchestrator/worktreeAdapter.d.ts.map +1 -0
- package/dist/orchestrator/worktreeDispatch.d.ts +84 -0
- package/dist/orchestrator/worktreeDispatch.d.ts.map +1 -0
- package/dist/orchestrator/worktreeReconciler.d.ts +74 -0
- package/dist/orchestrator/worktreeReconciler.d.ts.map +1 -0
- package/dist/storage/graph/worktreeRegistry.d.ts +29 -0
- package/dist/storage/graph/worktreeRegistry.d.ts.map +1 -0
- package/dist/telemetry/graphView.d.ts +75 -0
- package/dist/telemetry/graphView.d.ts.map +1 -0
- package/dist/web/console.d.ts +6 -0
- package/dist/web/console.d.ts.map +1 -1
- package/dist/web/graphRoute.d.ts +36 -0
- package/dist/web/graphRoute.d.ts.map +1 -0
- package/dist/web/server.d.ts +21 -2
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/start.d.ts +11 -0
- package/dist/web/start.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -33,6 +33,50 @@ function eventSessionID(event) {
|
|
|
33
33
|
}
|
|
34
34
|
return str(props.sessionID) ?? str(asRecord(props.part)?.sessionID) ?? str(asRecord(props.info)?.sessionID);
|
|
35
35
|
}
|
|
36
|
+
function partToFrame(part) {
|
|
37
|
+
if (part.type === "text") {
|
|
38
|
+
const text = str(part.text);
|
|
39
|
+
return text === undefined ? undefined : { kind: "text", text };
|
|
40
|
+
}
|
|
41
|
+
if (part.type === "tool") {
|
|
42
|
+
const tool = str(part.tool) ?? "tool";
|
|
43
|
+
const status = str(asRecord(part.state)?.status) ?? "run";
|
|
44
|
+
return { kind: "tool", text: `⚙ ${tool} · ${status}` };
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
function messagesToFrames(raw, sessionID) {
|
|
49
|
+
if (!Array.isArray(raw)) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
const frames = [];
|
|
53
|
+
for (const entry of raw) {
|
|
54
|
+
const record = asRecord(entry);
|
|
55
|
+
if (record === undefined) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const info = asRecord(record.info);
|
|
59
|
+
const entrySession = str(info?.sessionID) ?? str(record.sessionID);
|
|
60
|
+
if (entrySession !== undefined && entrySession !== sessionID) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const parts = record.parts;
|
|
64
|
+
if (!Array.isArray(parts)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const rawPart of parts) {
|
|
68
|
+
const part = asRecord(rawPart);
|
|
69
|
+
if (part === undefined) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const frame = partToFrame(part);
|
|
73
|
+
if (frame !== undefined) {
|
|
74
|
+
frames.push(frame);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return frames;
|
|
79
|
+
}
|
|
36
80
|
function eventToSessionFrame(event, sessionID) {
|
|
37
81
|
if (eventSessionID(event) !== sessionID) {
|
|
38
82
|
return;
|
|
@@ -41,19 +85,7 @@ function eventToSessionFrame(event, sessionID) {
|
|
|
41
85
|
switch (event.type) {
|
|
42
86
|
case "message.part.updated": {
|
|
43
87
|
const part = asRecord(props.part);
|
|
44
|
-
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (part.type === "text") {
|
|
48
|
-
const text = str(part.text);
|
|
49
|
-
return text === undefined ? undefined : { kind: "text", text };
|
|
50
|
-
}
|
|
51
|
-
if (part.type === "tool") {
|
|
52
|
-
const tool = str(part.tool) ?? "tool";
|
|
53
|
-
const status = str(asRecord(part.state)?.status) ?? "run";
|
|
54
|
-
return { kind: "tool", text: `⚙ ${tool} · ${status}` };
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
88
|
+
return part === undefined ? undefined : partToFrame(part);
|
|
57
89
|
}
|
|
58
90
|
case "session.idle":
|
|
59
91
|
return { kind: "status", text: "● sesión lista" };
|
|
@@ -163,6 +195,16 @@ function createConsoleClient(deps) {
|
|
|
163
195
|
async respondPermission(sessionID, permissionID, response) {
|
|
164
196
|
await post(`/session/${encodeURIComponent(sessionID)}/permissions/${encodeURIComponent(permissionID)}`, { response });
|
|
165
197
|
},
|
|
198
|
+
async listMessages(sessionID) {
|
|
199
|
+
const res = await doFetch(`${base}/session/${encodeURIComponent(sessionID)}/message`, { headers: { accept: "application/json", ...authHeaders } });
|
|
200
|
+
let raw;
|
|
201
|
+
try {
|
|
202
|
+
raw = await res.json();
|
|
203
|
+
} catch {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
return messagesToFrames(raw, sessionID);
|
|
207
|
+
},
|
|
166
208
|
async streamFrames(sessionID, onFrame, signal) {
|
|
167
209
|
const res = await doFetch(`${base}/global/event`, {
|
|
168
210
|
headers: { accept: "text/event-stream", ...authHeaders },
|
|
@@ -631,7 +673,10 @@ var BaselineModeSchema = z3.enum(["auto", "pinned"]);
|
|
|
631
673
|
var GraphModeSchema = z3.enum(["off", "shadow", "active"]);
|
|
632
674
|
var GraphConfigSchema = z3.object({
|
|
633
675
|
mode: GraphModeSchema.default("off"),
|
|
634
|
-
killSwitch: z3.boolean().default(false)
|
|
676
|
+
killSwitch: z3.boolean().default(false),
|
|
677
|
+
worktrees: z3.object({
|
|
678
|
+
enabled: z3.boolean().default(false)
|
|
679
|
+
}).default({ enabled: false })
|
|
635
680
|
});
|
|
636
681
|
var ConsoleHostSchema = z3.enum(["127.0.0.1", "localhost"]);
|
|
637
682
|
var ConsoleConfigSchema = z3.object({
|
|
@@ -645,7 +690,10 @@ var ConsoleConfigSchema = z3.object({
|
|
|
645
690
|
terminal: z3.object({
|
|
646
691
|
enabled: z3.boolean().default(true),
|
|
647
692
|
pty: z3.boolean().optional()
|
|
648
|
-
}).default({ enabled: true })
|
|
693
|
+
}).default({ enabled: true }),
|
|
694
|
+
graphView: z3.object({
|
|
695
|
+
enabled: z3.boolean().default(false)
|
|
696
|
+
}).optional()
|
|
649
697
|
}).default({
|
|
650
698
|
host: "127.0.0.1",
|
|
651
699
|
port: 4599,
|
|
@@ -1299,6 +1347,51 @@ code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px
|
|
|
1299
1347
|
.pty form.ptyinput button,.pty .pty-start{background:var(--frontier);color:#1a1200;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600;margin-top:8px}
|
|
1300
1348
|
.pty .pty-start:disabled{opacity:.5;cursor:default}
|
|
1301
1349
|
`;
|
|
1350
|
+
var GRAPH_STYLE = `
|
|
1351
|
+
.graph-wrap{overflow:auto;background:#0b0d11;border:1px solid var(--line);border-radius:8px;padding:8px}
|
|
1352
|
+
svg.graph{display:block}
|
|
1353
|
+
.gedge{stroke:#3a4250;stroke-width:1.5;marker-end:url(#garrow)}
|
|
1354
|
+
.gnode rect{fill:var(--panel);stroke:var(--line);stroke-width:1.5;rx:8}
|
|
1355
|
+
.gnode .gname{fill:var(--fg);font:600 12px ui-monospace,Consolas,monospace}
|
|
1356
|
+
.gnode .grole{fill:var(--muted);font-size:11px}
|
|
1357
|
+
.gnode .gstate{font-size:10px;text-transform:uppercase;letter-spacing:.05em}
|
|
1358
|
+
.gnode.state-pending rect{stroke:#6e7681}
|
|
1359
|
+
.gnode.state-pending .gstate{fill:#6e7681}
|
|
1360
|
+
.gnode.state-ready rect{stroke:var(--local)}
|
|
1361
|
+
.gnode.state-ready .gstate{fill:var(--local)}
|
|
1362
|
+
.gnode.state-active rect{stroke:var(--frontier)}
|
|
1363
|
+
.gnode.state-active .gstate{fill:var(--frontier)}
|
|
1364
|
+
.gnode.state-succeeded rect{stroke:var(--good)}
|
|
1365
|
+
.gnode.state-succeeded .gstate{fill:var(--good)}
|
|
1366
|
+
.gnode.state-failed rect{stroke:#f85149}
|
|
1367
|
+
.gnode.state-failed .gstate{fill:#f85149}
|
|
1368
|
+
.gnode.state-cancelled rect{stroke:#484f58}
|
|
1369
|
+
.gnode.state-cancelled .gstate{fill:#484f58}
|
|
1370
|
+
.gbadge{font-size:10px;font-family:ui-monospace,Consolas,monospace}
|
|
1371
|
+
.gbadge.badge-retry{fill:var(--frontier)}
|
|
1372
|
+
.gbadge.badge-error{fill:#f85149}
|
|
1373
|
+
.gbadge.badge-cancelled{fill:#484f58}
|
|
1374
|
+
.gbadge.badge-verdict.verdict-approved{fill:var(--good)}
|
|
1375
|
+
.gbadge.badge-verdict.verdict-rejected{fill:#f85149}
|
|
1376
|
+
.gbadge.badge-verdict.verdict-inconclusive{fill:var(--frontier)}
|
|
1377
|
+
.gbadge.badge-lineage{fill:var(--local)}
|
|
1378
|
+
.gbadge.badge-artifact{fill:var(--muted)}
|
|
1379
|
+
.gnode[data-session]{cursor:pointer}
|
|
1380
|
+
.gnode[data-session]:hover rect{stroke-width:2.5}
|
|
1381
|
+
.graph-health{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 12px}
|
|
1382
|
+
.gchip{font-size:11px;padding:2px 10px;border-radius:10px;border:1px solid var(--line);color:var(--muted)}
|
|
1383
|
+
.gchip.on{font-weight:600}
|
|
1384
|
+
.gchip.gh-recovery.on,.gchip.gh-unknown.on{color:var(--frontier);border-color:var(--frontier)}
|
|
1385
|
+
.gchip.gh-conflict.on,.gchip.gh-kill.on{color:#f85149;border-color:#f85149}
|
|
1386
|
+
.graph-drill{margin-top:14px;border-top:1px solid var(--line);padding-top:12px}
|
|
1387
|
+
.graph-drill h3{font-size:13px;margin:0 0 8px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
|
|
1388
|
+
.graph-drill .drilllog{background:#0b0d11;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:280px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap}
|
|
1389
|
+
.graph-drill .drilllog .tl{padding:1px 0}
|
|
1390
|
+
.graph-drill .drilllog .tl.tool{color:var(--frontier)}
|
|
1391
|
+
.graph-drill .drilllog .tl.status{color:var(--muted)}
|
|
1392
|
+
.graph-drill .drilllog .tl.permission{color:var(--frontier);font-weight:600}
|
|
1393
|
+
.graph-drill .drilllog .tl.error{color:#f85149}
|
|
1394
|
+
`;
|
|
1302
1395
|
var CONSOLE_CLIENT_JS = `
|
|
1303
1396
|
(function(){
|
|
1304
1397
|
var authTok='';
|
|
@@ -1424,6 +1517,47 @@ var CONSOLE_CLIENT_JS = `
|
|
|
1424
1517
|
');startBtn.disabled=false}
|
|
1425
1518
|
}
|
|
1426
1519
|
}
|
|
1520
|
+
function wireGraphDrill(){
|
|
1521
|
+
var root=document.documentElement;
|
|
1522
|
+
if(root.getAttribute('data-drilldown')!=='1'){return}
|
|
1523
|
+
var token=root.getAttribute('data-token');
|
|
1524
|
+
if(!token){return}
|
|
1525
|
+
var drill=document.querySelector('.graph-drill');
|
|
1526
|
+
if(!drill){return}
|
|
1527
|
+
var gview=drill.closest('.view');
|
|
1528
|
+
if(!gview){return}
|
|
1529
|
+
var log=drill.querySelector('.drilllog');
|
|
1530
|
+
var title=drill.querySelector('.drill-title');
|
|
1531
|
+
if(!log){return}
|
|
1532
|
+
var es=null;
|
|
1533
|
+
function append(cls,text){
|
|
1534
|
+
var line=document.createElement('div');
|
|
1535
|
+
line.className='tl '+cls;line.textContent=text;
|
|
1536
|
+
log.appendChild(line);log.scrollTop=log.scrollHeight;
|
|
1537
|
+
}
|
|
1538
|
+
function open(id){
|
|
1539
|
+
if(es){try{es.close()}catch(_){}}
|
|
1540
|
+
log.textContent='';drill.hidden=false;
|
|
1541
|
+
if(title){title.textContent='Conversación · '+id}
|
|
1542
|
+
var tk='token='+encodeURIComponent(token);
|
|
1543
|
+
var base='/console/session/'+encodeURIComponent(id);
|
|
1544
|
+
fetch(base+'/messages?'+tk).then(function(r){return r.ok?r.json():Promise.reject(r.status)})
|
|
1545
|
+
.then(function(d){(d&&d.frames||[]).forEach(function(f){append(f.kind,f.text)})})
|
|
1546
|
+
.catch(function(s){append('error','✖ no se pudo cargar la conversación ('+s+')')});
|
|
1547
|
+
if('EventSource' in window){
|
|
1548
|
+
try{
|
|
1549
|
+
es=new EventSource(base+'/stream?'+tk);
|
|
1550
|
+
es.addEventListener('frame',function(e){try{var f=JSON.parse(e.data);append(f.kind,f.text)}catch(_){}});
|
|
1551
|
+
es.onerror=function(){/* reconecta solo */};
|
|
1552
|
+
}catch(_){/* ignore */}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
gview.addEventListener('click',function(e){
|
|
1556
|
+
var t=e.target;
|
|
1557
|
+
while(t&&t!==gview&&!(t.getAttribute&&t.getAttribute('data-session'))){t=t.parentNode}
|
|
1558
|
+
if(t&&t.getAttribute&&t.getAttribute('data-session')){open(t.getAttribute('data-session'))}
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1427
1561
|
var bar=document.getElementById('tabbar');
|
|
1428
1562
|
if(bar){
|
|
1429
1563
|
bar.addEventListener('click',function(e){
|
|
@@ -1435,6 +1569,7 @@ var CONSOLE_CLIENT_JS = `
|
|
|
1435
1569
|
var saved='all';
|
|
1436
1570
|
try{saved=sessionStorage.getItem('openteam.tab')||'all'}catch(e){}
|
|
1437
1571
|
if(!selectTab(saved)){selectTab('all')}
|
|
1572
|
+
wireGraphDrill();
|
|
1438
1573
|
})();
|
|
1439
1574
|
`;
|
|
1440
1575
|
|
|
@@ -1684,14 +1819,15 @@ var STATUS_LABELS = {
|
|
|
1684
1819
|
function statusBadge(status) {
|
|
1685
1820
|
return `<span class="badge status-${status}">${STATUS_LABELS[status]}</span>`;
|
|
1686
1821
|
}
|
|
1687
|
-
function tabBar(sessions) {
|
|
1822
|
+
function tabBar(sessions, graphEnabled) {
|
|
1688
1823
|
const allTab = '<button type="button" class="tab" data-tab="all">Todas</button>';
|
|
1824
|
+
const graphTab = graphEnabled ? '<button type="button" class="tab" data-tab="graph">Grafo</button>' : "";
|
|
1689
1825
|
const tabs = sessions.map((tab) => {
|
|
1690
1826
|
const id = escapeHtml(tab.sessionID);
|
|
1691
1827
|
const short = escapeHtml(shortSessionID(tab.sessionID));
|
|
1692
1828
|
return `<button type="button" class="tab" data-tab="${id}"><span class="dot status-${tab.status}"></span>${short}</button>`;
|
|
1693
1829
|
}).join("");
|
|
1694
|
-
return `<nav class="tabs" id="tabbar">${allTab}${tabs}</nav>`;
|
|
1830
|
+
return `<nav class="tabs" id="tabbar">${allTab}${graphTab}${tabs}</nav>`;
|
|
1695
1831
|
}
|
|
1696
1832
|
function shortSessionID(sessionID) {
|
|
1697
1833
|
return sessionID.length <= 12 ? sessionID : `${sessionID.slice(0, 12)}…`;
|
|
@@ -1753,10 +1889,182 @@ function sessionView(tab, terminalEnabled, ptyEnabled) {
|
|
|
1753
1889
|
].join("");
|
|
1754
1890
|
return `<div class="view" data-view="${escapeHtml(tab.sessionID)}" hidden>${body}</div>`;
|
|
1755
1891
|
}
|
|
1892
|
+
var GRAPH_COL_W = 220;
|
|
1893
|
+
var GRAPH_ROW_H = 96;
|
|
1894
|
+
var GRAPH_NODE_W = 180;
|
|
1895
|
+
var GRAPH_NODE_H = 64;
|
|
1896
|
+
var GRAPH_PAD = 20;
|
|
1897
|
+
function computeLayers(projection) {
|
|
1898
|
+
const layer = new Map;
|
|
1899
|
+
for (const node of projection.nodes) {
|
|
1900
|
+
layer.set(node.nodeID, 0);
|
|
1901
|
+
}
|
|
1902
|
+
for (let pass = 0;pass < projection.nodes.length; pass += 1) {
|
|
1903
|
+
let changed = false;
|
|
1904
|
+
for (const edge of projection.edges) {
|
|
1905
|
+
const from = layer.get(edge.from);
|
|
1906
|
+
const to = layer.get(edge.to);
|
|
1907
|
+
if (from === undefined || to === undefined) {
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
if (from + 1 > to) {
|
|
1911
|
+
layer.set(edge.to, from + 1);
|
|
1912
|
+
changed = true;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
if (!changed) {
|
|
1916
|
+
break;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
return layer;
|
|
1920
|
+
}
|
|
1921
|
+
function layoutNodes(projection) {
|
|
1922
|
+
const layers = computeLayers(projection);
|
|
1923
|
+
const byLayer = [];
|
|
1924
|
+
for (const node of projection.nodes) {
|
|
1925
|
+
const l = layers.get(node.nodeID) ?? 0;
|
|
1926
|
+
let bucket = byLayer[l];
|
|
1927
|
+
if (bucket === undefined) {
|
|
1928
|
+
bucket = [];
|
|
1929
|
+
byLayer[l] = bucket;
|
|
1930
|
+
}
|
|
1931
|
+
bucket.push(node.nodeID);
|
|
1932
|
+
}
|
|
1933
|
+
const pos = new Map;
|
|
1934
|
+
let maxLayer = 0;
|
|
1935
|
+
let maxRows = 0;
|
|
1936
|
+
byLayer.forEach((ids, l) => {
|
|
1937
|
+
if (l > maxLayer) {
|
|
1938
|
+
maxLayer = l;
|
|
1939
|
+
}
|
|
1940
|
+
if (ids.length > maxRows) {
|
|
1941
|
+
maxRows = ids.length;
|
|
1942
|
+
}
|
|
1943
|
+
ids.forEach((id, row) => {
|
|
1944
|
+
pos.set(id, {
|
|
1945
|
+
x: GRAPH_PAD + l * GRAPH_COL_W,
|
|
1946
|
+
y: GRAPH_PAD + row * GRAPH_ROW_H
|
|
1947
|
+
});
|
|
1948
|
+
});
|
|
1949
|
+
});
|
|
1950
|
+
const width = GRAPH_PAD * 2 + maxLayer * GRAPH_COL_W + GRAPH_NODE_W;
|
|
1951
|
+
const height = GRAPH_PAD * 2 + Math.max(0, maxRows - 1) * GRAPH_ROW_H + GRAPH_NODE_H;
|
|
1952
|
+
return { pos, width, height };
|
|
1953
|
+
}
|
|
1954
|
+
function nodeBadges(node) {
|
|
1955
|
+
const badges = [];
|
|
1956
|
+
if (node.attempts > 1) {
|
|
1957
|
+
badges.push({ cls: "badge-retry", text: `⟳${node.attempts}` });
|
|
1958
|
+
}
|
|
1959
|
+
if (node.lastErrorClass !== undefined) {
|
|
1960
|
+
badges.push({
|
|
1961
|
+
cls: `badge-error err-${node.lastErrorClass}`,
|
|
1962
|
+
text: `⚠${node.lastErrorClass}`
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
if (node.state === "cancelled") {
|
|
1966
|
+
badges.push({ cls: "badge-cancelled", text: "⊘" });
|
|
1967
|
+
}
|
|
1968
|
+
if (node.review !== undefined) {
|
|
1969
|
+
const symbol = node.review.verdict === "approved" ? "✓" : node.review.verdict === "rejected" ? "✗" : "?";
|
|
1970
|
+
badges.push({
|
|
1971
|
+
cls: `badge-verdict verdict-${node.review.verdict}`,
|
|
1972
|
+
text: node.review.reassigned ? `${symbol}↔` : symbol
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
if (node.revisionOf !== undefined) {
|
|
1976
|
+
badges.push({
|
|
1977
|
+
cls: "badge-lineage",
|
|
1978
|
+
text: `↺${escapeHtml(node.revisionOf)}`
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
if (node.artifact !== undefined) {
|
|
1982
|
+
badges.push({ cls: "badge-artifact", text: "⧉" });
|
|
1983
|
+
}
|
|
1984
|
+
return badges;
|
|
1985
|
+
}
|
|
1986
|
+
function nodeSvg(node, at) {
|
|
1987
|
+
const badges = nodeBadges(node).map((badge, index) => `<text class="gbadge ${badge.cls}" x="${10 + index * 34}" y="58">${badge.text}</text>`).join("");
|
|
1988
|
+
const sessionAttr = node.sessionRef === undefined ? "" : ` data-session="${escapeHtml(node.sessionRef)}"`;
|
|
1989
|
+
return [
|
|
1990
|
+
`<g class="gnode state-${node.state}" data-node="${escapeHtml(node.nodeID)}" data-state="${node.state}" data-attempts="${node.attempts}"${sessionAttr} transform="translate(${at.x},${at.y})">`,
|
|
1991
|
+
`<rect width="${GRAPH_NODE_W}" height="${GRAPH_NODE_H}"></rect>`,
|
|
1992
|
+
`<text class="gname" x="10" y="20">${escapeHtml(node.nodeID)}</text>`,
|
|
1993
|
+
`<text class="grole" x="10" y="36">${escapeHtml(node.role)}</text>`,
|
|
1994
|
+
`<text class="gstate" x="10" y="50">${node.state}</text>`,
|
|
1995
|
+
badges,
|
|
1996
|
+
"</g>"
|
|
1997
|
+
].join("");
|
|
1998
|
+
}
|
|
1999
|
+
function edgeSvg(pos, from, to) {
|
|
2000
|
+
const a = pos.get(from);
|
|
2001
|
+
const b = pos.get(to);
|
|
2002
|
+
if (a === undefined || b === undefined) {
|
|
2003
|
+
return "";
|
|
2004
|
+
}
|
|
2005
|
+
const x1 = a.x + GRAPH_NODE_W;
|
|
2006
|
+
const y1 = a.y + GRAPH_NODE_H / 2;
|
|
2007
|
+
const x2 = b.x;
|
|
2008
|
+
const y2 = b.y + GRAPH_NODE_H / 2;
|
|
2009
|
+
return `<line class="gedge" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" data-from="${escapeHtml(from)}" data-to="${escapeHtml(to)}"></line>`;
|
|
2010
|
+
}
|
|
2011
|
+
function renderGraphView(projection) {
|
|
2012
|
+
if (projection === null) {
|
|
2013
|
+
return '<p class="muted">Sin run de grafo activo.</p>';
|
|
2014
|
+
}
|
|
2015
|
+
if (projection.nodes.length === 0) {
|
|
2016
|
+
return '<p class="muted">El run no tiene nodos.</p>';
|
|
2017
|
+
}
|
|
2018
|
+
const { pos, width, height } = layoutNodes(projection);
|
|
2019
|
+
const edges = projection.edges.map((edge) => edgeSvg(pos, edge.from, edge.to)).join("");
|
|
2020
|
+
const nodes = projection.nodes.map((node) => {
|
|
2021
|
+
const at = pos.get(node.nodeID);
|
|
2022
|
+
return at === undefined ? "" : nodeSvg(node, at);
|
|
2023
|
+
}).join("");
|
|
2024
|
+
return [
|
|
2025
|
+
`<div class="graph-wrap"><svg class="graph" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}" role="img" aria-label="Grafo de agentes">`,
|
|
2026
|
+
'<defs><marker id="garrow" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0 L8 4 L0 8 z" fill="#3a4250"></path></marker></defs>',
|
|
2027
|
+
edges,
|
|
2028
|
+
nodes,
|
|
2029
|
+
"</svg></div>"
|
|
2030
|
+
].join("");
|
|
2031
|
+
}
|
|
2032
|
+
function healthBanner(health) {
|
|
2033
|
+
const chip = (cls, on, label) => `<span class="gchip ${cls} ${on ? "on" : "off"}">${label}</span>`;
|
|
2034
|
+
return [
|
|
2035
|
+
'<div class="graph-health" role="status">',
|
|
2036
|
+
chip("gh-recovery", health.recoveryRequired, health.recoveryRequired ? "recuperación requerida" : "sin recuperación"),
|
|
2037
|
+
chip("gh-unknown", health.unknownEffects > 0, `efectos desconocidos: ${health.unknownEffects}`),
|
|
2038
|
+
chip("gh-conflict", health.conflicts > 0, `conflictos lease/worktree: ${health.conflicts}`),
|
|
2039
|
+
chip("gh-kill", health.killSwitch, health.killSwitch ? "kill switch activo" : "kill switch inactivo"),
|
|
2040
|
+
"</div>"
|
|
2041
|
+
].join("");
|
|
2042
|
+
}
|
|
2043
|
+
function drillPanel() {
|
|
2044
|
+
return [
|
|
2045
|
+
'<aside class="graph-drill" hidden>',
|
|
2046
|
+
'<h3 class="drill-title">Conversación</h3>',
|
|
2047
|
+
'<div class="drilllog" aria-live="polite"></div>',
|
|
2048
|
+
"</aside>"
|
|
2049
|
+
].join("");
|
|
2050
|
+
}
|
|
2051
|
+
function graphViewPanel(projection, drilldownEnabled) {
|
|
2052
|
+
return [
|
|
2053
|
+
'<div class="view" data-view="graph" hidden>',
|
|
2054
|
+
'<section class="panel" id="panel-graph">',
|
|
2055
|
+
"<h2>Grafo de agentes</h2>",
|
|
2056
|
+
projection === null ? "" : healthBanner(projection.health),
|
|
2057
|
+
renderGraphView(projection),
|
|
2058
|
+
drilldownEnabled ? drillPanel() : "",
|
|
2059
|
+
"</section>",
|
|
2060
|
+
"</div>"
|
|
2061
|
+
].join("");
|
|
2062
|
+
}
|
|
1756
2063
|
function renderConsoleHtml(state, options = {}) {
|
|
1757
2064
|
const refreshMs = options.refreshMs ?? 2000;
|
|
1758
2065
|
const terminalEnabled = options.terminalEnabled === true;
|
|
1759
2066
|
const ptyEnabled = options.ptyEnabled === true;
|
|
2067
|
+
const graphEnabled = options.graphView !== undefined;
|
|
1760
2068
|
const allBody = [
|
|
1761
2069
|
summaryPanel(state.cost),
|
|
1762
2070
|
totalsPanel(state.totals),
|
|
@@ -1770,10 +2078,12 @@ function renderConsoleHtml(state, options = {}) {
|
|
|
1770
2078
|
activityPanel(state.activity)
|
|
1771
2079
|
].join("");
|
|
1772
2080
|
const allView = `<div class="view" data-view="all">${allBody}</div>`;
|
|
2081
|
+
const graphDrilldown = graphEnabled && options.drilldownEnabled === true;
|
|
2082
|
+
const graphView = graphEnabled ? graphViewPanel(options.graphView?.projection ?? null, graphDrilldown) : "";
|
|
1773
2083
|
const sessionViews = state.sessionsDetail.map((tab) => sessionView(tab, terminalEnabled, ptyEnabled)).join("");
|
|
1774
|
-
const body = allView + sessionViews;
|
|
2084
|
+
const body = allView + graphView + sessionViews;
|
|
1775
2085
|
const session = state.session.id === undefined ? "" : ` · sesión ${escapeHtml(state.session.id)}`;
|
|
1776
|
-
const tokenAttr = terminalEnabled && options.consoleToken !== undefined ? ` data-token="${escapeHtml(options.consoleToken)}" data-terminal="1"${ptyEnabled ? ' data-pty="1"' : ""}` : "";
|
|
2086
|
+
const tokenAttr = (terminalEnabled || graphDrilldown) && options.consoleToken !== undefined ? ` data-token="${escapeHtml(options.consoleToken)}"${terminalEnabled ? ' data-terminal="1"' : ""}${ptyEnabled ? ' data-pty="1"' : ""}${graphDrilldown ? ' data-drilldown="1"' : ""}` : "";
|
|
1777
2087
|
return [
|
|
1778
2088
|
"<!doctype html>",
|
|
1779
2089
|
`<html lang="es" data-generated="${escapeHtml(state.generatedAt)}" data-refresh="${refreshMs}"${tokenAttr}>`,
|
|
@@ -1781,14 +2091,14 @@ function renderConsoleHtml(state, options = {}) {
|
|
|
1781
2091
|
'<meta charset="utf-8">',
|
|
1782
2092
|
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
1783
2093
|
"<title>openteam console</title>",
|
|
1784
|
-
`<style>${CONSOLE_STYLE}</style>`,
|
|
2094
|
+
`<style>${CONSOLE_STYLE}${graphEnabled ? GRAPH_STYLE : ""}</style>`,
|
|
1785
2095
|
"</head>",
|
|
1786
2096
|
"<body>",
|
|
1787
2097
|
"<header>",
|
|
1788
2098
|
"<h1>openteam console</h1>",
|
|
1789
2099
|
`<span class="meta">actualizado ${escapeHtml(state.generatedAt)}${session}</span>`,
|
|
1790
2100
|
"</header>",
|
|
1791
|
-
tabBar(state.sessionsDetail),
|
|
2101
|
+
tabBar(state.sessionsDetail, graphEnabled),
|
|
1792
2102
|
`<main>${body}</main>`,
|
|
1793
2103
|
`<script>${CONSOLE_CLIENT_JS}</script>`,
|
|
1794
2104
|
"</body>",
|
|
@@ -2120,7 +2430,7 @@ async function readJsonBody(request, limit) {
|
|
|
2120
2430
|
});
|
|
2121
2431
|
}
|
|
2122
2432
|
function parseConsolePath(path) {
|
|
2123
|
-
const match = /^\/console\/session\/([^/]+)\/(stream|input|permission|pty)$/.exec(path);
|
|
2433
|
+
const match = /^\/console\/session\/([^/]+)\/(stream|input|permission|pty|messages)$/.exec(path);
|
|
2124
2434
|
if (match === null) {
|
|
2125
2435
|
return;
|
|
2126
2436
|
}
|
|
@@ -2150,6 +2460,15 @@ data: {}
|
|
|
2150
2460
|
response.end();
|
|
2151
2461
|
}
|
|
2152
2462
|
}
|
|
2463
|
+
async function handleMessages(_request, response, sessionID, deps) {
|
|
2464
|
+
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
2465
|
+
if (endpoint === undefined) {
|
|
2466
|
+
response.writeHead(404, JSON_HEADERS).end(JSON.stringify({ error: "no endpoint" }));
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
const frames = await deps.createClient(endpoint).listMessages(sessionID);
|
|
2470
|
+
response.writeHead(200, JSON_HEADERS).end(JSON.stringify({ frames }));
|
|
2471
|
+
}
|
|
2153
2472
|
async function handleInput(request, response, sessionID, deps) {
|
|
2154
2473
|
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
2155
2474
|
if (endpoint === undefined) {
|
|
@@ -2217,7 +2536,9 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
2217
2536
|
response.writeHead(404, JSON_HEADERS).end(JSON.stringify({ error: "not found" }));
|
|
2218
2537
|
return true;
|
|
2219
2538
|
}
|
|
2220
|
-
|
|
2539
|
+
const readOnlyAction = route.action === "stream" || route.action === "messages";
|
|
2540
|
+
const channelEnabled = deps.terminalEnabled || readOnlyAction && deps.drilldownEnabled === true;
|
|
2541
|
+
if (!channelEnabled) {
|
|
2221
2542
|
response.writeHead(403, JSON_HEADERS).end(JSON.stringify({ error: "terminal disabled" }));
|
|
2222
2543
|
return true;
|
|
2223
2544
|
}
|
|
@@ -2229,6 +2550,10 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
2229
2550
|
await handleStream(request, response, route.sessionID, deps);
|
|
2230
2551
|
return true;
|
|
2231
2552
|
}
|
|
2553
|
+
if (route.action === "messages" && request.method === "GET") {
|
|
2554
|
+
await handleMessages(request, response, route.sessionID, deps);
|
|
2555
|
+
return true;
|
|
2556
|
+
}
|
|
2232
2557
|
if (route.action === "input" && request.method === "POST") {
|
|
2233
2558
|
await handleInput(request, response, route.sessionID, deps);
|
|
2234
2559
|
return true;
|
|
@@ -2245,10 +2570,15 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
2245
2570
|
return true;
|
|
2246
2571
|
}
|
|
2247
2572
|
|
|
2248
|
-
// src/web/
|
|
2573
|
+
// src/web/graphRoute.ts
|
|
2249
2574
|
var JSON_HEADERS2 = { "content-type": "application/json" };
|
|
2250
|
-
var
|
|
2251
|
-
|
|
2575
|
+
var GRAPH_SSE_EVENT = "graph";
|
|
2576
|
+
function graphSseMessage(json) {
|
|
2577
|
+
return `event: ${GRAPH_SSE_EVENT}
|
|
2578
|
+
data: ${json}
|
|
2579
|
+
|
|
2580
|
+
`;
|
|
2581
|
+
}
|
|
2252
2582
|
function bearerFromHeader2(request) {
|
|
2253
2583
|
const header = request.headers.authorization;
|
|
2254
2584
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
@@ -2261,6 +2591,39 @@ function authorized2(request, query, token) {
|
|
|
2261
2591
|
const provided = tokenFromQuery(query) ?? bearerFromHeader2(request);
|
|
2262
2592
|
return provided !== undefined && safeEqualToken(provided, token);
|
|
2263
2593
|
}
|
|
2594
|
+
async function handleGraphRoute(request, response, url, deps) {
|
|
2595
|
+
if (url.pathname !== "/api/graph") {
|
|
2596
|
+
return false;
|
|
2597
|
+
}
|
|
2598
|
+
if (request.method !== "GET") {
|
|
2599
|
+
response.writeHead(405, JSON_HEADERS2).end(JSON.stringify({ error: "method not allowed" }));
|
|
2600
|
+
return true;
|
|
2601
|
+
}
|
|
2602
|
+
if (deps.remoteToken !== undefined && !authorized2(request, url.searchParams, deps.remoteToken)) {
|
|
2603
|
+
response.writeHead(401, JSON_HEADERS2).end(JSON.stringify({ error: "unauthorized" }));
|
|
2604
|
+
return true;
|
|
2605
|
+
}
|
|
2606
|
+
const view = await deps.readView();
|
|
2607
|
+
response.writeHead(200, JSON_HEADERS2).end(JSON.stringify(view));
|
|
2608
|
+
return true;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// src/web/storageRoute.ts
|
|
2612
|
+
var JSON_HEADERS3 = { "content-type": "application/json" };
|
|
2613
|
+
var READ_OPS = new Set(["read", "exists", "list", "isDirectory", "stat"]);
|
|
2614
|
+
var WRITE_OPS = new Set(["write", "append", "mkdir", "delete"]);
|
|
2615
|
+
function bearerFromHeader3(request) {
|
|
2616
|
+
const header = request.headers.authorization;
|
|
2617
|
+
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2620
|
+
const value = header.slice("Bearer ".length).trim();
|
|
2621
|
+
return value.length === 0 ? undefined : value;
|
|
2622
|
+
}
|
|
2623
|
+
function authorized3(request, query, token) {
|
|
2624
|
+
const provided = tokenFromQuery(query) ?? bearerFromHeader3(request);
|
|
2625
|
+
return provided !== undefined && safeEqualToken(provided, token);
|
|
2626
|
+
}
|
|
2264
2627
|
function pathAllowed(path, allowPrefixes) {
|
|
2265
2628
|
if (allowPrefixes === undefined) {
|
|
2266
2629
|
return true;
|
|
@@ -2300,7 +2663,7 @@ async function readJsonBody2(request, limit) {
|
|
|
2300
2663
|
});
|
|
2301
2664
|
}
|
|
2302
2665
|
function sendJson(response, status, body) {
|
|
2303
|
-
response.writeHead(status,
|
|
2666
|
+
response.writeHead(status, JSON_HEADERS3).end(JSON.stringify(body));
|
|
2304
2667
|
}
|
|
2305
2668
|
async function handleRead(response, op, path, storage) {
|
|
2306
2669
|
if (op === "read") {
|
|
@@ -2374,7 +2737,7 @@ async function handleStorageRoute(request, response, url, deps) {
|
|
|
2374
2737
|
sendJson(response, 404, { error: "not found" });
|
|
2375
2738
|
return true;
|
|
2376
2739
|
}
|
|
2377
|
-
if (!
|
|
2740
|
+
if (!authorized3(request, url.searchParams, deps.token)) {
|
|
2378
2741
|
sendJson(response, 401, { error: "unauthorized" });
|
|
2379
2742
|
return true;
|
|
2380
2743
|
}
|
|
@@ -2415,12 +2778,12 @@ var SSE_HEADERS2 = {
|
|
|
2415
2778
|
"cache-control": "no-cache",
|
|
2416
2779
|
connection: "keep-alive"
|
|
2417
2780
|
};
|
|
2418
|
-
var
|
|
2781
|
+
var JSON_HEADERS4 = { "content-type": "application/json" };
|
|
2419
2782
|
var HTML_HEADERS = { "content-type": "text/html; charset=utf-8" };
|
|
2420
2783
|
function isAddressInUse(error) {
|
|
2421
2784
|
return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
|
|
2422
2785
|
}
|
|
2423
|
-
function
|
|
2786
|
+
function bearerFromHeader4(request) {
|
|
2424
2787
|
const header = request.headers.authorization;
|
|
2425
2788
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
2426
2789
|
return;
|
|
@@ -2429,16 +2792,19 @@ function bearerFromHeader3(request) {
|
|
|
2429
2792
|
return value.length === 0 ? undefined : value;
|
|
2430
2793
|
}
|
|
2431
2794
|
function readAuthorized(request, url, token) {
|
|
2432
|
-
const provided = tokenFromQuery(url.searchParams) ??
|
|
2795
|
+
const provided = tokenFromQuery(url.searchParams) ?? bearerFromHeader4(request);
|
|
2433
2796
|
return provided !== undefined && safeEqualToken(provided, token);
|
|
2434
2797
|
}
|
|
2435
2798
|
async function renderState(deps) {
|
|
2799
|
+
const graphView = deps.graphApi === undefined ? undefined : { projection: await deps.graphApi.readView() };
|
|
2436
2800
|
return renderConsoleHtml(buildConsoleState(await deps.readSnapshot()), {
|
|
2437
2801
|
refreshMs: deps.config.refreshMs,
|
|
2802
|
+
...graphView !== undefined ? { graphView } : {},
|
|
2438
2803
|
...deps.console !== undefined ? {
|
|
2439
2804
|
consoleToken: deps.console.token,
|
|
2440
2805
|
terminalEnabled: deps.console.terminalEnabled,
|
|
2441
|
-
...deps.console.ptyEnabled !== undefined ? { ptyEnabled: deps.console.ptyEnabled } : {}
|
|
2806
|
+
...deps.console.ptyEnabled !== undefined ? { ptyEnabled: deps.console.ptyEnabled } : {},
|
|
2807
|
+
...deps.console.drilldownEnabled !== undefined ? { drilldownEnabled: deps.console.drilldownEnabled } : {}
|
|
2442
2808
|
} : {}
|
|
2443
2809
|
});
|
|
2444
2810
|
}
|
|
@@ -2484,13 +2850,21 @@ async function createConsoleServer(deps) {
|
|
|
2484
2850
|
resolveEndpoint,
|
|
2485
2851
|
createClient: deps.console.createClient,
|
|
2486
2852
|
...deps.console.ptyEnabled !== undefined ? { ptyEnabled: deps.console.ptyEnabled } : {},
|
|
2853
|
+
...deps.console.drilldownEnabled !== undefined ? { drilldownEnabled: deps.console.drilldownEnabled } : {},
|
|
2487
2854
|
...deps.console.createPtyClient !== undefined ? { createPtyClient: deps.console.createPtyClient } : {}
|
|
2488
2855
|
};
|
|
2856
|
+
const graphDeps = deps.graphApi === undefined ? undefined : {
|
|
2857
|
+
readView: deps.graphApi.readView,
|
|
2858
|
+
...deps.storageApi?.token !== undefined ? { remoteToken: deps.storageApi.token } : {}
|
|
2859
|
+
};
|
|
2489
2860
|
const handle = async (request, response) => {
|
|
2490
2861
|
const url2 = new URL(request.url ?? "/", "http://localhost");
|
|
2491
2862
|
if (consoleDeps !== undefined && await handleConsoleRoute(request, response, url2, consoleDeps)) {
|
|
2492
2863
|
return;
|
|
2493
2864
|
}
|
|
2865
|
+
if (graphDeps !== undefined && await handleGraphRoute(request, response, url2, graphDeps)) {
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2494
2868
|
if (deps.storageApi !== undefined && await handleStorageRoute(request, response, url2, deps.storageApi)) {
|
|
2495
2869
|
return;
|
|
2496
2870
|
}
|
|
@@ -2501,7 +2875,7 @@ async function createConsoleServer(deps) {
|
|
|
2501
2875
|
const path = url2.pathname;
|
|
2502
2876
|
const remoteToken = deps.storageApi?.token;
|
|
2503
2877
|
if (remoteToken !== undefined && (path === "/" || path === "/index.html" || path === "/api/state" || path === "/events") && !readAuthorized(request, url2, remoteToken)) {
|
|
2504
|
-
response.writeHead(401,
|
|
2878
|
+
response.writeHead(401, JSON_HEADERS4).end(JSON.stringify({ error: "unauthorized" }));
|
|
2505
2879
|
return;
|
|
2506
2880
|
}
|
|
2507
2881
|
if (path === "/" || path === "/index.html") {
|
|
@@ -2509,11 +2883,11 @@ async function createConsoleServer(deps) {
|
|
|
2509
2883
|
return;
|
|
2510
2884
|
}
|
|
2511
2885
|
if (path === "/api/state") {
|
|
2512
|
-
response.writeHead(200,
|
|
2886
|
+
response.writeHead(200, JSON_HEADERS4).end(await stateJson(deps));
|
|
2513
2887
|
return;
|
|
2514
2888
|
}
|
|
2515
2889
|
if (path === "/healthz") {
|
|
2516
|
-
response.writeHead(200,
|
|
2890
|
+
response.writeHead(200, JSON_HEADERS4).end(JSON.stringify({ ok: true }));
|
|
2517
2891
|
return;
|
|
2518
2892
|
}
|
|
2519
2893
|
if (path === "/favicon.ico") {
|
|
@@ -2523,6 +2897,9 @@ async function createConsoleServer(deps) {
|
|
|
2523
2897
|
if (path === "/events") {
|
|
2524
2898
|
response.writeHead(200, SSE_HEADERS2);
|
|
2525
2899
|
response.write(sseMessage(await stateJson(deps)));
|
|
2900
|
+
if (deps.graphApi !== undefined) {
|
|
2901
|
+
response.write(graphSseMessage(JSON.stringify(await deps.graphApi.readView())));
|
|
2902
|
+
}
|
|
2526
2903
|
clients.add(response);
|
|
2527
2904
|
request.on("close", () => {
|
|
2528
2905
|
clients.delete(response);
|
|
@@ -2563,8 +2940,12 @@ async function createConsoleServer(deps) {
|
|
|
2563
2940
|
return;
|
|
2564
2941
|
}
|
|
2565
2942
|
const message = sseMessage(await stateJson(deps));
|
|
2943
|
+
const graphMessage = deps.graphApi !== undefined ? graphSseMessage(JSON.stringify(await deps.graphApi.readView())) : undefined;
|
|
2566
2944
|
for (const response of clients) {
|
|
2567
2945
|
response.write(message);
|
|
2946
|
+
if (graphMessage !== undefined) {
|
|
2947
|
+
response.write(graphMessage);
|
|
2948
|
+
}
|
|
2568
2949
|
}
|
|
2569
2950
|
};
|
|
2570
2951
|
const close = () => {
|
|
@@ -2895,12 +3276,14 @@ async function createConsoleRuntime(deps) {
|
|
|
2895
3276
|
...deps.session !== undefined ? { session: deps.session } : {}
|
|
2896
3277
|
});
|
|
2897
3278
|
const createServer = deps.serve ?? createConsoleServer;
|
|
3279
|
+
const graphApi = deps.config.graphView?.enabled === true && deps.graphView !== undefined ? { readView: deps.graphView.readView } : undefined;
|
|
2898
3280
|
const server = await createServer({
|
|
2899
3281
|
readSnapshot,
|
|
2900
3282
|
config: deps.config,
|
|
2901
3283
|
...deps.log !== undefined ? { log: deps.log } : {},
|
|
2902
3284
|
...deps.console !== undefined ? { console: deps.console } : {},
|
|
2903
|
-
...deps.storageApi !== undefined ? { storageApi: deps.storageApi } : {}
|
|
3285
|
+
...deps.storageApi !== undefined ? { storageApi: deps.storageApi } : {},
|
|
3286
|
+
...graphApi !== undefined ? { graphApi } : {}
|
|
2904
3287
|
});
|
|
2905
3288
|
const watchFn = deps.watch ?? ((path, listener) => fsWatch(path, { persistent: false }, listener));
|
|
2906
3289
|
const watcher = watchSources([deps.sessionsDir, decisionsPath, backlogPath, deps.agentDir], () => {
|