@monoes/monomindcli 2.12.0 → 2.14.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/.claude/skills/mastermind/SKILL.md +1 -15
- package/README.md +1 -1
- package/dist/src/commands/doctor-mcp-probe.d.ts +48 -0
- package/dist/src/commands/doctor-mcp-probe.d.ts.map +1 -0
- package/dist/src/commands/doctor-mcp-probe.js +205 -0
- package/dist/src/commands/doctor-mcp-probe.js.map +1 -0
- package/dist/src/commands/doctor-monoes-checks.d.ts +1 -0
- package/dist/src/commands/doctor-monoes-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-monoes-checks.js +96 -34
- package/dist/src/commands/doctor-monoes-checks.js.map +1 -1
- package/dist/src/commands/doctor-project-checks.d.ts +31 -1
- package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
- package/dist/src/commands/doctor-project-checks.js +109 -4
- package/dist/src/commands/doctor-project-checks.js.map +1 -1
- package/dist/src/commands/doctor.d.ts.map +1 -1
- package/dist/src/commands/doctor.js +21 -3
- package/dist/src/commands/doctor.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +29 -0
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +4 -3
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/commands/org-observe.d.ts.map +1 -1
- package/dist/src/commands/org-observe.js +14 -0
- package/dist/src/commands/org-observe.js.map +1 -1
- package/dist/src/commands/org.d.ts.map +1 -1
- package/dist/src/commands/org.js +21 -4
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/commands/ui.d.ts.map +1 -1
- package/dist/src/commands/ui.js +4 -0
- package/dist/src/commands/ui.js.map +1 -1
- package/dist/src/init/claudemd-generator.d.ts.map +1 -1
- package/dist/src/init/claudemd-generator.js +2 -1
- package/dist/src/init/claudemd-generator.js.map +1 -1
- package/dist/src/init/copy-assets.d.ts.map +1 -1
- package/dist/src/init/copy-assets.js +56 -18
- package/dist/src/init/copy-assets.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +9 -1
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/mcp-generator.d.ts.map +1 -1
- package/dist/src/init/mcp-generator.js +3 -3
- package/dist/src/init/mcp-generator.js.map +1 -1
- package/dist/src/init/never-commit.d.ts +39 -0
- package/dist/src/init/never-commit.d.ts.map +1 -0
- package/dist/src/init/never-commit.js +15 -0
- package/dist/src/init/never-commit.js.map +1 -0
- package/dist/src/init/shared.d.ts +74 -1
- package/dist/src/init/shared.d.ts.map +1 -1
- package/dist/src/init/shared.js +166 -0
- package/dist/src/init/shared.js.map +1 -1
- package/dist/src/init/types.d.ts +11 -0
- package/dist/src/init/types.d.ts.map +1 -1
- package/dist/src/init/types.js.map +1 -1
- package/dist/src/init/upgrade.d.ts.map +1 -1
- package/dist/src/init/upgrade.js +1 -0
- package/dist/src/init/upgrade.js.map +1 -1
- package/dist/src/init/write-kimicode.d.ts.map +1 -1
- package/dist/src/init/write-kimicode.js +13 -9
- package/dist/src/init/write-kimicode.js.map +1 -1
- package/dist/src/init/write-opencode.d.ts.map +1 -1
- package/dist/src/init/write-opencode.js +44 -2
- package/dist/src/init/write-opencode.js.map +1 -1
- package/dist/src/init/write-runtime-config.d.ts +2 -0
- package/dist/src/init/write-runtime-config.d.ts.map +1 -1
- package/dist/src/init/write-runtime-config.js +65 -38
- package/dist/src/init/write-runtime-config.js.map +1 -1
- package/dist/src/mastermind/manifest-data.d.ts.map +1 -1
- package/dist/src/mastermind/manifest-data.js +14 -0
- package/dist/src/mastermind/manifest-data.js.map +1 -1
- package/dist/src/mcp/monoes-mcp-entry.mjs +86 -0
- package/dist/src/mcp-tools/terminal-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/terminal-tools.js +52 -9
- package/dist/src/mcp-tools/terminal-tools.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts +84 -3
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +215 -17
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/orgrt/agent-runner.d.ts +38 -0
- package/dist/src/orgrt/agent-runner.d.ts.map +1 -1
- package/dist/src/orgrt/agent-runner.js +47 -0
- package/dist/src/orgrt/agent-runner.js.map +1 -1
- package/dist/src/orgrt/checkpoint.d.ts +12 -1
- package/dist/src/orgrt/checkpoint.d.ts.map +1 -1
- package/dist/src/orgrt/checkpoint.js +15 -6
- package/dist/src/orgrt/checkpoint.js.map +1 -1
- package/dist/src/orgrt/completion-gate.d.ts +74 -0
- package/dist/src/orgrt/completion-gate.d.ts.map +1 -1
- package/dist/src/orgrt/completion-gate.js +45 -0
- package/dist/src/orgrt/completion-gate.js.map +1 -1
- package/dist/src/orgrt/cost-tier.d.ts +144 -0
- package/dist/src/orgrt/cost-tier.d.ts.map +1 -0
- package/dist/src/orgrt/cost-tier.js +183 -0
- package/dist/src/orgrt/cost-tier.js.map +1 -0
- package/dist/src/orgrt/cross-org.d.ts.map +1 -1
- package/dist/src/orgrt/cross-org.js +14 -0
- package/dist/src/orgrt/cross-org.js.map +1 -1
- package/dist/src/orgrt/daemon.d.ts +12 -1
- package/dist/src/orgrt/daemon.d.ts.map +1 -1
- package/dist/src/orgrt/daemon.js +216 -49
- package/dist/src/orgrt/daemon.js.map +1 -1
- package/dist/src/orgrt/decisions.d.ts +14 -2
- package/dist/src/orgrt/decisions.d.ts.map +1 -1
- package/dist/src/orgrt/decisions.js +216 -10
- package/dist/src/orgrt/decisions.js.map +1 -1
- package/dist/src/orgrt/file-roots.d.ts +7 -0
- package/dist/src/orgrt/file-roots.d.ts.map +1 -1
- package/dist/src/orgrt/file-roots.js +38 -1
- package/dist/src/orgrt/file-roots.js.map +1 -1
- package/dist/src/orgrt/forwarder.d.ts.map +1 -1
- package/dist/src/orgrt/forwarder.js +16 -2
- package/dist/src/orgrt/forwarder.js.map +1 -1
- package/dist/src/orgrt/idle-deadline.d.ts +74 -1
- package/dist/src/orgrt/idle-deadline.d.ts.map +1 -1
- package/dist/src/orgrt/idle-deadline.js +52 -2
- package/dist/src/orgrt/idle-deadline.js.map +1 -1
- package/dist/src/orgrt/loadouts.d.ts +38 -0
- package/dist/src/orgrt/loadouts.d.ts.map +1 -0
- package/dist/src/orgrt/loadouts.js +129 -0
- package/dist/src/orgrt/loadouts.js.map +1 -0
- package/dist/src/orgrt/mailbox.d.ts +17 -1
- package/dist/src/orgrt/mailbox.d.ts.map +1 -1
- package/dist/src/orgrt/mailbox.js +50 -3
- package/dist/src/orgrt/mailbox.js.map +1 -1
- package/dist/src/orgrt/policy.d.ts +42 -2
- package/dist/src/orgrt/policy.d.ts.map +1 -1
- package/dist/src/orgrt/policy.js +53 -8
- package/dist/src/orgrt/policy.js.map +1 -1
- package/dist/src/orgrt/prompt-vars.d.ts +11 -0
- package/dist/src/orgrt/prompt-vars.d.ts.map +1 -0
- package/dist/src/orgrt/prompt-vars.js +49 -0
- package/dist/src/orgrt/prompt-vars.js.map +1 -0
- package/dist/src/orgrt/questions.d.ts +22 -1
- package/dist/src/orgrt/questions.d.ts.map +1 -1
- package/dist/src/orgrt/questions.js +24 -3
- package/dist/src/orgrt/questions.js.map +1 -1
- package/dist/src/orgrt/review-packet.d.ts +29 -0
- package/dist/src/orgrt/review-packet.d.ts.map +1 -0
- package/dist/src/orgrt/review-packet.js +67 -0
- package/dist/src/orgrt/review-packet.js.map +1 -0
- package/dist/src/orgrt/role-sandbox.d.ts.map +1 -1
- package/dist/src/orgrt/role-sandbox.js +16 -3
- package/dist/src/orgrt/role-sandbox.js.map +1 -1
- package/dist/src/orgrt/session-ledger.d.ts +61 -0
- package/dist/src/orgrt/session-ledger.d.ts.map +1 -0
- package/dist/src/orgrt/session-ledger.js +113 -0
- package/dist/src/orgrt/session-ledger.js.map +1 -0
- package/dist/src/orgrt/session.d.ts +45 -5
- package/dist/src/orgrt/session.d.ts.map +1 -1
- package/dist/src/orgrt/session.js +384 -27
- package/dist/src/orgrt/session.js.map +1 -1
- package/dist/src/orgrt/task-dag.d.ts +34 -1
- package/dist/src/orgrt/task-dag.d.ts.map +1 -1
- package/dist/src/orgrt/task-dag.js +58 -7
- package/dist/src/orgrt/task-dag.js.map +1 -1
- package/dist/src/orgrt/tool-spill.d.ts +94 -0
- package/dist/src/orgrt/tool-spill.d.ts.map +1 -0
- package/dist/src/orgrt/tool-spill.js +180 -0
- package/dist/src/orgrt/tool-spill.js.map +1 -0
- package/dist/src/orgrt/types.d.ts +138 -0
- package/dist/src/orgrt/types.d.ts.map +1 -1
- package/dist/src/orgrt/types.js +148 -0
- package/dist/src/orgrt/types.js.map +1 -1
- package/dist/src/platform-adapters/renderers/mcp.d.ts +20 -2
- package/dist/src/platform-adapters/renderers/mcp.d.ts.map +1 -1
- package/dist/src/platform-adapters/renderers/mcp.js +25 -5
- package/dist/src/platform-adapters/renderers/mcp.js.map +1 -1
- package/dist/src/platform-adapters/renderers/skills.d.ts +0 -6
- package/dist/src/platform-adapters/renderers/skills.d.ts.map +1 -1
- package/dist/src/platform-adapters/renderers/skills.js +12 -22
- package/dist/src/platform-adapters/renderers/skills.js.map +1 -1
- package/dist/src/protocol-capabilities.d.ts +2 -1
- package/dist/src/protocol-capabilities.d.ts.map +1 -1
- package/dist/src/protocol-capabilities.js +2 -1
- package/dist/src/protocol-capabilities.js.map +1 -1
- package/dist/src/ui/dashboard.html +457 -164
- package/dist/src/ui/org-hil.mjs +276 -0
- package/dist/src/ui/org-runtime.mjs +350 -0
- package/dist/src/ui/orgs.html +22 -4
- package/dist/src/ui/routes-monoes.mjs +28 -3
- package/dist/src/ui/routes-org.mjs +290 -541
- package/dist/src/ui/server.mjs +185 -19
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
|
@@ -1646,7 +1646,7 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
1646
1646
|
<div class="nav-item" data-view="global-tokens" title="Token usage across all projects">
|
|
1647
1647
|
<span class="ico">$</span><span class="lbl">Global Tokens</span>
|
|
1648
1648
|
</div>
|
|
1649
|
-
<div class="nav-item" data-view="humaninput" title="
|
|
1649
|
+
<div class="nav-item" data-view="humaninput" title="Questions, approvals and decision gates waiting on you, across all orgs">
|
|
1650
1650
|
<span class="ico">❓</span><span class="lbl">Human Input</span>
|
|
1651
1651
|
<span class="bdg" id="bdg-humaninput">—</span>
|
|
1652
1652
|
</div>
|
|
@@ -2094,6 +2094,7 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
2094
2094
|
<div id="org-detail-tabs">
|
|
2095
2095
|
<button class="odt-btn active" data-tab="chart" onclick="v2SwitchOrgTab('chart')">Chart</button>
|
|
2096
2096
|
<button class="odt-btn" data-tab="chat" onclick="v2SwitchOrgTab('chat')">Chat</button>
|
|
2097
|
+
<button class="odt-btn" data-tab="runtime" onclick="v2SwitchOrgTab('runtime')">Runtime</button>
|
|
2097
2098
|
<div class="odt-tab-sep" title="Secondary tabs"></div>
|
|
2098
2099
|
<button class="odt-btn secondary" data-tab="config" onclick="v2SwitchOrgTab('config')">Config</button>
|
|
2099
2100
|
<button class="odt-btn secondary" data-tab="files" onclick="v2SwitchOrgTab('files')">Files</button>
|
|
@@ -2103,6 +2104,7 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
2103
2104
|
<div id="org-detail-body">
|
|
2104
2105
|
<div class="odt-pane active" id="odt-chart"></div>
|
|
2105
2106
|
<div class="odt-pane" id="odt-chat" style="flex-direction:column;height:100%;overflow:hidden;"></div>
|
|
2107
|
+
<div class="odt-pane" id="odt-runtime" style="overflow-y:auto"></div>
|
|
2106
2108
|
<div class="odt-pane" id="odt-config" style="overflow-y:auto"></div>
|
|
2107
2109
|
<div class="odt-pane" id="odt-files" style="overflow-y:auto"></div>
|
|
2108
2110
|
<div class="odt-pane" id="odt-approvals"></div>
|
|
@@ -2303,11 +2305,11 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
2303
2305
|
<div class="vscroll">
|
|
2304
2306
|
<div style="display:flex;align-items:baseline;gap:10px;margin-bottom:4px">
|
|
2305
2307
|
<div class="pg-title" style="margin-bottom:0">Human Input</div>
|
|
2306
|
-
<span class="pg-sub" id="hi-sub" style="margin-bottom:0">
|
|
2308
|
+
<span class="pg-sub" id="hi-sub" style="margin-bottom:0">Questions, approvals and decision gates waiting on you</span>
|
|
2307
2309
|
</div>
|
|
2308
2310
|
<div class="hi-tab-bar" style="display:flex;gap:4px;margin-top:12px;border-bottom:1px solid var(--border);padding-bottom:6px">
|
|
2309
2311
|
<button class="odt-btn active" data-hitab="pending" onclick="hiSwitchTab('pending')">Pending <span style="font-size:10px;background:var(--surface-hi);color:var(--text-lo);border-radius:8px;padding:1px 6px;min-width:18px;display:inline-block;text-align:center" id="hi-tab-count-pending">—</span></button>
|
|
2310
|
-
<button class="odt-btn" data-hitab="answered" onclick="hiSwitchTab('answered')">
|
|
2312
|
+
<button class="odt-btn" data-hitab="answered" onclick="hiSwitchTab('answered')">Resolved <span style="font-size:10px;background:var(--surface-hi);color:var(--text-lo);border-radius:8px;padding:1px 6px;min-width:18px;display:inline-block;text-align:center" id="hi-tab-count-answered">—</span></button>
|
|
2311
2313
|
</div>
|
|
2312
2314
|
<div id="hi-content" style="margin-top:16px"><div class="loading-txt">Loading…</div></div>
|
|
2313
2315
|
</div>
|
|
@@ -2446,6 +2448,13 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
2446
2448
|
// automatically. EventSource can't carry custom headers at all, so SSE call
|
|
2447
2449
|
// sites must use window.mmEventSource(url) instead of `new EventSource(url)`
|
|
2448
2450
|
// directly — it appends the credential as a query param.
|
|
2451
|
+
//
|
|
2452
|
+
// i-073: the query param is accepted for GET/HEAD ONLY — a POST/PUT/DELETE
|
|
2453
|
+
// with only ?token= is a CORS-simple request (no preflight, reachable from a
|
|
2454
|
+
// plain <form>), so mutations MUST send the header. Any new call that
|
|
2455
|
+
// mutates has to go through fetch() (the wrapper above), never a hand-built
|
|
2456
|
+
// URL with the token appended — that would silently reopen the CSRF hole
|
|
2457
|
+
// this item closed.
|
|
2449
2458
|
(() => {
|
|
2450
2459
|
const AUTH_HEADER_NAME = 'x-monomind-' + 'token';
|
|
2451
2460
|
const AUTH_QUERY_PARAM = 'to' + 'ken';
|
|
@@ -2620,6 +2629,8 @@ async function init() {
|
|
|
2620
2629
|
sbProjEl.title = DIR;
|
|
2621
2630
|
_showNavProjectCtx(DIR);
|
|
2622
2631
|
} catch (_) {}
|
|
2632
|
+
// Count what is waiting on a human now, not only once the view is opened.
|
|
2633
|
+
_hiScheduleReload();
|
|
2623
2634
|
// deep-link: ?sess=<id>&proj=<path>
|
|
2624
2635
|
const params = new URLSearchParams(window.location.search);
|
|
2625
2636
|
let projParam = params.get('proj');
|
|
@@ -4439,38 +4450,57 @@ async function _gdCopyPath(p) {
|
|
|
4439
4450
|
// renderDocMarkdown() and gdCopyCode() moved to markdown.js (#124) — loaded via
|
|
4440
4451
|
// <script src="markdown.js"> above; both remain plain globals.
|
|
4441
4452
|
|
|
4453
|
+
// Human Input: everything an org is waiting on a human for — ask_human
|
|
4454
|
+
// questions, tool/action approvals and decision gates — across projects.
|
|
4455
|
+
// Each entry: { kind: 'question'|'approval'|'gate', project, item }.
|
|
4442
4456
|
let _hiEntries = [];
|
|
4443
4457
|
let _hiTab = 'pending';
|
|
4444
4458
|
|
|
4459
|
+
function _hiIsPending(e) {
|
|
4460
|
+
if (e.kind === 'question') return e.item.answer === null || e.item.answer === undefined;
|
|
4461
|
+
return (e.item.status || 'pending') === 'pending';
|
|
4462
|
+
}
|
|
4463
|
+
function _hiWhen(e) {
|
|
4464
|
+
const i = e.item;
|
|
4465
|
+
return i.answeredAt || i.resolvedAt || i.ts || i.createdAt || 0;
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4445
4468
|
async function renderHumanInputView() {
|
|
4446
4469
|
const el = document.getElementById('hi-content');
|
|
4447
|
-
el.innerHTML = '<div class="loading-txt">Loading all projects…</div>';
|
|
4470
|
+
if (!_hiEntries.length) el.innerHTML = '<div class="loading-txt">Loading all projects…</div>';
|
|
4448
4471
|
try {
|
|
4449
4472
|
const data = await apiFetch('/api/projects');
|
|
4450
4473
|
const projects = (data?.projects || []).slice(0, 8);
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
updateHumanInputBadge(0);
|
|
4455
|
-
renderHiCurrentTab(projects.length);
|
|
4456
|
-
return;
|
|
4457
|
-
}
|
|
4474
|
+
// The project this dashboard serves always counts, even when it is not
|
|
4475
|
+
// among the most recently active Claude projects.
|
|
4476
|
+
if (DIR && !projects.some(p => p.path === DIR)) projects.unshift({ path: DIR, name: baseName(DIR) });
|
|
4458
4477
|
const results = await Promise.allSettled(
|
|
4459
|
-
projects.map(p => apiFetch(`/api/
|
|
4478
|
+
projects.map(p => apiFetch(`/api/hil?dir=${enc(p.path)}`).then(d => ({ project: p, d: d || {} })))
|
|
4460
4479
|
);
|
|
4461
4480
|
const entries = [];
|
|
4481
|
+
const errors = [];
|
|
4462
4482
|
for (const r of results) {
|
|
4463
4483
|
if (r.status !== 'fulfilled') continue;
|
|
4464
|
-
const { project,
|
|
4465
|
-
for (const
|
|
4484
|
+
const { project, d } = r.value;
|
|
4485
|
+
for (const item of d.questions || []) entries.push({ kind: 'question', project, item });
|
|
4486
|
+
for (const item of d.approvals || []) if (item.id) entries.push({ kind: 'approval', project, item });
|
|
4487
|
+
for (const item of d.gates || []) entries.push({ kind: 'gate', project, item });
|
|
4488
|
+
for (const e of d.errors || []) errors.push(`${project.name || project.path}/${e.org}: ${e.error}`);
|
|
4466
4489
|
}
|
|
4467
|
-
entries.sort((a, b) => (b
|
|
4490
|
+
entries.sort((a, b) => _hiWhen(b) - _hiWhen(a));
|
|
4468
4491
|
_hiEntries = entries;
|
|
4469
|
-
updateHumanInputBadge(entries.filter(
|
|
4470
|
-
renderHiCurrentTab(projects.length);
|
|
4492
|
+
updateHumanInputBadge(entries.filter(_hiIsPending).length);
|
|
4493
|
+
renderHiCurrentTab(projects.length, errors);
|
|
4471
4494
|
} catch (e) { el.innerHTML = `<div class="empty">Failed: ${esc(e.message)}</div>`; }
|
|
4472
4495
|
}
|
|
4473
4496
|
|
|
4497
|
+
// Coalesce bursts of org:question / org:gate / resolution events into one reload.
|
|
4498
|
+
let _hiReloadTimer = null;
|
|
4499
|
+
function _hiScheduleReload() {
|
|
4500
|
+
clearTimeout(_hiReloadTimer);
|
|
4501
|
+
_hiReloadTimer = setTimeout(renderHumanInputView, 400);
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4474
4504
|
function _hiSwitchTab(tab) {
|
|
4475
4505
|
_hiTab = tab;
|
|
4476
4506
|
document.querySelectorAll('.hi-tab-bar .odt-btn').forEach(b =>
|
|
@@ -4478,39 +4508,80 @@ function _hiSwitchTab(tab) {
|
|
|
4478
4508
|
renderHiCurrentTab();
|
|
4479
4509
|
}
|
|
4480
4510
|
|
|
4481
|
-
|
|
4511
|
+
const _HI_KIND_LABEL = { question: 'Question', approval: 'Approval', gate: 'Decision gate' };
|
|
4512
|
+
|
|
4513
|
+
function _hiPill(text, color) {
|
|
4514
|
+
return `<span class="ss-pill" style="color:${color};border-color:${color}">${esc(text)}</span>`;
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4517
|
+
function _hiEntryHtml(e) {
|
|
4518
|
+
const i = e.item;
|
|
4519
|
+
const pending = _hiIsPending(e);
|
|
4520
|
+
const data = `data-kind="${e.kind}" data-dir="${esc(e.project.path)}" data-org="${esc(i.org)}" data-id="${esc(e.kind === 'question' ? i.questionId : i.id)}"`;
|
|
4521
|
+
let badge = '';
|
|
4522
|
+
let body = '';
|
|
4523
|
+
let resolved = '';
|
|
4524
|
+
let actions = '';
|
|
4525
|
+
if (e.kind === 'question') {
|
|
4526
|
+
// Only a pending question can hold a run; once answered the flag is history.
|
|
4527
|
+
if (pending) badge = i.blocking === false
|
|
4528
|
+
? _hiPill('non-blocking', 'var(--text-lo)')
|
|
4529
|
+
: _hiPill('blocking — run waits', 'var(--amber, #d29922)');
|
|
4530
|
+
body = `<div style="color:var(--text-hi);white-space:pre-wrap;max-height:240px;overflow:auto">${esc(i.question)}</div>`;
|
|
4531
|
+
resolved = `<span style="color:var(--text-xs)">Answer:</span> ${esc(i.answer ?? '')}`;
|
|
4532
|
+
actions = `<input type="text" class="hi-answer-input" placeholder="Your answer…" aria-label="Answer" style="flex:1;padding:4px 8px;font-size:12px" />
|
|
4533
|
+
<button class="btn" style="font-size:11px" ${data} onclick="hiAct(this,'answer')">Answer</button>`;
|
|
4534
|
+
} else if (e.kind === 'approval') {
|
|
4535
|
+
badge = _hiPill(i.action || 'tool', 'var(--accent, #58a6ff)');
|
|
4536
|
+
body = `<div style="color:var(--text-hi)">Wants to run <b>${esc(i.action)}</b></div>` +
|
|
4537
|
+
(i.input ? `<pre style="font-size:11px;white-space:pre-wrap;max-height:160px;overflow:auto;margin:0;color:var(--text-lo)">${esc(JSON.stringify(i.input, null, 2))}</pre>` : '');
|
|
4538
|
+
resolved = `${_hiPill(i.status, i.status === 'approved' ? 'var(--green)' : 'var(--red)')}`;
|
|
4539
|
+
actions = `<button class="btn" style="font-size:11px;color:var(--green);border-color:var(--green)" ${data} onclick="hiAct(this,'approve')">Approve</button>
|
|
4540
|
+
<button class="btn" style="font-size:11px;color:var(--red);border-color:var(--red)" ${data} onclick="hiAct(this,'reject')">Deny</button>`;
|
|
4541
|
+
} else {
|
|
4542
|
+
body = `<div style="color:var(--text-hi);font-weight:600">${esc(i.name || '')}</div>
|
|
4543
|
+
<div style="color:var(--text-lo);white-space:pre-wrap;max-height:240px;overflow:auto">${esc(i.description || '')}</div>`;
|
|
4544
|
+
resolved = `${_hiPill(i.status, i.status === 'approved' ? 'var(--green)' : 'var(--red)')}${i.resolution ? ` ${esc(i.resolution)}` : ''}`;
|
|
4545
|
+
actions = `<input type="text" class="hi-answer-input" placeholder="Resolution note (optional)…" aria-label="Resolution note" style="flex:1;padding:4px 8px;font-size:12px" />
|
|
4546
|
+
<button class="btn" style="font-size:11px;color:var(--green);border-color:var(--green)" ${data} onclick="hiAct(this,'approve')">Approve</button>
|
|
4547
|
+
<button class="btn" style="font-size:11px;color:var(--red);border-color:var(--red)" ${data} onclick="hiAct(this,'reject')">Reject</button>`;
|
|
4548
|
+
}
|
|
4549
|
+
const who = i.resolvedBy ? ` · by ${esc(i.resolvedBy)}` : '';
|
|
4550
|
+
return `<div class="sess-row" style="flex-direction:column;align-items:stretch;gap:6px">
|
|
4551
|
+
<div style="display:flex;justify-content:space-between;gap:8px;align-items:center">
|
|
4552
|
+
<div><span style="color:var(--text-xs)">${_HI_KIND_LABEL[e.kind]}</span> · <b>${esc(i.org)}</b> · ${esc(i.role || i.roleId || '')} ${badge}
|
|
4553
|
+
<span style="color:var(--text-xs)">(${esc(e.project.name || e.project.path)})</span></div>
|
|
4554
|
+
<div style="color:var(--text-xs);font-size:11px;font-family:var(--mono)">${relTime(i.ts || i.createdAt)}</div>
|
|
4555
|
+
</div>
|
|
4556
|
+
${body}
|
|
4557
|
+
${pending
|
|
4558
|
+
? `<div style="display:flex;gap:6px;align-items:center">${actions}</div>`
|
|
4559
|
+
: `<div style="display:flex;justify-content:space-between;gap:8px;padding:6px 8px;background:var(--surface-hi);border-radius:4px">
|
|
4560
|
+
<div style="white-space:pre-wrap;max-height:160px;overflow:auto;min-width:0">${resolved}</div>
|
|
4561
|
+
<div style="color:var(--text-xs);font-size:11px;font-family:var(--mono);flex-shrink:0">${relTime(i.answeredAt || i.resolvedAt)}${who}</div>
|
|
4562
|
+
</div>`}
|
|
4563
|
+
</div>`;
|
|
4564
|
+
}
|
|
4565
|
+
|
|
4566
|
+
function renderHiCurrentTab(projectCount, errors) {
|
|
4482
4567
|
const el = document.getElementById('hi-content');
|
|
4483
|
-
const pending = _hiEntries.filter(
|
|
4484
|
-
const
|
|
4568
|
+
const pending = _hiEntries.filter(_hiIsPending);
|
|
4569
|
+
const done = _hiEntries.filter(e => !_hiIsPending(e));
|
|
4485
4570
|
document.getElementById('hi-tab-count-pending').textContent = pending.length || '—';
|
|
4486
|
-
document.getElementById('hi-tab-count-answered').textContent =
|
|
4487
|
-
const entries = _hiTab === 'answered' ?
|
|
4571
|
+
document.getElementById('hi-tab-count-answered').textContent = done.length || '—';
|
|
4572
|
+
const entries = _hiTab === 'answered' ? done : pending;
|
|
4488
4573
|
const projTxt = projectCount != null ? ` across ${projectCount} project${projectCount !== 1 ? 's' : ''}` : '';
|
|
4574
|
+
const blocking = pending.filter(e => e.kind !== 'question' || e.item.blocking !== false).length;
|
|
4489
4575
|
document.getElementById('hi-sub').textContent = _hiTab === 'answered'
|
|
4490
|
-
? (
|
|
4491
|
-
: (pending.length ? `${pending.length}
|
|
4576
|
+
? (done.length ? `${done.length} resolved${projTxt}` : `Nothing resolved yet${projTxt}`)
|
|
4577
|
+
: (pending.length ? `${pending.length} waiting on you (${blocking} holding a run)${projTxt}` : `Nothing waiting on you${projTxt}`);
|
|
4578
|
+
const errHtml = (errors || []).length
|
|
4579
|
+
? `<div class="empty" style="color:var(--red)">Unreadable: ${errors.map(esc).join('; ')}</div>` : '';
|
|
4492
4580
|
if (!entries.length) {
|
|
4493
|
-
el.innerHTML =
|
|
4581
|
+
el.innerHTML = `${errHtml}<div class="empty"><div class="empty-ico">❓</div><div>${_hiTab === 'answered' ? 'Nothing resolved yet' : 'Nothing waiting on you'}</div></div>`;
|
|
4494
4582
|
return;
|
|
4495
4583
|
}
|
|
4496
|
-
el.innerHTML =
|
|
4497
|
-
<div class="sess-row" data-question-id="${esc(q.questionId)}" style="flex-direction:column;align-items:stretch;gap:6px">
|
|
4498
|
-
<div style="display:flex;justify-content:space-between;gap:8px">
|
|
4499
|
-
<div><b>${esc(q.org)}</b> · ${esc(q.role)} <span style="color:var(--text-xs)">(${esc(project.name || project.path)})</span></div>
|
|
4500
|
-
<div style="color:var(--text-xs);font-size:11px;font-family:var(--mono)">${relTime(q.ts)}</div>
|
|
4501
|
-
</div>
|
|
4502
|
-
<div style="color:var(--text-hi)">${esc(q.question)}</div>
|
|
4503
|
-
${_hiTab === 'answered' ? `
|
|
4504
|
-
<div style="display:flex;justify-content:space-between;gap:8px;padding:6px 8px;background:var(--surface-hi);border-radius:4px">
|
|
4505
|
-
<div><span style="color:var(--text-xs)">Answer:</span> ${esc(q.answer)}</div>
|
|
4506
|
-
<div style="color:var(--text-xs);font-size:11px;font-family:var(--mono);flex-shrink:0">${relTime(q.answeredAt)}</div>
|
|
4507
|
-
</div>` : `
|
|
4508
|
-
<div style="display:flex;gap:6px">
|
|
4509
|
-
<input type="text" class="hi-answer-input" placeholder="Your answer…" style="flex:1;padding:4px 8px;font-size:12px" />
|
|
4510
|
-
<button class="btn" style="font-size:11px" onclick="submitHumanAnswer('${esc(String(project.path).replace(/['\\]/g,'\\$&'))}','${esc(String(q.org).replace(/['\\]/g,'\\$&'))}','${esc(String(q.role).replace(/['\\]/g,'\\$&'))}','${esc(String(q.questionId).replace(/['\\]/g,'\\$&'))}',this)">Answer</button>
|
|
4511
|
-
</div>`}
|
|
4512
|
-
</div>
|
|
4513
|
-
`).join('')}</div>`;
|
|
4584
|
+
el.innerHTML = `${errHtml}<div class="sess-list">${entries.map(_hiEntryHtml).join('')}</div>`;
|
|
4514
4585
|
}
|
|
4515
4586
|
|
|
4516
4587
|
function updateHumanInputBadge(count) {
|
|
@@ -4518,26 +4589,38 @@ function updateHumanInputBadge(count) {
|
|
|
4518
4589
|
if (b) b.textContent = String(count);
|
|
4519
4590
|
}
|
|
4520
4591
|
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
const input = row.querySelector('.hi-answer-input');
|
|
4524
|
-
const answer = input.value.trim();
|
|
4525
|
-
if (!answer) return;
|
|
4526
|
-
btnEl.disabled = true;
|
|
4592
|
+
/** POST a human decision; toasts the outcome. Resolves to the response data, or null on failure. */
|
|
4593
|
+
async function _hilPost(url, body) {
|
|
4527
4594
|
try {
|
|
4528
|
-
const res = await fetch(
|
|
4529
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
4530
|
-
body: JSON.stringify({ dir, org, role, questionId, answer }),
|
|
4595
|
+
const res = await fetch(url, {
|
|
4596
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
4531
4597
|
});
|
|
4532
|
-
const data = await res.json();
|
|
4533
|
-
if (data.ok) {
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4598
|
+
const data = await res.json().catch(() => ({}));
|
|
4599
|
+
if (!res.ok || !data.ok) { showToast('Not delivered', data.error || `HTTP ${res.status}`, 'err'); return null; }
|
|
4600
|
+
showToast('Done', data.delivery === 'live'
|
|
4601
|
+
? 'Delivered to the running org'
|
|
4602
|
+
: 'Recorded — the org picks it up when it next runs', 'ok');
|
|
4603
|
+
return data;
|
|
4604
|
+
} catch (e) { showToast('Error', e.message, 'err'); return null; }
|
|
4605
|
+
}
|
|
4606
|
+
|
|
4607
|
+
async function _hiAct(btnEl, verb) {
|
|
4608
|
+
const { kind, dir, org, id } = btnEl.dataset;
|
|
4609
|
+
const row = btnEl.closest('.sess-row');
|
|
4610
|
+
const text = row?.querySelector('.hi-answer-input')?.value.trim() || '';
|
|
4611
|
+
const q = `?dir=${enc(dir)}`;
|
|
4612
|
+
let ok = null;
|
|
4613
|
+
row?.querySelectorAll('button').forEach(b => { b.disabled = true; });
|
|
4614
|
+
if (kind === 'question') {
|
|
4615
|
+
if (!text) { row?.querySelectorAll('button').forEach(b => { b.disabled = false; }); return; }
|
|
4616
|
+
ok = await _hilPost('/api/questions/answer', { dir, org, questionId: id, answer: text });
|
|
4617
|
+
} else if (kind === 'approval') {
|
|
4618
|
+
ok = await _hilPost(`/api/org/${enc(org)}/approvals/${enc(id)}${q}`, { action: verb });
|
|
4619
|
+
} else if (kind === 'gate') {
|
|
4620
|
+
ok = await _hilPost(`/api/org/${enc(org)}/gates/${enc(id)}${q}`, { approved: verb === 'approve', resolution: text || undefined });
|
|
4621
|
+
}
|
|
4622
|
+
if (!ok) { row?.querySelectorAll('button').forEach(b => { b.disabled = false; }); return; }
|
|
4623
|
+
renderHumanInputView();
|
|
4541
4624
|
}
|
|
4542
4625
|
|
|
4543
4626
|
function _cvExcerptText(ev) {
|
|
@@ -5902,6 +5985,7 @@ function v2RenderOrgTab(tab) {
|
|
|
5902
5985
|
return;
|
|
5903
5986
|
}
|
|
5904
5987
|
if (tab === 'chart') v2RenderOrgChart();
|
|
5988
|
+
else if (tab === 'runtime') v2RenderOrgRuntime();
|
|
5905
5989
|
else if (tab === 'roles') v2RenderOrgRoles();
|
|
5906
5990
|
else if (tab === 'health') v2RenderOrgHealth();
|
|
5907
5991
|
else if (tab === 'heartbeats') v2RenderOrgHeartbeats();
|
|
@@ -7114,34 +7198,38 @@ function v2RenderOrgLive() {
|
|
|
7114
7198
|
}
|
|
7115
7199
|
|
|
7116
7200
|
// ── APPROVALS ──────────────────────────────────────────────
|
|
7201
|
+
// Tool/action approvals (<org>/approvals.json): a role asked to run a
|
|
7202
|
+
// sensitive tool (Bash, WebFetch, org_complete, policy.approvalTools…).
|
|
7117
7203
|
async function v2RenderOrgApprovals() {
|
|
7118
7204
|
const el = document.getElementById('odt-approvals');
|
|
7119
7205
|
if (!el || !_v2SelOrg) return;
|
|
7120
7206
|
const _rendOrg = _v2SelOrg;
|
|
7121
|
-
el.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
7207
|
+
if (!el.querySelector('table')) el.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
7122
7208
|
try {
|
|
7123
7209
|
const _enc = encodeURIComponent(_rendOrg);
|
|
7124
|
-
const
|
|
7210
|
+
const res = await fetch(`/api/org/${_enc}/approvals${DIR ? `?dir=${encodeURIComponent(DIR)}` : ''}`);
|
|
7211
|
+
const data = await res.json().catch(() => ({}));
|
|
7125
7212
|
if (_v2SelOrg !== _rendOrg) return;
|
|
7126
|
-
|
|
7127
|
-
|
|
7213
|
+
if (!res.ok) { el.innerHTML = `<div class="empty">Failed: ${esc(data.error || `HTTP ${res.status}`)}</div>`; return; }
|
|
7214
|
+
const approvals = data.approvals || [];
|
|
7215
|
+
if (!approvals.length) { el.innerHTML = '<div class="empty">No approval requests</div>'; return; }
|
|
7128
7216
|
el.innerHTML = `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
7129
7217
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
7130
|
-
<th style="padding:6px 8px">
|
|
7218
|
+
<th style="padding:6px 8px">Role</th><th>Action</th><th>Input</th><th>Status</th><th>When</th><th></th>
|
|
7131
7219
|
</tr></thead><tbody>` +
|
|
7132
|
-
approvals.slice(0,
|
|
7133
|
-
const cls = a.status === 'approved' ? 'on' : a.status === '
|
|
7134
|
-
const
|
|
7135
|
-
const aid = a.id || '';
|
|
7220
|
+
approvals.slice(0, 100).map(a => {
|
|
7221
|
+
const cls = a.status === 'approved' ? 'on' : a.status === 'denied' ? 'warn' : '';
|
|
7222
|
+
const input = a.input ? JSON.stringify(a.input) : '';
|
|
7136
7223
|
return `<tr style="border-top:1px solid var(--border)">
|
|
7137
|
-
<td style="padding:6px 8px;color:var(--text-hi)">${esc(a.
|
|
7138
|
-
<td style="padding:6px 8px;color:var(--text-
|
|
7139
|
-
<td style="padding:6px 8px"
|
|
7140
|
-
<td style="padding:6px 8px
|
|
7224
|
+
<td style="padding:6px 8px;color:var(--text-hi)">${esc(a.roleId || '—')}</td>
|
|
7225
|
+
<td style="padding:6px 8px;color:var(--text-hi);font-family:var(--mono)">${esc(a.action || '—')}</td>
|
|
7226
|
+
<td style="padding:6px 8px;color:var(--text-lo);max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);font-size:11px" title="${esc(input)}">${esc(input.slice(0, 120) || '—')}</td>
|
|
7227
|
+
<td style="padding:6px 8px"><span class="ss-pill ${cls}">${esc(a.status)}</span>${a.resolvedBy ? `<div style="color:var(--text-xs);font-size:10px">by ${esc(a.resolvedBy)}</div>` : ''}</td>
|
|
7228
|
+
<td style="padding:6px 8px;color:var(--text-xs);font-size:11px;font-family:var(--mono)">${relTime(a.resolvedAt || a.ts)}</td>
|
|
7141
7229
|
<td style="padding:6px 8px;white-space:nowrap">
|
|
7142
|
-
${pending
|
|
7143
|
-
? `<button class="btn" style="font-size:10px;color:var(--green);border-color:var(--green)" title="Approve" aria-label="Approve" data-aid="${esc(
|
|
7144
|
-
<button class="btn" style="font-size:10px;color:var(--red);border-color:var(--red);margin-left:3px" title="
|
|
7230
|
+
${a.status === 'pending' && a.id
|
|
7231
|
+
? `<button class="btn" style="font-size:10px;color:var(--green);border-color:var(--green)" title="Approve" aria-label="Approve" data-aid="${esc(a.id)}" onclick="orgApprovalAction(this.dataset.aid,'approve')">✓</button>
|
|
7232
|
+
<button class="btn" style="font-size:10px;color:var(--red);border-color:var(--red);margin-left:3px" title="Deny" aria-label="Deny" data-aid="${esc(a.id)}" onclick="orgApprovalAction(this.dataset.aid,'reject')">✕</button>`
|
|
7145
7233
|
: ''}
|
|
7146
7234
|
</td>
|
|
7147
7235
|
</tr>`;
|
|
@@ -7150,15 +7238,10 @@ async function v2RenderOrgApprovals() {
|
|
|
7150
7238
|
}
|
|
7151
7239
|
|
|
7152
7240
|
async function _orgApprovalAction(id, action) {
|
|
7153
|
-
if (!confirm(`${action} this request?`)) return;
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action })
|
|
7158
|
-
});
|
|
7159
|
-
showToast('Done', `${action}d`, 'ok');
|
|
7160
|
-
v2RenderOrgApprovals();
|
|
7161
|
-
} catch (e) { showToast('Error', e.message, 'err'); }
|
|
7241
|
+
if (!confirm(`${action === 'approve' ? 'Approve' : 'Deny'} this request?`)) return;
|
|
7242
|
+
const _enc = encodeURIComponent(_v2SelOrg);
|
|
7243
|
+
const ok = await _hilPost(`/api/org/${_enc}/approvals/${encodeURIComponent(id)}${DIR ? `?dir=${encodeURIComponent(DIR)}` : ''}`, { action });
|
|
7244
|
+
if (ok) v2RenderOrgApprovals();
|
|
7162
7245
|
}
|
|
7163
7246
|
|
|
7164
7247
|
// ── DECISIONS ──────────────────────────────────────────────
|
|
@@ -7203,20 +7286,35 @@ function v2RenderDecisionsTable() {
|
|
|
7203
7286
|
if (!filtered.length) { el.innerHTML = `${filterBar}<div class="empty">No decision gates match</div>`; return; }
|
|
7204
7287
|
el.innerHTML = filterBar + `<table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
7205
7288
|
<thead><tr style="color:var(--text-xs);text-align:left">
|
|
7206
|
-
<th style="padding:6px 8px">Role</th><th>Gate</th><th>Status</th><th>Resolution</th><th>Date</th>
|
|
7289
|
+
<th style="padding:6px 8px">Role</th><th>Gate</th><th>Status</th><th>Resolution</th><th>Date</th><th></th>
|
|
7207
7290
|
</tr></thead><tbody>` +
|
|
7208
7291
|
filtered.slice(0, 100).map(g => {
|
|
7209
7292
|
const cls = g.status === 'approved' ? 'on' : g.status === 'rejected' ? 'warn' : '';
|
|
7210
7293
|
return `<tr style="border-top:1px solid var(--border)">
|
|
7211
7294
|
<td style="padding:6px 8px;color:var(--text-hi)">${esc(g.roleId || '—')}</td>
|
|
7212
7295
|
<td style="padding:6px 8px;color:var(--text-lo);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(g.description || '')}">${esc(g.name || '—')}</td>
|
|
7213
|
-
<td style="padding:6px 8px"><span class="ss-pill ${cls}">${esc(g.status || 'pending')}</span
|
|
7214
|
-
<td style="padding:6px 8px;color:var(--text-lo);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(g.resolution || '—')}</td>
|
|
7296
|
+
<td style="padding:6px 8px"><span class="ss-pill ${cls}">${esc(g.status || 'pending')}</span>${g.resolvedBy ? `<div style="color:var(--text-xs);font-size:10px">by ${esc(g.resolvedBy)}</div>` : ''}</td>
|
|
7297
|
+
<td style="padding:6px 8px;color:var(--text-lo);max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${esc(g.resolution || '')}">${esc(g.resolution || '—')}</td>
|
|
7215
7298
|
<td style="padding:6px 8px;color:var(--text-xs);font-size:11px;font-family:var(--mono)">${relTime(g.resolvedAt || g.createdAt)}</td>
|
|
7299
|
+
<td style="padding:6px 8px;white-space:nowrap">
|
|
7300
|
+
${(g.status || 'pending') === 'pending'
|
|
7301
|
+
? `<button class="btn" style="font-size:10px;color:var(--green);border-color:var(--green)" title="Approve" aria-label="Approve gate" data-gid="${esc(g.id)}" onclick="orgGateAction(this.dataset.gid,true)">✓</button>
|
|
7302
|
+
<button class="btn" style="font-size:10px;color:var(--red);border-color:var(--red);margin-left:3px" title="Reject" aria-label="Reject gate" data-gid="${esc(g.id)}" onclick="orgGateAction(this.dataset.gid,false)">✕</button>`
|
|
7303
|
+
: ''}
|
|
7304
|
+
</td>
|
|
7216
7305
|
</tr>`;
|
|
7217
7306
|
}).join('') + '</tbody></table>';
|
|
7218
7307
|
}
|
|
7219
7308
|
|
|
7309
|
+
async function _orgGateAction(id, approved) {
|
|
7310
|
+
const gate = _v2DecisionsCache.find(g => g.id === id);
|
|
7311
|
+
const resolution = prompt(`${approved ? 'Approve' : 'Reject'} "${gate?.name || id}"?\nOptional note for the role:`, '');
|
|
7312
|
+
if (resolution === null) return;
|
|
7313
|
+
const _enc = encodeURIComponent(_v2SelOrg);
|
|
7314
|
+
const ok = await _hilPost(`/api/org/${_enc}/gates/${encodeURIComponent(id)}${DIR ? `?dir=${encodeURIComponent(DIR)}` : ''}`, { approved, resolution: resolution.trim() || undefined });
|
|
7315
|
+
if (ok) v2RenderOrgDecisions();
|
|
7316
|
+
}
|
|
7317
|
+
|
|
7220
7318
|
// ── SECRETS ────────────────────────────────────────────────
|
|
7221
7319
|
async function v2RenderOrgSecrets() {
|
|
7222
7320
|
const el = document.getElementById('odt-secrets');
|
|
@@ -7934,7 +8032,10 @@ window.odtSendChatMessage = async () => {
|
|
|
7934
8032
|
const _sendQ = DIR ? `?dir=${encodeURIComponent(DIR)}` : '';
|
|
7935
8033
|
const res = await fetch(`/api/orgs/${encodeURIComponent(_v2SelOrg)}/chat${_sendQ}`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ text, role: role || undefined, runId: _odtChatCurrentId || null }) });
|
|
7936
8034
|
const data = await res.json().catch(() => ({}));
|
|
7937
|
-
if (!data.
|
|
8035
|
+
if (!res.ok || !data.ok) {
|
|
8036
|
+
inp.value = text;
|
|
8037
|
+
showToast('Not delivered', data.error || `HTTP ${res.status}`, 'err');
|
|
8038
|
+
} else if (data.delivery !== 'live') {
|
|
7938
8039
|
const lbl = document.getElementById('odt-chat-live-lbl');
|
|
7939
8040
|
if (lbl) { const prev = lbl.textContent; lbl.textContent = 'QUEUED (org offline)'; setTimeout(() => { lbl.textContent = prev; }, 4000); }
|
|
7940
8041
|
}
|
|
@@ -8484,8 +8585,16 @@ function _odtHandleLiveEvent(ev) {
|
|
|
8484
8585
|
}
|
|
8485
8586
|
if (ev?.type === 'org:question' && ev.from) {
|
|
8486
8587
|
if (ev.org === _v2SelOrg) v2ShowQuestionBubble(ev.from, ev.question || '');
|
|
8487
|
-
|
|
8488
|
-
|
|
8588
|
+
}
|
|
8589
|
+
_v2RuntimeNudge(ev);
|
|
8590
|
+
// Anything a human must act on, or just did: refresh the Human Input list,
|
|
8591
|
+
// its badge, and the selected org's approvals/decisions panes.
|
|
8592
|
+
if (['org:question', 'org:gate', 'org:question-answered', 'org:approval:resolved', 'org:gate:resolved'].includes(ev?.type)) {
|
|
8593
|
+
_hiScheduleReload();
|
|
8594
|
+
if (ev.org === _v2SelOrg) {
|
|
8595
|
+
if (document.getElementById('odt-approvals')?.offsetParent) v2RenderOrgApprovals();
|
|
8596
|
+
if (document.getElementById('odt-decisions')?.offsetParent) v2RenderOrgDecisions();
|
|
8597
|
+
}
|
|
8489
8598
|
}
|
|
8490
8599
|
if (ev?.type === 'org:question-answered' && ev.role) {
|
|
8491
8600
|
if (ev.org === _v2SelOrg) v2ClearQuestionBubble(ev.role);
|
|
@@ -8596,90 +8705,273 @@ function _odtHandleLiveEvent(ev) {
|
|
|
8596
8705
|
}
|
|
8597
8706
|
}
|
|
8598
8707
|
|
|
8708
|
+
// ── RUNTIME ────────────────────────────────────────────────
|
|
8709
|
+
// What the org is doing, from the runtime's own sources (GET /api/org/:n/runtime):
|
|
8710
|
+
// live status, idle watchdog + hold deadline, budget on its enforcement basis,
|
|
8711
|
+
// per-role model/effort/tier and usage (cache tokens included), the task DAG
|
|
8712
|
+
// with evidence-gate failures, gate/watchdog audit events, and how runs ended.
|
|
8713
|
+
let _v2RuntimeTimer = null;
|
|
8714
|
+
let _v2RuntimeReload = null;
|
|
8715
|
+
|
|
8716
|
+
function _rtTok(n) {
|
|
8717
|
+
if (n === null || n === undefined) return '?';
|
|
8718
|
+
n = Number(n) || 0;
|
|
8719
|
+
if (n >= 1e9) return `${(n / 1e9).toFixed(2)}B`;
|
|
8720
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
8721
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
8722
|
+
return String(n);
|
|
8723
|
+
}
|
|
8724
|
+
function _rtClock(iso) {
|
|
8725
|
+
const t = typeof iso === 'number' ? iso : Date.parse(iso || '');
|
|
8726
|
+
return t ? new Date(t).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '—';
|
|
8727
|
+
}
|
|
8728
|
+
function _rtIn(iso) {
|
|
8729
|
+
const ms = (typeof iso === 'number' ? iso : Date.parse(iso || '')) - Date.now();
|
|
8730
|
+
if (!Number.isFinite(ms)) return '';
|
|
8731
|
+
return ms <= 0 ? 'now' : `in ${fmtDur(ms)}`;
|
|
8732
|
+
}
|
|
8733
|
+
const _RT_CLOSED_BY = {
|
|
8734
|
+
'org-complete': 'boss called org_complete',
|
|
8735
|
+
'idle-stop': 'idle watchdog stopped it',
|
|
8736
|
+
'failed-start': 'failed to start',
|
|
8737
|
+
'scheduled-deadline': 'scheduled deadline',
|
|
8738
|
+
'boss-restart': 'boss restarted',
|
|
8739
|
+
'boss-restart-exhausted': 'boss restarts exhausted',
|
|
8740
|
+
'mark-complete': 'marked complete by hand',
|
|
8741
|
+
};
|
|
8742
|
+
const _RT_AUDIT_COLOR = {
|
|
8743
|
+
'task-evidence-refused': 'var(--amber, #d29922)',
|
|
8744
|
+
'task-evidence-escalated': 'var(--red)',
|
|
8745
|
+
'org-complete-refused': 'var(--amber, #d29922)',
|
|
8746
|
+
'hold-expired': 'var(--amber, #d29922)',
|
|
8747
|
+
'no-progress': 'var(--amber, #d29922)',
|
|
8748
|
+
'idle-nudge': 'var(--text-lo)',
|
|
8749
|
+
'idle-stop': 'var(--red)',
|
|
8750
|
+
};
|
|
8751
|
+
|
|
8752
|
+
function _rtCard(label, value, sub) {
|
|
8753
|
+
return `<div style="border:1px solid var(--border);border-radius:6px;padding:10px 12px;min-width:0">
|
|
8754
|
+
<div style="font-size:9px;letter-spacing:1.5px;color:var(--text-xs)">${label}</div>
|
|
8755
|
+
<div style="font-size:14px;color:var(--text-hi);margin-top:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${value}</div>
|
|
8756
|
+
${sub ? `<div style="font-size:11px;color:var(--text-lo);margin-top:2px">${sub}</div>` : ''}
|
|
8757
|
+
</div>`;
|
|
8758
|
+
}
|
|
8759
|
+
function _rtTable(title, head, rows, empty) {
|
|
8760
|
+
const h = `<div style="font-size:9px;letter-spacing:2px;color:var(--text-xs);margin:18px 0 6px">${title}</div>`;
|
|
8761
|
+
if (!rows.length) return `${h}<div class="empty" style="padding:8px 0">${empty}</div>`;
|
|
8762
|
+
return `${h}<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse;font-size:12px">
|
|
8763
|
+
<thead><tr style="color:var(--text-xs);text-align:left">${head.map(c => `<th style="padding:4px 8px;font-weight:500;white-space:nowrap">${c}</th>`).join('')}</tr></thead>
|
|
8764
|
+
<tbody>${rows.map(r => `<tr style="border-top:1px solid var(--border)">${r.map(c => `<td style="padding:5px 8px;vertical-align:top">${c}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`;
|
|
8765
|
+
}
|
|
8766
|
+
|
|
8767
|
+
async function v2RenderOrgRuntime() {
|
|
8768
|
+
const el = document.getElementById('odt-runtime');
|
|
8769
|
+
if (!el || !_v2SelOrg) return;
|
|
8770
|
+
clearTimeout(_v2RuntimeTimer);
|
|
8771
|
+
const _rendOrg = _v2SelOrg;
|
|
8772
|
+
if (!el.dataset.org || el.dataset.org !== _rendOrg) el.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
8773
|
+
let v;
|
|
8774
|
+
try {
|
|
8775
|
+
const res = await fetch(`/api/org/${enc(_rendOrg)}/runtime${DIR ? `?dir=${enc(DIR)}` : ''}`);
|
|
8776
|
+
v = await res.json().catch(() => ({}));
|
|
8777
|
+
if (!res.ok) throw new Error(v.error || `HTTP ${res.status}`);
|
|
8778
|
+
} catch (e) {
|
|
8779
|
+
if (_v2SelOrg === _rendOrg) el.innerHTML = `<div class="empty">Failed: ${esc(e.message)}</div>`;
|
|
8780
|
+
return;
|
|
8781
|
+
}
|
|
8782
|
+
if (_v2SelOrg !== _rendOrg || _v2OrgTab !== 'runtime') return;
|
|
8783
|
+
el.dataset.org = _rendOrg;
|
|
8784
|
+
const s = v.settings || {};
|
|
8785
|
+
const mono = t => `<span style="font-family:var(--mono)">${t}</span>`;
|
|
8786
|
+
|
|
8787
|
+
// Status strip
|
|
8788
|
+
const statusSub = v.live
|
|
8789
|
+
? `${esc(v.run || '')}${v.startedAt ? ` · up ${fmtDur(Date.now() - v.startedAt)}` : ''}`
|
|
8790
|
+
: `${esc(v.run || 'no run yet')}${v.closedBy ? ` · ${esc(_RT_CLOSED_BY[v.closedBy] || v.closedBy)}` : ''}`;
|
|
8791
|
+
let idle = '—', idleSub = s.idle_minutes === 0 ? 'watchdog disabled' : `window ${s.idle_minutes}m`;
|
|
8792
|
+
if (v.idle) {
|
|
8793
|
+
if (v.idle.idle_hold && v.idle.idle_hold !== 'unknown') {
|
|
8794
|
+
idle = `held: ${esc(v.idle.idle_hold)}`;
|
|
8795
|
+
idleSub = v.idle.idle_hold_until ? `hold ends ${_rtClock(v.idle.idle_hold_until)} (${_rtIn(v.idle.idle_hold_until)})` : 'hold has no deadline';
|
|
8796
|
+
} else if (v.idle.idle_stop_at) {
|
|
8797
|
+
idle = `stops ${_rtIn(v.idle.idle_stop_at)}`;
|
|
8798
|
+
idleSub = `if idle until ${_rtClock(v.idle.idle_stop_at)}`;
|
|
8799
|
+
} else idle = 'not reported yet';
|
|
8800
|
+
}
|
|
8801
|
+
const b = v.budget || {};
|
|
8802
|
+
const pct = b.tokens && b.used !== null ? Math.min(100, (b.used / b.tokens) * 100) : null;
|
|
8803
|
+
const budget = b.used === null ? 'unknown' : `${_rtTok(b.used)} / ${_rtTok(b.tokens)}`;
|
|
8804
|
+
const budgetWhy = b.used === null ? ' · run predates cache metering' : '';
|
|
8805
|
+
const budgetSub = `${esc(b.basis)} basis${pct !== null ? ` · ${pct.toFixed(1)}%` : ''}${budgetWhy} · ${fmtCost(v.totals?.cost_usd)}`;
|
|
8806
|
+
const gate = s.completion_evidence ? 'evidence required' : 'off';
|
|
8807
|
+
const gateSub = s.completion_evidence
|
|
8808
|
+
? `escalate after ${s.max_evidence_attempts} refusals · org_complete: ${esc(s.completion)}`
|
|
8809
|
+
: `org_complete gate: ${esc(s.completion)}`;
|
|
8810
|
+
|
|
8811
|
+
let html = '<div style="padding:16px">';
|
|
8812
|
+
if (!v.valid) html += `<div class="empty" style="color:var(--red);text-align:left;margin-bottom:10px">Definition does not parse — <code>org run</code> will refuse it:<br>${v.problems.map(esc).join('<br>')}</div>`;
|
|
8813
|
+
if (v.liveError) html += `<div class="empty" style="color:var(--amber, #d29922);text-align:left;margin-bottom:10px">A daemon is registered for this org but did not answer: ${esc(v.liveError)}</div>`;
|
|
8814
|
+
html += `<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px">
|
|
8815
|
+
${_rtCard('STATUS', `<span class="ss-pill ${v.live ? 'on' : ''}">${esc(v.status)}</span>`, statusSub)}
|
|
8816
|
+
${_rtCard('IDLE WATCHDOG', idle, idleSub)}
|
|
8817
|
+
${_rtCard('BUDGET (THIS RUN)', budget, budgetSub)}
|
|
8818
|
+
${_rtCard('COMPLETION GATE', gate, gateSub)}
|
|
8819
|
+
${_rtCard('SESSIONS', `per ${esc(s.session_scope)}`, `workspace: ${esc(s.workspace)} · max ${s.max_concurrent_agents} agents`)}
|
|
8820
|
+
</div>`;
|
|
8821
|
+
|
|
8822
|
+
// Roles
|
|
8823
|
+
html += _rtTable(`ROLES${v.run ? ` — USAGE IN ${esc(v.run)}` : ''}`,
|
|
8824
|
+
['Role', 'Runtime', 'Model', 'Effort', 'Tier', 'Status', 'In', 'Out', 'Cache read', 'Cache write', 'Cost', 'Last active'],
|
|
8825
|
+
(v.roles || []).map(r => [
|
|
8826
|
+
`<span style="color:var(--text-hi)">${esc(r.title)}</span>${r.title !== r.id ? `<div style="font-size:10px;color:var(--text-xs)">${esc(r.id)}</div>` : ''}`,
|
|
8827
|
+
esc(r.runtime),
|
|
8828
|
+
r.tierError
|
|
8829
|
+
? `<span style="color:var(--red)" title="${esc(r.tierError)}">tier error</span>`
|
|
8830
|
+
: `<span style="white-space:nowrap">${mono(esc(r.model || 'default'))}</span><div style="font-size:10px;color:var(--text-xs)">${esc(r.modelSource)}</div>`,
|
|
8831
|
+
esc(r.effort || '—'),
|
|
8832
|
+
esc(r.tier || '—'),
|
|
8833
|
+
`<span class="ss-pill ${r.status === 'running' || r.status === 'working' ? 'on' : ''}">${esc(r.status)}</span>`,
|
|
8834
|
+
// A run recorded before the four-way meter only has an input+output total.
|
|
8835
|
+
...(r.usage.legacy_tokens && !r.usage.tokens
|
|
8836
|
+
? [`<span title="Recorded before the meter split input, output and cache tokens">${mono(_rtTok(r.usage.legacy_tokens))} in+out</span>`, '—', '—', '—']
|
|
8837
|
+
: [mono(_rtTok(r.usage.tokens_in)), mono(_rtTok(r.usage.tokens_out)),
|
|
8838
|
+
mono(_rtTok(r.usage.cache_read)), mono(_rtTok(r.usage.cache_creation))]),
|
|
8839
|
+
mono(fmtCost(r.usage.cost_usd)),
|
|
8840
|
+
`<span style="color:var(--text-xs)">${r.lastActivity ? relTime(r.lastActivity) : '—'}</span>`,
|
|
8841
|
+
]), 'No roles');
|
|
8842
|
+
|
|
8843
|
+
// Tasks
|
|
8844
|
+
const open = (v.tasks || []).filter(t => !['done', 'cancelled', 'merged', 'split'].includes(t.status));
|
|
8845
|
+
html += _rtTable(`TASKS (${open.length} open of ${(v.tasks || []).length})`,
|
|
8846
|
+
['Task', 'Assignee', 'Status', 'Evidence refusals', 'Loadout', 'Blocked'],
|
|
8847
|
+
[...open, ...(v.tasks || []).filter(t => !open.includes(t))].slice(0, 60).map(t => [
|
|
8848
|
+
`<span style="color:var(--text-hi)">${esc(t.title || t.id)}</span><div style="font-size:10px;color:var(--text-xs)">${esc(t.id)}</div>`,
|
|
8849
|
+
esc(t.assignee || '—'),
|
|
8850
|
+
`<span class="ss-pill ${t.status === 'done' ? 'on' : t.status === 'failed' || t.status === 'blocked' ? 'warn' : ''}">${esc(t.status)}</span>`,
|
|
8851
|
+
t.evidenceFailures ? `<span style="color:${t.evidenceFailures >= s.max_evidence_attempts ? 'var(--red)' : 'var(--amber, #d29922)'}">${t.evidenceFailures} / ${s.max_evidence_attempts}</span>` : '—',
|
|
8852
|
+
esc(t.loadout || '—'),
|
|
8853
|
+
t.blockedUntil ? `until ${_rtClock(t.blockedUntil)}${t.blockedReason ? ` — ${esc(t.blockedReason)}` : ''}` : '—',
|
|
8854
|
+
]), 'No task graph for this run (the boss has not used org_task)');
|
|
8855
|
+
|
|
8856
|
+
// Audit trail
|
|
8857
|
+
html += _rtTable('EVIDENCE GATE & WATCHDOG EVENTS', ['When', 'Event', 'Role', 'Detail'],
|
|
8858
|
+
(v.audit || []).map(a => [
|
|
8859
|
+
`<span style="color:var(--text-xs);white-space:nowrap">${_rtClock(a.ts)}</span>`,
|
|
8860
|
+
`<span class="ss-pill" style="color:${_RT_AUDIT_COLOR[a.reason] || 'var(--text-lo)'};border-color:currentColor;white-space:nowrap">${esc(a.reason)}</span>`,
|
|
8861
|
+
esc(a.from || '—'),
|
|
8862
|
+
`<div style="white-space:pre-wrap;max-height:120px;overflow:auto">${esc(a.msg)}${a.data?.refusal ? `\n<span style="color:var(--text-lo)">${esc(a.data.refusal)}</span>` : ''}</div>`,
|
|
8863
|
+
]), 'None in this run');
|
|
8864
|
+
|
|
8865
|
+
// History
|
|
8866
|
+
html += _rtTable('RECENT RUNS', ['Run', 'Ended', 'Duration', 'How it ended', 'Outcome', 'Open tasks at stop', 'Tokens', 'Cost'],
|
|
8867
|
+
(v.history || []).map(h => [
|
|
8868
|
+
mono(esc(h.run)),
|
|
8869
|
+
`<span style="color:var(--text-xs)">${relTime(h.endedAt)}</span>`,
|
|
8870
|
+
h.durationMs ? fmtDur(h.durationMs) : '—',
|
|
8871
|
+
esc(h.closedBy ? (_RT_CLOSED_BY[h.closedBy] || h.closedBy) : 'stopped (manual or shutdown)') + (h.crashes?.length ? `<div style="color:var(--red);font-size:10px">crashed: ${h.crashes.map(esc).join(', ')}</div>` : ''),
|
|
8872
|
+
h.outcome
|
|
8873
|
+
? `<span class="ss-pill ${h.outcome.status === 'achieved' ? 'on' : 'warn'}">${esc(h.outcome.status)}</span>${h.blocker ? ` blocker: ${esc(h.blocker)}` : ''}<div style="font-size:11px;color:var(--text-lo);max-width:320px;white-space:pre-wrap;max-height:80px;overflow:auto">${esc(h.blockerDetail || h.outcome.summary || '')}</div>`
|
|
8874
|
+
: '<span style="color:var(--text-xs)">none claimed</span>',
|
|
8875
|
+
h.runnableTasksAtStop ? `<span style="color:var(--amber, #d29922)">${h.runnableTasksAtStop}</span>` : '0',
|
|
8876
|
+
mono(_rtTok(h.totalTokens)),
|
|
8877
|
+
mono(fmtCost(h.totalCostUsd)),
|
|
8878
|
+
]), 'No finished runs yet');
|
|
8879
|
+
html += '</div>';
|
|
8880
|
+
el.innerHTML = html;
|
|
8881
|
+
// A live run changes by the second; poll lightly on top of the SSE nudges.
|
|
8882
|
+
if (v.live) _v2RuntimeTimer = setTimeout(() => { if (_v2OrgTab === 'runtime') v2RenderOrgRuntime(); }, 15000);
|
|
8883
|
+
}
|
|
8884
|
+
|
|
8885
|
+
/** SSE nudge: refresh the Runtime pane shortly after an event for its org. */
|
|
8886
|
+
function _v2RuntimeNudge(ev) {
|
|
8887
|
+
if (_v2OrgTab !== 'runtime' || !ev?.org || ev.org !== _v2SelOrg) return;
|
|
8888
|
+
clearTimeout(_v2RuntimeReload);
|
|
8889
|
+
_v2RuntimeReload = setTimeout(v2RenderOrgRuntime, 2000);
|
|
8890
|
+
}
|
|
8891
|
+
|
|
8599
8892
|
// ── ORG CONFIG ─────────────────────────────────────────────
|
|
8893
|
+
// Edits the Org Runtime v2 definition (goal, schedule, run_config, default
|
|
8894
|
+
// cost tier). Saved through POST /api/org/:n/config, which merges into the file
|
|
8895
|
+
// on disk and refuses anything `org run` would refuse. Roles, policies and
|
|
8896
|
+
// providers stay in the JSON (Files tab / `monomind org edit`).
|
|
8600
8897
|
function v2RenderOrgConfig() {
|
|
8601
8898
|
const el = document.getElementById('odt-config');
|
|
8602
8899
|
if (!el || !_v2OrgData || !_v2SelOrg) return;
|
|
8603
|
-
const d = _v2OrgData;
|
|
8900
|
+
const d = _v2OrgData.config || _v2OrgData;
|
|
8604
8901
|
const rc = d.run_config || {};
|
|
8605
|
-
const
|
|
8606
|
-
const
|
|
8607
|
-
const
|
|
8608
|
-
const
|
|
8609
|
-
const
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
+sec('
|
|
8616
|
-
fld('
|
|
8617
|
-
+fld('
|
|
8618
|
-
+
|
|
8619
|
-
+fld('
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
+fld('
|
|
8627
|
-
+fld('
|
|
8628
|
-
+fld('
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
)
|
|
8632
|
-
+
|
|
8633
|
-
fld('Approval Policy', sel('oc-gov-policy', govPolicies, gov.policy||'auto'))
|
|
8634
|
-
+fld('Approvals File', inp('oc-gov-file', gov.approvals_file||(`.monomind/orgs/${d.name}-approvals.json`)))
|
|
8635
|
-
)
|
|
8636
|
-
+'<div style="display:flex;align-items:center;gap:10px"><button class="btn" style="border-color:var(--accent);color:var(--accent)" onclick="v2SaveOrgConfig()">Save Config</button><span id="oc-status-msg" style="font-size:11px;color:var(--text-lo)"></span></div>'
|
|
8637
|
-
+'</div>';
|
|
8902
|
+
const inp = (id, val, type, extra) => `<input class="filter-input" id="${id}" type="${type || 'text'}" value="${esc(String(val ?? ''))}" ${extra || ''} style="width:100%;box-sizing:border-box">`;
|
|
8903
|
+
const sel = (id, opts, cur) => `<select class="filter-input" id="${id}" style="width:100%;box-sizing:border-box;cursor:pointer">${opts.map(([v, l]) => `<option value="${esc(v)}"${String(cur ?? '') === v ? ' selected' : ''}>${esc(l)}</option>`).join('')}</select>`;
|
|
8904
|
+
const fld = (lbl, html, hint) => `<div style="display:flex;flex-direction:column;gap:4px"><div style="font-size:10px;color:var(--text-lo)">${lbl}</div>${html}${hint ? `<div style="font-size:10px;color:var(--text-xs)">${hint}</div>` : ''}</div>`;
|
|
8905
|
+
const sec = (title, inner) => `<div style="margin-bottom:22px"><div style="font-size:9px;letter-spacing:2px;color:var(--text-xs);margin-bottom:10px;padding-bottom:5px;border-bottom:1px solid var(--border)">${title}</div><div style="display:grid;grid-template-columns:1fr 1fr;gap:11px">${inner}</div></div>`;
|
|
8906
|
+
const tiers = ['standard', 'economy', 'budget', ...Object.keys(d.cost_tiers?.tiers || {})];
|
|
8907
|
+
el.innerHTML = '<div style="padding:18px;overflow-y:auto;max-width:720px">'
|
|
8908
|
+
+ sec('GENERAL',
|
|
8909
|
+
fld('Name', inp('oc-name', d.name || '', 'text', 'readonly style="opacity:0.5"'))
|
|
8910
|
+
+ fld('Schedule', inp('oc-schedule', d.schedule ?? '', 'text', 'placeholder="cron, or empty for none"'))
|
|
8911
|
+
+ `<div style="grid-column:1/-1">${fld('Goal', `<textarea id="oc-goal" class="filter-input" rows="4" style="width:100%;box-sizing:border-box;resize:vertical;line-height:1.5">${esc(d.goal || '')}</textarea>`)}</div>`)
|
|
8912
|
+
+ sec('BUDGET & CONCURRENCY',
|
|
8913
|
+
fld('Budget tokens (per run)', inp('oc-budget', rc.budget_tokens ?? '', 'number', 'min="1" placeholder="1000000"'), 'Must be positive.')
|
|
8914
|
+
+ fld('Budget basis', sel('oc-basis', [['', 'uncached (default)'], ['uncached', 'uncached — input + output'], ['billable', 'billable — includes cache tokens']], rc.budget_tokens_basis ?? ''), 'billable is ~100x larger on a cached run; resize the budget with it.')
|
|
8915
|
+
+ fld('Max concurrent agents', inp('oc-max-agents', rc.max_concurrent_agents ?? '', 'number', 'min="1" placeholder="4"'))
|
|
8916
|
+
+ fld('Max turns per message', inp('oc-max-turns', rc.max_turns_per_message ?? '', 'number', 'min="1"')))
|
|
8917
|
+
+ sec('COMPLETION',
|
|
8918
|
+
fld('org_complete gate', sel('oc-completion', [['', 'boss (default)'], ['boss', 'boss — partial needs a real blocker'], ['dag', 'dag — also refuse while tasks are runnable']], rc.completion ?? ''))
|
|
8919
|
+
+ fld('Evidence gate on org_task_done', sel('oc-evidence', [['false', 'off'], ['true', 'on — commands + exit codes pinned to HEAD']], String(!!rc.completion_evidence)))
|
|
8920
|
+
+ fld('Escalate after N evidence refusals', inp('oc-evidence-max', rc.max_evidence_attempts ?? '', 'number', 'min="1" placeholder="3"'), 'Then the task is failed and handed to the boss.'))
|
|
8921
|
+
+ sec('SESSIONS & WATCHDOG',
|
|
8922
|
+
fld('Idle minutes (0 disables)', inp('oc-idle', rc.idle_minutes ?? '', 'number', 'min="0" step="any" placeholder="10"'), 'Every hold on the idle clock expires after an hour.')
|
|
8923
|
+
+ fld('Session scope', sel('oc-scope', [['', 'role (default)'], ['role', 'role — one session per role'], ['task', 'task — one session per task']], rc.session_scope ?? ''))
|
|
8924
|
+
+ fld('Workspace', inp('oc-workspace', rc.workspace ?? '', 'text', 'placeholder="repo | isolated | worktree | worktree-per-role | /abs/path"'))
|
|
8925
|
+
+ fld('Memory namespace', inp('oc-namespace', rc.memory_namespace ?? '', 'text', `placeholder="org:${esc(d.name || '')}"`)))
|
|
8926
|
+
+ sec('COST TIERS',
|
|
8927
|
+
fld('Default tier', sel('oc-tier', [['', 'none — models as configured'], ...[...new Set(tiers)].map(t => [t, t])], d.cost_tiers?.default ?? ''), 'Per-role tiers and exemptions: edit cost_tiers.roles in the JSON.'))
|
|
8928
|
+
+ '<div style="display:flex;align-items:flex-start;gap:10px"><button class="btn" style="border-color:var(--accent);color:var(--accent)" onclick="v2SaveOrgConfig()">Save Config</button><span id="oc-status-msg" style="font-size:11px;color:var(--text-lo);white-space:pre-wrap"></span></div>'
|
|
8929
|
+
+ '</div>';
|
|
8638
8930
|
}
|
|
8639
8931
|
|
|
8640
8932
|
async function _v2SaveOrgConfig() {
|
|
8641
8933
|
if (!_v2SelOrg || !_v2OrgData) return;
|
|
8642
8934
|
const g = id => document.getElementById(id);
|
|
8643
|
-
//
|
|
8644
|
-
|
|
8645
|
-
const
|
|
8646
|
-
const
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
max_concurrent_agents:
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
policy: (g('oc-gov-policy')?.value)||'auto',
|
|
8667
|
-
approvals_file: (g('oc-gov-file')?.value||'').trim()||(`.monomind/orgs/${_v2SelOrg}-approvals.json`),
|
|
8935
|
+
// '' = not set → null removes the key so the runtime default applies.
|
|
8936
|
+
const num = id => { const v = (g(id)?.value || '').trim(); return v === '' ? null : Number(v); };
|
|
8937
|
+
const str = id => { const v = (g(id)?.value || '').trim(); return v === '' ? null : v; };
|
|
8938
|
+
const patch = {
|
|
8939
|
+
goal: g('oc-goal')?.value ?? '',
|
|
8940
|
+
// A numeric schedule (an interval) stays a number; a cron stays a string.
|
|
8941
|
+
schedule: (() => {
|
|
8942
|
+
const v = str('oc-schedule');
|
|
8943
|
+
const was = (_v2OrgData.config || _v2OrgData).schedule;
|
|
8944
|
+
return v !== null && typeof was === 'number' && /^\d+$/.test(v) ? Number(v) : v;
|
|
8945
|
+
})(),
|
|
8946
|
+
run_config: {
|
|
8947
|
+
budget_tokens: num('oc-budget'),
|
|
8948
|
+
budget_tokens_basis: str('oc-basis'),
|
|
8949
|
+
max_concurrent_agents: num('oc-max-agents'),
|
|
8950
|
+
max_turns_per_message: num('oc-max-turns'),
|
|
8951
|
+
completion: str('oc-completion'),
|
|
8952
|
+
completion_evidence: g('oc-evidence')?.value === 'true' ? true : null,
|
|
8953
|
+
max_evidence_attempts: num('oc-evidence-max'),
|
|
8954
|
+
idle_minutes: num('oc-idle'),
|
|
8955
|
+
session_scope: str('oc-scope'),
|
|
8956
|
+
workspace: str('oc-workspace'),
|
|
8957
|
+
memory_namespace: str('oc-namespace'),
|
|
8668
8958
|
},
|
|
8959
|
+
cost_tiers_default: str('oc-tier') || '',
|
|
8669
8960
|
};
|
|
8670
|
-
const msg =
|
|
8671
|
-
if (msg) { msg.style.color='var(--text-lo)'; msg.textContent='Saving
|
|
8961
|
+
const msg = g('oc-status-msg');
|
|
8962
|
+
if (msg) { msg.style.color = 'var(--text-lo)'; msg.textContent = 'Saving…'; }
|
|
8672
8963
|
try {
|
|
8673
|
-
const
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8964
|
+
const r = await fetch(`/api/org/${enc(_v2SelOrg)}/config${DIR ? `?dir=${enc(DIR)}` : ''}`, {
|
|
8965
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(patch),
|
|
8966
|
+
});
|
|
8967
|
+
const json = await r.json().catch(() => ({}));
|
|
8968
|
+
if (!r.ok || !json.ok) throw new Error((json.error || `HTTP ${r.status}`).split('; ').join('\n'));
|
|
8969
|
+
if (_v2OrgData.config) _v2OrgData.config = json.def;
|
|
8970
|
+
Object.assign(_v2OrgData, { goal: json.def.goal, schedule: json.def.schedule, run_config: json.def.run_config, cost_tiers: json.def.cost_tiers });
|
|
8971
|
+
if (msg) { msg.style.color = 'oklch(68% 0.20 150)'; msg.textContent = 'Saved — applies from the next run'; setTimeout(() => { if (msg) msg.textContent = ''; }, 4000); }
|
|
8972
|
+
showToast('Config saved', 'Validated against the org schema', 'ok');
|
|
8973
|
+
} catch (e) {
|
|
8974
|
+
if (msg) { msg.style.color = 'oklch(60% 0.22 25)'; msg.textContent = `Not saved:\n${e.message}`; }
|
|
8683
8975
|
}
|
|
8684
8976
|
}
|
|
8685
8977
|
|
|
@@ -14211,7 +14503,7 @@ Object.assign(window, {
|
|
|
14211
14503
|
gdOpen: _gdOpen,
|
|
14212
14504
|
gdRevealPath: _gdRevealPath,
|
|
14213
14505
|
gdCopyPath: _gdCopyPath,
|
|
14214
|
-
|
|
14506
|
+
hiAct: _hiAct,
|
|
14215
14507
|
v2ViewOrgFile: _v2ViewOrgFile,
|
|
14216
14508
|
minimapJump: _minimapJump,
|
|
14217
14509
|
stopLoop: _stopLoop,
|
|
@@ -14220,6 +14512,7 @@ Object.assign(window, {
|
|
|
14220
14512
|
v2TriggerAvatarUpload: _v2TriggerAvatarUpload,
|
|
14221
14513
|
v2HandleAvatarFile: _v2HandleAvatarFile,
|
|
14222
14514
|
orgApprovalAction: _orgApprovalAction,
|
|
14515
|
+
orgGateAction: _orgGateAction,
|
|
14223
14516
|
generateOrgSettingsCmd: _generateOrgSettingsCmd,
|
|
14224
14517
|
setOdtChatMode: _setOdtChatMode,
|
|
14225
14518
|
v2SaveOrgConfig: _v2SaveOrgConfig,
|