@jmanuelcorral/openteam 0.1.40 → 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/AGENTS.md +15 -0
- package/README.es.md +1 -1
- package/README.md +1 -1
- package/dist/cli.js +457 -37
- package/dist/commands/graph.d.ts +73 -0
- package/dist/commands/graph.d.ts.map +1 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/migrations.d.ts +27 -0
- package/dist/config/migrations.d.ts.map +1 -0
- package/dist/config/resolve.d.ts +34 -0
- package/dist/config/resolve.d.ts.map +1 -0
- package/dist/config/schema.d.ts +57 -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/context/redaction.d.ts +48 -0
- package/dist/context/redaction.d.ts.map +1 -0
- package/dist/context/schema.d.ts +115 -0
- package/dist/context/schema.d.ts.map +1 -0
- package/dist/context/types.d.ts +8 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/graph/events.d.ts +25 -0
- package/dist/graph/events.d.ts.map +1 -0
- package/dist/graph/ids.d.ts +5 -0
- package/dist/graph/ids.d.ts.map +1 -0
- package/dist/graph/planner.d.ts +15 -0
- package/dist/graph/planner.d.ts.map +1 -0
- package/dist/graph/policies.d.ts +34 -0
- package/dist/graph/policies.d.ts.map +1 -0
- package/dist/graph/privacyPolicy.d.ts +29 -0
- package/dist/graph/privacyPolicy.d.ts.map +1 -0
- package/dist/graph/reducer.d.ts +27 -0
- package/dist/graph/reducer.d.ts.map +1 -0
- package/dist/graph/replay.d.ts +20 -0
- package/dist/graph/replay.d.ts.map +1 -0
- package/dist/graph/reviewerPolicy.d.ts +27 -0
- package/dist/graph/reviewerPolicy.d.ts.map +1 -0
- package/dist/graph/schema.d.ts +220 -0
- package/dist/graph/schema.d.ts.map +1 -0
- package/dist/graph/types.d.ts +40 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/validate.d.ts +19 -0
- package/dist/graph/validate.d.ts.map +1 -0
- package/dist/index.js +46 -3
- package/dist/orchestrator/graphCancellation.d.ts +84 -0
- package/dist/orchestrator/graphCancellation.d.ts.map +1 -0
- package/dist/orchestrator/graphEffects.d.ts +63 -0
- package/dist/orchestrator/graphEffects.d.ts.map +1 -0
- package/dist/orchestrator/graphReview.d.ts +84 -0
- package/dist/orchestrator/graphReview.d.ts.map +1 -0
- package/dist/orchestrator/graphRuntime.d.ts +69 -0
- package/dist/orchestrator/graphRuntime.d.ts.map +1 -0
- package/dist/orchestrator/opencodeSessionAdapter.d.ts +117 -0
- package/dist/orchestrator/opencodeSessionAdapter.d.ts.map +1 -0
- package/dist/orchestrator/sdd.d.ts +38 -2
- package/dist/orchestrator/sdd.d.ts.map +1 -1
- package/dist/orchestrator/sddCompiler.d.ts +44 -0
- package/dist/orchestrator/sddCompiler.d.ts.map +1 -0
- package/dist/orchestrator/sessionReconciler.d.ts +74 -0
- package/dist/orchestrator/sessionReconciler.d.ts.map +1 -0
- package/dist/orchestrator/shadowComparator.d.ts +62 -0
- package/dist/orchestrator/shadowComparator.d.ts.map +1 -0
- 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/codec.d.ts +52 -0
- package/dist/storage/graph/codec.d.ts.map +1 -0
- package/dist/storage/graph/fsGraphJournal.d.ts +3 -0
- package/dist/storage/graph/fsGraphJournal.d.ts.map +1 -0
- package/dist/storage/graph/importLegacy.d.ts +62 -0
- package/dist/storage/graph/importLegacy.d.ts.map +1 -0
- package/dist/storage/graph/memoryGraphJournal.d.ts +11 -0
- package/dist/storage/graph/memoryGraphJournal.d.ts.map +1 -0
- package/dist/storage/graph/migrateLegacyBriefs.d.ts +32 -0
- package/dist/storage/graph/migrateLegacyBriefs.d.ts.map +1 -0
- package/dist/storage/graph/projections.d.ts +31 -0
- package/dist/storage/graph/projections.d.ts.map +1 -0
- package/dist/storage/graph/provider.d.ts +52 -0
- package/dist/storage/graph/provider.d.ts.map +1 -0
- package/dist/storage/graph/recovery.d.ts +38 -0
- package/dist/storage/graph/recovery.d.ts.map +1 -0
- package/dist/storage/graph/runRegistry.d.ts +17 -0
- package/dist/storage/graph/runRegistry.d.ts.map +1 -0
- package/dist/storage/graph/snapshot.d.ts +44 -0
- package/dist/storage/graph/snapshot.d.ts.map +1 -0
- package/dist/storage/graph/workspaceLease.d.ts +36 -0
- package/dist/storage/graph/workspaceLease.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/storage/graph/writer.d.ts +20 -0
- package/dist/storage/graph/writer.d.ts.map +1 -0
- package/dist/telemetry/graphProjection.d.ts +36 -0
- package/dist/telemetry/graphProjection.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 },
|
|
@@ -628,6 +670,14 @@ var PrivacyModeSchema = z3.enum([
|
|
|
628
670
|
"off"
|
|
629
671
|
]);
|
|
630
672
|
var BaselineModeSchema = z3.enum(["auto", "pinned"]);
|
|
673
|
+
var GraphModeSchema = z3.enum(["off", "shadow", "active"]);
|
|
674
|
+
var GraphConfigSchema = z3.object({
|
|
675
|
+
mode: GraphModeSchema.default("off"),
|
|
676
|
+
killSwitch: z3.boolean().default(false),
|
|
677
|
+
worktrees: z3.object({
|
|
678
|
+
enabled: z3.boolean().default(false)
|
|
679
|
+
}).default({ enabled: false })
|
|
680
|
+
});
|
|
631
681
|
var ConsoleHostSchema = z3.enum(["127.0.0.1", "localhost"]);
|
|
632
682
|
var ConsoleConfigSchema = z3.object({
|
|
633
683
|
host: ConsoleHostSchema.default("127.0.0.1"),
|
|
@@ -640,7 +690,10 @@ var ConsoleConfigSchema = z3.object({
|
|
|
640
690
|
terminal: z3.object({
|
|
641
691
|
enabled: z3.boolean().default(true),
|
|
642
692
|
pty: z3.boolean().optional()
|
|
643
|
-
}).default({ enabled: true })
|
|
693
|
+
}).default({ enabled: true }),
|
|
694
|
+
graphView: z3.object({
|
|
695
|
+
enabled: z3.boolean().default(false)
|
|
696
|
+
}).optional()
|
|
644
697
|
}).default({
|
|
645
698
|
host: "127.0.0.1",
|
|
646
699
|
port: 4599,
|
|
@@ -769,7 +822,8 @@ var OpenTeamConfigObjectSchema = z3.object({
|
|
|
769
822
|
privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive"),
|
|
770
823
|
console: ConsoleConfigSchema,
|
|
771
824
|
storage: StorageConfigSchema,
|
|
772
|
-
memory: MemoryConfigSchema.optional()
|
|
825
|
+
memory: MemoryConfigSchema.optional(),
|
|
826
|
+
graph: GraphConfigSchema.optional()
|
|
773
827
|
});
|
|
774
828
|
var OpenTeamConfigSchema = OpenTeamConfigObjectSchema;
|
|
775
829
|
function resolveMemorySemantic(config) {
|
|
@@ -1293,6 +1347,51 @@ code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px
|
|
|
1293
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}
|
|
1294
1348
|
.pty .pty-start:disabled{opacity:.5;cursor:default}
|
|
1295
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
|
+
`;
|
|
1296
1395
|
var CONSOLE_CLIENT_JS = `
|
|
1297
1396
|
(function(){
|
|
1298
1397
|
var authTok='';
|
|
@@ -1418,6 +1517,47 @@ var CONSOLE_CLIENT_JS = `
|
|
|
1418
1517
|
');startBtn.disabled=false}
|
|
1419
1518
|
}
|
|
1420
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
|
+
}
|
|
1421
1561
|
var bar=document.getElementById('tabbar');
|
|
1422
1562
|
if(bar){
|
|
1423
1563
|
bar.addEventListener('click',function(e){
|
|
@@ -1429,6 +1569,7 @@ var CONSOLE_CLIENT_JS = `
|
|
|
1429
1569
|
var saved='all';
|
|
1430
1570
|
try{saved=sessionStorage.getItem('openteam.tab')||'all'}catch(e){}
|
|
1431
1571
|
if(!selectTab(saved)){selectTab('all')}
|
|
1572
|
+
wireGraphDrill();
|
|
1432
1573
|
})();
|
|
1433
1574
|
`;
|
|
1434
1575
|
|
|
@@ -1678,14 +1819,15 @@ var STATUS_LABELS = {
|
|
|
1678
1819
|
function statusBadge(status) {
|
|
1679
1820
|
return `<span class="badge status-${status}">${STATUS_LABELS[status]}</span>`;
|
|
1680
1821
|
}
|
|
1681
|
-
function tabBar(sessions) {
|
|
1822
|
+
function tabBar(sessions, graphEnabled) {
|
|
1682
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>' : "";
|
|
1683
1825
|
const tabs = sessions.map((tab) => {
|
|
1684
1826
|
const id = escapeHtml(tab.sessionID);
|
|
1685
1827
|
const short = escapeHtml(shortSessionID(tab.sessionID));
|
|
1686
1828
|
return `<button type="button" class="tab" data-tab="${id}"><span class="dot status-${tab.status}"></span>${short}</button>`;
|
|
1687
1829
|
}).join("");
|
|
1688
|
-
return `<nav class="tabs" id="tabbar">${allTab}${tabs}</nav>`;
|
|
1830
|
+
return `<nav class="tabs" id="tabbar">${allTab}${graphTab}${tabs}</nav>`;
|
|
1689
1831
|
}
|
|
1690
1832
|
function shortSessionID(sessionID) {
|
|
1691
1833
|
return sessionID.length <= 12 ? sessionID : `${sessionID.slice(0, 12)}…`;
|
|
@@ -1747,10 +1889,182 @@ function sessionView(tab, terminalEnabled, ptyEnabled) {
|
|
|
1747
1889
|
].join("");
|
|
1748
1890
|
return `<div class="view" data-view="${escapeHtml(tab.sessionID)}" hidden>${body}</div>`;
|
|
1749
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
|
+
}
|
|
1750
2063
|
function renderConsoleHtml(state, options = {}) {
|
|
1751
2064
|
const refreshMs = options.refreshMs ?? 2000;
|
|
1752
2065
|
const terminalEnabled = options.terminalEnabled === true;
|
|
1753
2066
|
const ptyEnabled = options.ptyEnabled === true;
|
|
2067
|
+
const graphEnabled = options.graphView !== undefined;
|
|
1754
2068
|
const allBody = [
|
|
1755
2069
|
summaryPanel(state.cost),
|
|
1756
2070
|
totalsPanel(state.totals),
|
|
@@ -1764,10 +2078,12 @@ function renderConsoleHtml(state, options = {}) {
|
|
|
1764
2078
|
activityPanel(state.activity)
|
|
1765
2079
|
].join("");
|
|
1766
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) : "";
|
|
1767
2083
|
const sessionViews = state.sessionsDetail.map((tab) => sessionView(tab, terminalEnabled, ptyEnabled)).join("");
|
|
1768
|
-
const body = allView + sessionViews;
|
|
2084
|
+
const body = allView + graphView + sessionViews;
|
|
1769
2085
|
const session = state.session.id === undefined ? "" : ` · sesión ${escapeHtml(state.session.id)}`;
|
|
1770
|
-
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"' : ""}` : "";
|
|
1771
2087
|
return [
|
|
1772
2088
|
"<!doctype html>",
|
|
1773
2089
|
`<html lang="es" data-generated="${escapeHtml(state.generatedAt)}" data-refresh="${refreshMs}"${tokenAttr}>`,
|
|
@@ -1775,14 +2091,14 @@ function renderConsoleHtml(state, options = {}) {
|
|
|
1775
2091
|
'<meta charset="utf-8">',
|
|
1776
2092
|
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
1777
2093
|
"<title>openteam console</title>",
|
|
1778
|
-
`<style>${CONSOLE_STYLE}</style>`,
|
|
2094
|
+
`<style>${CONSOLE_STYLE}${graphEnabled ? GRAPH_STYLE : ""}</style>`,
|
|
1779
2095
|
"</head>",
|
|
1780
2096
|
"<body>",
|
|
1781
2097
|
"<header>",
|
|
1782
2098
|
"<h1>openteam console</h1>",
|
|
1783
2099
|
`<span class="meta">actualizado ${escapeHtml(state.generatedAt)}${session}</span>`,
|
|
1784
2100
|
"</header>",
|
|
1785
|
-
tabBar(state.sessionsDetail),
|
|
2101
|
+
tabBar(state.sessionsDetail, graphEnabled),
|
|
1786
2102
|
`<main>${body}</main>`,
|
|
1787
2103
|
`<script>${CONSOLE_CLIENT_JS}</script>`,
|
|
1788
2104
|
"</body>",
|
|
@@ -2114,7 +2430,7 @@ async function readJsonBody(request, limit) {
|
|
|
2114
2430
|
});
|
|
2115
2431
|
}
|
|
2116
2432
|
function parseConsolePath(path) {
|
|
2117
|
-
const match = /^\/console\/session\/([^/]+)\/(stream|input|permission|pty)$/.exec(path);
|
|
2433
|
+
const match = /^\/console\/session\/([^/]+)\/(stream|input|permission|pty|messages)$/.exec(path);
|
|
2118
2434
|
if (match === null) {
|
|
2119
2435
|
return;
|
|
2120
2436
|
}
|
|
@@ -2144,6 +2460,15 @@ data: {}
|
|
|
2144
2460
|
response.end();
|
|
2145
2461
|
}
|
|
2146
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
|
+
}
|
|
2147
2472
|
async function handleInput(request, response, sessionID, deps) {
|
|
2148
2473
|
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
2149
2474
|
if (endpoint === undefined) {
|
|
@@ -2211,7 +2536,9 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
2211
2536
|
response.writeHead(404, JSON_HEADERS).end(JSON.stringify({ error: "not found" }));
|
|
2212
2537
|
return true;
|
|
2213
2538
|
}
|
|
2214
|
-
|
|
2539
|
+
const readOnlyAction = route.action === "stream" || route.action === "messages";
|
|
2540
|
+
const channelEnabled = deps.terminalEnabled || readOnlyAction && deps.drilldownEnabled === true;
|
|
2541
|
+
if (!channelEnabled) {
|
|
2215
2542
|
response.writeHead(403, JSON_HEADERS).end(JSON.stringify({ error: "terminal disabled" }));
|
|
2216
2543
|
return true;
|
|
2217
2544
|
}
|
|
@@ -2223,6 +2550,10 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
2223
2550
|
await handleStream(request, response, route.sessionID, deps);
|
|
2224
2551
|
return true;
|
|
2225
2552
|
}
|
|
2553
|
+
if (route.action === "messages" && request.method === "GET") {
|
|
2554
|
+
await handleMessages(request, response, route.sessionID, deps);
|
|
2555
|
+
return true;
|
|
2556
|
+
}
|
|
2226
2557
|
if (route.action === "input" && request.method === "POST") {
|
|
2227
2558
|
await handleInput(request, response, route.sessionID, deps);
|
|
2228
2559
|
return true;
|
|
@@ -2239,10 +2570,15 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
2239
2570
|
return true;
|
|
2240
2571
|
}
|
|
2241
2572
|
|
|
2242
|
-
// src/web/
|
|
2573
|
+
// src/web/graphRoute.ts
|
|
2243
2574
|
var JSON_HEADERS2 = { "content-type": "application/json" };
|
|
2244
|
-
var
|
|
2245
|
-
|
|
2575
|
+
var GRAPH_SSE_EVENT = "graph";
|
|
2576
|
+
function graphSseMessage(json) {
|
|
2577
|
+
return `event: ${GRAPH_SSE_EVENT}
|
|
2578
|
+
data: ${json}
|
|
2579
|
+
|
|
2580
|
+
`;
|
|
2581
|
+
}
|
|
2246
2582
|
function bearerFromHeader2(request) {
|
|
2247
2583
|
const header = request.headers.authorization;
|
|
2248
2584
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
@@ -2255,6 +2591,39 @@ function authorized2(request, query, token) {
|
|
|
2255
2591
|
const provided = tokenFromQuery(query) ?? bearerFromHeader2(request);
|
|
2256
2592
|
return provided !== undefined && safeEqualToken(provided, token);
|
|
2257
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
|
+
}
|
|
2258
2627
|
function pathAllowed(path, allowPrefixes) {
|
|
2259
2628
|
if (allowPrefixes === undefined) {
|
|
2260
2629
|
return true;
|
|
@@ -2294,7 +2663,7 @@ async function readJsonBody2(request, limit) {
|
|
|
2294
2663
|
});
|
|
2295
2664
|
}
|
|
2296
2665
|
function sendJson(response, status, body) {
|
|
2297
|
-
response.writeHead(status,
|
|
2666
|
+
response.writeHead(status, JSON_HEADERS3).end(JSON.stringify(body));
|
|
2298
2667
|
}
|
|
2299
2668
|
async function handleRead(response, op, path, storage) {
|
|
2300
2669
|
if (op === "read") {
|
|
@@ -2368,7 +2737,7 @@ async function handleStorageRoute(request, response, url, deps) {
|
|
|
2368
2737
|
sendJson(response, 404, { error: "not found" });
|
|
2369
2738
|
return true;
|
|
2370
2739
|
}
|
|
2371
|
-
if (!
|
|
2740
|
+
if (!authorized3(request, url.searchParams, deps.token)) {
|
|
2372
2741
|
sendJson(response, 401, { error: "unauthorized" });
|
|
2373
2742
|
return true;
|
|
2374
2743
|
}
|
|
@@ -2409,12 +2778,12 @@ var SSE_HEADERS2 = {
|
|
|
2409
2778
|
"cache-control": "no-cache",
|
|
2410
2779
|
connection: "keep-alive"
|
|
2411
2780
|
};
|
|
2412
|
-
var
|
|
2781
|
+
var JSON_HEADERS4 = { "content-type": "application/json" };
|
|
2413
2782
|
var HTML_HEADERS = { "content-type": "text/html; charset=utf-8" };
|
|
2414
2783
|
function isAddressInUse(error) {
|
|
2415
2784
|
return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
|
|
2416
2785
|
}
|
|
2417
|
-
function
|
|
2786
|
+
function bearerFromHeader4(request) {
|
|
2418
2787
|
const header = request.headers.authorization;
|
|
2419
2788
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
2420
2789
|
return;
|
|
@@ -2423,16 +2792,19 @@ function bearerFromHeader3(request) {
|
|
|
2423
2792
|
return value.length === 0 ? undefined : value;
|
|
2424
2793
|
}
|
|
2425
2794
|
function readAuthorized(request, url, token) {
|
|
2426
|
-
const provided = tokenFromQuery(url.searchParams) ??
|
|
2795
|
+
const provided = tokenFromQuery(url.searchParams) ?? bearerFromHeader4(request);
|
|
2427
2796
|
return provided !== undefined && safeEqualToken(provided, token);
|
|
2428
2797
|
}
|
|
2429
2798
|
async function renderState(deps) {
|
|
2799
|
+
const graphView = deps.graphApi === undefined ? undefined : { projection: await deps.graphApi.readView() };
|
|
2430
2800
|
return renderConsoleHtml(buildConsoleState(await deps.readSnapshot()), {
|
|
2431
2801
|
refreshMs: deps.config.refreshMs,
|
|
2802
|
+
...graphView !== undefined ? { graphView } : {},
|
|
2432
2803
|
...deps.console !== undefined ? {
|
|
2433
2804
|
consoleToken: deps.console.token,
|
|
2434
2805
|
terminalEnabled: deps.console.terminalEnabled,
|
|
2435
|
-
...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 } : {}
|
|
2436
2808
|
} : {}
|
|
2437
2809
|
});
|
|
2438
2810
|
}
|
|
@@ -2478,13 +2850,21 @@ async function createConsoleServer(deps) {
|
|
|
2478
2850
|
resolveEndpoint,
|
|
2479
2851
|
createClient: deps.console.createClient,
|
|
2480
2852
|
...deps.console.ptyEnabled !== undefined ? { ptyEnabled: deps.console.ptyEnabled } : {},
|
|
2853
|
+
...deps.console.drilldownEnabled !== undefined ? { drilldownEnabled: deps.console.drilldownEnabled } : {},
|
|
2481
2854
|
...deps.console.createPtyClient !== undefined ? { createPtyClient: deps.console.createPtyClient } : {}
|
|
2482
2855
|
};
|
|
2856
|
+
const graphDeps = deps.graphApi === undefined ? undefined : {
|
|
2857
|
+
readView: deps.graphApi.readView,
|
|
2858
|
+
...deps.storageApi?.token !== undefined ? { remoteToken: deps.storageApi.token } : {}
|
|
2859
|
+
};
|
|
2483
2860
|
const handle = async (request, response) => {
|
|
2484
2861
|
const url2 = new URL(request.url ?? "/", "http://localhost");
|
|
2485
2862
|
if (consoleDeps !== undefined && await handleConsoleRoute(request, response, url2, consoleDeps)) {
|
|
2486
2863
|
return;
|
|
2487
2864
|
}
|
|
2865
|
+
if (graphDeps !== undefined && await handleGraphRoute(request, response, url2, graphDeps)) {
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2488
2868
|
if (deps.storageApi !== undefined && await handleStorageRoute(request, response, url2, deps.storageApi)) {
|
|
2489
2869
|
return;
|
|
2490
2870
|
}
|
|
@@ -2495,7 +2875,7 @@ async function createConsoleServer(deps) {
|
|
|
2495
2875
|
const path = url2.pathname;
|
|
2496
2876
|
const remoteToken = deps.storageApi?.token;
|
|
2497
2877
|
if (remoteToken !== undefined && (path === "/" || path === "/index.html" || path === "/api/state" || path === "/events") && !readAuthorized(request, url2, remoteToken)) {
|
|
2498
|
-
response.writeHead(401,
|
|
2878
|
+
response.writeHead(401, JSON_HEADERS4).end(JSON.stringify({ error: "unauthorized" }));
|
|
2499
2879
|
return;
|
|
2500
2880
|
}
|
|
2501
2881
|
if (path === "/" || path === "/index.html") {
|
|
@@ -2503,11 +2883,11 @@ async function createConsoleServer(deps) {
|
|
|
2503
2883
|
return;
|
|
2504
2884
|
}
|
|
2505
2885
|
if (path === "/api/state") {
|
|
2506
|
-
response.writeHead(200,
|
|
2886
|
+
response.writeHead(200, JSON_HEADERS4).end(await stateJson(deps));
|
|
2507
2887
|
return;
|
|
2508
2888
|
}
|
|
2509
2889
|
if (path === "/healthz") {
|
|
2510
|
-
response.writeHead(200,
|
|
2890
|
+
response.writeHead(200, JSON_HEADERS4).end(JSON.stringify({ ok: true }));
|
|
2511
2891
|
return;
|
|
2512
2892
|
}
|
|
2513
2893
|
if (path === "/favicon.ico") {
|
|
@@ -2517,6 +2897,9 @@ async function createConsoleServer(deps) {
|
|
|
2517
2897
|
if (path === "/events") {
|
|
2518
2898
|
response.writeHead(200, SSE_HEADERS2);
|
|
2519
2899
|
response.write(sseMessage(await stateJson(deps)));
|
|
2900
|
+
if (deps.graphApi !== undefined) {
|
|
2901
|
+
response.write(graphSseMessage(JSON.stringify(await deps.graphApi.readView())));
|
|
2902
|
+
}
|
|
2520
2903
|
clients.add(response);
|
|
2521
2904
|
request.on("close", () => {
|
|
2522
2905
|
clients.delete(response);
|
|
@@ -2557,8 +2940,12 @@ async function createConsoleServer(deps) {
|
|
|
2557
2940
|
return;
|
|
2558
2941
|
}
|
|
2559
2942
|
const message = sseMessage(await stateJson(deps));
|
|
2943
|
+
const graphMessage = deps.graphApi !== undefined ? graphSseMessage(JSON.stringify(await deps.graphApi.readView())) : undefined;
|
|
2560
2944
|
for (const response of clients) {
|
|
2561
2945
|
response.write(message);
|
|
2946
|
+
if (graphMessage !== undefined) {
|
|
2947
|
+
response.write(graphMessage);
|
|
2948
|
+
}
|
|
2562
2949
|
}
|
|
2563
2950
|
};
|
|
2564
2951
|
const close = () => {
|
|
@@ -2889,12 +3276,14 @@ async function createConsoleRuntime(deps) {
|
|
|
2889
3276
|
...deps.session !== undefined ? { session: deps.session } : {}
|
|
2890
3277
|
});
|
|
2891
3278
|
const createServer = deps.serve ?? createConsoleServer;
|
|
3279
|
+
const graphApi = deps.config.graphView?.enabled === true && deps.graphView !== undefined ? { readView: deps.graphView.readView } : undefined;
|
|
2892
3280
|
const server = await createServer({
|
|
2893
3281
|
readSnapshot,
|
|
2894
3282
|
config: deps.config,
|
|
2895
3283
|
...deps.log !== undefined ? { log: deps.log } : {},
|
|
2896
3284
|
...deps.console !== undefined ? { console: deps.console } : {},
|
|
2897
|
-
...deps.storageApi !== undefined ? { storageApi: deps.storageApi } : {}
|
|
3285
|
+
...deps.storageApi !== undefined ? { storageApi: deps.storageApi } : {},
|
|
3286
|
+
...graphApi !== undefined ? { graphApi } : {}
|
|
2898
3287
|
});
|
|
2899
3288
|
const watchFn = deps.watch ?? ((path, listener) => fsWatch(path, { persistent: false }, listener));
|
|
2900
3289
|
const watcher = watchSources([deps.sessionsDir, decisionsPath, backlogPath, deps.agentDir], () => {
|
|
@@ -5123,9 +5512,40 @@ async function runSetup(deps) {
|
|
|
5123
5512
|
}
|
|
5124
5513
|
}
|
|
5125
5514
|
|
|
5515
|
+
// src/config/migrations.ts
|
|
5516
|
+
var CURRENT_CONFIG_VERSION = 1;
|
|
5517
|
+
|
|
5518
|
+
class ConfigMigrationError extends Error {
|
|
5519
|
+
code;
|
|
5520
|
+
detail;
|
|
5521
|
+
constructor(code, detail) {
|
|
5522
|
+
super(`${code}: ${detail}`);
|
|
5523
|
+
this.name = "ConfigMigrationError";
|
|
5524
|
+
this.code = code;
|
|
5525
|
+
this.detail = detail;
|
|
5526
|
+
}
|
|
5527
|
+
}
|
|
5528
|
+
function migrateConfig(raw) {
|
|
5529
|
+
if (typeof raw !== "object" || raw === null) {
|
|
5530
|
+
return raw;
|
|
5531
|
+
}
|
|
5532
|
+
const record = raw;
|
|
5533
|
+
const version = record.configVersion;
|
|
5534
|
+
if (version === undefined) {
|
|
5535
|
+
return { ...record, configVersion: CURRENT_CONFIG_VERSION };
|
|
5536
|
+
}
|
|
5537
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version < 1) {
|
|
5538
|
+
throw new ConfigMigrationError("unknown-version", String(version));
|
|
5539
|
+
}
|
|
5540
|
+
if (version > CURRENT_CONFIG_VERSION) {
|
|
5541
|
+
throw new ConfigMigrationError("unsupported-version", String(version));
|
|
5542
|
+
}
|
|
5543
|
+
return record;
|
|
5544
|
+
}
|
|
5545
|
+
|
|
5126
5546
|
// src/config/load.ts
|
|
5127
5547
|
function loadOpenTeamConfig(raw) {
|
|
5128
|
-
return OpenTeamConfigSchema.parse(raw ?? {});
|
|
5548
|
+
return OpenTeamConfigSchema.parse(migrateConfig(raw ?? {}));
|
|
5129
5549
|
}
|
|
5130
5550
|
|
|
5131
5551
|
// src/memory/types.ts
|