@agentproto/apps 0.20.3 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents-overview/panel.mjs +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 +2 -2
- package/repo-maintenance/.agentproto/workflows/maintain/WORKFLOW.md +17 -1
- package/repo-maintenance/.agentproto/workflows/maintain/entry.mjs +80 -5
- package/session-steward/.agentproto/workflows/session-steward/WORKFLOW.md +6 -3
- package/session-steward/.agentproto/workflows/session-steward/entry.mjs +7 -3
- package/session-steward/.agentproto/workflows/session-steward/origin-policy.mjs +2 -2
- package/session-steward/README.md +1 -1
- package/session-steward/routines/session-steward-hourly/ROUTINE.md +3 -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.0",
|
|
4
4
|
"description": "@agentproto/apps — a home for ready-made agentproto apps (teams of agents + their workflows), declared with @agentproto/app-kit. Import a team (code-team, content-team, …) and use any of its agents/workflows from your own host: in-process via toMastraAgents(), or emit its manifests to disk.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agentproto",
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
"zod": "^4.6.5",
|
|
176
176
|
"@agentproto/agent": "0.2.5",
|
|
177
177
|
"@agentproto/app-client": "0.4.1",
|
|
178
|
-
"@agentproto/app-kit": "1.
|
|
178
|
+
"@agentproto/app-kit": "1.7.0",
|
|
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,49 @@ 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" || w.tree?.state !== "clean") continue
|
|
172
|
+
if (w.integration?.state === "open") continue
|
|
173
|
+
out.set(w.path, w.branch)
|
|
174
|
+
}
|
|
175
|
+
return out
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The worktree path a branch_gc entry is held by, when that hold is the only
|
|
179
|
+
* thing between it and `review`: held for `worktree`, unmerged, old enough,
|
|
180
|
+
* and the worktree is one {@link reviewableWorktrees} kept. Else null. */
|
|
181
|
+
function heldWorktreeOf(e, heldWorktrees) {
|
|
182
|
+
if (e?.class !== "hold" || e.holdReason !== "worktree" || e.status !== "unmerged") return null
|
|
183
|
+
if ((e.ageDays ?? 0) < HELD_WORKTREE_MIN_AGE_DAYS) return null
|
|
184
|
+
return heldWorktrees.get(e.holdDetail) === e.name ? e.holdDetail : null
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** One line per reviewed held worktree (from the verify plan's stored
|
|
188
|
+
* verdicts): what the reviewer said, and — when it agreed nothing of value
|
|
189
|
+
* is lost — the command that removes it. Removal stays a human step. */
|
|
190
|
+
export function heldWorktreeVerdicts(branchGcVerifyResult, worktreeGcPlanResult) {
|
|
191
|
+
const held = reviewableWorktrees(worktreeGcPlanResult)
|
|
192
|
+
const byPath = new Map()
|
|
193
|
+
for (const e of branchGcVerifyResult?.plan?.entries ?? []) {
|
|
194
|
+
const path = heldWorktreeOf(e, held)
|
|
195
|
+
if (!path || !e.verdict || byPath.has(path)) continue
|
|
196
|
+
byPath.set(path, { name: e.name, path, sha: e.sha, triage: e.verdict.triage, agree: e.verdict.agree ?? null })
|
|
197
|
+
}
|
|
198
|
+
return [...byPath.values()]
|
|
199
|
+
}
|
|
200
|
+
|
|
150
201
|
/** One `review`-class branch_gc plan entry per BRANCH NAME — a local branch
|
|
151
202
|
* and its remote twin share one review instead of costing the reviewer two
|
|
152
203
|
* turns, whether they share a tip (grouped by sha, as before) or DIVERGED
|
|
@@ -167,13 +218,15 @@ const VERDICT_NEEDS = (id, checkId) => ({
|
|
|
167
218
|
* queued with only its unreviewed tips. Returns the queue (oldest first,
|
|
168
219
|
* then the larger residual) plus how many names were skipped as already
|
|
169
220
|
* reviewed. */
|
|
170
|
-
export function buildReviewQueue(branchGcPlanResult) {
|
|
221
|
+
export function buildReviewQueue(branchGcPlanResult, worktreeGcPlanResult, opts = {}) {
|
|
171
222
|
const plan = branchGcPlanResult?.plan
|
|
172
223
|
const entries = Array.isArray(plan?.entries) ? plan.entries : []
|
|
224
|
+
const heldWorktrees = opts.reviewHeldWorktrees === false ? new Map() : reviewableWorktrees(worktreeGcPlanResult)
|
|
225
|
+
const reviewable = e => e.class === "review" || heldWorktreeOf(e, heldWorktrees) !== null
|
|
173
226
|
const bySha = new Map()
|
|
174
227
|
const dates = new Map()
|
|
175
228
|
for (const e of entries) {
|
|
176
|
-
if (e
|
|
229
|
+
if (!reviewable(e)) continue
|
|
177
230
|
dates.set(e.sha, e.date ?? "")
|
|
178
231
|
if (e.verdict) continue
|
|
179
232
|
const seen = bySha.get(e.sha)
|
|
@@ -205,6 +258,7 @@ export function buildReviewQueue(branchGcPlanResult) {
|
|
|
205
258
|
residualFiles: e.residualFiles ?? [],
|
|
206
259
|
residualFileCount: e.residualFileCount ?? 0,
|
|
207
260
|
refs: [e.ref],
|
|
261
|
+
...(heldWorktreeOf(e, heldWorktrees) ? { worktree: heldWorktreeOf(e, heldWorktrees) } : {}),
|
|
208
262
|
})
|
|
209
263
|
}
|
|
210
264
|
// Merge candidates whose refs share one branch NAME: `foo` and `origin/foo`
|
|
@@ -241,7 +295,7 @@ export function buildReviewQueue(branchGcPlanResult) {
|
|
|
241
295
|
const pendingNames = new Set(queue.map(c => c.name))
|
|
242
296
|
const fullyReviewed = new Set()
|
|
243
297
|
for (const e of entries) {
|
|
244
|
-
if (e
|
|
298
|
+
if (reviewable(e) && e.verdict && !pendingNames.has(e.name)) fullyReviewed.add(e.name)
|
|
245
299
|
}
|
|
246
300
|
return { queue, alreadyReviewed: fullyReviewed.size }
|
|
247
301
|
}
|
|
@@ -441,6 +495,19 @@ export function buildReport(b) {
|
|
|
441
495
|
} else if (reviewCandidates.length > 0 && reviewOutcome.skipped === 0) {
|
|
442
496
|
lines.push("- every review candidate has a recorded verdict")
|
|
443
497
|
}
|
|
498
|
+
const heldVerdicts = b.input?.reviewHeldWorktrees === false ? [] : heldWorktreeVerdicts(b.steps.branchGcVerify, wtPlan)
|
|
499
|
+
if (heldVerdicts.length > 0) {
|
|
500
|
+
lines.push("")
|
|
501
|
+
lines.push(`## Held worktrees reviewed (${heldVerdicts.length})`)
|
|
502
|
+
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.")
|
|
503
|
+
for (const v of heldVerdicts.slice(0, REPORT_LIST_CAP)) {
|
|
504
|
+
lines.push(
|
|
505
|
+
`- \`${v.name}\` — ${v.triage}` +
|
|
506
|
+
(v.agree === true ? ` — nothing of value lost: \`agentproto worktree rm ${v.path}\`` : ` — keep (${v.path})`),
|
|
507
|
+
)
|
|
508
|
+
}
|
|
509
|
+
if (heldVerdicts.length > REPORT_LIST_CAP) lines.push(`- … and ${heldVerdicts.length - REPORT_LIST_CAP} more`)
|
|
510
|
+
}
|
|
444
511
|
lines.push("")
|
|
445
512
|
lines.push("## Apply")
|
|
446
513
|
if (!applyMerged) {
|
|
@@ -485,6 +552,7 @@ export default {
|
|
|
485
552
|
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
553
|
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
554
|
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 },
|
|
555
|
+
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
556
|
notify: {
|
|
489
557
|
type: "object",
|
|
490
558
|
description: "Optional agentpush `to` target ({ channel, address }) to notify with the report when set. Omit for no notification.",
|
|
@@ -510,7 +578,11 @@ export default {
|
|
|
510
578
|
id: "worktreeGcPlan",
|
|
511
579
|
kind: "tool",
|
|
512
580
|
tool: "worktree_gc",
|
|
513
|
-
|
|
581
|
+
// `wait: true` — without it worktree_gc returns `{ jobId, status:
|
|
582
|
+
// "running" }` after its 25 s default `waitMs`, and on a repo with
|
|
583
|
+
// dozens of worktrees the report would count 0 classified. branch_gc
|
|
584
|
+
// already blocks by default.
|
|
585
|
+
inputs: { repoRoot: "$input.repoRoot", workspaceSlug: "$input.workspaceSlug", apply: false, wait: true },
|
|
514
586
|
},
|
|
515
587
|
{
|
|
516
588
|
id: "branchGcPlan",
|
|
@@ -521,7 +593,7 @@ export default {
|
|
|
521
593
|
{
|
|
522
594
|
id: "reviewQueue",
|
|
523
595
|
kind: "transform",
|
|
524
|
-
compute: b => buildReviewQueue(b.steps.branchGcPlan),
|
|
596
|
+
compute: b => buildReviewQueue(b.steps.branchGcPlan, b.steps.worktreeGcPlan, { reviewHeldWorktrees: b.input?.reviewHeldWorktrees !== false }),
|
|
525
597
|
},
|
|
526
598
|
{
|
|
527
599
|
id: "reviewCandidates",
|
|
@@ -629,6 +701,9 @@ export default {
|
|
|
629
701
|
workspaceSlug: "$input.workspaceSlug",
|
|
630
702
|
apply: "$input.applyMerged",
|
|
631
703
|
salvageDirty: false,
|
|
704
|
+
// Same as worktreeGcPlan: block for the real outcomes, or an apply
|
|
705
|
+
// would run unreported in the background after the workflow ends.
|
|
706
|
+
wait: true,
|
|
632
707
|
},
|
|
633
708
|
},
|
|
634
709
|
{
|
|
@@ -91,11 +91,11 @@ inputs:
|
|
|
91
91
|
type: array
|
|
92
92
|
description: >-
|
|
93
93
|
Origins that may be closed under the current rules. A trailing `*` is a
|
|
94
|
-
prefix wildcard. Default `["cron:*", "gate"]`. Executors (a session with
|
|
94
|
+
prefix wildcard. Default `["cron:*", "gate", "workflow", "review"]`. Executors (a session with
|
|
95
95
|
a `parentSessionId`) are closable regardless.
|
|
96
96
|
items:
|
|
97
97
|
type: string
|
|
98
|
-
default: ["cron:*", "gate"]
|
|
98
|
+
default: ["cron:*", "gate", "workflow", "review"]
|
|
99
99
|
outputs: {}
|
|
100
100
|
steps:
|
|
101
101
|
- id: modelRoles
|
|
@@ -172,6 +172,7 @@ steps:
|
|
|
172
172
|
sessionIds: [$item.sessionId]
|
|
173
173
|
verdict: $item.verdict
|
|
174
174
|
note: $item.note
|
|
175
|
+
wait: true
|
|
175
176
|
|
|
176
177
|
- id: installedApps
|
|
177
178
|
kind: tool
|
|
@@ -388,6 +389,7 @@ steps:
|
|
|
388
389
|
verdict: $item.verdict
|
|
389
390
|
judgedBy: $item.judgedBy
|
|
390
391
|
note: $item.note
|
|
392
|
+
wait: true
|
|
391
393
|
|
|
392
394
|
- id: memoryWriteQueue
|
|
393
395
|
kind: transform
|
|
@@ -495,7 +497,8 @@ as observed, never nudged.
|
|
|
495
497
|
`origin`/`parentSessionId` runs through the pure `decideAction`
|
|
496
498
|
(`origin-policy.mjs`): a `userOrigins` match (`chat-starter`, `vscode` by
|
|
497
499
|
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`,
|
|
500
|
+
`apply: true` and a confident `done` verdict. `cron:*`, `gate`, `workflow`
|
|
501
|
+
(workflow-step sessions), `review` (reviewer lanes) and
|
|
499
502
|
executors (a session with a `parentSessionId`) stay closeable. Both lists
|
|
500
503
|
are workflow inputs; a trailing `*` is a prefix wildcard.
|
|
501
504
|
- The report carries an `origin` column and the retained action (e.g.
|
|
@@ -1000,7 +1000,7 @@ export default {
|
|
|
1000
1000
|
appId: { type: "string", description: `Installed app whose \`app_state\` ledger holds the verdict memory. Default ${DEFAULT_APP_ID}.` },
|
|
1001
1001
|
stableVerdictPasses: { type: "number", description: `Consecutive passes on an unchanged evidence fingerprint before the judge cache stops re-judging. Default ${DEFAULT_STABLE_VERDICT_PASSES}.`, default: DEFAULT_STABLE_VERDICT_PASSES },
|
|
1002
1002
|
userOrigins: { type: "array", description: `Origins that are ALWAYS flag-only, never closed (a human is in the loop). Trailing \`*\` is a prefix wildcard. Default ${JSON.stringify(DEFAULT_USER_ORIGINS)}.`, items: { type: "string" }, default: DEFAULT_USER_ORIGINS },
|
|
1003
|
-
closableOrigins: { type: "array", description: `Origins that may be closed under the current rules (cron jobs, gates). Trailing \`*\` is a prefix wildcard. Executors (a session with a parentSessionId) are closable regardless. Default ${JSON.stringify(DEFAULT_CLOSABLE_ORIGINS)}.`, items: { type: "string" }, default: DEFAULT_CLOSABLE_ORIGINS },
|
|
1003
|
+
closableOrigins: { type: "array", description: `Origins that may be closed under the current rules (cron jobs, gates, workflow steps, reviewer lanes). Trailing \`*\` is a prefix wildcard. Executors (a session with a parentSessionId) are closable regardless. Default ${JSON.stringify(DEFAULT_CLOSABLE_ORIGINS)}.`, items: { type: "string" }, default: DEFAULT_CLOSABLE_ORIGINS },
|
|
1004
1004
|
},
|
|
1005
1005
|
outputs: {},
|
|
1006
1006
|
steps: [
|
|
@@ -1038,7 +1038,10 @@ export default {
|
|
|
1038
1038
|
id: "autoApplyOne",
|
|
1039
1039
|
kind: "tool",
|
|
1040
1040
|
tool: "session_wrapup_apply",
|
|
1041
|
-
|
|
1041
|
+
// `wait: true` — past its 25 s default `waitMs` the tool returns a
|
|
1042
|
+
// bare `{ jobId, status: "running" }` and the report would show no
|
|
1043
|
+
// outcome for a session that did get closed.
|
|
1044
|
+
inputs: { sessionIds: ["$item.sessionId"], verdict: "$item.verdict", note: "$item.note", wait: true },
|
|
1042
1045
|
},
|
|
1043
1046
|
],
|
|
1044
1047
|
},
|
|
@@ -1229,7 +1232,8 @@ export default {
|
|
|
1229
1232
|
id: "judgedApplyOne",
|
|
1230
1233
|
kind: "tool",
|
|
1231
1234
|
tool: "session_wrapup_apply",
|
|
1232
|
-
|
|
1235
|
+
// `wait: true`: same as autoApplyOne.
|
|
1236
|
+
inputs: { sessionIds: ["$item.sessionId"], verdict: "$item.verdict", judgedBy: "$item.judgedBy", note: "$item.note", wait: true },
|
|
1233
1237
|
},
|
|
1234
1238
|
],
|
|
1235
1239
|
},
|
|
@@ -12,7 +12,7 @@ export const DEFAULT_USER_ORIGINS = ["chat-starter", "vscode"]
|
|
|
12
12
|
/** Origins the steward MAY close under the current rules. `cron:*` matches
|
|
13
13
|
* every cron-spawned job (`origin: "cron:<jobId>"`); `gate` matches
|
|
14
14
|
* supervision-gate sessions. */
|
|
15
|
-
export const DEFAULT_CLOSABLE_ORIGINS = ["cron:*", "gate"]
|
|
15
|
+
export const DEFAULT_CLOSABLE_ORIGINS = ["cron:*", "gate", "workflow", "review"]
|
|
16
16
|
|
|
17
17
|
/** The reason shown (and recorded) when a would-be close is bounded by origin. */
|
|
18
18
|
export const USER_ORIGIN_REASON = "flag (origine utilisateur)"
|
|
@@ -46,7 +46,7 @@ export function resolveOriginPolicy(policy) {
|
|
|
46
46
|
/** The origin class of a candidate:
|
|
47
47
|
* - `"user"` — human-launched: a `userOrigins` match, OR a root with no
|
|
48
48
|
* origin and no parent. FLAG ONLY, never close.
|
|
49
|
-
* - `"closable"` — a `closableOrigins` match (cron:*, gate) or an executor
|
|
49
|
+
* - `"closable"` — a `closableOrigins` match (cron:*, gate, workflow, review) or an executor
|
|
50
50
|
* (has a `parentSessionId`). Close allowed under the current rules.
|
|
51
51
|
* `userOrigins` wins over both `closableOrigins` and the executor rule, so a
|
|
52
52
|
* `vscode` executor is still user-origin. An unrecognized root origin is
|
|
@@ -53,7 +53,7 @@ the `userOrigins` / `closableOrigins` inputs:
|
|
|
53
53
|
`origin` and no `parentSessionId` (a human launched it). A would-be close —
|
|
54
54
|
even a rule-certain `close`/`stuck`, even a confident `done` — is recorded
|
|
55
55
|
as a `needs-input` flag with reason `flag (origine utilisateur)`.
|
|
56
|
-
- **Close allowed:** `cron:*`, `gate`, and executors (a session with a
|
|
56
|
+
- **Close allowed:** `cron:*`, `gate`, `workflow` (step sessions), `review` (reviewer lanes), and executors (a session with a
|
|
57
57
|
`parentSessionId`).
|
|
58
58
|
|
|
59
59
|
A trailing `*` in either list is a prefix wildcard. The report carries the
|