@agentproto/apps 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/agents-overview/panel.mjs +14 -67
  2. package/dist/agents-overview/panel.mjs.map +1 -1
  3. package/dist/agents-overview.mjs +13 -68
  4. package/dist/agents-overview.mjs.map +1 -1
  5. package/dist/bin/sync.d.ts.map +1 -1
  6. package/dist/bin/sync.mjs +2374 -2
  7. package/dist/bin/sync.mjs.map +1 -1
  8. package/dist/bureau-sessions/panel.mjs +14 -67
  9. package/dist/bureau-sessions/panel.mjs.map +1 -1
  10. package/dist/bureau-sessions.mjs +13 -68
  11. package/dist/bureau-sessions.mjs.map +1 -1
  12. package/dist/config/fragment.d.ts +50 -0
  13. package/dist/config/fragment.d.ts.map +1 -0
  14. package/dist/config/index.d.ts +23 -0
  15. package/dist/config/index.d.ts.map +1 -0
  16. package/dist/config/ui.d.ts +43 -0
  17. package/dist/config/ui.d.ts.map +1 -0
  18. package/dist/config.mjs +2373 -0
  19. package/dist/config.mjs.map +1 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.mjs +2706 -138
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/live-session/logic.d.ts +2 -0
  25. package/dist/live-session/logic.d.ts.map +1 -1
  26. package/dist/live-session/panel.d.ts.map +1 -1
  27. package/dist/live-session/panel.mjs +27 -74
  28. package/dist/live-session/panel.mjs.map +1 -1
  29. package/dist/live-session.mjs +26 -75
  30. package/dist/live-session.mjs.map +1 -1
  31. package/dist/panel-bridge.d.ts +11 -6
  32. package/dist/panel-bridge.d.ts.map +1 -1
  33. package/dist/session-chat/index.d.ts.map +1 -1
  34. package/dist/session-chat/panel.mjs +14 -67
  35. package/dist/session-chat/panel.mjs.map +1 -1
  36. package/dist/session-chat.mjs +28 -69
  37. package/dist/session-chat.mjs.map +1 -1
  38. package/dist/session-story/panel.mjs +14 -67
  39. package/dist/session-story/panel.mjs.map +1 -1
  40. package/dist/session-story.mjs +13 -68
  41. package/dist/session-story.mjs.map +1 -1
  42. package/dist/session-viewer/ui.d.ts.map +1 -1
  43. package/dist/session-viewer.mjs +10 -0
  44. package/dist/session-viewer.mjs.map +1 -1
  45. package/dist/sessions-panel/panel.d.ts.map +1 -1
  46. package/dist/sessions-panel/panel.mjs +15 -67
  47. package/dist/sessions-panel/panel.mjs.map +1 -1
  48. package/dist/sessions-panel.mjs +14 -68
  49. package/dist/sessions-panel.mjs.map +1 -1
  50. package/dist/work-board/panel.d.ts +1 -1
  51. package/dist/work-board/panel.d.ts.map +1 -1
  52. package/dist/work-board/panel.generated.d.ts +1 -1
  53. package/dist/work-board/panel.generated.d.ts.map +1 -1
  54. package/dist/work-board/panel.mjs +293 -62
  55. package/dist/work-board/panel.mjs.map +1 -1
  56. package/dist/work-board.mjs +293 -62
  57. package/dist/work-board.mjs.map +1 -1
  58. package/package.json +12 -4
  59. package/repo-maintenance/.agentproto/APP.md +22 -0
  60. package/repo-maintenance/.agentproto/agents/repo-maintenance-reviewer/AGENT.md +142 -0
  61. package/repo-maintenance/.agentproto/workflows/maintain/WORKFLOW.md +262 -0
  62. package/repo-maintenance/.agentproto/workflows/maintain/entry.mjs +672 -0
  63. package/repo-maintenance/README.md +80 -0
  64. package/repo-maintenance/routines/repo-maintenance-daily/ROUTINE.md +77 -0
  65. package/repo-maintenance/routines/repo-maintenance-weekly/ROUTINE.md +82 -0
@@ -1,9 +1,10 @@
1
+ import { DISPLAY_MODE_SCRIPT_BODY } from '@agentproto/app-client/display-mode';
2
+
1
3
  /**
2
4
  * @agentproto/apps v0.1.0
3
5
  * Ready-made agentproto apps (teams of agents + workflows) built on app-kit.
4
6
  */
5
7
 
