@jmanuelcorral/openteam 0.10.0 → 0.12.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/AGENTS.md +1 -1
- package/README.es.md +8 -5
- package/README.md +16 -5
- package/dist/cli.js +539 -361
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/local.d.ts +13 -0
- package/dist/commands/local.d.ts.map +1 -1
- package/dist/console/assets.d.ts +2 -2
- package/dist/console/assets.d.ts.map +1 -1
- package/dist/console/configView.d.ts +9 -2
- package/dist/console/configView.d.ts.map +1 -1
- package/dist/console/render.d.ts +16 -20
- package/dist/console/render.d.ts.map +1 -1
- package/dist/index.js +139 -80
- package/dist/messages/commands.d.ts +14 -2
- package/dist/messages/commands.d.ts.map +1 -1
- package/dist/orchestrator/coordinator.d.ts +0 -15
- package/dist/orchestrator/coordinator.d.ts.map +1 -1
- package/dist/web/graphRoute.d.ts +18 -0
- package/dist/web/graphRoute.d.ts.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2040,6 +2040,13 @@ var FAILURE_CAUSE_RULES = [
|
|
|
2040
2040
|
"prompt-server"
|
|
2041
2041
|
]
|
|
2042
2042
|
];
|
|
2043
|
+
var TIMEOUT_NEEDLES = [
|
|
2044
|
+
"timed out",
|
|
2045
|
+
"timeout",
|
|
2046
|
+
"timedout",
|
|
2047
|
+
"etimedout",
|
|
2048
|
+
"deadline exceeded"
|
|
2049
|
+
];
|
|
2043
2050
|
function matchFailureCause(lowerReason) {
|
|
2044
2051
|
for (const [needles, cls] of FAILURE_CAUSE_RULES) {
|
|
2045
2052
|
if (needles.some((needle) => lowerReason.includes(needle))) {
|
|
@@ -2050,7 +2057,7 @@ function matchFailureCause(lowerReason) {
|
|
|
2050
2057
|
}
|
|
2051
2058
|
function classifyFailureForTelemetry(failureReason, failureStage) {
|
|
2052
2059
|
const lower = failureReason.toLowerCase();
|
|
2053
|
-
if (
|
|
2060
|
+
if (TIMEOUT_NEEDLES.some((needle) => lower.includes(needle))) {
|
|
2054
2061
|
return "prompt-timeout";
|
|
2055
2062
|
}
|
|
2056
2063
|
if (lower.includes("did not include a session id") || lower.includes("missing session")) {
|
|
@@ -3517,91 +3524,80 @@ var RESTART_REQUIRED_KEYS = [
|
|
|
3517
3524
|
"console.port"
|
|
3518
3525
|
];
|
|
3519
3526
|
|
|
3527
|
+
// src/web/graphRoute.ts
|
|
3528
|
+
var GRAPH_DISABLED_REMEDIATION = 'Agent tree is off. In .opencode/openteam.json set graph.mode to a value other than "off" and console.graphView.enabled to true, then restart the console.';
|
|
3529
|
+
var GRAPH_DISABLED_ENVELOPE = {
|
|
3530
|
+
disabled: true,
|
|
3531
|
+
reason: "graph-disabled",
|
|
3532
|
+
remediation: GRAPH_DISABLED_REMEDIATION
|
|
3533
|
+
};
|
|
3534
|
+
var JSON_HEADERS = { "content-type": "application/json" };
|
|
3535
|
+
var GRAPH_SSE_EVENT = "graph";
|
|
3536
|
+
function graphSseMessage(json) {
|
|
3537
|
+
return `event: ${GRAPH_SSE_EVENT}
|
|
3538
|
+
data: ${json}
|
|
3539
|
+
|
|
3540
|
+
`;
|
|
3541
|
+
}
|
|
3542
|
+
function bearerFromHeader(request) {
|
|
3543
|
+
const header = request.headers.authorization;
|
|
3544
|
+
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
3545
|
+
return;
|
|
3546
|
+
}
|
|
3547
|
+
const value = header.slice("Bearer ".length).trim();
|
|
3548
|
+
return value.length === 0 ? undefined : value;
|
|
3549
|
+
}
|
|
3550
|
+
function authorized(request, query, token) {
|
|
3551
|
+
const provided = tokenFromQuery(query) ?? bearerFromHeader(request);
|
|
3552
|
+
return provided !== undefined && safeEqualToken(provided, token);
|
|
3553
|
+
}
|
|
3554
|
+
async function handleGraphRoute(request, response, url, deps) {
|
|
3555
|
+
if (url.pathname !== "/api/graph") {
|
|
3556
|
+
return false;
|
|
3557
|
+
}
|
|
3558
|
+
if (request.method !== "GET") {
|
|
3559
|
+
response.writeHead(405, JSON_HEADERS).end(JSON.stringify({ error: "method not allowed" }));
|
|
3560
|
+
return true;
|
|
3561
|
+
}
|
|
3562
|
+
if (deps.remoteToken !== undefined && !authorized(request, url.searchParams, deps.remoteToken)) {
|
|
3563
|
+
response.writeHead(401, JSON_HEADERS).end(JSON.stringify({ error: "unauthorized" }));
|
|
3564
|
+
return true;
|
|
3565
|
+
}
|
|
3566
|
+
const view = await deps.readView();
|
|
3567
|
+
response.writeHead(200, JSON_HEADERS).end(JSON.stringify(view));
|
|
3568
|
+
return true;
|
|
3569
|
+
}
|
|
3570
|
+
|
|
3520
3571
|
// src/console/assets.ts
|
|
3521
3572
|
var CONSOLE_STYLE = `
|
|
3522
|
-
:root{color-scheme:dark;--bg:#
|
|
3523
|
-
*{box-sizing:border-box}
|
|
3524
|
-
body{
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
.
|
|
3531
|
-
.
|
|
3532
|
-
.
|
|
3533
|
-
.
|
|
3534
|
-
.
|
|
3535
|
-
.
|
|
3536
|
-
.
|
|
3537
|
-
.
|
|
3538
|
-
.
|
|
3539
|
-
|
|
3540
|
-
th,td{text-align:left;padding:
|
|
3541
|
-
|
|
3542
|
-
.
|
|
3543
|
-
.
|
|
3544
|
-
.
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
.tag{color:var(--frontier);font-size:12px}
|
|
3551
|
-
.kind{display:inline-block;min-width:66px;color:var(--muted);font-size:12px}
|
|
3552
|
-
code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px 5px;border-radius:4px}
|
|
3553
|
-
.mono{font-family:ui-monospace,Consolas,monospace;font-size:12px}
|
|
3554
|
-
.tabs{display:flex;gap:6px;padding:12px 24px 0;flex-wrap:wrap;border-bottom:1px solid var(--line)}
|
|
3555
|
-
.tabs .tab{background:var(--panel);color:var(--fg);border:1px solid var(--line);border-bottom:none;border-radius:8px 8px 0 0;padding:8px 14px;cursor:pointer;font:inherit;display:flex;align-items:center;gap:6px}
|
|
3556
|
-
.tabs .tab.active{background:var(--bg);border-color:var(--local);color:var(--fg)}
|
|
3557
|
-
.dot{width:8px;height:8px;border-radius:50%;display:inline-block;background:var(--muted)}
|
|
3558
|
-
.badge{font-size:11px;padding:1px 8px;border-radius:10px;border:1px solid var(--line);color:var(--muted);vertical-align:middle}
|
|
3559
|
-
.status-running{background:var(--good)}
|
|
3560
|
-
.badge.status-running{color:var(--good);border-color:var(--good)}
|
|
3561
|
-
.status-idle{background:var(--muted)}
|
|
3562
|
-
.badge.status-idle{color:var(--muted)}
|
|
3563
|
-
.status-waiting-input{background:var(--frontier)}
|
|
3564
|
-
.badge.status-waiting-input{color:var(--frontier);border-color:var(--frontier)}
|
|
3565
|
-
.status-ended{background:#6e7681}
|
|
3566
|
-
.badge.status-ended{color:#6e7681}
|
|
3567
|
-
.term .termlog{background:#0b0d11;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap}
|
|
3568
|
-
.term .tl{padding:1px 0}
|
|
3569
|
-
.term .tl.input{color:var(--local)}
|
|
3570
|
-
.term .tl.tool{color:var(--frontier)}
|
|
3571
|
-
.term .tl.status{color:var(--muted)}
|
|
3572
|
-
.term .tl.error{color:#f85149}
|
|
3573
|
-
.term .tl.permission{color:var(--frontier);font-weight:600}
|
|
3574
|
-
.term form.terminput{display:flex;gap:8px;margin-top:10px}
|
|
3575
|
-
.term form.terminput input{flex:1;background:#0b0d11;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
|
|
3576
|
-
.term form.terminput button{background:var(--local);color:#04122b;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600}
|
|
3577
|
-
.pty .ptylog{background:#05070a;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap;color:#c8d1dc}
|
|
3578
|
-
.pty form.ptyinput{display:flex;gap:8px;margin-top:10px}
|
|
3579
|
-
.pty form.ptyinput input{flex:1;background:#05070a;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
|
|
3580
|
-
.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}
|
|
3581
|
-
.pty .pty-start:disabled{opacity:.5;cursor:default}
|
|
3582
|
-
.config-panel{grid-column:1/-1}
|
|
3583
|
-
.config-form{display:flex;flex-direction:column;gap:12px}
|
|
3584
|
-
.config-note{margin:0;font-size:12px;color:var(--muted)}
|
|
3585
|
-
.config-note.config-overlay{color:var(--frontier)}
|
|
3586
|
-
.reload-legend h3,.config-summary h4{font-size:12px;margin:0 0 6px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
|
|
3587
|
-
.reload-table{width:100%}
|
|
3588
|
-
.reload-table th{white-space:nowrap;vertical-align:top;width:1%}
|
|
3589
|
-
.reload-table th.reload-hot{color:var(--good)}
|
|
3590
|
-
.reload-table th.reload-restart{color:var(--frontier)}
|
|
3591
|
-
.config-label{font-size:12px;color:var(--muted)}
|
|
3592
|
-
.config-json{width:100%;min-height:280px;resize:vertical;background:#0b0d11;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:10px;font-family:ui-monospace,Consolas,monospace;font-size:12px;line-height:1.5}
|
|
3593
|
-
.config-actions{display:flex;gap:8px}
|
|
3594
|
-
.config-save{background:var(--good);color:#08110a;border:0;border-radius:8px;padding:8px 16px;font-weight:600;cursor:pointer}
|
|
3595
|
-
.config-save:disabled{opacity:.5;cursor:default}
|
|
3596
|
-
.config-result:empty{display:none}
|
|
3597
|
-
.config-summary{border:1px solid var(--line);border-radius:8px;padding:12px;margin-top:4px}
|
|
3598
|
-
.config-ok{margin:0 0 8px;color:var(--good);font-weight:600}
|
|
3599
|
-
.config-err{margin:0;color:var(--frontier);font-weight:600}
|
|
3600
|
-
.config-applied h4{color:var(--good)}
|
|
3601
|
-
.config-restart h4{color:var(--frontier)}
|
|
3602
|
-
.config-warn{margin:0 0 6px;color:var(--frontier);font-size:13px}
|
|
3603
|
-
.config-summary ul{margin:0;padding-left:18px}
|
|
3604
|
-
.config-summary li{padding:2px 0;border:0}
|
|
3573
|
+
:root{color-scheme:dark;--bg:#0f0f0f;--surface:#17181b;--surface-2:#1e2024;--panel:var(--surface);--fg:#eceef1;--muted:#a2a8b2;--faint:#6b7079;--border:rgba(255,255,255,.08);--border-strong:rgba(255,255,255,.16);--line:var(--border);--accent:#35c07a;--accent-bg:rgba(53,192,122,.12);--accent-fg:#57d492;--good:var(--accent-fg);--local:#6aa6f5;--frontier:#e0a53a;--warn:#e0a53a;--warn-bg:rgba(224,165,58,.12);--danger:#f0687f;--danger-bg:rgba(240,104,127,.12);--info:#6aa6f5;--info-bg:rgba(106,166,245,.12);--font-sans:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;--font-mono:"JetBrains Mono","IBM Plex Mono",ui-monospace,"SFMono-Regular",Consolas,monospace;--shadow-pop:0 8px 28px rgba(0,0,0,.5);--sidebar-w:248px}
|
|
3574
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
3575
|
+
html,body{height:100%}
|
|
3576
|
+
body{background:var(--bg);color:var(--fg);font:14px/1.55 var(--font-sans);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-variant-numeric:tabular-nums}
|
|
3577
|
+
.mono,code,.hash{font-family:var(--font-mono);font-variant-numeric:tabular-nums}
|
|
3578
|
+
button{font:inherit;cursor:pointer}
|
|
3579
|
+
button:focus-visible,input:focus-visible,textarea:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
|
|
3580
|
+
::selection{background:var(--accent-bg)}
|
|
3581
|
+
.app{display:grid;grid-template-columns:var(--sidebar-w) 1fr;grid-template-rows:100vh;min-height:100vh}
|
|
3582
|
+
.sidebar{background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;min-height:0}
|
|
3583
|
+
.brand{display:flex;align-items:center;gap:10px;padding:18px 18px 16px;border-bottom:1px solid var(--border)}
|
|
3584
|
+
.logo{width:26px;height:26px;border-radius:6px;background:var(--accent);color:#fff;display:grid;place-items:center;flex:none}
|
|
3585
|
+
.logo svg{width:15px;height:15px}.brand .name{font-weight:600;letter-spacing:-.01em}.brand .tag{font:10px/1 var(--font-mono);letter-spacing:.08em;color:var(--accent-fg);background:var(--accent-bg);padding:3px 6px;border-radius:4px;margin-left:auto;text-transform:uppercase}
|
|
3586
|
+
.nav{padding:10px;display:flex;flex-direction:column;gap:2px}.nav-label{font:10px/1 var(--font-mono);letter-spacing:.12em;text-transform:uppercase;color:var(--faint);padding:14px 8px 6px}.nav-item{display:flex;align-items:center;gap:10px;padding:9px 10px;border-radius:7px;color:var(--muted);background:transparent;border:0;width:100%;text-align:left;letter-spacing:-.005em;transition:background .12s,color .12s}.nav-item svg{width:17px;height:17px;flex:none}.nav-item .count{margin-left:auto;font:11px/1 var(--font-mono);color:var(--faint)}.nav-item .pill{margin-left:auto}.nav-item:hover{background:var(--surface-2);color:var(--fg)}.nav-item[aria-current="page"]{background:var(--accent-bg);color:var(--accent-fg);font-weight:550}.nav-item[aria-current="page"] .count{color:var(--accent-fg)}
|
|
3587
|
+
.sidebar-foot{margin-top:auto;padding:14px 16px;border-top:1px solid var(--border);font-size:12px;color:var(--muted);display:flex;flex-direction:column;gap:8px}.host-row{display:flex;align-items:center;gap:8px}.dot{width:8px;height:8px;border-radius:50%;display:inline-block;flex:none;background:var(--muted)}.dot.live,.dot.status-running{background:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)}.dot.warn,.dot.status-waiting-input{background:var(--warn)}.dot.down{background:var(--danger)}.dot.status-idle{background:var(--muted)}.dot.status-ended{background:#6e7681}
|
|
3588
|
+
.main{min-width:0;min-height:0;display:flex;flex-direction:column}.topbar{height:56px;flex:none;border-bottom:1px solid var(--border);background:color-mix(in srgb,var(--surface) 82%,transparent);backdrop-filter:blur(8px);display:flex;align-items:center;gap:14px;padding:0 20px}.topbar h1{font-size:15px;font-weight:600;letter-spacing:-.01em}.refresh,.token-chip{display:inline-flex;align-items:center;gap:7px;font:11px/1 var(--font-mono);color:var(--muted);padding:5px 9px;border:1px solid var(--border);border-radius:6px;background:var(--surface)}.refresh .beat{width:6px;height:6px;border-radius:50%;background:var(--accent);animation:beat 2s ease-in-out infinite}@keyframes beat{0%,100%{opacity:1}50%{opacity:.25}}.spacer{margin-left:auto}.icon-btn{width:34px;height:34px;border-radius:7px;border:1px solid var(--border);background:var(--surface);color:var(--muted);display:grid;place-items:center;transition:background .12s,color .12s,border-color .12s}.icon-btn:hover{background:var(--surface-2);color:var(--fg);border-color:var(--border-strong)}.icon-btn svg{width:17px;height:17px}.token-chip b{color:var(--fg);font-weight:600}
|
|
3589
|
+
.scroll{flex:1;overflow-y:auto;min-height:0}.screen{display:none;padding:22px 24px 40px;max-width:1320px}.screen.active{display:block}.screen[hidden]{display:none!important}.screen-head{margin-bottom:18px}.screen-head h2{font-size:19px;font-weight:600;letter-spacing:-.02em;display:flex;align-items:center;gap:8px}.screen-head p{color:var(--muted);font-size:13px;margin-top:3px;max-width:74ch;text-wrap:pretty}.screen-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}.screen-grid .wide,.panel.wide{grid-column:1/-1}
|
|
3590
|
+
.panel,.card{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:16px}.panel h2,.panel h3,.card-head h3{font-size:13px;margin:0 0 12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}.card-head{display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--border);margin:-16px -16px 14px}.card-head .sub{font:11px/1 var(--font-mono);color:var(--faint);margin-left:auto}.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}.stat{background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:15px 16px;display:flex;flex-direction:column;gap:2px}.stat .k{font:11px/1 var(--font-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--faint)}.stat .v{font-size:20px;font-weight:600;letter-spacing:-.02em;margin-top:6px}.stat .d{font-size:12px;color:var(--muted);margin-top:4px;display:flex;align-items:center;gap:6px}.good{color:var(--good)}.local{color:var(--local)}.frontier{color:var(--frontier)}.muted{color:var(--muted)}.faint{color:var(--faint)}
|
|
3591
|
+
table{width:100%;border-collapse:collapse;font-size:13px}th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--border)}th{font:11px/1 var(--font-mono);letter-spacing:.05em;text-transform:uppercase;color:var(--faint);font-weight:500}tbody tr:last-child td{border-bottom:0}tbody tr:hover{background:var(--surface-2)}td.num{text-align:right;font-family:var(--font-mono)}.hash{color:var(--faint)}
|
|
3592
|
+
.pill,.badge{display:inline-flex;align-items:center;gap:5px;font:11px/1 var(--font-mono);letter-spacing:.02em;padding:3px 8px;border-radius:999px;border:1px solid transparent}.pill.local,.badge.status-running{color:var(--accent-fg);background:var(--accent-bg);border-color:var(--accent-bg)}.pill.frontier{color:var(--info);background:var(--info-bg)}.pill.warn,.badge.status-waiting-input{color:var(--warn);background:var(--warn-bg);border-color:var(--warn-bg)}.pill.danger{color:var(--danger);background:var(--danger-bg)}.pill.idle,.badge.status-idle,.badge.status-ended{color:var(--muted);background:var(--surface-2)}
|
|
3593
|
+
.progress{height:10px;background:var(--surface-2);border-radius:6px;overflow:hidden;border:1px solid var(--border)}.progress .bar{height:100%;background:var(--accent)}ul.items,ul.timeline{list-style:none;margin:8px 0 0;padding:0;max-height:320px;overflow:auto}ul.items li,ul.timeline li{padding:6px 0;border-bottom:1px solid var(--border)}ul.items li.done{color:var(--muted)}ul.items .box{font-family:var(--font-mono)}.who{color:var(--local)}.tag{color:var(--frontier);font-size:12px}.kind{display:inline-block;min-width:66px;color:var(--muted);font-size:12px}code{background:var(--bg);padding:1px 5px;border-radius:4px}
|
|
3594
|
+
.logs-layout{display:grid;grid-template-columns:220px 1fr;gap:16px;align-items:start;margin-bottom:16px}.session-list{padding:6px;max-height:70vh;overflow-y:auto}.session-btn{display:flex;align-items:center;gap:8px;width:100%;padding:8px 10px;border-radius:7px;border:0;background:transparent;color:var(--muted);text-align:left;font:12px/1.3 var(--font-mono)}.session-btn:hover{background:var(--surface-2);color:var(--fg)}.session-btn.sel,.session-btn[aria-selected="true"]{background:var(--accent-bg);color:var(--accent-fg)}.session-btn .sid{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-btn .n{margin-left:auto;color:var(--faint)}.session-detail{display:flex;flex-direction:column;gap:16px}.session-detail[hidden]{display:none}.empty-state{padding:28px;text-align:center;color:var(--muted)}
|
|
3595
|
+
.term .termlog,.pty .ptylog{background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:var(--font-mono);font-size:12px;white-space:pre-wrap}.term .tl{padding:1px 0}.term .tl.input{color:var(--local)}.term .tl.tool{color:var(--frontier)}.term .tl.status{color:var(--muted)}.term .tl.error{color:var(--danger)}.term .tl.permission{color:var(--frontier);font-weight:600}.term form.terminput,.pty form.ptyinput{display:flex;gap:8px;margin-top:10px}.term form.terminput input,.pty form.ptyinput input{flex:1;background:var(--bg);color:var(--fg);border:1px solid var(--border);border-radius:8px;padding:8px 10px;font:inherit}.term form.terminput button{background:var(--local);color:#04122b;border:none;border-radius:8px;padding:8px 16px;font:inherit;font-weight:600}.pty .ptylog{background:#05070a;color:#c8d1dc}.pty form.ptyinput button,.pty .pty-start{background:var(--frontier);color:#1a1200;border:none;border-radius:8px;padding:8px 16px;font:inherit;font-weight:600;margin-top:8px}.pty .pty-start:disabled{opacity:.5;cursor:default}
|
|
3596
|
+
.config-panel{grid-column:1/-1}.config-form{display:flex;flex-direction:column;gap:12px}.config-note{margin:0;font-size:12px;color:var(--muted)}.config-note.config-overlay{color:var(--frontier)}.reload-legend h3,.config-summary h4{font-size:12px;margin:0 0 6px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}.reload-table{width:100%}.reload-table th{white-space:nowrap;vertical-align:top;width:1%}.reload-table th.reload-hot{color:var(--good)}.reload-table th.reload-restart{color:var(--frontier)}.config-label{font-size:12px;color:var(--muted)}.config-json{width:100%;min-height:280px;resize:vertical;background:var(--bg);color:var(--fg);border:1px solid var(--border-strong);border-radius:8px;padding:10px;font-family:var(--font-mono);font-size:12px;line-height:1.5}.config-actions{display:flex;gap:8px}.config-save{background:var(--accent);color:#fff;border:0;border-radius:8px;padding:8px 16px;font-weight:600;cursor:pointer}.config-save:disabled{opacity:.5;cursor:default}.config-result:empty{display:none}.config-summary{border:1px solid var(--border);border-radius:8px;padding:12px;margin-top:4px}.config-ok{margin:0 0 8px;color:var(--good);font-weight:600}.config-err{margin:0;color:var(--frontier);font-weight:600}.config-applied h4{color:var(--good)}.config-restart h4{color:var(--frontier)}.config-warn{margin:0 0 6px;color:var(--frontier);font-size:13px}.config-summary ul{margin:0;padding-left:18px}.config-summary li{padding:2px 0;border:0}
|
|
3597
|
+
.diag-banner{border-bottom:1px solid color-mix(in srgb,var(--warn) 40%,transparent);background:var(--warn-bg);color:var(--warn);padding:10px 20px;font-size:13px}.graph-panel{min-height:240px}.graph-disabled .hint{margin-top:6px}.scrim{display:none}.menu-btn{display:none}
|
|
3598
|
+
@media (max-width:900px){.app{grid-template-columns:1fr}.sidebar{position:fixed;inset:0 auto 0 0;width:280px;z-index:50;transform:translateX(-100%);transition:transform .22s ease}body.nav-open .sidebar{transform:translateX(0);box-shadow:var(--shadow-pop)}body.nav-open .scrim{display:block;position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:40}.menu-btn{display:grid}.logs-layout{grid-template-columns:1fr}.grid{grid-template-columns:1fr}.token-chip .lbl{display:none}}
|
|
3599
|
+
@media (max-width:560px){.screen{padding:16px 14px 32px}.screen-grid{grid-template-columns:1fr}.topbar{padding:0 14px}.refresh{display:none}}
|
|
3600
|
+
@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important}}
|
|
3605
3601
|
`;
|
|
3606
3602
|
var GRAPH_STYLE = `
|
|
3607
3603
|
.graph-wrap{overflow:auto;background:#0b0d11;border:1px solid var(--line);border-radius:8px;padding:8px}
|
|
@@ -3671,24 +3667,69 @@ var CONSOLE_CLIENT_JS = `
|
|
|
3671
3667
|
var ms=parseInt(document.documentElement.getAttribute('data-refresh')||'2000',10);
|
|
3672
3668
|
setInterval(function(){reloadIfChanged(current)},isNaN(ms)?2000:Math.max(250,ms));
|
|
3673
3669
|
|
|
3674
|
-
function
|
|
3675
|
-
var
|
|
3676
|
-
|
|
3677
|
-
|
|
3670
|
+
function configActive(){
|
|
3671
|
+
var s=document.getElementById('screen-config');
|
|
3672
|
+
return !!(s&&!s.hidden);
|
|
3673
|
+
}
|
|
3674
|
+
function wireLogsScreen(screen){
|
|
3675
|
+
if(screen.__logsWired){return}
|
|
3676
|
+
screen.__logsWired=true;
|
|
3677
|
+
var buttons=screen.querySelectorAll('[data-session-target]');
|
|
3678
|
+
var cards=screen.querySelectorAll('[data-session-view]');
|
|
3679
|
+
function showSession(id){
|
|
3680
|
+
var matched=false;
|
|
3681
|
+
for(var i=0;i<buttons.length;i++){
|
|
3682
|
+
var on=buttons[i].getAttribute('data-session-target')===id;
|
|
3683
|
+
buttons[i].classList.toggle('sel',on);
|
|
3684
|
+
buttons[i].setAttribute('aria-selected',on?'true':'false');
|
|
3685
|
+
}
|
|
3686
|
+
for(var j=0;j<cards.length;j++){
|
|
3687
|
+
var show=cards[j].getAttribute('data-session-view')===id;
|
|
3688
|
+
cards[j].hidden=!show;
|
|
3689
|
+
if(show){matched=true;connectTerminal(cards[j],id)}
|
|
3690
|
+
}
|
|
3691
|
+
return matched;
|
|
3692
|
+
}
|
|
3693
|
+
for(var k=0;k<buttons.length;k++){
|
|
3694
|
+
buttons[k].addEventListener('click',function(e){
|
|
3695
|
+
var id=e.currentTarget.getAttribute('data-session-target');
|
|
3696
|
+
if(id){showSession(id)}
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
if(buttons.length>0){
|
|
3700
|
+
showSession(buttons[0].getAttribute('data-session-target'));
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
function selectScreen(name){
|
|
3704
|
+
var screens=document.querySelectorAll('.screen');
|
|
3705
|
+
var items=document.querySelectorAll('[data-screen]');
|
|
3678
3706
|
var matched=false;
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3707
|
+
var titleText='';
|
|
3708
|
+
var activeScreen=null;
|
|
3709
|
+
for(var i=0;i<screens.length;i++){
|
|
3710
|
+
var show=screens[i].id==='screen-'+name;
|
|
3711
|
+
screens[i].classList.toggle('active',show);
|
|
3712
|
+
screens[i].hidden=!show;
|
|
3713
|
+
if(show){matched=true;activeScreen=screens[i]}
|
|
3683
3714
|
}
|
|
3684
3715
|
if(!matched){return false}
|
|
3685
|
-
|
|
3716
|
+
for(var j=0;j<items.length;j++){
|
|
3717
|
+
var on=items[j].getAttribute('data-screen')===name;
|
|
3718
|
+
if(on){
|
|
3719
|
+
items[j].setAttribute('aria-current','page');
|
|
3720
|
+
titleText=items[j].getAttribute('data-title')||'';
|
|
3721
|
+
}else{
|
|
3722
|
+
items[j].removeAttribute('aria-current');
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
var title=document.getElementById('screen-title');
|
|
3726
|
+
if(title){title.textContent=titleText}
|
|
3727
|
+
if(name==='config'&&activeScreen){loadConfig(activeScreen)}
|
|
3728
|
+
if(name==='logs'&&activeScreen){wireLogsScreen(activeScreen)}
|
|
3729
|
+
try{localStorage.setItem('openteam.screen',name)}catch(e){}
|
|
3730
|
+
document.body.classList.remove('nav-open');
|
|
3686
3731
|
return true;
|
|
3687
3732
|
}
|
|
3688
|
-
function configActive(){
|
|
3689
|
-
var v=document.querySelector('main .view[data-view="config"]');
|
|
3690
|
-
return !!(v&&!v.hidden);
|
|
3691
|
-
}
|
|
3692
3733
|
function esc(s){return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}
|
|
3693
3734
|
// Config editor (W7-S09 / #193). The config values travel only over the
|
|
3694
3735
|
// token-gated /api/config, never in this open page, so the browser fetches
|
|
@@ -3827,7 +3868,7 @@ var CONSOLE_CLIENT_JS = `
|
|
|
3827
3868
|
if(!token){return}
|
|
3828
3869
|
var drill=document.querySelector('.graph-drill');
|
|
3829
3870
|
if(!drill){return}
|
|
3830
|
-
var gview=drill.closest('.
|
|
3871
|
+
var gview=drill.closest('.screen');
|
|
3831
3872
|
if(!gview){return}
|
|
3832
3873
|
var log=drill.querySelector('.drilllog');
|
|
3833
3874
|
var title=drill.querySelector('.drill-title');
|
|
@@ -3861,17 +3902,21 @@ var CONSOLE_CLIENT_JS = `
|
|
|
3861
3902
|
if(t&&t.getAttribute&&t.getAttribute('data-session')){open(t.getAttribute('data-session'))}
|
|
3862
3903
|
});
|
|
3863
3904
|
}
|
|
3864
|
-
var
|
|
3865
|
-
if(
|
|
3866
|
-
|
|
3905
|
+
var nav=document.querySelector('.nav');
|
|
3906
|
+
if(nav){
|
|
3907
|
+
nav.addEventListener('click',function(e){
|
|
3867
3908
|
var t=e.target;
|
|
3868
|
-
while(t&&t!==
|
|
3869
|
-
if(t&&t.getAttribute('data-
|
|
3909
|
+
while(t&&t!==nav&&!(t.getAttribute&&t.getAttribute('data-screen'))){t=t.parentNode}
|
|
3910
|
+
if(t&&t.getAttribute&&t.getAttribute('data-screen')){selectScreen(t.getAttribute('data-screen'))}
|
|
3870
3911
|
});
|
|
3871
3912
|
}
|
|
3872
|
-
var
|
|
3873
|
-
|
|
3874
|
-
|
|
3913
|
+
var toggle=document.querySelector('[data-toggle-nav]');
|
|
3914
|
+
if(toggle){toggle.addEventListener('click',function(){document.body.classList.toggle('nav-open')})}
|
|
3915
|
+
var close=document.querySelector('[data-close-nav]');
|
|
3916
|
+
if(close){close.addEventListener('click',function(){document.body.classList.remove('nav-open')})}
|
|
3917
|
+
var saved='tree';
|
|
3918
|
+
try{saved=localStorage.getItem('openteam.screen')||'tree'}catch(e){}
|
|
3919
|
+
if(!selectScreen(saved)){selectScreen('tree')}
|
|
3875
3920
|
wireGraphDrill();
|
|
3876
3921
|
})();
|
|
3877
3922
|
`;
|
|
@@ -3901,6 +3946,13 @@ function avgMs(stats) {
|
|
|
3901
3946
|
function agentModel(agent) {
|
|
3902
3947
|
return agent.model === undefined ? "inherits default" : `${agent.model.providerID}/${agent.model.modelID}`;
|
|
3903
3948
|
}
|
|
3949
|
+
function domIdPart(value) {
|
|
3950
|
+
const cleaned = value.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3951
|
+
return cleaned.length === 0 ? "session" : cleaned;
|
|
3952
|
+
}
|
|
3953
|
+
function panelId(base, idSuffix) {
|
|
3954
|
+
return idSuffix === undefined ? base : `${base}-${idSuffix}`;
|
|
3955
|
+
}
|
|
3904
3956
|
function summaryPanel(cost) {
|
|
3905
3957
|
return [
|
|
3906
3958
|
'<section class="panel" id="panel-summary">',
|
|
@@ -3979,10 +4031,11 @@ function teamPanel(team) {
|
|
|
3979
4031
|
function routeRow(route) {
|
|
3980
4032
|
return `<tr><td>${route.tier}</td><td class="${route.routeKind}">${route.routeKind}</td><td>${escapeHtml(route.model)}</td><td class="hash">${escapeHtml(route.promptHash)}</td><td>${usd(route.estimatedCostUSD)}</td><td class="good">${usd(route.estimatedSavingsUSD)}</td></tr>`;
|
|
3981
4033
|
}
|
|
3982
|
-
function routesPanel(routes) {
|
|
4034
|
+
function routesPanel(routes, idSuffix) {
|
|
4035
|
+
const id = panelId("panel-routes", idSuffix);
|
|
3983
4036
|
if (routes.length === 0) {
|
|
3984
4037
|
return [
|
|
3985
|
-
|
|
4038
|
+
`<section class="panel" id="${id}">`,
|
|
3986
4039
|
"<h2>Recent decisions</h2>",
|
|
3987
4040
|
'<p class="muted">No routing decisions yet.</p>',
|
|
3988
4041
|
"</section>"
|
|
@@ -3990,7 +4043,7 @@ function routesPanel(routes) {
|
|
|
3990
4043
|
}
|
|
3991
4044
|
const rows = routes.map(routeRow).join("");
|
|
3992
4045
|
return [
|
|
3993
|
-
|
|
4046
|
+
`<section class="panel" id="${id}">`,
|
|
3994
4047
|
"<h2>Recent decisions</h2>",
|
|
3995
4048
|
"<table><thead><tr><th>Tier</th><th>Route</th><th>Model</th><th>Prompt (hash)</th><th>Cost</th><th>Savings</th></tr></thead>",
|
|
3996
4049
|
`<tbody>${rows}</tbody></table>`,
|
|
@@ -4001,10 +4054,11 @@ function activityRow(entry) {
|
|
|
4001
4054
|
const who = entry.agent === undefined ? "" : `<span class="who">${escapeHtml(entry.agent)}</span> `;
|
|
4002
4055
|
return `<li class="${entry.kind}"><span class="kind">${entry.kind}</span> ${who}${escapeHtml(entry.summary)}</li>`;
|
|
4003
4056
|
}
|
|
4004
|
-
function activityPanel(activity) {
|
|
4057
|
+
function activityPanel(activity, idSuffix) {
|
|
4058
|
+
const id = panelId("panel-activity", idSuffix);
|
|
4005
4059
|
if (activity.length === 0) {
|
|
4006
4060
|
return [
|
|
4007
|
-
|
|
4061
|
+
`<section class="panel" id="${id}">`,
|
|
4008
4062
|
"<h2>Activity</h2>",
|
|
4009
4063
|
'<p class="muted">No activity recorded.</p>',
|
|
4010
4064
|
"</section>"
|
|
@@ -4012,7 +4066,7 @@ function activityPanel(activity) {
|
|
|
4012
4066
|
}
|
|
4013
4067
|
const rows = activity.map(activityRow).join("");
|
|
4014
4068
|
return [
|
|
4015
|
-
|
|
4069
|
+
`<section class="panel" id="${id}">`,
|
|
4016
4070
|
"<h2>Activity</h2>",
|
|
4017
4071
|
`<ul class="timeline">${rows}</ul>`,
|
|
4018
4072
|
"</section>"
|
|
@@ -4074,10 +4128,11 @@ function decisionRow(decision2) {
|
|
|
4074
4128
|
const tags = decision2.tags === undefined || decision2.tags.length === 0 ? "" : ` ${decision2.tags.map((tag) => `<span class="tag">#${escapeHtml(tag)}</span>`).join(" ")}`;
|
|
4075
4129
|
return `<li class="decision"><span class="kind">decision</span> ${who}${escapeHtml(decision2.summary)}${tags}</li>`;
|
|
4076
4130
|
}
|
|
4077
|
-
function decisionsPanel(decisions) {
|
|
4131
|
+
function decisionsPanel(decisions, idSuffix) {
|
|
4132
|
+
const id = panelId("panel-decisions", idSuffix);
|
|
4078
4133
|
if (decisions.length === 0) {
|
|
4079
4134
|
return [
|
|
4080
|
-
|
|
4135
|
+
`<section class="panel" id="${id}">`,
|
|
4081
4136
|
"<h2>Decisions</h2>",
|
|
4082
4137
|
'<p class="muted">No decisions recorded (the scribe writes to <code>openteam-decisions.md</code>).</p>',
|
|
4083
4138
|
"</section>"
|
|
@@ -4085,7 +4140,7 @@ function decisionsPanel(decisions) {
|
|
|
4085
4140
|
}
|
|
4086
4141
|
const rows = decisions.map(decisionRow).join("");
|
|
4087
4142
|
return [
|
|
4088
|
-
|
|
4143
|
+
`<section class="panel" id="${id}">`,
|
|
4089
4144
|
"<h2>Decisions</h2>",
|
|
4090
4145
|
`<ul class="timeline">${rows}</ul>`,
|
|
4091
4146
|
"</section>"
|
|
@@ -4096,10 +4151,11 @@ function meetingRow(meeting) {
|
|
|
4096
4151
|
const purpose = meeting.purpose === undefined ? "" : `: ${escapeHtml(meeting.purpose)}`;
|
|
4097
4152
|
return `<li class="agent"><span class="kind">meeting</span> <span class="who">${roles}</span>${purpose}</li>`;
|
|
4098
4153
|
}
|
|
4099
|
-
function meetingsPanel(meetings) {
|
|
4154
|
+
function meetingsPanel(meetings, idSuffix) {
|
|
4155
|
+
const id = panelId("panel-meetings", idSuffix);
|
|
4100
4156
|
if (meetings.length === 0) {
|
|
4101
4157
|
return [
|
|
4102
|
-
|
|
4158
|
+
`<section class="panel" id="${id}">`,
|
|
4103
4159
|
"<h2>Meetings</h2>",
|
|
4104
4160
|
'<p class="muted">No multi-agent meetings yet.</p>',
|
|
4105
4161
|
"</section>"
|
|
@@ -4107,7 +4163,7 @@ function meetingsPanel(meetings) {
|
|
|
4107
4163
|
}
|
|
4108
4164
|
const rows = meetings.map(meetingRow).join("");
|
|
4109
4165
|
return [
|
|
4110
|
-
|
|
4166
|
+
`<section class="panel" id="${id}">`,
|
|
4111
4167
|
"<h2>Meetings</h2>",
|
|
4112
4168
|
`<ul class="timeline">${rows}</ul>`,
|
|
4113
4169
|
"</section>"
|
|
@@ -4122,20 +4178,54 @@ var STATUS_LABELS = {
|
|
|
4122
4178
|
function statusBadge(status) {
|
|
4123
4179
|
return `<span class="badge status-${status}">${STATUS_LABELS[status]}</span>`;
|
|
4124
4180
|
}
|
|
4125
|
-
function tabBar(sessions, graphEnabled, configEnabled) {
|
|
4126
|
-
const allTab = '<button type="button" class="tab" data-tab="all">All</button>';
|
|
4127
|
-
const graphTab = graphEnabled ? '<button type="button" class="tab" data-tab="graph">Graph</button>' : "";
|
|
4128
|
-
const configTab = configEnabled ? '<button type="button" class="tab" data-tab="config">Configuration</button>' : "";
|
|
4129
|
-
const tabs = sessions.map((tab) => {
|
|
4130
|
-
const id = escapeHtml(tab.sessionID);
|
|
4131
|
-
const short = escapeHtml(shortSessionID(tab.sessionID));
|
|
4132
|
-
return `<button type="button" class="tab" data-tab="${id}"><span class="dot status-${tab.status}"></span>${short}</button>`;
|
|
4133
|
-
}).join("");
|
|
4134
|
-
return `<nav class="tabs" id="tabbar">${allTab}${graphTab}${configTab}${tabs}</nav>`;
|
|
4135
|
-
}
|
|
4136
4181
|
function shortSessionID(sessionID) {
|
|
4137
4182
|
return sessionID.length <= 12 ? sessionID : `${sessionID.slice(0, 12)}…`;
|
|
4138
4183
|
}
|
|
4184
|
+
var ICON_TREE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="3" width="6" height="5" rx="1"></rect><rect x="3" y="16" width="6" height="5" rx="1"></rect><rect x="15" y="16" width="6" height="5" rx="1"></rect><path d="M12 8v4M6 16v-2h12v2"></path></svg>';
|
|
4185
|
+
var ICON_ROUTING = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"></path><path d="M7 15l3-4 3 2 4-6"></path></svg>';
|
|
4186
|
+
var ICON_LOGS = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5h16M4 10h16M4 15h10M4 20h13"></path></svg>';
|
|
4187
|
+
var ICON_CONFIG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"></path><path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-2.7 1.1V21a2 2 0 1 1-4 0v-.1A1.6 1.6 0 0 0 6 19.4a1.6 1.6 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0-1.1-2.7H0"></path></svg>';
|
|
4188
|
+
function navButton(screen, label, icon, options = {}) {
|
|
4189
|
+
const active = options.active === true ? ' aria-current="page"' : "";
|
|
4190
|
+
const count = options.count === undefined ? "" : `<span class="count mono"${screen === "tree" ? " data-live-count" : ""}>${options.count}</span>`;
|
|
4191
|
+
const pill = options.pill === undefined ? "" : `<span class="pill warn">${escapeHtml(options.pill)}</span>`;
|
|
4192
|
+
return `<button type="button" class="nav-item" role="tab" data-screen="${screen}" data-title="${escapeHtml(label)}" aria-controls="screen-${screen}"${active}>${icon} ${escapeHtml(label)}${count}${pill}</button>`;
|
|
4193
|
+
}
|
|
4194
|
+
function sidebarNav(liveCount, logCount) {
|
|
4195
|
+
return [
|
|
4196
|
+
'<aside class="sidebar" id="sidebar">',
|
|
4197
|
+
'<div class="brand">',
|
|
4198
|
+
'<span class="logo" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="5" r="2.4"></circle><circle cx="5.5" cy="18" r="2.4"></circle><circle cx="18.5" cy="18" r="2.4"></circle><path d="M12 7.4v4.2M12 11.6 6.6 16M12 11.6 17.4 16"></path></svg></span>',
|
|
4199
|
+
'<span class="name">openteam</span>',
|
|
4200
|
+
'<span class="tag">console</span>',
|
|
4201
|
+
"</div>",
|
|
4202
|
+
'<nav class="nav" aria-label="Console surfaces">',
|
|
4203
|
+
'<div class="nav-label">Operations</div>',
|
|
4204
|
+
navButton("tree", "Agent tree", ICON_TREE, {
|
|
4205
|
+
active: true,
|
|
4206
|
+
count: liveCount
|
|
4207
|
+
}),
|
|
4208
|
+
navButton("routing", "Routing + telemetry", ICON_ROUTING),
|
|
4209
|
+
navButton("logs", "Logs / activity", ICON_LOGS, { count: logCount }),
|
|
4210
|
+
navButton("config", "Configuration", ICON_CONFIG, { pill: "editable" }),
|
|
4211
|
+
"</nav>",
|
|
4212
|
+
'<div class="sidebar-foot">',
|
|
4213
|
+
'<div class="host-row"><span class="dot live"></span><span>loopback console</span></div>',
|
|
4214
|
+
'<div class="host-row faint"><span>prompts stay redacted by hash</span></div>',
|
|
4215
|
+
"</div>",
|
|
4216
|
+
"</aside>"
|
|
4217
|
+
].join("");
|
|
4218
|
+
}
|
|
4219
|
+
function screenSection(screen, label, heading, description, body, active = false) {
|
|
4220
|
+
const className = active ? "screen active" : "screen";
|
|
4221
|
+
const hidden = active ? "" : " hidden";
|
|
4222
|
+
return [
|
|
4223
|
+
`<section class="${className}" id="screen-${screen}" role="tabpanel" aria-label="${escapeHtml(label)}"${hidden}>`,
|
|
4224
|
+
`<div class="screen-head"><h2>${heading}</h2><p>${description}</p></div>`,
|
|
4225
|
+
body,
|
|
4226
|
+
"</section>"
|
|
4227
|
+
].join("");
|
|
4228
|
+
}
|
|
4139
4229
|
function sessionHeaderPanel(tab) {
|
|
4140
4230
|
const endpoint = tab.endpoint === undefined ? '<span class="muted">endpoint not announced</span>' : `<code>${escapeHtml(tab.endpoint.url)}</code>`;
|
|
4141
4231
|
const dir = tab.endpoint?.directory === undefined ? "" : `<div class="stat"><span class="k">Directory</span><span class="v mono">${escapeHtml(tab.endpoint.directory)}</span></div>`;
|
|
@@ -4181,17 +4271,18 @@ function ptyPanel(tab) {
|
|
|
4181
4271
|
"</section>"
|
|
4182
4272
|
].join("");
|
|
4183
4273
|
}
|
|
4184
|
-
function
|
|
4274
|
+
function sessionDetailCard(tab, terminalEnabled, ptyEnabled, visible, index) {
|
|
4275
|
+
const idSuffix = `${index}-${domIdPart(tab.sessionID)}`;
|
|
4185
4276
|
const body = [
|
|
4186
4277
|
sessionHeaderPanel(tab),
|
|
4187
4278
|
terminalEnabled ? terminalPanel(tab) : "",
|
|
4188
4279
|
terminalEnabled && ptyEnabled ? ptyPanel(tab) : "",
|
|
4189
|
-
routesPanel(tab.recentRoutes),
|
|
4190
|
-
decisionsPanel(tab.decisions),
|
|
4191
|
-
meetingsPanel(tab.meetings),
|
|
4192
|
-
activityPanel(tab.activity)
|
|
4280
|
+
routesPanel(tab.recentRoutes, idSuffix),
|
|
4281
|
+
decisionsPanel(tab.decisions, idSuffix),
|
|
4282
|
+
meetingsPanel(tab.meetings, idSuffix),
|
|
4283
|
+
activityPanel(tab.activity, idSuffix)
|
|
4193
4284
|
].join("");
|
|
4194
|
-
return `<
|
|
4285
|
+
return `<article class="session-detail" id="session-${idSuffix}" data-session-view="${escapeHtml(tab.sessionID)}"${visible ? "" : " hidden"}>${body}</article>`;
|
|
4195
4286
|
}
|
|
4196
4287
|
var GRAPH_COL_W = 220;
|
|
4197
4288
|
var GRAPH_ROW_H = 96;
|
|
@@ -4356,51 +4447,110 @@ function drillPanel() {
|
|
|
4356
4447
|
"</aside>"
|
|
4357
4448
|
].join("");
|
|
4358
4449
|
}
|
|
4359
|
-
function
|
|
4450
|
+
function treeGraphPanel(projection, drilldownEnabled, disabled) {
|
|
4360
4451
|
return [
|
|
4361
|
-
'<
|
|
4362
|
-
|
|
4363
|
-
"<h2>Agent graph</h2>",
|
|
4452
|
+
'<section class="panel graph-panel" id="panel-graph">',
|
|
4453
|
+
"<h3>Live graph</h3>",
|
|
4364
4454
|
disabled || projection === null ? "" : healthBanner(projection.health),
|
|
4455
|
+
'<div id="tree-content" data-graph-mount>',
|
|
4365
4456
|
renderGraphView(projection, { disabled }),
|
|
4457
|
+
"</div>",
|
|
4366
4458
|
!disabled && drilldownEnabled ? drillPanel() : "",
|
|
4367
|
-
"</section>"
|
|
4368
|
-
"</div>"
|
|
4459
|
+
"</section>"
|
|
4369
4460
|
].join("");
|
|
4370
4461
|
}
|
|
4371
4462
|
function graphDisabledHint() {
|
|
4372
4463
|
return [
|
|
4373
4464
|
'<div class="graph-disabled" role="status">',
|
|
4374
|
-
|
|
4375
|
-
'<p class="hint">Set <code>graph.mode</code> to <code>shadow</code> in ',
|
|
4376
|
-
"<code>.opencode/openteam.json</code> to enable it, then restart the session.</p>",
|
|
4465
|
+
`<p class="muted">${escapeHtml(GRAPH_DISABLED_REMEDIATION)}</p>`,
|
|
4377
4466
|
"</div>"
|
|
4378
4467
|
].join("");
|
|
4379
4468
|
}
|
|
4380
|
-
function
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
const ptyEnabled = options.ptyEnabled === true;
|
|
4384
|
-
const graphEnabled = options.graphView !== undefined;
|
|
4385
|
-
const allBody = [
|
|
4469
|
+
function routingScreenBody(state) {
|
|
4470
|
+
return [
|
|
4471
|
+
'<div class="screen-grid">',
|
|
4386
4472
|
summaryPanel(state.cost),
|
|
4387
4473
|
totalsPanel(state.totals),
|
|
4388
4474
|
sessionsPanel(state.sessions),
|
|
4389
|
-
loopPanel(state.loop),
|
|
4390
|
-
teamPanel(state.team),
|
|
4391
4475
|
tierPanel(state.cost),
|
|
4392
4476
|
routesPanel(state.recentRoutes),
|
|
4477
|
+
"</div>"
|
|
4478
|
+
].join("");
|
|
4479
|
+
}
|
|
4480
|
+
function emptySessionList() {
|
|
4481
|
+
return [
|
|
4482
|
+
'<div class="card">',
|
|
4483
|
+
'<div class="card-head"><h3>Sessions</h3><span class="sub">0</span></div>',
|
|
4484
|
+
'<div class="empty-state">No sessions recorded yet.</div>',
|
|
4485
|
+
"</div>"
|
|
4486
|
+
].join("");
|
|
4487
|
+
}
|
|
4488
|
+
function sessionList(sessions) {
|
|
4489
|
+
if (sessions.length === 0) {
|
|
4490
|
+
return emptySessionList();
|
|
4491
|
+
}
|
|
4492
|
+
const buttons = sessions.map((tab, index) => {
|
|
4493
|
+
const selected = index === 0;
|
|
4494
|
+
return [
|
|
4495
|
+
`<button type="button" class="session-btn${selected ? " sel" : ""}" role="tab" aria-selected="${selected ? "true" : "false"}" data-session-target="${escapeHtml(tab.sessionID)}">`,
|
|
4496
|
+
`<span class="dot status-${tab.status}"></span>`,
|
|
4497
|
+
`<span class="sid">${escapeHtml(shortSessionID(tab.sessionID))}</span>`,
|
|
4498
|
+
`<span class="n">${tab.summary.messageCount}</span>`,
|
|
4499
|
+
"</button>"
|
|
4500
|
+
].join("");
|
|
4501
|
+
}).join("");
|
|
4502
|
+
return [
|
|
4503
|
+
'<div class="card">',
|
|
4504
|
+
`<div class="card-head"><h3>Sessions</h3><span class="sub">${sessions.length}</span></div>`,
|
|
4505
|
+
'<div class="session-list" role="tablist" aria-label="Sessions">',
|
|
4506
|
+
buttons,
|
|
4507
|
+
"</div>",
|
|
4508
|
+
"</div>"
|
|
4509
|
+
].join("");
|
|
4510
|
+
}
|
|
4511
|
+
function sessionDetailList(sessions, terminalEnabled, ptyEnabled) {
|
|
4512
|
+
if (sessions.length === 0) {
|
|
4513
|
+
return '<div class="card empty-state">No session activity recorded yet.</div>';
|
|
4514
|
+
}
|
|
4515
|
+
return sessions.map((tab, index) => sessionDetailCard(tab, terminalEnabled, ptyEnabled, index === 0, index)).join("");
|
|
4516
|
+
}
|
|
4517
|
+
function logsScreenBody(state, terminalEnabled, ptyEnabled) {
|
|
4518
|
+
return [
|
|
4519
|
+
'<div class="logs-layout">',
|
|
4520
|
+
sessionList(state.sessionsDetail),
|
|
4521
|
+
"<div>",
|
|
4522
|
+
sessionDetailList(state.sessionsDetail, terminalEnabled, ptyEnabled),
|
|
4523
|
+
"</div>",
|
|
4524
|
+
"</div>",
|
|
4525
|
+
'<div class="screen-grid">',
|
|
4526
|
+
loopPanel(state.loop),
|
|
4527
|
+
teamPanel(state.team),
|
|
4393
4528
|
decisionsPanel(state.decisions),
|
|
4394
4529
|
meetingsPanel(state.meetings),
|
|
4395
|
-
activityPanel(state.activity)
|
|
4530
|
+
activityPanel(state.activity),
|
|
4531
|
+
"</div>"
|
|
4396
4532
|
].join("");
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4533
|
+
}
|
|
4534
|
+
function configScreenBody(hasToken) {
|
|
4535
|
+
return renderConfigEditorPanel({ hasToken });
|
|
4536
|
+
}
|
|
4537
|
+
function renderConsoleHtml(state, options = {}) {
|
|
4538
|
+
const refreshMs = options.refreshMs ?? 2000;
|
|
4539
|
+
const terminalEnabled = options.terminalEnabled === true;
|
|
4540
|
+
const ptyEnabled = options.ptyEnabled === true;
|
|
4541
|
+
const graphOptionProvided = options.graphView !== undefined;
|
|
4542
|
+
const graphDrilldown = graphOptionProvided && options.drilldownEnabled === true;
|
|
4543
|
+
const graphProjection = options.graphView?.projection ?? null;
|
|
4544
|
+
const graphDisabled = options.graphView === undefined || options.graphView.disabled === true;
|
|
4545
|
+
const liveCount = graphOptionProvided && graphProjection !== null && !graphDisabled ? graphProjection.nodes.length : state.sessionsDetail.length;
|
|
4401
4546
|
const configEnabled = options.configEditor !== undefined;
|
|
4402
|
-
const
|
|
4403
|
-
const body =
|
|
4547
|
+
const treeBody = treeGraphPanel(graphProjection, graphDrilldown, graphDisabled);
|
|
4548
|
+
const body = [
|
|
4549
|
+
screenSection("tree", "Live agent tree", "Agent tree · live", "Live agent topology. Graph updates arrive over the <code>graph</code> event on <code>/events</code>.", treeBody, true),
|
|
4550
|
+
screenSection("routing", "Routing and telemetry", "Routing + telemetry", "Aggregate routing and cost telemetry from <code>GET /api/state</code> across all sessions.", routingScreenBody(state)),
|
|
4551
|
+
screenSection("logs", "Logs and session activity", "Logs / activity", "Per-session activity. Prompts are referenced by hash only and never rendered in clear text.", logsScreenBody(state, terminalEnabled, ptyEnabled)),
|
|
4552
|
+
screenSection("config", "Editable configuration", 'Configuration <span class="pill warn">editable</span>', "Edit the whole openteam JSON configuration. Roles are editable inside the JSON when present; unsupported surfaces are not shown.", configScreenBody(options.configEditor?.hasToken === true))
|
|
4553
|
+
].join("");
|
|
4404
4554
|
const session = state.session.id === undefined ? "" : ` · session ${escapeHtml(state.session.id)}`;
|
|
4405
4555
|
const tokenAttr = (terminalEnabled || graphDrilldown || configEnabled) && options.consoleToken !== undefined ? ` data-token="${escapeHtml(options.consoleToken)}"${terminalEnabled ? ' data-terminal="1"' : ""}${ptyEnabled ? ' data-pty="1"' : ""}${graphDrilldown ? ' data-drilldown="1"' : ""}${configEnabled ? ' data-config="1"' : ""}` : "";
|
|
4406
4556
|
return [
|
|
@@ -4410,16 +4560,24 @@ function renderConsoleHtml(state, options = {}) {
|
|
|
4410
4560
|
'<meta charset="utf-8">',
|
|
4411
4561
|
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
4412
4562
|
"<title>openteam console</title>",
|
|
4413
|
-
`<style>${CONSOLE_STYLE}${
|
|
4563
|
+
`<style>${CONSOLE_STYLE}${graphOptionProvided ? GRAPH_STYLE : ""}</style>`,
|
|
4414
4564
|
"</head>",
|
|
4415
4565
|
"<body>",
|
|
4416
|
-
"
|
|
4417
|
-
|
|
4418
|
-
|
|
4566
|
+
'<div class="app">',
|
|
4567
|
+
sidebarNav(liveCount, state.sessionsDetail.length),
|
|
4568
|
+
'<div class="scrim" data-close-nav></div>',
|
|
4569
|
+
'<main class="main">',
|
|
4570
|
+
'<header class="topbar">',
|
|
4571
|
+
'<button type="button" class="icon-btn menu-btn" data-toggle-nav aria-label="Open navigation"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 6h16M4 12h16M4 18h16"></path></svg></button>',
|
|
4572
|
+
'<h1 id="screen-title">Agent tree</h1>',
|
|
4573
|
+
`<span class="refresh" title="SSE + polling"><span class="beat"></span> ${refreshMs} ms · SSE</span>`,
|
|
4574
|
+
'<div class="spacer"></div>',
|
|
4575
|
+
`<span class="token-chip" title="Page status"><span class="lbl">updated</span> <b class="mono">${escapeHtml(state.generatedAt)}${session}</b></span>`,
|
|
4419
4576
|
"</header>",
|
|
4420
4577
|
options.diagnosticActive === true ? '<div class="diag-banner" role="alert">Diagnostic mode enabled — this loopback console may show raw prompt and model text locally.</div>' : "",
|
|
4421
|
-
|
|
4422
|
-
|
|
4578
|
+
`<div class="scroll">${body}</div>`,
|
|
4579
|
+
"</main>",
|
|
4580
|
+
"</div>",
|
|
4423
4581
|
`<script>${CONSOLE_CLIENT_JS}</script>`,
|
|
4424
4582
|
"</body>",
|
|
4425
4583
|
"</html>"
|
|
@@ -4482,23 +4640,21 @@ function renderConfigSaveSummary(write) {
|
|
|
4482
4640
|
}
|
|
4483
4641
|
return `<div class="config-summary">${parts.join("")}</div>`;
|
|
4484
4642
|
}
|
|
4485
|
-
function
|
|
4643
|
+
function renderConfigEditorPanel(opts) {
|
|
4486
4644
|
const inner = opts.hasToken ? [
|
|
4487
4645
|
'<p class="muted">Edit the openteam configuration. Portable keys are committed to <code>.opencode/openteam.json</code>; machine-specific keys stay in your local overlay and are never committed; secrets are read from environment variables and are never shown here.</p>',
|
|
4488
4646
|
'<div class="config-mount" data-config-mount><p class="muted">Loading configuration…</p></div>'
|
|
4489
4647
|
].join("") : '<p class="muted">Configuration editing requires the console session token, which is not present in this page. This view is read-only.</p>';
|
|
4490
4648
|
return [
|
|
4491
|
-
'<div class="view" data-view="config" hidden>',
|
|
4492
4649
|
'<section class="panel config-panel">',
|
|
4493
|
-
"<
|
|
4650
|
+
"<h3>Configuration JSON</h3>",
|
|
4494
4651
|
inner,
|
|
4495
|
-
"</section>"
|
|
4496
|
-
"</div>"
|
|
4652
|
+
"</section>"
|
|
4497
4653
|
].join("");
|
|
4498
4654
|
}
|
|
4499
4655
|
|
|
4500
4656
|
// src/web/configRoute.ts
|
|
4501
|
-
var
|
|
4657
|
+
var JSON_HEADERS2 = { "content-type": "application/json" };
|
|
4502
4658
|
var LOOPBACK_HOSTS = new Set([
|
|
4503
4659
|
"127.0.0.1",
|
|
4504
4660
|
"localhost",
|
|
@@ -4506,7 +4662,7 @@ var LOOPBACK_HOSTS = new Set([
|
|
|
4506
4662
|
"::1"
|
|
4507
4663
|
]);
|
|
4508
4664
|
var MAX_CONFIG_BODY_CHARS = 256 * 1024;
|
|
4509
|
-
function
|
|
4665
|
+
function bearerFromHeader2(request) {
|
|
4510
4666
|
const header = request.headers.authorization;
|
|
4511
4667
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
4512
4668
|
return;
|
|
@@ -4519,7 +4675,7 @@ function queryToken(query) {
|
|
|
4519
4675
|
return value === null || value.length === 0 ? undefined : value;
|
|
4520
4676
|
}
|
|
4521
4677
|
function readAuthorized(request, query, token) {
|
|
4522
|
-
const provided =
|
|
4678
|
+
const provided = bearerFromHeader2(request) ?? queryToken(query);
|
|
4523
4679
|
return provided !== undefined && safeEqualToken(provided, token);
|
|
4524
4680
|
}
|
|
4525
4681
|
function isCrossOriginMutation(request) {
|
|
@@ -4570,34 +4726,34 @@ async function handleConfigRoute(request, response, url, deps) {
|
|
|
4570
4726
|
}
|
|
4571
4727
|
if (request.method === "GET") {
|
|
4572
4728
|
if (!readAuthorized(request, url.searchParams, deps.token)) {
|
|
4573
|
-
response.writeHead(401,
|
|
4729
|
+
response.writeHead(401, JSON_HEADERS2).end(JSON.stringify({ error: "unauthorized" }));
|
|
4574
4730
|
return true;
|
|
4575
4731
|
}
|
|
4576
4732
|
const result = await deps.editor.read();
|
|
4577
|
-
response.writeHead(200,
|
|
4733
|
+
response.writeHead(200, JSON_HEADERS2).end(JSON.stringify({ ...result, render: renderConfigEditorForm(result) }));
|
|
4578
4734
|
return true;
|
|
4579
4735
|
}
|
|
4580
4736
|
if (request.method === "POST") {
|
|
4581
4737
|
if (isCrossOriginMutation(request)) {
|
|
4582
|
-
response.writeHead(403,
|
|
4738
|
+
response.writeHead(403, JSON_HEADERS2).end(JSON.stringify({ error: "cross-origin forbidden" }));
|
|
4583
4739
|
return true;
|
|
4584
4740
|
}
|
|
4585
|
-
const bearer =
|
|
4741
|
+
const bearer = bearerFromHeader2(request);
|
|
4586
4742
|
if (bearer === undefined || !safeEqualToken(bearer, deps.token)) {
|
|
4587
|
-
response.writeHead(401,
|
|
4743
|
+
response.writeHead(401, JSON_HEADERS2).end(JSON.stringify({ error: "unauthorized" }));
|
|
4588
4744
|
return true;
|
|
4589
4745
|
}
|
|
4590
4746
|
const body = await readJsonBody(request, MAX_CONFIG_BODY_CHARS);
|
|
4591
4747
|
if (!body.ok) {
|
|
4592
|
-
response.writeHead(400,
|
|
4748
|
+
response.writeHead(400, JSON_HEADERS2).end(JSON.stringify({ ok: false, error: "invalid JSON body" }));
|
|
4593
4749
|
return true;
|
|
4594
4750
|
}
|
|
4595
4751
|
const result = await deps.editor.write(body.value);
|
|
4596
4752
|
const responseBody = result.status === 200 ? { ...result.body, render: renderConfigSaveSummary(result.body) } : result.body;
|
|
4597
|
-
response.writeHead(result.status,
|
|
4753
|
+
response.writeHead(result.status, JSON_HEADERS2).end(JSON.stringify(responseBody));
|
|
4598
4754
|
return true;
|
|
4599
4755
|
}
|
|
4600
|
-
response.writeHead(405,
|
|
4756
|
+
response.writeHead(405, JSON_HEADERS2).end(JSON.stringify({ error: "method not allowed" }));
|
|
4601
4757
|
return true;
|
|
4602
4758
|
}
|
|
4603
4759
|
function createConfigReloader(deps) {
|
|
@@ -4921,8 +5077,8 @@ var SSE_HEADERS = {
|
|
|
4921
5077
|
"cache-control": "no-cache",
|
|
4922
5078
|
connection: "keep-alive"
|
|
4923
5079
|
};
|
|
4924
|
-
var
|
|
4925
|
-
function
|
|
5080
|
+
var JSON_HEADERS3 = { "content-type": "application/json" };
|
|
5081
|
+
function bearerFromHeader3(request) {
|
|
4926
5082
|
const header = request.headers.authorization;
|
|
4927
5083
|
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
4928
5084
|
return;
|
|
@@ -4930,8 +5086,8 @@ function bearerFromHeader2(request) {
|
|
|
4930
5086
|
const value = header.slice("Bearer ".length).trim();
|
|
4931
5087
|
return value.length === 0 ? undefined : value;
|
|
4932
5088
|
}
|
|
4933
|
-
function
|
|
4934
|
-
const provided = tokenFromQuery(query) ??
|
|
5089
|
+
function authorized2(request, query, token) {
|
|
5090
|
+
const provided = tokenFromQuery(query) ?? bearerFromHeader3(request);
|
|
4935
5091
|
return provided !== undefined && safeEqualToken(provided, token);
|
|
4936
5092
|
}
|
|
4937
5093
|
async function readJsonBody2(request, limit) {
|
|
@@ -4974,7 +5130,7 @@ function parseConsolePath(path) {
|
|
|
4974
5130
|
async function handleStream(request, response, sessionID, deps, diagnostic) {
|
|
4975
5131
|
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
4976
5132
|
if (endpoint === undefined) {
|
|
4977
|
-
response.writeHead(404,
|
|
5133
|
+
response.writeHead(404, JSON_HEADERS3).end(JSON.stringify({ error: "no endpoint" }));
|
|
4978
5134
|
return;
|
|
4979
5135
|
}
|
|
4980
5136
|
response.writeHead(200, SSE_HEADERS);
|
|
@@ -4995,31 +5151,31 @@ data: {}
|
|
|
4995
5151
|
async function handleMessages(_request, response, sessionID, deps, diagnostic) {
|
|
4996
5152
|
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
4997
5153
|
if (endpoint === undefined) {
|
|
4998
|
-
response.writeHead(404,
|
|
5154
|
+
response.writeHead(404, JSON_HEADERS3).end(JSON.stringify({ error: "no endpoint" }));
|
|
4999
5155
|
return;
|
|
5000
5156
|
}
|
|
5001
5157
|
const frames = await deps.createClient(endpoint).listMessages(sessionID, { diagnostic });
|
|
5002
|
-
response.writeHead(200,
|
|
5158
|
+
response.writeHead(200, JSON_HEADERS3).end(JSON.stringify({ frames }));
|
|
5003
5159
|
}
|
|
5004
5160
|
async function handleInput(request, response, sessionID, deps) {
|
|
5005
5161
|
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
5006
5162
|
if (endpoint === undefined) {
|
|
5007
|
-
response.writeHead(404,
|
|
5163
|
+
response.writeHead(404, JSON_HEADERS3).end(JSON.stringify({ error: "no endpoint" }));
|
|
5008
5164
|
return;
|
|
5009
5165
|
}
|
|
5010
5166
|
const body = await readJsonBody2(request, MAX_INPUT_CHARS + 1024);
|
|
5011
5167
|
const input = parseSessionInput(body);
|
|
5012
5168
|
if (input === undefined) {
|
|
5013
|
-
response.writeHead(400,
|
|
5169
|
+
response.writeHead(400, JSON_HEADERS3).end(JSON.stringify({ error: "invalid input" }));
|
|
5014
5170
|
return;
|
|
5015
5171
|
}
|
|
5016
5172
|
await deps.createClient(endpoint).sendPrompt(sessionID, input.text);
|
|
5017
|
-
response.writeHead(202,
|
|
5173
|
+
response.writeHead(202, JSON_HEADERS3).end(JSON.stringify({ ok: true }));
|
|
5018
5174
|
}
|
|
5019
5175
|
async function handlePermission(request, response, sessionID, deps) {
|
|
5020
5176
|
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
5021
5177
|
if (endpoint === undefined) {
|
|
5022
|
-
response.writeHead(404,
|
|
5178
|
+
response.writeHead(404, JSON_HEADERS3).end(JSON.stringify({ error: "no endpoint" }));
|
|
5023
5179
|
return;
|
|
5024
5180
|
}
|
|
5025
5181
|
const body = await readJsonBody2(request, 4096);
|
|
@@ -5027,34 +5183,34 @@ async function handlePermission(request, response, sessionID, deps) {
|
|
|
5027
5183
|
const permissionID = record.permissionID;
|
|
5028
5184
|
const responseValue = record.response;
|
|
5029
5185
|
if (typeof permissionID !== "string" || permissionID.length === 0 || responseValue !== "once" && responseValue !== "always" && responseValue !== "reject") {
|
|
5030
|
-
response.writeHead(400,
|
|
5186
|
+
response.writeHead(400, JSON_HEADERS3).end(JSON.stringify({ error: "invalid permission" }));
|
|
5031
5187
|
return;
|
|
5032
5188
|
}
|
|
5033
5189
|
await deps.createClient(endpoint).respondPermission(sessionID, permissionID, responseValue);
|
|
5034
|
-
response.writeHead(202,
|
|
5190
|
+
response.writeHead(202, JSON_HEADERS3).end(JSON.stringify({ ok: true }));
|
|
5035
5191
|
}
|
|
5036
5192
|
async function handlePty(_request, response, sessionID, deps) {
|
|
5037
5193
|
if (deps.ptyEnabled !== true || deps.createPtyClient === undefined) {
|
|
5038
|
-
response.writeHead(403,
|
|
5194
|
+
response.writeHead(403, JSON_HEADERS3).end(JSON.stringify({ error: "pty disabled" }));
|
|
5039
5195
|
return;
|
|
5040
5196
|
}
|
|
5041
5197
|
const endpoint = await deps.resolveEndpoint(sessionID);
|
|
5042
5198
|
if (endpoint === undefined) {
|
|
5043
|
-
response.writeHead(404,
|
|
5199
|
+
response.writeHead(404, JSON_HEADERS3).end(JSON.stringify({ error: "no endpoint" }));
|
|
5044
5200
|
return;
|
|
5045
5201
|
}
|
|
5046
5202
|
const client = deps.createPtyClient(endpoint);
|
|
5047
5203
|
const pty = await client.createPty({ command: "", title: "openteam" });
|
|
5048
5204
|
if (pty === undefined) {
|
|
5049
|
-
response.writeHead(502,
|
|
5205
|
+
response.writeHead(502, JSON_HEADERS3).end(JSON.stringify({ error: "pty create failed" }));
|
|
5050
5206
|
return;
|
|
5051
5207
|
}
|
|
5052
5208
|
const token = await client.connectToken(pty.id);
|
|
5053
5209
|
if (token === undefined) {
|
|
5054
|
-
response.writeHead(502,
|
|
5210
|
+
response.writeHead(502, JSON_HEADERS3).end(JSON.stringify({ error: "connect token failed" }));
|
|
5055
5211
|
return;
|
|
5056
5212
|
}
|
|
5057
|
-
response.writeHead(200,
|
|
5213
|
+
response.writeHead(200, JSON_HEADERS3).end(JSON.stringify({
|
|
5058
5214
|
ptyID: pty.id,
|
|
5059
5215
|
wsUrl: client.wsUrl(pty.id, token.ticket)
|
|
5060
5216
|
}));
|
|
@@ -5065,17 +5221,17 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
5065
5221
|
}
|
|
5066
5222
|
const route = parseConsolePath(url.pathname);
|
|
5067
5223
|
if (route === undefined) {
|
|
5068
|
-
response.writeHead(404,
|
|
5224
|
+
response.writeHead(404, JSON_HEADERS3).end(JSON.stringify({ error: "not found" }));
|
|
5069
5225
|
return true;
|
|
5070
5226
|
}
|
|
5071
5227
|
const readOnlyAction = route.action === "stream" || route.action === "messages";
|
|
5072
5228
|
const channelEnabled = deps.terminalEnabled || readOnlyAction && deps.drilldownEnabled === true;
|
|
5073
5229
|
if (!channelEnabled) {
|
|
5074
|
-
response.writeHead(403,
|
|
5230
|
+
response.writeHead(403, JSON_HEADERS3).end(JSON.stringify({ error: "terminal disabled" }));
|
|
5075
5231
|
return true;
|
|
5076
5232
|
}
|
|
5077
|
-
if (!
|
|
5078
|
-
response.writeHead(401,
|
|
5233
|
+
if (!authorized2(request, url.searchParams, deps.token)) {
|
|
5234
|
+
response.writeHead(401, JSON_HEADERS3).end(JSON.stringify({ error: "unauthorized" }));
|
|
5079
5235
|
return true;
|
|
5080
5236
|
}
|
|
5081
5237
|
const diagnostic = deps.diagnosticEnabled === true && url.searchParams.get("diagnostic") === "1";
|
|
@@ -5099,45 +5255,7 @@ async function handleConsoleRoute(request, response, url, deps) {
|
|
|
5099
5255
|
await handlePty(request, response, route.sessionID, deps);
|
|
5100
5256
|
return true;
|
|
5101
5257
|
}
|
|
5102
|
-
response.writeHead(405,
|
|
5103
|
-
return true;
|
|
5104
|
-
}
|
|
5105
|
-
|
|
5106
|
-
// src/web/graphRoute.ts
|
|
5107
|
-
var JSON_HEADERS3 = { "content-type": "application/json" };
|
|
5108
|
-
var GRAPH_SSE_EVENT = "graph";
|
|
5109
|
-
function graphSseMessage(json) {
|
|
5110
|
-
return `event: ${GRAPH_SSE_EVENT}
|
|
5111
|
-
data: ${json}
|
|
5112
|
-
|
|
5113
|
-
`;
|
|
5114
|
-
}
|
|
5115
|
-
function bearerFromHeader3(request) {
|
|
5116
|
-
const header = request.headers.authorization;
|
|
5117
|
-
if (typeof header !== "string" || !header.startsWith("Bearer ")) {
|
|
5118
|
-
return;
|
|
5119
|
-
}
|
|
5120
|
-
const value = header.slice("Bearer ".length).trim();
|
|
5121
|
-
return value.length === 0 ? undefined : value;
|
|
5122
|
-
}
|
|
5123
|
-
function authorized2(request, query, token) {
|
|
5124
|
-
const provided = tokenFromQuery(query) ?? bearerFromHeader3(request);
|
|
5125
|
-
return provided !== undefined && safeEqualToken(provided, token);
|
|
5126
|
-
}
|
|
5127
|
-
async function handleGraphRoute(request, response, url, deps) {
|
|
5128
|
-
if (url.pathname !== "/api/graph") {
|
|
5129
|
-
return false;
|
|
5130
|
-
}
|
|
5131
|
-
if (request.method !== "GET") {
|
|
5132
|
-
response.writeHead(405, JSON_HEADERS3).end(JSON.stringify({ error: "method not allowed" }));
|
|
5133
|
-
return true;
|
|
5134
|
-
}
|
|
5135
|
-
if (deps.remoteToken !== undefined && !authorized2(request, url.searchParams, deps.remoteToken)) {
|
|
5136
|
-
response.writeHead(401, JSON_HEADERS3).end(JSON.stringify({ error: "unauthorized" }));
|
|
5137
|
-
return true;
|
|
5138
|
-
}
|
|
5139
|
-
const view = await deps.readView();
|
|
5140
|
-
response.writeHead(200, JSON_HEADERS3).end(JSON.stringify(view));
|
|
5258
|
+
response.writeHead(405, JSON_HEADERS3).end(JSON.stringify({ error: "method not allowed" }));
|
|
5141
5259
|
return true;
|
|
5142
5260
|
}
|
|
5143
5261
|
|
|
@@ -5408,6 +5526,14 @@ async function createConsoleServer(deps) {
|
|
|
5408
5526
|
if (graphDeps !== undefined && await handleGraphRoute(request, response, url2, graphDeps)) {
|
|
5409
5527
|
return;
|
|
5410
5528
|
}
|
|
5529
|
+
if (graphDeps === undefined && url2.pathname === "/api/graph") {
|
|
5530
|
+
if (request.method !== "GET") {
|
|
5531
|
+
response.writeHead(405, { "content-type": "application/json" }).end(JSON.stringify({ error: "method not allowed" }));
|
|
5532
|
+
return;
|
|
5533
|
+
}
|
|
5534
|
+
response.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(GRAPH_DISABLED_ENVELOPE));
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5411
5537
|
if (deps.storageApi !== undefined && await handleStorageRoute(request, response, url2, deps.storageApi)) {
|
|
5412
5538
|
return;
|
|
5413
5539
|
}
|
|
@@ -5982,7 +6108,7 @@ async function createConsoleRuntime(deps) {
|
|
|
5982
6108
|
});
|
|
5983
6109
|
const createServer = deps.serve ?? createConsoleServer;
|
|
5984
6110
|
const graphApi = deps.config.graphView?.enabled === true && deps.graphView !== undefined ? { readView: deps.graphView.readView } : undefined;
|
|
5985
|
-
const graphDisabled =
|
|
6111
|
+
const graphDisabled = graphApi === undefined;
|
|
5986
6112
|
const server = await createServer({
|
|
5987
6113
|
readSnapshot,
|
|
5988
6114
|
config: deps.config,
|
|
@@ -9939,6 +10065,123 @@ function isGateApplicable(mode) {
|
|
|
9939
10065
|
return mode === "active";
|
|
9940
10066
|
}
|
|
9941
10067
|
|
|
10068
|
+
// src/messages/commands.ts
|
|
10069
|
+
var clearCacheMessages = {
|
|
10070
|
+
header: "openteam clear-cache — frozen plugin cache entries:",
|
|
10071
|
+
columns: {
|
|
10072
|
+
specDir: "spec dir",
|
|
10073
|
+
pinned: "spec-pinned",
|
|
10074
|
+
installed: "installed",
|
|
10075
|
+
mtime: "mtime"
|
|
10076
|
+
},
|
|
10077
|
+
reparseSkipSuffix: " [SKIP — reparse point]",
|
|
10078
|
+
deletedLabel: "deleted.",
|
|
10079
|
+
lockedLabel: (message) => `[LOCKED] ${message}`,
|
|
10080
|
+
pathOutsideWarning: (specDir, absolutePath) => ` ⚠ ${specDir}: path outside cacheRoot (${absolutePath}), skipped.`,
|
|
10081
|
+
processed: (count) => `${count} entry(ies) processed.`,
|
|
10082
|
+
found: (count) => `${count} entry(ies) found. Use --delete to remove them.`
|
|
10083
|
+
};
|
|
10084
|
+
var baselineMessages = {
|
|
10085
|
+
effectiveAuto: "cheapest-capable (auto)",
|
|
10086
|
+
pinnedSuffix: (ref) => `${ref} (pinned)`,
|
|
10087
|
+
summary: (params) => [
|
|
10088
|
+
"openteam baseline:",
|
|
10089
|
+
` mode: ${params.mode}`,
|
|
10090
|
+
` pinned: ${params.pinned}`,
|
|
10091
|
+
` hardDefault: ${params.hardDefault}`,
|
|
10092
|
+
` effective: ${params.effective}`
|
|
10093
|
+
],
|
|
10094
|
+
invalidModel: (input) => `Invalid model "${input}". Use the provider/model format, e.g. anthropic/claude-sonnet-4-5.`,
|
|
10095
|
+
pinnedTo: (ref) => `Baseline pinned to ${ref} (pinned mode).`,
|
|
10096
|
+
autoMode: "Baseline set to auto mode (cheapest-capable)."
|
|
10097
|
+
};
|
|
10098
|
+
var localMessages = {
|
|
10099
|
+
help: {
|
|
10100
|
+
status: " openteam local status Show the routing mode (local-first / local-only / frontier-only)",
|
|
10101
|
+
off: " openteam local off Use frontier models only (no local runtime)",
|
|
10102
|
+
only: " openteam local only Use local models only (never frontier)",
|
|
10103
|
+
on: " openteam local on Re-enable local-first routing"
|
|
10104
|
+
},
|
|
10105
|
+
noRuntimes: "none",
|
|
10106
|
+
modeFrontierOnly: "frontier-only (frontierOnly)",
|
|
10107
|
+
modeLocalOnly: "local-only (localOnly)",
|
|
10108
|
+
modeLocalFirst: "local-first",
|
|
10109
|
+
summary: (params) => [
|
|
10110
|
+
"openteam local:",
|
|
10111
|
+
` mode: ${params.mode}`,
|
|
10112
|
+
` frontierOnly: ${params.frontierOnly}`,
|
|
10113
|
+
` localOnly: ${params.localOnly}`,
|
|
10114
|
+
` privacy: ${params.privacy}`,
|
|
10115
|
+
` runtimes: ${params.runtimes}`
|
|
10116
|
+
],
|
|
10117
|
+
frontierOnlyNoChange: "No change: already in frontier-only mode.",
|
|
10118
|
+
privacyAdjustedNote: " Privacy adjusted to 'consentBeforeFrontier' (forceLocalOnSensitive requires a local runtime).",
|
|
10119
|
+
frontierOnlyEnabled: "Frontier-only mode enabled (frontierOnly=true, localOnly=false).",
|
|
10120
|
+
localOnlyNoRuntimes: "Cannot enable local-only mode: no enabled local runtime is configured. Run 'openteam setup' or enable a local runtime in .opencode/openteam.json first.",
|
|
10121
|
+
localOnlyNoChange: "No change: already in local-only mode.",
|
|
10122
|
+
localOnlyEnabled: "Local-only mode enabled (localOnly=true, frontierOnly=false).",
|
|
10123
|
+
localFirstNoChange: "No change: local-first routing is already active.",
|
|
10124
|
+
localFirstReEnabled: "Local-first routing re-enabled (frontierOnly=false, localOnly=false). Make sure a local runtime is reachable.",
|
|
10125
|
+
unknownSubcommand: (subcommand, help) => `Unknown local subcommand: ${subcommand}
|
|
10126
|
+
|
|
10127
|
+
${help}`
|
|
10128
|
+
};
|
|
10129
|
+
var frontierCatalogMessages = {
|
|
10130
|
+
free: "free",
|
|
10131
|
+
costPerMillion: (inputUSD, outputUSD) => `$${inputUSD}/$${outputUSD} per 1M`
|
|
10132
|
+
};
|
|
10133
|
+
var migrateMessages = {
|
|
10134
|
+
header: "openteam migrate:",
|
|
10135
|
+
nothingToMigrate: " nothing to migrate — the P15b layout is already in place.",
|
|
10136
|
+
counts: (params) => [
|
|
10137
|
+
` moved: ${params.moved} file(s)`,
|
|
10138
|
+
` deduped: ${params.deduped} file(s) (destination already identical)`,
|
|
10139
|
+
` conflicts: ${params.conflicts} file(s)`
|
|
10140
|
+
],
|
|
10141
|
+
relocatedHeader: " relocated:",
|
|
10142
|
+
relocatedEntry: (from, to, deduped) => ` · ${from} → ${to}${deduped ? " (deduped)" : ""}`,
|
|
10143
|
+
conflictsHeader: " ⚠ left in place (destination exists with different content — no data lost):",
|
|
10144
|
+
conflictEntry: (from, to) => ` · ${from} → ${to}`,
|
|
10145
|
+
manualWorktreesHeader: " ⚠ legacy git worktree(s) need a manual move (run `git worktree move`):",
|
|
10146
|
+
manualWorktreeEntry: (label, from, to) => ` · ${label}: ${from} → ${to}`,
|
|
10147
|
+
success: " ✓ machine-local runtime output relocated under .opencode/openteam-local/."
|
|
10148
|
+
};
|
|
10149
|
+
var rolesInitMessages = {
|
|
10150
|
+
header: "openteam roles init:",
|
|
10151
|
+
title: "openteam roles init",
|
|
10152
|
+
noRoster: (path) => ` ✗ no roster at ${path}.`,
|
|
10153
|
+
noRosterRemedy: " Cast the team first: run `openteam setup`, then ask the orchestrator to register the cast.",
|
|
10154
|
+
unparseableRoster: (path, error) => ` ✗ ${path} is present but unparseable: ${error}`,
|
|
10155
|
+
unparseableRosterRemedy: " Run `openteam doctor` for the remedy, then re-run this command.",
|
|
10156
|
+
allProfiled: (count) => ` ✓ ${count} roster role(s) checked; no unprofiled roles — every role resolves a routing profile.`,
|
|
10157
|
+
whyTitle: "Why you are being asked",
|
|
10158
|
+
why: (count) => [
|
|
10159
|
+
`${count} roster role(s) resolve no routing profile.`,
|
|
10160
|
+
"Until a profile exists they are frontier-eligible: their work can escalate",
|
|
10161
|
+
"to a frontier model. A 'local only' note in the roster prose does not",
|
|
10162
|
+
"prevent this — prose outside the JSON fence is never read."
|
|
10163
|
+
].join(`
|
|
10164
|
+
`),
|
|
10165
|
+
policyQuestion: (roleID, agentName) => `Routing policy for "${roleID}" (${agentName})`,
|
|
10166
|
+
localOnlyLabel: (roleID, agentName) => `Local only — ${roleID} (${agentName}) never leaves this machine`,
|
|
10167
|
+
localOnlyHint: "sets requiresLocalRuntime; the role fails closed when no local runtime is reachable",
|
|
10168
|
+
frontierOkLabel: "Local first, frontier allowed",
|
|
10169
|
+
frontierOkHint: "records today's implicit behaviour: cheapest-capable frontier when the task warrants it",
|
|
10170
|
+
skipLabel: "Skip for now",
|
|
10171
|
+
skipHint: "leaves the role unprofiled, so it stays frontier-eligible and doctor keeps reporting it",
|
|
10172
|
+
runtimeQuestion: (roleID) => `Preferred local runtime(s) for "${roleID}" (in failover order)`,
|
|
10173
|
+
runtimeHint: "machine-local — written to the git-ignored overlay",
|
|
10174
|
+
cancelled: (reason) => ` ✗ cancelled (${reason}); nothing was written.`,
|
|
10175
|
+
invalidResult: (reason) => ` ✗ refusing to write: the resulting config is invalid — ${reason}`,
|
|
10176
|
+
nothingWritten: (skipped) => ` · skipped ${skipped}; nothing written.`,
|
|
10177
|
+
nothingToWriteOutro: "Nothing to write.",
|
|
10178
|
+
pinnedLocal: (roleIDs, path) => ` ✓ pinned local-only: ${roleIDs} → ${path} (tracked, travels with the roster)`,
|
|
10179
|
+
frontierAllowed: (roleIDs, path) => ` ✓ frontier allowed: ${roleIDs} → ${path} (model choice stays "auto": cheapest-capable per task)`,
|
|
10180
|
+
runtimeBinding: (path) => ` ✓ runtime binding: ${path} (git-ignored, machine-local)`,
|
|
10181
|
+
skippedSummary: (roleIDs) => ` · skipped: ${roleIDs} — still frontier-eligible; re-run this command to revisit.`,
|
|
10182
|
+
outro: "Role policy updated."
|
|
10183
|
+
};
|
|
10184
|
+
|
|
9942
10185
|
// src/orchestrator/ralphLoop.ts
|
|
9943
10186
|
var RALPH_ROLE_ID = "ralph";
|
|
9944
10187
|
var DEFAULT_RALPH_MAX_ITERATIONS = 10;
|
|
@@ -10293,109 +10536,6 @@ function describeOtelBackendForDoctor(config, env) {
|
|
|
10293
10536
|
};
|
|
10294
10537
|
}
|
|
10295
10538
|
|
|
10296
|
-
// src/messages/commands.ts
|
|
10297
|
-
var clearCacheMessages = {
|
|
10298
|
-
header: "openteam clear-cache — frozen plugin cache entries:",
|
|
10299
|
-
columns: {
|
|
10300
|
-
specDir: "spec dir",
|
|
10301
|
-
pinned: "spec-pinned",
|
|
10302
|
-
installed: "installed",
|
|
10303
|
-
mtime: "mtime"
|
|
10304
|
-
},
|
|
10305
|
-
reparseSkipSuffix: " [SKIP — reparse point]",
|
|
10306
|
-
deletedLabel: "deleted.",
|
|
10307
|
-
lockedLabel: (message) => `[LOCKED] ${message}`,
|
|
10308
|
-
pathOutsideWarning: (specDir, absolutePath) => ` ⚠ ${specDir}: path outside cacheRoot (${absolutePath}), skipped.`,
|
|
10309
|
-
processed: (count) => `${count} entry(ies) processed.`,
|
|
10310
|
-
found: (count) => `${count} entry(ies) found. Use --delete to remove them.`
|
|
10311
|
-
};
|
|
10312
|
-
var baselineMessages = {
|
|
10313
|
-
effectiveAuto: "cheapest-capable (auto)",
|
|
10314
|
-
pinnedSuffix: (ref) => `${ref} (pinned)`,
|
|
10315
|
-
summary: (params) => [
|
|
10316
|
-
"openteam baseline:",
|
|
10317
|
-
` mode: ${params.mode}`,
|
|
10318
|
-
` pinned: ${params.pinned}`,
|
|
10319
|
-
` hardDefault: ${params.hardDefault}`,
|
|
10320
|
-
` effective: ${params.effective}`
|
|
10321
|
-
],
|
|
10322
|
-
invalidModel: (input) => `Invalid model "${input}". Use the provider/model format, e.g. anthropic/claude-sonnet-4-5.`,
|
|
10323
|
-
pinnedTo: (ref) => `Baseline pinned to ${ref} (pinned mode).`,
|
|
10324
|
-
autoMode: "Baseline set to auto mode (cheapest-capable)."
|
|
10325
|
-
};
|
|
10326
|
-
var localMessages = {
|
|
10327
|
-
noRuntimes: "none",
|
|
10328
|
-
modeFrontierOnly: "frontier-only (frontierOnly)",
|
|
10329
|
-
modeLocalFirst: "local-first",
|
|
10330
|
-
summary: (params) => [
|
|
10331
|
-
"openteam local:",
|
|
10332
|
-
` mode: ${params.mode}`,
|
|
10333
|
-
` frontierOnly: ${params.frontierOnly}`,
|
|
10334
|
-
` privacy: ${params.privacy}`,
|
|
10335
|
-
` runtimes: ${params.runtimes}`
|
|
10336
|
-
],
|
|
10337
|
-
frontierOnlyNoChange: "No change: already in frontier-only mode.",
|
|
10338
|
-
privacyAdjustedNote: " Privacy adjusted to 'consentBeforeFrontier' (forceLocalOnSensitive requires a local runtime).",
|
|
10339
|
-
frontierOnlyEnabled: "Frontier-only mode enabled (frontierOnly=true).",
|
|
10340
|
-
localFirstNoChange: "No change: local-first routing is already active.",
|
|
10341
|
-
localFirstReEnabled: "Local-first routing re-enabled (frontierOnly=false). Make sure a local runtime is reachable."
|
|
10342
|
-
};
|
|
10343
|
-
var frontierCatalogMessages = {
|
|
10344
|
-
free: "free",
|
|
10345
|
-
costPerMillion: (inputUSD, outputUSD) => `$${inputUSD}/$${outputUSD} per 1M`
|
|
10346
|
-
};
|
|
10347
|
-
var migrateMessages = {
|
|
10348
|
-
header: "openteam migrate:",
|
|
10349
|
-
nothingToMigrate: " nothing to migrate — the P15b layout is already in place.",
|
|
10350
|
-
counts: (params) => [
|
|
10351
|
-
` moved: ${params.moved} file(s)`,
|
|
10352
|
-
` deduped: ${params.deduped} file(s) (destination already identical)`,
|
|
10353
|
-
` conflicts: ${params.conflicts} file(s)`
|
|
10354
|
-
],
|
|
10355
|
-
relocatedHeader: " relocated:",
|
|
10356
|
-
relocatedEntry: (from, to, deduped) => ` · ${from} → ${to}${deduped ? " (deduped)" : ""}`,
|
|
10357
|
-
conflictsHeader: " ⚠ left in place (destination exists with different content — no data lost):",
|
|
10358
|
-
conflictEntry: (from, to) => ` · ${from} → ${to}`,
|
|
10359
|
-
manualWorktreesHeader: " ⚠ legacy git worktree(s) need a manual move (run `git worktree move`):",
|
|
10360
|
-
manualWorktreeEntry: (label, from, to) => ` · ${label}: ${from} → ${to}`,
|
|
10361
|
-
success: " ✓ machine-local runtime output relocated under .opencode/openteam-local/."
|
|
10362
|
-
};
|
|
10363
|
-
var rolesInitMessages = {
|
|
10364
|
-
header: "openteam roles init:",
|
|
10365
|
-
title: "openteam roles init",
|
|
10366
|
-
noRoster: (path) => ` ✗ no roster at ${path}.`,
|
|
10367
|
-
noRosterRemedy: " Cast the team first: run `openteam setup`, then ask the orchestrator to register the cast.",
|
|
10368
|
-
unparseableRoster: (path, error) => ` ✗ ${path} is present but unparseable: ${error}`,
|
|
10369
|
-
unparseableRosterRemedy: " Run `openteam doctor` for the remedy, then re-run this command.",
|
|
10370
|
-
allProfiled: (count) => ` ✓ ${count} roster role(s) checked; no unprofiled roles — every role resolves a routing profile.`,
|
|
10371
|
-
whyTitle: "Why you are being asked",
|
|
10372
|
-
why: (count) => [
|
|
10373
|
-
`${count} roster role(s) resolve no routing profile.`,
|
|
10374
|
-
"Until a profile exists they are frontier-eligible: their work can escalate",
|
|
10375
|
-
"to a frontier model. A 'local only' note in the roster prose does not",
|
|
10376
|
-
"prevent this — prose outside the JSON fence is never read."
|
|
10377
|
-
].join(`
|
|
10378
|
-
`),
|
|
10379
|
-
policyQuestion: (roleID, agentName) => `Routing policy for "${roleID}" (${agentName})`,
|
|
10380
|
-
localOnlyLabel: (roleID, agentName) => `Local only — ${roleID} (${agentName}) never leaves this machine`,
|
|
10381
|
-
localOnlyHint: "sets requiresLocalRuntime; the role fails closed when no local runtime is reachable",
|
|
10382
|
-
frontierOkLabel: "Local first, frontier allowed",
|
|
10383
|
-
frontierOkHint: "records today's implicit behaviour: cheapest-capable frontier when the task warrants it",
|
|
10384
|
-
skipLabel: "Skip for now",
|
|
10385
|
-
skipHint: "leaves the role unprofiled, so it stays frontier-eligible and doctor keeps reporting it",
|
|
10386
|
-
runtimeQuestion: (roleID) => `Preferred local runtime(s) for "${roleID}" (in failover order)`,
|
|
10387
|
-
runtimeHint: "machine-local — written to the git-ignored overlay",
|
|
10388
|
-
cancelled: (reason) => ` ✗ cancelled (${reason}); nothing was written.`,
|
|
10389
|
-
invalidResult: (reason) => ` ✗ refusing to write: the resulting config is invalid — ${reason}`,
|
|
10390
|
-
nothingWritten: (skipped) => ` · skipped ${skipped}; nothing written.`,
|
|
10391
|
-
nothingToWriteOutro: "Nothing to write.",
|
|
10392
|
-
pinnedLocal: (roleIDs, path) => ` ✓ pinned local-only: ${roleIDs} → ${path} (tracked, travels with the roster)`,
|
|
10393
|
-
frontierAllowed: (roleIDs, path) => ` ✓ frontier allowed: ${roleIDs} → ${path} (model choice stays "auto": cheapest-capable per task)`,
|
|
10394
|
-
runtimeBinding: (path) => ` ✓ runtime binding: ${path} (git-ignored, machine-local)`,
|
|
10395
|
-
skippedSummary: (roleIDs) => ` · skipped: ${roleIDs} — still frontier-eligible; re-run this command to revisit.`,
|
|
10396
|
-
outro: "Role policy updated."
|
|
10397
|
-
};
|
|
10398
|
-
|
|
10399
10539
|
// src/commands/baseline.ts
|
|
10400
10540
|
function formatRef(ref) {
|
|
10401
10541
|
return ref === null ? "—" : `${ref.providerID}/${ref.modelID}`;
|
|
@@ -10897,6 +11037,9 @@ function renderWorktreeReconciliation(report) {
|
|
|
10897
11037
|
}
|
|
10898
11038
|
|
|
10899
11039
|
// src/commands/local.ts
|
|
11040
|
+
function hasEnabledLocalRuntime(config) {
|
|
11041
|
+
return config.local.runtimes.some((runtime) => runtime.enabled);
|
|
11042
|
+
}
|
|
10900
11043
|
function enabledRuntimesSummary(config) {
|
|
10901
11044
|
const enabled = config.local.runtimes.filter((runtime) => runtime.enabled);
|
|
10902
11045
|
if (enabled.length === 0) {
|
|
@@ -10909,10 +11052,11 @@ function enabledRuntimesSummary(config) {
|
|
|
10909
11052
|
}).join(", ");
|
|
10910
11053
|
}
|
|
10911
11054
|
function showLocal(config) {
|
|
10912
|
-
const mode = config.router.frontierOnly ? localMessages.modeFrontierOnly : localMessages.modeLocalFirst;
|
|
11055
|
+
const mode = config.router.localOnly ? localMessages.modeLocalOnly : config.router.frontierOnly ? localMessages.modeFrontierOnly : localMessages.modeLocalFirst;
|
|
10913
11056
|
const lines = localMessages.summary({
|
|
10914
11057
|
mode,
|
|
10915
11058
|
frontierOnly: config.router.frontierOnly,
|
|
11059
|
+
localOnly: config.router.localOnly,
|
|
10916
11060
|
privacy: config.privacyMode,
|
|
10917
11061
|
runtimes: enabledRuntimesSummary(config)
|
|
10918
11062
|
});
|
|
@@ -10920,13 +11064,13 @@ function showLocal(config) {
|
|
|
10920
11064
|
`) };
|
|
10921
11065
|
}
|
|
10922
11066
|
function setFrontierOnly(config) {
|
|
10923
|
-
if (config.router.frontierOnly) {
|
|
11067
|
+
if (config.router.frontierOnly && !config.router.localOnly) {
|
|
10924
11068
|
return { message: localMessages.frontierOnlyNoChange };
|
|
10925
11069
|
}
|
|
10926
11070
|
const flipsPrivacy = config.privacyMode === "forceLocalOnSensitive";
|
|
10927
11071
|
const next = {
|
|
10928
11072
|
...config,
|
|
10929
|
-
router: { ...config.router, frontierOnly: true },
|
|
11073
|
+
router: { ...config.router, frontierOnly: true, localOnly: false },
|
|
10930
11074
|
privacyMode: flipsPrivacy ? "consentBeforeFrontier" : config.privacyMode
|
|
10931
11075
|
};
|
|
10932
11076
|
const privacyNote = flipsPrivacy ? localMessages.privacyAdjustedNote : "";
|
|
@@ -10935,13 +11079,29 @@ function setFrontierOnly(config) {
|
|
|
10935
11079
|
message: `${localMessages.frontierOnlyEnabled}${privacyNote}`
|
|
10936
11080
|
};
|
|
10937
11081
|
}
|
|
11082
|
+
function setLocalOnly(config) {
|
|
11083
|
+
if (!hasEnabledLocalRuntime(config)) {
|
|
11084
|
+
return { message: localMessages.localOnlyNoRuntimes };
|
|
11085
|
+
}
|
|
11086
|
+
if (config.router.localOnly && !config.router.frontierOnly) {
|
|
11087
|
+
return { message: localMessages.localOnlyNoChange };
|
|
11088
|
+
}
|
|
11089
|
+
const next = {
|
|
11090
|
+
...config,
|
|
11091
|
+
router: { ...config.router, frontierOnly: false, localOnly: true }
|
|
11092
|
+
};
|
|
11093
|
+
return {
|
|
11094
|
+
config: next,
|
|
11095
|
+
message: localMessages.localOnlyEnabled
|
|
11096
|
+
};
|
|
11097
|
+
}
|
|
10938
11098
|
function setLocalFirst(config) {
|
|
10939
|
-
if (!config.router.frontierOnly) {
|
|
11099
|
+
if (!config.router.frontierOnly && !config.router.localOnly) {
|
|
10940
11100
|
return { message: localMessages.localFirstNoChange };
|
|
10941
11101
|
}
|
|
10942
11102
|
const next = {
|
|
10943
11103
|
...config,
|
|
10944
|
-
router: { ...config.router, frontierOnly: false }
|
|
11104
|
+
router: { ...config.router, frontierOnly: false, localOnly: false }
|
|
10945
11105
|
};
|
|
10946
11106
|
return {
|
|
10947
11107
|
config: next,
|
|
@@ -12406,9 +12566,10 @@ var HELP = [
|
|
|
12406
12566
|
" openteam baseline show Show the effective baseline",
|
|
12407
12567
|
" openteam baseline set <p/model> Pin the baseline (pinned mode)",
|
|
12408
12568
|
" openteam baseline auto Cheapest-capable baseline (auto mode)",
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12569
|
+
localMessages.help.status,
|
|
12570
|
+
localMessages.help.off,
|
|
12571
|
+
localMessages.help.only,
|
|
12572
|
+
localMessages.help.on,
|
|
12412
12573
|
" openteam doctor Diagnose runtimes and config",
|
|
12413
12574
|
" openteam roles init Compose per-role routing policy for unprofiled roster roles",
|
|
12414
12575
|
" openteam migrate Relocate pre-P15b artifacts to their split homes (idempotent)",
|
|
@@ -12579,23 +12740,40 @@ ${HELP}`
|
|
|
12579
12740
|
}
|
|
12580
12741
|
async function runLocal(positionals, deps, configPath, configResolution) {
|
|
12581
12742
|
const sub = positionals[1] ?? "status";
|
|
12582
|
-
if (sub !== "status" && sub !== "off" && sub !== "on") {
|
|
12743
|
+
if (sub !== "status" && sub !== "off" && sub !== "only" && sub !== "on") {
|
|
12583
12744
|
return {
|
|
12584
12745
|
exitCode: 1,
|
|
12585
|
-
stdout:
|
|
12586
|
-
|
|
12587
|
-
${HELP}`
|
|
12746
|
+
stdout: localMessages.unknownSubcommand(sub, HELP)
|
|
12588
12747
|
};
|
|
12589
12748
|
}
|
|
12590
12749
|
const config = await deps.loadConfig(configPath, configResolution);
|
|
12591
12750
|
if (sub === "status") {
|
|
12592
12751
|
return { exitCode: 0, stdout: showLocal(config).message };
|
|
12593
12752
|
}
|
|
12594
|
-
|
|
12753
|
+
let outcome;
|
|
12754
|
+
switch (sub) {
|
|
12755
|
+
case "off":
|
|
12756
|
+
outcome = setFrontierOnly(config);
|
|
12757
|
+
break;
|
|
12758
|
+
case "only":
|
|
12759
|
+
outcome = setLocalOnly(config);
|
|
12760
|
+
break;
|
|
12761
|
+
case "on":
|
|
12762
|
+
outcome = setLocalFirst(config);
|
|
12763
|
+
break;
|
|
12764
|
+
default:
|
|
12765
|
+
return {
|
|
12766
|
+
exitCode: 1,
|
|
12767
|
+
stdout: localMessages.unknownSubcommand(sub, HELP)
|
|
12768
|
+
};
|
|
12769
|
+
}
|
|
12595
12770
|
if (outcome.config !== undefined) {
|
|
12596
12771
|
await deps.saveConfig(outcome.config, configPath, configResolution);
|
|
12597
12772
|
}
|
|
12598
|
-
return {
|
|
12773
|
+
return {
|
|
12774
|
+
exitCode: sub === "only" && !hasEnabledLocalRuntime(config) ? 1 : 0,
|
|
12775
|
+
stdout: outcome.message
|
|
12776
|
+
};
|
|
12599
12777
|
}
|
|
12600
12778
|
async function runYolo(positionals, deps, opencodeConfigPaths) {
|
|
12601
12779
|
const sub = positionals[1] ?? "status";
|
|
@@ -15263,7 +15441,7 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
|
|
|
15263
15441
|
// package.json
|
|
15264
15442
|
var package_default = {
|
|
15265
15443
|
name: "@jmanuelcorral/openteam",
|
|
15266
|
-
version: "0.
|
|
15444
|
+
version: "0.12.0",
|
|
15267
15445
|
packageManager: "bun@1.3.14",
|
|
15268
15446
|
description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
|
|
15269
15447
|
license: "MIT",
|