@jmanuelcorral/openteam 0.1.41 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.opencode/openteam.example.json +6 -1
- package/README.es.md +1 -1
- package/README.md +1 -1
- package/dist/capabilities/types.d.ts +4 -4
- package/dist/classifier/types.d.ts +3 -3
- package/dist/cli.js +430 -36
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/graph.d.ts +36 -2
- package/dist/commands/graph.d.ts.map +1 -1
- package/dist/config/graphFeatureGate.d.ts +96 -0
- package/dist/config/graphFeatureGate.d.ts.map +1 -0
- package/dist/config/migrations.d.ts.map +1 -1
- package/dist/config/resolve.d.ts +20 -1
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/schema.d.ts +40 -19
- 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/context/redaction.d.ts +23 -0
- package/dist/context/redaction.d.ts.map +1 -1
- package/dist/context/schema.d.ts +3 -3
- package/dist/contract/opencode.d.ts +2 -2
- package/dist/contract/opencode.d.ts.map +1 -1
- package/dist/graph/certificate.d.ts +505 -0
- package/dist/graph/certificate.d.ts.map +1 -0
- package/dist/graph/ids.d.ts +9 -0
- package/dist/graph/ids.d.ts.map +1 -1
- package/dist/graph/privacyPolicy.d.ts.map +1 -1
- package/dist/graph/reducer.d.ts.map +1 -1
- package/dist/graph/replay.d.ts.map +1 -1
- package/dist/graph/reviewerPolicy.d.ts.map +1 -1
- package/dist/graph/schema.d.ts +33 -33
- package/dist/graph/schema.d.ts.map +1 -1
- package/dist/graph/soakLedger.d.ts +144 -0
- package/dist/graph/soakLedger.d.ts.map +1 -0
- package/dist/graph/types.d.ts +2 -0
- package/dist/graph/types.d.ts.map +1 -1
- package/dist/graph/validate.d.ts.map +1 -1
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2351 -180
- package/dist/local/registry.d.ts.map +1 -1
- package/dist/memory/types.d.ts +2 -2
- package/dist/orchestrator/graphFacade.d.ts +39 -0
- package/dist/orchestrator/graphFacade.d.ts.map +1 -0
- package/dist/orchestrator/graphIngress.d.ts +136 -0
- package/dist/orchestrator/graphIngress.d.ts.map +1 -0
- package/dist/orchestrator/graphShadow.d.ts +121 -0
- package/dist/orchestrator/graphShadow.d.ts.map +1 -0
- package/dist/orchestrator/ledger.d.ts +14 -2
- package/dist/orchestrator/ledger.d.ts.map +1 -1
- package/dist/orchestrator/opencodeSessionAdapter.d.ts.map +1 -1
- package/dist/orchestrator/roles.d.ts +3 -3
- package/dist/orchestrator/sdd.d.ts +55 -1
- package/dist/orchestrator/sdd.d.ts.map +1 -1
- package/dist/orchestrator/sddCompiler.d.ts +6 -2
- package/dist/orchestrator/sddCompiler.d.ts.map +1 -1
- 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/plugin/graphShadowIngress.d.ts +165 -0
- package/dist/plugin/graphShadowIngress.d.ts.map +1 -0
- package/dist/plugin/graphTool.d.ts +63 -0
- package/dist/plugin/graphTool.d.ts.map +1 -0
- package/dist/plugin/hooks.d.ts +6 -0
- package/dist/plugin/hooks.d.ts.map +1 -1
- package/dist/plugin/legacyRunProducer.d.ts +60 -0
- package/dist/plugin/legacyRunProducer.d.ts.map +1 -0
- package/dist/plugin/reportRunSchema.d.ts +60 -0
- package/dist/plugin/reportRunSchema.d.ts.map +1 -0
- package/dist/plugin/sessionTracker.d.ts +42 -0
- package/dist/plugin/sessionTracker.d.ts.map +1 -0
- package/dist/plugin/shadowPipeline.d.ts +49 -0
- package/dist/plugin/shadowPipeline.d.ts.map +1 -0
- package/dist/plugin/soakObserver.d.ts +73 -0
- package/dist/plugin/soakObserver.d.ts.map +1 -0
- package/dist/storage/graph/codec.d.ts.map +1 -1
- package/dist/storage/graph/migrateLegacyBriefs.d.ts.map +1 -1
- package/dist/storage/graph/migration.d.ts +91 -0
- package/dist/storage/graph/migration.d.ts.map +1 -0
- package/dist/storage/graph/provider.d.ts +1 -0
- package/dist/storage/graph/provider.d.ts.map +1 -1
- package/dist/storage/graph/recovery.d.ts.map +1 -1
- package/dist/storage/graph/runRegistry.d.ts.map +1 -1
- package/dist/storage/graph/soakRecorder.d.ts +59 -0
- package/dist/storage/graph/soakRecorder.d.ts.map +1 -0
- package/dist/storage/graph/workspaceLease.d.ts.map +1 -1
- package/dist/storage/graph/worktreeRegistry.d.ts +29 -0
- package/dist/storage/graph/worktreeRegistry.d.ts.map +1 -0
- package/dist/storage/graph/writer.d.ts +15 -0
- package/dist/storage/graph/writer.d.ts.map +1 -1
- package/dist/storage/path.d.ts.map +1 -1
- package/dist/telemetry/events.d.ts +12 -12
- package/dist/telemetry/graphRuntimeHealth.d.ts +53 -0
- package/dist/telemetry/graphRuntimeHealth.d.ts.map +1 -0
- package/dist/telemetry/graphView.d.ts +76 -0
- package/dist/telemetry/graphView.d.ts.map +1 -0
- package/dist/telemetry/types.d.ts +4 -4
- 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 +7 -5
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,11 @@ 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
|
+
operatorApproval: z3.boolean().default(false),
|
|
678
|
+
worktrees: z3.object({
|
|
679
|
+
enabled: z3.boolean().default(false)
|
|
680
|
+
}).default({ enabled: false })
|
|
635
681
|
});
|
|
636
682
|
var ConsoleHostSchema = z3.enum(["127.0.0.1", "localhost"]);
|
|
637
683
|
var ConsoleConfigSchema = z3.object({
|
|
@@ -645,7 +691,10 @@ var ConsoleConfigSchema = z3.object({
|
|
|
645
691
|
terminal: z3.object({
|
|
646
692
|
enabled: z3.boolean().default(true),
|
|
647
693
|
pty: z3.boolean().optional()
|
|
648
|
-
}).default({ enabled: true })
|
|
694
|
+
}).default({ enabled: true }),
|
|
695
|
+
graphView: z3.object({
|
|
696
|
+
enabled: z3.boolean().default(false)
|
|
697
|
+
}).optional()
|
|
649
698
|
}).default({
|
|
650
699
|
host: "127.0.0.1",
|
|
651
700
|
port: 4599,
|
|
@@ -1299,6 +1348,51 @@ code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px
|
|
|
1299
1348
|
.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
1349
|
.pty .pty-start:disabled{opacity:.5;cursor:default}
|
|
1301
1350
|
`;
|
|
1351
|
+
var GRAPH_STYLE = `
|
|
1352
|
+
.graph-wrap{overflow:auto;background:#0b0d11;border:1px solid var(--line);border-radius:8px;padding:8px}
|
|
1353
|
+
svg.graph{display:block}
|
|
1354
|
+
.gedge{stroke:#3a4250;stroke-width:1.5;marker-end:url(#garrow)}
|
|
1355
|
+
.gnode rect{fill:var(--panel);stroke:var(--line);stroke-width:1.5;rx:8}
|
|
1356
|
+
.gnode .gname{fill:var(--fg);font:600 12px ui-monospace,Consolas,monospace}
|
|
1357
|
+
.gnode .grole{fill:var(--muted);font-size:11px}
|
|
1358
|
+
.gnode .gstate{font-size:10px;text-transform:uppercase;letter-spacing:.05em}
|
|
1359
|
+
.gnode.state-pending rect{stroke:#6e7681}
|
|
1360
|
+
.gnode.state-pending .gstate{fill:#6e7681}
|
|
1361
|
+
.gnode.state-ready rect{stroke:var(--local)}
|
|
1362
|
+
.gnode.state-ready .gstate{fill:var(--local)}
|
|
1363
|
+
.gnode.state-active rect{stroke:var(--frontier)}
|
|
1364
|
+
.gnode.state-active .gstate{fill:var(--frontier)}
|
|
1365
|
+
.gnode.state-succeeded rect{stroke:var(--good)}
|
|
1366
|
+
.gnode.state-succeeded .gstate{fill:var(--good)}
|
|
1367
|
+
.gnode.state-failed rect{stroke:#f85149}
|
|
1368
|
+
.gnode.state-failed .gstate{fill:#f85149}
|
|
1369
|
+
.gnode.state-cancelled rect{stroke:#484f58}
|
|
1370
|
+
.gnode.state-cancelled .gstate{fill:#484f58}
|
|
1371
|
+
.gbadge{font-size:10px;font-family:ui-monospace,Consolas,monospace}
|
|
1372
|
+
.gbadge.badge-retry{fill:var(--frontier)}
|
|
1373
|
+
.gbadge.badge-error{fill:#f85149}
|
|
1374
|
+
.gbadge.badge-cancelled{fill:#484f58}
|
|
1375
|
+
.gbadge.badge-verdict.verdict-approved{fill:var(--good)}
|
|
1376
|
+
.gbadge.badge-verdict.verdict-rejected{fill:#f85149}
|
|
1377
|
+
.gbadge.badge-verdict.verdict-inconclusive{fill:var(--frontier)}
|
|
1378
|
+
.gbadge.badge-lineage{fill:var(--local)}
|
|
1379
|
+
.gbadge.badge-artifact{fill:var(--muted)}
|
|
1380
|
+
.gnode[data-session]{cursor:pointer}
|
|
1381
|
+
.gnode[data-session]:hover rect{stroke-width:2.5}
|
|
1382
|
+
.graph-health{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 12px}
|
|
1383
|
+
.gchip{font-size:11px;padding:2px 10px;border-radius:10px;border:1px solid var(--line);color:var(--muted)}
|
|
1384
|
+
.gchip.on{font-weight:600}
|
|
1385
|
+
.gchip.gh-recovery.on,.gchip.gh-unknown.on{color:var(--frontier);border-color:var(--frontier)}
|
|
1386
|
+
.gchip.gh-conflict.on,.gchip.gh-kill.on{color:#f85149;border-color:#f85149}
|
|
1387
|
+
.graph-drill{margin-top:14px;border-top:1px solid var(--line);padding-top:12px}
|
|
1388
|
+
.graph-drill h3{font-size:13px;margin:0 0 8px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
|
|
1389
|
+
.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}
|
|
1390
|
+
.graph-drill .drilllog .tl{padding:1px 0}
|
|
1391
|
+
.graph-drill .drilllog .tl.tool{color:var(--frontier)}
|
|
1392
|
+
.graph-drill .drilllog .tl.status{color:var(--muted)}
|
|
1393
|
+
.graph-drill .drilllog .tl.permission{color:var(--frontier);font-weight:600}
|
|
1394
|
+
.graph-drill .drilllog .tl.error{color:#f85149}
|
|
1395
|
+
`;
|
|
1302
1396
|
var CONSOLE_CLIENT_JS = `
|
|
1303
1397
|
(function(){
|
|
1304
1398
|
var authTok='';
|
|
@@ -1424,6 +1518,47 @@ var CONSOLE_CLIENT_JS = `
|
|
|
1424
1518
|
');startBtn.disabled=false}
|
|
1425
1519
|
}
|
|
1426
1520
|
}
|
|
1521
|
+
function wireGraphDrill(){
|
|
1522
|
+
var root=document.documentElement;
|
|
1523
|
+
if(root.getAttribute('data-drilldown')!=='1'){return}
|
|
1524
|
+
var token=root.getAttribute('data-token');
|
|
1525
|
+
if(!token){return}
|
|
1526
|
+
var drill=document.querySelector('.graph-drill');
|
|
1527
|
+
if(!drill){return}
|
|
1528
|
+
var gview=drill.closest('.view');
|
|
1529
|
+
if(!gview){return}
|
|
1530
|
+
var log=drill.querySelector('.drilllog');
|
|
1531
|
+
var title=drill.querySelector('.drill-title');
|
|
1532
|
+
if(!log){return}
|
|
1533
|
+
var es=null;
|
|
1534
|
+
function append(cls,text){
|
|
1535
|
+
var line=document.createElement('div');
|
|
1536
|
+
line.className='tl '+cls;line.textContent=text;
|
|
1537
|
+
log.appendChild(line);log.scrollTop=log.scrollHeight;
|
|
1538
|
+
}
|
|
1539
|
+
function open(id){
|
|
1540
|
+
if(es){try{es.close()}catch(_){}}
|
|
1541
|
+
log.textContent='';drill.hidden=false;
|
|
1542
|
+
if(title){title.textContent='Conversación · '+id}
|
|
1543
|
+
var tk='token='+encodeURIComponent(token);
|
|
1544
|
+
var base='/console/session/'+encodeURIComponent(id);
|
|
1545
|
+
fetch(base+'/messages?'+tk).then(function(r){return r.ok?r.json():Promise.reject(r.status)})
|
|
1546
|
+
.then(function(d){(d&&d.frames||[]).forEach(function(f){append(f.kind,f.text)})})
|
|
1547
|
+
.catch(function(s){append('error','✖ no se pudo cargar la conversación ('+s+')')});
|
|
1548
|
+
if('EventSource' in window){
|
|
1549
|
+
try{
|
|
1550
|
+
es=new EventSource(base+'/stream?'+tk);
|
|
1551
|
+
es.addEventListener('frame',function(e){try{var f=JSON.parse(e.data);append(f.kind,f.text)}catch(_){}});
|
|
1552
|
+
es.onerror=function(){/* reconecta solo */};
|
|
1553
|
+
}catch(_){/* ignore */}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
gview.addEventListener('click',function(e){
|
|
1557
|
+
var t=e.target;
|
|
1558
|
+
while(t&&t!==gview&&!(t.getAttribute&&t.getAttribute('data-session'))){t=t.parentNode}
|
|
1559
|
+
if(t&&t.getAttribute&&t.getAttribute('data-session')){open(t.getAttribute('data-session'))}
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1427
1562
|
var bar=document.getElementById('tabbar');
|
|
1428
1563
|
if(bar){
|
|
1429
1564
|
bar.addEventListener('click',function(e){
|
|
@@ -1435,6 +1570,7 @@ var CONSOLE_CLIENT_JS = `
|
|
|
1435
1570
|
var saved='all';
|
|
1436
1571
|
try{saved=sessionStorage.getItem('openteam.tab')||'all'}catch(e){}
|
|
1437
1572
|
if(!selectTab(saved)){selectTab('all')}
|
|
1573
|
+
wireGraphDrill();
|
|
1438
1574
|
})();
|
|
1439
1575
|
`;
|
|
1440
1576
|
|
|
@@ -1684,14 +1820,15 @@ var STATUS_LABELS = {
|
|
|
1684
1820
|
function statusBadge(status) {
|
|
1685
1821
|
return `<span class="badge status-${status}">${STATUS_LABELS[status]}</span>`;
|
|
1686
1822
|
}
|
|
1687
|
-
function tabBar(sessions) {
|
|
1823
|
+
function tabBar(sessions, graphEnabled) {
|
|
1688
1824
|
const allTab = '<button type="button" class="tab" data-tab="all">Todas</button>';
|
|
1825
|
+
const graphTab = graphEnabled ? '<button type="button" class="tab" data-tab="graph">Grafo</button>' : "";
|
|
1689
1826
|
const tabs = sessions.map((tab) => {
|
|
1690
1827
|
const id = escapeHtml(tab.sessionID);
|
|
1691
1828
|
const short = escapeHtml(shortSessionID(tab.sessionID));
|
|
1692
1829
|
return `<button type="button" class="tab" data-tab="${id}"><span class="dot status-${tab.status}"></span>${short}</button>`;
|
|
1693
1830
|
}).join("");
|
|
1694
|
-
return `<nav class="tabs" id="tabbar">${allTab}${tabs}</nav>`;
|
|
1831
|
+
return `<nav class="tabs" id="tabbar">${allTab}${graphTab}${tabs}</nav>`;
|
|
1695
1832
|
}
|
|
1696
1833
|
function shortSessionID(sessionID) {
|
|
1697
1834
|
return sessionID.length <= 12 ? sessionID : `${sessionID.slice(0, 12)}…`;
|
|
@@ -1753,10 +1890,183 @@ function sessionView(tab, terminalEnabled, ptyEnabled) {
|
|
|
1753
1890
|
].join("");
|
|
1754
1891
|
return `<div class="view" data-view="${escapeHtml(tab.sessionID)}" hidden>${body}</div>`;
|
|
1755
1892
|
}
|
|
1893
|
+
var GRAPH_COL_W = 220;
|
|
1894
|
+
var GRAPH_ROW_H = 96;
|
|
1895
|
+
var GRAPH_NODE_W = 180;
|
|
1896
|
+
var GRAPH_NODE_H = 64;
|
|
1897
|
+
var GRAPH_PAD = 20;
|
|
1898
|
+
function computeLayers(projection) {
|
|
1899
|
+
const layer = new Map;
|
|
1900
|
+
for (const node of projection.nodes) {
|
|
1901
|
+
layer.set(node.nodeID, 0);
|
|
1902
|
+
}
|
|
1903
|
+
for (let pass = 0;pass < projection.nodes.length; pass += 1) {
|
|
1904
|
+
let changed = false;
|
|
1905
|
+
for (const edge of projection.edges) {
|
|
1906
|
+
const from = layer.get(edge.from);
|
|
1907
|
+
const to = layer.get(edge.to);
|
|
1908
|
+
if (from === undefined || to === undefined) {
|
|
1909
|
+
continue;
|
|
1910
|
+
}
|
|
1911
|
+
if (from + 1 > to) {
|
|
1912
|
+
layer.set(edge.to, from + 1);
|
|
1913
|
+
changed = true;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
if (!changed) {
|
|
1917
|
+
break;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
return layer;
|
|
1921
|
+
}
|
|
1922
|
+
function layoutNodes(projection) {
|
|
1923
|
+
const layers = computeLayers(projection);
|
|
1924
|
+
const byLayer = [];
|
|
1925
|
+
for (const node of projection.nodes) {
|
|
1926
|
+
const l = layers.get(node.nodeID) ?? 0;
|
|
1927
|
+
let bucket = byLayer[l];
|
|
1928
|
+
if (bucket === undefined) {
|
|
1929
|
+
bucket = [];
|
|
1930
|
+
byLayer[l] = bucket;
|
|
1931
|
+
}
|
|
1932
|
+
bucket.push(node.nodeID);
|
|
1933
|
+
}
|
|
1934
|
+
const pos = new Map;
|
|
1935
|
+
let maxLayer = 0;
|
|
1936
|
+
let maxRows = 0;
|
|
1937
|
+
byLayer.forEach((ids, l) => {
|
|
1938
|
+
if (l > maxLayer) {
|
|
1939
|
+
maxLayer = l;
|
|
1940
|
+
}
|
|
1941
|
+
if (ids.length > maxRows) {
|
|
1942
|
+
maxRows = ids.length;
|
|
1943
|
+
}
|
|
1944
|
+
ids.forEach((id, row) => {
|
|
1945
|
+
pos.set(id, {
|
|
1946
|
+
x: GRAPH_PAD + l * GRAPH_COL_W,
|
|
1947
|
+
y: GRAPH_PAD + row * GRAPH_ROW_H
|
|
1948
|
+
});
|
|
1949
|
+
});
|
|
1950
|
+
});
|
|
1951
|
+
const width = GRAPH_PAD * 2 + maxLayer * GRAPH_COL_W + GRAPH_NODE_W;
|
|
1952
|
+
const height = GRAPH_PAD * 2 + Math.max(0, maxRows - 1) * GRAPH_ROW_H + GRAPH_NODE_H;
|
|
1953
|
+
return { pos, width, height };
|
|
1954
|
+
}
|
|
1955
|
+
function nodeBadges(node) {
|
|
1956
|
+
const badges = [];
|
|
1957
|
+
if (node.attempts > 1) {
|
|
1958
|
+
badges.push({ cls: "badge-retry", text: `⟳${node.attempts}` });
|
|
1959
|
+
}
|
|
1960
|
+
if (node.lastErrorClass !== undefined) {
|
|
1961
|
+
badges.push({
|
|
1962
|
+
cls: `badge-error err-${node.lastErrorClass}`,
|
|
1963
|
+
text: `⚠${node.lastErrorClass}`
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
if (node.state === "cancelled") {
|
|
1967
|
+
badges.push({ cls: "badge-cancelled", text: "⊘" });
|
|
1968
|
+
}
|
|
1969
|
+
if (node.review !== undefined) {
|
|
1970
|
+
const symbol = node.review.verdict === "approved" ? "✓" : node.review.verdict === "rejected" ? "✗" : "?";
|
|
1971
|
+
badges.push({
|
|
1972
|
+
cls: `badge-verdict verdict-${node.review.verdict}`,
|
|
1973
|
+
text: node.review.reassigned ? `${symbol}↔` : symbol
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
if (node.revisionOf !== undefined) {
|
|
1977
|
+
badges.push({
|
|
1978
|
+
cls: "badge-lineage",
|
|
1979
|
+
text: `↺${escapeHtml(node.revisionOf)}`
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
if (node.artifact !== undefined) {
|
|
1983
|
+
badges.push({ cls: "badge-artifact", text: "⧉" });
|
|
1984
|
+
}
|
|
1985
|
+
return badges;
|
|
1986
|
+
}
|
|
1987
|
+
function nodeSvg(node, at) {
|
|
1988
|
+
const badges = nodeBadges(node).map((badge, index) => `<text class="gbadge ${badge.cls}" x="${10 + index * 34}" y="58">${badge.text}</text>`).join("");
|
|
1989
|
+
const sessionAttr = node.sessionRef === undefined ? "" : ` data-session="${escapeHtml(node.sessionRef)}"`;
|
|
1990
|
+
return [
|
|
1991
|
+
`<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})">`,
|
|
1992
|
+
`<rect width="${GRAPH_NODE_W}" height="${GRAPH_NODE_H}"></rect>`,
|
|
1993
|
+
`<text class="gname" x="10" y="20">${escapeHtml(node.nodeID)}</text>`,
|
|
1994
|
+
`<text class="grole" x="10" y="36">${escapeHtml(node.role)}</text>`,
|
|
1995
|
+
`<text class="gstate" x="10" y="50">${node.state}</text>`,
|
|
1996
|
+
badges,
|
|
1997
|
+
"</g>"
|
|
1998
|
+
].join("");
|
|
1999
|
+
}
|
|
2000
|
+
function edgeSvg(pos, from, to) {
|
|
2001
|
+
const a = pos.get(from);
|
|
2002
|
+
const b = pos.get(to);
|
|
2003
|
+
if (a === undefined || b === undefined) {
|
|
2004
|
+
return "";
|
|
2005
|
+
}
|
|
2006
|
+
const x1 = a.x + GRAPH_NODE_W;
|
|
2007
|
+
const y1 = a.y + GRAPH_NODE_H / 2;
|
|
2008
|
+
const x2 = b.x;
|
|
2009
|
+
const y2 = b.y + GRAPH_NODE_H / 2;
|
|
2010
|
+
return `<line class="gedge" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" data-from="${escapeHtml(from)}" data-to="${escapeHtml(to)}"></line>`;
|
|
2011
|
+
}
|
|
2012
|
+
function renderGraphView(projection) {
|
|
2013
|
+
if (projection === null) {
|
|
2014
|
+
return '<p class="muted">Sin run de grafo activo.</p>';
|
|
2015
|
+
}
|
|
2016
|
+
if (projection.nodes.length === 0) {
|
|
2017
|
+
return '<p class="muted">El run no tiene nodos.</p>';
|
|
2018
|
+
}
|
|
2019
|
+
const { pos, width, height } = layoutNodes(projection);
|
|
2020
|
+
const edges = projection.edges.map((edge) => edgeSvg(pos, edge.from, edge.to)).join("");
|
|
2021
|
+
const nodes = projection.nodes.map((node) => {
|
|
2022
|
+
const at = pos.get(node.nodeID);
|
|
2023
|
+
return at === undefined ? "" : nodeSvg(node, at);
|
|
2024
|
+
}).join("");
|
|
2025
|
+
return [
|
|
2026
|
+
`<div class="graph-wrap"><svg class="graph" viewBox="0 0 ${width} ${height}" width="${width}" height="${height}" role="img" aria-label="Grafo de agentes">`,
|
|
2027
|
+
'<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>',
|
|
2028
|
+
edges,
|
|
2029
|
+
nodes,
|
|
2030
|
+
"</svg></div>"
|
|
2031
|
+
].join("");
|
|
2032
|
+
}
|
|
2033
|
+
function healthBanner(health) {
|
|
2034
|
+
const chip = (cls, on, label) => `<span class="gchip ${cls} ${on ? "on" : "off"}">${label}</span>`;
|
|
2035
|
+
return [
|
|
2036
|
+
'<div class="graph-health" role="status">',
|
|
2037
|
+
chip("gh-recovery", health.recoveryRequired, health.recoveryRequired ? "recuperación requerida" : "sin recuperación"),
|
|
2038
|
+
chip("gh-unknown", health.unknownEffects > 0, `efectos desconocidos: ${health.unknownEffects}`),
|
|
2039
|
+
chip("gh-stale", health.staleCompletions > 0, `completaciones tardías: ${health.staleCompletions}`),
|
|
2040
|
+
chip("gh-conflict", health.conflicts > 0, `conflictos lease/worktree: ${health.conflicts}`),
|
|
2041
|
+
chip("gh-kill", health.killSwitch, health.killSwitch ? "kill switch activo" : "kill switch inactivo"),
|
|
2042
|
+
"</div>"
|
|
2043
|
+
].join("");
|
|
2044
|
+
}
|
|
2045
|
+
function drillPanel() {
|
|
2046
|
+
return [
|
|
2047
|
+
'<aside class="graph-drill" hidden>',
|
|
2048
|
+
'<h3 class="drill-title">Conversación</h3>',
|
|
2049
|
+
'<div class="drilllog" aria-live="polite"></div>',
|
|
2050
|
+
"</aside>"
|
|
2051
|
+
].join("");
|
|
2052
|
+
}
|
|
2053
|
+
function graphViewPanel(projection, drilldownEnabled) {
|
|
2054
|
+
return [
|
|
2055
|
+
'<div class="view" data-view="graph" hidden>',
|
|
2056
|
+
'<section class="panel" id="panel-graph">',
|
|
2057
|
+
"<h2>Grafo de agentes</h2>",
|
|
2058
|
+
projection === null ? "" : healthBanner(projection.health),
|
|
2059
|
+
renderGraphView(projection),
|
|
2060
|
+
drilldownEnabled ? drillPanel() : "",
|
|
2061
|
+
"</section>",
|
|
2062
|
+
"</div>"
|
|
2063
|
+
].join("");
|
|
2064
|
+
}
|
|
1756
2065
|
function renderConsoleHtml(state, options = {}) {
|
|
1757
2066
|
const refreshMs = options.refreshMs ?? 2000;
|
|
1758
2067
|
const terminalEnabled = options.terminalEnabled === true;
|
|
1759
2068
|
const ptyEnabled = options.ptyEnabled === true;
|
|
2069
|
+
const graphEnabled = options.graphView !== undefined;
|
|
1760
2070
|
const allBody = [
|
|
1761
2071
|
summaryPanel(state.cost),
|
|
1762
2072
|
totalsPanel(state.totals),
|
|
@@ -1770,10 +2080,12 @@ function renderConsoleHtml(state, options = {}) {
|
|
|
1770
2080
|
activityPanel(state.activity)
|
|
1771
2081
|
].join("");
|
|
1772
2082
|
const allView = `<div class="view" data-view="all">${allBody}</div>`;
|
|
2083
|
+
const graphDrilldown = graphEnabled && options.drilldownEnabled === true;
|
|
2084
|
+
const graphView = graphEnabled ? graphViewPanel(options.graphView?.projection ?? null, graphDrilldown) : "";
|
|
1773
2085
|
const sessionViews = state.sessionsDetail.map((tab) => sessionView(tab, terminalEnabled, ptyEnabled)).join("");
|
|
1774
|
-
const body = allView + sessionViews;
|
|
2086
|
+
const body = allView + graphView + sessionViews;
|
|
1775
2087
|
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"' : ""}` : "";
|
|
2088
|
+
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
2089
|
return [
|
|
1778
2090
|
"<!doctype html>",
|
|
1779
2091
|
`<html lang="es" data-generated="${escapeHtml(state.generatedAt)}" data-refresh="${refreshMs}"${tokenAttr}>`,
|
|
@@ -1781,14 +2093,14 @@ function renderConsoleHtml(state, options = {}) {
|
|
|
1781
2093
|
'<meta charset="utf-8">',
|
|
1782
2094
|
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
1783
2095
|
"<title>openteam console</title>",
|
|
1784
|
-
`<style>${CONSOLE_STYLE}</style>`,
|
|
2096
|
+
`<style>${CONSOLE_STYLE}${graphEnabled ? GRAPH_STYLE : ""}</style>`,
|
|
1785
2097
|
"</head>",
|
|
1786
2098
|
"<body>",
|
|
1787
2099
|
"<header>",
|
|
1788
2100
|
"<h1>openteam console</h1>",
|
|
1789
2101
|
`<span class="meta">actualizado ${escapeHtml(state.generatedAt)}${session}</span>`,
|
|
1790
2102
|
"</header>",
|
|
1791
|
-
tabBar(state.sessionsDetail),
|
|
2103
|
+
tabBar(state.sessionsDetail, graphEnabled),
|
|
1792
2104
|
`<main>${body}</main>`,
|
|
1793
2105
|
`<script>${CONSOLE_CLIENT_JS}</script>`,
|
|
1794
2106
|
"</body>",
|
|
@@ -2120,7 +2432,7 @@ async function readJsonBody(request, limit) {
|
|
|
2120
2432
|
});
|
|
2121
2433
|
}
|
|
2122
2434
|
function parseConsolePath(path) {
|
|
2123
|
-
const match = /^\/console\/session\/([^/]+)\/(stream|input|permission|pty)$/.exec(path);
|
|
2435
|
+
const match = /^\/console\/session\/([^/]+)\/(stream|input|permission|pty|messages)$/.exec(path);
|
|
2124
2436
|
if (match === null) {
|
|
2125
2437
|
return;
|
|
2126
2438
|
}
|
|
@@ -2150,6 +2462,15 @@ data: {}
|
|
|
2150
2462
|
response.end();
|
|
2151
2463
|
}
|
|
2152
2464
|
}
|
|
2465
|
+
async function handleMessages(_request, response, sessionID, deps) {
|
|
2466
|
+
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
2467
|
+
if (endpoint === undefined) {
|
|
2468
|
+
response.writeHead(404, JSON_HEADERS).end(JSON.stringify({ error: "no endpoint" }));
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
const frames = await deps.createClient(endpoint).listMessages(sessionID);
|
|
2472
|
+
response.writeHead(200, JSON_HEADERS).end(JSON.stringify({ frames }));
|
|
2473
|
+
}
|
|
2153
2474
|
async function handleInput(request, response, sessionID, deps) {
|
|
2154
2475
|
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
2155
2476
|
if (endpoint === undefined) {
|
|
@@ -2217,7 +2538,9 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
2217
2538
|
response.writeHead(404, JSON_HEADERS).end(JSON.stringify({ error: "not found" }));
|
|
2218
2539
|
return true;
|
|
2219
2540
|
}
|
|
2220
|
-
|
|
2541
|
+
const readOnlyAction = route.action === "stream" || route.action === "messages";
|
|
2542
|
+
const channelEnabled = deps.terminalEnabled || readOnlyAction && deps.drilldownEnabled === true;
|
|
2543
|
+
if (!channelEnabled) {
|
|
2221
2544
|
response.writeHead(403, JSON_HEADERS).end(JSON.stringify({ error: "terminal disabled" }));
|
|
2222
2545
|
return true;
|
|
2223
2546
|
}
|
|
@@ -2229,6 +2552,10 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
2229
2552
|
await handleStream(request, response, route.sessionID, deps);
|
|
2230
2553
|
return true;
|
|
2231
2554
|
}
|
|
2555
|
+
if (route.action === "messages" && request.method === "GET") {
|
|
2556
|
+
await handleMessages(request, response, route.sessionID, deps);
|
|
2557
|
+
return true;
|
|
2558
|
+
}
|
|
2232
2559
|
if (route.action === "input" && request.method === "POST") {
|
|
2233
2560
|
await handleInput(request, response, route.sessionID, deps);
|
|
2234
2561
|
return true;
|
|
@@ -2245,10 +2572,15 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
2245
2572
|
return true;
|
|
2246
2573
|
}
|
|
2247
2574
|
|
|
2248
|
-
// src/web/
|
|
2575
|
+
// src/web/graphRoute.ts
|
|
2249
2576
|
var JSON_HEADERS2 = { "content-type": "application/json" };
|
|
2250
|
-
var
|
|
2251
|
-
|
|
2577
|
+
var GRAPH_SSE_EVENT = "graph";
|
|
2578
|
+
function graphSseMessage(json) {
|
|
2579
|
+
return `event: ${GRAPH_SSE_EVENT}
|
|
2580
|
+
data: ${json}
|
|
2581
|
+
|
|
2582
|
+
`;
|
|
2583
|
+
}
|
|
2252
2584
|
function bearerFromHeader2(request) {
|
|
2253
2585
|
const header = request.headers.authorization;
|
|
2254
2586
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
@@ -2261,6 +2593,39 @@ function authorized2(request, query, token) {
|
|
|
2261
2593
|
const provided = tokenFromQuery(query) ?? bearerFromHeader2(request);
|
|
2262
2594
|
return provided !== undefined && safeEqualToken(provided, token);
|
|
2263
2595
|
}
|
|
2596
|
+
async function handleGraphRoute(request, response, url, deps) {
|
|
2597
|
+
if (url.pathname !== "/api/graph") {
|
|
2598
|
+
return false;
|
|
2599
|
+
}
|
|
2600
|
+
if (request.method !== "GET") {
|
|
2601
|
+
response.writeHead(405, JSON_HEADERS2).end(JSON.stringify({ error: "method not allowed" }));
|
|
2602
|
+
return true;
|
|
2603
|
+
}
|
|
2604
|
+
if (deps.remoteToken !== undefined && !authorized2(request, url.searchParams, deps.remoteToken)) {
|
|
2605
|
+
response.writeHead(401, JSON_HEADERS2).end(JSON.stringify({ error: "unauthorized" }));
|
|
2606
|
+
return true;
|
|
2607
|
+
}
|
|
2608
|
+
const view = await deps.readView();
|
|
2609
|
+
response.writeHead(200, JSON_HEADERS2).end(JSON.stringify(view));
|
|
2610
|
+
return true;
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
// src/web/storageRoute.ts
|
|
2614
|
+
var JSON_HEADERS3 = { "content-type": "application/json" };
|
|
2615
|
+
var READ_OPS = new Set(["read", "exists", "list", "isDirectory", "stat"]);
|
|
2616
|
+
var WRITE_OPS = new Set(["write", "append", "mkdir", "delete"]);
|
|
2617
|
+
function bearerFromHeader3(request) {
|
|
2618
|
+
const header = request.headers.authorization;
|
|
2619
|
+
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
const value = header.slice("Bearer ".length).trim();
|
|
2623
|
+
return value.length === 0 ? undefined : value;
|
|
2624
|
+
}
|
|
2625
|
+
function authorized3(request, query, token) {
|
|
2626
|
+
const provided = tokenFromQuery(query) ?? bearerFromHeader3(request);
|
|
2627
|
+
return provided !== undefined && safeEqualToken(provided, token);
|
|
2628
|
+
}
|
|
2264
2629
|
function pathAllowed(path, allowPrefixes) {
|
|
2265
2630
|
if (allowPrefixes === undefined) {
|
|
2266
2631
|
return true;
|
|
@@ -2300,7 +2665,7 @@ async function readJsonBody2(request, limit) {
|
|
|
2300
2665
|
});
|
|
2301
2666
|
}
|
|
2302
2667
|
function sendJson(response, status, body) {
|
|
2303
|
-
response.writeHead(status,
|
|
2668
|
+
response.writeHead(status, JSON_HEADERS3).end(JSON.stringify(body));
|
|
2304
2669
|
}
|
|
2305
2670
|
async function handleRead(response, op, path, storage) {
|
|
2306
2671
|
if (op === "read") {
|
|
@@ -2374,7 +2739,7 @@ async function handleStorageRoute(request, response, url, deps) {
|
|
|
2374
2739
|
sendJson(response, 404, { error: "not found" });
|
|
2375
2740
|
return true;
|
|
2376
2741
|
}
|
|
2377
|
-
if (!
|
|
2742
|
+
if (!authorized3(request, url.searchParams, deps.token)) {
|
|
2378
2743
|
sendJson(response, 401, { error: "unauthorized" });
|
|
2379
2744
|
return true;
|
|
2380
2745
|
}
|
|
@@ -2415,12 +2780,12 @@ var SSE_HEADERS2 = {
|
|
|
2415
2780
|
"cache-control": "no-cache",
|
|
2416
2781
|
connection: "keep-alive"
|
|
2417
2782
|
};
|
|
2418
|
-
var
|
|
2783
|
+
var JSON_HEADERS4 = { "content-type": "application/json" };
|
|
2419
2784
|
var HTML_HEADERS = { "content-type": "text/html; charset=utf-8" };
|
|
2420
2785
|
function isAddressInUse(error) {
|
|
2421
2786
|
return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
|
|
2422
2787
|
}
|
|
2423
|
-
function
|
|
2788
|
+
function bearerFromHeader4(request) {
|
|
2424
2789
|
const header = request.headers.authorization;
|
|
2425
2790
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
2426
2791
|
return;
|
|
@@ -2429,16 +2794,19 @@ function bearerFromHeader3(request) {
|
|
|
2429
2794
|
return value.length === 0 ? undefined : value;
|
|
2430
2795
|
}
|
|
2431
2796
|
function readAuthorized(request, url, token) {
|
|
2432
|
-
const provided = tokenFromQuery(url.searchParams) ??
|
|
2797
|
+
const provided = tokenFromQuery(url.searchParams) ?? bearerFromHeader4(request);
|
|
2433
2798
|
return provided !== undefined && safeEqualToken(provided, token);
|
|
2434
2799
|
}
|
|
2435
2800
|
async function renderState(deps) {
|
|
2801
|
+
const graphView = deps.graphApi === undefined ? undefined : { projection: await deps.graphApi.readView() };
|
|
2436
2802
|
return renderConsoleHtml(buildConsoleState(await deps.readSnapshot()), {
|
|
2437
2803
|
refreshMs: deps.config.refreshMs,
|
|
2804
|
+
...graphView !== undefined ? { graphView } : {},
|
|
2438
2805
|
...deps.console !== undefined ? {
|
|
2439
2806
|
consoleToken: deps.console.token,
|
|
2440
2807
|
terminalEnabled: deps.console.terminalEnabled,
|
|
2441
|
-
...deps.console.ptyEnabled !== undefined ? { ptyEnabled: deps.console.ptyEnabled } : {}
|
|
2808
|
+
...deps.console.ptyEnabled !== undefined ? { ptyEnabled: deps.console.ptyEnabled } : {},
|
|
2809
|
+
...deps.console.drilldownEnabled !== undefined ? { drilldownEnabled: deps.console.drilldownEnabled } : {}
|
|
2442
2810
|
} : {}
|
|
2443
2811
|
});
|
|
2444
2812
|
}
|
|
@@ -2484,13 +2852,21 @@ async function createConsoleServer(deps) {
|
|
|
2484
2852
|
resolveEndpoint,
|
|
2485
2853
|
createClient: deps.console.createClient,
|
|
2486
2854
|
...deps.console.ptyEnabled !== undefined ? { ptyEnabled: deps.console.ptyEnabled } : {},
|
|
2855
|
+
...deps.console.drilldownEnabled !== undefined ? { drilldownEnabled: deps.console.drilldownEnabled } : {},
|
|
2487
2856
|
...deps.console.createPtyClient !== undefined ? { createPtyClient: deps.console.createPtyClient } : {}
|
|
2488
2857
|
};
|
|
2858
|
+
const graphDeps = deps.graphApi === undefined ? undefined : {
|
|
2859
|
+
readView: deps.graphApi.readView,
|
|
2860
|
+
...deps.storageApi?.token !== undefined ? { remoteToken: deps.storageApi.token } : {}
|
|
2861
|
+
};
|
|
2489
2862
|
const handle = async (request, response) => {
|
|
2490
2863
|
const url2 = new URL(request.url ?? "/", "http://localhost");
|
|
2491
2864
|
if (consoleDeps !== undefined && await handleConsoleRoute(request, response, url2, consoleDeps)) {
|
|
2492
2865
|
return;
|
|
2493
2866
|
}
|
|
2867
|
+
if (graphDeps !== undefined && await handleGraphRoute(request, response, url2, graphDeps)) {
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2494
2870
|
if (deps.storageApi !== undefined && await handleStorageRoute(request, response, url2, deps.storageApi)) {
|
|
2495
2871
|
return;
|
|
2496
2872
|
}
|
|
@@ -2501,7 +2877,7 @@ async function createConsoleServer(deps) {
|
|
|
2501
2877
|
const path = url2.pathname;
|
|
2502
2878
|
const remoteToken = deps.storageApi?.token;
|
|
2503
2879
|
if (remoteToken !== undefined && (path === "/" || path === "/index.html" || path === "/api/state" || path === "/events") && !readAuthorized(request, url2, remoteToken)) {
|
|
2504
|
-
response.writeHead(401,
|
|
2880
|
+
response.writeHead(401, JSON_HEADERS4).end(JSON.stringify({ error: "unauthorized" }));
|
|
2505
2881
|
return;
|
|
2506
2882
|
}
|
|
2507
2883
|
if (path === "/" || path === "/index.html") {
|
|
@@ -2509,11 +2885,11 @@ async function createConsoleServer(deps) {
|
|
|
2509
2885
|
return;
|
|
2510
2886
|
}
|
|
2511
2887
|
if (path === "/api/state") {
|
|
2512
|
-
response.writeHead(200,
|
|
2888
|
+
response.writeHead(200, JSON_HEADERS4).end(await stateJson(deps));
|
|
2513
2889
|
return;
|
|
2514
2890
|
}
|
|
2515
2891
|
if (path === "/healthz") {
|
|
2516
|
-
response.writeHead(200,
|
|
2892
|
+
response.writeHead(200, JSON_HEADERS4).end(JSON.stringify({ ok: true }));
|
|
2517
2893
|
return;
|
|
2518
2894
|
}
|
|
2519
2895
|
if (path === "/favicon.ico") {
|
|
@@ -2523,6 +2899,9 @@ async function createConsoleServer(deps) {
|
|
|
2523
2899
|
if (path === "/events") {
|
|
2524
2900
|
response.writeHead(200, SSE_HEADERS2);
|
|
2525
2901
|
response.write(sseMessage(await stateJson(deps)));
|
|
2902
|
+
if (deps.graphApi !== undefined) {
|
|
2903
|
+
response.write(graphSseMessage(JSON.stringify(await deps.graphApi.readView())));
|
|
2904
|
+
}
|
|
2526
2905
|
clients.add(response);
|
|
2527
2906
|
request.on("close", () => {
|
|
2528
2907
|
clients.delete(response);
|
|
@@ -2563,8 +2942,12 @@ async function createConsoleServer(deps) {
|
|
|
2563
2942
|
return;
|
|
2564
2943
|
}
|
|
2565
2944
|
const message = sseMessage(await stateJson(deps));
|
|
2945
|
+
const graphMessage = deps.graphApi !== undefined ? graphSseMessage(JSON.stringify(await deps.graphApi.readView())) : undefined;
|
|
2566
2946
|
for (const response of clients) {
|
|
2567
2947
|
response.write(message);
|
|
2948
|
+
if (graphMessage !== undefined) {
|
|
2949
|
+
response.write(graphMessage);
|
|
2950
|
+
}
|
|
2568
2951
|
}
|
|
2569
2952
|
};
|
|
2570
2953
|
const close = () => {
|
|
@@ -2895,12 +3278,14 @@ async function createConsoleRuntime(deps) {
|
|
|
2895
3278
|
...deps.session !== undefined ? { session: deps.session } : {}
|
|
2896
3279
|
});
|
|
2897
3280
|
const createServer = deps.serve ?? createConsoleServer;
|
|
3281
|
+
const graphApi = deps.config.graphView?.enabled === true && deps.graphView !== undefined ? { readView: deps.graphView.readView } : undefined;
|
|
2898
3282
|
const server = await createServer({
|
|
2899
3283
|
readSnapshot,
|
|
2900
3284
|
config: deps.config,
|
|
2901
3285
|
...deps.log !== undefined ? { log: deps.log } : {},
|
|
2902
3286
|
...deps.console !== undefined ? { console: deps.console } : {},
|
|
2903
|
-
...deps.storageApi !== undefined ? { storageApi: deps.storageApi } : {}
|
|
3287
|
+
...deps.storageApi !== undefined ? { storageApi: deps.storageApi } : {},
|
|
3288
|
+
...graphApi !== undefined ? { graphApi } : {}
|
|
2904
3289
|
});
|
|
2905
3290
|
const watchFn = deps.watch ?? ((path, listener) => fsWatch(path, { persistent: false }, listener));
|
|
2906
3291
|
const watcher = watchSources([deps.sessionsDir, decisionsPath, backlogPath, deps.agentDir], () => {
|
|
@@ -4041,6 +4426,15 @@ function renderDoctor(input) {
|
|
|
4041
4426
|
if (reachable === 0) {
|
|
4042
4427
|
lines.push(" ⚠ Ningún runtime local alcanzable: todo el tráfico irá a frontier.");
|
|
4043
4428
|
}
|
|
4429
|
+
if (input.graphHealth !== undefined) {
|
|
4430
|
+
const h = input.graphHealth;
|
|
4431
|
+
lines.push(" graph health:");
|
|
4432
|
+
lines.push(` recuperación requerida: ${h.recoveryRequired ? "sí" : "no"}`);
|
|
4433
|
+
lines.push(` efectos desconocidos: ${h.unknownEffects}`);
|
|
4434
|
+
lines.push(` completaciones tardías: ${h.staleCompletions}`);
|
|
4435
|
+
lines.push(` conflictos: ${h.conflicts}`);
|
|
4436
|
+
lines.push(` kill switch: ${h.killSwitch ? "activo" : "inactivo"}`);
|
|
4437
|
+
}
|
|
4044
4438
|
return lines.join(`
|
|
4045
4439
|
`);
|
|
4046
4440
|
}
|