@agentproto/apps 0.20.3 → 0.21.1
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 +21 -8
- package/dist/agents-overview/panel.mjs.map +1 -1
- package/dist/agents-overview.mjs +21 -8
- package/dist/agents-overview.mjs.map +1 -1
- package/dist/bin/sync.mjs +19 -2
- package/dist/bin/sync.mjs.map +1 -1
- package/dist/bureau-sessions/panel.mjs +21 -8
- package/dist/bureau-sessions/panel.mjs.map +1 -1
- package/dist/bureau-sessions.mjs +21 -8
- package/dist/bureau-sessions.mjs.map +1 -1
- package/dist/index.mjs +63 -20
- package/dist/index.mjs.map +1 -1
- package/dist/live-session/panel.mjs +21 -8
- package/dist/live-session/panel.mjs.map +1 -1
- package/dist/live-session.mjs +21 -8
- package/dist/live-session.mjs.map +1 -1
- package/dist/ops-panel/ui.d.ts +1 -1
- package/dist/ops-panel/ui.d.ts.map +1 -1
- package/dist/ops-panel.mjs +19 -2
- package/dist/ops-panel.mjs.map +1 -1
- package/dist/panel-bridge.d.ts.map +1 -1
- package/dist/review-panel/panel.d.ts +1 -1
- package/dist/review-panel/panel.d.ts.map +1 -1
- package/dist/review-panel/panel.generated.d.ts +1 -1
- package/dist/review-panel/panel.generated.d.ts.map +1 -1
- package/dist/review-panel/panel.mjs +1 -1
- package/dist/review-panel/panel.mjs.map +1 -1
- package/dist/review-panel.mjs +1 -1
- package/dist/review-panel.mjs.map +1 -1
- package/dist/session-chat/panel.mjs +21 -8
- package/dist/session-chat/panel.mjs.map +1 -1
- package/dist/session-chat.mjs +21 -8
- package/dist/session-chat.mjs.map +1 -1
- package/dist/session-story/panel.mjs +21 -8
- package/dist/session-story/panel.mjs.map +1 -1
- package/dist/session-story.mjs +21 -8
- package/dist/session-story.mjs.map +1 -1
- package/dist/sessions-panel/panel.mjs +21 -8
- package/dist/sessions-panel/panel.mjs.map +1 -1
- package/dist/sessions-panel.mjs +21 -8
- package/dist/sessions-panel.mjs.map +1 -1
- package/dist/store/panel.d.ts +1 -1
- package/dist/store/panel.d.ts.map +1 -1
- package/dist/store/panel.generated.d.ts +1 -1
- package/dist/store/panel.generated.d.ts.map +1 -1
- package/dist/store/panel.mjs +1 -1
- package/dist/store/panel.mjs.map +1 -1
- package/dist/store.mjs +1 -1
- package/dist/store.mjs.map +1 -1
- package/dist/work-board/panel.d.ts +1 -1
- package/dist/work-board/panel.d.ts.map +1 -1
- package/dist/work-board/panel.generated.d.ts +1 -1
- package/dist/work-board/panel.generated.d.ts.map +1 -1
- package/dist/work-board/panel.mjs +21 -8
- package/dist/work-board/panel.mjs.map +1 -1
- package/dist/work-board.mjs +21 -8
- package/dist/work-board.mjs.map +1 -1
- package/package.json +3 -3
- package/repo-maintenance/.agentproto/workflows/maintain/WORKFLOW.md +17 -1
- package/repo-maintenance/.agentproto/workflows/maintain/entry.mjs +88 -5
- package/session-steward/.agentproto/workflows/session-steward/WORKFLOW.md +38 -6
- package/session-steward/.agentproto/workflows/session-steward/entry.mjs +44 -6
- package/session-steward/.agentproto/workflows/session-steward/origin-policy.mjs +2 -2
- package/session-steward/README.md +12 -4
- package/session-steward/routines/session-steward-hourly/ROUTINE.md +6 -3
package/dist/work-board.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;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 _hostCaps = null;\\nvar _standaloneApp = null;\\nfunction _isStandalone(){\\n return window.parent === window && !!window.McpApp && typeof window.McpApp.connect === 'function';\\n}\\n// ── Per-boot embed token (runtime embed-tokens.ts) ────────────────────────\\n// Panels that iframe the daemon's standalone app host carry this proof so\\n// GET /apps/:appId/ui?embed=1&et=... can drop its anti-framing headers for\\n// MCP-Apps hosts whose widget context is an opaque origin (Claude Desktop).\\n// registerMcpApps replaces the placeholder with a real per-boot token when\\n// serving the panel as a resource; every other render (tests, standalone\\n// tab, VS Code HTTP-iframe panel — the latter two already pass\\n// iframeEmbedAllowed's origin checks) keeps the literal, and\\n// withEmbedToken() then degrades to an identity function.\\nwindow.__AGENPROTO_EMBED_TOKEN__ = \\\"__AGENPROTO_EMBED_TOKEN__\\\";\\nfunction withEmbedToken(url){\\n var t = window.__AGENPROTO_EMBED_TOKEN__;\\n if (!t || t === '__AGENPROTO_EMBED_TOKEN__') return url;\\n return url + (url.indexOf('?') >= 0 ? '&' : '?') + 'et=' + encodeURIComponent(t);\\n}\\nfunction post(msg){ window.parent.postMessage(msg, '*'); }\\nfunction getHostContext(){ return _hostContext; }\\nfunction getHostCapabilities(){ return _hostCaps; }\\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 display-mode 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 // McpUiInitializeResult.hostCapabilities (ext-apps spec) — e.g. openLinks,\\n // \\\"Host supports opening external URLs\\\". Captured once, read via\\n // getHostCapabilities() by panels that need to branch on it.\\n if (result && result.hostCapabilities) _hostCaps = result.hostCapabilities;\\n rpcNotify('ui/notifications/initialized', {});\\n });\\n}\\nfunction requestDisplayMode(mode){\\n return rpcRequest('ui/request-display-mode', {mode: mode});\\n}\\nfunction openLink(url){\\n if (_standaloneApp && typeof _standaloneApp.openLink === 'function') return _standaloneApp.openLink(url);\\n return rpcRequest('ui/open-link', {url: url});\\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 (shared: @agentproto/app-client/display-mode) ──\\n// The installer is inlined verbatim (it is self-contained and idempotent),\\n// then handed this panel's own JSON-RPC plumbing. NO auto-request: the\\n// panel stays inline until the user clicks, and the buttons only appear for\\n// the modes the host advertises in hostContext.availableDisplayModes.\\n(function () {\\n if (window.AgentprotoUI && window.AgentprotoUI.installDisplayMode) return;\\n window.AgentprotoUI = window.AgentprotoUI || {};\\n\\n var STYLE_ID = \\\"agentproto-display-mode-style\\\";\\n var BAR_ID = \\\"agentproto-display-mode-bar\\\";\\n // Gap between the host's safe area (or the viewport edge) and the button.\\n var EDGE_GAP = 8;\\n\\n // Glyph + accessible label per mode. The button is an icon; the words go\\n // to title/aria-label so the control stays compact without going mute to\\n // a screen reader.\\n var LABELS = {\\n fullscreen: {\\n on: \\\"\\\\u2921\\\", off: \\\"\\\\u2922\\\",\\n onLabel: \\\"R\\\\u00e9duire\\\", offLabel: \\\"Agrandir\\\"\\n },\\n pip: {\\n on: \\\"\\\\u2921\\\", off: \\\"\\\\uD83D\\\\uDCCC\\\",\\n onLabel: \\\"D\\\\u00e9tacher\\\", offLabel: \\\"\\\\u00c9pingler sur le c\\\\u00f4t\\\\u00e9 (pip)\\\"\\n }\\n };\\n\\n // Every colour and both offsets resolve through an app-overridable custom\\n // property first, so an app can restyle/reposition the button with a\\n // stylesheet and never touch this script. The safe-area vars are what the\\n // host's own chrome feeds (see applyInsets), sitting UNDER the app's.\\n function ensureStyle() {\\n if (!document.head || document.getElementById(STYLE_ID)) return;\\n var style = document.createElement(\\\"style\\\");\\n style.id = STYLE_ID;\\n style.textContent =\\n \\\"#\\\" + BAR_ID + \\\"{position:fixed;display:flex;gap:6px;align-items:center;z-index:10;\\\" +\\n \\\"top:var(--agentproto-dm-top,var(--agentproto-dm-safe-top,8px));\\\" +\\n \\\"right:var(--agentproto-dm-right,var(--agentproto-dm-safe-right,8px))}\\\" +\\n \\\".agentproto-dm-btn{display:inline-flex;align-items:center;justify-content:center;\\\" +\\n \\\"box-sizing:border-box;width:28px;height:28px;padding:0;margin:0;border-radius:6px;\\\" +\\n \\\"cursor:pointer;-webkit-appearance:none;appearance:none;\\\" +\\n \\\"font:600 14px/1 system-ui,-apple-system,sans-serif;\\\" +\\n \\\"border:1px solid var(--agentproto-dm-border,#d0d0d0);\\\" +\\n \\\"background:var(--agentproto-dm-bg,#fff);\\\" +\\n \\\"color:var(--agentproto-dm-fg,#1a1a1a);\\\" +\\n \\\"box-shadow:0 1px 4px rgba(0,0,0,.18)}\\\" +\\n \\\".agentproto-dm-btn:hover{filter:brightness(.95)}\\\" +\\n \\\".agentproto-dm-btn:focus-visible{outline:2px solid var(--agentproto-dm-fg,#1a1a1a);outline-offset:2px}\\\" +\\n \\\".agentproto-dm-btn[data-theme=\\\\\\\"dark\\\\\\\"]{border-color:var(--agentproto-dm-border,#555);background:var(--agentproto-dm-bg,#2a2a2a);color:var(--agentproto-dm-fg,#f0f0f0);box-shadow:0 1px 4px rgba(0,0,0,.5)}\\\" +\\n \\\"@media (prefers-color-scheme:dark){\\\" +\\n \\\".agentproto-dm-btn:not([data-theme=\\\\\\\"light\\\\\\\"]){border-color:var(--agentproto-dm-border,#555);background:var(--agentproto-dm-bg,#2a2a2a);color:var(--agentproto-dm-fg,#f0f0f0);box-shadow:0 1px 4px rgba(0,0,0,.5)}}\\\";\\n document.head.appendChild(style);\\n }\\n\\n function ready(fn) {\\n if (document.body) fn();\\n else document.addEventListener(\\\"DOMContentLoaded\\\", fn);\\n }\\n\\n function metaToggle() {\\n var el = document.querySelector('meta[name=\\\"agentproto-display-toggle\\\"]');\\n if (!el) return \\\"\\\";\\n return (el.getAttribute(\\\"content\\\") || \\\"\\\").toLowerCase().replace(/^\\\\s+|\\\\s+$/g, \\\"\\\");\\n }\\n\\n function ensureBar() {\\n var bar = document.getElementById(BAR_ID);\\n if (bar) return bar;\\n bar = document.createElement(\\\"div\\\");\\n bar.id = BAR_ID;\\n document.body.appendChild(bar);\\n return bar;\\n }\\n\\n function px(v) {\\n return typeof v === \\\"number\\\" && isFinite(v) && v > 0 ? v : 0;\\n }\\n\\n window.AgentprotoUI.installDisplayMode = function (api, opts) {\\n api = api || {};\\n opts = opts || {};\\n\\n var ctx = {};\\n var listeners = [];\\n var buttons = [];\\n // Modes the host has refused once. Checked before availability, so a\\n // later host-context-changed can never resurrect a dead button.\\n var refused = {};\\n var loggedKeys = false;\\n\\n var setting = opts.toggle || metaToggle() || \\\"auto\\\";\\n var optimistic = setting === \\\"optimistic\\\";\\n var autoMount = setting !== \\\"none\\\";\\n\\n function get() {\\n return ctx.displayMode || \\\"inline\\\";\\n }\\n\\n function available() {\\n var a = ctx.availableDisplayModes;\\n return a && a.slice ? a.slice() : [];\\n }\\n\\n function supports(mode) {\\n if (refused[mode]) return false;\\n var a = ctx.availableDisplayModes;\\n if (a && a.indexOf && a.indexOf(mode) >= 0) return true;\\n // Optimistic: the host advertises nothing (Claude Desktop, some\\n // embedders) but may still honour the request. Fullscreen only — pip\\n // is niche enough that guessing at it is just noise.\\n return optimistic && mode === \\\"fullscreen\\\";\\n }\\n\\n // Host chrome to stay clear of. Published as custom properties rather\\n // than inline styles so an app's own --agentproto-dm-top/right still\\n // wins (an inline style would beat any stylesheet it could write).\\n function applyInsets() {\\n var insets = ctx.safeAreaInsets || {};\\n var root = document.documentElement;\\n if (!root || !root.style || !root.style.setProperty) return;\\n root.style.setProperty(\\\"--agentproto-dm-safe-top\\\", (px(insets.top) + EDGE_GAP) + \\\"px\\\");\\n root.style.setProperty(\\\"--agentproto-dm-safe-right\\\", (px(insets.right) + EDGE_GAP) + \\\"px\\\");\\n }\\n\\n function syncButton(btn) {\\n var mode = btn.getAttribute(\\\"data-agentproto-dm\\\") || \\\"fullscreen\\\";\\n var labels = LABELS[mode] || LABELS.fullscreen;\\n var active = get() === mode;\\n btn.style.display = supports(mode) ? \\\"\\\" : \\\"none\\\";\\n btn.textContent = active ? labels.on : labels.off;\\n var label = active ? labels.onLabel : labels.offLabel;\\n btn.title = label;\\n btn.setAttribute(\\\"aria-label\\\", label);\\n btn.setAttribute(\\\"aria-pressed\\\", active ? \\\"true\\\" : \\\"false\\\");\\n if (ctx.theme === \\\"dark\\\" || ctx.theme === \\\"light\\\") btn.setAttribute(\\\"data-theme\\\", ctx.theme);\\n else btn.removeAttribute(\\\"data-theme\\\");\\n }\\n\\n function sync() {\\n applyInsets();\\n for (var i = buttons.length - 1; i >= 0; i--) {\\n // An app that removed its inline toggle stops being our problem.\\n if (buttons[i].isConnected === false) { buttons.splice(i, 1); continue; }\\n syncButton(buttons[i]);\\n }\\n }\\n\\n function emit() {\\n var mode = get();\\n for (var i = 0; i < listeners.length; i++) {\\n try { listeners[i](mode, ctx); } catch (_) {}\\n }\\n }\\n\\n // host-context-changed carries only the changed keys — merge, matching\\n // the official ext-apps App behaviour.\\n function setContext(next) {\\n if (!next || typeof next !== \\\"object\\\") return;\\n for (var k in next) {\\n if (Object.prototype.hasOwnProperty.call(next, k)) ctx[k] = next[k];\\n }\\n sync();\\n emit();\\n }\\n\\n function retire(mode) {\\n refused[mode] = true;\\n sync();\\n }\\n\\n function request(mode) {\\n var pending;\\n try {\\n pending = api.requestDisplayMode\\n ? api.requestDisplayMode(mode)\\n : Promise.reject(new Error(\\\"display mode: no host\\\"));\\n } catch (err) {\\n pending = Promise.reject(err);\\n }\\n return Promise.resolve(pending).then(function (result) {\\n // McpUiRequestDisplayModeResult.mode is the mode actually set, and\\n // the spec lets it differ from the one asked for — so a host that\\n // answers \\\"still inline\\\" is a refusal, not a success.\\n var settled = (result && result.mode) || mode;\\n if (optimistic && mode !== \\\"inline\\\" && settled !== mode) {\\n retire(mode);\\n throw new Error(\\\"host refused display mode: \\\" + mode);\\n }\\n // Hosts are not required to follow up with host-context-changed —\\n // trust the result so the glyph flips either way.\\n setContext({ displayMode: settled });\\n return settled;\\n }, function (err) {\\n if (optimistic && mode !== \\\"inline\\\") retire(mode);\\n throw err;\\n });\\n }\\n\\n function makeButton(mode) {\\n var btn = document.createElement(\\\"button\\\");\\n btn.type = \\\"button\\\";\\n btn.className = \\\"agentproto-dm-btn\\\";\\n // Stable id for the floating pair (styling hooks, tests). An app that\\n // mounts extra inline toggles gets unnamed ones rather than duplicates.\\n var id = mode === \\\"fullscreen\\\"\\n ? \\\"agentproto-display-mode\\\"\\n : \\\"agentproto-display-mode-\\\" + mode;\\n if (!document.getElementById(id)) btn.id = id;\\n btn.setAttribute(\\\"data-agentproto-dm\\\", mode);\\n btn.style.display = \\\"none\\\";\\n btn.addEventListener(\\\"click\\\", function () {\\n var cur = get();\\n // Same rule both copies had: anything that isn't inline collapses\\n // back to inline, so the button is always an escape hatch.\\n var inPanel = cur === \\\"fullscreen\\\" || cur === \\\"pip\\\";\\n request(inPanel ? \\\"inline\\\" : mode)[\\\"catch\\\"](function () {});\\n });\\n return btn;\\n }\\n\\n function mountToggle(el, mode) {\\n ensureStyle();\\n mode = mode || \\\"fullscreen\\\";\\n var btn = makeButton(mode);\\n if (el) el.appendChild(btn);\\n else ensureBar().appendChild(btn);\\n buttons.push(btn);\\n syncButton(btn);\\n return btn;\\n }\\n\\n function onChange(cb) {\\n if (typeof cb !== \\\"function\\\") return function () {};\\n listeners.push(cb);\\n try { cb(get(), ctx); } catch (_) {}\\n return function () {\\n var i = listeners.indexOf(cb);\\n if (i >= 0) listeners.splice(i, 1);\\n };\\n }\\n\\n ready(function () {\\n ensureStyle();\\n // Only the first installer in a document owns the floating bar: a\\n // built-in panel served standalone gets the injected window.McpApp\\n // bridge's copy AND its own, and two stacked buttons is a bug.\\n if (autoMount && !document.getElementById(BAR_ID)) {\\n ensureBar();\\n mountToggle(null, \\\"pip\\\");\\n mountToggle(null, \\\"fullscreen\\\");\\n }\\n sync();\\n });\\n\\n // The diagnostic both copies had, now on BOTH paths: it used to exist\\n // only in the built-in panel bridge, so an installed app debugged in\\n // Claude Desktop (which advertises no fullscreen, hence no button)\\n // printed nothing at all and left the user with no way to tell \\\"the\\n // host refuses\\\" from \\\"the toggle is broken\\\".\\n function observe(next) {\\n console.log('[mcp-app] displayMode=', next && next.displayMode,\\n 'availableDisplayModes=', next && next.availableDisplayModes);\\n setContext(next);\\n // What ELSE does this host send? Keys only, once — a host context can\\n // carry toolInfo (the instantiating tool call's arguments) and other\\n // host-private fields, so the names are the diagnostic and the values\\n // stay out of the console.\\n if (!loggedKeys && next && typeof next === \\\"object\\\") {\\n loggedKeys = true;\\n var keys = [];\\n for (var k in ctx) {\\n if (Object.prototype.hasOwnProperty.call(ctx, k)) keys.push(k);\\n }\\n console.log('[mcp-app] hostContext keys=', keys.sort().join(','));\\n }\\n }\\n\\n if (typeof api.onHostContext === \\\"function\\\") api.onHostContext(observe);\\n else if (typeof api.getHostContext === \\\"function\\\") observe(api.getHostContext());\\n\\n return {\\n get: get,\\n available: available,\\n request: request,\\n onChange: onChange,\\n mountToggle: mountToggle\\n };\\n };\\n})();\\nvar displayMode = window.AgentprotoUI.installDisplayMode({\\n getHostContext: getHostContext,\\n onHostContext: onHostContext,\\n requestDisplayMode: requestDisplayMode\\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"]}
|
|
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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;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 _hostCaps = null;\\nvar _standaloneApp = null;\\nfunction _isStandalone(){\\n return window.parent === window && !!window.McpApp && typeof window.McpApp.connect === 'function';\\n}\\n// ── Per-boot embed token (runtime embed-tokens.ts) ────────────────────────\\n// Panels that iframe the daemon's standalone app host carry this proof so\\n// GET /apps/:appId/ui?embed=1&et=... can drop its anti-framing headers for\\n// MCP-Apps hosts whose widget context is an opaque origin (Claude Desktop).\\n// registerMcpApps replaces the placeholder with a real per-boot token when\\n// serving the panel as a resource; every other render (tests, standalone\\n// tab, VS Code HTTP-iframe panel — the latter two already pass\\n// iframeEmbedAllowed's origin checks) keeps the literal, and\\n// withEmbedToken() then degrades to an identity function.\\nwindow.__AGENPROTO_EMBED_TOKEN__ = \\\"__AGENPROTO_EMBED_TOKEN__\\\";\\nfunction withEmbedToken(url){\\n var t = window.__AGENPROTO_EMBED_TOKEN__;\\n if (!t || t === '__AGENPROTO_EMBED_TOKEN__') return url;\\n return url + (url.indexOf('?') >= 0 ? '&' : '?') + 'et=' + encodeURIComponent(t);\\n}\\nfunction post(msg){ window.parent.postMessage(msg, '*'); }\\nfunction getHostContext(){ return _hostContext; }\\nfunction getHostCapabilities(){ return _hostCaps; }\\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 display-mode 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 // McpUiInitializeResult.hostCapabilities (ext-apps spec) — e.g. openLinks,\\n // \\\"Host supports opening external URLs\\\". Captured once, read via\\n // getHostCapabilities() by panels that need to branch on it.\\n if (result && result.hostCapabilities) _hostCaps = result.hostCapabilities;\\n rpcNotify('ui/notifications/initialized', {});\\n });\\n}\\nfunction requestDisplayMode(mode){\\n return rpcRequest('ui/request-display-mode', {mode: mode});\\n}\\nfunction openLink(url){\\n if (_standaloneApp && typeof _standaloneApp.openLink === 'function') return _standaloneApp.openLink(url);\\n return rpcRequest('ui/open-link', {url: url});\\n}\\n// An MCP tool-result envelope: {content: [{type: 'text', text}, ...]}.\\nfunction _isEnvelope(v){\\n return !!v && typeof v === 'object' && Array.isArray(v.content) &&\\n !!v.content[0] && v.content[0].type === 'text' && typeof v.content[0].text === 'string';\\n}\\n// Peels every envelope layer. The standalone path (POST /apps/:id/tool-call,\\n// runtime app-tools.ts dispatchAllowlistedAppTool) wraps an already-wrapped\\n// builtin result a second time, and an inner isError rides inside that outer\\n// success — so isError is honoured at each layer, not just the first.\\nfunction _unwrapToolResult(result){\\n var env = result;\\n while (true){\\n var first = env && env.content && env.content[0];\\n var text = (first && first.text) || '';\\n if (env && env.isError) throw new Error(text || 'tool error');\\n try { var value = JSON.parse(text || '{}'); } catch(_) { return text; }\\n if (!_isEnvelope(value)) return value;\\n env = value;\\n }\\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(_unwrapToolResult);\\n}\\n\\n// ── Display-mode toggle (shared: @agentproto/app-client/display-mode) ──\\n// The installer is inlined verbatim (it is self-contained and idempotent),\\n// then handed this panel's own JSON-RPC plumbing. NO auto-request: the\\n// panel stays inline until the user clicks, and the buttons only appear for\\n// the modes the host advertises in hostContext.availableDisplayModes.\\n(function () {\\n if (window.AgentprotoUI && window.AgentprotoUI.installDisplayMode) return;\\n window.AgentprotoUI = window.AgentprotoUI || {};\\n\\n var STYLE_ID = \\\"agentproto-display-mode-style\\\";\\n var BAR_ID = \\\"agentproto-display-mode-bar\\\";\\n // Gap between the host's safe area (or the viewport edge) and the button.\\n var EDGE_GAP = 8;\\n\\n // Glyph + accessible label per mode. The button is an icon; the words go\\n // to title/aria-label so the control stays compact without going mute to\\n // a screen reader.\\n var LABELS = {\\n fullscreen: {\\n on: \\\"\\\\u2921\\\", off: \\\"\\\\u2922\\\",\\n onLabel: \\\"R\\\\u00e9duire\\\", offLabel: \\\"Agrandir\\\"\\n },\\n pip: {\\n on: \\\"\\\\u2921\\\", off: \\\"\\\\uD83D\\\\uDCCC\\\",\\n onLabel: \\\"D\\\\u00e9tacher\\\", offLabel: \\\"\\\\u00c9pingler sur le c\\\\u00f4t\\\\u00e9 (pip)\\\"\\n }\\n };\\n\\n // Every colour and both offsets resolve through an app-overridable custom\\n // property first, so an app can restyle/reposition the button with a\\n // stylesheet and never touch this script. The safe-area vars are what the\\n // host's own chrome feeds (see applyInsets), sitting UNDER the app's.\\n function ensureStyle() {\\n if (!document.head || document.getElementById(STYLE_ID)) return;\\n var style = document.createElement(\\\"style\\\");\\n style.id = STYLE_ID;\\n style.textContent =\\n \\\"#\\\" + BAR_ID + \\\"{position:fixed;display:flex;gap:6px;align-items:center;z-index:10;\\\" +\\n \\\"top:var(--agentproto-dm-top,var(--agentproto-dm-safe-top,8px));\\\" +\\n \\\"right:var(--agentproto-dm-right,var(--agentproto-dm-safe-right,8px))}\\\" +\\n \\\".agentproto-dm-btn{display:inline-flex;align-items:center;justify-content:center;\\\" +\\n \\\"box-sizing:border-box;width:28px;height:28px;padding:0;margin:0;border-radius:6px;\\\" +\\n \\\"cursor:pointer;-webkit-appearance:none;appearance:none;\\\" +\\n \\\"font:600 14px/1 system-ui,-apple-system,sans-serif;\\\" +\\n \\\"border:1px solid var(--agentproto-dm-border,#d0d0d0);\\\" +\\n \\\"background:var(--agentproto-dm-bg,#fff);\\\" +\\n \\\"color:var(--agentproto-dm-fg,#1a1a1a);\\\" +\\n \\\"box-shadow:0 1px 4px rgba(0,0,0,.18)}\\\" +\\n \\\".agentproto-dm-btn:hover{filter:brightness(.95)}\\\" +\\n \\\".agentproto-dm-btn:focus-visible{outline:2px solid var(--agentproto-dm-fg,#1a1a1a);outline-offset:2px}\\\" +\\n \\\".agentproto-dm-btn[data-theme=\\\\\\\"dark\\\\\\\"]{border-color:var(--agentproto-dm-border,#555);background:var(--agentproto-dm-bg,#2a2a2a);color:var(--agentproto-dm-fg,#f0f0f0);box-shadow:0 1px 4px rgba(0,0,0,.5)}\\\" +\\n \\\"@media (prefers-color-scheme:dark){\\\" +\\n \\\".agentproto-dm-btn:not([data-theme=\\\\\\\"light\\\\\\\"]){border-color:var(--agentproto-dm-border,#555);background:var(--agentproto-dm-bg,#2a2a2a);color:var(--agentproto-dm-fg,#f0f0f0);box-shadow:0 1px 4px rgba(0,0,0,.5)}}\\\";\\n document.head.appendChild(style);\\n }\\n\\n function ready(fn) {\\n if (document.body) fn();\\n else document.addEventListener(\\\"DOMContentLoaded\\\", fn);\\n }\\n\\n function metaToggle() {\\n var el = document.querySelector('meta[name=\\\"agentproto-display-toggle\\\"]');\\n if (!el) return \\\"\\\";\\n return (el.getAttribute(\\\"content\\\") || \\\"\\\").toLowerCase().replace(/^\\\\s+|\\\\s+$/g, \\\"\\\");\\n }\\n\\n function ensureBar() {\\n var bar = document.getElementById(BAR_ID);\\n if (bar) return bar;\\n bar = document.createElement(\\\"div\\\");\\n bar.id = BAR_ID;\\n document.body.appendChild(bar);\\n return bar;\\n }\\n\\n function px(v) {\\n return typeof v === \\\"number\\\" && isFinite(v) && v > 0 ? v : 0;\\n }\\n\\n window.AgentprotoUI.installDisplayMode = function (api, opts) {\\n api = api || {};\\n opts = opts || {};\\n\\n var ctx = {};\\n var listeners = [];\\n var buttons = [];\\n // Modes the host has refused once. Checked before availability, so a\\n // later host-context-changed can never resurrect a dead button.\\n var refused = {};\\n var loggedKeys = false;\\n\\n var setting = opts.toggle || metaToggle() || \\\"auto\\\";\\n var optimistic = setting === \\\"optimistic\\\";\\n var autoMount = setting !== \\\"none\\\";\\n\\n function get() {\\n return ctx.displayMode || \\\"inline\\\";\\n }\\n\\n function available() {\\n var a = ctx.availableDisplayModes;\\n return a && a.slice ? a.slice() : [];\\n }\\n\\n function supports(mode) {\\n if (refused[mode]) return false;\\n var a = ctx.availableDisplayModes;\\n if (a && a.indexOf && a.indexOf(mode) >= 0) return true;\\n // Optimistic: the host advertises nothing (Claude Desktop, some\\n // embedders) but may still honour the request. Fullscreen only — pip\\n // is niche enough that guessing at it is just noise.\\n return optimistic && mode === \\\"fullscreen\\\";\\n }\\n\\n // Host chrome to stay clear of. Published as custom properties rather\\n // than inline styles so an app's own --agentproto-dm-top/right still\\n // wins (an inline style would beat any stylesheet it could write).\\n function applyInsets() {\\n var insets = ctx.safeAreaInsets || {};\\n var root = document.documentElement;\\n if (!root || !root.style || !root.style.setProperty) return;\\n root.style.setProperty(\\\"--agentproto-dm-safe-top\\\", (px(insets.top) + EDGE_GAP) + \\\"px\\\");\\n root.style.setProperty(\\\"--agentproto-dm-safe-right\\\", (px(insets.right) + EDGE_GAP) + \\\"px\\\");\\n }\\n\\n function syncButton(btn) {\\n var mode = btn.getAttribute(\\\"data-agentproto-dm\\\") || \\\"fullscreen\\\";\\n var labels = LABELS[mode] || LABELS.fullscreen;\\n var active = get() === mode;\\n btn.style.display = supports(mode) ? \\\"\\\" : \\\"none\\\";\\n btn.textContent = active ? labels.on : labels.off;\\n var label = active ? labels.onLabel : labels.offLabel;\\n btn.title = label;\\n btn.setAttribute(\\\"aria-label\\\", label);\\n btn.setAttribute(\\\"aria-pressed\\\", active ? \\\"true\\\" : \\\"false\\\");\\n if (ctx.theme === \\\"dark\\\" || ctx.theme === \\\"light\\\") btn.setAttribute(\\\"data-theme\\\", ctx.theme);\\n else btn.removeAttribute(\\\"data-theme\\\");\\n }\\n\\n function sync() {\\n applyInsets();\\n for (var i = buttons.length - 1; i >= 0; i--) {\\n // An app that removed its inline toggle stops being our problem.\\n if (buttons[i].isConnected === false) { buttons.splice(i, 1); continue; }\\n syncButton(buttons[i]);\\n }\\n }\\n\\n function emit() {\\n var mode = get();\\n for (var i = 0; i < listeners.length; i++) {\\n try { listeners[i](mode, ctx); } catch (_) {}\\n }\\n }\\n\\n // host-context-changed carries only the changed keys — merge, matching\\n // the official ext-apps App behaviour.\\n function setContext(next) {\\n if (!next || typeof next !== \\\"object\\\") return;\\n for (var k in next) {\\n if (Object.prototype.hasOwnProperty.call(next, k)) ctx[k] = next[k];\\n }\\n sync();\\n emit();\\n }\\n\\n function retire(mode) {\\n refused[mode] = true;\\n sync();\\n }\\n\\n function request(mode) {\\n var pending;\\n try {\\n pending = api.requestDisplayMode\\n ? api.requestDisplayMode(mode)\\n : Promise.reject(new Error(\\\"display mode: no host\\\"));\\n } catch (err) {\\n pending = Promise.reject(err);\\n }\\n return Promise.resolve(pending).then(function (result) {\\n // McpUiRequestDisplayModeResult.mode is the mode actually set, and\\n // the spec lets it differ from the one asked for — so a host that\\n // answers \\\"still inline\\\" is a refusal, not a success.\\n var settled = (result && result.mode) || mode;\\n if (optimistic && mode !== \\\"inline\\\" && settled !== mode) {\\n retire(mode);\\n throw new Error(\\\"host refused display mode: \\\" + mode);\\n }\\n // Hosts are not required to follow up with host-context-changed —\\n // trust the result so the glyph flips either way.\\n setContext({ displayMode: settled });\\n return settled;\\n }, function (err) {\\n if (optimistic && mode !== \\\"inline\\\") retire(mode);\\n throw err;\\n });\\n }\\n\\n function makeButton(mode) {\\n var btn = document.createElement(\\\"button\\\");\\n btn.type = \\\"button\\\";\\n btn.className = \\\"agentproto-dm-btn\\\";\\n // Stable id for the floating pair (styling hooks, tests). An app that\\n // mounts extra inline toggles gets unnamed ones rather than duplicates.\\n var id = mode === \\\"fullscreen\\\"\\n ? \\\"agentproto-display-mode\\\"\\n : \\\"agentproto-display-mode-\\\" + mode;\\n if (!document.getElementById(id)) btn.id = id;\\n btn.setAttribute(\\\"data-agentproto-dm\\\", mode);\\n btn.style.display = \\\"none\\\";\\n btn.addEventListener(\\\"click\\\", function () {\\n var cur = get();\\n // Same rule both copies had: anything that isn't inline collapses\\n // back to inline, so the button is always an escape hatch.\\n var inPanel = cur === \\\"fullscreen\\\" || cur === \\\"pip\\\";\\n request(inPanel ? \\\"inline\\\" : mode)[\\\"catch\\\"](function () {});\\n });\\n return btn;\\n }\\n\\n function mountToggle(el, mode) {\\n ensureStyle();\\n mode = mode || \\\"fullscreen\\\";\\n var btn = makeButton(mode);\\n if (el) el.appendChild(btn);\\n else ensureBar().appendChild(btn);\\n buttons.push(btn);\\n syncButton(btn);\\n return btn;\\n }\\n\\n function onChange(cb) {\\n if (typeof cb !== \\\"function\\\") return function () {};\\n listeners.push(cb);\\n try { cb(get(), ctx); } catch (_) {}\\n return function () {\\n var i = listeners.indexOf(cb);\\n if (i >= 0) listeners.splice(i, 1);\\n };\\n }\\n\\n ready(function () {\\n ensureStyle();\\n // Only the first installer in a document owns the floating bar: a\\n // built-in panel served standalone gets the injected window.McpApp\\n // bridge's copy AND its own, and two stacked buttons is a bug.\\n if (autoMount && !document.getElementById(BAR_ID)) {\\n ensureBar();\\n mountToggle(null, \\\"pip\\\");\\n mountToggle(null, \\\"fullscreen\\\");\\n }\\n sync();\\n });\\n\\n // The diagnostic both copies had, now on BOTH paths: it used to exist\\n // only in the built-in panel bridge, so an installed app debugged in\\n // Claude Desktop (which advertises no fullscreen, hence no button)\\n // printed nothing at all and left the user with no way to tell \\\"the\\n // host refuses\\\" from \\\"the toggle is broken\\\".\\n function observe(next) {\\n console.log('[mcp-app] displayMode=', next && next.displayMode,\\n 'availableDisplayModes=', next && next.availableDisplayModes);\\n setContext(next);\\n // What ELSE does this host send? Keys only, once — a host context can\\n // carry toolInfo (the instantiating tool call's arguments) and other\\n // host-private fields, so the names are the diagnostic and the values\\n // stay out of the console.\\n if (!loggedKeys && next && typeof next === \\\"object\\\") {\\n loggedKeys = true;\\n var keys = [];\\n for (var k in ctx) {\\n if (Object.prototype.hasOwnProperty.call(ctx, k)) keys.push(k);\\n }\\n console.log('[mcp-app] hostContext keys=', keys.sort().join(','));\\n }\\n }\\n\\n if (typeof api.onHostContext === \\\"function\\\") api.onHostContext(observe);\\n else if (typeof api.getHostContext === \\\"function\\\") observe(api.getHostContext());\\n\\n return {\\n get: get,\\n available: available,\\n request: request,\\n onChange: onChange,\\n mountToggle: mountToggle\\n };\\n };\\n})();\\nvar displayMode = window.AgentprotoUI.installDisplayMode({\\n getHostContext: getHostContext,\\n onHostContext: onHostContext,\\n requestDisplayMode: requestDisplayMode\\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.21.1",
|
|
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",
|
|
@@ -173,9 +173,9 @@
|
|
|
173
173
|
},
|
|
174
174
|
"dependencies": {
|
|
175
175
|
"zod": "^4.6.5",
|
|
176
|
-
"@agentproto/agent": "0.2.5",
|
|
177
176
|
"@agentproto/app-client": "0.4.1",
|
|
178
|
-
"@agentproto/app-kit": "1.
|
|
177
|
+
"@agentproto/app-kit": "1.7.1",
|
|
178
|
+
"@agentproto/agent": "0.2.5",
|
|
179
179
|
"@agentproto/workflow": "0.7.1"
|
|
180
180
|
},
|
|
181
181
|
"peerDependencies": {
|
|
@@ -44,6 +44,14 @@ inputs:
|
|
|
44
44
|
with a stored verdict are never re-reviewed, so daily runs walk the
|
|
45
45
|
backlog.
|
|
46
46
|
default: 40
|
|
47
|
+
reviewHeldWorktrees:
|
|
48
|
+
type: boolean
|
|
49
|
+
description: >-
|
|
50
|
+
Also review branches held only because they're checked out in an idle,
|
|
51
|
+
clean linked worktree with no open PR (tip at least 3 days old). The
|
|
52
|
+
report lists each verdict and the removal command when the reviewer
|
|
53
|
+
agreed; nothing is removed automatically.
|
|
54
|
+
default: true
|
|
47
55
|
notify:
|
|
48
56
|
type: object
|
|
49
57
|
description: >-
|
|
@@ -68,11 +76,15 @@ steps:
|
|
|
68
76
|
- id: worktreeGcPlan
|
|
69
77
|
kind: tool
|
|
70
78
|
name: Plan worktree gc
|
|
79
|
+
description: >-
|
|
80
|
+
`wait: true` — without it worktree_gc falls back to a background job
|
|
81
|
+
after 25 s and returns a bare jobId instead of the plan.
|
|
71
82
|
tool: worktree_gc
|
|
72
83
|
inputs:
|
|
73
84
|
repoRoot: $input.repoRoot
|
|
74
85
|
workspaceSlug: $input.workspaceSlug
|
|
75
86
|
apply: false
|
|
87
|
+
wait: true
|
|
76
88
|
|
|
77
89
|
- id: branchGcPlan
|
|
78
90
|
kind: tool
|
|
@@ -89,7 +101,10 @@ steps:
|
|
|
89
101
|
name: Queue unreviewed review candidates, one per branch name
|
|
90
102
|
description: >-
|
|
91
103
|
Entry-based — no string expression language for `compute` in the
|
|
92
|
-
declarative manifest.
|
|
104
|
+
declarative manifest. With `reviewHeldWorktrees` (default true) it also
|
|
105
|
+
queues branches branch_gc held only for `worktree` when worktreeGcPlan
|
|
106
|
+
shows that worktree idle, clean and without an open PR (entry.mjs's
|
|
107
|
+
reviewableWorktrees). See entry.mjs's buildReviewQueue: dedupes by tip
|
|
93
108
|
sha, then merges candidates whose refs share one branch name (a local
|
|
94
109
|
branch and its remote twin review together even when their tips
|
|
95
110
|
diverged — the newest tip is primary, the older ones ride along as
|
|
@@ -175,6 +190,7 @@ steps:
|
|
|
175
190
|
workspaceSlug: $input.workspaceSlug
|
|
176
191
|
apply: $input.applyMerged
|
|
177
192
|
salvageDirty: false
|
|
193
|
+
wait: true
|
|
178
194
|
|
|
179
195
|
- id: branchGcApply
|
|
180
196
|
kind: tool
|
|
@@ -41,6 +41,9 @@ const DEFAULT_MAX_REVIEWS = 40
|
|
|
41
41
|
/** Longest name list the markdown report inlines; the full lists are in the
|
|
42
42
|
* run output (`gaps`, …). */
|
|
43
43
|
const REPORT_LIST_CAP = 20
|
|
44
|
+
/** A worktree-held branch is reviewed only once its tip is this old — the
|
|
45
|
+
* same floor as branch_gc's `minAgeDays` default for `review`. */
|
|
46
|
+
const HELD_WORKTREE_MIN_AGE_DAYS = 3
|
|
44
47
|
|
|
45
48
|
const REVIEWER_REF = "@agentproto/repo-maintenance-reviewer"
|
|
46
49
|
|
|
@@ -84,6 +87,11 @@ const REVIEW_PROMPT =
|
|
|
84
87
|
"(the tree base would have if this branch merged cleanly, or null when the merge conflicts): " +
|
|
85
88
|
"{{#item.mergedTree}}{{item.mergedTree}}{{/item.mergedTree}}{{^item.mergedTree}}null{{/item.mergedTree}}. Ahead {{item.ahead}}, behind {{item.behind}}. Push state: {{item.pushed}}. " +
|
|
86
89
|
"Refs of this branch (across its tips): {{item.refs}}." +
|
|
90
|
+
"{{#item.worktree}}\n\nThis branch is checked out in the linked worktree {{item.worktree}} — " +
|
|
91
|
+
"idle (no live session), clean tree, no open PR — which is the only reason branch gc held it. " +
|
|
92
|
+
"Your verdict decides whether that worktree (and the branch) can be removed: agree only when " +
|
|
93
|
+
"everything committed on it is in base or not worth keeping. Never touch that worktree; read " +
|
|
94
|
+
"the branch through its sha like any other.{{/item.worktree}}" +
|
|
87
95
|
"{{#item.otherTips}}\n\nThis branch name has OLDER tip(s) besides {{item.sha}} — " +
|
|
88
96
|
"{{item.otherTips}} (JSON array of { sha, refs }; each sha may carry work the newer tip does " +
|
|
89
97
|
"not). The coverage above describes ONLY the primary tip {{item.sha}}. Inspect EVERY sha " +
|
|
@@ -147,6 +155,57 @@ const VERDICT_NEEDS = (id, checkId) => ({
|
|
|
147
155
|
},
|
|
148
156
|
})
|
|
149
157
|
|
|
158
|
+
/** Linked worktrees whose branch is worth an LLM review: idle (no live
|
|
159
|
+
* session), clean tree, and no open PR — `path → branch`. branch_gc holds
|
|
160
|
+
* every branch checked out in a worktree (`holdReason: "worktree"`) and
|
|
161
|
+
* worktree_gc holds every worktree whose branch isn't provably merged, so
|
|
162
|
+
* without this an abandoned-but-unmerged worktree is held by both forever.
|
|
163
|
+
* `worktree_gc`'s dry run never lists the main checkout, so its branch is
|
|
164
|
+
* never a candidate. A dirty tree is left out: its WIP isn't on the tip a
|
|
165
|
+
* reviewer reads. */
|
|
166
|
+
export function reviewableWorktrees(worktreeGcPlanResult) {
|
|
167
|
+
const out = new Map()
|
|
168
|
+
const entries = Array.isArray(worktreeGcPlanResult?.plan) ? worktreeGcPlanResult.plan : []
|
|
169
|
+
for (const w of entries) {
|
|
170
|
+
if (w?.class !== "hold" || !w.branch || !w.path) continue
|
|
171
|
+
if (w.liveness?.state !== "idle" || treeState(w.tree) !== "clean") continue
|
|
172
|
+
if (w.integration?.state === "open") continue
|
|
173
|
+
out.set(w.path, w.branch)
|
|
174
|
+
}
|
|
175
|
+
return out
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** A worktree_gc plan entry's tree state. The daemon's `worktree_gc` tool
|
|
179
|
+
* flattens it to the bare discriminant (`tree: "clean"`, `toGcPlanEntryView`
|
|
180
|
+
* in packages/cli/src/commands/worktree.ts); the engine's own `GcPlanEntry`
|
|
181
|
+
* (and `agentproto worktree gc --json`) carries `{ state, … }`. */
|
|
182
|
+
function treeState(tree) {
|
|
183
|
+
return typeof tree === "string" ? tree : tree?.state
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** The worktree path a branch_gc entry is held by, when that hold is the only
|
|
187
|
+
* thing between it and `review`: held for `worktree`, unmerged, old enough,
|
|
188
|
+
* and the worktree is one {@link reviewableWorktrees} kept. Else null. */
|
|
189
|
+
function heldWorktreeOf(e, heldWorktrees) {
|
|
190
|
+
if (e?.class !== "hold" || e.holdReason !== "worktree" || e.status !== "unmerged") return null
|
|
191
|
+
if ((e.ageDays ?? 0) < HELD_WORKTREE_MIN_AGE_DAYS) return null
|
|
192
|
+
return heldWorktrees.get(e.holdDetail) === e.name ? e.holdDetail : null
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** One line per reviewed held worktree (from the verify plan's stored
|
|
196
|
+
* verdicts): what the reviewer said, and — when it agreed nothing of value
|
|
197
|
+
* is lost — the command that removes it. Removal stays a human step. */
|
|
198
|
+
export function heldWorktreeVerdicts(branchGcVerifyResult, worktreeGcPlanResult) {
|
|
199
|
+
const held = reviewableWorktrees(worktreeGcPlanResult)
|
|
200
|
+
const byPath = new Map()
|
|
201
|
+
for (const e of branchGcVerifyResult?.plan?.entries ?? []) {
|
|
202
|
+
const path = heldWorktreeOf(e, held)
|
|
203
|
+
if (!path || !e.verdict || byPath.has(path)) continue
|
|
204
|
+
byPath.set(path, { name: e.name, path, sha: e.sha, triage: e.verdict.triage, agree: e.verdict.agree ?? null })
|
|
205
|
+
}
|
|
206
|
+
return [...byPath.values()]
|
|
207
|
+
}
|
|
208
|
+
|
|
150
209
|
/** One `review`-class branch_gc plan entry per BRANCH NAME — a local branch
|
|
151
210
|
* and its remote twin share one review instead of costing the reviewer two
|
|
152
211
|
* turns, whether they share a tip (grouped by sha, as before) or DIVERGED
|
|
@@ -167,13 +226,15 @@ const VERDICT_NEEDS = (id, checkId) => ({
|
|
|
167
226
|
* queued with only its unreviewed tips. Returns the queue (oldest first,
|
|
168
227
|
* then the larger residual) plus how many names were skipped as already
|
|
169
228
|
* reviewed. */
|
|
170
|
-
export function buildReviewQueue(branchGcPlanResult) {
|
|
229
|
+
export function buildReviewQueue(branchGcPlanResult, worktreeGcPlanResult, opts = {}) {
|
|
171
230
|
const plan = branchGcPlanResult?.plan
|
|
172
231
|
const entries = Array.isArray(plan?.entries) ? plan.entries : []
|
|
232
|
+
const heldWorktrees = opts.reviewHeldWorktrees === false ? new Map() : reviewableWorktrees(worktreeGcPlanResult)
|
|
233
|
+
const reviewable = e => e.class === "review" || heldWorktreeOf(e, heldWorktrees) !== null
|
|
173
234
|
const bySha = new Map()
|
|
174
235
|
const dates = new Map()
|
|
175
236
|
for (const e of entries) {
|
|
176
|
-
if (e
|
|
237
|
+
if (!reviewable(e)) continue
|
|
177
238
|
dates.set(e.sha, e.date ?? "")
|
|
178
239
|
if (e.verdict) continue
|
|
179
240
|
const seen = bySha.get(e.sha)
|
|
@@ -205,6 +266,7 @@ export function buildReviewQueue(branchGcPlanResult) {
|
|
|
205
266
|
residualFiles: e.residualFiles ?? [],
|
|
206
267
|
residualFileCount: e.residualFileCount ?? 0,
|
|
207
268
|
refs: [e.ref],
|
|
269
|
+
...(heldWorktreeOf(e, heldWorktrees) ? { worktree: heldWorktreeOf(e, heldWorktrees) } : {}),
|
|
208
270
|
})
|
|
209
271
|
}
|
|
210
272
|
// Merge candidates whose refs share one branch NAME: `foo` and `origin/foo`
|
|
@@ -241,7 +303,7 @@ export function buildReviewQueue(branchGcPlanResult) {
|
|
|
241
303
|
const pendingNames = new Set(queue.map(c => c.name))
|
|
242
304
|
const fullyReviewed = new Set()
|
|
243
305
|
for (const e of entries) {
|
|
244
|
-
if (e
|
|
306
|
+
if (reviewable(e) && e.verdict && !pendingNames.has(e.name)) fullyReviewed.add(e.name)
|
|
245
307
|
}
|
|
246
308
|
return { queue, alreadyReviewed: fullyReviewed.size }
|
|
247
309
|
}
|
|
@@ -441,6 +503,19 @@ export function buildReport(b) {
|
|
|
441
503
|
} else if (reviewCandidates.length > 0 && reviewOutcome.skipped === 0) {
|
|
442
504
|
lines.push("- every review candidate has a recorded verdict")
|
|
443
505
|
}
|
|
506
|
+
const heldVerdicts = b.input?.reviewHeldWorktrees === false ? [] : heldWorktreeVerdicts(b.steps.branchGcVerify, wtPlan)
|
|
507
|
+
if (heldVerdicts.length > 0) {
|
|
508
|
+
lines.push("")
|
|
509
|
+
lines.push(`## Held worktrees reviewed (${heldVerdicts.length})`)
|
|
510
|
+
lines.push("Idle, clean worktrees whose branch isn't provably merged — gc holds them; the reviewer's verdict says whether they can go. Removal is manual.")
|
|
511
|
+
for (const v of heldVerdicts.slice(0, REPORT_LIST_CAP)) {
|
|
512
|
+
lines.push(
|
|
513
|
+
`- \`${v.name}\` — ${v.triage}` +
|
|
514
|
+
(v.agree === true ? ` — nothing of value lost: \`agentproto worktree rm ${v.path}\`` : ` — keep (${v.path})`),
|
|
515
|
+
)
|
|
516
|
+
}
|
|
517
|
+
if (heldVerdicts.length > REPORT_LIST_CAP) lines.push(`- … and ${heldVerdicts.length - REPORT_LIST_CAP} more`)
|
|
518
|
+
}
|
|
444
519
|
lines.push("")
|
|
445
520
|
lines.push("## Apply")
|
|
446
521
|
if (!applyMerged) {
|
|
@@ -485,6 +560,7 @@ export default {
|
|
|
485
560
|
reviewModelSmall: { type: "string", description: `Model for a review candidate with residualFileCount <= 3. Default: the \`${ROLE_REVIEW_SMALL}\` model role (repo agentproto.json \`models\` > daemon config \`models\` > built-in).` },
|
|
486
561
|
reviewModelLarge: { type: "string", description: `Model for a review candidate with residualFileCount > 3, and the retry reviewer. Default: the \`${ROLE_REVIEW_LARGE}\` model role (repo agentproto.json \`models\` > daemon config \`models\` > built-in).` },
|
|
487
562
|
maxReviews: { type: "number", description: `Most review candidates to review this run — newest tip first, then the larger residual. The rest are reported as not reviewed this run; tips with a stored verdict are never re-reviewed, so daily runs walk the backlog. Default ${DEFAULT_MAX_REVIEWS}.`, default: DEFAULT_MAX_REVIEWS },
|
|
563
|
+
reviewHeldWorktrees: { type: "boolean", description: `Also review branches held only because they're checked out in an idle, clean linked worktree with no open PR (tip ≥ ${HELD_WORKTREE_MIN_AGE_DAYS}d old). The report lists each verdict and the removal command when the reviewer agreed; nothing is removed automatically. Default true.`, default: true },
|
|
488
564
|
notify: {
|
|
489
565
|
type: "object",
|
|
490
566
|
description: "Optional agentpush `to` target ({ channel, address }) to notify with the report when set. Omit for no notification.",
|
|
@@ -510,7 +586,11 @@ export default {
|
|
|
510
586
|
id: "worktreeGcPlan",
|
|
511
587
|
kind: "tool",
|
|
512
588
|
tool: "worktree_gc",
|
|
513
|
-
|
|
589
|
+
// `wait: true` — without it worktree_gc returns `{ jobId, status:
|
|
590
|
+
// "running" }` after its 25 s default `waitMs`, and on a repo with
|
|
591
|
+
// dozens of worktrees the report would count 0 classified. branch_gc
|
|
592
|
+
// already blocks by default.
|
|
593
|
+
inputs: { repoRoot: "$input.repoRoot", workspaceSlug: "$input.workspaceSlug", apply: false, wait: true },
|
|
514
594
|
},
|
|
515
595
|
{
|
|
516
596
|
id: "branchGcPlan",
|
|
@@ -521,7 +601,7 @@ export default {
|
|
|
521
601
|
{
|
|
522
602
|
id: "reviewQueue",
|
|
523
603
|
kind: "transform",
|
|
524
|
-
compute: b => buildReviewQueue(b.steps.branchGcPlan),
|
|
604
|
+
compute: b => buildReviewQueue(b.steps.branchGcPlan, b.steps.worktreeGcPlan, { reviewHeldWorktrees: b.input?.reviewHeldWorktrees !== false }),
|
|
525
605
|
},
|
|
526
606
|
{
|
|
527
607
|
id: "reviewCandidates",
|
|
@@ -629,6 +709,9 @@ export default {
|
|
|
629
709
|
workspaceSlug: "$input.workspaceSlug",
|
|
630
710
|
apply: "$input.applyMerged",
|
|
631
711
|
salvageDirty: false,
|
|
712
|
+
// Same as worktreeGcPlan: block for the real outcomes, or an apply
|
|
713
|
+
// would run unreported in the background after the workflow ends.
|
|
714
|
+
wait: true,
|
|
632
715
|
},
|
|
633
716
|
},
|
|
634
717
|
{
|
|
@@ -56,6 +56,24 @@ inputs:
|
|
|
56
56
|
Ask low-confidence idle sessions directly whether they're done. Off by
|
|
57
57
|
default — it spends a turn in someone else's conversation.
|
|
58
58
|
default: false
|
|
59
|
+
recurring:
|
|
60
|
+
type: boolean
|
|
61
|
+
description: >-
|
|
62
|
+
Set by scheduled runs (the hourly routine). A recurring run closes
|
|
63
|
+
sessions nobody asked about, so a keepAlive session must also have been
|
|
64
|
+
idle `keepAliveAskAfterMinutes`. Default false = an on-demand run, which
|
|
65
|
+
applies no such delay.
|
|
66
|
+
default: false
|
|
67
|
+
keepAliveAskAfterMinutes:
|
|
68
|
+
type: number
|
|
69
|
+
description: >-
|
|
70
|
+
With `askSessions`, for `recurring` runs only: a keepAlive session whose
|
|
71
|
+
worktree is merged or clean (nothing uncommitted, nothing ahead of base)
|
|
72
|
+
is asked only once idle this many minutes; 0 disables the recurring
|
|
73
|
+
keepAlive ask. On-demand runs ignore it. keepAlive only re-lights a
|
|
74
|
+
session after a daemon restart; it does not stop a declared DONE from
|
|
75
|
+
closing it.
|
|
76
|
+
default: 1440
|
|
59
77
|
callerSessionId:
|
|
60
78
|
type: string
|
|
61
79
|
description: The calling session's id — never a candidate.
|
|
@@ -91,11 +109,11 @@ inputs:
|
|
|
91
109
|
type: array
|
|
92
110
|
description: >-
|
|
93
111
|
Origins that may be closed under the current rules. A trailing `*` is a
|
|
94
|
-
prefix wildcard. Default `["cron:*", "gate"]`. Executors (a session with
|
|
112
|
+
prefix wildcard. Default `["cron:*", "gate", "workflow", "review"]`. Executors (a session with
|
|
95
113
|
a `parentSessionId`) are closable regardless.
|
|
96
114
|
items:
|
|
97
115
|
type: string
|
|
98
|
-
default: ["cron:*", "gate"]
|
|
116
|
+
default: ["cron:*", "gate", "workflow", "review"]
|
|
99
117
|
outputs: {}
|
|
100
118
|
steps:
|
|
101
119
|
- id: modelRoles
|
|
@@ -172,6 +190,7 @@ steps:
|
|
|
172
190
|
sessionIds: [$item.sessionId]
|
|
173
191
|
verdict: $item.verdict
|
|
174
192
|
note: $item.note
|
|
193
|
+
wait: true
|
|
175
194
|
|
|
176
195
|
- id: installedApps
|
|
177
196
|
kind: tool
|
|
@@ -341,7 +360,10 @@ steps:
|
|
|
341
360
|
- id: askQueue
|
|
342
361
|
kind: transform
|
|
343
362
|
name: Low-confidence idle sessions to ask directly
|
|
344
|
-
description:
|
|
363
|
+
description: >-
|
|
364
|
+
Entry-based. Empty unless `askSessions`. A keepAlive session is included
|
|
365
|
+
only with a merged/clean worktree, and on a `recurring` run only once
|
|
366
|
+
idle `keepAliveAskAfterMinutes` (default 1440, 0 = never).
|
|
345
367
|
|
|
346
368
|
- id: ask
|
|
347
369
|
kind: map
|
|
@@ -388,6 +410,7 @@ steps:
|
|
|
388
410
|
verdict: $item.verdict
|
|
389
411
|
judgedBy: $item.judgedBy
|
|
390
412
|
note: $item.note
|
|
413
|
+
wait: true
|
|
391
414
|
|
|
392
415
|
- id: memoryWriteQueue
|
|
393
416
|
kind: transform
|
|
@@ -486,8 +509,16 @@ as observed, never nudged.
|
|
|
486
509
|
- `session_wrapup_apply` re-classifies each id right before acting and always
|
|
487
510
|
refuses `keep`-class ids; this workflow never feeds it one.
|
|
488
511
|
- Rules only ever close `close`/`stuck` ids; a `keepAlive` session is never
|
|
489
|
-
in those classes, so only a confident judge verdict (with
|
|
490
|
-
close it — as FIX-9A allows.
|
|
512
|
+
in those classes, so only a confident judge verdict or a declared DONE (with
|
|
513
|
+
`judgedBy`) can close it — as FIX-9A allows. `keepAlive` means "re-light
|
|
514
|
+
after a daemon restart", not "never close": with `askSessions`, a keepAlive
|
|
515
|
+
session whose worktree is merged or clean (no uncommitted change, nothing
|
|
516
|
+
ahead of base; unknown worktree is not clean) is asked like any other. Closing
|
|
517
|
+
is an active act, so an on-demand run (`recurring: false`, e.g. `agentproto
|
|
518
|
+
steward --ask-sessions`) applies no idle delay to it beyond `idleMinutes`; only
|
|
519
|
+
a `recurring` run waits `keepAliveAskAfterMinutes` (default 1440 = 24 h, 0
|
|
520
|
+
disables), so nobody wakes up to find a session closed that they meant to
|
|
521
|
+
resume. A steward close is a deliberate end, so the sentinel never revives it.
|
|
491
522
|
- A malformed judge reply is `active` with confidence 0 — never acted on.
|
|
492
523
|
- The caller's own session (`callerSessionId`) is dropped from every list.
|
|
493
524
|
- `blocked` / `needs-input` only FLAG a session; it keeps running.
|
|
@@ -495,7 +526,8 @@ as observed, never nudged.
|
|
|
495
526
|
`origin`/`parentSessionId` runs through the pure `decideAction`
|
|
496
527
|
(`origin-policy.mjs`): a `userOrigins` match (`chat-starter`, `vscode` by
|
|
497
528
|
default) or a root with no origin and no parent is FLAG-ONLY, even with
|
|
498
|
-
`apply: true` and a confident `done` verdict. `cron:*`, `gate`,
|
|
529
|
+
`apply: true` and a confident `done` verdict. `cron:*`, `gate`, `workflow`
|
|
530
|
+
(workflow-step sessions), `review` (reviewer lanes) and
|
|
499
531
|
executors (a session with a `parentSessionId`) stay closeable. Both lists
|
|
500
532
|
are workflow inputs; a trailing `*` is a prefix wildcard.
|
|
501
533
|
- The report carries an `origin` column and the retained action (e.g.
|