@agentproto/apps 0.11.0 → 0.13.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 +39 -67
- package/dist/agents-overview/panel.mjs.map +1 -1
- package/dist/agents-overview.mjs +38 -68
- package/dist/agents-overview.mjs.map +1 -1
- package/dist/bin/sync.d.ts.map +1 -1
- package/dist/bin/sync.mjs +2374 -2
- package/dist/bin/sync.mjs.map +1 -1
- package/dist/bureau-sessions/panel.mjs +39 -67
- package/dist/bureau-sessions/panel.mjs.map +1 -1
- package/dist/bureau-sessions.mjs +38 -68
- package/dist/bureau-sessions.mjs.map +1 -1
- package/dist/config/fragment.d.ts +50 -0
- package/dist/config/fragment.d.ts.map +1 -0
- package/dist/config/index.d.ts +23 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/ui.d.ts +43 -0
- package/dist/config/ui.d.ts.map +1 -0
- package/dist/config.mjs +2373 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +3200 -150
- package/dist/index.mjs.map +1 -1
- package/dist/live-session/logic.d.ts +2 -0
- package/dist/live-session/logic.d.ts.map +1 -1
- package/dist/live-session/panel.d.ts.map +1 -1
- package/dist/live-session/panel.mjs +52 -74
- package/dist/live-session/panel.mjs.map +1 -1
- package/dist/live-session.mjs +51 -75
- package/dist/live-session.mjs.map +1 -1
- package/dist/panel-bridge.d.ts +11 -6
- package/dist/panel-bridge.d.ts.map +1 -1
- package/dist/session-chat/blob-embed.d.ts +53 -0
- package/dist/session-chat/blob-embed.d.ts.map +1 -0
- package/dist/session-chat/index.d.ts +13 -0
- package/dist/session-chat/index.d.ts.map +1 -1
- package/dist/session-chat/panel.d.ts +38 -2
- package/dist/session-chat/panel.d.ts.map +1 -1
- package/dist/session-chat/panel.mjs +556 -6
- package/dist/session-chat/panel.mjs.map +1 -1
- package/dist/session-chat.mjs +591 -14
- package/dist/session-chat.mjs.map +1 -1
- package/dist/session-story/panel.mjs +39 -67
- package/dist/session-story/panel.mjs.map +1 -1
- package/dist/session-story.mjs +38 -68
- package/dist/session-story.mjs.map +1 -1
- package/dist/session-viewer/ui.d.ts.map +1 -1
- package/dist/session-viewer.mjs +10 -0
- package/dist/session-viewer.mjs.map +1 -1
- package/dist/sessions-panel/panel.d.ts.map +1 -1
- package/dist/sessions-panel/panel.mjs +40 -67
- package/dist/sessions-panel/panel.mjs.map +1 -1
- package/dist/sessions-panel.mjs +39 -68
- package/dist/sessions-panel.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 +318 -62
- package/dist/work-board/panel.mjs.map +1 -1
- package/dist/work-board.mjs +318 -62
- package/dist/work-board.mjs.map +1 -1
- package/package.json +12 -4
- package/repo-maintenance/.agentproto/APP.md +22 -0
- package/repo-maintenance/.agentproto/agents/repo-maintenance-reviewer/AGENT.md +142 -0
- package/repo-maintenance/.agentproto/workflows/maintain/WORKFLOW.md +262 -0
- package/repo-maintenance/.agentproto/workflows/maintain/entry.mjs +672 -0
- package/repo-maintenance/README.md +79 -0
- package/repo-maintenance/routines/repo-maintenance-daily/ROUTINE.md +77 -0
- package/repo-maintenance/routines/repo-maintenance-weekly/ROUTINE.md +82 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/panel-bridge.ts","../src/sessions-panel/panel.ts","../src/sessions-panel/index.ts"],"names":[],"mappings":";;;;;;;;;;AAqFO,SAAS,kBAAkB,OAAA,EAAyB;AACzD,EAAA,OAAO,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,oBAAA,EAiEa,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,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,KAAA,CAAA;AA4F7C;;;AC5NO,IAAM,UAAA,GAAa,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,EA2ExB,iBAAA,CAAkB,2BAA2B,CAAC;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAAA;;;ACvEzC,IAAM,wBAAA,GAA2B,EAAE,MAAA,CAAO;AAAA,EAC/C,MAAA,EAAQ,EACL,IAAA,CAAK,CAAC,WAAW,KAAK,CAAC,CAAA,CACvB,QAAA,EAAS,CACT,QAAA;AAAA,IACC;AAAA;AAEN,CAAC;AAqBM,SAAS,qBACd,GAAA,EACqD;AACrD,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,qBAAA;AAAA,IACJ,KAAA,EAAO,gBAAA;AAAA,IACP,WAAA,EACE,wTAAA;AAAA,IAKF,WAAA,EAAa,wBAAA;AAAA,IACb,OAAA,EAAS,OAAO,KAAA,MAAW;AAAA,MACzB,QAAA,EAAU,GAAA,CAAI,YAAA,CAAa,KAAA,CAAM,MAAM;AAAA,KACzC,CAAA;AAAA,IACA,IAAA,EAAM;AAAA,GACR;AACF;AAEO,IAAM,mBAA8B,SAAA,CAAU;AAAA,EACnD,EAAA,EAAI,4BAAA;AAAA,EACJ,IAAA,EAAM,gBAAA;AAAA,EACN,WAAA,EACE,oIAAA;AAAA,EAEF,QAAQ,EAAC;AAAA,EACT,EAAA,EAAI;AAAA,IACF,IAAA,EAAM,UAAA;AAAA,IACN,KAAA,EAAO,gBAAA;AAAA,IACP,KAAA,EAAO,CAAC,cAAA,EAAgB,cAAc;AAAA;AAE1C,CAAC","file":"sessions-panel.mjs","sourcesContent":["/**\n * Shared MCP Apps client bridge for agentproto panels.\n *\n * One spec-correct copy of the JSON-RPC-over-postMessage bridge that every\n * ui:// panel embeds in its <script> block. Mirrors the behaviour of\n * @agstudio/mcp-apps' mcpAppClientScript() — which we cannot import here:\n * agentproto is a standalone pnpm workspace with no @agstudio/* dependency\n * (same isolation invariant as mcp-apps-adapter.ts).\n *\n * Handshake (MCP Apps ext spec 2026-01-26, ext-apps McpUiInitializeRequest):\n * params REQUIRE `appInfo` (Implementation: {name, version}),\n * `appCapabilities`, and `protocolVersion`. Sending `clientInfo` instead\n * of `appInfo` fails the host's schema validation — hosts either reply\n * with an error or silently drop the request, so the panel's initBridge()\n * promise never resolves and the panel renders blank while every\n * server-side surface (tools, resources) works. That exact bug shipped in\n * 4 copy-pasted bridges; this module is the single corrected source.\n *\n * The script defines, in panel scope:\n * post / rpcRequest / rpcNotify — raw JSON-RPC plumbing\n * onHostNotification(cb) — subscribe to host-pushed\n * notifications (method, params)\n * initBridge() — ui/initialize → ui/notifications/initialized\n * callTool(name, args) — tools/call + JSON text unwrap\n * getHostContext() / onHostContext(cb) — host context (displayMode, theme, …),\n * captured from the initialize result and\n * merged on ui/notifications/host-context-changed\n * requestDisplayMode(mode) — ui/request-display-mode (inline|fullscreen|pip)\n *\n * Display-mode toggle: the bridge also injects two floating buttons\n * (fullscreen + pip) in the top-right corner of every panel, shown only for\n * the modes the host actually advertises via hostContext.availableDisplayModes\n * — same pattern as guilde's canvas.app.ts canvasShellHtml() (the reference\n * implementation that renders correctly in Claude hosts). No auto-request:\n * panels stay inline until the user clicks.\n *\n * Standalone HTTP mode: `GET /apps/:appId/ui` (packages/runtime\n * http-server.ts) serves this exact script with NO postMessage host on the\n * other end of `window.parent` — it's a plain browser tab (or a VS Code\n * *HTTP-iframe* panel), so `window.parent === window` and nothing ever\n * answers `initBridge()`'s `ui/initialize` request, leaving every panel\n * stuck on \"Connecting to bridge…\" forever. `injectStandaloneAppBridge`\n * (app-ui-apps.ts) papers over exactly this for installed apps by injecting\n * `window.McpApp.connect() -> Promise<{callTool, ...}>`, a REST-backed\n * stand-in whose `callTool` POSTs to the sibling `./tool-call` route — but\n * this bridge never consumed `window.McpApp`, so builtin panels (the only\n * consumers of this module) were left hanging regardless.\n *\n * Fixed here by detecting that same standalone shape and short-circuiting\n * both `initBridge()` (resolves locally, no round trip) and `callTool()`\n * (routes through `window.McpApp.connect()` instead of `tools/call` over\n * postMessage). The detection requires BOTH `window.parent === window` (no\n * host to talk to — this is the actual condition standalone mode means) AND\n * `window.McpApp` being present with a `connect` function (the capability\n * this bridge needs from it): `window.McpApp` presence alone was considered\n * and rejected as the sole signal, even though it happens to also hold today\n * — the VS Code webview's `srcdoc` relay (packages/vscode\n * appPanel.ts/appPanelController.ts) answers this bridge's raw postMessage\n * JSON-RPC directly and never defines `window.McpApp` for a builtin panel\n * (only `createUiHtmlCache`'s `injectMcpAppBridge`, used for INSTALLED\n * apps' `ui.path` html, defines it) — but that's an accident of today's\n * wiring, not a guarantee; `window.parent === window` is the direct,\n * load-bearing signal (\"is there anyone to postMessage?\") and doesn't\n * depend on which injector happened to run. There is no host in standalone\n * mode, so `initBridge()` seeds a default `hostContext` of `{displayMode:\n * 'inline', availableDisplayModes: []}` — an empty `availableDisplayModes`\n * is what keeps the `#dm`/`#pin` toggle buttons hidden (they only show for\n * modes `hostContext.availableDisplayModes` lists), rather than rendering\n * two buttons with nothing to switch to.\n *\n * The real postMessage-host path (a compliant MCP-Apps host, or the VS Code\n * srcdoc relay) is untouched byte-for-byte: same `rpcRequest`/`rpcNotify`\n * over `window.parent.postMessage`, same handshake, same display-mode\n * plumbing.\n *\n * Asymmetry not fixed here (flagged, not papered over): `window.McpApp`'s\n * `sendMessage`/`updateModelContext` (STANDALONE_REST_BRIDGE_SCRIPT,\n * app-ui-apps.ts) reject with \"no host (standalone mode)\", while\n * `appPanelController.ts`'s `ui/message`/`ui/update-model-context` handlers\n * accept-and-drop (`return {}`). This bridge doesn't currently expose either\n * call, so the difference is latent, but it's the last behavioural delta\n * between the two panel paths and worth a follow-up if this bridge ever\n * grows those methods.\n */\n\nexport function panelBridgeScript(appName: string): string {\n return `// ── MCP Apps bridge (shared: panel-bridge.ts) ──\n// JSON-RPC 2.0 over window.parent.postMessage · spec 2026-01-26\nvar _nextId = 1, _pending = {}, _notifyHandlers = [];\nvar _hostContext = null, _hostContextHandlers = [];\nvar _standaloneApp = null;\nfunction _isStandalone(){\n return window.parent === window && !!window.McpApp && typeof window.McpApp.connect === 'function';\n}\nfunction post(msg){ window.parent.postMessage(msg, '*'); }\nfunction getHostContext(){ return _hostContext; }\nfunction onHostContext(cb){\n _hostContextHandlers.push(cb);\n // Replay the last context so a late subscriber isn't stuck blind.\n if (_hostContext){ try { cb(_hostContext); } catch(_) {} }\n}\nfunction _setHostContext(ctx){\n if (!ctx || typeof ctx !== 'object') return;\n // ui/notifications/host-context-changed carries only the changed keys —\n // merge, matching the official ext-apps App behaviour.\n _hostContext = Object.assign({}, _hostContext || {}, ctx);\n for (var i = 0; i < _hostContextHandlers.length; i++){\n try { _hostContextHandlers[i](_hostContext); } catch(_) {}\n }\n}\nfunction rpcRequest(method, params){\n return new Promise(function(resolve, reject){\n var id = _nextId++;\n _pending[id] = {resolve: resolve, reject: reject};\n post({jsonrpc: '2.0', id: id, method: method, params: params || {}});\n });\n}\nfunction rpcNotify(method, params){ post({jsonrpc: '2.0', method: method, params: params || {}}); }\nfunction onHostNotification(cb){ _notifyHandlers.push(cb); }\nwindow.addEventListener('message', function(evt){\n var msg = evt.data;\n if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0') return;\n if (msg.id != null && msg.method == null){\n var p = _pending[msg.id];\n if (!p) return;\n delete _pending[msg.id];\n if (msg.error) p.reject(new Error(msg.error.message || ('rpc error ' + msg.error.code)));\n else p.resolve(msg.result);\n return;\n }\n if (msg.method){\n if (msg.method === 'ui/notifications/host-context-changed'){\n _setHostContext(msg.params || {});\n }\n for (var i = 0; i < _notifyHandlers.length; i++){\n try { _notifyHandlers[i](msg.method, msg.params || {}); } catch(_) {}\n }\n }\n});\nfunction initBridge(){\n if (_isStandalone()){\n return window.McpApp.connect().then(function(conn){\n _standaloneApp = conn;\n // No host to advertise a hostContext — default to inline with no\n // other modes available, which keeps the #dm/#pin toggle buttons\n // hidden (they only show for modes hostContext.availableDisplayModes\n // lists) instead of rendering two buttons with nothing to switch to.\n _setHostContext({displayMode: 'inline', availableDisplayModes: []});\n });\n }\n return rpcRequest('ui/initialize', {\n appInfo: {name: ${JSON.stringify(appName)}, version: '0.1.0'},\n appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},\n protocolVersion: '2026-01-26'\n }).then(function(result){\n // The initialize result carries the initial hostContext (displayMode +\n // availableDisplayModes) — capture it before notifying the host.\n if (result && result.hostContext) _setHostContext(result.hostContext);\n rpcNotify('ui/notifications/initialized', {});\n });\n}\nfunction requestDisplayMode(mode){\n return rpcRequest('ui/request-display-mode', {mode: mode});\n}\nfunction callTool(name, args){\n var raw = _standaloneApp\n ? _standaloneApp.callTool(name, args || {})\n : rpcRequest('tools/call', {name: name, arguments: args || {}});\n return raw.then(function(result){\n if (result.isError){\n var e = (result.content && result.content[0] && result.content[0].text) || 'tool error';\n throw new Error(e);\n }\n var text = (result.content && result.content[0] && result.content[0].text) || '{}';\n return JSON.parse(text);\n });\n}\n\n// ── Display-mode toggle buttons (NO auto-request) ──────────────────────\n// Injected by the shared bridge so every panel gets them without touching\n// its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the\n// panel stays inline by default; the user expands on demand. Buttons only\n// appear for modes the host advertises in hostContext.availableDisplayModes.\n(function(){\n function mount(){\n var style = document.createElement('style');\n style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'\n + 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'\n + 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'\n + 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'\n + '#dm{right:8px}#pin{right:118px}'\n + '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'\n + '@media (prefers-color-scheme:dark){'\n + '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'\n + '#dm:hover,#pin:hover{background:#333;border-color:#777}}';\n document.head.appendChild(style);\n\n var btn = document.createElement('button');\n btn.id = 'dm'; btn.type = 'button'; btn.title = \"Basculer l'affichage\";\n var pin = document.createElement('button');\n pin.id = 'pin'; pin.type = 'button'; pin.title = 'Épingler sur le côté (pip)';\n document.body.appendChild(pin);\n document.body.appendChild(btn);\n\n function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }\n\n // Re-sync button visibility + label from the current host context.\n function syncBtn(ctx){\n ctx = ctx || {};\n var avail = ctx.availableDisplayModes;\n // Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)\n console.log('[mcp-app] displayMode=', ctx.displayMode,\n 'availableDisplayModes=', avail);\n\n // Fullscreen toggle button.\n if (has(avail, 'fullscreen')){\n btn.style.display = 'block';\n btn.textContent = (ctx.displayMode === 'fullscreen') ? '⤡ Réduire' : '⤢ Agrandir';\n } else { btn.style.display = 'none'; }\n\n // Dedicated pip (\"pinned on side\") button — only if the host advertises pip.\n if (has(avail, 'pip')){\n pin.style.display = 'block';\n pin.textContent = (ctx.displayMode === 'pip') ? '⤡ Détacher' : '📌 Épingler';\n } else { pin.style.display = 'none'; }\n }\n\n onHostContext(syncBtn);\n\n btn.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');\n requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});\n });\n\n pin.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});\n });\n }\n if (document.body) mount();\n else document.addEventListener('DOMContentLoaded', mount);\n})();`\n}\n","/**\n * HTML bundle for the agentproto sessions panel — the `PANEL_HTML` served\n * as the `ui://agentproto_sessions/view` resource by `registerMcpApps`\n * (see ../mcp-app-types.ts and runtime's mcp-apps-adapter.ts). Mounted via\n * the `agentproto_sessions` tool defined in ./index.ts.\n *\n * Protocol: MCP Apps ext spec 2026-01-26\n * – Bridge: JSON-RPC 2.0 over window.parent.postMessage\n * – Handshake: ui/initialize → host result → ui/notifications/initialized\n * – Data: tools/call → session_list + agent_output (3 s poll)\n * – Kill action: tools/call → agent_kill | terminal_kill\n * (goes through Claude's normal tool approval flow)\n *\n * The HTML bundle is fully autonomous (zero CDN, zero external deps).\n * ANSI-to-HTML renderer handles SGR codes (colors 0-15, bold, dim,\n * italic, underline, reset). CR-overwrite sequences are stripped so\n * progress bars don't corrupt the output.\n */\n\nimport { panelBridgeScript } from \"../panel-bridge.js\"\n\n// ── HTML bundle ─────────────────────────────────────────────────────────────\n\nexport const PANEL_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>agentproto sessions</title>\n<style>\n*{box-sizing:border-box;margin:0;padding:0}\n:root{\n --bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;\n --text:#e6edf3;--text2:#8b949e;\n --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;\n}\nhtml,body{height:100%;font-family:Menlo,Monaco,'Courier New',monospace;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}\n#app{display:flex;height:100%}\n#sidebar{width:220px;min-width:180px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}\n#sidebar-hdr{padding:10px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}\n#sidebar-hdr h1{font-size:11px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.06em}\n#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:15px;line-height:1;padding:2px 4px;border-radius:4px;font-family:inherit}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n#session-list{flex:1;overflow-y:auto;padding:4px 0}\n.si{padding:8px 12px;cursor:pointer;border-left:2px solid transparent}\n.si:hover{background:var(--bg3)}\n.si.active{background:var(--bg3);border-left-color:var(--blue)}\n.sn{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.sm{font-size:11px;color:var(--text2);margin-top:2px;display:flex;gap:6px;align-items:center}\n.badge{display:inline-block;padding:1px 5px;border-radius:10px;font-size:10px;font-weight:600}\n.br{background:rgba(63,185,80,.15);color:var(--green)}\n.bs{background:rgba(88,166,255,.15);color:var(--blue)}\n.be{background:rgba(139,148,158,.1);color:var(--text2)}\n.bk{background:rgba(248,81,73,.1);color:var(--red)}\n.berr{background:rgba(248,81,73,.2);color:var(--red)}\n#main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}\n#toolbar{padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2)}\n#session-title{font-size:12px;font-weight:500;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:4px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}\n.abtn:hover{background:var(--border)}\n.abtn:disabled{opacity:.4;cursor:default}\n.abtn.danger{border-color:var(--red);color:var(--red)}\n.abtn.danger:hover{background:rgba(248,81,73,.1)}\n#output{flex:1;overflow-y:auto;padding:8px 12px;background:var(--bg);font-size:12px;line-height:1.6}\n.line{white-space:pre-wrap;word-break:break-all}\n#empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text2);gap:8px;text-align:center;padding:24px}\n#empty .ico{font-size:32px}\n#statusbar{padding:4px 12px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n.errmsg{color:var(--red);padding:12px}\n/* ANSI SGR */\n.ab{font-weight:bold}.ad{opacity:.6}.ai{font-style:italic}.au{text-decoration:underline}\n.f0{color:#21262d}.f1{color:#f85149}.f2{color:#3fb950}.f3{color:#d29922}\n.f4{color:#58a6ff}.f5{color:#bc8cff}.f6{color:#39d353}.f7{color:#e6edf3}\n.f8{color:#8b949e}.f9{color:#ff7b72}.f10{color:#56d364}.f11{color:#e3b341}\n.f12{color:#79c0ff}.f13{color:#d2a8ff}.f14{color:#56d364}.f15{color:#f0f6fc}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"sidebar\">\n <div id=\"sidebar-hdr\">\n <h1>Sessions</h1>\n <button id=\"refresh-btn\" title=\"Refresh\" onclick=\"doRefresh()\">↻</button>\n </div>\n <div id=\"session-list\"></div>\n </div>\n <div id=\"main\">\n <div id=\"toolbar\" style=\"display:none\">\n <span id=\"session-title\"></span>\n <button class=\"abtn danger\" id=\"kill-btn\" onclick=\"doKill()\">Kill</button>\n </div>\n <div id=\"output\">\n <div id=\"empty\"><div class=\"ico\">⚡</div><div>Select a session</div></div>\n </div>\n <div id=\"statusbar\">Connecting to bridge…</div>\n </div>\n</div>\n<script>\n${panelBridgeScript(\"agentproto-sessions-panel\")}\n\n// ============================================================\n// ANSI-to-HTML renderer (SGR codes: colors 0-15, bold/dim/italic/underline)\n// ============================================================\n\nfunction escHtml(s) {\n return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');\n}\n\nfunction ansiToHtml(raw) {\n // Strip CR overwrite sequences (progress bars: CR without LF)\n var text = raw.replace(/[^\\\\n]*\\\\r([^\\\\n])/g, '$1').replace(/\\\\r/g, '');\n var bold = false, dim = false, italic = false, underline = false, fg = -1;\n var out = '';\n // Split on ESC [ ... m (SGR) sequences; keep delimiters\n var ESC = '\u001b';\n var parts = text.split(/([\u001b]\\\\[[0-9;]*m)/);\n for (var pi = 0; pi < parts.length; pi++) {\n var part = parts[pi];\n if (part.length === 0) continue;\n if (part.charCodeAt(0) === 0x1b && part[1] === '[') {\n // Parse SGR parameters\n var inner = part.slice(2, part.length - 1); // strip ESC[ and m\n var codes = inner === '' ? [0] : inner.split(';').map(Number);\n for (var ci = 0; ci < codes.length; ci++) {\n var c = codes[ci];\n if (c === 0) { bold = false; dim = false; italic = false; underline = false; fg = -1; }\n else if (c === 1) bold = true;\n else if (c === 2) dim = true;\n else if (c === 3) italic = true;\n else if (c === 4) underline = true;\n else if (c === 22) { bold = false; dim = false; }\n else if (c === 23) italic = false;\n else if (c === 24) underline = false;\n else if (c >= 30 && c <= 37) fg = c - 30;\n else if (c === 39) fg = -1;\n else if (c >= 90 && c <= 97) fg = c - 90 + 8;\n }\n } else {\n // Text segment — strip any remaining non-printable ESC sequences\n var safe = escHtml(part).replace(/\u001b\\\\[[^m]*[A-Za-z]/g, '');\n if (safe === '') continue;\n var cls = '';\n if (bold) cls += ' ab';\n if (dim) cls += ' ad';\n if (italic) cls += ' ai';\n if (underline) cls += ' au';\n if (fg >= 0 && fg <= 15) cls += ' f' + fg;\n cls = cls.trim();\n out += cls ? '<span class=\"' + cls + '\">' + safe + '</span>' : safe;\n }\n }\n return out;\n}\n\n// ============================================================\n// App state\n// ============================================================\n\nvar sessions = [];\nvar activeId = null;\nvar outputLines = [];\nvar nextCursor = 0;\nvar pollTimer = null;\nvar pollActive = false;\n\nfunction setStatus(msg) {\n document.getElementById('statusbar').textContent = msg;\n}\n\n// ============================================================\n// Session list\n// ============================================================\n\nfunction badgeClass(status) {\n if (status === 'running') return 'br';\n if (status === 'starting') return 'bs';\n if (status === 'killed') return 'bk';\n if (status === 'error') return 'berr';\n return 'be'; // exited\n}\n\n// Derive a turn-aware display badge. A session's 'status' only tracks process\n// liveness, so an agent-cli process stays \"running\" while idle between turns.\n// Read 'busy' / 'awaitingInput' to show real activity instead:\n// working — a turn is in flight (busy)\n// waiting — turn ended on awaiting-input (needs a reply)\n// idle — process alive, no turn running (last turn done)\nfunction displayBadge(s) {\n if (s.status === 'running' && s.kind === 'agent-cli') {\n if (s.busy) return { label: 'working', cls: 'br' };\n if (s.awaitingInput) return { label: 'waiting', cls: 'bs' };\n return { label: 'idle', cls: 'be' };\n }\n return { label: s.status, cls: badgeClass(s.status) };\n}\n\nfunction renderSidebar() {\n var el = document.getElementById('session-list');\n if (sessions.length === 0) {\n el.innerHTML = '<div style=\"padding:12px;color:var(--text2);font-size:11px\">No sessions</div>';\n return;\n }\n var html = '';\n for (var i = 0; i < sessions.length; i++) {\n var s = sessions[i];\n var label = s.label || s.name || (s.command ? s.command.split('/').pop() : null) || s.id.slice(0, 8);\n var db = displayBadge(s);\n var active = s.id === activeId ? ' active' : '';\n // blockedOn (set while the turn waits on a spawned sub-agent or a\n // shell command) rides next to the status badge; waiting-on-user is\n // NOT here — that's awaitingInput, a different signal.\n var blocked = '';\n if (s.blockedOn === 'subagent') blocked = '<span class=\"badge bs\">🧩 sous-agent</span>';\n else if (s.blockedOn === 'command') blocked = '<span class=\"badge bs\">⏳ commande</span>';\n html += '<div class=\"si' + active + '\" onclick=\"selectSession(\\\\'' + s.id + '\\\\')\">'\n + '<div class=\"sn\">' + escHtml(label) + '</div>'\n + '<div class=\"sm\">'\n + '<span class=\"badge ' + db.cls + '\">' + db.label + '</span>'\n + blocked\n + '<span>' + escHtml(s.kind || '') + '</span>'\n + '</div></div>';\n }\n el.innerHTML = html;\n}\n\nfunction loadSessions() {\n // {kind:'all'} is session_list's live-able default — agent-CLI + terminal/\n // PTY only. kind:\"command\" rows (a shell-execution log, not a resumable\n // session) are excluded unless includeCommands is explicitly passed.\n return callTool('session_list', {kind: 'all'}).then(function(data) {\n sessions = data.sessions || [];\n renderSidebar();\n setStatus(sessions.length + ' session' + (sessions.length === 1 ? '' : 's') + ' · ' + new Date().toLocaleTimeString());\n }).catch(function(e) {\n setStatus('Error: ' + e.message);\n });\n}\n\n// ============================================================\n// Output panel\n// ============================================================\n\nfunction renderOutputFull() {\n var el = document.getElementById('output');\n if (outputLines.length === 0) {\n el.innerHTML = '<div id=\"empty\"><div class=\"ico\">⚡</div><div>No output yet</div></div>';\n return;\n }\n var html = '';\n for (var i = 0; i < outputLines.length; i++) {\n html += '<div class=\"line\">' + ansiToHtml(outputLines[i]) + '</div>';\n }\n el.innerHTML = html;\n el.scrollTop = el.scrollHeight;\n}\n\nfunction appendLines(lines) {\n var el = document.getElementById('output');\n // Remove empty-state placeholder if present\n var empty = el.querySelector('#empty');\n if (empty) empty.parentNode.removeChild(empty);\n var frag = document.createDocumentFragment();\n for (var i = 0; i < lines.length; i++) {\n var div = document.createElement('div');\n div.className = 'line';\n div.innerHTML = ansiToHtml(lines[i]);\n frag.appendChild(div);\n }\n el.appendChild(frag);\n el.scrollTop = el.scrollHeight;\n}\n\nfunction selectSession(id) {\n activeId = id;\n outputLines = [];\n nextCursor = 0;\n renderSidebar();\n\n var s = null;\n for (var i = 0; i < sessions.length; i++) {\n if (sessions[i].id === id) { s = sessions[i]; break; }\n }\n\n var toolbar = document.getElementById('toolbar');\n toolbar.style.display = 'flex';\n document.getElementById('session-title').textContent = (s && (s.label || s.name)) || id.slice(0, 12);\n\n var killBtn = document.getElementById('kill-btn');\n killBtn.disabled = !s || (s.status !== 'running' && s.status !== 'starting');\n\n document.getElementById('output').innerHTML = '<div style=\"padding:12px;color:var(--text2)\">Loading…</div>';\n\n // Initial load\n callTool('agent_output', {sessionId: id, lastN: 200}).then(function(data) {\n outputLines = data.lines || [];\n nextCursor = data.nextCursor || 0;\n renderOutputFull();\n }).catch(function(e) {\n document.getElementById('output').innerHTML = '<div class=\"errmsg\">Error: ' + escHtml(e.message) + '</div>';\n });\n}\n\n// ============================================================\n// Kill\n// ============================================================\n\nfunction doKill() {\n if (!activeId) return;\n var s = null;\n for (var i = 0; i < sessions.length; i++) {\n if (sessions[i].id === activeId) { s = sessions[i]; break; }\n }\n var toolName = (s && s.pty) ? 'terminal_kill' : 'agent_kill';\n callTool(toolName, {sessionId: activeId}).then(function() {\n return doRefresh();\n }).catch(function(e) {\n setStatus('Kill failed: ' + e.message);\n });\n}\n\n// ============================================================\n// Poll\n// ============================================================\n\nfunction doPoll() {\n if (pollActive) return;\n pollActive = true;\n var p = loadSessions();\n if (activeId) {\n var capturedId = activeId;\n var capturedCursor = nextCursor;\n p = p.then(function() {\n return callTool('agent_output', {sessionId: capturedId, since: capturedCursor});\n }).then(function(data) {\n if (capturedId !== activeId) return; // user switched sessions\n var newLines = data.lines || [];\n if (newLines.length > 0) {\n outputLines = outputLines.concat(newLines);\n if (outputLines.length > 2000) outputLines = outputLines.slice(-2000);\n nextCursor = data.nextCursor || nextCursor;\n appendLines(newLines);\n }\n }).catch(function() {});\n }\n p.then(function() {\n pollActive = false;\n pollTimer = setTimeout(doPoll, 3000);\n }).catch(function() {\n pollActive = false;\n pollTimer = setTimeout(doPoll, 3000);\n });\n}\n\nfunction doRefresh() {\n return loadSessions().then(function() {\n if (activeId) {\n return callTool('agent_output', {sessionId: activeId, lastN: 200}).then(function(data) {\n outputLines = data.lines || [];\n nextCursor = data.nextCursor || 0;\n renderOutputFull();\n });\n }\n });\n}\n\n// ============================================================\n// Boot\n// ============================================================\n\ninitBridge().then(function() {\n return loadSessions();\n}).then(function() {\n pollTimer = setTimeout(doPoll, 3000);\n}).catch(function(e) {\n setStatus('Bridge error: ' + e.message);\n document.getElementById('output').innerHTML = '<div class=\"errmsg\">Failed to connect to MCP bridge: ' + escHtml(e.message) + '</div>';\n});\n</script>\n</body>\n</html>`\n","/**\n * McpApp definition for the agentproto sessions panel.\n *\n * Uses the AgnoMcpApp contract (a local McpApp-compatible shape that does\n * NOT depend on @agstudio/mcp-apps or Mastra). The host (@agentproto/runtime)\n * wires this factory's output via `registerMcpApps` from its\n * mcp-apps-adapter.ts at boot time — see runtime's builtin-apps.ts.\n *\n * Protocol flow:\n * 1. Tool `agentproto_sessions` is called by the host.\n * 2. execute() returns a sessions snapshot → injected into the HTML as\n * window.__APP_INIT__ (static initial render, no bridge needed).\n * 3. The HTML panel opens a JSON-RPC bridge (postMessage) and polls\n * `session_list` + `agent_output` every 3 s for live data.\n *\n * This module also exports `sessionsPanelApp`, a real `defineApp()`\n * `AppHandle` (`agents: []`, UI-only) — the catalog/emit/`app_install` path.\n * It's separate from `makeSessionsPanelApp` above, which the daemon mounts\n * directly at boot: that factory closes over LIVE `listSessions`, something\n * a static emitted `ui.html` snapshot can't carry.\n */\n\nimport { z } from \"zod\"\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { PANEL_HTML } from \"./panel.js\"\nimport type { AgnoMcpApp } from \"../mcp-app-types.js\"\n\nexport const sessionsPanelInputSchema = z.object({\n filter: z\n .enum([\"running\", \"all\"])\n .optional()\n .describe(\n \"Which sessions to return. `running` = only alive; `all` = running + recent (default).\",\n ),\n})\n\nexport type SessionsInput = z.infer<typeof sessionsPanelInputSchema>\nexport type SessionsOutput<TSession = unknown> = { sessions: TSession[] }\n\n/**\n * Generic over the host's own session-descriptor shape — this package\n * doesn't own that type (it lives in @agentproto/runtime's session\n * registry), so the factory stays agnostic to it and the host supplies the\n * concrete type as a type argument when it wires the app up.\n */\nexport interface SessionsPanelOps<TSession = unknown> {\n listSessions(filter?: \"running\" | \"all\"): TSession[]\n}\n\nexport type { AgnoMcpApp }\n\n/**\n * Factory: close over the sessions registry so execute() doesn't need\n * an AppContext (agentproto has no userId/guildId concept).\n */\nexport function makeSessionsPanelApp<TSession = unknown>(\n ops: SessionsPanelOps<TSession>,\n): AgnoMcpApp<SessionsInput, SessionsOutput<TSession>> {\n return {\n id: \"agentproto_sessions\",\n title: \"Agent Sessions\",\n description:\n \"Open the agentproto sessions panel — an interactive UI that shows all \" +\n \"running and recent agent-CLI and terminal/PTY sessions. Raw shell-\" +\n \"command runs are a log, not a resumable session, and don't appear \" +\n \"here — see `command_list`. The panel polls live data and lets you \" +\n \"inspect output or kill sessions.\",\n inputSchema: sessionsPanelInputSchema,\n execute: async (input) => ({\n sessions: ops.listSessions(input.filter),\n }),\n html: PANEL_HTML,\n }\n}\n\nexport const sessionsPanelApp: AppHandle = defineApp({\n id: \"@agentproto/sessions-panel\",\n name: \"Agent Sessions\",\n description:\n \"Open the agentproto sessions panel — an interactive UI that shows all running and recent \" +\n \"agent-CLI and terminal/PTY sessions.\",\n agents: [],\n ui: {\n html: PANEL_HTML,\n title: \"Agent Sessions\",\n tools: [\"session_list\", \"agent_output\"],\n },\n})\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/panel-bridge.ts","../src/sessions-panel/panel.ts","../src/sessions-panel/index.ts"],"names":[],"mappings":";;;;;;;;;AA4FO,SAAS,kBAAkB,OAAA,EAAyB;AACzD,EAAA,OAAO,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,oBAAA,EAkFa,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC,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,EAwC3C,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA,GAAA,CAAA;AAM1B;;;ACtMO,IAAM,UAAA,GAAa,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,EA2ExB,iBAAA,CAAkB,2BAA2B,CAAC;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAAA;;;ACvEzC,IAAM,wBAAA,GAA2B,EAAE,MAAA,CAAO;AAAA,EAC/C,MAAA,EAAQ,EACL,IAAA,CAAK,CAAC,WAAW,KAAK,CAAC,CAAA,CACvB,QAAA,EAAS,CACT,QAAA;AAAA,IACC;AAAA;AAEN,CAAC;AAqBM,SAAS,qBACd,GAAA,EACqD;AACrD,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,qBAAA;AAAA,IACJ,KAAA,EAAO,gBAAA;AAAA,IACP,WAAA,EACE,wTAAA;AAAA,IAKF,WAAA,EAAa,wBAAA;AAAA,IACb,OAAA,EAAS,OAAO,KAAA,MAAW;AAAA,MACzB,QAAA,EAAU,GAAA,CAAI,YAAA,CAAa,KAAA,CAAM,MAAM;AAAA,KACzC,CAAA;AAAA,IACA,IAAA,EAAM;AAAA,GACR;AACF;AAEO,IAAM,mBAA8B,SAAA,CAAU;AAAA,EACnD,EAAA,EAAI,4BAAA;AAAA,EACJ,IAAA,EAAM,gBAAA;AAAA,EACN,WAAA,EACE,oIAAA;AAAA,EAEF,QAAQ,EAAC;AAAA,EACT,EAAA,EAAI;AAAA,IACF,IAAA,EAAM,UAAA;AAAA,IACN,KAAA,EAAO,gBAAA;AAAA,IACP,KAAA,EAAO,CAAC,cAAA,EAAgB,cAAc;AAAA;AAE1C,CAAC","file":"sessions-panel.mjs","sourcesContent":["/**\n * Shared MCP Apps client bridge for agentproto panels.\n *\n * One spec-correct copy of the JSON-RPC-over-postMessage bridge that every\n * ui:// panel embeds in its <script> block. Mirrors the behaviour of\n * @agstudio/mcp-apps' mcpAppClientScript() — which we cannot import here:\n * agentproto is a standalone pnpm workspace with no @agstudio/* dependency\n * (same isolation invariant as mcp-apps-adapter.ts).\n *\n * Handshake (MCP Apps ext spec 2026-01-26, ext-apps McpUiInitializeRequest):\n * params REQUIRE `appInfo` (Implementation: {name, version}),\n * `appCapabilities`, and `protocolVersion`. Sending `clientInfo` instead\n * of `appInfo` fails the host's schema validation — hosts either reply\n * with an error or silently drop the request, so the panel's initBridge()\n * promise never resolves and the panel renders blank while every\n * server-side surface (tools, resources) works. That exact bug shipped in\n * 4 copy-pasted bridges; this module is the single corrected source.\n *\n * The script defines, in panel scope:\n * post / rpcRequest / rpcNotify — raw JSON-RPC plumbing\n * onHostNotification(cb) — subscribe to host-pushed\n * notifications (method, params)\n * initBridge() — ui/initialize → ui/notifications/initialized\n * callTool(name, args) — tools/call + JSON text unwrap\n * getHostContext() / onHostContext(cb) — host context (displayMode, theme, …),\n * captured from the initialize result and\n * merged on ui/notifications/host-context-changed\n * requestDisplayMode(mode) — ui/request-display-mode (inline|fullscreen|pip)\n *\n * Display-mode toggle: the bridge also mounts two floating buttons\n * (fullscreen + pip) in the top-right corner of every panel, shown only for\n * the modes the host actually advertises via hostContext.availableDisplayModes.\n * No auto-request: panels stay inline until the user clicks. The buttons\n * themselves are NOT implemented here — `@agentproto/app-client/display-mode`\n * is the one implementation, shared with the `window.McpApp` bridge injected\n * into installed apps (`packages/runtime/src/app-ui-apps.ts`); this module\n * only hands it `getHostContext`/`onHostContext`/`requestDisplayMode`. It\n * also leaves `window.AgentprotoUI.installDisplayMode` defined in the panel,\n * which is what a panel with its own header calls to place the toggle\n * inline instead (see that module's `mountToggle`).\n *\n * Standalone HTTP mode: `GET /apps/:appId/ui` (packages/runtime\n * http-server.ts) serves this exact script with NO postMessage host on the\n * other end of `window.parent` — it's a plain browser tab (or a VS Code\n * *HTTP-iframe* panel), so `window.parent === window` and nothing ever\n * answers `initBridge()`'s `ui/initialize` request, leaving every panel\n * stuck on \"Connecting to bridge…\" forever. `injectStandaloneAppBridge`\n * (app-ui-apps.ts) papers over exactly this for installed apps by injecting\n * `window.McpApp.connect() -> Promise<{callTool, ...}>`, a REST-backed\n * stand-in whose `callTool` POSTs to the sibling `./tool-call` route — but\n * this bridge never consumed `window.McpApp`, so builtin panels (the only\n * consumers of this module) were left hanging regardless.\n *\n * Fixed here by detecting that same standalone shape and short-circuiting\n * both `initBridge()` (resolves locally, no round trip) and `callTool()`\n * (routes through `window.McpApp.connect()` instead of `tools/call` over\n * postMessage). The detection requires BOTH `window.parent === window` (no\n * host to talk to — this is the actual condition standalone mode means) AND\n * `window.McpApp` being present with a `connect` function (the capability\n * this bridge needs from it): `window.McpApp` presence alone was considered\n * and rejected as the sole signal, even though it happens to also hold today\n * — the VS Code webview's `srcdoc` relay (packages/vscode\n * appPanel.ts/appPanelController.ts) answers this bridge's raw postMessage\n * JSON-RPC directly and never defines `window.McpApp` for a builtin panel\n * (only `createUiHtmlCache`'s `injectMcpAppBridge`, used for INSTALLED\n * apps' `ui.path` html, defines it) — but that's an accident of today's\n * wiring, not a guarantee; `window.parent === window` is the direct,\n * load-bearing signal (\"is there anyone to postMessage?\") and doesn't\n * depend on which injector happened to run. There is no host in standalone\n * mode, so `initBridge()` seeds a default `hostContext` of `{displayMode:\n * 'inline', availableDisplayModes: []}` — an empty `availableDisplayModes`\n * is what keeps the display-mode toggle buttons hidden (they only show for\n * modes `hostContext.availableDisplayModes` lists), rather than rendering\n * two buttons with nothing to switch to.\n *\n * The real postMessage-host path (a compliant MCP-Apps host, or the VS Code\n * srcdoc relay) is untouched byte-for-byte: same `rpcRequest`/`rpcNotify`\n * over `window.parent.postMessage`, same handshake, same display-mode\n * plumbing.\n *\n * Asymmetry not fixed here (flagged, not papered over): `window.McpApp`'s\n * `sendMessage`/`updateModelContext` (STANDALONE_REST_BRIDGE_SCRIPT,\n * app-ui-apps.ts) reject with \"no host (standalone mode)\", while\n * `appPanelController.ts`'s `ui/message`/`ui/update-model-context` handlers\n * accept-and-drop (`return {}`). This bridge doesn't currently expose either\n * call, so the difference is latent, but it's the last behavioural delta\n * between the two panel paths and worth a follow-up if this bridge ever\n * grows those methods.\n */\n\nimport { DISPLAY_MODE_SCRIPT_BODY } from \"@agentproto/app-client/display-mode\"\n\nexport function panelBridgeScript(appName: string): string {\n return `// ── MCP Apps bridge (shared: panel-bridge.ts) ──\n// JSON-RPC 2.0 over window.parent.postMessage · spec 2026-01-26\nvar _nextId = 1, _pending = {}, _notifyHandlers = [];\nvar _hostContext = null, _hostContextHandlers = [];\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: ${JSON.stringify(appName)}, version: '0.1.0'},\n appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},\n protocolVersion: '2026-01-26'\n }).then(function(result){\n // The initialize result carries the initial hostContext (displayMode +\n // availableDisplayModes) — capture it before notifying the host.\n if (result && result.hostContext) _setHostContext(result.hostContext);\n // 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${DISPLAY_MODE_SCRIPT_BODY}\nvar displayMode = window.AgentprotoUI.installDisplayMode({\n getHostContext: getHostContext,\n onHostContext: onHostContext,\n requestDisplayMode: requestDisplayMode\n});`\n}\n","/**\n * HTML bundle for the agentproto sessions panel — the `PANEL_HTML` served\n * as the `ui://agentproto_sessions/view` resource by `registerMcpApps`\n * (see ../mcp-app-types.ts and runtime's mcp-apps-adapter.ts). Mounted via\n * the `agentproto_sessions` tool defined in ./index.ts.\n *\n * Protocol: MCP Apps ext spec 2026-01-26\n * – Bridge: JSON-RPC 2.0 over window.parent.postMessage\n * – Handshake: ui/initialize → host result → ui/notifications/initialized\n * – Data: tools/call → session_list + agent_output (3 s poll)\n * – Kill action: tools/call → agent_kill | terminal_kill\n * (goes through Claude's normal tool approval flow)\n *\n * The HTML bundle is fully autonomous (zero CDN, zero external deps).\n * ANSI-to-HTML renderer handles SGR codes (colors 0-15, bold, dim,\n * italic, underline, reset). CR-overwrite sequences are stripped so\n * progress bars don't corrupt the output.\n */\n\nimport { panelBridgeScript } from \"../panel-bridge.js\"\n\n// ── HTML bundle ─────────────────────────────────────────────────────────────\n\nexport const PANEL_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>agentproto sessions</title>\n<style>\n*{box-sizing:border-box;margin:0;padding:0}\n:root{\n --bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;\n --text:#e6edf3;--text2:#8b949e;\n --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;\n}\nhtml,body{height:100%;font-family:Menlo,Monaco,'Courier New',monospace;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}\n#app{display:flex;height:100%}\n#sidebar{width:220px;min-width:180px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}\n#sidebar-hdr{padding:10px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}\n#sidebar-hdr h1{font-size:11px;font-weight:600;color:var(--text2);text-transform:uppercase;letter-spacing:.06em}\n#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:15px;line-height:1;padding:2px 4px;border-radius:4px;font-family:inherit}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n#session-list{flex:1;overflow-y:auto;padding:4px 0}\n.si{padding:8px 12px;cursor:pointer;border-left:2px solid transparent}\n.si:hover{background:var(--bg3)}\n.si.active{background:var(--bg3);border-left-color:var(--blue)}\n.sn{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.sm{font-size:11px;color:var(--text2);margin-top:2px;display:flex;gap:6px;align-items:center}\n.badge{display:inline-block;padding:1px 5px;border-radius:10px;font-size:10px;font-weight:600}\n.br{background:rgba(63,185,80,.15);color:var(--green)}\n.bs{background:rgba(88,166,255,.15);color:var(--blue)}\n.be{background:rgba(139,148,158,.1);color:var(--text2)}\n.bk{background:rgba(248,81,73,.1);color:var(--red)}\n.berr{background:rgba(248,81,73,.2);color:var(--red)}\n#main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}\n#toolbar{padding:8px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2)}\n#session-title{font-size:12px;font-weight:500;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:4px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}\n.abtn:hover{background:var(--border)}\n.abtn:disabled{opacity:.4;cursor:default}\n.abtn.danger{border-color:var(--red);color:var(--red)}\n.abtn.danger:hover{background:rgba(248,81,73,.1)}\n#output{flex:1;overflow-y:auto;padding:8px 12px;background:var(--bg);font-size:12px;line-height:1.6}\n.line{white-space:pre-wrap;word-break:break-all}\n#empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text2);gap:8px;text-align:center;padding:24px}\n#empty .ico{font-size:32px}\n#statusbar{padding:4px 12px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n.errmsg{color:var(--red);padding:12px}\n/* ANSI SGR */\n.ab{font-weight:bold}.ad{opacity:.6}.ai{font-style:italic}.au{text-decoration:underline}\n.f0{color:#21262d}.f1{color:#f85149}.f2{color:#3fb950}.f3{color:#d29922}\n.f4{color:#58a6ff}.f5{color:#bc8cff}.f6{color:#39d353}.f7{color:#e6edf3}\n.f8{color:#8b949e}.f9{color:#ff7b72}.f10{color:#56d364}.f11{color:#e3b341}\n.f12{color:#79c0ff}.f13{color:#d2a8ff}.f14{color:#56d364}.f15{color:#f0f6fc}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"sidebar\">\n <div id=\"sidebar-hdr\">\n <h1>Sessions</h1>\n <button id=\"refresh-btn\" title=\"Refresh\" onclick=\"doRefresh()\">↻</button>\n </div>\n <div id=\"session-list\"></div>\n </div>\n <div id=\"main\">\n <div id=\"toolbar\" style=\"display:none\">\n <span id=\"session-title\"></span>\n <button class=\"abtn danger\" id=\"kill-btn\" onclick=\"doKill()\">Kill</button>\n </div>\n <div id=\"output\">\n <div id=\"empty\"><div class=\"ico\">⚡</div><div>Select a session</div></div>\n </div>\n <div id=\"statusbar\">Connecting to bridge…</div>\n </div>\n</div>\n<script>\n${panelBridgeScript(\"agentproto-sessions-panel\")}\n\n// ============================================================\n// ANSI-to-HTML renderer (SGR codes: colors 0-15, bold/dim/italic/underline)\n// ============================================================\n\nfunction escHtml(s) {\n return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');\n}\n\nfunction ansiToHtml(raw) {\n // Strip CR overwrite sequences (progress bars: CR without LF)\n var text = raw.replace(/[^\\\\n]*\\\\r([^\\\\n])/g, '$1').replace(/\\\\r/g, '');\n var bold = false, dim = false, italic = false, underline = false, fg = -1;\n var out = '';\n // Split on ESC [ ... m (SGR) sequences; keep delimiters\n var ESC = '\u001b';\n var parts = text.split(/([\u001b]\\\\[[0-9;]*m)/);\n for (var pi = 0; pi < parts.length; pi++) {\n var part = parts[pi];\n if (part.length === 0) continue;\n if (part.charCodeAt(0) === 0x1b && part[1] === '[') {\n // Parse SGR parameters\n var inner = part.slice(2, part.length - 1); // strip ESC[ and m\n var codes = inner === '' ? [0] : inner.split(';').map(Number);\n for (var ci = 0; ci < codes.length; ci++) {\n var c = codes[ci];\n if (c === 0) { bold = false; dim = false; italic = false; underline = false; fg = -1; }\n else if (c === 1) bold = true;\n else if (c === 2) dim = true;\n else if (c === 3) italic = true;\n else if (c === 4) underline = true;\n else if (c === 22) { bold = false; dim = false; }\n else if (c === 23) italic = false;\n else if (c === 24) underline = false;\n else if (c >= 30 && c <= 37) fg = c - 30;\n else if (c === 39) fg = -1;\n else if (c >= 90 && c <= 97) fg = c - 90 + 8;\n }\n } else {\n // Text segment — strip any remaining non-printable ESC sequences\n var safe = escHtml(part).replace(/\u001b\\\\[[^m]*[A-Za-z]/g, '');\n if (safe === '') continue;\n var cls = '';\n if (bold) cls += ' ab';\n if (dim) cls += ' ad';\n if (italic) cls += ' ai';\n if (underline) cls += ' au';\n if (fg >= 0 && fg <= 15) cls += ' f' + fg;\n cls = cls.trim();\n out += cls ? '<span class=\"' + cls + '\">' + safe + '</span>' : safe;\n }\n }\n return out;\n}\n\n// ============================================================\n// App state\n// ============================================================\n\nvar sessions = [];\nvar activeId = null;\nvar outputLines = [];\nvar nextCursor = 0;\nvar pollTimer = null;\nvar pollActive = false;\n\nfunction setStatus(msg) {\n document.getElementById('statusbar').textContent = msg;\n}\n\n// ============================================================\n// Session list\n// ============================================================\n\nfunction badgeClass(status) {\n if (status === 'running') return 'br';\n if (status === 'starting') return 'bs';\n if (status === 'killed') return 'bk';\n if (status === 'error') return 'berr';\n return 'be'; // exited\n}\n\n// Derive a turn-aware display badge. A session's 'status' only tracks process\n// liveness, so an agent-cli process stays \"running\" while idle between turns.\n// Read 'busy' / 'awaitingInput' to show real activity instead:\n// working — a turn is in flight (busy)\n// waiting — turn ended on awaiting-input (needs a reply)\n// idle — process alive, no turn running (last turn done)\nfunction displayBadge(s) {\n if (s.status === 'running' && s.kind === 'agent-cli') {\n if (s.busy) return { label: 'working', cls: 'br' };\n if (s.awaitingInput) return { label: 'waiting', cls: 'bs' };\n return { label: 'idle', cls: 'be' };\n }\n return { label: s.status, cls: badgeClass(s.status) };\n}\n\nfunction renderSidebar() {\n var el = document.getElementById('session-list');\n if (sessions.length === 0) {\n el.innerHTML = '<div style=\"padding:12px;color:var(--text2);font-size:11px\">No sessions</div>';\n return;\n }\n var html = '';\n for (var i = 0; i < sessions.length; i++) {\n var s = sessions[i];\n var label = s.label || s.name || (s.command ? s.command.split('/').pop() : null) || s.id.slice(0, 8);\n var db = displayBadge(s);\n var active = s.id === activeId ? ' active' : '';\n // blockedOn (set while the turn waits on a spawned sub-agent or a\n // shell command) rides next to the status badge; waiting-on-user is\n // NOT here — that's awaitingInput, a different signal.\n var blocked = '';\n if (s.blockedOn === 'subagent') blocked = '<span class=\"badge bs\">🧩 sous-agent</span>';\n else if (s.blockedOn === 'command') blocked = '<span class=\"badge bs\">⏳ commande</span>';\n else if (s.blockedOn === 'inbox') blocked = '<span class=\"badge bs\">📬 attend messages</span>';\n html += '<div class=\"si' + active + '\" onclick=\"selectSession(\\\\'' + s.id + '\\\\')\">'\n + '<div class=\"sn\">' + escHtml(label) + '</div>'\n + '<div class=\"sm\">'\n + '<span class=\"badge ' + db.cls + '\">' + db.label + '</span>'\n + blocked\n + '<span>' + escHtml(s.kind || '') + '</span>'\n + '</div></div>';\n }\n el.innerHTML = html;\n}\n\nfunction loadSessions() {\n // {kind:'all'} is session_list's live-able default — agent-CLI + terminal/\n // PTY only. kind:\"command\" rows (a shell-execution log, not a resumable\n // session) are excluded unless includeCommands is explicitly passed.\n return callTool('session_list', {kind: 'all'}).then(function(data) {\n sessions = data.sessions || [];\n renderSidebar();\n setStatus(sessions.length + ' session' + (sessions.length === 1 ? '' : 's') + ' · ' + new Date().toLocaleTimeString());\n }).catch(function(e) {\n setStatus('Error: ' + e.message);\n });\n}\n\n// ============================================================\n// Output panel\n// ============================================================\n\nfunction renderOutputFull() {\n var el = document.getElementById('output');\n if (outputLines.length === 0) {\n el.innerHTML = '<div id=\"empty\"><div class=\"ico\">⚡</div><div>No output yet</div></div>';\n return;\n }\n var html = '';\n for (var i = 0; i < outputLines.length; i++) {\n html += '<div class=\"line\">' + ansiToHtml(outputLines[i]) + '</div>';\n }\n el.innerHTML = html;\n el.scrollTop = el.scrollHeight;\n}\n\nfunction appendLines(lines) {\n var el = document.getElementById('output');\n // Remove empty-state placeholder if present\n var empty = el.querySelector('#empty');\n if (empty) empty.parentNode.removeChild(empty);\n var frag = document.createDocumentFragment();\n for (var i = 0; i < lines.length; i++) {\n var div = document.createElement('div');\n div.className = 'line';\n div.innerHTML = ansiToHtml(lines[i]);\n frag.appendChild(div);\n }\n el.appendChild(frag);\n el.scrollTop = el.scrollHeight;\n}\n\nfunction selectSession(id) {\n activeId = id;\n outputLines = [];\n nextCursor = 0;\n renderSidebar();\n\n var s = null;\n for (var i = 0; i < sessions.length; i++) {\n if (sessions[i].id === id) { s = sessions[i]; break; }\n }\n\n var toolbar = document.getElementById('toolbar');\n toolbar.style.display = 'flex';\n document.getElementById('session-title').textContent = (s && (s.label || s.name)) || id.slice(0, 12);\n\n var killBtn = document.getElementById('kill-btn');\n killBtn.disabled = !s || (s.status !== 'running' && s.status !== 'starting');\n\n document.getElementById('output').innerHTML = '<div style=\"padding:12px;color:var(--text2)\">Loading…</div>';\n\n // Initial load\n callTool('agent_output', {sessionId: id, lastN: 200}).then(function(data) {\n outputLines = data.lines || [];\n nextCursor = data.nextCursor || 0;\n renderOutputFull();\n }).catch(function(e) {\n document.getElementById('output').innerHTML = '<div class=\"errmsg\">Error: ' + escHtml(e.message) + '</div>';\n });\n}\n\n// ============================================================\n// Kill\n// ============================================================\n\nfunction doKill() {\n if (!activeId) return;\n var s = null;\n for (var i = 0; i < sessions.length; i++) {\n if (sessions[i].id === activeId) { s = sessions[i]; break; }\n }\n var toolName = (s && s.pty) ? 'terminal_kill' : 'agent_kill';\n callTool(toolName, {sessionId: activeId}).then(function() {\n return doRefresh();\n }).catch(function(e) {\n setStatus('Kill failed: ' + e.message);\n });\n}\n\n// ============================================================\n// Poll\n// ============================================================\n\nfunction doPoll() {\n if (pollActive) return;\n pollActive = true;\n var p = loadSessions();\n if (activeId) {\n var capturedId = activeId;\n var capturedCursor = nextCursor;\n p = p.then(function() {\n return callTool('agent_output', {sessionId: capturedId, since: capturedCursor});\n }).then(function(data) {\n if (capturedId !== activeId) return; // user switched sessions\n var newLines = data.lines || [];\n if (newLines.length > 0) {\n outputLines = outputLines.concat(newLines);\n if (outputLines.length > 2000) outputLines = outputLines.slice(-2000);\n nextCursor = data.nextCursor || nextCursor;\n appendLines(newLines);\n }\n }).catch(function() {});\n }\n p.then(function() {\n pollActive = false;\n pollTimer = setTimeout(doPoll, 3000);\n }).catch(function() {\n pollActive = false;\n pollTimer = setTimeout(doPoll, 3000);\n });\n}\n\nfunction doRefresh() {\n return loadSessions().then(function() {\n if (activeId) {\n return callTool('agent_output', {sessionId: activeId, lastN: 200}).then(function(data) {\n outputLines = data.lines || [];\n nextCursor = data.nextCursor || 0;\n renderOutputFull();\n });\n }\n });\n}\n\n// ============================================================\n// Boot\n// ============================================================\n\ninitBridge().then(function() {\n return loadSessions();\n}).then(function() {\n pollTimer = setTimeout(doPoll, 3000);\n}).catch(function(e) {\n setStatus('Bridge error: ' + e.message);\n document.getElementById('output').innerHTML = '<div class=\"errmsg\">Failed to connect to MCP bridge: ' + escHtml(e.message) + '</div>';\n});\n</script>\n</body>\n</html>`\n","/**\n * McpApp definition for the agentproto sessions panel.\n *\n * Uses the AgnoMcpApp contract (a local McpApp-compatible shape that does\n * NOT depend on @agstudio/mcp-apps or Mastra). The host (@agentproto/runtime)\n * wires this factory's output via `registerMcpApps` from its\n * mcp-apps-adapter.ts at boot time — see runtime's builtin-apps.ts.\n *\n * Protocol flow:\n * 1. Tool `agentproto_sessions` is called by the host.\n * 2. execute() returns a sessions snapshot → injected into the HTML as\n * window.__APP_INIT__ (static initial render, no bridge needed).\n * 3. The HTML panel opens a JSON-RPC bridge (postMessage) and polls\n * `session_list` + `agent_output` every 3 s for live data.\n *\n * This module also exports `sessionsPanelApp`, a real `defineApp()`\n * `AppHandle` (`agents: []`, UI-only) — the catalog/emit/`app_install` path.\n * It's separate from `makeSessionsPanelApp` above, which the daemon mounts\n * directly at boot: that factory closes over LIVE `listSessions`, something\n * a static emitted `ui.html` snapshot can't carry.\n */\n\nimport { z } from \"zod\"\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { PANEL_HTML } from \"./panel.js\"\nimport type { AgnoMcpApp } from \"../mcp-app-types.js\"\n\nexport const sessionsPanelInputSchema = z.object({\n filter: z\n .enum([\"running\", \"all\"])\n .optional()\n .describe(\n \"Which sessions to return. `running` = only alive; `all` = running + recent (default).\",\n ),\n})\n\nexport type SessionsInput = z.infer<typeof sessionsPanelInputSchema>\nexport type SessionsOutput<TSession = unknown> = { sessions: TSession[] }\n\n/**\n * Generic over the host's own session-descriptor shape — this package\n * doesn't own that type (it lives in @agentproto/runtime's session\n * registry), so the factory stays agnostic to it and the host supplies the\n * concrete type as a type argument when it wires the app up.\n */\nexport interface SessionsPanelOps<TSession = unknown> {\n listSessions(filter?: \"running\" | \"all\"): TSession[]\n}\n\nexport type { AgnoMcpApp }\n\n/**\n * Factory: close over the sessions registry so execute() doesn't need\n * an AppContext (agentproto has no userId/guildId concept).\n */\nexport function makeSessionsPanelApp<TSession = unknown>(\n ops: SessionsPanelOps<TSession>,\n): AgnoMcpApp<SessionsInput, SessionsOutput<TSession>> {\n return {\n id: \"agentproto_sessions\",\n title: \"Agent Sessions\",\n description:\n \"Open the agentproto sessions panel — an interactive UI that shows all \" +\n \"running and recent agent-CLI and terminal/PTY sessions. Raw shell-\" +\n \"command runs are a log, not a resumable session, and don't appear \" +\n \"here — see `command_list`. The panel polls live data and lets you \" +\n \"inspect output or kill sessions.\",\n inputSchema: sessionsPanelInputSchema,\n execute: async (input) => ({\n sessions: ops.listSessions(input.filter),\n }),\n html: PANEL_HTML,\n }\n}\n\nexport const sessionsPanelApp: AppHandle = defineApp({\n id: \"@agentproto/sessions-panel\",\n name: \"Agent Sessions\",\n description:\n \"Open the agentproto sessions panel — an interactive UI that shows all running and recent \" +\n \"agent-CLI and terminal/PTY sessions.\",\n agents: [],\n ui: {\n html: PANEL_HTML,\n title: \"Agent Sessions\",\n tools: [\"session_list\", \"agent_output\"],\n },\n})\n"]}
|
|
@@ -68,5 +68,5 @@ export declare const WORK_BOARD_TOOL_ID = "agentproto_work_board";
|
|
|
68
68
|
* dispatches the panel's `tools/call` directly — a builtin is not an
|
|
69
69
|
* installed app, so `app_tool_call` cannot route for it. */
|
|
70
70
|
export declare const WORK_BOARD_UI_TOOLS: readonly ["task_list", "task_claim", "task_update", "task_create"];
|
|
71
|
-
export declare const WORK_BOARD_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<title>agentproto work board</title>\n<!-- PANEL_BRIDGE: replaced with the inline panelBridgeScript() output by\n vite.config.ts's injectPanelBridge plugin \u2014 a classic (non-module)\n script so its `initBridge`/`callTool` land as globals before the\n deferred module script below runs. -->\n<script>// \u2500\u2500 MCP Apps bridge (shared: panel-bridge.ts) \u2500\u2500\n// JSON-RPC 2.0 over window.parent.postMessage \u00B7 spec 2026-01-26\nvar _nextId = 1, _pending = {}, _notifyHandlers = [];\nvar _hostContext = null, _hostContextHandlers = [];\nvar _standaloneApp = null;\nfunction _isStandalone(){\n return window.parent === window && !!window.McpApp && typeof window.McpApp.connect === 'function';\n}\nfunction post(msg){ window.parent.postMessage(msg, '*'); }\nfunction getHostContext(){ return _hostContext; }\nfunction onHostContext(cb){\n _hostContextHandlers.push(cb);\n // Replay the last context so a late subscriber isn't stuck blind.\n if (_hostContext){ try { cb(_hostContext); } catch(_) {} }\n}\nfunction _setHostContext(ctx){\n if (!ctx || typeof ctx !== 'object') return;\n // ui/notifications/host-context-changed carries only the changed keys \u2014\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 \u2014 default to inline with no\n // other modes available, which keeps the #dm/#pin toggle buttons\n // hidden (they only show for modes hostContext.availableDisplayModes\n // lists) instead of rendering two buttons with nothing to switch to.\n _setHostContext({displayMode: 'inline', availableDisplayModes: []});\n });\n }\n return rpcRequest('ui/initialize', {\n appInfo: {name: \"agentproto-work-board-panel\", version: '0.1.0'},\n appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},\n protocolVersion: '2026-01-26'\n }).then(function(result){\n // The initialize result carries the initial hostContext (displayMode +\n // availableDisplayModes) \u2014 capture it before notifying the host.\n if (result && result.hostContext) _setHostContext(result.hostContext);\n rpcNotify('ui/notifications/initialized', {});\n });\n}\nfunction requestDisplayMode(mode){\n return rpcRequest('ui/request-display-mode', {mode: mode});\n}\nfunction callTool(name, args){\n var raw = _standaloneApp\n ? _standaloneApp.callTool(name, args || {})\n : rpcRequest('tools/call', {name: name, arguments: args || {}});\n return raw.then(function(result){\n if (result.isError){\n var e = (result.content && result.content[0] && result.content[0].text) || 'tool error';\n throw new Error(e);\n }\n var text = (result.content && result.content[0] && result.content[0].text) || '{}';\n return JSON.parse(text);\n });\n}\n\n// \u2500\u2500 Display-mode toggle buttons (NO auto-request) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Injected by the shared bridge so every panel gets them without touching\n// its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the\n// panel stays inline by default; the user expands on demand. Buttons only\n// appear for modes the host advertises in hostContext.availableDisplayModes.\n(function(){\n function mount(){\n var style = document.createElement('style');\n style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'\n + 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'\n + 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'\n + 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'\n + '#dm{right:8px}#pin{right:118px}'\n + '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'\n + '@media (prefers-color-scheme:dark){'\n + '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'\n + '#dm:hover,#pin:hover{background:#333;border-color:#777}}';\n document.head.appendChild(style);\n\n var btn = document.createElement('button');\n btn.id = 'dm'; btn.type = 'button'; btn.title = \"Basculer l'affichage\";\n var pin = document.createElement('button');\n pin.id = 'pin'; pin.type = 'button'; pin.title = '\u00C9pingler sur le c\u00F4t\u00E9 (pip)';\n document.body.appendChild(pin);\n document.body.appendChild(btn);\n\n function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }\n\n // Re-sync button visibility + label from the current host context.\n function syncBtn(ctx){\n ctx = ctx || {};\n var avail = ctx.availableDisplayModes;\n // Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)\n console.log('[mcp-app] displayMode=', ctx.displayMode,\n 'availableDisplayModes=', avail);\n\n // Fullscreen toggle button.\n if (has(avail, 'fullscreen')){\n btn.style.display = 'block';\n btn.textContent = (ctx.displayMode === 'fullscreen') ? '\u2921 R\u00E9duire' : '\u2922 Agrandir';\n } else { btn.style.display = 'none'; }\n\n // Dedicated pip (\"pinned on side\") button \u2014 only if the host advertises pip.\n if (has(avail, 'pip')){\n pin.style.display = 'block';\n pin.textContent = (ctx.displayMode === 'pip') ? '\u2921 D\u00E9tacher' : '\uD83D\uDCCC \u00C9pingler';\n } else { pin.style.display = 'none'; }\n }\n\n onHostContext(syncBtn);\n\n btn.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');\n requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});\n });\n\n pin.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});\n });\n }\n if (document.body) mount();\n else document.addEventListener('DOMContentLoaded', mount);\n})();</script>\n <script>(function() {\n \"use strict\";\n const COLUMN_STATUSES = [\"pending\", \"in_progress\", \"done\", \"failed\"];\n const COLUMN_LABELS = {\n pending: \"Pending\",\n in_progress: \"In Progress\",\n done: \"Done\",\n failed: \"Failed\"\n };\n function esc(value) {\n return value.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n }\n function columnOf(status) {\n return status === \"cancelled\" ? \"failed\" : status;\n }\n function verificationTag(task) {\n const v = task.verification;\n if (v) {\n if (v.kind === \"gate\") return '<span class=\"tag t-gate\">✓ gate</span>';\n if (v.kind === \"self-report\") return '<span class=\"tag t-self\">self-report</span>';\n if (v.kind === \"human\") return '<span class=\"tag t-human\">human</span>';\n }\n if (task.status === \"in_progress\" && task.verify) {\n return '<span class=\"tag t-gated\">gated</span>';\n }\n return \"\";\n }\n function ownerTag(task) {\n if (!task.owner) return '<span class=\"tag t-unclaimed\">Unclaimed</span>';\n return `<span class=\"tag t-owner\">${esc(task.owner)}</span>`;\n }\n function actionsFor(task) {\n if (task.status === \"pending\") {\n return [\n task.owner ? { label: \"Start\", action: \"start\", cls: \"primary\" } : { label: \"Claim\", action: \"claim\", cls: \"primary\" },\n { label: \"Cancel\", action: \"cancel\", cls: \"danger\" }\n ];\n }\n if (task.status === \"in_progress\") {\n return [\n { label: \"Done\", action: \"done\", cls: \"primary\" },\n { label: \"Fail\", action: \"fail\", cls: \"danger\" },\n { label: \"Release\", action: \"release\", cls: \"\" },\n { label: \"Cancel\", action: \"cancel\", cls: \"danger\" }\n ];\n }\n if (task.status === \"done\") {\n return [{ label: \"Reopen\", action: \"reopen\", cls: \"\" }];\n }\n return [];\n }\n function renderCard(task) {\n let html = `<div class=\"card\" data-taskid=\"${esc(task.taskId)}\">`;\n html += `<div class=\"c-title\">${esc(task.title)}</div>`;\n html += `<div class=\"c-id\">#${esc(task.taskId)}</div>`;\n html += `<div class=\"c-row\">${ownerTag(task)}${verificationTag(task)}`;\n if (task.status === \"cancelled\") html += '<span class=\"tag t-cancelled\">cancelled</span>';\n html += \"</div>\";\n if (task.lastVerifyError) {\n html += `<div class=\"c-err\">verify failed: ${esc(task.lastVerifyError)}</div>`;\n }\n const actions = actionsFor(task);\n if (actions.length > 0) {\n html += '<div class=\"c-actions\">';\n for (const a of actions) {\n html += `<button class=\"abtn ${a.cls}\" data-taskid=\"${esc(task.taskId)}\" data-action=\"${a.action}\">${a.label}</button>`;\n }\n html += \"</div>\";\n }\n html += \"</div>\";\n return html;\n }\n function renderColumns(tasks2) {\n let html = \"\";\n for (const status of COLUMN_STATUSES) {\n const rows = tasks2.filter((t) => columnOf(t.status) === status);\n html += `<div class=\"col\"><div class=\"col-hdr\"><span>${COLUMN_LABELS[status]}</span><span>${rows.length}</span></div><div class=\"col-body\">`;\n html += rows.length === 0 ? '<div class=\"empty\">No tasks</div>' : rows.map(renderCard).join(\"\");\n html += \"</div></div>\";\n }\n return html;\n }\n function getEl(id) {\n const el = document.getElementById(id);\n if (!el) throw new Error(`work board: missing #${id}`);\n return el;\n }\n function getInput(id) {\n const el = document.getElementById(id);\n if (!(el instanceof HTMLInputElement)) throw new Error(`work board: missing input #${id}`);\n return el;\n }\n function isCardAction(value) {\n switch (value) {\n case \"claim\":\n case \"start\":\n case \"done\":\n case \"fail\":\n case \"release\":\n case \"cancel\":\n case \"reopen\":\n return true;\n default:\n return false;\n }\n }\n let boardId = null;\n let tasks = [];\n let pollActive = false;\n function boot() {\n const columnsEl = getEl(\"columns\");\n const statusbarEl = getEl(\"statusbar\");\n const boardIdEl = getEl(\"board-id\");\n const boardInputEl = getInput(\"board-input\");\n const newTitleEl = getInput(\"new-title\");\n function setStatus(msg) {\n statusbarEl.textContent = msg;\n }\n function render() {\n boardIdEl.textContent = boardId ?? \"\u2014\";\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\"} \u00B7 ${(/* @__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 \u2014 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>/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 \u2014 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 \u2014 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 \u2014 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 \u2014 the agentproto brand tokens (see\n projects/agentproto/site/src/app/brand.css), used whenever no\n --vscode-* variable is set \u2014 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` \u2014 a standalone tab has\n no host to pick a theme for it, unlike VS Code's own.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\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 \u2014 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/* \u2500\u2500 Base \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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\" \u2014 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\">\u2014</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";
|
|
71
|
+
export declare const WORK_BOARD_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n<title>agentproto work board</title>\n<!-- PANEL_BRIDGE: replaced with the inline panelBridgeScript() output by\n vite.config.ts's injectPanelBridge plugin \u2014 a classic (non-module)\n script so its `initBridge`/`callTool` land as globals before the\n deferred module script below runs. -->\n<script>// \u2500\u2500 MCP Apps bridge (shared: panel-bridge.ts) \u2500\u2500\n// JSON-RPC 2.0 over window.parent.postMessage \u00B7 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// \u2500\u2500 Per-boot embed token (runtime embed-tokens.ts) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 \u2014 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 \u2014\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 \u2014 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) \u2014 capture it before notifying the host.\n if (result && result.hostContext) _setHostContext(result.hostContext);\n // McpUiInitializeResult.hostCapabilities (ext-apps spec) \u2014 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// \u2500\u2500 Display-mode toggle (shared: @agentproto/app-client/display-mode) \u2500\u2500\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 \u2014 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 \u2014 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 \u2014 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 \u2014\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 \u2014 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 ?? \"\u2014\";\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\"} \u00B7 ${(/* @__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 \u2014 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>/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 \u2014 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 \u2014 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 \u2014 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 \u2014 the agentproto brand tokens (see\n projects/agentproto/site/src/app/brand.css), used whenever no\n --vscode-* variable is set \u2014 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` \u2014 a standalone tab has\n no host to pick a theme for it, unlike VS Code's own.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\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 \u2014 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/* \u2500\u2500 Base \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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\" \u2014 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\">\u2014</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";
|
|
72
72
|
//# sourceMappingURL=panel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/work-board/panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAIH;;;;mBAImB;AACnB,eAAO,MAAM,iBAAiB,2BAA2B,CAAA;AAEzD;gEACgE;AAChE,eAAO,MAAM,kBAAkB,0BAA0B,CAAA;AAEzD;;;6DAG6D;AAC7D,eAAO,MAAM,mBAAmB,oEAKtB,CAAA;AAEV,eAAO,MAAM,eAAe,
|
|
1
|
+
{"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/work-board/panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAIH;;;;mBAImB;AACnB,eAAO,MAAM,iBAAiB,2BAA2B,CAAA;AAEzD;gEACgE;AAChE,eAAO,MAAM,kBAAkB,0BAA0B,CAAA;AAEzD;;;6DAG6D;AAC7D,eAAO,MAAM,mBAAmB,oEAKtB,CAAA;AAEV,eAAO,MAAM,eAAe,uloCAA4B,CAAA"}
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
* `pnpm --filter @agentproto/apps run build:ui:check` (wired into CI) fails
|
|
10
10
|
* if this file drifts from what the Vite sources actually produce.
|
|
11
11
|
*/
|
|
12
|
-
export declare 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 \u2014 a classic (non-module)\n script so its `initBridge`/`callTool` land as globals before the\n deferred module script below runs. -->\n<script>// \u2500\u2500 MCP Apps bridge (shared: panel-bridge.ts) \u2500\u2500\n// JSON-RPC 2.0 over window.parent.postMessage \u00B7 spec 2026-01-26\nvar _nextId = 1, _pending = {}, _notifyHandlers = [];\nvar _hostContext = null, _hostContextHandlers = [];\nvar _standaloneApp = null;\nfunction _isStandalone(){\n return window.parent === window && !!window.McpApp && typeof window.McpApp.connect === 'function';\n}\nfunction post(msg){ window.parent.postMessage(msg, '*'); }\nfunction getHostContext(){ return _hostContext; }\nfunction onHostContext(cb){\n _hostContextHandlers.push(cb);\n // Replay the last context so a late subscriber isn't stuck blind.\n if (_hostContext){ try { cb(_hostContext); } catch(_) {} }\n}\nfunction _setHostContext(ctx){\n if (!ctx || typeof ctx !== 'object') return;\n // ui/notifications/host-context-changed carries only the changed keys \u2014\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 \u2014 default to inline with no\n // other modes available, which keeps the #dm/#pin toggle buttons\n // hidden (they only show for modes hostContext.availableDisplayModes\n // lists) instead of rendering two buttons with nothing to switch to.\n _setHostContext({displayMode: 'inline', availableDisplayModes: []});\n });\n }\n return rpcRequest('ui/initialize', {\n appInfo: {name: \"agentproto-work-board-panel\", version: '0.1.0'},\n appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},\n protocolVersion: '2026-01-26'\n }).then(function(result){\n // The initialize result carries the initial hostContext (displayMode +\n // availableDisplayModes) \u2014 capture it before notifying the host.\n if (result && result.hostContext) _setHostContext(result.hostContext);\n rpcNotify('ui/notifications/initialized', {});\n });\n}\nfunction requestDisplayMode(mode){\n return rpcRequest('ui/request-display-mode', {mode: mode});\n}\nfunction callTool(name, args){\n var raw = _standaloneApp\n ? _standaloneApp.callTool(name, args || {})\n : rpcRequest('tools/call', {name: name, arguments: args || {}});\n return raw.then(function(result){\n if (result.isError){\n var e = (result.content && result.content[0] && result.content[0].text) || 'tool error';\n throw new Error(e);\n }\n var text = (result.content && result.content[0] && result.content[0].text) || '{}';\n return JSON.parse(text);\n });\n}\n\n// \u2500\u2500 Display-mode toggle buttons (NO auto-request) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Injected by the shared bridge so every panel gets them without touching\n// its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the\n// panel stays inline by default; the user expands on demand. Buttons only\n// appear for modes the host advertises in hostContext.availableDisplayModes.\n(function(){\n function mount(){\n var style = document.createElement('style');\n style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'\n + 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'\n + 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'\n + 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'\n + '#dm{right:8px}#pin{right:118px}'\n + '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'\n + '@media (prefers-color-scheme:dark){'\n + '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'\n + '#dm:hover,#pin:hover{background:#333;border-color:#777}}';\n document.head.appendChild(style);\n\n var btn = document.createElement('button');\n btn.id = 'dm'; btn.type = 'button'; btn.title = \"Basculer l'affichage\";\n var pin = document.createElement('button');\n pin.id = 'pin'; pin.type = 'button'; pin.title = '\u00C9pingler sur le c\u00F4t\u00E9 (pip)';\n document.body.appendChild(pin);\n document.body.appendChild(btn);\n\n function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }\n\n // Re-sync button visibility + label from the current host context.\n function syncBtn(ctx){\n ctx = ctx || {};\n var avail = ctx.availableDisplayModes;\n // Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)\n console.log('[mcp-app] displayMode=', ctx.displayMode,\n 'availableDisplayModes=', avail);\n\n // Fullscreen toggle button.\n if (has(avail, 'fullscreen')){\n btn.style.display = 'block';\n btn.textContent = (ctx.displayMode === 'fullscreen') ? '\u2921 R\u00E9duire' : '\u2922 Agrandir';\n } else { btn.style.display = 'none'; }\n\n // Dedicated pip (\"pinned on side\") button \u2014 only if the host advertises pip.\n if (has(avail, 'pip')){\n pin.style.display = 'block';\n pin.textContent = (ctx.displayMode === 'pip') ? '\u2921 D\u00E9tacher' : '\uD83D\uDCCC \u00C9pingler';\n } else { pin.style.display = 'none'; }\n }\n\n onHostContext(syncBtn);\n\n btn.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');\n requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});\n });\n\n pin.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});\n });\n }\n if (document.body) mount();\n else document.addEventListener('DOMContentLoaded', mount);\n})();</script>\n <script>(function() {\n \"use strict\";\n const COLUMN_STATUSES = [\"pending\", \"in_progress\", \"done\", \"failed\"];\n const COLUMN_LABELS = {\n pending: \"Pending\",\n in_progress: \"In Progress\",\n done: \"Done\",\n failed: \"Failed\"\n };\n function esc(value) {\n return value.replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n }\n function columnOf(status) {\n return status === \"cancelled\" ? \"failed\" : status;\n }\n function verificationTag(task) {\n const v = task.verification;\n if (v) {\n if (v.kind === \"gate\") return '<span class=\"tag t-gate\">✓ gate</span>';\n if (v.kind === \"self-report\") return '<span class=\"tag t-self\">self-report</span>';\n if (v.kind === \"human\") return '<span class=\"tag t-human\">human</span>';\n }\n if (task.status === \"in_progress\" && task.verify) {\n return '<span class=\"tag t-gated\">gated</span>';\n }\n return \"\";\n }\n function ownerTag(task) {\n if (!task.owner) return '<span class=\"tag t-unclaimed\">Unclaimed</span>';\n return `<span class=\"tag t-owner\">${esc(task.owner)}</span>`;\n }\n function actionsFor(task) {\n if (task.status === \"pending\") {\n return [\n task.owner ? { label: \"Start\", action: \"start\", cls: \"primary\" } : { label: \"Claim\", action: \"claim\", cls: \"primary\" },\n { label: \"Cancel\", action: \"cancel\", cls: \"danger\" }\n ];\n }\n if (task.status === \"in_progress\") {\n return [\n { label: \"Done\", action: \"done\", cls: \"primary\" },\n { label: \"Fail\", action: \"fail\", cls: \"danger\" },\n { label: \"Release\", action: \"release\", cls: \"\" },\n { label: \"Cancel\", action: \"cancel\", cls: \"danger\" }\n ];\n }\n if (task.status === \"done\") {\n return [{ label: \"Reopen\", action: \"reopen\", cls: \"\" }];\n }\n return [];\n }\n function renderCard(task) {\n let html = `<div class=\"card\" data-taskid=\"${esc(task.taskId)}\">`;\n html += `<div class=\"c-title\">${esc(task.title)}</div>`;\n html += `<div class=\"c-id\">#${esc(task.taskId)}</div>`;\n html += `<div class=\"c-row\">${ownerTag(task)}${verificationTag(task)}`;\n if (task.status === \"cancelled\") html += '<span class=\"tag t-cancelled\">cancelled</span>';\n html += \"</div>\";\n if (task.lastVerifyError) {\n html += `<div class=\"c-err\">verify failed: ${esc(task.lastVerifyError)}</div>`;\n }\n const actions = actionsFor(task);\n if (actions.length > 0) {\n html += '<div class=\"c-actions\">';\n for (const a of actions) {\n html += `<button class=\"abtn ${a.cls}\" data-taskid=\"${esc(task.taskId)}\" data-action=\"${a.action}\">${a.label}</button>`;\n }\n html += \"</div>\";\n }\n html += \"</div>\";\n return html;\n }\n function renderColumns(tasks2) {\n let html = \"\";\n for (const status of COLUMN_STATUSES) {\n const rows = tasks2.filter((t) => columnOf(t.status) === status);\n html += `<div class=\"col\"><div class=\"col-hdr\"><span>${COLUMN_LABELS[status]}</span><span>${rows.length}</span></div><div class=\"col-body\">`;\n html += rows.length === 0 ? '<div class=\"empty\">No tasks</div>' : rows.map(renderCard).join(\"\");\n html += \"</div></div>\";\n }\n return html;\n }\n function getEl(id) {\n const el = document.getElementById(id);\n if (!el) throw new Error(`work board: missing #${id}`);\n return el;\n }\n function getInput(id) {\n const el = document.getElementById(id);\n if (!(el instanceof HTMLInputElement)) throw new Error(`work board: missing input #${id}`);\n return el;\n }\n function isCardAction(value) {\n switch (value) {\n case \"claim\":\n case \"start\":\n case \"done\":\n case \"fail\":\n case \"release\":\n case \"cancel\":\n case \"reopen\":\n return true;\n default:\n return false;\n }\n }\n let boardId = null;\n let tasks = [];\n let pollActive = false;\n function boot() {\n const columnsEl = getEl(\"columns\");\n const statusbarEl = getEl(\"statusbar\");\n const boardIdEl = getEl(\"board-id\");\n const boardInputEl = getInput(\"board-input\");\n const newTitleEl = getInput(\"new-title\");\n function setStatus(msg) {\n statusbarEl.textContent = msg;\n }\n function render() {\n boardIdEl.textContent = boardId ?? \"\u2014\";\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\"} \u00B7 ${(/* @__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 \u2014 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>/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 \u2014 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 \u2014 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 \u2014 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 \u2014 the agentproto brand tokens (see\n projects/agentproto/site/src/app/brand.css), used whenever no\n --vscode-* variable is set \u2014 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` \u2014 a standalone tab has\n no host to pick a theme for it, unlike VS Code's own.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\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 \u2014 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/* \u2500\u2500 Base \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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\" \u2014 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\">\u2014</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";
|
|
12
|
+
export declare 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 \u2014 a classic (non-module)\n script so its `initBridge`/`callTool` land as globals before the\n deferred module script below runs. -->\n<script>// \u2500\u2500 MCP Apps bridge (shared: panel-bridge.ts) \u2500\u2500\n// JSON-RPC 2.0 over window.parent.postMessage \u00B7 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// \u2500\u2500 Per-boot embed token (runtime embed-tokens.ts) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 \u2014 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 \u2014\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 \u2014 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) \u2014 capture it before notifying the host.\n if (result && result.hostContext) _setHostContext(result.hostContext);\n // McpUiInitializeResult.hostCapabilities (ext-apps spec) \u2014 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// \u2500\u2500 Display-mode toggle (shared: @agentproto/app-client/display-mode) \u2500\u2500\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 \u2014 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 \u2014 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 \u2014 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 \u2014\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 \u2014 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 ?? \"\u2014\";\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\"} \u00B7 ${(/* @__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 \u2014 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>/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\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 \u2014 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 \u2014 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 \u2014 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 \u2014 the agentproto brand tokens (see\n projects/agentproto/site/src/app/brand.css), used whenever no\n --vscode-* variable is set \u2014 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` \u2014 a standalone tab has\n no host to pick a theme for it, unlike VS Code's own.\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\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 \u2014 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/* \u2500\u2500 Base \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\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\" \u2014 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\">\u2014</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";
|
|
13
13
|
//# sourceMappingURL=panel.generated.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel.generated.d.ts","sourceRoot":"","sources":["../../src/work-board/panel.generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,yBAAyB,
|
|
1
|
+
{"version":3,"file":"panel.generated.d.ts","sourceRoot":"","sources":["../../src/work-board/panel.generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,yBAAyB,uloCAAs2lC,CAAA"}
|