6
- // src/panel-bridge.ts
7
8
  function panelBridgeScript(appName) {
8
9
  return `// \u2500\u2500 MCP Apps bridge (shared: panel-bridge.ts) \u2500\u2500
9
10
  // JSON-RPC 2.0 over window.parent.postMessage \xB7 spec 2026-01-26
@@ -80,7 +81,7 @@ function initBridge(){
80
81
  return window.McpApp.connect().then(function(conn){
81
82
  _standaloneApp = conn;
82
83
  // No host to advertise a hostContext \u2014 default to inline with no
83
- // other modes available, which keeps the #dm/#pin toggle buttons
84
+ // other modes available, which keeps the display-mode toggle buttons
84
85
  // hidden (they only show for modes hostContext.availableDisplayModes
85
86
  // lists) instead of rendering two buttons with nothing to switch to.
86
87
  _setHostContext({displayMode: 'inline', availableDisplayModes: []});
@@ -122,71 +123,17 @@ function callTool(name, args){
122
123
  });
123
124
  }
124
125
 
125
- // \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
126
- // Injected by the shared bridge so every panel gets them without touching
127
- // its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the
128
- // panel stays inline by default; the user expands on demand. Buttons only
129
- // appear for modes the host advertises in hostContext.availableDisplayModes.
130
- (function(){
131
- function mount(){
132
- var style = document.createElement('style');
133
- style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'
134
- + 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'
135
- + 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'
136
- + 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'
137
- + '#dm{right:8px}#pin{right:118px}'
138
- + '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'
139
- + '@media (prefers-color-scheme:dark){'
140
- + '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'
141
- + '#dm:hover,#pin:hover{background:#333;border-color:#777}}';
142
- document.head.appendChild(style);
143
-
144
- var btn = document.createElement('button');
145
- btn.id = 'dm'; btn.type = 'button'; btn.title = "Basculer l'affichage";
146
- var pin = document.createElement('button');
147
- pin.id = 'pin'; pin.type = 'button'; pin.title = '\xC9pingler sur le c\xF4t\xE9 (pip)';
148
- document.body.appendChild(pin);
149
- document.body.appendChild(btn);
150
-
151
- function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }
152
-
153
- // Re-sync button visibility + label from the current host context.
154
- function syncBtn(ctx){
155
- ctx = ctx || {};
156
- var avail = ctx.availableDisplayModes;
157
- // Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)
158
- console.log('[mcp-app] displayMode=', ctx.displayMode,
159
- 'availableDisplayModes=', avail);
160
-
161
- // Fullscreen toggle button.
162
- if (has(avail, 'fullscreen')){
163
- btn.style.display = 'block';
164
- btn.textContent = (ctx.displayMode === 'fullscreen') ? '\u2921 R\xE9duire' : '\u2922 Agrandir';
165
- } else { btn.style.display = 'none'; }
166
-
167
- // Dedicated pip ("pinned on side") button \u2014 only if the host advertises pip.
168
- if (has(avail, 'pip')){
169
- pin.style.display = 'block';
170
- pin.textContent = (ctx.displayMode === 'pip') ? '\u2921 D\xE9tacher' : '\u{1F4CC} \xC9pingler';
171
- } else { pin.style.display = 'none'; }
172
- }
173
-
174
- onHostContext(syncBtn);
175
-
176
- btn.addEventListener('click', function(){
177
- var ctx = getHostContext() || {};
178
- var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');
179
- requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});
180
- });
181
-
182
- pin.addEventListener('click', function(){
183
- var ctx = getHostContext() || {};
184
- requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});
185
- });
186
- }
187
- if (document.body) mount();
188
- else document.addEventListener('DOMContentLoaded', mount);
189
- })();`;
126
+ // \u2500\u2500 Display-mode toggle (shared: @agentproto/app-client/display-mode) \u2500\u2500
127
+ // The installer is inlined verbatim (it is self-contained and idempotent),
128
+ // then handed this panel's own JSON-RPC plumbing. NO auto-request: the
129
+ // panel stays inline until the user clicks, and the buttons only appear for
130
+ // the modes the host advertises in hostContext.availableDisplayModes.
131
+ ${DISPLAY_MODE_SCRIPT_BODY}
132
+ var displayMode = window.AgentprotoUI.installDisplayMode({
133
+ getHostContext: getHostContext,
134
+ onHostContext: onHostContext,
135
+ requestDisplayMode: requestDisplayMode
136
+ });`;
190
137
  }
191
138
 
192
139
  // src/agents-overview/panel.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/panel-bridge.ts","../../src/agents-overview/panel.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;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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;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;AAoG7C;;;AC3PO,IAAM,oBAAA,GAAuB,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,EAuDlC,iBAAA,CAAkB,4BAA4B,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,OAAA","file":"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 _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 #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 // 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 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 \"agents — vue claire\" panel — the\n * `AGENTS_OVERVIEW_HTML` served as the `ui://agentproto_agents_overview/view`\n * resource. Mounted via the `agentproto_agents_overview` tool defined in\n * ./index.ts.\n *\n * The panel polls `session_list` every ~12s, then calls the host's\n * `summarize_session` tool (registered server-side by\n * @agentproto/runtime's summarize-session-tool.ts, NOT part of this\n * package — that tool is tied to the runtime's session ring-buffer/hot\n * path) for each visible session to fetch the plain sentence + state.\n */\n\nimport { panelBridgeScript } from \"../panel-bridge.js\"\n\n// ── HTML bundle (zero CDN, CSP-safe inline) ──────────────────────────────────\n\nexport const AGENTS_OVERVIEW_HTML = `<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>agents — vue claire</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:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}\n#app{display:flex;flex-direction:column;height:100%}\n#hdr{padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;background:var(--bg2);flex-shrink:0}\n#hdr h1{font-size:13px;font-weight:600;letter-spacing:.02em}\n#hdr .sub{font-size:11px;color:var(--text2);margin-top:2px}\n#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:16px;line-height:1;padding:4px 6px;border-radius:6px}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n#grid{flex:1;overflow-y:auto;padding:14px 16px;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;align-content:start}\n.card{background:var(--bg2);border:1px solid var(--border);border-left:3px solid var(--border);border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;min-height:96px}\n.card.s-todo{border-left-color:var(--purple)}\n.card.s-work{border-left-color:var(--green)}\n.card.s-wait{border-left-color:var(--yellow)}\n.card.s-done{border-left-color:var(--text2)}\n.card-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\n.title{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}\n.state{font-size:10px;font-weight:700;padding:2px 8px;border-radius:11px;white-space:nowrap;letter-spacing:.02em}\n.state.s-todo{background:rgba(188,140,255,.16);color:var(--purple)}\n.state.s-work{background:rgba(63,185,80,.16);color:var(--green)}\n.state.s-wait{background:rgba(210,153,34,.16);color:var(--yellow)}\n.state.s-done{background:rgba(139,148,158,.14);color:var(--text2)}\n.summary{font-size:12.5px;line-height:1.5;color:var(--text);word-break:break-word}\n.summary.muted{color:var(--text2);font-style:italic}\n.meta{font-size:10.5px;color:var(--text2);display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:auto}\n.dot{width:5px;height:5px;border-radius:50%;background:var(--text2);display:inline-block}\n#empty{grid-column:1/-1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text2);gap:8px;padding:48px;text-align:center}\n#empty .ico{font-size:30px}\n#statusbar{padding:5px 16px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"hdr\">\n <div>\n <h1>Agents — vue claire</h1>\n <div class=\"sub\">une carte par agent · résumé + état</div>\n </div>\n <button id=\"refresh-btn\" title=\"Rafraîchir\" onclick=\"doRefresh()\">&#8635;</button>\n </div>\n <div id=\"grid\"><div id=\"empty\"><div class=\"ico\">&#9889;</div><div>Connexion au bridge&#8230;</div></div></div>\n <div id=\"statusbar\">Connexion&#8230;</div>\n</div>\n<script>\n${panelBridgeScript(\"agentproto-agents-overview\")}\n\n// ── State ──\nvar REFRESH_MS = 12000;\nvar pollTimer = null, polling = false;\nfunction esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }\nfunction setStatus(m){ document.getElementById('statusbar').textContent = m; }\n\nvar STATE_CLASS = {\n 'à traiter':'s-todo', 'au travail':'s-work', 'en attente':'s-wait', 'terminé':'s-done'\n};\nfunction stateClass(st){ return STATE_CLASS[st] || 's-wait'; }\n\nfunction fmtAgo(iso){\n if (!iso) return '';\n var t = Date.parse(iso);\n if (isNaN(t)) return '';\n var s = Math.max(0, Math.round((Date.now() - t) / 1000));\n if (s < 60) return 'il y a ' + s + 's';\n var m = Math.round(s/60);\n if (m < 60) return 'il y a ' + m + 'min';\n var h = Math.round(m/60);\n return 'il y a ' + h + 'h';\n}\n\nfunction titleOf(s){\n return s.label || s.name || (s.command ? s.command.split(/\\\\s+/)[0].split('/').pop() : null) || s.id.slice(0,8);\n}\n\nfunction render(sessions, summaries){\n var grid = document.getElementById('grid');\n if (!sessions.length){\n grid.innerHTML = '<div id=\"empty\"><div class=\"ico\">&#128564;</div><div>Aucune session d\\\\'agent</div></div>';\n return;\n }\n var html = '';\n for (var i=0;i<sessions.length;i++){\n var s = sessions[i];\n var sum = summaries[s.id] || {};\n var st = sum.state || 'en attente';\n var cls = stateClass(st);\n var summary = sum.summary || 'Résumé indisponible.';\n var muted = !sum.summary ? ' muted' : '';\n html += '<div class=\"card ' + cls + '\">'\n + '<div class=\"card-top\">'\n + '<span class=\"title\">' + esc(titleOf(s)) + '</span>'\n + '<span class=\"state ' + cls + '\">' + esc(st) + '</span>'\n + '</div>'\n + '<div class=\"summary' + muted + '\">' + esc(summary) + '</div>'\n + '<div class=\"meta\">'\n + '<span>' + esc(s.kind || '') + '</span><span class=\"dot\"></span>'\n + '<span>' + esc(s.status || '') + '</span>'\n + (s.lastOutputAt ? '<span class=\"dot\"></span><span>' + esc(fmtAgo(s.lastOutputAt)) + '</span>' : '')\n + '</div>'\n + '</div>';\n }\n grid.innerHTML = html;\n}\n\nfunction loadAndRender(){\n return callTool('session_list', {kind:'all'}).then(function(data){\n var all = data.sessions || [];\n var agents = all.filter(function(s){ return s.kind === 'agent-cli'; });\n // Render shells immediately, then fill summaries as they arrive.\n var summaries = {};\n render(agents, summaries);\n setStatus(agents.length + ' agent' + (agents.length===1?'':'s') + ' · ' + new Date().toLocaleTimeString('fr-FR'));\n return Promise.all(agents.map(function(s){\n return callTool('summarize_session', {sessionId: s.id}).then(function(sum){\n summaries[s.id] = sum;\n }).catch(function(){ /* leave shell */ });\n })).then(function(){ render(agents, summaries); });\n }).catch(function(e){ setStatus('Erreur : ' + e.message); });\n}\n\nfunction doPoll(){\n if (polling) return;\n polling = true;\n loadAndRender().then(function(){\n polling = false;\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n }).catch(function(){\n polling = false;\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n });\n}\nfunction doRefresh(){ if (pollTimer) clearTimeout(pollTimer); return loadAndRender().then(function(){ pollTimer = setTimeout(doPoll, REFRESH_MS); }); }\n\ninitBridge().then(loadAndRender).then(function(){\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n}).catch(function(e){\n setStatus('Bridge : ' + e.message);\n document.getElementById('grid').innerHTML = '<div id=\"empty\"><div class=\"ico\">&#9888;</div><div>Échec connexion bridge : ' + esc(e.message) + '</div></div>';\n});\n</script>\n</body>\n</html>`\n"]}
1
+ {"version":3,"sources":["../../src/panel-bridge.ts","../../src/agents-overview/panel.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;;;AC5MO,IAAM,oBAAA,GAAuB,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,EAuDlC,iBAAA,CAAkB,4BAA4B,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,OAAA","file":"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 \"agents — vue claire\" panel — the\n * `AGENTS_OVERVIEW_HTML` served as the `ui://agentproto_agents_overview/view`\n * resource. Mounted via the `agentproto_agents_overview` tool defined in\n * ./index.ts.\n *\n * The panel polls `session_list` every ~12s, then calls the host's\n * `summarize_session` tool (registered server-side by\n * @agentproto/runtime's summarize-session-tool.ts, NOT part of this\n * package — that tool is tied to the runtime's session ring-buffer/hot\n * path) for each visible session to fetch the plain sentence + state.\n */\n\nimport { panelBridgeScript } from \"../panel-bridge.js\"\n\n// ── HTML bundle (zero CDN, CSP-safe inline) ──────────────────────────────────\n\nexport const AGENTS_OVERVIEW_HTML = `<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>agents — vue claire</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:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}\n#app{display:flex;flex-direction:column;height:100%}\n#hdr{padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;background:var(--bg2);flex-shrink:0}\n#hdr h1{font-size:13px;font-weight:600;letter-spacing:.02em}\n#hdr .sub{font-size:11px;color:var(--text2);margin-top:2px}\n#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:16px;line-height:1;padding:4px 6px;border-radius:6px}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n#grid{flex:1;overflow-y:auto;padding:14px 16px;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;align-content:start}\n.card{background:var(--bg2);border:1px solid var(--border);border-left:3px solid var(--border);border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;min-height:96px}\n.card.s-todo{border-left-color:var(--purple)}\n.card.s-work{border-left-color:var(--green)}\n.card.s-wait{border-left-color:var(--yellow)}\n.card.s-done{border-left-color:var(--text2)}\n.card-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\n.title{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}\n.state{font-size:10px;font-weight:700;padding:2px 8px;border-radius:11px;white-space:nowrap;letter-spacing:.02em}\n.state.s-todo{background:rgba(188,140,255,.16);color:var(--purple)}\n.state.s-work{background:rgba(63,185,80,.16);color:var(--green)}\n.state.s-wait{background:rgba(210,153,34,.16);color:var(--yellow)}\n.state.s-done{background:rgba(139,148,158,.14);color:var(--text2)}\n.summary{font-size:12.5px;line-height:1.5;color:var(--text);word-break:break-word}\n.summary.muted{color:var(--text2);font-style:italic}\n.meta{font-size:10.5px;color:var(--text2);display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:auto}\n.dot{width:5px;height:5px;border-radius:50%;background:var(--text2);display:inline-block}\n#empty{grid-column:1/-1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text2);gap:8px;padding:48px;text-align:center}\n#empty .ico{font-size:30px}\n#statusbar{padding:5px 16px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"hdr\">\n <div>\n <h1>Agents — vue claire</h1>\n <div class=\"sub\">une carte par agent · résumé + état</div>\n </div>\n <button id=\"refresh-btn\" title=\"Rafraîchir\" onclick=\"doRefresh()\">&#8635;</button>\n </div>\n <div id=\"grid\"><div id=\"empty\"><div class=\"ico\">&#9889;</div><div>Connexion au bridge&#8230;</div></div></div>\n <div id=\"statusbar\">Connexion&#8230;</div>\n</div>\n<script>\n${panelBridgeScript(\"agentproto-agents-overview\")}\n\n// ── State ──\nvar REFRESH_MS = 12000;\nvar pollTimer = null, polling = false;\nfunction esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }\nfunction setStatus(m){ document.getElementById('statusbar').textContent = m; }\n\nvar STATE_CLASS = {\n 'à traiter':'s-todo', 'au travail':'s-work', 'en attente':'s-wait', 'terminé':'s-done'\n};\nfunction stateClass(st){ return STATE_CLASS[st] || 's-wait'; }\n\nfunction fmtAgo(iso){\n if (!iso) return '';\n var t = Date.parse(iso);\n if (isNaN(t)) return '';\n var s = Math.max(0, Math.round((Date.now() - t) / 1000));\n if (s < 60) return 'il y a ' + s + 's';\n var m = Math.round(s/60);\n if (m < 60) return 'il y a ' + m + 'min';\n var h = Math.round(m/60);\n return 'il y a ' + h + 'h';\n}\n\nfunction titleOf(s){\n return s.label || s.name || (s.command ? s.command.split(/\\\\s+/)[0].split('/').pop() : null) || s.id.slice(0,8);\n}\n\nfunction render(sessions, summaries){\n var grid = document.getElementById('grid');\n if (!sessions.length){\n grid.innerHTML = '<div id=\"empty\"><div class=\"ico\">&#128564;</div><div>Aucune session d\\\\'agent</div></div>';\n return;\n }\n var html = '';\n for (var i=0;i<sessions.length;i++){\n var s = sessions[i];\n var sum = summaries[s.id] || {};\n var st = sum.state || 'en attente';\n var cls = stateClass(st);\n var summary = sum.summary || 'Résumé indisponible.';\n var muted = !sum.summary ? ' muted' : '';\n html += '<div class=\"card ' + cls + '\">'\n + '<div class=\"card-top\">'\n + '<span class=\"title\">' + esc(titleOf(s)) + '</span>'\n + '<span class=\"state ' + cls + '\">' + esc(st) + '</span>'\n + '</div>'\n + '<div class=\"summary' + muted + '\">' + esc(summary) + '</div>'\n + '<div class=\"meta\">'\n + '<span>' + esc(s.kind || '') + '</span><span class=\"dot\"></span>'\n + '<span>' + esc(s.status || '') + '</span>'\n + (s.lastOutputAt ? '<span class=\"dot\"></span><span>' + esc(fmtAgo(s.lastOutputAt)) + '</span>' : '')\n + '</div>'\n + '</div>';\n }\n grid.innerHTML = html;\n}\n\nfunction loadAndRender(){\n return callTool('session_list', {kind:'all'}).then(function(data){\n var all = data.sessions || [];\n var agents = all.filter(function(s){ return s.kind === 'agent-cli'; });\n // Render shells immediately, then fill summaries as they arrive.\n var summaries = {};\n render(agents, summaries);\n setStatus(agents.length + ' agent' + (agents.length===1?'':'s') + ' · ' + new Date().toLocaleTimeString('fr-FR'));\n return Promise.all(agents.map(function(s){\n return callTool('summarize_session', {sessionId: s.id}).then(function(sum){\n summaries[s.id] = sum;\n }).catch(function(){ /* leave shell */ });\n })).then(function(){ render(agents, summaries); });\n }).catch(function(e){ setStatus('Erreur : ' + e.message); });\n}\n\nfunction doPoll(){\n if (polling) return;\n polling = true;\n loadAndRender().then(function(){\n polling = false;\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n }).catch(function(){\n polling = false;\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n });\n}\nfunction doRefresh(){ if (pollTimer) clearTimeout(pollTimer); return loadAndRender().then(function(){ pollTimer = setTimeout(doPoll, REFRESH_MS); }); }\n\ninitBridge().then(loadAndRender).then(function(){\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n}).catch(function(e){\n setStatus('Bridge : ' + e.message);\n document.getElementById('grid').innerHTML = '<div id=\"empty\"><div class=\"ico\">&#9888;</div><div>Échec connexion bridge : ' + esc(e.message) + '</div></div>';\n});\n</script>\n</body>\n</html>`\n"]}
@@ -1,13 +1,12 @@
1
1
  import { z } from 'zod';
2
2
  import { defineApp } from '@agentproto/app-kit';
3
+ import { DISPLAY_MODE_SCRIPT_BODY } from '@agentproto/app-client/display-mode';
3
4
 
4
5
  /**
5
6
  * @agentproto/apps v0.1.0
6
7
  * Ready-made agentproto apps (teams of agents + workflows) built on app-kit.
7
8
  */
8
9
 
9
-
10
- // src/panel-bridge.ts
11
10
  function panelBridgeScript(appName) {
12
11
  return `// \u2500\u2500 MCP Apps bridge (shared: panel-bridge.ts) \u2500\u2500
13
12
  // JSON-RPC 2.0 over window.parent.postMessage \xB7 spec 2026-01-26
@@ -84,7 +83,7 @@ function initBridge(){
84
83
  return window.McpApp.connect().then(function(conn){
85
84
  _standaloneApp = conn;
86
85
  // No host to advertise a hostContext \u2014 default to inline with no
87
- // other modes available, which keeps the #dm/#pin toggle buttons
86
+ // other modes available, which keeps the display-mode toggle buttons
88
87
  // hidden (they only show for modes hostContext.availableDisplayModes
89
88
  // lists) instead of rendering two buttons with nothing to switch to.
90
89
  _setHostContext({displayMode: 'inline', availableDisplayModes: []});
@@ -126,71 +125,17 @@ function callTool(name, args){
126
125
  });
127
126
  }
128
127
 
129
- // \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
130
- // Injected by the shared bridge so every panel gets them without touching
131
- // its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the
132
- // panel stays inline by default; the user expands on demand. Buttons only
133
- // appear for modes the host advertises in hostContext.availableDisplayModes.
134
- (function(){
135
- function mount(){
136
- var style = document.createElement('style');
137
- style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'
138
- + 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'
139
- + 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'
140
- + 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'
141
- + '#dm{right:8px}#pin{right:118px}'
142
- + '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'
143
- + '@media (prefers-color-scheme:dark){'
144
- + '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'
145
- + '#dm:hover,#pin:hover{background:#333;border-color:#777}}';
146
- document.head.appendChild(style);
147
-
148
- var btn = document.createElement('button');
149
- btn.id = 'dm'; btn.type = 'button'; btn.title = "Basculer l'affichage";
150
- var pin = document.createElement('button');
151
- pin.id = 'pin'; pin.type = 'button'; pin.title = '\xC9pingler sur le c\xF4t\xE9 (pip)';
152
- document.body.appendChild(pin);
153
- document.body.appendChild(btn);
154
-
155
- function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }
156
-
157
- // Re-sync button visibility + label from the current host context.
158
- function syncBtn(ctx){
159
- ctx = ctx || {};
160
- var avail = ctx.availableDisplayModes;
161
- // Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)
162
- console.log('[mcp-app] displayMode=', ctx.displayMode,
163
- 'availableDisplayModes=', avail);
164
-
165
- // Fullscreen toggle button.
166
- if (has(avail, 'fullscreen')){
167
- btn.style.display = 'block';
168
- btn.textContent = (ctx.displayMode === 'fullscreen') ? '\u2921 R\xE9duire' : '\u2922 Agrandir';
169
- } else { btn.style.display = 'none'; }
170
-
171
- // Dedicated pip ("pinned on side") button \u2014 only if the host advertises pip.
172
- if (has(avail, 'pip')){
173
- pin.style.display = 'block';
174
- pin.textContent = (ctx.displayMode === 'pip') ? '\u2921 D\xE9tacher' : '\u{1F4CC} \xC9pingler';
175
- } else { pin.style.display = 'none'; }
176
- }
177
-
178
- onHostContext(syncBtn);
179
-
180
- btn.addEventListener('click', function(){
181
- var ctx = getHostContext() || {};
182
- var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');
183
- requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});
184
- });
185
-
186
- pin.addEventListener('click', function(){
187
- var ctx = getHostContext() || {};
188
- requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});
189
- });
190
- }
191
- if (document.body) mount();
192
- else document.addEventListener('DOMContentLoaded', mount);
193
- })();`;
128
+ // \u2500\u2500 Display-mode toggle (shared: @agentproto/app-client/display-mode) \u2500\u2500
129
+ // The installer is inlined verbatim (it is self-contained and idempotent),
130
+ // then handed this panel's own JSON-RPC plumbing. NO auto-request: the
131
+ // panel stays inline until the user clicks, and the buttons only appear for
132
+ // the modes the host advertises in hostContext.availableDisplayModes.
133
+ ${DISPLAY_MODE_SCRIPT_BODY}
134
+ var displayMode = window.AgentprotoUI.installDisplayMode({
135
+ getHostContext: getHostContext,
136
+ onHostContext: onHostContext,
137
+ requestDisplayMode: requestDisplayMode
138
+ });`;
194
139
  }
195
140
 
196
141
  // src/agents-overview/panel.ts
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/panel-bridge.ts","../src/agents-overview/panel.ts","../src/agents-overview/index.ts"],"names":[],"mappings":"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","file":"agents-overview.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 _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 #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 // 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 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 \"agents — vue claire\" panel — the\n * `AGENTS_OVERVIEW_HTML` served as the `ui://agentproto_agents_overview/view`\n * resource. Mounted via the `agentproto_agents_overview` tool defined in\n * ./index.ts.\n *\n * The panel polls `session_list` every ~12s, then calls the host's\n * `summarize_session` tool (registered server-side by\n * @agentproto/runtime's summarize-session-tool.ts, NOT part of this\n * package — that tool is tied to the runtime's session ring-buffer/hot\n * path) for each visible session to fetch the plain sentence + state.\n */\n\nimport { panelBridgeScript } from \"../panel-bridge.js\"\n\n// ── HTML bundle (zero CDN, CSP-safe inline) ──────────────────────────────────\n\nexport const AGENTS_OVERVIEW_HTML = `<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>agents — vue claire</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:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}\n#app{display:flex;flex-direction:column;height:100%}\n#hdr{padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;background:var(--bg2);flex-shrink:0}\n#hdr h1{font-size:13px;font-weight:600;letter-spacing:.02em}\n#hdr .sub{font-size:11px;color:var(--text2);margin-top:2px}\n#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:16px;line-height:1;padding:4px 6px;border-radius:6px}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n#grid{flex:1;overflow-y:auto;padding:14px 16px;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;align-content:start}\n.card{background:var(--bg2);border:1px solid var(--border);border-left:3px solid var(--border);border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;min-height:96px}\n.card.s-todo{border-left-color:var(--purple)}\n.card.s-work{border-left-color:var(--green)}\n.card.s-wait{border-left-color:var(--yellow)}\n.card.s-done{border-left-color:var(--text2)}\n.card-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\n.title{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}\n.state{font-size:10px;font-weight:700;padding:2px 8px;border-radius:11px;white-space:nowrap;letter-spacing:.02em}\n.state.s-todo{background:rgba(188,140,255,.16);color:var(--purple)}\n.state.s-work{background:rgba(63,185,80,.16);color:var(--green)}\n.state.s-wait{background:rgba(210,153,34,.16);color:var(--yellow)}\n.state.s-done{background:rgba(139,148,158,.14);color:var(--text2)}\n.summary{font-size:12.5px;line-height:1.5;color:var(--text);word-break:break-word}\n.summary.muted{color:var(--text2);font-style:italic}\n.meta{font-size:10.5px;color:var(--text2);display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:auto}\n.dot{width:5px;height:5px;border-radius:50%;background:var(--text2);display:inline-block}\n#empty{grid-column:1/-1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text2);gap:8px;padding:48px;text-align:center}\n#empty .ico{font-size:30px}\n#statusbar{padding:5px 16px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"hdr\">\n <div>\n <h1>Agents — vue claire</h1>\n <div class=\"sub\">une carte par agent · résumé + état</div>\n </div>\n <button id=\"refresh-btn\" title=\"Rafraîchir\" onclick=\"doRefresh()\">&#8635;</button>\n </div>\n <div id=\"grid\"><div id=\"empty\"><div class=\"ico\">&#9889;</div><div>Connexion au bridge&#8230;</div></div></div>\n <div id=\"statusbar\">Connexion&#8230;</div>\n</div>\n<script>\n${panelBridgeScript(\"agentproto-agents-overview\")}\n\n// ── State ──\nvar REFRESH_MS = 12000;\nvar pollTimer = null, polling = false;\nfunction esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }\nfunction setStatus(m){ document.getElementById('statusbar').textContent = m; }\n\nvar STATE_CLASS = {\n 'à traiter':'s-todo', 'au travail':'s-work', 'en attente':'s-wait', 'terminé':'s-done'\n};\nfunction stateClass(st){ return STATE_CLASS[st] || 's-wait'; }\n\nfunction fmtAgo(iso){\n if (!iso) return '';\n var t = Date.parse(iso);\n if (isNaN(t)) return '';\n var s = Math.max(0, Math.round((Date.now() - t) / 1000));\n if (s < 60) return 'il y a ' + s + 's';\n var m = Math.round(s/60);\n if (m < 60) return 'il y a ' + m + 'min';\n var h = Math.round(m/60);\n return 'il y a ' + h + 'h';\n}\n\nfunction titleOf(s){\n return s.label || s.name || (s.command ? s.command.split(/\\\\s+/)[0].split('/').pop() : null) || s.id.slice(0,8);\n}\n\nfunction render(sessions, summaries){\n var grid = document.getElementById('grid');\n if (!sessions.length){\n grid.innerHTML = '<div id=\"empty\"><div class=\"ico\">&#128564;</div><div>Aucune session d\\\\'agent</div></div>';\n return;\n }\n var html = '';\n for (var i=0;i<sessions.length;i++){\n var s = sessions[i];\n var sum = summaries[s.id] || {};\n var st = sum.state || 'en attente';\n var cls = stateClass(st);\n var summary = sum.summary || 'Résumé indisponible.';\n var muted = !sum.summary ? ' muted' : '';\n html += '<div class=\"card ' + cls + '\">'\n + '<div class=\"card-top\">'\n + '<span class=\"title\">' + esc(titleOf(s)) + '</span>'\n + '<span class=\"state ' + cls + '\">' + esc(st) + '</span>'\n + '</div>'\n + '<div class=\"summary' + muted + '\">' + esc(summary) + '</div>'\n + '<div class=\"meta\">'\n + '<span>' + esc(s.kind || '') + '</span><span class=\"dot\"></span>'\n + '<span>' + esc(s.status || '') + '</span>'\n + (s.lastOutputAt ? '<span class=\"dot\"></span><span>' + esc(fmtAgo(s.lastOutputAt)) + '</span>' : '')\n + '</div>'\n + '</div>';\n }\n grid.innerHTML = html;\n}\n\nfunction loadAndRender(){\n return callTool('session_list', {kind:'all'}).then(function(data){\n var all = data.sessions || [];\n var agents = all.filter(function(s){ return s.kind === 'agent-cli'; });\n // Render shells immediately, then fill summaries as they arrive.\n var summaries = {};\n render(agents, summaries);\n setStatus(agents.length + ' agent' + (agents.length===1?'':'s') + ' · ' + new Date().toLocaleTimeString('fr-FR'));\n return Promise.all(agents.map(function(s){\n return callTool('summarize_session', {sessionId: s.id}).then(function(sum){\n summaries[s.id] = sum;\n }).catch(function(){ /* leave shell */ });\n })).then(function(){ render(agents, summaries); });\n }).catch(function(e){ setStatus('Erreur : ' + e.message); });\n}\n\nfunction doPoll(){\n if (polling) return;\n polling = true;\n loadAndRender().then(function(){\n polling = false;\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n }).catch(function(){\n polling = false;\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n });\n}\nfunction doRefresh(){ if (pollTimer) clearTimeout(pollTimer); return loadAndRender().then(function(){ pollTimer = setTimeout(doPoll, REFRESH_MS); }); }\n\ninitBridge().then(loadAndRender).then(function(){\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n}).catch(function(e){\n setStatus('Bridge : ' + e.message);\n document.getElementById('grid').innerHTML = '<div id=\"empty\"><div class=\"ico\">&#9888;</div><div>Échec connexion bridge : ' + esc(e.message) + '</div></div>';\n});\n</script>\n</body>\n</html>`\n","/**\n * McpApp definition for the agentproto \"agents — vue claire\" panel.\n *\n * A plain-language overview of agent-cli sessions: one card per session\n * with a single human sentence (what the agent is doing/last said) plus a\n * coarse state — `à traiter` / `au travail` / `en attente` / `terminé`.\n *\n * Uses the AgnoMcpApp contract (a local McpApp-compatible shape that does\n * NOT depend on @agstudio/mcp-apps). The host (@agentproto/runtime) wires\n * this factory's output via `registerMcpApps` at boot time — see runtime's\n * builtin-apps.ts.\n *\n * Protocol flow:\n * 1. Tool `agentproto_agents_overview` is called by the host.\n * 2. execute() returns the agent-session snapshot → injected into the\n * HTML as the initial render via the tool result JSON.\n * 3. The HTML panel opens a JSON-RPC bridge (postMessage) and polls\n * `session_list` every ~12 s, then calls `summarize_session` for\n * each visible session to fetch the plain sentence + state.\n *\n * The per-session summary is generated SERVER-SIDE by the\n * `summarize_session` tool, which stays in @agentproto/runtime\n * (summarize-session-tool.ts) — it's tied to the runtime's session\n * ring-buffer/hot path, not to this panel's UI. Only the panel factory +\n * HTML live here.\n *\n * This module also exports `agentsOverviewApp`, a real `defineApp()`\n * `AppHandle` (`agents: []`, UI-only) — the catalog/emit/`app_install` path.\n * It's separate from `makeAgentsOverviewApp` 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 { AGENTS_OVERVIEW_HTML } from \"./panel.js\"\nimport type { AgnoMcpApp } from \"../mcp-app-types.js\"\n\nexport const agentsOverviewInputSchema = z.object({\n filter: z\n .enum([\"running\", \"all\"])\n .optional()\n .describe(\"`running` = only alive agent sessions; `all` = running + recent (default).\"),\n})\n\nexport type AgentsOverviewInput = z.infer<typeof agentsOverviewInputSchema>\nexport type AgentsOverviewOutput<TSession = unknown> = { sessions: TSession[] }\n\n/** Generic over the host's own session-descriptor shape — see\n * sessions-panel/index.ts's SessionsPanelOps for why. */\nexport interface AgentsOverviewOps<TSession = unknown> {\n listSessions(filter?: \"running\" | \"all\"): TSession[]\n}\n\nexport type { AgnoMcpApp }\n\nexport function makeAgentsOverviewApp<TSession = unknown>(\n ops: AgentsOverviewOps<TSession>,\n): AgnoMcpApp<AgentsOverviewInput, AgentsOverviewOutput<TSession>> {\n return {\n id: \"agentproto_agents_overview\",\n title: \"Agents — vue claire\",\n description:\n \"Open the agents overview — a plain-language card per agent session \" +\n \"with one human sentence (what it's doing / last said) and a coarse \" +\n \"state (à traiter / au travail / en attente / terminé). Polls live and \" +\n \"asks the server to summarise each session.\",\n inputSchema: agentsOverviewInputSchema,\n execute: async input => ({ sessions: ops.listSessions(input.filter) }),\n html: AGENTS_OVERVIEW_HTML,\n }\n}\n\nexport const agentsOverviewApp: AppHandle = defineApp({\n id: \"@agentproto/agents-overview\",\n name: \"Agents — vue claire\",\n description:\n \"Open the agents overview — a plain-language card per agent session with one human sentence \" +\n \"and a coarse state (à traiter / au travail / en attente / terminé).\",\n agents: [],\n ui: {\n html: AGENTS_OVERVIEW_HTML,\n title: \"Agents — vue claire\",\n tools: [\"session_list\", \"summarize_session\"],\n },\n})\n"]}
1
+ {"version":3,"sources":["../src/panel-bridge.ts","../src/agents-overview/panel.ts","../src/agents-overview/index.ts"],"names":[],"mappings":"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","file":"agents-overview.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 \"agents — vue claire\" panel — the\n * `AGENTS_OVERVIEW_HTML` served as the `ui://agentproto_agents_overview/view`\n * resource. Mounted via the `agentproto_agents_overview` tool defined in\n * ./index.ts.\n *\n * The panel polls `session_list` every ~12s, then calls the host's\n * `summarize_session` tool (registered server-side by\n * @agentproto/runtime's summarize-session-tool.ts, NOT part of this\n * package — that tool is tied to the runtime's session ring-buffer/hot\n * path) for each visible session to fetch the plain sentence + state.\n */\n\nimport { panelBridgeScript } from \"../panel-bridge.js\"\n\n// ── HTML bundle (zero CDN, CSP-safe inline) ──────────────────────────────────\n\nexport const AGENTS_OVERVIEW_HTML = `<!DOCTYPE html>\n<html lang=\"fr\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>agents — vue claire</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:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;font-size:13px;background:var(--bg);color:var(--text);overflow:hidden}\n#app{display:flex;flex-direction:column;height:100%}\n#hdr{padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;background:var(--bg2);flex-shrink:0}\n#hdr h1{font-size:13px;font-weight:600;letter-spacing:.02em}\n#hdr .sub{font-size:11px;color:var(--text2);margin-top:2px}\n#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:16px;line-height:1;padding:4px 6px;border-radius:6px}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n#grid{flex:1;overflow-y:auto;padding:14px 16px;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;align-content:start}\n.card{background:var(--bg2);border:1px solid var(--border);border-left:3px solid var(--border);border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:8px;min-height:96px}\n.card.s-todo{border-left-color:var(--purple)}\n.card.s-work{border-left-color:var(--green)}\n.card.s-wait{border-left-color:var(--yellow)}\n.card.s-done{border-left-color:var(--text2)}\n.card-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\n.title{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}\n.state{font-size:10px;font-weight:700;padding:2px 8px;border-radius:11px;white-space:nowrap;letter-spacing:.02em}\n.state.s-todo{background:rgba(188,140,255,.16);color:var(--purple)}\n.state.s-work{background:rgba(63,185,80,.16);color:var(--green)}\n.state.s-wait{background:rgba(210,153,34,.16);color:var(--yellow)}\n.state.s-done{background:rgba(139,148,158,.14);color:var(--text2)}\n.summary{font-size:12.5px;line-height:1.5;color:var(--text);word-break:break-word}\n.summary.muted{color:var(--text2);font-style:italic}\n.meta{font-size:10.5px;color:var(--text2);display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:auto}\n.dot{width:5px;height:5px;border-radius:50%;background:var(--text2);display:inline-block}\n#empty{grid-column:1/-1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text2);gap:8px;padding:48px;text-align:center}\n#empty .ico{font-size:30px}\n#statusbar{padding:5px 16px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"hdr\">\n <div>\n <h1>Agents — vue claire</h1>\n <div class=\"sub\">une carte par agent · résumé + état</div>\n </div>\n <button id=\"refresh-btn\" title=\"Rafraîchir\" onclick=\"doRefresh()\">&#8635;</button>\n </div>\n <div id=\"grid\"><div id=\"empty\"><div class=\"ico\">&#9889;</div><div>Connexion au bridge&#8230;</div></div></div>\n <div id=\"statusbar\">Connexion&#8230;</div>\n</div>\n<script>\n${panelBridgeScript(\"agentproto-agents-overview\")}\n\n// ── State ──\nvar REFRESH_MS = 12000;\nvar pollTimer = null, polling = false;\nfunction esc(s){ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); }\nfunction setStatus(m){ document.getElementById('statusbar').textContent = m; }\n\nvar STATE_CLASS = {\n 'à traiter':'s-todo', 'au travail':'s-work', 'en attente':'s-wait', 'terminé':'s-done'\n};\nfunction stateClass(st){ return STATE_CLASS[st] || 's-wait'; }\n\nfunction fmtAgo(iso){\n if (!iso) return '';\n var t = Date.parse(iso);\n if (isNaN(t)) return '';\n var s = Math.max(0, Math.round((Date.now() - t) / 1000));\n if (s < 60) return 'il y a ' + s + 's';\n var m = Math.round(s/60);\n if (m < 60) return 'il y a ' + m + 'min';\n var h = Math.round(m/60);\n return 'il y a ' + h + 'h';\n}\n\nfunction titleOf(s){\n return s.label || s.name || (s.command ? s.command.split(/\\\\s+/)[0].split('/').pop() : null) || s.id.slice(0,8);\n}\n\nfunction render(sessions, summaries){\n var grid = document.getElementById('grid');\n if (!sessions.length){\n grid.innerHTML = '<div id=\"empty\"><div class=\"ico\">&#128564;</div><div>Aucune session d\\\\'agent</div></div>';\n return;\n }\n var html = '';\n for (var i=0;i<sessions.length;i++){\n var s = sessions[i];\n var sum = summaries[s.id] || {};\n var st = sum.state || 'en attente';\n var cls = stateClass(st);\n var summary = sum.summary || 'Résumé indisponible.';\n var muted = !sum.summary ? ' muted' : '';\n html += '<div class=\"card ' + cls + '\">'\n + '<div class=\"card-top\">'\n + '<span class=\"title\">' + esc(titleOf(s)) + '</span>'\n + '<span class=\"state ' + cls + '\">' + esc(st) + '</span>'\n + '</div>'\n + '<div class=\"summary' + muted + '\">' + esc(summary) + '</div>'\n + '<div class=\"meta\">'\n + '<span>' + esc(s.kind || '') + '</span><span class=\"dot\"></span>'\n + '<span>' + esc(s.status || '') + '</span>'\n + (s.lastOutputAt ? '<span class=\"dot\"></span><span>' + esc(fmtAgo(s.lastOutputAt)) + '</span>' : '')\n + '</div>'\n + '</div>';\n }\n grid.innerHTML = html;\n}\n\nfunction loadAndRender(){\n return callTool('session_list', {kind:'all'}).then(function(data){\n var all = data.sessions || [];\n var agents = all.filter(function(s){ return s.kind === 'agent-cli'; });\n // Render shells immediately, then fill summaries as they arrive.\n var summaries = {};\n render(agents, summaries);\n setStatus(agents.length + ' agent' + (agents.length===1?'':'s') + ' · ' + new Date().toLocaleTimeString('fr-FR'));\n return Promise.all(agents.map(function(s){\n return callTool('summarize_session', {sessionId: s.id}).then(function(sum){\n summaries[s.id] = sum;\n }).catch(function(){ /* leave shell */ });\n })).then(function(){ render(agents, summaries); });\n }).catch(function(e){ setStatus('Erreur : ' + e.message); });\n}\n\nfunction doPoll(){\n if (polling) return;\n polling = true;\n loadAndRender().then(function(){\n polling = false;\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n }).catch(function(){\n polling = false;\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n });\n}\nfunction doRefresh(){ if (pollTimer) clearTimeout(pollTimer); return loadAndRender().then(function(){ pollTimer = setTimeout(doPoll, REFRESH_MS); }); }\n\ninitBridge().then(loadAndRender).then(function(){\n pollTimer = setTimeout(doPoll, REFRESH_MS);\n}).catch(function(e){\n setStatus('Bridge : ' + e.message);\n document.getElementById('grid').innerHTML = '<div id=\"empty\"><div class=\"ico\">&#9888;</div><div>Échec connexion bridge : ' + esc(e.message) + '</div></div>';\n});\n</script>\n</body>\n</html>`\n","/**\n * McpApp definition for the agentproto \"agents — vue claire\" panel.\n *\n * A plain-language overview of agent-cli sessions: one card per session\n * with a single human sentence (what the agent is doing/last said) plus a\n * coarse state — `à traiter` / `au travail` / `en attente` / `terminé`.\n *\n * Uses the AgnoMcpApp contract (a local McpApp-compatible shape that does\n * NOT depend on @agstudio/mcp-apps). The host (@agentproto/runtime) wires\n * this factory's output via `registerMcpApps` at boot time — see runtime's\n * builtin-apps.ts.\n *\n * Protocol flow:\n * 1. Tool `agentproto_agents_overview` is called by the host.\n * 2. execute() returns the agent-session snapshot → injected into the\n * HTML as the initial render via the tool result JSON.\n * 3. The HTML panel opens a JSON-RPC bridge (postMessage) and polls\n * `session_list` every ~12 s, then calls `summarize_session` for\n * each visible session to fetch the plain sentence + state.\n *\n * The per-session summary is generated SERVER-SIDE by the\n * `summarize_session` tool, which stays in @agentproto/runtime\n * (summarize-session-tool.ts) — it's tied to the runtime's session\n * ring-buffer/hot path, not to this panel's UI. Only the panel factory +\n * HTML live here.\n *\n * This module also exports `agentsOverviewApp`, a real `defineApp()`\n * `AppHandle` (`agents: []`, UI-only) — the catalog/emit/`app_install` path.\n * It's separate from `makeAgentsOverviewApp` 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 { AGENTS_OVERVIEW_HTML } from \"./panel.js\"\nimport type { AgnoMcpApp } from \"../mcp-app-types.js\"\n\nexport const agentsOverviewInputSchema = z.object({\n filter: z\n .enum([\"running\", \"all\"])\n .optional()\n .describe(\"`running` = only alive agent sessions; `all` = running + recent (default).\"),\n})\n\nexport type AgentsOverviewInput = z.infer<typeof agentsOverviewInputSchema>\nexport type AgentsOverviewOutput<TSession = unknown> = { sessions: TSession[] }\n\n/** Generic over the host's own session-descriptor shape — see\n * sessions-panel/index.ts's SessionsPanelOps for why. */\nexport interface AgentsOverviewOps<TSession = unknown> {\n listSessions(filter?: \"running\" | \"all\"): TSession[]\n}\n\nexport type { AgnoMcpApp }\n\nexport function makeAgentsOverviewApp<TSession = unknown>(\n ops: AgentsOverviewOps<TSession>,\n): AgnoMcpApp<AgentsOverviewInput, AgentsOverviewOutput<TSession>> {\n return {\n id: \"agentproto_agents_overview\",\n title: \"Agents — vue claire\",\n description:\n \"Open the agents overview — a plain-language card per agent session \" +\n \"with one human sentence (what it's doing / last said) and a coarse \" +\n \"state (à traiter / au travail / en attente / terminé). Polls live and \" +\n \"asks the server to summarise each session.\",\n inputSchema: agentsOverviewInputSchema,\n execute: async input => ({ sessions: ops.listSessions(input.filter) }),\n html: AGENTS_OVERVIEW_HTML,\n }\n}\n\nexport const agentsOverviewApp: AppHandle = defineApp({\n id: \"@agentproto/agents-overview\",\n name: \"Agents — vue claire\",\n description:\n \"Open the agents overview — a plain-language card per agent session with one human sentence \" +\n \"and a coarse state (à traiter / au travail / en attente / terminé).\",\n agents: [],\n ui: {\n html: AGENTS_OVERVIEW_HTML,\n title: \"Agents — vue claire\",\n tools: [\"session_list\", \"summarize_session\"],\n },\n})\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/bin/sync.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAcH,UAAU,YAAY;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAWD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAevE;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAKrG"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/bin/sync.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAeH,UAAU,YAAY;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAYD,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAevE;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAKrG"}