taskflow-mcp-core 0.3.0-beta.1.1 → 1.0.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/CONTROL_GUIDE.md +113 -0
- package/README.md +34 -25
- package/dist/control/admission-service.d.ts +15 -0
- package/dist/control/admission-service.d.ts.map +1 -0
- package/dist/control/admission-service.js +64 -0
- package/dist/control/admission-service.js.map +1 -0
- package/dist/control/approval-edits.d.ts +12 -0
- package/dist/control/approval-edits.d.ts.map +1 -0
- package/dist/control/approval-edits.js +66 -0
- package/dist/control/approval-edits.js.map +1 -0
- package/dist/control/approval-service.d.ts +173 -0
- package/dist/control/approval-service.d.ts.map +1 -0
- package/dist/control/approval-service.js +342 -0
- package/dist/control/approval-service.js.map +1 -0
- package/dist/control/authorization.d.ts +94 -0
- package/dist/control/authorization.d.ts.map +1 -0
- package/dist/control/authorization.js +302 -0
- package/dist/control/authorization.js.map +1 -0
- package/dist/control/control-cli.d.ts +3 -0
- package/dist/control/control-cli.d.ts.map +1 -0
- package/dist/control/control-cli.js +279 -0
- package/dist/control/control-cli.js.map +1 -0
- package/dist/control/control-console.d.ts +17 -0
- package/dist/control/control-console.d.ts.map +1 -0
- package/dist/control/control-console.js +67 -0
- package/dist/control/control-console.js.map +1 -0
- package/dist/control/control-host.d.ts +98 -0
- package/dist/control/control-host.d.ts.map +1 -0
- package/dist/control/control-host.js +459 -0
- package/dist/control/control-host.js.map +1 -0
- package/dist/control/control-mcp.d.ts +11 -0
- package/dist/control/control-mcp.d.ts.map +1 -0
- package/dist/control/control-mcp.js +78 -0
- package/dist/control/control-mcp.js.map +1 -0
- package/dist/control/errors.d.ts +44 -0
- package/dist/control/errors.d.ts.map +1 -0
- package/dist/control/errors.js +93 -0
- package/dist/control/errors.js.map +1 -0
- package/dist/control/hello.d.ts +39 -0
- package/dist/control/hello.d.ts.map +1 -0
- package/dist/control/hello.js +80 -0
- package/dist/control/hello.js.map +1 -0
- package/dist/control/host-runtime.d.ts +32 -0
- package/dist/control/host-runtime.d.ts.map +1 -0
- package/dist/control/host-runtime.js +542 -0
- package/dist/control/host-runtime.js.map +1 -0
- package/dist/control/index.d.ts +32 -0
- package/dist/control/index.d.ts.map +1 -0
- package/dist/control/index.js +32 -0
- package/dist/control/index.js.map +1 -0
- package/dist/control/legacy-conflict.d.ts +19 -0
- package/dist/control/legacy-conflict.d.ts.map +1 -0
- package/dist/control/legacy-conflict.js +176 -0
- package/dist/control/legacy-conflict.js.map +1 -0
- package/dist/control/local-bootstrap.d.ts +21 -0
- package/dist/control/local-bootstrap.d.ts.map +1 -0
- package/dist/control/local-bootstrap.js +224 -0
- package/dist/control/local-bootstrap.js.map +1 -0
- package/dist/control/modes.d.ts +60 -0
- package/dist/control/modes.d.ts.map +1 -0
- package/dist/control/modes.js +86 -0
- package/dist/control/modes.js.map +1 -0
- package/dist/control/project-admin-cli.d.ts +3 -0
- package/dist/control/project-admin-cli.d.ts.map +1 -0
- package/dist/control/project-admin-cli.js +155 -0
- package/dist/control/project-admin-cli.js.map +1 -0
- package/dist/control/project-registry.d.ts +20 -0
- package/dist/control/project-registry.d.ts.map +1 -0
- package/dist/control/project-registry.js +181 -0
- package/dist/control/project-registry.js.map +1 -0
- package/dist/control/runtime-checkpoint.d.ts +11 -0
- package/dist/control/runtime-checkpoint.d.ts.map +1 -0
- package/dist/control/runtime-checkpoint.js +39 -0
- package/dist/control/runtime-checkpoint.js.map +1 -0
- package/dist/control/runtime-provider.d.ts +100 -0
- package/dist/control/runtime-provider.d.ts.map +1 -0
- package/dist/control/runtime-provider.js +258 -0
- package/dist/control/runtime-provider.js.map +1 -0
- package/dist/control/schema/approval.d.ts +108 -0
- package/dist/control/schema/approval.d.ts.map +1 -0
- package/dist/control/schema/approval.js +65 -0
- package/dist/control/schema/approval.js.map +1 -0
- package/dist/control/schema/commands.d.ts +663 -0
- package/dist/control/schema/commands.d.ts.map +1 -0
- package/dist/control/schema/commands.js +147 -0
- package/dist/control/schema/commands.js.map +1 -0
- package/dist/control/schema/common.d.ts +24 -0
- package/dist/control/schema/common.d.ts.map +1 -0
- package/dist/control/schema/common.js +38 -0
- package/dist/control/schema/common.js.map +1 -0
- package/dist/control/schema/coordinator.d.ts +98 -0
- package/dist/control/schema/coordinator.d.ts.map +1 -0
- package/dist/control/schema/coordinator.js +87 -0
- package/dist/control/schema/coordinator.js.map +1 -0
- package/dist/control/schema/evidence.d.ts +140 -0
- package/dist/control/schema/evidence.d.ts.map +1 -0
- package/dist/control/schema/evidence.js +64 -0
- package/dist/control/schema/evidence.js.map +1 -0
- package/dist/control/schema/header.d.ts +79 -0
- package/dist/control/schema/header.d.ts.map +1 -0
- package/dist/control/schema/header.js +66 -0
- package/dist/control/schema/header.js.map +1 -0
- package/dist/control/schema/index.d.ts +31 -0
- package/dist/control/schema/index.d.ts.map +1 -0
- package/dist/control/schema/index.js +31 -0
- package/dist/control/schema/index.js.map +1 -0
- package/dist/control/schema/plan.d.ts +291 -0
- package/dist/control/schema/plan.d.ts.map +1 -0
- package/dist/control/schema/plan.js +80 -0
- package/dist/control/schema/plan.js.map +1 -0
- package/dist/control/schema/policy.d.ts +96 -0
- package/dist/control/schema/policy.d.ts.map +1 -0
- package/dist/control/schema/policy.js +73 -0
- package/dist/control/schema/policy.js.map +1 -0
- package/dist/control/schema/run.d.ts +61 -0
- package/dist/control/schema/run.d.ts.map +1 -0
- package/dist/control/schema/run.js +56 -0
- package/dist/control/schema/run.js.map +1 -0
- package/dist/control/schema/te-mirrors.d.ts +145 -0
- package/dist/control/schema/te-mirrors.d.ts.map +1 -0
- package/dist/control/schema/te-mirrors.js +145 -0
- package/dist/control/schema/te-mirrors.js.map +1 -0
- package/dist/control/schema/transport.d.ts +479 -0
- package/dist/control/schema/transport.d.ts.map +1 -0
- package/dist/control/schema/transport.js +181 -0
- package/dist/control/schema/transport.js.map +1 -0
- package/dist/control/singleton.d.ts +135 -0
- package/dist/control/singleton.d.ts.map +1 -0
- package/dist/control/singleton.js +607 -0
- package/dist/control/singleton.js.map +1 -0
- package/dist/control/store/coordinator-store.d.ts +143 -0
- package/dist/control/store/coordinator-store.d.ts.map +1 -0
- package/dist/control/store/coordinator-store.js +620 -0
- package/dist/control/store/coordinator-store.js.map +1 -0
- package/dist/control/store/evidence-adapter.d.ts +10 -0
- package/dist/control/store/evidence-adapter.d.ts.map +1 -0
- package/dist/control/store/evidence-adapter.js +114 -0
- package/dist/control/store/evidence-adapter.js.map +1 -0
- package/dist/control/store/evidence-explanation.d.ts +55 -0
- package/dist/control/store/evidence-explanation.d.ts.map +1 -0
- package/dist/control/store/evidence-explanation.js +50 -0
- package/dist/control/store/evidence-explanation.js.map +1 -0
- package/dist/control/store/evidence-store.d.ts +143 -0
- package/dist/control/store/evidence-store.d.ts.map +1 -0
- package/dist/control/store/evidence-store.js +437 -0
- package/dist/control/store/evidence-store.js.map +1 -0
- package/dist/control/store/index.d.ts +3 -0
- package/dist/control/store/index.d.ts.map +1 -0
- package/dist/control/store/index.js +2 -0
- package/dist/control/store/index.js.map +1 -0
- package/dist/control/store/journal.d.ts +270 -0
- package/dist/control/store/journal.d.ts.map +1 -0
- package/dist/control/store/journal.js +50 -0
- package/dist/control/store/journal.js.map +1 -0
- package/dist/control/store/project-lifecycle.d.ts +49 -0
- package/dist/control/store/project-lifecycle.d.ts.map +1 -0
- package/dist/control/store/project-lifecycle.js +288 -0
- package/dist/control/store/project-lifecycle.js.map +1 -0
- package/dist/control/store/store.d.ts +93 -0
- package/dist/control/store/store.d.ts.map +1 -0
- package/dist/control/store/store.js +873 -0
- package/dist/control/store/store.js.map +1 -0
- package/dist/control/te-provider.d.ts +86 -0
- package/dist/control/te-provider.d.ts.map +1 -0
- package/dist/control/te-provider.js +131 -0
- package/dist/control/te-provider.js.map +1 -0
- package/dist/control/uds.d.ts +114 -0
- package/dist/control/uds.d.ts.map +1 -0
- package/dist/control/uds.js +456 -0
- package/dist/control/uds.js.map +1 -0
- package/dist/control/web-console/view.d.ts +5 -0
- package/dist/control/web-console/view.d.ts.map +1 -0
- package/dist/control/web-console/view.js +122 -0
- package/dist/control/web-console/view.js.map +1 -0
- package/dist/control/web-console.d.ts +50 -0
- package/dist/control/web-console.d.ts.map +1 -0
- package/dist/control/web-console.js +317 -0
- package/dist/control/web-console.js.map +1 -0
- package/dist/mcp/jsonrpc.d.ts.map +1 -1
- package/dist/mcp/jsonrpc.js +12 -3
- package/dist/mcp/jsonrpc.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +26 -17
- package/dist/mcp/server.js.map +1 -1
- package/package.json +13 -5
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Static shell only. All owner-provided content is inserted with textContent. */
|
|
2
|
+
export const CONSOLE_HTML = `<!doctype html>
|
|
3
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Taskflow · Operator console</title><link rel="stylesheet" href="/style.css"><script src="/app.js" defer></script></head>
|
|
4
|
+
<body><header><div><strong>Taskflow</strong><span class="subtitle">Operator console</span></div><span id="connection" role="status">Disconnected</span></header>
|
|
5
|
+
<section id="login" class="login"><h1>Connect to your local control service</h1><p>Enter the one-use session token provided by your trusted launcher. The token stays on this machine.</p><label for="token">Launcher session token</label><input id="token" type="password" autocomplete="off" spellcheck="false"><button id="connect">Connect</button><p id="login-error" role="alert"></p></section>
|
|
6
|
+
<div id="console" hidden><aside><div class="section-heading"><h2>Projects</h2><button id="refresh-projects" class="quiet">Refresh</button></div><div id="projects"></div><button id="logout" class="quiet">End browser session</button></aside>
|
|
7
|
+
<main><div class="section-heading"><div><h1 id="project-title">Select a project</h1><p id="project-context" class="mono muted"></p></div><button id="refresh" class="quiet">Refresh view</button></div>
|
|
8
|
+
<p id="notice" role="status" aria-live="polite"></p><nav aria-label="Project views"><button data-tab="runs" class="selected">Runs</button><button data-tab="approvals">Approvals</button><button data-tab="evidence">Receipt & evidence</button></nav>
|
|
9
|
+
<section id="runs-panel"><h2>Runs</h2><p class="muted">Status and stage are reported by the project ledger.</p><div id="runs"></div><pre id="run-detail" hidden></pre></section>
|
|
10
|
+
<section id="approvals-panel" hidden><h2>Approval inbox</h2><p id="edit-capability" class="muted"></p><div id="approvals"></div></section>
|
|
11
|
+
<section id="evidence-panel" hidden><h2>Receipt & evidence</h2><label for="run-choice">Run</label><select id="run-choice"></select><div class="actions"><button id="receipt">View receipt</button><button id="why-stale" class="quiet">Why stale</button><input id="effect-id" placeholder="Effect ID" aria-label="Effect ID"><input id="phase-id" placeholder="Phase ID (optional)" aria-label="Phase ID"><button id="why-effect" class="quiet">Why effect</button></div><p class="muted">Receipts are issued by control after final settlement. Missing evidence is never treated as verified.</p><pre id="evidence-output">Choose a run to inspect its committed evidence.</pre></section>
|
|
12
|
+
<details><summary>Control connection details</summary><pre id="control-status"></pre></details></main></div>
|
|
13
|
+
<footer>Local operator session · Live authorization remains with ControlHost</footer></body></html>`;
|
|
14
|
+
export const CONSOLE_CSS = `:root{color-scheme:light;--ink:#192a30;--muted:#67777c;--line:#dce4e6;--accent:#146a60;--canvas:#f6f8f8}*{box-sizing:border-box}body{margin:0;background:var(--canvas);color:var(--ink);font:14px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}header{height:68px;padding:0 30px;background:white;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between}header strong{font-size:20px;letter-spacing:-.6px}.subtitle{margin-left:18px;color:var(--muted)}#connection{font-size:12px;border:1px solid var(--line);padding:4px 10px;border-radius:20px}.login{max-width:610px;margin:70px auto;padding:32px;background:#fff;border:1px solid var(--line);border-radius:12px}h1{font-size:23px;letter-spacing:-.5px;margin:0 0 8px}h2{font-size:16px;margin:0}p{margin:8px 0 18px}.muted{color:var(--muted)}.mono,pre{font-family:ui-monospace,SFMono-Regular,monospace}label{display:block;margin:14px 0 6px;font-weight:600}input,select,textarea{font:inherit;padding:8px 10px;background:white;border:1px solid #b8c8cc;border-radius:6px;color:var(--ink)}input:focus,select:focus,textarea:focus{outline:2px solid #8ac5bb;outline-offset:2px}#token{width:100%;margin-bottom:14px}button{font:inherit;font-weight:600;cursor:pointer;background:var(--accent);color:white;border:1px solid var(--accent);border-radius:6px;padding:7px 13px}button:hover{filter:brightness(.95)}button:disabled{cursor:not-allowed;opacity:.48}.quiet{background:white;color:var(--ink);border-color:var(--line)}#console{display:grid;grid-template-columns:246px minmax(0,1fr);min-height:calc(100vh - 110px)}#console[hidden]{display:none}aside{padding:25px 18px;border-right:1px solid var(--line);background:#eef3f2}main{padding:30px 36px;max-width:1400px;width:100%}.section-heading{display:flex;align-items:center;justify-content:space-between;gap:14px;margin-bottom:20px}.section-heading p{margin-bottom:0;font-size:11px;overflow-wrap:anywhere}.project{display:block;width:100%;text-align:left;margin:8px 0;padding:12px;background:transparent;border:1px solid transparent;color:var(--ink);overflow-wrap:anywhere}.project.selected{background:white;border-color:#c5d7d2;box-shadow:0 1px 3px #163c3010}.project small{display:block;color:var(--muted);font-weight:400;margin-top:5px}#logout{margin-top:28px;width:100%}nav{display:flex;gap:6px;border-bottom:1px solid var(--line);margin:20px 0 25px;padding-bottom:10px}nav button{background:transparent;color:var(--muted);border-color:transparent}nav button.selected{background:#e4efeb;color:#155f53}#notice{min-height:22px;margin:0;color:var(--muted)}#notice.error,#login-error{color:#9c3131}.card{background:white;border:1px solid var(--line);border-radius:9px;padding:17px;margin:12px 0}.card h3{margin:0 0 6px;font-size:14px;overflow-wrap:anywhere}.row{display:flex;align-items:center;justify-content:space-between;gap:16px}.tag{display:inline-block;background:#edf1f2;color:#385158;font-size:12px;border-radius:4px;padding:2px 7px;margin-right:6px}.actions{display:flex;flex-wrap:wrap;gap:8px;margin-top:15px}.actions input{width:180px}.empty{padding:28px;border:1px dashed #c6d3d5;border-radius:8px;color:var(--muted);background:#fcfdfd}pre{white-space:pre-wrap;overflow-wrap:anywhere;padding:18px;border:1px solid var(--line);border-radius:8px;background:#fff;font-size:12px}textarea{display:block;width:100%;min-height:70px;margin:12px 0}details{margin-top:30px;color:var(--muted)}summary{cursor:pointer}footer{padding:10px 30px;font-size:11px;color:var(--muted);border-top:1px solid var(--line)}[hidden]{display:none!important}@media(max-width:800px){#console{grid-template-columns:1fr}aside{border-right:0;border-bottom:1px solid var(--line)}main{padding:24px 18px}.subtitle{display:none}.row{align-items:flex-start}.section-heading{align-items:flex-start}.login{margin:30px 15px}header{padding:0 18px}}`;
|
|
15
|
+
export const CONSOLE_JS = String.raw `'use strict';
|
|
16
|
+
const $ = (id) => document.getElementById(id);
|
|
17
|
+
const state = { csrf: '', features: {}, project: null, projects: [], runs: [], generation: 0, evidenceGeneration: 0, commands: new Map() };
|
|
18
|
+
function node(tag, text, className) { const e = document.createElement(tag); if (text !== undefined) e.textContent = text; if (className) e.className = className; return e; }
|
|
19
|
+
function clear(e) { e.replaceChildren(); }
|
|
20
|
+
function notice(text, error = false) { $('notice').textContent = text; $('notice').className = error ? 'error' : ''; }
|
|
21
|
+
function message(error) { return (error.code ? error.code + ': ' : '') + error.message; }
|
|
22
|
+
async function api(path, body, method) {
|
|
23
|
+
const headers = {}; if (body !== undefined) headers['Content-Type'] = 'application/json'; if (state.csrf) headers['X-Taskflow-CSRF'] = state.csrf;
|
|
24
|
+
const response = await fetch(path, { method: method || (body === undefined ? 'GET' : 'POST'), headers, credentials: 'same-origin', ...(body === undefined ? {} : { body: JSON.stringify(body) }) });
|
|
25
|
+
const value = await response.json(); if (!response.ok) { const e = new Error(value.error?.message || 'Control request failed'); e.code = value.error?.code; if (response.status === 401) $('connection').textContent = 'Session expired'; throw e; } return value;
|
|
26
|
+
}
|
|
27
|
+
function projectUrl(project, suffix) { return '/api/projects/' + encodeURIComponent(project.projectId) + suffix + '?controlDomainId=' + encodeURIComponent(project.controlDomainId); }
|
|
28
|
+
function empty(parent, text) { parent.append(node('p', text, 'empty')); }
|
|
29
|
+
function tab(name) { for (const n of ['runs', 'approvals', 'evidence']) $(n + '-panel').hidden = name !== n; for (const b of document.querySelectorAll('[data-tab]')) b.classList.toggle('selected', b.dataset.tab === name); }
|
|
30
|
+
for (const b of document.querySelectorAll('[data-tab]')) b.addEventListener('click', () => tab(b.dataset.tab));
|
|
31
|
+
function renderProjects() {
|
|
32
|
+
clear($('projects')); if (!state.projects.length) empty($('projects'), 'No authorized projects are mounted.');
|
|
33
|
+
for (const p of state.projects) { const b = node('button', undefined, 'project'); b.classList.toggle('selected', state.project?.projectId === p.projectId); b.append(node('span', p.name || p.path || p.projectId), node('small', p.mountState || 'Mounted project')); b.addEventListener('click', () => { state.project = p; renderProjects(); void refreshProject(); }); $('projects').append(b); }
|
|
34
|
+
}
|
|
35
|
+
function renderRuns(runs, project) {
|
|
36
|
+
clear($('runs')); clear($('run-choice')); state.runs = runs;
|
|
37
|
+
if (!runs.length) { empty($('runs'), 'No runs in this project.'); $('run-choice').append(node('option', 'No runs available')); }
|
|
38
|
+
for (const r of runs) {
|
|
39
|
+
const option = node('option', r.runId); option.value = r.runId; $('run-choice').append(option);
|
|
40
|
+
const card = node('div', undefined, 'card'); const row = node('div', undefined, 'row'); const info = node('div'); info.append(node('h3', r.runId));
|
|
41
|
+
const labels = node('div'); for (const value of [r.status, r.stage, 'slot: ' + r.slot]) labels.append(node('span', value, 'tag')); if (r.needsOperator) labels.append(node('span', 'Needs operator', 'tag')); info.append(labels);
|
|
42
|
+
const details = node('button', 'Inspect', 'quiet'); details.addEventListener('click', async () => { const g = state.generation; notice('Loading run…'); try { const data = await api(projectUrl(project, '/runs/' + encodeURIComponent(r.runId))); if (g !== state.generation) return; $('run-detail').hidden = false; $('run-detail').textContent = JSON.stringify(data.result, null, 2); $('run-choice').value = r.runId; notice('Run loaded from control.'); } catch (e) { if (g === state.generation) notice(message(e), true); } });
|
|
43
|
+
row.append(info, details); card.append(row); $('runs').append(card);
|
|
44
|
+
}
|
|
45
|
+
for (const id of ['receipt', 'why-stale', 'why-effect']) $(id).disabled = runs.length === 0;
|
|
46
|
+
}
|
|
47
|
+
function renderApprovals(approvals, project) {
|
|
48
|
+
clear($('approvals')); $('edit-capability').textContent = state.features.approvalOutputEdit ? 'Enter the revised output, then choose Edit to continue the run.' : state.features.approvalEditWithArtifactRef ? 'Editing requires an existing, authorized output ArtifactRef.' : 'Editing is not supported by the connected control service.';
|
|
49
|
+
if (!approvals.length) empty($('approvals'), 'No approval requests in this project.');
|
|
50
|
+
for (const a of approvals) {
|
|
51
|
+
const card = node('div', undefined, 'card'); card.append(node('h3', a.message || a.approvalRequestId), node('p', 'Run ' + a.runId + ' · version ' + a.expectedRunVersion + ' · ' + a.status, 'mono muted'), node('p', 'Deadline: ' + new Date(a.deadline).toLocaleString(), 'muted'));
|
|
52
|
+
let preparedEdit; const editRef = node('textarea'); editRef.placeholder = state.features.approvalOutputEdit ? 'Edited output' : 'Output ArtifactRef JSON'; editRef.setAttribute('aria-label', editRef.placeholder); editRef.hidden = !state.features.approvalEditWithArtifactRef || !a.allowedDecisions.includes('edit'); card.append(editRef);
|
|
53
|
+
const buttons = node('div', undefined, 'actions');
|
|
54
|
+
for (const decision of a.allowedDecisions) {
|
|
55
|
+
const b = node('button', decision[0].toUpperCase() + decision.slice(1), decision === 'approve' ? '' : 'quiet'); b.disabled = a.status !== 'pending' || (decision === 'edit' && !state.features.approvalEditWithArtifactRef); b.addEventListener('click', async () => {
|
|
56
|
+
const g = state.generation; const peers = Array.from(buttons.querySelectorAll('button')); peers.forEach(x => x.disabled = true); notice('Submitting ' + decision + '…');
|
|
57
|
+
try {
|
|
58
|
+
let editArtifactRef; let expectedRunVersion = a.expectedRunVersion;
|
|
59
|
+
if (decision === 'edit' && state.features.approvalOutputEdit) {
|
|
60
|
+
if (!editRef.value.trim()) throw new Error('Enter revised output text before choosing Edit.');
|
|
61
|
+
if (!preparedEdit || preparedEdit.content !== editRef.value) {
|
|
62
|
+
const staged = await api(projectUrl(project, '/runs/' + encodeURIComponent(a.runId) + '/approval-output'), { content: editRef.value });
|
|
63
|
+
const fresh = await api(projectUrl(project, '/approvals'));
|
|
64
|
+
const current = fresh.result.find(item => item.approvalRequestId === a.approvalRequestId);
|
|
65
|
+
if (!current || current.status !== 'pending' || current.expectedRunVersion !== a.expectedRunVersion + 1) { const error = new Error('Approval changed while staging the edit.'); error.code = 'TF_STALE_VERSION'; throw error; }
|
|
66
|
+
preparedEdit = { content: editRef.value, artifact: staged.result, version: current.expectedRunVersion };
|
|
67
|
+
}
|
|
68
|
+
editArtifactRef = preparedEdit.artifact; expectedRunVersion = preparedEdit.version;
|
|
69
|
+
} else if (decision === 'edit') editArtifactRef = JSON.parse(editRef.value);
|
|
70
|
+
const key = [project.projectId, a.approvalRequestId, expectedRunVersion, decision, JSON.stringify(editArtifactRef)].join(':');
|
|
71
|
+
if (!state.commands.has(key)) state.commands.set(key, crypto.randomUUID());
|
|
72
|
+
const body = { commandId: state.commands.get(key), runId: a.runId, expectedRunVersion, decision, ...(editArtifactRef === undefined ? {} : { editArtifactRef, editKind: 'output' }) };
|
|
73
|
+
await api(projectUrl(project, '/approvals/' + encodeURIComponent(a.approvalRequestId) + '/decisions'), body);
|
|
74
|
+
if (g !== state.generation) return; await refreshProject(); notice('Decision committed by control.');
|
|
75
|
+
} catch (e) {
|
|
76
|
+
if (g !== state.generation) return;
|
|
77
|
+
if (e.code === 'TF_STALE_VERSION') await refreshProject();
|
|
78
|
+
notice(message(e) + (e.code === 'TF_STALE_VERSION' ? ' The current request has been refreshed; review before deciding again.' : ''), true);
|
|
79
|
+
peers.forEach(x => x.disabled = a.status !== 'pending' || (x.textContent === 'Edit' && !state.features.approvalEditWithArtifactRef));
|
|
80
|
+
}
|
|
81
|
+
}); buttons.append(b);
|
|
82
|
+
}
|
|
83
|
+
card.append(buttons); $('approvals').append(card);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function refreshProject() {
|
|
87
|
+
const p = state.project; const g = ++state.generation; $('run-detail').hidden = true; clear($('run-detail')); clear($('runs')); clear($('approvals')); clear($('run-choice')); $('evidence-output').textContent = 'Choose a run to inspect its committed evidence.';
|
|
88
|
+
for (const id of ['receipt', 'why-stale', 'why-effect']) $(id).disabled = true;
|
|
89
|
+
if (!p) { $('project-title').textContent = 'Select a project'; $('project-context').textContent = ''; return; }
|
|
90
|
+
$('project-title').textContent = p.name || p.path || p.projectId; $('project-context').textContent = p.projectId + ' · domain ' + p.controlDomainId; notice('Loading project…');
|
|
91
|
+
const results = await Promise.allSettled([api(projectUrl(p, '/runs')), api(projectUrl(p, '/approvals'))]); if (g !== state.generation) return;
|
|
92
|
+
const errors = [];
|
|
93
|
+
for (let i = 0; i < results.length; i++) { const r = results[i]; const target = $(i === 0 ? 'runs' : 'approvals'); if (r.status === 'fulfilled' && Array.isArray(r.value.result)) { if (i === 0) renderRuns(r.value.result, p); else renderApprovals(r.value.result, p); } else { const text = r.status === 'rejected' ? message(r.reason) : 'Control returned an invalid list'; empty(target, text); errors.push(text); } }
|
|
94
|
+
notice(errors.length ? errors.join(' · ') : 'Updated from the project ledger.', errors.length > 0);
|
|
95
|
+
}
|
|
96
|
+
async function projects() {
|
|
97
|
+
notice('Loading projects…');
|
|
98
|
+
try { const data = await api('/api/projects'); if (!Array.isArray(data.result)) throw new Error('Control returned an invalid project list'); state.projects = data.result; state.project = state.projects.find(p => p.projectId === state.project?.projectId) || state.projects[0] || null; renderProjects(); await refreshProject(); }
|
|
99
|
+
catch (e) { state.projects = []; state.project = null; renderProjects(); await refreshProject(); notice(message(e), true); }
|
|
100
|
+
}
|
|
101
|
+
async function connected(session) {
|
|
102
|
+
state.csrf = session.csrf; state.features = session.features; $('token').value = ''; $('login').hidden = true; $('console').hidden = false; $('connection').textContent = 'Local session';
|
|
103
|
+
try { const status = await api('/api/status'); $('control-status').textContent = JSON.stringify(status.result, null, 2); } catch (e) { $('control-status').textContent = message(e); }
|
|
104
|
+
await projects();
|
|
105
|
+
}
|
|
106
|
+
$('connect').addEventListener('click', async () => { $('connect').disabled = true; $('login-error').textContent = ''; try { const secret = $('token').value; $('token').value = ''; await connected(await api('/api/session', { token: secret })); } catch (e) { $('login-error').textContent = message(e); } finally { $('connect').disabled = false; } });
|
|
107
|
+
$('token').addEventListener('keydown', (e) => { if (e.key === 'Enter') $('connect').click(); });
|
|
108
|
+
$('refresh-projects').addEventListener('click', () => { void projects(); }); $('refresh').addEventListener('click', () => { void refreshProject(); });
|
|
109
|
+
$('logout').addEventListener('click', async () => { try { await api('/api/session', undefined, 'DELETE'); location.reload(); } catch (e) { notice(message(e), true); } });
|
|
110
|
+
async function evidence(kind) {
|
|
111
|
+
const p = state.project; const runId = $('run-choice').value; if (!p || !runId || !state.runs.length) return;
|
|
112
|
+
const g = state.generation; const e = ++state.evidenceGeneration; const effectId = $('effect-id').value; const phaseId = $('phase-id').value;
|
|
113
|
+
const current = () => g === state.generation && e === state.evidenceGeneration && runId === $('run-choice').value && effectId === $('effect-id').value && phaseId === $('phase-id').value;
|
|
114
|
+
$('evidence-output').textContent = 'Loading evidence…';
|
|
115
|
+
try { let url = projectUrl(p, '/runs/' + encodeURIComponent(runId) + (kind === 'receipt' ? '/receipt' : '/why')); if (kind !== 'receipt') url += '&kind=' + kind; if (kind === 'effect') url += '&effectId=' + encodeURIComponent(effectId); if (phaseId) url += '&phaseId=' + encodeURIComponent(phaseId); const data = await api(url); if (!current()) return; $('evidence-output').textContent = data.result === null && kind === 'receipt' ? 'No final Receipt has been issued for this run.' : JSON.stringify(data.result, null, 2); }
|
|
116
|
+
catch (error) { if (current()) $('evidence-output').textContent = message(error); }
|
|
117
|
+
}
|
|
118
|
+
for (const id of ['run-choice', 'effect-id', 'phase-id']) $(id).addEventListener('input', () => { state.evidenceGeneration++; $('evidence-output').textContent = 'Choose an evidence view for the current selection.'; });
|
|
119
|
+
$('receipt').addEventListener('click', () => { void evidence('receipt'); }); $('why-stale').addEventListener('click', () => { void evidence('stale'); }); $('why-effect').addEventListener('click', () => { void evidence('effect'); });
|
|
120
|
+
void api('/api/session').then(connected).catch(() => {});
|
|
121
|
+
`;
|
|
122
|
+
//# sourceMappingURL=view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../../../../taskflow-control/src/web-console/view.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;oGAWwE,CAAC;AAErG,MAAM,CAAC,MAAM,WAAW,GAAG,4wHAA4wH,CAAC;AAExyH,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0GnC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export type WebConsoleOperation = "control.status" | "projects.list" | "runs.list" | "runs.get" | "approvals.list" | "approval.decide" | "approvals.stageEdit" | "receipts.get" | "evidence.why";
|
|
2
|
+
export interface WebConsoleProject {
|
|
3
|
+
projectId: string;
|
|
4
|
+
controlDomainId: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
mountState?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Internal seam for the owner to map to its authenticated ControlHost client.
|
|
10
|
+
* projects.list returns WebConsoleProject[]; runs.list returns RunSnapshot[];
|
|
11
|
+
* approvals.list returns ApprovalRequest[] (scoped by the requested project);
|
|
12
|
+
* receipts.get returns Receipt|null; runs.get/control.status/evidence.why return
|
|
13
|
+
* their actual owner DTO. No zero-ID snapshots or synthetic success fallback.
|
|
14
|
+
*/
|
|
15
|
+
export interface WebConsoleControlClient {
|
|
16
|
+
call(operation: WebConsoleOperation, params: Readonly<Record<string, unknown>>): Promise<unknown>;
|
|
17
|
+
}
|
|
18
|
+
export interface WebConsoleAuthorization {
|
|
19
|
+
operation: WebConsoleOperation;
|
|
20
|
+
/** Re-check before dispatch and before disclosing its result; no second call. */
|
|
21
|
+
purpose: "execute" | "disclose";
|
|
22
|
+
/** Random browser-session handle, NEVER a claimed principal. */
|
|
23
|
+
sessionId: string;
|
|
24
|
+
projectId?: string;
|
|
25
|
+
controlDomainId?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface WebConsoleOptions {
|
|
28
|
+
/** Required live check. Resolve verified identity from trusted launcher state,
|
|
29
|
+
* never from HTTP input/sessionId alone. Re-check project/operation policy for
|
|
30
|
+
* both purposes. The owner must also enforce live policy at mutation commit. */
|
|
31
|
+
authorize: (request: WebConsoleAuthorization) => Promise<WebConsoleControlClient>;
|
|
32
|
+
port?: number;
|
|
33
|
+
/** Absolute browser session lifetime, default 30 minutes. */
|
|
34
|
+
sessionTtlMs?: number;
|
|
35
|
+
/** Absolute one-use bootstrap lifetime, default 5 minutes. */
|
|
36
|
+
bootstrapTtlMs?: number;
|
|
37
|
+
/** Owner enables only after its output-edit ArtifactRef contract is wired. */
|
|
38
|
+
approvalEditWithArtifactRef?: boolean;
|
|
39
|
+
/** Owner supports staging output text as a same-run artifact. */
|
|
40
|
+
approvalOutputEdit?: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface WebConsoleServer {
|
|
43
|
+
readonly url: string;
|
|
44
|
+
/** One-use secret for trusted launcher/browser handoff. Never log, put in a
|
|
45
|
+
* URL, persist in localStorage or send to a remote service. */
|
|
46
|
+
readonly bootstrapToken: string;
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
export declare function startWebConsole(options: WebConsoleOptions): Promise<WebConsoleServer>;
|
|
50
|
+
//# sourceMappingURL=web-console.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-console.d.ts","sourceRoot":"","sources":["../../../taskflow-control/src/web-console.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,GAAG,eAAe,GAAG,WAAW,GAAG,UAAU,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,qBAAqB,GAAG,cAAc,GAAG,cAAc,CAAC;AACjM,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AACD;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACvC,IAAI,CAAC,SAAS,EAAE,mBAAmB,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAClG;AACD,MAAM,WAAW,uBAAuB;IACvC,SAAS,EAAE,mBAAmB,CAAC;IAC/B,iFAAiF;IACjF,OAAO,EAAE,SAAS,GAAG,UAAU,CAAC;IAChC,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CACzB;AACD,MAAM,WAAW,iBAAiB;IACjC;;oFAEgF;IAChF,SAAS,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,iEAAiE;IACjE,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC7B;AACD,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB;mEAC+D;IAC/D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AA8CD,wBAAsB,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwL3F"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/** Explicitly launched, loopback-only operator console. No store or auth authority.
|
|
2
|
+
* The trusted launcher owns OS identity; authorize MUST verify current policy on
|
|
3
|
+
* each operation and return a client bound to that verified principal. Browser
|
|
4
|
+
* sessions prove possession of launcher authority, not OS identity themselves.
|
|
5
|
+
*/
|
|
6
|
+
import { randomBytes, timingSafeEqual } from "node:crypto";
|
|
7
|
+
import * as http from "node:http";
|
|
8
|
+
import { Value } from "typebox/value";
|
|
9
|
+
import { ArtifactRefSchema } from "./schema/evidence.js";
|
|
10
|
+
import { UuidSchema } from "./schema/common.js";
|
|
11
|
+
import { ControlError } from "./errors.js";
|
|
12
|
+
import { CONSOLE_HTML, CONSOLE_JS, CONSOLE_CSS } from "./web-console/view.js";
|
|
13
|
+
class HttpFailure extends Error {
|
|
14
|
+
status;
|
|
15
|
+
constructor(status, message) { super(message); this.status = status; }
|
|
16
|
+
}
|
|
17
|
+
const token = () => randomBytes(32).toString("base64url");
|
|
18
|
+
const sameSecret = (actual, expected) => {
|
|
19
|
+
if (typeof actual !== "string")
|
|
20
|
+
return false;
|
|
21
|
+
const a = Buffer.from(actual);
|
|
22
|
+
const b = Buffer.from(expected);
|
|
23
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
24
|
+
};
|
|
25
|
+
function positive(value, name) {
|
|
26
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
27
|
+
throw new Error(`${name} must be a positive safe integer`);
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function uuid(value, name) {
|
|
31
|
+
if (!Value.Check(UuidSchema, value))
|
|
32
|
+
throw new HttpFailure(400, `${name} must be a UUID`);
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function boundedText(value, name) {
|
|
36
|
+
if (typeof value !== "string" || !value || value.length > 512)
|
|
37
|
+
throw new HttpFailure(400, `${name} is required (maximum 512 characters)`);
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function onlyKeys(body, keys) {
|
|
41
|
+
if (Object.keys(body).some((key) => !keys.includes(key)))
|
|
42
|
+
throw new HttpFailure(400, "Unexpected request field");
|
|
43
|
+
}
|
|
44
|
+
async function jsonBody(request) {
|
|
45
|
+
if (request.headers["content-type"]?.split(";")[0]?.trim() !== "application/json")
|
|
46
|
+
throw new HttpFailure(415, "Content-Type must be application/json");
|
|
47
|
+
if (Number(request.headers["content-length"]) > 65_536)
|
|
48
|
+
throw new HttpFailure(413, "Request body exceeds 64 KiB");
|
|
49
|
+
const chunks = [];
|
|
50
|
+
let bytes = 0;
|
|
51
|
+
for await (const chunk of request) {
|
|
52
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
53
|
+
bytes += buffer.length;
|
|
54
|
+
if (bytes > 65_536)
|
|
55
|
+
throw new HttpFailure(413, "Request body exceeds 64 KiB");
|
|
56
|
+
chunks.push(buffer);
|
|
57
|
+
}
|
|
58
|
+
let raw;
|
|
59
|
+
try {
|
|
60
|
+
raw = JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new HttpFailure(400, "Malformed JSON request");
|
|
64
|
+
}
|
|
65
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
66
|
+
throw new HttpFailure(400, "Request must be a JSON object");
|
|
67
|
+
return raw;
|
|
68
|
+
}
|
|
69
|
+
export async function startWebConsole(options) {
|
|
70
|
+
if (typeof options.authorize !== "function")
|
|
71
|
+
throw new Error("A live authorize callback is required");
|
|
72
|
+
const port = options.port ?? 0;
|
|
73
|
+
if (!Number.isInteger(port) || port < 0 || port > 65_535)
|
|
74
|
+
throw new Error("port must be 0..65535");
|
|
75
|
+
const sessionTtlMs = positive(options.sessionTtlMs ?? 30 * 60_000, "sessionTtlMs");
|
|
76
|
+
const bootstrapExpiresAt = Date.now() + positive(options.bootstrapTtlMs ?? 5 * 60_000, "bootstrapTtlMs");
|
|
77
|
+
const bootstrapToken = token();
|
|
78
|
+
let bootstrapUsed = false;
|
|
79
|
+
let closed = false;
|
|
80
|
+
let closing;
|
|
81
|
+
let origin = "";
|
|
82
|
+
let authority = "";
|
|
83
|
+
const sessions = new Map();
|
|
84
|
+
const sockets = new Set();
|
|
85
|
+
const features = { approvalEditWithArtifactRef: options.approvalEditWithArtifactRef === true, approvalOutputEdit: options.approvalOutputEdit === true };
|
|
86
|
+
const server = http.createServer((request, response) => { void handle(request, response); });
|
|
87
|
+
server.requestTimeout = 10_000;
|
|
88
|
+
server.headersTimeout = 10_000;
|
|
89
|
+
server.on("connection", (socket) => { sockets.add(socket); socket.on("close", () => sockets.delete(socket)); });
|
|
90
|
+
server.on("clientError", (_error, socket) => socket.destroy());
|
|
91
|
+
await new Promise((resolve, reject) => {
|
|
92
|
+
server.once("error", reject);
|
|
93
|
+
server.listen(port, "127.0.0.1", () => { server.off("error", reject); resolve(); });
|
|
94
|
+
});
|
|
95
|
+
authority = `127.0.0.1:${server.address().port}`;
|
|
96
|
+
origin = `http://${authority}`;
|
|
97
|
+
server.on("error", () => { });
|
|
98
|
+
async function handle(request, response) {
|
|
99
|
+
response.setHeader("Cache-Control", "no-store");
|
|
100
|
+
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
101
|
+
response.setHeader("Referrer-Policy", "no-referrer");
|
|
102
|
+
response.setHeader("Content-Security-Policy", "default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'");
|
|
103
|
+
const assertConnected = () => {
|
|
104
|
+
if (closed || request.aborted || request.socket.destroyed || response.destroyed || response.writableEnded)
|
|
105
|
+
throw new HttpFailure(503, "Console request closed");
|
|
106
|
+
};
|
|
107
|
+
let assertSession;
|
|
108
|
+
const send = (status, value) => {
|
|
109
|
+
assertConnected();
|
|
110
|
+
response.statusCode = status;
|
|
111
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
112
|
+
response.end(JSON.stringify(value));
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
assertConnected();
|
|
116
|
+
if (request.headers.host !== authority)
|
|
117
|
+
throw new HttpFailure(403, "Invalid console Host");
|
|
118
|
+
if (request.headers.origin !== undefined && request.headers.origin !== origin)
|
|
119
|
+
throw new HttpFailure(403, "Invalid console Origin");
|
|
120
|
+
const site = request.headers["sec-fetch-site"];
|
|
121
|
+
if (site !== undefined && site !== "same-origin" && site !== "none")
|
|
122
|
+
throw new HttpFailure(403, "Cross-site console request denied");
|
|
123
|
+
const url = new URL(request.url ?? "/", origin);
|
|
124
|
+
if (url.origin !== origin)
|
|
125
|
+
throw new HttpFailure(403, "Invalid request origin");
|
|
126
|
+
const method = request.method ?? "GET";
|
|
127
|
+
if (method === "GET" && ["/", "/app.js", "/style.css"].includes(url.pathname)) {
|
|
128
|
+
response.setHeader("Content-Type", url.pathname === "/" ? "text/html; charset=utf-8" : url.pathname === "/app.js" ? "text/javascript; charset=utf-8" : "text/css; charset=utf-8");
|
|
129
|
+
response.end(url.pathname === "/" ? CONSOLE_HTML : url.pathname === "/app.js" ? CONSOLE_JS : CONSOLE_CSS);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!url.pathname.startsWith("/api/"))
|
|
133
|
+
throw new HttpFailure(404, "Unknown console route");
|
|
134
|
+
const mutating = method !== "GET";
|
|
135
|
+
if (!["GET", "POST", "DELETE"].includes(method))
|
|
136
|
+
throw new HttpFailure(405, "Method not allowed");
|
|
137
|
+
if (mutating && request.headers.origin !== origin)
|
|
138
|
+
throw new HttpFailure(403, "Console mutations require same Origin");
|
|
139
|
+
if (url.pathname === "/api/session" && method === "POST") {
|
|
140
|
+
const body = await jsonBody(request);
|
|
141
|
+
assertConnected();
|
|
142
|
+
onlyKeys(body, ["token"]);
|
|
143
|
+
if (bootstrapUsed || Date.now() >= bootstrapExpiresAt || !sameSecret(body.token, bootstrapToken))
|
|
144
|
+
throw new HttpFailure(401, "Invalid or expired launcher token");
|
|
145
|
+
// Consume synchronously before any await: concurrent exchanges cannot reuse it.
|
|
146
|
+
bootstrapUsed = true;
|
|
147
|
+
const sessionId = token();
|
|
148
|
+
const session = { csrf: token(), expiresAt: Date.now() + sessionTtlMs };
|
|
149
|
+
sessions.set(sessionId, session);
|
|
150
|
+
response.setHeader("Set-Cookie", `taskflow_console=${sessionId}; HttpOnly; SameSite=Strict; Path=/; Max-Age=${Math.ceil(sessionTtlMs / 1000)}`);
|
|
151
|
+
send(200, { csrf: session.csrf, expiresAt: session.expiresAt, features });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const cookies = (request.headers.cookie ?? "").split(";").map((part) => part.trim()).filter((part) => part.startsWith("taskflow_console="));
|
|
155
|
+
const sessionId = cookies.length === 1 ? cookies[0].slice("taskflow_console=".length) : "";
|
|
156
|
+
const session = sessions.get(sessionId);
|
|
157
|
+
if (!session || Date.now() >= session.expiresAt) {
|
|
158
|
+
sessions.delete(sessionId);
|
|
159
|
+
throw new HttpFailure(401, "Console session expired; launch a new session");
|
|
160
|
+
}
|
|
161
|
+
assertSession = () => {
|
|
162
|
+
assertConnected();
|
|
163
|
+
if (sessions.get(sessionId) !== session || Date.now() >= session.expiresAt) {
|
|
164
|
+
if (sessions.get(sessionId) === session)
|
|
165
|
+
sessions.delete(sessionId);
|
|
166
|
+
throw new HttpFailure(401, "Console session expired; launch a new session");
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
if (mutating && !sameSecret(request.headers["x-taskflow-csrf"], session.csrf))
|
|
170
|
+
throw new HttpFailure(403, "Invalid CSRF token");
|
|
171
|
+
if (url.pathname === "/api/session") {
|
|
172
|
+
if (method === "DELETE") {
|
|
173
|
+
sessions.delete(sessionId);
|
|
174
|
+
response.setHeader("Set-Cookie", "taskflow_console=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0");
|
|
175
|
+
send(200, { closed: true });
|
|
176
|
+
}
|
|
177
|
+
else if (method === "GET")
|
|
178
|
+
send(200, { csrf: session.csrf, expiresAt: session.expiresAt, features });
|
|
179
|
+
else
|
|
180
|
+
throw new HttpFailure(405, "Method not allowed");
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
let operation;
|
|
184
|
+
let params = {};
|
|
185
|
+
if (url.pathname === "/api/status" && method === "GET")
|
|
186
|
+
operation = "control.status";
|
|
187
|
+
else if (url.pathname === "/api/projects" && method === "GET")
|
|
188
|
+
operation = "projects.list";
|
|
189
|
+
else {
|
|
190
|
+
const segments = url.pathname.split("/").slice(1);
|
|
191
|
+
if (segments[0] !== "api" || segments[1] !== "projects")
|
|
192
|
+
throw new HttpFailure(404, "Unknown console route");
|
|
193
|
+
params = { projectId: uuid(segments[2], "projectId"), controlDomainId: uuid(url.searchParams.get("controlDomainId"), "controlDomainId") };
|
|
194
|
+
if (segments.length === 4 && method === "GET" && segments[3] === "runs")
|
|
195
|
+
operation = "runs.list";
|
|
196
|
+
else if (segments.length === 4 && method === "GET" && segments[3] === "approvals")
|
|
197
|
+
operation = "approvals.list";
|
|
198
|
+
else if (segments[3] === "runs" && method === "GET" && [5, 6].includes(segments.length)) {
|
|
199
|
+
params.runId = uuid(segments[4], "runId");
|
|
200
|
+
if (segments.length === 5)
|
|
201
|
+
operation = "runs.get";
|
|
202
|
+
else if (segments[5] === "receipt")
|
|
203
|
+
operation = "receipts.get";
|
|
204
|
+
else if (segments[5] === "why") {
|
|
205
|
+
operation = "evidence.why";
|
|
206
|
+
params.kind = url.searchParams.get("kind");
|
|
207
|
+
if (params.kind !== "stale" && params.kind !== "effect")
|
|
208
|
+
throw new HttpFailure(400, "why kind must be stale or effect");
|
|
209
|
+
if (params.kind === "effect")
|
|
210
|
+
params.effectId = boundedText(url.searchParams.get("effectId"), "effectId");
|
|
211
|
+
if (url.searchParams.has("phaseId"))
|
|
212
|
+
params.phaseId = boundedText(url.searchParams.get("phaseId"), "phaseId");
|
|
213
|
+
}
|
|
214
|
+
else
|
|
215
|
+
throw new HttpFailure(404, "Unknown console route");
|
|
216
|
+
}
|
|
217
|
+
else if (segments[3] === "runs" && segments[5] === "approval-output" && segments.length === 6 && method === "POST") {
|
|
218
|
+
if (!features.approvalOutputEdit)
|
|
219
|
+
throw new HttpFailure(409, "Output editing is unavailable on this control service");
|
|
220
|
+
operation = "approvals.stageEdit";
|
|
221
|
+
params.runId = uuid(segments[4], "runId");
|
|
222
|
+
const body = await jsonBody(request);
|
|
223
|
+
assertSession();
|
|
224
|
+
onlyKeys(body, ["content"]);
|
|
225
|
+
if (typeof body.content !== "string" || !body.content.trim() || body.content.includes("\0"))
|
|
226
|
+
throw new HttpFailure(400, "Output text must be nonempty and contain no NUL characters");
|
|
227
|
+
params.editKind = "output";
|
|
228
|
+
params.content = body.content;
|
|
229
|
+
}
|
|
230
|
+
else if (segments[3] === "approvals" && segments[5] === "decisions" && segments.length === 6 && method === "POST") {
|
|
231
|
+
operation = "approval.decide";
|
|
232
|
+
params.approvalRequestId = uuid(segments[4], "approvalRequestId");
|
|
233
|
+
const body = await jsonBody(request);
|
|
234
|
+
assertSession();
|
|
235
|
+
onlyKeys(body, ["commandId", "runId", "expectedRunVersion", "decision", "editArtifactRef", "editKind"]);
|
|
236
|
+
params.commandId = uuid(body.commandId, "commandId");
|
|
237
|
+
params.runId = uuid(body.runId, "runId");
|
|
238
|
+
if (!Number.isSafeInteger(body.expectedRunVersion) || Number(body.expectedRunVersion) < 0)
|
|
239
|
+
throw new HttpFailure(400, "expectedRunVersion must be a non-negative safe integer");
|
|
240
|
+
params.expectedRunVersion = body.expectedRunVersion;
|
|
241
|
+
if (typeof body.decision !== "string" || !["approve", "reject", "edit"].includes(body.decision))
|
|
242
|
+
throw new HttpFailure(400, "Invalid approval decision");
|
|
243
|
+
params.decision = body.decision;
|
|
244
|
+
if (body.decision === "edit") {
|
|
245
|
+
if (!features.approvalEditWithArtifactRef)
|
|
246
|
+
throw new HttpFailure(409, "Approval editing is unavailable on this control service");
|
|
247
|
+
if (!Value.Check(ArtifactRefSchema, body.editArtifactRef))
|
|
248
|
+
throw new HttpFailure(400, "edit requires a valid editArtifactRef");
|
|
249
|
+
params.editArtifactRef = body.editArtifactRef;
|
|
250
|
+
if (body.editKind !== undefined && body.editKind !== "output")
|
|
251
|
+
throw new HttpFailure(400, "Console edits support output only");
|
|
252
|
+
params.editKind = "output";
|
|
253
|
+
}
|
|
254
|
+
else if (body.editArtifactRef !== undefined || body.editKind !== undefined)
|
|
255
|
+
throw new HttpFailure(400, "editArtifactRef requires edit decision");
|
|
256
|
+
}
|
|
257
|
+
else
|
|
258
|
+
throw new HttpFailure(404, "Unknown console route");
|
|
259
|
+
}
|
|
260
|
+
const authorization = { operation, sessionId, ...(typeof params.projectId === "string" ? { projectId: params.projectId } : {}), ...(typeof params.controlDomainId === "string" ? { controlDomainId: params.controlDomainId } : {}) };
|
|
261
|
+
assertSession();
|
|
262
|
+
const client = await options.authorize({ ...authorization, purpose: "execute" });
|
|
263
|
+
assertSession();
|
|
264
|
+
// A mutation is dispatched once. Revocation while it executes suppresses its
|
|
265
|
+
// response; authoritative cancellation/commit fencing remains the owner's job.
|
|
266
|
+
let outcome;
|
|
267
|
+
try {
|
|
268
|
+
outcome = { ok: true, result: await client.call(operation, params) };
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
outcome = { ok: false, error };
|
|
272
|
+
}
|
|
273
|
+
assertSession();
|
|
274
|
+
await options.authorize({ ...authorization, purpose: "disclose" });
|
|
275
|
+
assertSession();
|
|
276
|
+
if (!outcome.ok)
|
|
277
|
+
throw outcome.error;
|
|
278
|
+
send(200, { result: outcome.result });
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
if (closed || request.aborted || request.socket.destroyed || response.destroyed || response.writableEnded) {
|
|
282
|
+
response.destroy();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// Do not disclose a delayed owner error to an expired or logged-out session.
|
|
286
|
+
try {
|
|
287
|
+
assertSession?.();
|
|
288
|
+
}
|
|
289
|
+
catch (sessionError) {
|
|
290
|
+
error = sessionError;
|
|
291
|
+
}
|
|
292
|
+
if (response.headersSent) {
|
|
293
|
+
response.destroy();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (error instanceof HttpFailure)
|
|
297
|
+
send(error.status, { error: { message: error.message } });
|
|
298
|
+
else if (error instanceof ControlError) {
|
|
299
|
+
const status = ["TF_POLICY_DENIED", "TF_AUTHORITY_REVOKED", "TF_CROSS_PRINCIPAL_COMMAND"].includes(error.code) ? 403 : ["TF_STALE_VERSION", "TF_IDEMPOTENCY_CONFLICT"].includes(error.code) ? 409 : error.code === "TF_FEATURE_REQUIRED" ? 503 : 500;
|
|
300
|
+
send(status, { error: error.toEnvelope() });
|
|
301
|
+
}
|
|
302
|
+
else
|
|
303
|
+
send(500, { error: { message: "Control service request failed" } });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return { url: origin, bootstrapToken, close: () => {
|
|
307
|
+
if (closing)
|
|
308
|
+
return closing;
|
|
309
|
+
closed = true;
|
|
310
|
+
sessions.clear();
|
|
311
|
+
for (const socket of sockets)
|
|
312
|
+
socket.destroy();
|
|
313
|
+
closing = new Promise((resolve) => server.close(() => resolve()));
|
|
314
|
+
return closing;
|
|
315
|
+
} };
|
|
316
|
+
}
|
|
317
|
+
//# sourceMappingURL=web-console.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-console.js","sourceRoot":"","sources":["../../../taskflow-control/src/web-console.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonrpc.d.ts","sourceRoot":"","sources":["../../src/mcp/jsonrpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,kDAAkD;AAClD,eAAO,MAAM,GAAG;aACf,WAAW,EAAE,CAAC,KAAK;aACnB,eAAe,EAAE,CAAC,KAAK;aACvB,gBAAgB,EAAE,CAAC,KAAK;aACxB,cAAc,EAAE,CAAC,KAAK;aACtB,cAAc,EAAE,CAAC,KAAK;aACtB,iBAAiB,EAAE,CAAC,KAAK;CAChB,CAAC;AAEX;;gFAEgF;AAChF,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,+EAA+E;AAC/E,qBAAa,QAAS,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAKxD;CACD;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IAC1B,mDAAmD;IACnD,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClC,wEAAwE;IACxE,MAAM,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAE9F;;;GAGG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,EAAE,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;CAAO,GACxE,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"jsonrpc.d.ts","sourceRoot":"","sources":["../../src/mcp/jsonrpc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,cAAc;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CACf;AAED,kDAAkD;AAClD,eAAO,MAAM,GAAG;aACf,WAAW,EAAE,CAAC,KAAK;aACnB,eAAe,EAAE,CAAC,KAAK;aACvB,gBAAgB,EAAE,CAAC,KAAK;aACxB,cAAc,EAAE,CAAC,KAAK;aACtB,cAAc,EAAE,CAAC,KAAK;aACtB,iBAAiB,EAAE,CAAC,KAAK;CAChB,CAAC;AAEX;;gFAEgF;AAChF,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,+EAA+E;AAC/E,qBAAa,QAAS,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,YAAY,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAKxD;CACD;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IAC1B,mDAAmD;IACnD,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAClC,wEAAwE;IACxE,MAAM,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;AAE9F;;;GAGG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACpC,EAAE,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;CAAO,GACxE,OAAO,CAAC,IAAI,CAAC,CA0Mf"}
|
package/dist/mcp/jsonrpc.js
CHANGED
|
@@ -72,15 +72,24 @@ export function serveStdio(handlers, io = {}) {
|
|
|
72
72
|
const trimmed = line.trim();
|
|
73
73
|
if (!trimmed)
|
|
74
74
|
return;
|
|
75
|
-
let
|
|
75
|
+
let parsed;
|
|
76
76
|
try {
|
|
77
|
-
|
|
77
|
+
parsed = JSON.parse(trimmed);
|
|
78
78
|
}
|
|
79
79
|
catch {
|
|
80
80
|
// No recoverable id — per JSON-RPC, a parse error uses id:null.
|
|
81
81
|
respondErr(null, { code: RPC.PARSE_ERROR, message: "Parse error" });
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
85
|
+
respondErr(null, { code: RPC.INVALID_REQUEST, message: "Invalid Request" });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const msg = parsed;
|
|
89
|
+
if (msg.id !== undefined && msg.id !== null && typeof msg.id !== "string" && typeof msg.id !== "number") {
|
|
90
|
+
respondErr(null, { code: RPC.INVALID_REQUEST, message: "Invalid Request" });
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
84
93
|
const isNotification = msg.id === undefined || msg.id === null;
|
|
85
94
|
const id = (msg.id ?? null);
|
|
86
95
|
if (msg.jsonrpc !== "2.0" || typeof msg.method !== "string") {
|
|
@@ -99,7 +108,7 @@ export function serveStdio(handlers, io = {}) {
|
|
|
99
108
|
}
|
|
100
109
|
return;
|
|
101
110
|
}
|
|
102
|
-
const handler = handlers[msg.method];
|
|
111
|
+
const handler = Object.hasOwn(handlers, msg.method) ? handlers[msg.method] : undefined;
|
|
103
112
|
if (!handler) {
|
|
104
113
|
// Unknown notifications are silently ignored (e.g. notifications/*).
|
|
105
114
|
if (!isNotification)
|