@agentproto/apps 0.10.0 → 0.11.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/dist/agents-overview/panel.mjs +18 -1
- package/dist/agents-overview/panel.mjs.map +1 -1
- package/dist/agents-overview.mjs +18 -1
- package/dist/agents-overview.mjs.map +1 -1
- package/dist/bureau-sessions/panel.mjs +18 -1
- package/dist/bureau-sessions/panel.mjs.map +1 -1
- package/dist/bureau-sessions.mjs +18 -1
- package/dist/bureau-sessions.mjs.map +1 -1
- package/dist/index.mjs +622 -288
- package/dist/index.mjs.map +1 -1
- package/dist/live-session/panel.mjs +18 -1
- package/dist/live-session/panel.mjs.map +1 -1
- package/dist/live-session.mjs +18 -1
- package/dist/live-session.mjs.map +1 -1
- package/dist/panel-bridge.d.ts +48 -0
- package/dist/panel-bridge.d.ts.map +1 -1
- package/dist/session-story/panel.mjs +18 -1
- package/dist/session-story/panel.mjs.map +1 -1
- package/dist/session-story.mjs +18 -1
- package/dist/session-story.mjs.map +1 -1
- package/dist/sessions-panel/panel.mjs +18 -1
- package/dist/sessions-panel/panel.mjs.map +1 -1
- package/dist/sessions-panel.mjs +18 -1
- package/dist/sessions-panel.mjs.map +1 -1
- package/dist/work-board/index.d.ts.map +1 -1
- package/dist/work-board/panel.d.ts +39 -6
- package/dist/work-board/panel.d.ts.map +1 -1
- package/dist/work-board/panel.generated.d.ts +13 -0
- package/dist/work-board/panel.generated.d.ts.map +1 -0
- package/dist/work-board/panel.mjs +469 -296
- package/dist/work-board/panel.mjs.map +1 -1
- package/dist/work-board.mjs +471 -298
- package/dist/work-board.mjs.map +1 -1
- package/package.json +12 -8
package/dist/work-board.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/panel-bridge.ts","../src/work-board/panel.ts","../src/work-board/index.ts"],"names":[],"mappings":"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","file":"work-board.mjs","sourcesContent":["/**\n * Shared MCP Apps client bridge for agentproto panels.\n *\n * One spec-correct copy of the JSON-RPC-over-postMessage bridge that every\n * ui:// panel embeds in its <script> block. Mirrors the behaviour of\n * @agstudio/mcp-apps' mcpAppClientScript() — which we cannot import here:\n * agentproto is a standalone pnpm workspace with no @agstudio/* dependency\n * (same isolation invariant as mcp-apps-adapter.ts).\n *\n * Handshake (MCP Apps ext spec 2026-01-26, ext-apps McpUiInitializeRequest):\n * params REQUIRE `appInfo` (Implementation: {name, version}),\n * `appCapabilities`, and `protocolVersion`. Sending `clientInfo` instead\n * of `appInfo` fails the host's schema validation — hosts either reply\n * with an error or silently drop the request, so the panel's initBridge()\n * promise never resolves and the panel renders blank while every\n * server-side surface (tools, resources) works. That exact bug shipped in\n * 4 copy-pasted bridges; this module is the single corrected source.\n *\n * The script defines, in panel scope:\n * post / rpcRequest / rpcNotify — raw JSON-RPC plumbing\n * onHostNotification(cb) — subscribe to host-pushed\n * notifications (method, params)\n * initBridge() — ui/initialize → ui/notifications/initialized\n * callTool(name, args) — tools/call + JSON text unwrap\n * getHostContext() / onHostContext(cb) — host context (displayMode, theme, …),\n * captured from the initialize result and\n * merged on ui/notifications/host-context-changed\n * requestDisplayMode(mode) — ui/request-display-mode (inline|fullscreen|pip)\n *\n * Display-mode toggle: the bridge also injects two floating buttons\n * (fullscreen + pip) in the top-right corner of every panel, shown only for\n * the modes the host actually advertises via hostContext.availableDisplayModes\n * — same pattern as guilde's canvas.app.ts canvasShellHtml() (the reference\n * implementation that renders correctly in Claude hosts). No auto-request:\n * panels stay inline until the user clicks.\n */\n\nexport function panelBridgeScript(appName: string): string {\n return `// ── MCP Apps bridge (shared: panel-bridge.ts) ──\n// JSON-RPC 2.0 over window.parent.postMessage · spec 2026-01-26\nvar _nextId = 1, _pending = {}, _notifyHandlers = [];\nvar _hostContext = null, _hostContextHandlers = [];\nfunction post(msg){ window.parent.postMessage(msg, '*'); }\nfunction getHostContext(){ return _hostContext; }\nfunction onHostContext(cb){\n _hostContextHandlers.push(cb);\n // Replay the last context so a late subscriber isn't stuck blind.\n if (_hostContext){ try { cb(_hostContext); } catch(_) {} }\n}\nfunction _setHostContext(ctx){\n if (!ctx || typeof ctx !== 'object') return;\n // ui/notifications/host-context-changed carries only the changed keys —\n // merge, matching the official ext-apps App behaviour.\n _hostContext = Object.assign({}, _hostContext || {}, ctx);\n for (var i = 0; i < _hostContextHandlers.length; i++){\n try { _hostContextHandlers[i](_hostContext); } catch(_) {}\n }\n}\nfunction rpcRequest(method, params){\n return new Promise(function(resolve, reject){\n var id = _nextId++;\n _pending[id] = {resolve: resolve, reject: reject};\n post({jsonrpc: '2.0', id: id, method: method, params: params || {}});\n });\n}\nfunction rpcNotify(method, params){ post({jsonrpc: '2.0', method: method, params: params || {}}); }\nfunction onHostNotification(cb){ _notifyHandlers.push(cb); }\nwindow.addEventListener('message', function(evt){\n var msg = evt.data;\n if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0') return;\n if (msg.id != null && msg.method == null){\n var p = _pending[msg.id];\n if (!p) return;\n delete _pending[msg.id];\n if (msg.error) p.reject(new Error(msg.error.message || ('rpc error ' + msg.error.code)));\n else p.resolve(msg.result);\n return;\n }\n if (msg.method){\n if (msg.method === 'ui/notifications/host-context-changed'){\n _setHostContext(msg.params || {});\n }\n for (var i = 0; i < _notifyHandlers.length; i++){\n try { _notifyHandlers[i](msg.method, msg.params || {}); } catch(_) {}\n }\n }\n});\nfunction initBridge(){\n return rpcRequest('ui/initialize', {\n appInfo: {name: ${JSON.stringify(appName)}, version: '0.1.0'},\n appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},\n protocolVersion: '2026-01-26'\n }).then(function(result){\n // The initialize result carries the initial hostContext (displayMode +\n // availableDisplayModes) — capture it before notifying the host.\n if (result && result.hostContext) _setHostContext(result.hostContext);\n rpcNotify('ui/notifications/initialized', {});\n });\n}\nfunction requestDisplayMode(mode){\n return rpcRequest('ui/request-display-mode', {mode: mode});\n}\nfunction callTool(name, args){\n return rpcRequest('tools/call', {name: name, arguments: args || {}}).then(function(result){\n if (result.isError){\n var e = (result.content && result.content[0] && result.content[0].text) || 'tool error';\n throw new Error(e);\n }\n var text = (result.content && result.content[0] && result.content[0].text) || '{}';\n return JSON.parse(text);\n });\n}\n\n// ── Display-mode toggle buttons (NO auto-request) ──────────────────────\n// Injected by the shared bridge so every panel gets them without touching\n// its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the\n// panel stays inline by default; the user expands on demand. Buttons only\n// appear for modes the host advertises in hostContext.availableDisplayModes.\n(function(){\n function mount(){\n var style = document.createElement('style');\n style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'\n + 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'\n + 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'\n + 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'\n + '#dm{right:8px}#pin{right:118px}'\n + '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'\n + '@media (prefers-color-scheme:dark){'\n + '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'\n + '#dm:hover,#pin:hover{background:#333;border-color:#777}}';\n document.head.appendChild(style);\n\n var btn = document.createElement('button');\n btn.id = 'dm'; btn.type = 'button'; btn.title = \"Basculer l'affichage\";\n var pin = document.createElement('button');\n pin.id = 'pin'; pin.type = 'button'; pin.title = 'Épingler sur le côté (pip)';\n document.body.appendChild(pin);\n document.body.appendChild(btn);\n\n function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }\n\n // Re-sync button visibility + label from the current host context.\n function syncBtn(ctx){\n ctx = ctx || {};\n var avail = ctx.availableDisplayModes;\n // Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)\n console.log('[mcp-app] displayMode=', ctx.displayMode,\n 'availableDisplayModes=', avail);\n\n // Fullscreen toggle button.\n if (has(avail, 'fullscreen')){\n btn.style.display = 'block';\n btn.textContent = (ctx.displayMode === 'fullscreen') ? '⤡ Réduire' : '⤢ Agrandir';\n } else { btn.style.display = 'none'; }\n\n // Dedicated pip (\"pinned on side\") button — only if the host advertises pip.\n if (has(avail, 'pip')){\n pin.style.display = 'block';\n pin.textContent = (ctx.displayMode === 'pip') ? '⤡ Détacher' : '📌 Épingler';\n } else { pin.style.display = 'none'; }\n }\n\n onHostContext(syncBtn);\n\n btn.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');\n requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});\n });\n\n pin.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});\n });\n }\n if (document.body) mount();\n else document.addEventListener('DOMContentLoaded', mount);\n})();`\n}\n","/**\n * HTML bundle for the agentproto work-board panel — the `WORK_BOARD_HTML`\n * served as the `ui://agentproto_work_board/view` resource by\n * `registerMcpApps` (see ../mcp-app-types.ts and runtime's\n * mcp-apps-adapter.ts). Mounted via the `agentproto_work_board` tool defined\n * in ./index.ts.\n *\n * A kanban over the daemon's Task ledger (`packages/runtime/src/\n * task-ledger.ts`) — the one writable multi-party entity the daemon exposes\n * (Activity is a read-only projection; a session has a lifecycle, not an\n * intention). Columns are the ledger's own status enum, folding `cancelled`\n * into the `failed` column (tagged distinctly — see `columnOf` below)\n * rather than adding a fifth column for a status v1 treats as terminal\n * scrap. The board/swimlane selector is the ledger's own board id\n * (`tree:<rootSessionId>` or `ws:<slug>`) — always shown verbatim, never\n * hidden behind a friendly name, per the ledger's scoping model.\n *\n * Protocol: MCP Apps ext spec 2026-01-26\n * – Bridge: JSON-RPC 2.0 over window.parent.postMessage\n * – Handshake: ui/initialize → host result → ui/notifications/initialized\n * – Data: tools/call → task_list (`full: true` — the compact projection\n * drops `verification`, which the verification tell needs) on a ~4 s poll\n * – Actions: tools/call → task_claim / task_update / task_create\n *\n * Read-only-safe first: no drag-and-drop. Each card gets the explicit status\n * actions valid from its current state (Claim/Start/Done/Fail/Release/\n * Cancel/Reopen) — a correct read-only-plus-explicit-actions board beats a\n * half-wired drag, per the brief.\n *\n * The verification tell (never render an unverified done as gate-passed):\n * - `verification.kind === \"gate\"` → solid green \"✓ gate\"\n * - `verification.kind === \"self-report\"` → amber outline \"self-report\"\n * - `verification.kind === \"human\"` → blue \"human\"\n * - a declared `verify` with no verification yet → grey \"gated\" tag\n * An owner-less task always reads \"Unclaimed\", never \"pending\" (that word is\n * reserved for the Activity vocabulary's \"blocked\" meaning elsewhere).\n */\n\nimport { panelBridgeScript } from \"../panel-bridge.js\"\n\nexport const WORK_BOARD_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>agentproto work board</title>\n<style>\n*{box-sizing:border-box;margin:0;padding:0}\n:root{\n --bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;\n --text:#e6edf3;--text2:#8b949e;\n --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;\n}\nhtml,body{height:100%;font-family:Menlo,Monaco,'Courier New',monospace;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}\n#app{display:flex;flex-direction:column;height:100%}\n#toolbar{padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2);flex-shrink:0}\n#board-label{font-size:11px;color:var(--text2)}\n#board-id{font-weight:600;color:var(--text);font-size:12px}\n#board-input{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:4px 8px;border-radius:4px;font-size:12px;font-family:inherit;width:220px}\n.tbtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:4px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}\n.tbtn:hover{background:var(--border)}\n#new-title{flex:1;min-width:120px;background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:4px 8px;border-radius:4px;font-size:12px;font-family:inherit}\n#columns{flex:1;display:flex;overflow-x:auto;overflow-y:hidden}\n.col{width:260px;min-width:220px;flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid var(--border)}\n.col-hdr{padding:8px 10px;border-bottom:1px solid var(--border);background:var(--bg2);font-size:11px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.05em;display:flex;justify-content:space-between}\n.col-body{flex:1;overflow-y:auto;padding:8px;display:flex;flex-direction:column;gap:8px}\n.card{background:var(--bg2);border:1px solid var(--border);border-radius:6px;padding:8px 10px}\n.c-title{font-size:12px;font-weight:500;margin-bottom:4px;word-break:break-word}\n.c-id{font-size:10px;color:var(--text2);margin-bottom:6px}\n.c-row{display:flex;flex-wrap:wrap;gap:4px;align-items:center;margin-bottom:6px}\n.tag{display:inline-block;padding:1px 6px;border-radius:10px;font-size:10px;font-weight:600;white-space:nowrap}\n.t-unclaimed{background:rgba(139,148,158,.15);color:var(--text2);border:1px dashed var(--text2)}\n.t-owner{background:rgba(88,166,255,.15);color:var(--blue)}\n.t-gate{background:rgba(63,185,80,.18);color:var(--green)}\n.t-self{background:rgba(210,153,34,.18);color:var(--yellow)}\n.t-human{background:rgba(88,166,255,.18);color:var(--blue)}\n.t-gated{background:rgba(139,148,158,.12);color:var(--text2)}\n.t-cancelled{background:rgba(139,148,158,.12);color:var(--text2)}\n.c-err{font-size:10px;color:var(--red);margin-bottom:6px;word-break:break-word}\n.c-actions{display:flex;flex-wrap:wrap;gap:4px}\n.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:3px 8px;border-radius:4px;cursor:pointer;font-size:11px;font-family:inherit}\n.abtn:hover{background:var(--border)}\n.abtn.danger{border-color:var(--red);color:var(--red)}\n.abtn.primary{border-color:var(--green);color:var(--green)}\n#statusbar{padding:4px 12px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n.empty{padding:16px 8px;color:var(--text2);font-size:11px;text-align:center}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"toolbar\">\n <span id=\"board-label\">Board:</span>\n <span id=\"board-id\">—</span>\n <input id=\"board-input\" type=\"text\" placeholder=\"tree:<sessionId> or ws:<slug>\">\n <button class=\"tbtn\" id=\"go-btn\">Go</button>\n <button class=\"tbtn\" id=\"refresh-btn\" title=\"Refresh\">↻</button>\n <input id=\"new-title\" type=\"text\" placeholder=\"New task title…\">\n <button class=\"tbtn\" id=\"add-btn\">+ Add</button>\n </div>\n <div id=\"columns\"></div>\n <div id=\"statusbar\">Connecting to bridge…</div>\n</div>\n<script>\n${panelBridgeScript(\"agentproto-work-board-panel\")}\n\n// ============================================================\n// State\n// ============================================================\n\nvar COLUMNS = [\n {key: 'pending', label: 'Pending'},\n {key: 'in_progress', label: 'In Progress'},\n {key: 'done', label: 'Done'},\n {key: 'failed', label: 'Failed'}\n];\n\nvar boardId = null;\nvar tasks = [];\nvar pollTimer = null;\nvar pollActive = false;\n\nfunction setStatus(msg) {\n document.getElementById('statusbar').textContent = msg;\n}\n\nfunction esc(s) {\n return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\n// A cancelled task folds into the Failed column, tagged distinctly — v1\n// treats both as terminal scrap, and a fifth column for a rarely-used\n// status isn't worth the width.\nfunction columnOf(status) {\n if (status === 'cancelled') return 'failed';\n return status;\n}\n\n// ============================================================\n// Verification tell — never render an unverified done as gate-passed.\n// ============================================================\n\nfunction verificationTag(t) {\n var v = t.verification;\n if (v) {\n if (v.kind === 'gate') return '<span class=\"tag t-gate\">✓ gate</span>';\n if (v.kind === 'self-report') return '<span class=\"tag t-self\">self-report</span>';\n if (v.kind === 'human') return '<span class=\"tag t-human\">human</span>';\n }\n if (t.status === 'in_progress' && t.verify) {\n return '<span class=\"tag t-gated\">gated</span>';\n }\n return '';\n}\n\nfunction ownerTag(t) {\n if (!t.owner) return '<span class=\"tag t-unclaimed\">Unclaimed</span>';\n return '<span class=\"tag t-owner\">' + esc(t.owner) + '</span>';\n}\n\n// ============================================================\n// Card actions — explicit status moves, no drag-and-drop (read-only-safe\n// first: see module docblock).\n// ============================================================\n\nfunction actionsFor(t) {\n var a = [];\n if (t.status === 'pending') {\n if (!t.owner) a.push({label: 'Claim', action: 'claim', cls: 'primary'});\n else a.push({label: 'Start', action: 'start', cls: 'primary'});\n a.push({label: 'Cancel', action: 'cancel', cls: 'danger'});\n } else if (t.status === 'in_progress') {\n a.push({label: 'Done', action: 'done', cls: 'primary'});\n a.push({label: 'Fail', action: 'fail', cls: 'danger'});\n a.push({label: 'Release', action: 'release', cls: ''});\n a.push({label: 'Cancel', action: 'cancel', cls: 'danger'});\n } else if (t.status === 'done') {\n a.push({label: 'Reopen', action: 'reopen', cls: ''});\n }\n return a;\n}\n\nfunction applyAction(taskId, action) {\n var t = null;\n for (var i = 0; i < tasks.length; i++) {\n if (tasks[i].taskId === taskId) { t = tasks[i]; break; }\n }\n if (!t) return;\n var p;\n if (action === 'claim') {\n p = callTool('task_claim', {taskId: t.taskId, rev: t.rev});\n } else if (action === 'start') {\n p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'in_progress'});\n } else if (action === 'done') {\n p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'done'});\n } else if (action === 'fail') {\n p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'failed'});\n } else if (action === 'release') {\n p = callTool('task_update', {taskId: t.taskId, rev: t.rev, owner: null});\n } else if (action === 'cancel') {\n p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'cancelled'});\n } else if (action === 'reopen') {\n p = callTool('task_update', {taskId: t.taskId, rev: t.rev, status: 'pending'});\n } else {\n return;\n }\n p.then(function (result) {\n if (result && result.conflict) {\n setStatus('Someone else moved \"' + t.title + '\" first — refreshed.');\n } else if (result && result.error) {\n setStatus('Action failed: ' + result.error);\n } else {\n setStatus('Updated \"' + t.title + '\".');\n }\n return loadBoard();\n }).catch(function (e) {\n setStatus('Action failed: ' + e.message);\n });\n}\n\n// ============================================================\n// Render\n// ============================================================\n\nfunction renderCard(t) {\n var html = '<div class=\"card\" data-taskid=\"' + esc(t.taskId) + '\">';\n html += '<div class=\"c-title\">' + esc(t.title) + '</div>';\n html += '<div class=\"c-id\">#' + esc(t.taskId) + '</div>';\n html += '<div class=\"c-row\">' + ownerTag(t) + verificationTag(t);\n if (t.status === 'cancelled') html += '<span class=\"tag t-cancelled\">cancelled</span>';\n html += '</div>';\n if (t.lastVerifyError) {\n html += '<div class=\"c-err\">verify failed: ' + esc(t.lastVerifyError) + '</div>';\n }\n var actions = actionsFor(t);\n if (actions.length > 0) {\n html += '<div class=\"c-actions\">';\n for (var i = 0; i < actions.length; i++) {\n var a = actions[i];\n html += '<button class=\"abtn ' + a.cls + '\" data-taskid=\"' + esc(t.taskId) + '\" data-action=\"' + a.action + '\">' + a.label + '</button>';\n }\n html += '</div>';\n }\n html += '</div>';\n return html;\n}\n\nfunction render() {\n document.getElementById('board-id').textContent = boardId || '—';\n if (!document.getElementById('board-input').value) {\n document.getElementById('board-input').value = boardId || '';\n }\n var el = document.getElementById('columns');\n var html = '';\n for (var c = 0; c < COLUMNS.length; c++) {\n var col = COLUMNS[c];\n var rows = tasks.filter(function (t) { return columnOf(t.status) === col.key; });\n html += '<div class=\"col\"><div class=\"col-hdr\"><span>' + col.label + '</span><span>' + rows.length + '</span></div><div class=\"col-body\">';\n if (rows.length === 0) {\n html += '<div class=\"empty\">No tasks</div>';\n } else {\n for (var i = 0; i < rows.length; i++) html += renderCard(rows[i]);\n }\n html += '</div></div>';\n }\n el.innerHTML = html;\n}\n\n// ============================================================\n// Data\n// ============================================================\n\nfunction loadBoard() {\n var args = {includeClosed: true, full: true};\n if (boardId) args.boardId = boardId;\n return callTool('task_list', args).then(function (data) {\n boardId = data.boardId || boardId;\n tasks = data.tasks || [];\n render();\n setStatus(tasks.length + ' task' + (tasks.length === 1 ? '' : 's') + ' · ' + new Date().toLocaleTimeString());\n }).catch(function (e) {\n setStatus('Error: ' + e.message);\n });\n}\n\nfunction doPoll() {\n if (pollActive) return;\n pollActive = true;\n loadBoard().then(function () {\n pollActive = false;\n pollTimer = setTimeout(doPoll, 4000);\n }).catch(function () {\n pollActive = false;\n pollTimer = setTimeout(doPoll, 4000);\n });\n}\n\n// ============================================================\n// Toolbar wiring\n// ============================================================\n\ndocument.getElementById('columns').addEventListener('click', function (evt) {\n var btn = evt.target.closest ? evt.target.closest('button[data-action]') : null;\n if (!btn) return;\n applyAction(btn.getAttribute('data-taskid'), btn.getAttribute('data-action'));\n});\n\ndocument.getElementById('refresh-btn').addEventListener('click', function () {\n loadBoard();\n});\n\ndocument.getElementById('go-btn').addEventListener('click', function () {\n var v = document.getElementById('board-input').value.trim();\n if (!v) return;\n boardId = v;\n loadBoard();\n});\n\ndocument.getElementById('add-btn').addEventListener('click', function () {\n var input = document.getElementById('new-title');\n var title = input.value.trim();\n if (!title) return;\n var args = {title: title};\n if (boardId) args.boardId = boardId;\n callTool('task_create', args).then(function (result) {\n if (result && result.error) {\n setStatus('Create failed: ' + result.error);\n return;\n }\n input.value = '';\n setStatus('Created \"' + title + '\".');\n return loadBoard();\n }).catch(function (e) {\n setStatus('Create failed: ' + e.message);\n });\n});\n\n// ============================================================\n// Boot\n// ============================================================\n\ninitBridge().then(function () {\n return loadBoard();\n}).then(function () {\n pollTimer = setTimeout(doPoll, 4000);\n}).catch(function (e) {\n setStatus('Bridge error: ' + e.message);\n});\n</script>\n</body>\n</html>`\n","/**\n * McpApp definition for the agentproto work-board panel — a kanban over the\n * daemon's Task ledger (`packages/runtime/src/task-ledger.ts`), the ONLY\n * writable multi-party entity the daemon exposes (Activity is a read-only\n * projection recomputed on every `list()`; a session has a lifecycle, not an\n * intention).\n *\n * Uses the AgnoMcpApp contract (a local McpApp-compatible shape that does\n * NOT depend on @agstudio/mcp-apps or Mastra), same as sessions-panel /\n * session-chat — the host (@agentproto/runtime) wires this factory's output\n * via `registerMcpApps` from its mcp-apps-adapter.ts at boot time, with the\n * mounted list assembled in runtime's builtin-apps.ts.\n *\n * Protocol flow:\n * 1. Tool `agentproto_work_board` is called by the host with an optional\n * `boardId`.\n * 2. execute() answers an initial `{boardId, tasks}` snapshot off the\n * ledger — resolved to the caller's default board when `boardId` is\n * omitted (a session's `tree:<root>`, the operator's `ws:<slug>`).\n * 3. The HTML panel opens a JSON-RPC bridge (postMessage) and polls\n * `task_list` (`full: true` — the compact projection drops\n * `verification`) every ~4 s; writes go through `task_claim` /\n * `task_update` / `task_create`, the same verbs any other caller uses —\n * no second write path.\n *\n * This module also exports `workBoardApp`, a real `defineApp()` `AppHandle`\n * (`agents: []`, UI-only) — the catalog/emit/`app_install` path. It's\n * separate from `makeWorkBoardApp` above, which the daemon mounts directly\n * at boot: that factory closes over the LIVE ledger read, something a\n * static emitted `ui.html` snapshot can't carry.\n */\n\nimport { z } from \"zod\"\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { WORK_BOARD_HTML } from \"./panel.js\"\nimport type { AgnoMcpApp } from \"../mcp-app-types.js\"\n\nexport const workBoardInputSchema = z.object({\n boardId: z\n .string()\n .optional()\n .describe(\n \"Board to open — `tree:<rootSessionId>` (a supervisor and its \" +\n \"executors) or `ws:<slug>` (an operator workspace). Omit → the \" +\n \"caller's default board.\",\n ),\n})\n\nexport type WorkBoardInput = z.infer<typeof workBoardInputSchema>\n\nexport interface WorkBoardOutput<TTask = unknown> {\n /** Always echoed back — the ledger's board ids are the swimlane\n * selector, and must be visible rather than hidden behind a friendly\n * name (it's how the operator knows which mission they're looking at). */\n boardId: string\n tasks: TTask[]\n}\n\n/**\n * Generic over the host's own task-record shape — this package doesn't own\n * that type (it lives in @agentproto/runtime's task ledger), so the factory\n * stays agnostic to it and the host supplies the concrete type as a type\n * argument when it wires the app up (mirrors SessionsPanelOps<TSession>).\n */\nexport interface WorkBoardOps<TTask = unknown> {\n /** Full (unprojected) records for a board — NOT `task_list`'s default\n * compact projection, since the verification tell needs `verification`\n * (self-report vs gate vs human), which compact mode omits. Omit\n * `boardId` to resolve the caller's default board. */\n listTasks(boardId?: string): WorkBoardOutput<TTask>\n}\n\nexport type { AgnoMcpApp }\n\n/**\n * Factory: close over the ledger read so execute() doesn't need an\n * AppContext (agentproto has no userId/guildId concept) — mirrors\n * makeSessionsPanelApp.\n */\nexport function makeWorkBoardApp<TTask = unknown>(\n ops: WorkBoardOps<TTask>,\n): AgnoMcpApp<WorkBoardInput, WorkBoardOutput<TTask>> {\n return {\n id: \"agentproto_work_board\",\n title: \"Work Board\",\n description:\n \"Open the agentproto work board — a kanban over the daemon's Task \" +\n \"ledger (columns: pending/in_progress/done/failed, cancelled folded \" +\n \"into failed). Swimlane is the ledger's own board id (`tree:<root>` \" +\n \"or `ws:<slug>`). Each card shows its owner (or \\\"Unclaimed\\\") and the \" +\n \"verification tell — a gate-passed done reads visually distinct from \" +\n \"a self-reported one. Pass `boardId` to open a specific board.\",\n inputSchema: workBoardInputSchema,\n execute: async input => ops.listTasks(input.boardId),\n html: WORK_BOARD_HTML,\n }\n}\n\nexport const workBoardApp: AppHandle = defineApp({\n id: \"@agentproto/work-board\",\n name: \"Work Board\",\n description:\n \"Open the agentproto work board — a kanban over the daemon's Task ledger, scoped by board id \" +\n \"(`tree:<rootSessionId>` or `ws:<slug>`), with an explicit status action per card.\",\n agents: [],\n ui: {\n html: WORK_BOARD_HTML,\n title: \"Work Board\",\n tools: [\"task_list\", \"task_claim\", \"task_update\", \"task_create\"],\n },\n})\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/work-board/panel.generated.ts","../src/work-board/panel.ts","../src/work-board/index.ts"],"names":[],"mappings":";;;;;;;;;;AAYO,IAAM,yBAAA,GAA4B,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;;;ACoDlC,IAAM,iBAAA,GAAoB,wBAAA;AAI1B,IAAM,kBAAA,GAAqB,uBAAA;AAM3B,IAAM,mBAAA,GAAsB;AAAA,EACjC,WAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA;AAEO,IAAM,eAAA,GAAkB,yBAAA;;;ACvCxB,IAAM,oBAAA,GAAuB,EAAE,MAAA,CAAO;AAAA,EAC3C,OAAA,EAAS,CAAA,CACN,MAAA,EAAO,CACP,UAAS,CACT,QAAA;AAAA,IACC;AAAA;AAIN,CAAC;AAiCM,SAAS,iBACd,GAAA,EACoD;AACpD,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,kBAAA;AAAA,IACJ,KAAA,EAAO,YAAA;AAAA,IACP,WAAA,EACE,0ZAAA;AAAA,IAMF,WAAA,EAAa,oBAAA;AAAA,IACb,SAAS,OAAM,KAAA,KAAS,GAAA,CAAI,SAAA,CAAU,MAAM,OAAO,CAAA;AAAA,IACnD,IAAA,EAAM;AAAA,GACR;AACF;AAEO,IAAM,eAA0B,SAAA,CAAU;AAAA,EAC/C,EAAA,EAAI,iBAAA;AAAA,EACJ,IAAA,EAAM,YAAA;AAAA,EACN,WAAA,EACE,oLAAA;AAAA,EAEF,QAAQ,EAAC;AAAA,EACT,EAAA,EAAI;AAAA,IACF,IAAA,EAAM,eAAA;AAAA,IACN,KAAA,EAAO,YAAA;AAAA,IACP,KAAA,EAAO,CAAC,GAAG,mBAAmB;AAAA;AAElC,CAAC","file":"work-board.mjs","sourcesContent":["/**\n * @generated — DO NOT EDIT BY HAND.\n *\n * Built from src/work-board/ui/** (Vite + vite-plugin-singlefile) by\n * scripts/build-work-board-ui.mjs. Edit the Vite sources and regenerate:\n *\n * pnpm --filter @agentproto/apps run build:ui\n *\n * `pnpm --filter @agentproto/apps run build:ui:check` (wired into CI) fails\n * if this file drifts from what the Vite sources actually produce.\n */\n\nexport const WORK_BOARD_HTML_GENERATED = \"<!DOCTYPE html>\\n<html lang=\\\"en\\\">\\n<head>\\n<meta charset=\\\"UTF-8\\\" />\\n<meta name=\\\"viewport\\\" content=\\\"width=device-width, initial-scale=1.0\\\" />\\n<title>agentproto work board</title>\\n<!-- PANEL_BRIDGE: replaced with the inline panelBridgeScript() output by\\n vite.config.ts's injectPanelBridge plugin — a classic (non-module)\\n script so its `initBridge`/`callTool` land as globals before the\\n deferred module script below runs. -->\\n<script>// ── MCP Apps bridge (shared: panel-bridge.ts) ──\\n// JSON-RPC 2.0 over window.parent.postMessage · spec 2026-01-26\\nvar _nextId = 1, _pending = {}, _notifyHandlers = [];\\nvar _hostContext = null, _hostContextHandlers = [];\\nvar _standaloneApp = null;\\nfunction _isStandalone(){\\n return window.parent === window && !!window.McpApp && typeof window.McpApp.connect === 'function';\\n}\\nfunction post(msg){ window.parent.postMessage(msg, '*'); }\\nfunction getHostContext(){ return _hostContext; }\\nfunction onHostContext(cb){\\n _hostContextHandlers.push(cb);\\n // Replay the last context so a late subscriber isn't stuck blind.\\n if (_hostContext){ try { cb(_hostContext); } catch(_) {} }\\n}\\nfunction _setHostContext(ctx){\\n if (!ctx || typeof ctx !== 'object') return;\\n // ui/notifications/host-context-changed carries only the changed keys —\\n // merge, matching the official ext-apps App behaviour.\\n _hostContext = Object.assign({}, _hostContext || {}, ctx);\\n for (var i = 0; i < _hostContextHandlers.length; i++){\\n try { _hostContextHandlers[i](_hostContext); } catch(_) {}\\n }\\n}\\nfunction rpcRequest(method, params){\\n return new Promise(function(resolve, reject){\\n var id = _nextId++;\\n _pending[id] = {resolve: resolve, reject: reject};\\n post({jsonrpc: '2.0', id: id, method: method, params: params || {}});\\n });\\n}\\nfunction rpcNotify(method, params){ post({jsonrpc: '2.0', method: method, params: params || {}}); }\\nfunction onHostNotification(cb){ _notifyHandlers.push(cb); }\\nwindow.addEventListener('message', function(evt){\\n var msg = evt.data;\\n if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0') return;\\n if (msg.id != null && msg.method == null){\\n var p = _pending[msg.id];\\n if (!p) return;\\n delete _pending[msg.id];\\n if (msg.error) p.reject(new Error(msg.error.message || ('rpc error ' + msg.error.code)));\\n else p.resolve(msg.result);\\n return;\\n }\\n if (msg.method){\\n if (msg.method === 'ui/notifications/host-context-changed'){\\n _setHostContext(msg.params || {});\\n }\\n for (var i = 0; i < _notifyHandlers.length; i++){\\n try { _notifyHandlers[i](msg.method, msg.params || {}); } catch(_) {}\\n }\\n }\\n});\\nfunction initBridge(){\\n if (_isStandalone()){\\n return window.McpApp.connect().then(function(conn){\\n _standaloneApp = conn;\\n // No host to advertise a hostContext — default to inline with no\\n // other modes available, which keeps the #dm/#pin toggle buttons\\n // hidden (they only show for modes hostContext.availableDisplayModes\\n // lists) instead of rendering two buttons with nothing to switch to.\\n _setHostContext({displayMode: 'inline', availableDisplayModes: []});\\n });\\n }\\n return rpcRequest('ui/initialize', {\\n appInfo: {name: \\\"agentproto-work-board-panel\\\", version: '0.1.0'},\\n appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},\\n protocolVersion: '2026-01-26'\\n }).then(function(result){\\n // The initialize result carries the initial hostContext (displayMode +\\n // availableDisplayModes) — capture it before notifying the host.\\n if (result && result.hostContext) _setHostContext(result.hostContext);\\n rpcNotify('ui/notifications/initialized', {});\\n });\\n}\\nfunction requestDisplayMode(mode){\\n return rpcRequest('ui/request-display-mode', {mode: mode});\\n}\\nfunction callTool(name, args){\\n var raw = _standaloneApp\\n ? _standaloneApp.callTool(name, args || {})\\n : rpcRequest('tools/call', {name: name, arguments: args || {}});\\n return raw.then(function(result){\\n if (result.isError){\\n var e = (result.content && result.content[0] && result.content[0].text) || 'tool error';\\n throw new Error(e);\\n }\\n var text = (result.content && result.content[0] && result.content[0].text) || '{}';\\n return JSON.parse(text);\\n });\\n}\\n\\n// ── Display-mode toggle buttons (NO auto-request) ──────────────────────\\n// Injected by the shared bridge so every panel gets them without touching\\n// its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the\\n// panel stays inline by default; the user expands on demand. Buttons only\\n// appear for modes the host advertises in hostContext.availableDisplayModes.\\n(function(){\\n function mount(){\\n var style = document.createElement('style');\\n style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'\\n + 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'\\n + 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'\\n + 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'\\n + '#dm{right:8px}#pin{right:118px}'\\n + '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'\\n + '@media (prefers-color-scheme:dark){'\\n + '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'\\n + '#dm:hover,#pin:hover{background:#333;border-color:#777}}';\\n document.head.appendChild(style);\\n\\n var btn = document.createElement('button');\\n btn.id = 'dm'; btn.type = 'button'; btn.title = \\\"Basculer l'affichage\\\";\\n var pin = document.createElement('button');\\n pin.id = 'pin'; pin.type = 'button'; pin.title = 'Épingler sur le côté (pip)';\\n document.body.appendChild(pin);\\n document.body.appendChild(btn);\\n\\n function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }\\n\\n // Re-sync button visibility + label from the current host context.\\n function syncBtn(ctx){\\n ctx = ctx || {};\\n var avail = ctx.availableDisplayModes;\\n // Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)\\n console.log('[mcp-app] displayMode=', ctx.displayMode,\\n 'availableDisplayModes=', avail);\\n\\n // Fullscreen toggle button.\\n if (has(avail, 'fullscreen')){\\n btn.style.display = 'block';\\n btn.textContent = (ctx.displayMode === 'fullscreen') ? '⤡ Réduire' : '⤢ Agrandir';\\n } else { btn.style.display = 'none'; }\\n\\n // Dedicated pip (\\\"pinned on side\\\") button — only if the host advertises pip.\\n if (has(avail, 'pip')){\\n pin.style.display = 'block';\\n pin.textContent = (ctx.displayMode === 'pip') ? '⤡ Détacher' : '📌 Épingler';\\n } else { pin.style.display = 'none'; }\\n }\\n\\n onHostContext(syncBtn);\\n\\n btn.addEventListener('click', function(){\\n var ctx = getHostContext() || {};\\n var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');\\n requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});\\n });\\n\\n pin.addEventListener('click', function(){\\n var ctx = getHostContext() || {};\\n requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});\\n });\\n }\\n if (document.body) mount();\\n else document.addEventListener('DOMContentLoaded', mount);\\n})();</script>\\n <script>(function() {\\n \\\"use strict\\\";\\n const COLUMN_STATUSES = [\\\"pending\\\", \\\"in_progress\\\", \\\"done\\\", \\\"failed\\\"];\\n const COLUMN_LABELS = {\\n pending: \\\"Pending\\\",\\n in_progress: \\\"In Progress\\\",\\n done: \\\"Done\\\",\\n failed: \\\"Failed\\\"\\n };\\n function esc(value) {\\n return value.replace(/&/g, \\\"&\\\").replace(/</g, \\\"<\\\").replace(/>/g, \\\">\\\").replace(/\\\"/g, \\\""\\\");\\n }\\n function columnOf(status) {\\n return status === \\\"cancelled\\\" ? \\\"failed\\\" : status;\\n }\\n function verificationTag(task) {\\n const v = task.verification;\\n if (v) {\\n if (v.kind === \\\"gate\\\") return '<span class=\\\"tag t-gate\\\">✓ gate</span>';\\n if (v.kind === \\\"self-report\\\") return '<span class=\\\"tag t-self\\\">self-report</span>';\\n if (v.kind === \\\"human\\\") return '<span class=\\\"tag t-human\\\">human</span>';\\n }\\n if (task.status === \\\"in_progress\\\" && task.verify) {\\n return '<span class=\\\"tag t-gated\\\">gated</span>';\\n }\\n return \\\"\\\";\\n }\\n function ownerTag(task) {\\n if (!task.owner) return '<span class=\\\"tag t-unclaimed\\\">Unclaimed</span>';\\n return `<span class=\\\"tag t-owner\\\">${esc(task.owner)}</span>`;\\n }\\n function actionsFor(task) {\\n if (task.status === \\\"pending\\\") {\\n return [\\n task.owner ? { label: \\\"Start\\\", action: \\\"start\\\", cls: \\\"primary\\\" } : { label: \\\"Claim\\\", action: \\\"claim\\\", cls: \\\"primary\\\" },\\n { label: \\\"Cancel\\\", action: \\\"cancel\\\", cls: \\\"danger\\\" }\\n ];\\n }\\n if (task.status === \\\"in_progress\\\") {\\n return [\\n { label: \\\"Done\\\", action: \\\"done\\\", cls: \\\"primary\\\" },\\n { label: \\\"Fail\\\", action: \\\"fail\\\", cls: \\\"danger\\\" },\\n { label: \\\"Release\\\", action: \\\"release\\\", cls: \\\"\\\" },\\n { label: \\\"Cancel\\\", action: \\\"cancel\\\", cls: \\\"danger\\\" }\\n ];\\n }\\n if (task.status === \\\"done\\\") {\\n return [{ label: \\\"Reopen\\\", action: \\\"reopen\\\", cls: \\\"\\\" }];\\n }\\n return [];\\n }\\n function renderCard(task) {\\n let html = `<div class=\\\"card\\\" data-taskid=\\\"${esc(task.taskId)}\\\">`;\\n html += `<div class=\\\"c-title\\\">${esc(task.title)}</div>`;\\n html += `<div class=\\\"c-id\\\">#${esc(task.taskId)}</div>`;\\n html += `<div class=\\\"c-row\\\">${ownerTag(task)}${verificationTag(task)}`;\\n if (task.status === \\\"cancelled\\\") html += '<span class=\\\"tag t-cancelled\\\">cancelled</span>';\\n html += \\\"</div>\\\";\\n if (task.lastVerifyError) {\\n html += `<div class=\\\"c-err\\\">verify failed: ${esc(task.lastVerifyError)}</div>`;\\n }\\n const actions = actionsFor(task);\\n if (actions.length > 0) {\\n html += '<div class=\\\"c-actions\\\">';\\n for (const a of actions) {\\n html += `<button class=\\\"abtn ${a.cls}\\\" data-taskid=\\\"${esc(task.taskId)}\\\" data-action=\\\"${a.action}\\\">${a.label}</button>`;\\n }\\n html += \\\"</div>\\\";\\n }\\n html += \\\"</div>\\\";\\n return html;\\n }\\n function renderColumns(tasks2) {\\n let html = \\\"\\\";\\n for (const status of COLUMN_STATUSES) {\\n const rows = tasks2.filter((t) => columnOf(t.status) === status);\\n html += `<div class=\\\"col\\\"><div class=\\\"col-hdr\\\"><span>${COLUMN_LABELS[status]}</span><span>${rows.length}</span></div><div class=\\\"col-body\\\">`;\\n html += rows.length === 0 ? '<div class=\\\"empty\\\">No tasks</div>' : rows.map(renderCard).join(\\\"\\\");\\n html += \\\"</div></div>\\\";\\n }\\n return html;\\n }\\n function getEl(id) {\\n const el = document.getElementById(id);\\n if (!el) throw new Error(`work board: missing #${id}`);\\n return el;\\n }\\n function getInput(id) {\\n const el = document.getElementById(id);\\n if (!(el instanceof HTMLInputElement)) throw new Error(`work board: missing input #${id}`);\\n return el;\\n }\\n function isCardAction(value) {\\n switch (value) {\\n case \\\"claim\\\":\\n case \\\"start\\\":\\n case \\\"done\\\":\\n case \\\"fail\\\":\\n case \\\"release\\\":\\n case \\\"cancel\\\":\\n case \\\"reopen\\\":\\n return true;\\n default:\\n return false;\\n }\\n }\\n let boardId = null;\\n let tasks = [];\\n let pollActive = false;\\n function boot() {\\n const columnsEl = getEl(\\\"columns\\\");\\n const statusbarEl = getEl(\\\"statusbar\\\");\\n const boardIdEl = getEl(\\\"board-id\\\");\\n const boardInputEl = getInput(\\\"board-input\\\");\\n const newTitleEl = getInput(\\\"new-title\\\");\\n function setStatus(msg) {\\n statusbarEl.textContent = msg;\\n }\\n function render() {\\n boardIdEl.textContent = boardId ?? \\\"—\\\";\\n if (!boardInputEl.value) boardInputEl.value = boardId ?? \\\"\\\";\\n columnsEl.innerHTML = renderColumns(tasks);\\n }\\n function findTask(taskId) {\\n return tasks.find((t) => t.taskId === taskId);\\n }\\n function loadBoard() {\\n const args = {\\n includeClosed: true,\\n full: true,\\n ...boardId ? { boardId } : {}\\n };\\n return callTool(\\\"task_list\\\", args).then((data) => {\\n boardId = data.boardId ?? boardId;\\n tasks = data.tasks ?? [];\\n render();\\n setStatus(`${tasks.length} task${tasks.length === 1 ? \\\"\\\" : \\\"s\\\"} · ${(/* @__PURE__ */ new Date()).toLocaleTimeString()}`);\\n }).catch((e) => {\\n setStatus(`Error: ${e.message}`);\\n });\\n }\\n function applyAction(taskId, action) {\\n const task = findTask(taskId);\\n if (!task) return;\\n const base = { taskId: task.taskId, rev: task.rev };\\n let promise;\\n if (action === \\\"claim\\\") {\\n promise = callTool(\\\"task_claim\\\", base);\\n } else if (action === \\\"start\\\") {\\n promise = callTool(\\\"task_update\\\", { ...base, status: \\\"in_progress\\\" });\\n } else if (action === \\\"done\\\") {\\n promise = callTool(\\\"task_update\\\", { ...base, status: \\\"done\\\" });\\n } else if (action === \\\"fail\\\") {\\n promise = callTool(\\\"task_update\\\", { ...base, status: \\\"failed\\\" });\\n } else if (action === \\\"release\\\") {\\n promise = callTool(\\\"task_update\\\", { ...base, owner: null });\\n } else if (action === \\\"cancel\\\") {\\n promise = callTool(\\\"task_update\\\", { ...base, status: \\\"cancelled\\\" });\\n } else {\\n promise = callTool(\\\"task_update\\\", { ...base, status: \\\"pending\\\" });\\n }\\n promise.then((result) => {\\n if (\\\"conflict\\\" in result) {\\n setStatus(`Someone else moved \\\"${task.title}\\\" first — refreshed.`);\\n } else if (\\\"error\\\" in result) {\\n setStatus(`Action failed: ${result.error}`);\\n } else {\\n setStatus(`Updated \\\"${task.title}\\\".`);\\n }\\n return loadBoard();\\n }).catch((e) => {\\n setStatus(`Action failed: ${e.message}`);\\n });\\n }\\n function doPoll() {\\n if (pollActive) return;\\n pollActive = true;\\n loadBoard().finally(() => {\\n pollActive = false;\\n setTimeout(doPoll, 4e3);\\n });\\n }\\n columnsEl.addEventListener(\\\"click\\\", (evt) => {\\n if (!(evt.target instanceof Element)) return;\\n const btn = evt.target.closest(\\\"button[data-action]\\\");\\n if (!(btn instanceof HTMLButtonElement)) return;\\n const taskId = btn.getAttribute(\\\"data-taskid\\\");\\n const action = btn.getAttribute(\\\"data-action\\\");\\n if (!taskId || !action || !isCardAction(action)) return;\\n applyAction(taskId, action);\\n });\\n getEl(\\\"refresh-btn\\\").addEventListener(\\\"click\\\", () => {\\n void loadBoard();\\n });\\n getEl(\\\"go-btn\\\").addEventListener(\\\"click\\\", () => {\\n const value = boardInputEl.value.trim();\\n if (!value) return;\\n boardId = value;\\n void loadBoard();\\n });\\n getEl(\\\"add-btn\\\").addEventListener(\\\"click\\\", () => {\\n const title = newTitleEl.value.trim();\\n if (!title) return;\\n const args = boardId ? { title, boardId } : { title };\\n callTool(\\\"task_create\\\", args).then((result) => {\\n if (result.error) {\\n setStatus(`Create failed: ${result.error}`);\\n return void 0;\\n }\\n newTitleEl.value = \\\"\\\";\\n setStatus(`Created \\\"${title}\\\".`);\\n return loadBoard();\\n }).catch((e) => {\\n setStatus(`Create failed: ${e.message}`);\\n });\\n });\\n initBridge().then(() => loadBoard()).then(() => {\\n setTimeout(doPoll, 4e3);\\n }).catch((e) => {\\n setStatus(`Bridge error: ${e.message}`);\\n });\\n }\\n if (document.readyState === \\\"loading\\\") {\\n document.addEventListener(\\\"DOMContentLoaded\\\", boot);\\n } else {\\n boot();\\n }\\n})();</script>\\n <style rel=\\\"stylesheet\\\" crossorigin>/* ═══════════════════════════════════════════════════════════════════\\n Semantic tokens + runtime mapping sheets.\\n\\n Every rule below the next section reads ONLY the --wb-* custom\\n properties. Each one resolves through TWO mapping sheets, chosen at\\n PAINT time via plain CSS var() fallback — no build step, no JS, so a\\n host that swaps its theme live (VS Code) is never frozen at whatever\\n theme happened to be active when this bundle was built:\\n\\n 1. embedded — VS Code injects a live --vscode-* custom-property set\\n into the webview and swaps it on theme change. Whenever the\\n relevant --vscode-* variable exists, it wins — in a VS Code\\n `srcdoc` relay panel AND (structurally identically) any other host\\n that happened to expose the same names, though none is known to.\\n 2. standalone — the agentproto brand tokens (see\\n projects/agentproto/site/src/app/brand.css), used whenever no\\n --vscode-* variable is set — a plain MCP-Apps host or the daemon's\\n own `/apps/.../ui` tab. `--phos`/`--amber`/`--crimson` are\\n THEME-INVARIANT in brand.css (the phosphor terminal palette), so\\n they're identical in both blocks below; the rest follow brand.css's\\n light/dark split via `prefers-color-scheme` — a standalone tab has\\n no host to pick a theme for it, unlike VS Code's own.\\n ═══════════════════════════════════════════════════════════════════ */\\n\\n:root {\\n --wb-bg: var(--vscode-editor-background, #f5f6f3);\\n --wb-surface: var(--vscode-sideBar-background, #ffffff);\\n --wb-ink: var(--vscode-editor-foreground, #171a1e);\\n --wb-ink-muted: var(--vscode-descriptionForeground, #5c6470);\\n --wb-border: var(--vscode-panel-border, #dde0da);\\n --wb-accent: var(--vscode-button-background, #0f62fe);\\n\\n /* Status colors — phos/amber/crimson are the theme-invariant terminal\\n palette in brand.css, so light and dark standalone fallbacks match. */\\n --wb-status-pending: var(--vscode-descriptionForeground, #5c6470);\\n --wb-status-in-progress: var(--vscode-charts-yellow, #f0b429);\\n --wb-status-done: var(--vscode-charts-green, #3edc81);\\n --wb-status-failed: var(--vscode-charts-red, #fa4d56);\\n --wb-status-cancelled: var(--vscode-descriptionForeground, #5c6470);\\n --wb-status-cancelled-weak: color-mix(in srgb, var(--wb-status-cancelled) 55%, transparent);\\n}\\n\\n@media (prefers-color-scheme: dark) {\\n :root {\\n --wb-bg: var(--vscode-editor-background, #101214);\\n --wb-surface: var(--vscode-sideBar-background, #16191d);\\n --wb-ink: var(--vscode-editor-foreground, #e8eaed);\\n --wb-ink-muted: var(--vscode-descriptionForeground, #99a2ad);\\n --wb-border: var(--vscode-panel-border, #272c33);\\n --wb-accent: var(--vscode-button-background, #4589ff);\\n\\n --wb-status-pending: var(--vscode-descriptionForeground, #99a2ad);\\n --wb-status-in-progress: var(--vscode-charts-yellow, #f0b429);\\n --wb-status-done: var(--vscode-charts-green, #3edc81);\\n --wb-status-failed: var(--vscode-charts-red, #fa4d56);\\n --wb-status-cancelled: var(--vscode-descriptionForeground, #99a2ad);\\n --wb-status-cancelled-weak: color-mix(in srgb, var(--wb-status-cancelled) 55%, transparent);\\n }\\n}\\n\\n/* ── Base ─────────────────────────────────────────────────────────── */\\n\\n* { box-sizing: border-box; margin: 0; padding: 0; }\\n\\nhtml, body {\\n height: 100%;\\n font-family: \\\"IBM Plex Sans\\\", -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", Roboto, Helvetica, Arial, sans-serif;\\n font-size: 13px;\\n background: var(--wb-bg);\\n color: var(--wb-ink);\\n overflow: hidden;\\n}\\n\\n.mono {\\n font-family: \\\"IBM Plex Mono\\\", ui-monospace, SFMono-Regular, Menlo, Monaco, \\\"Courier New\\\", monospace;\\n}\\n\\n#app { display: flex; flex-direction: column; height: 100%; }\\n\\n#toolbar {\\n padding: 8px 12px;\\n border-bottom: 1px solid var(--wb-border);\\n display: flex;\\n align-items: center;\\n gap: 8px;\\n background: var(--wb-surface);\\n flex-shrink: 0;\\n}\\n#board-label { font-size: 11px; color: var(--wb-ink-muted); }\\n#board-id { font-weight: 600; color: var(--wb-ink); font-size: 12px; font-family: \\\"IBM Plex Mono\\\", ui-monospace, SFMono-Regular, Menlo, Monaco, \\\"Courier New\\\", monospace; }\\n#board-input {\\n background: var(--wb-bg);\\n border: 1px solid var(--wb-border);\\n color: var(--wb-ink);\\n padding: 4px 8px;\\n border-radius: 4px;\\n font-size: 12px;\\n font-family: inherit;\\n width: 220px;\\n}\\n.tbtn {\\n background: var(--wb-bg);\\n border: 1px solid var(--wb-border);\\n color: var(--wb-ink);\\n padding: 4px 10px;\\n border-radius: 4px;\\n cursor: pointer;\\n font-size: 12px;\\n font-family: inherit;\\n}\\n.tbtn:hover { background: var(--wb-border); }\\n#new-title {\\n flex: 1;\\n min-width: 120px;\\n background: var(--wb-bg);\\n border: 1px solid var(--wb-border);\\n color: var(--wb-ink);\\n padding: 4px 8px;\\n border-radius: 4px;\\n font-size: 12px;\\n font-family: inherit;\\n}\\n\\n#columns { flex: 1; display: flex; overflow-x: auto; overflow-y: hidden; }\\n.col {\\n width: 260px;\\n min-width: 220px;\\n flex-shrink: 0;\\n display: flex;\\n flex-direction: column;\\n border-right: 1px solid var(--wb-border);\\n}\\n.col-hdr {\\n padding: 8px 10px;\\n border-bottom: 1px solid var(--wb-border);\\n background: var(--wb-surface);\\n font-size: 11px;\\n font-weight: 600;\\n color: var(--wb-ink-muted);\\n text-transform: uppercase;\\n letter-spacing: 0.05em;\\n display: flex;\\n justify-content: space-between;\\n}\\n.col-body { flex: 1; overflow-y: auto; padding: 8px; display: flex; flex-direction: column; gap: 8px; }\\n\\n.card {\\n background: var(--wb-surface);\\n border: 1px solid var(--wb-border);\\n border-radius: 6px;\\n padding: 8px 10px;\\n}\\n.c-title { font-size: 12px; font-weight: 500; margin-bottom: 4px; word-break: break-word; }\\n.c-id {\\n font-size: 10px;\\n color: var(--wb-ink-muted);\\n margin-bottom: 6px;\\n font-family: \\\"IBM Plex Mono\\\", ui-monospace, SFMono-Regular, Menlo, Monaco, \\\"Courier New\\\", monospace;\\n}\\n.c-row { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; margin-bottom: 6px; }\\n\\n.tag { display: inline-block; padding: 1px 6px; border-radius: 10px; font-size: 10px; font-weight: 600; white-space: nowrap; }\\n.t-unclaimed { background: color-mix(in srgb, var(--wb-ink-muted) 15%, transparent); color: var(--wb-ink-muted); border: 1px dashed var(--wb-ink-muted); }\\n.t-owner { background: color-mix(in srgb, var(--wb-accent) 15%, transparent); color: var(--wb-accent); }\\n\\n/* Verification tell: gate solid green, self-report amber outline, human\\n blue, a declared verify with no verification yet grey \\\"gated\\\" — never\\n render an unverified done as gate-passed (see render.ts's verificationTag). */\\n.t-gate { background: color-mix(in srgb, var(--wb-status-done) 18%, transparent); color: var(--wb-status-done); }\\n.t-self { background: transparent; color: var(--wb-status-in-progress); border: 1px solid var(--wb-status-in-progress); }\\n.t-human { background: color-mix(in srgb, var(--wb-accent) 18%, transparent); color: var(--wb-accent); }\\n.t-gated { background: color-mix(in srgb, var(--wb-ink-muted) 12%, transparent); color: var(--wb-ink-muted); }\\n.t-cancelled {\\n background: color-mix(in srgb, var(--wb-status-cancelled) 10%, transparent);\\n color: var(--wb-status-cancelled-weak);\\n border: 1px dashed color-mix(in srgb, var(--wb-status-cancelled) 40%, transparent);\\n}\\n\\n.c-err { font-size: 10px; color: var(--wb-status-failed); margin-bottom: 6px; word-break: break-word; }\\n.c-actions { display: flex; flex-wrap: wrap; gap: 4px; }\\n.abtn {\\n background: var(--wb-bg);\\n border: 1px solid var(--wb-border);\\n color: var(--wb-ink);\\n padding: 3px 8px;\\n border-radius: 4px;\\n cursor: pointer;\\n font-size: 11px;\\n font-family: inherit;\\n}\\n.abtn:hover { background: var(--wb-border); }\\n.abtn.danger { border-color: var(--wb-status-failed); color: var(--wb-status-failed); }\\n.abtn.primary { border-color: var(--wb-status-done); color: var(--wb-status-done); }\\n\\n#statusbar {\\n padding: 4px 12px;\\n font-size: 11px;\\n color: var(--wb-ink-muted);\\n border-top: 1px solid var(--wb-border);\\n background: var(--wb-surface);\\n flex-shrink: 0;\\n}\\n.empty { padding: 16px 8px; color: var(--wb-ink-muted); font-size: 11px; text-align: center; }</style>\\n</head>\\n<body>\\n<div id=\\\"app\\\">\\n <div id=\\\"toolbar\\\">\\n <span id=\\\"board-label\\\">Board:</span>\\n <span id=\\\"board-id\\\">—</span>\\n <input id=\\\"board-input\\\" type=\\\"text\\\" placeholder=\\\"tree:<sessionId> or ws:<slug>\\\">\\n <button class=\\\"tbtn\\\" id=\\\"go-btn\\\">Go</button>\\n <button class=\\\"tbtn\\\" id=\\\"refresh-btn\\\" title=\\\"Refresh\\\">↻</button>\\n <input id=\\\"new-title\\\" type=\\\"text\\\" placeholder=\\\"New task title…\\\">\\n <button class=\\\"tbtn\\\" id=\\\"add-btn\\\">+ Add</button>\\n </div>\\n <div id=\\\"columns\\\"></div>\\n <div id=\\\"statusbar\\\">Connecting to bridge…</div>\\n</div>\\n</body>\\n</html>\\n\"\n","/**\n * HTML bundle for the agentproto work-board panel — the `WORK_BOARD_HTML`\n * served as the `ui://agentproto_work_board/view` resource by\n * `registerMcpApps` (see ../mcp-app-types.ts and runtime's\n * mcp-apps-adapter.ts). Mounted via the `agentproto_work_board` tool defined\n * in ./index.ts.\n *\n * A kanban over the daemon's Task ledger (`packages/runtime/src/\n * task-ledger.ts`) — the one writable multi-party entity the daemon exposes\n * (Activity is a read-only projection; a session has a lifecycle, not an\n * intention). Columns are the ledger's own status enum, folding `cancelled`\n * into the `failed` column (tagged distinctly — see `columnOf` in\n * ./ui/render.ts) rather than adding a fifth column for a status v1 treats\n * as terminal scrap. The board/swimlane selector is the ledger's own board\n * id (`tree:<rootSessionId>` or `ws:<slug>`) — always shown verbatim, never\n * hidden behind a friendly name, per the ledger's scoping model.\n *\n * `WORK_BOARD_HTML` is a real single-file Vite build, not a hand-authored\n * template string: the source lives in ./ui/** (index.html/main.ts/\n * types.ts/render.ts/style.css, bundled by ./ui/vite.config.ts with\n * vite-plugin-singlefile) and is committed, pre-built, into\n * ./panel.generated.ts by `pnpm --filter @agentproto/apps run build:ui`\n * (scripts/build-work-board-ui.mjs) — so this package still builds with\n * plain `pnpm build` on a fresh clone, no Vite step in the critical path.\n * `build:ui:check` (wired into CI) fails if that generated file drifts from\n * what the Vite sources actually produce. See ./ui/vite.config.ts for how\n * the shared `panelBridgeScript` (../panel-bridge.ts) gets inlined as a\n * classic `<script>` ahead of the bundled module script.\n *\n * Protocol: MCP Apps ext spec 2026-01-26\n * – Bridge: JSON-RPC 2.0 over window.parent.postMessage (../panel-bridge.ts,\n * unmodified by this change — this app only calls its `initBridge()`/\n * `callTool()` surface). Standalone (no host at all, e.g. a top-level\n * `GET /apps/:appId/ui` tab) does NOT connect yet: panel-bridge.ts's\n * handshake has no reply to wait for in that case and hangs on\n * \"Connecting to bridge…\" — a known gap being fixed in panel-bridge.ts\n * itself by a separate change. This app will pick that fix up automatically\n * (rebuild + rebase) once it lands; nothing here needs to special-case it.\n * – Handshake: ui/initialize → host result → ui/notifications/initialized\n * – Data: tools/call → task_list (`full: true` — the compact projection\n * drops `verification`, which the verification tell needs) on a ~4 s poll\n * – Actions: tools/call → task_claim / task_update / task_create\n *\n * Read-only-safe first: no drag-and-drop. Each card gets the explicit status\n * actions valid from its current state (Claim/Start/Done/Fail/Release/\n * Cancel/Reopen) — a correct read-only-plus-explicit-actions board beats a\n * half-wired drag, per the brief.\n *\n * The verification tell (never render an unverified done as gate-passed):\n * - `verification.kind === \"gate\"` → solid green \"✓ gate\"\n * - `verification.kind === \"self-report\"` → amber outline \"self-report\"\n * - `verification.kind === \"human\"` → blue \"human\"\n * - a declared `verify` with no verification yet → grey \"gated\" tag\n * An owner-less task always reads \"Unclaimed\", never \"pending\" (that word is\n * reserved for the Activity vocabulary's \"blocked\" meaning elsewhere).\n */\n\nimport { WORK_BOARD_HTML_GENERATED } from \"./panel.generated.js\"\n\n/** The builtin's app id — how `app_catalog` reports the panel. Lives in this\n * html-only entry (not ./index.ts) so a consumer that must NOT pull the\n * app-kit dependency graph — the VS Code extension bundles with esbuild and\n * chokes on its transitive native `.node` deps — can still name the panel\n * from source. */\nexport const WORK_BOARD_APP_ID = \"@agentproto/work-board\"\n\n/** The MCP tool the panel is registered under (./index.ts `makeWorkBoardApp`),\n * hence its resource uri `ui://agentproto_work_board/view`. */\nexport const WORK_BOARD_TOOL_ID = \"agentproto_work_board\"\n\n/** The daemon tools this panel's html calls, in order of use. Consumed as\n * `ui.tools` by ./index.ts and as the client-side allowlist by any host that\n * dispatches the panel's `tools/call` directly — a builtin is not an\n * installed app, so `app_tool_call` cannot route for it. */\nexport const WORK_BOARD_UI_TOOLS = [\n \"task_list\",\n \"task_claim\",\n \"task_update\",\n \"task_create\",\n] as const\n\nexport const WORK_BOARD_HTML = WORK_BOARD_HTML_GENERATED\n","/**\n * McpApp definition for the agentproto work-board panel — a kanban over the\n * daemon's Task ledger (`packages/runtime/src/task-ledger.ts`), the ONLY\n * writable multi-party entity the daemon exposes (Activity is a read-only\n * projection recomputed on every `list()`; a session has a lifecycle, not an\n * intention).\n *\n * Uses the AgnoMcpApp contract (a local McpApp-compatible shape that does\n * NOT depend on @agstudio/mcp-apps or Mastra), same as sessions-panel /\n * session-chat — the host (@agentproto/runtime) wires this factory's output\n * via `registerMcpApps` from its mcp-apps-adapter.ts at boot time, with the\n * mounted list assembled in runtime's builtin-apps.ts.\n *\n * Protocol flow:\n * 1. Tool `agentproto_work_board` is called by the host with an optional\n * `boardId`.\n * 2. execute() answers an initial `{boardId, tasks}` snapshot off the\n * ledger — resolved to the caller's default board when `boardId` is\n * omitted (a session's `tree:<root>`, the operator's `ws:<slug>`).\n * 3. The HTML panel opens a JSON-RPC bridge (postMessage) and polls\n * `task_list` (`full: true` — the compact projection drops\n * `verification`) every ~4 s; writes go through `task_claim` /\n * `task_update` / `task_create`, the same verbs any other caller uses —\n * no second write path.\n *\n * This module also exports `workBoardApp`, a real `defineApp()` `AppHandle`\n * (`agents: []`, UI-only) — the catalog/emit/`app_install` path. It's\n * separate from `makeWorkBoardApp` above, which the daemon mounts directly\n * at boot: that factory closes over the LIVE ledger read, something a\n * static emitted `ui.html` snapshot can't carry.\n */\n\nimport { z } from \"zod\"\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport {\n WORK_BOARD_APP_ID,\n WORK_BOARD_HTML,\n WORK_BOARD_TOOL_ID,\n WORK_BOARD_UI_TOOLS,\n} from \"./panel.js\"\nimport type { AgnoMcpApp } from \"../mcp-app-types.js\"\n\nexport const workBoardInputSchema = z.object({\n boardId: z\n .string()\n .optional()\n .describe(\n \"Board to open — `tree:<rootSessionId>` (a supervisor and its \" +\n \"executors) or `ws:<slug>` (an operator workspace). Omit → the \" +\n \"caller's default board.\",\n ),\n})\n\nexport type WorkBoardInput = z.infer<typeof workBoardInputSchema>\n\nexport interface WorkBoardOutput<TTask = unknown> {\n /** Always echoed back — the ledger's board ids are the swimlane\n * selector, and must be visible rather than hidden behind a friendly\n * name (it's how the operator knows which mission they're looking at). */\n boardId: string\n tasks: TTask[]\n}\n\n/**\n * Generic over the host's own task-record shape — this package doesn't own\n * that type (it lives in @agentproto/runtime's task ledger), so the factory\n * stays agnostic to it and the host supplies the concrete type as a type\n * argument when it wires the app up (mirrors SessionsPanelOps<TSession>).\n */\nexport interface WorkBoardOps<TTask = unknown> {\n /** Full (unprojected) records for a board — NOT `task_list`'s default\n * compact projection, since the verification tell needs `verification`\n * (self-report vs gate vs human), which compact mode omits. Omit\n * `boardId` to resolve the caller's default board. */\n listTasks(boardId?: string): WorkBoardOutput<TTask>\n}\n\nexport type { AgnoMcpApp }\n\n/**\n * Factory: close over the ledger read so execute() doesn't need an\n * AppContext (agentproto has no userId/guildId concept) — mirrors\n * makeSessionsPanelApp.\n */\nexport function makeWorkBoardApp<TTask = unknown>(\n ops: WorkBoardOps<TTask>,\n): AgnoMcpApp<WorkBoardInput, WorkBoardOutput<TTask>> {\n return {\n id: WORK_BOARD_TOOL_ID,\n title: \"Work Board\",\n description:\n \"Open the agentproto work board — a kanban over the daemon's Task \" +\n \"ledger (columns: pending/in_progress/done/failed, cancelled folded \" +\n \"into failed). Swimlane is the ledger's own board id (`tree:<root>` \" +\n \"or `ws:<slug>`). Each card shows its owner (or \\\"Unclaimed\\\") and the \" +\n \"verification tell — a gate-passed done reads visually distinct from \" +\n \"a self-reported one. Pass `boardId` to open a specific board.\",\n inputSchema: workBoardInputSchema,\n execute: async input => ops.listTasks(input.boardId),\n html: WORK_BOARD_HTML,\n }\n}\n\nexport const workBoardApp: AppHandle = defineApp({\n id: WORK_BOARD_APP_ID,\n name: \"Work Board\",\n description:\n \"Open the agentproto work board — a kanban over the daemon's Task ledger, scoped by board id \" +\n \"(`tree:<rootSessionId>` or `ws:<slug>`), with an explicit status action per card.\",\n agents: [],\n ui: {\n html: WORK_BOARD_HTML,\n title: \"Work Board\",\n tools: [...WORK_BOARD_UI_TOOLS],\n },\n})\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentproto/apps",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "@agentproto/apps — a home for ready-made agentproto apps (teams of agents + their workflows), declared with @agentproto/app-kit. Import a team (code-team, content-team, …) and use any of its agents/workflows from your own host: in-process via toMastraAgents(), or emit its manifests to disk.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|
|
@@ -145,21 +145,23 @@
|
|
|
145
145
|
"access": "public"
|
|
146
146
|
},
|
|
147
147
|
"dependencies": {
|
|
148
|
-
"zod": "^4.5
|
|
149
|
-
"@agentproto/agent": "0.2.
|
|
150
|
-
"@agentproto/app-kit": "1.2.
|
|
151
|
-
"@agentproto/workflow": "0.6.
|
|
148
|
+
"zod": "^4.6.5",
|
|
149
|
+
"@agentproto/agent": "0.2.4",
|
|
150
|
+
"@agentproto/app-kit": "1.2.1",
|
|
151
|
+
"@agentproto/workflow": "0.6.1"
|
|
152
152
|
},
|
|
153
153
|
"peerDependencies": {
|
|
154
154
|
"@mastra/core": ">=1.0.0"
|
|
155
155
|
},
|
|
156
156
|
"devDependencies": {
|
|
157
|
-
"@mastra/core": "1.
|
|
157
|
+
"@mastra/core": "1.66.0",
|
|
158
158
|
"@types/node": "^25.9.5",
|
|
159
159
|
"tsup": "^8.5.1",
|
|
160
160
|
"typescript": "^5.9.3",
|
|
161
|
+
"vite": "^7.3.6",
|
|
162
|
+
"vite-plugin-singlefile": "^2.3.0",
|
|
161
163
|
"vitest": "^3.2.7",
|
|
162
|
-
"@agentproto/mastra": "0.2.
|
|
164
|
+
"@agentproto/mastra": "0.2.14",
|
|
163
165
|
"@agentproto/tooling": "0.1.0"
|
|
164
166
|
},
|
|
165
167
|
"scripts": {
|
|
@@ -168,6 +170,8 @@
|
|
|
168
170
|
"clean": "rm -rf dist",
|
|
169
171
|
"check-types": "tsc --noEmit",
|
|
170
172
|
"test": "vitest run",
|
|
171
|
-
"test:watch": "vitest"
|
|
173
|
+
"test:watch": "vitest",
|
|
174
|
+
"build:ui": "tsc --noEmit -p src/work-board/ui/tsconfig.json && node scripts/build-work-board-ui.mjs",
|
|
175
|
+
"build:ui:check": "tsc --noEmit -p src/work-board/ui/tsconfig.json && node scripts/build-work-board-ui.mjs --check"
|
|
172
176
|
}
|
|
173
177
|
}
|