@agentproto/runtime 1.1.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/catalog-models.d.ts +255 -0
- package/dist/catalog-models.mjs +386 -0
- package/dist/catalog-models.mjs.map +1 -0
- package/dist/config.d.ts +417 -1
- package/dist/config.mjs.map +1 -1
- package/dist/context-continuity-B9n0t0v-.d.ts +53 -0
- package/dist/index.d.ts +6236 -2815
- package/dist/index.mjs +21878 -10947
- package/dist/index.mjs.map +1 -1
- package/dist/pr-provenance.d.ts +140 -0
- package/dist/pr-provenance.mjs +106 -0
- package/dist/pr-provenance.mjs.map +1 -0
- package/dist/resume-strategies.d.ts +39 -2
- package/dist/resume-strategies.mjs +765 -28
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/session-config-DIf6wYYP.d.ts +192 -0
- package/dist/session-story-panel.d.ts +26 -0
- package/dist/session-story-panel.mjs +970 -0
- package/dist/session-story-panel.mjs.map +1 -0
- package/dist/session-story.d.ts +12 -2
- package/dist/spawn-defaults-7uHRnYH1.d.ts +414 -0
- package/dist/telegram-proxy.d.ts +35 -0
- package/dist/telegram-proxy.mjs +115 -0
- package/dist/telegram-proxy.mjs.map +1 -0
- package/dist/user-presets.d.ts +91 -0
- package/dist/user-presets.mjs +84 -0
- package/dist/user-presets.mjs.map +1 -0
- package/package.json +48 -23
- package/dist/config-BRKy_SAF.d.ts +0 -569
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/panel-bridge.ts","../src/session-story-panel.ts"],"names":[],"mappings":";;;;;;AAqCO,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,oBAAA,EAmDa,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,KAAA,CAAA;AAyF7C;;;ACnJO,IAAM,wBAAA,GAA2B,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA,EA8StC,iBAAA,CAAkB,gCAAgC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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":"session-story-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\nexport function panelBridgeScript(appName: string): string {\n return `// ── MCP Apps bridge (shared: panel-bridge.ts) ──\n// JSON-RPC 2.0 over window.parent.postMessage · spec 2026-01-26\nvar _nextId = 1, _pending = {}, _notifyHandlers = [];\nvar _hostContext = null, _hostContextHandlers = [];\nfunction post(msg){ window.parent.postMessage(msg, '*'); }\nfunction getHostContext(){ return _hostContext; }\nfunction onHostContext(cb){\n _hostContextHandlers.push(cb);\n // Replay the last context so a late subscriber isn't stuck blind.\n if (_hostContext){ try { cb(_hostContext); } catch(_) {} }\n}\nfunction _setHostContext(ctx){\n if (!ctx || typeof ctx !== 'object') return;\n // ui/notifications/host-context-changed carries only the changed keys —\n // merge, matching the official ext-apps App behaviour.\n _hostContext = Object.assign({}, _hostContext || {}, ctx);\n for (var i = 0; i < _hostContextHandlers.length; i++){\n try { _hostContextHandlers[i](_hostContext); } catch(_) {}\n }\n}\nfunction rpcRequest(method, params){\n return new Promise(function(resolve, reject){\n var id = _nextId++;\n _pending[id] = {resolve: resolve, reject: reject};\n post({jsonrpc: '2.0', id: id, method: method, params: params || {}});\n });\n}\nfunction rpcNotify(method, params){ post({jsonrpc: '2.0', method: method, params: params || {}}); }\nfunction onHostNotification(cb){ _notifyHandlers.push(cb); }\nwindow.addEventListener('message', function(evt){\n var msg = evt.data;\n if (!msg || typeof msg !== 'object' || msg.jsonrpc !== '2.0') return;\n if (msg.id != null && msg.method == null){\n var p = _pending[msg.id];\n if (!p) return;\n delete _pending[msg.id];\n if (msg.error) p.reject(new Error(msg.error.message || ('rpc error ' + msg.error.code)));\n else p.resolve(msg.result);\n return;\n }\n if (msg.method){\n if (msg.method === 'ui/notifications/host-context-changed'){\n _setHostContext(msg.params || {});\n }\n for (var i = 0; i < _notifyHandlers.length; i++){\n try { _notifyHandlers[i](msg.method, msg.params || {}); } catch(_) {}\n }\n }\n});\nfunction initBridge(){\n return rpcRequest('ui/initialize', {\n appInfo: {name: ${JSON.stringify(appName)}, version: '0.1.0'},\n appCapabilities: {availableDisplayModes: ['inline', 'fullscreen', 'pip']},\n protocolVersion: '2026-01-26'\n }).then(function(result){\n // The initialize result carries the initial hostContext (displayMode +\n // availableDisplayModes) — capture it before notifying the host.\n if (result && result.hostContext) _setHostContext(result.hostContext);\n rpcNotify('ui/notifications/initialized', {});\n });\n}\nfunction requestDisplayMode(mode){\n return rpcRequest('ui/request-display-mode', {mode: mode});\n}\nfunction callTool(name, args){\n return rpcRequest('tools/call', {name: name, arguments: args || {}}).then(function(result){\n if (result.isError){\n var e = (result.content && result.content[0] && result.content[0].text) || 'tool error';\n throw new Error(e);\n }\n var text = (result.content && result.content[0] && result.content[0].text) || '{}';\n return JSON.parse(text);\n });\n}\n\n// ── Display-mode toggle buttons (NO auto-request) ──────────────────────\n// Injected by the shared bridge so every panel gets them without touching\n// its own markup. Mirrors guilde canvas.app.ts canvasShellHtml(): the\n// panel stays inline by default; the user expands on demand. Buttons only\n// appear for modes the host advertises in hostContext.availableDisplayModes.\n(function(){\n function mount(){\n var style = document.createElement('style');\n style.textContent = '#dm,#pin{display:none;position:fixed;top:8px;z-index:10;'\n + 'border:1px solid #d0d0d0;background:#fff;color:#1a1a1a;'\n + 'font:600 13px/1 system-ui,sans-serif;padding:7px 12px;border-radius:6px;'\n + 'cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.18)}'\n + '#dm{right:8px}#pin{right:118px}'\n + '#dm:hover,#pin:hover{background:#f2f2f2;border-color:#b0b0b0}'\n + '@media (prefers-color-scheme:dark){'\n + '#dm,#pin{border-color:#555;background:#2a2a2a;color:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.5)}'\n + '#dm:hover,#pin:hover{background:#333;border-color:#777}}';\n document.head.appendChild(style);\n\n var btn = document.createElement('button');\n btn.id = 'dm'; btn.type = 'button'; btn.title = \"Basculer l'affichage\";\n var pin = document.createElement('button');\n pin.id = 'pin'; pin.type = 'button'; pin.title = 'Épingler sur le côté (pip)';\n document.body.appendChild(pin);\n document.body.appendChild(btn);\n\n function has(avail, m){ return !!avail && avail.indexOf(m) >= 0; }\n\n // Re-sync button visibility + label from the current host context.\n function syncBtn(ctx){\n ctx = ctx || {};\n var avail = ctx.availableDisplayModes;\n // Diagnostic: what does THIS host actually advertise? (inline/fullscreen/pip)\n console.log('[mcp-app] displayMode=', ctx.displayMode,\n 'availableDisplayModes=', avail);\n\n // Fullscreen toggle button.\n if (has(avail, 'fullscreen')){\n btn.style.display = 'block';\n btn.textContent = (ctx.displayMode === 'fullscreen') ? '⤡ Réduire' : '⤢ Agrandir';\n } else { btn.style.display = 'none'; }\n\n // Dedicated pip (\"pinned on side\") button — only if the host advertises pip.\n if (has(avail, 'pip')){\n pin.style.display = 'block';\n pin.textContent = (ctx.displayMode === 'pip') ? '⤡ Détacher' : '📌 Épingler';\n } else { pin.style.display = 'none'; }\n }\n\n onHostContext(syncBtn);\n\n btn.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n var inPanel = (ctx.displayMode === 'fullscreen' || ctx.displayMode === 'pip');\n requestDisplayMode(inPanel ? 'inline' : 'fullscreen').catch(function(){});\n });\n\n pin.addEventListener('click', function(){\n var ctx = getHostContext() || {};\n requestDisplayMode(ctx.displayMode === 'pip' ? 'inline' : 'pip').catch(function(){});\n });\n }\n if (document.body) mount();\n else document.addEventListener('DOMContentLoaded', mount);\n})();`\n}\n","/**\n * MCP App resource: agentproto session story panel.\n *\n * Ported 1:1 from the validated mockup at docs/session-story-mockup.html —\n * same CSS, same DOM structure/classes, same interaction model (anchored\n * side panel, sticky collapsible chapter headers, Simple/Tech toggle,\n * composer with local chapter-routing classification). Fake data is\n * replaced with live data pulled through the MCP Apps postMessage bridge.\n *\n * The HTML must stay fully self-contained (CSP: inline CSS/JS only, no\n * CDN, no external fonts) — see mcp-apps-adapter.ts and the other panels\n * in this file family (sessions-panel.ts, agents-overview-app.ts) for the\n * same constraint. Because of that, this file cannot `import` the pure\n * `buildStory` module from session-story.ts — the folding/chaptering\n * heuristics are ported to vanilla JS below (`buildStoryJs`), mirroring\n * session-story.ts function-for-function so the two stay easy to diff.\n *\n * Protocol: MCP Apps ext spec 2026-01-26\n * – Bridge: JSON-RPC 2.0 over window.parent.postMessage\n * – Handshake: ui/initialize → host result → ui/notifications/initialized\n * – Data: tools/call → session_list (status/picker) + agent_export\n * (timeline, re-fetched on turn boundaries) + agent_prompt (composer)\n */\n\nimport { panelBridgeScript } from \"./panel-bridge.js\"\n\nconst RESOURCE_URI = \"ui://agentproto_session_story/view\"\nconst MIME_TYPE = \"text/html;profile=mcp-app\"\nvoid RESOURCE_URI\nvoid MIME_TYPE\n\nexport const SESSION_STORY_PANEL_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\" />\n<title>session story</title>\n<style>\n :root {\n color-scheme: light;\n --bg:#faf8f5; --panel:#fffdfa; --line:#ece5da; --line-soft:#f2ede3;\n --ink:#241f1a; --ink-mute:#7d7060; --ink-faint:#a9997f; --ink-ghost:#b3a893;\n --accent:#0d7a4f; --accent-soft:#e8f4ec;\n --gold:#a6701b; --gold-soft:#fff2dc;\n --blue:#1d4e80; --blue-soft:#e9f1fb;\n --violet:#5b3fa6; --violet-soft:#ede9ff;\n --sel:#fbeccd; --red:#b3261e; --red-soft:#fbeae8;\n }\n * { box-sizing:border-box; }\n html,body { height:100%; }\n body { margin:0; font-family:ui-sans-serif,system-ui,-apple-system,\"Segoe UI\",sans-serif; background:var(--bg); color:var(--ink); -webkit-font-smoothing:antialiased; }\n .app { height:100vh; display:flex; flex-direction:column; }\n .hidden { display:none !important; }\n\n /* ── picker screen ── */\n #pickerScreen { height:100vh; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; padding:24px; }\n #pickerScreen h1 { font-size:15px; font-weight:700; }\n #pickerList { width:min(520px,90vw); max-height:60vh; overflow-y:auto; border:1px solid var(--line); border-radius:12px; background:var(--panel); }\n .pk-item { padding:10px 14px; border-bottom:1px solid var(--line-soft); cursor:pointer; display:flex; align-items:center; gap:10px; }\n .pk-item:last-child { border-bottom:none; }\n .pk-item:hover { background:var(--line-soft); }\n .pk-name { flex:1; min-width:0; font-size:13px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n .pk-meta { flex:none; font-size:10.5px; color:var(--ink-faint); font-weight:600; }\n .pk-empty { padding:24px; text-align:center; color:var(--ink-mute); font-size:12.5px; }\n .badge { display:inline-block; padding:1px 8px; border-radius:999px; font-size:10px; font-weight:700; }\n .badge.running { background:var(--accent-soft); color:var(--accent); }\n .badge.starting { background:var(--blue-soft); color:var(--blue); }\n .badge.exited { background:var(--line-soft); color:var(--ink-faint); }\n .badge.killed, .badge.error { background:var(--red-soft); color:var(--red); }\n\n /* ── big picture : mission + plan de sous-tâches ── */\n .hero { flex:none; padding:13px 20px 0; border-bottom:1px solid var(--line); background:var(--panel); }\n .hero-top { display:flex; align-items:center; gap:13px; }\n .pulse { width:10px; height:10px; border-radius:50%; background:var(--accent); flex:none;\n box-shadow:0 0 0 0 rgba(13,122,79,.35); animation:pulse 2.4s infinite; }\n .pulse.off { background:var(--ink-ghost); animation:none; }\n @keyframes pulse { 70% { box-shadow:0 0 0 9px rgba(13,122,79,0); } 100% { box-shadow:0 0 0 0 rgba(13,122,79,0); } }\n .who { min-width:0; flex:1; }\n .who .h1 { font-size:14.5px; font-weight:700; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n .who .h2 { font-size:12px; color:var(--ink-mute); margin-top:1px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n .modewrap { display:flex; border:1px solid var(--line); border-radius:9px; overflow:hidden; flex:none; }\n .modewrap button { border:none; background:var(--panel); color:var(--ink-mute); font-size:11px; font-weight:700; padding:6px 11px; cursor:pointer; }\n .modewrap button.on { background:var(--ink); color:#fdf9f2; }\n button.sim, a.sim { border:1px solid var(--line); background:var(--panel); color:var(--ink-mute); font-weight:700;\n font-size:11.5px; border-radius:8px; padding:6px 12px; cursor:pointer; flex:none; }\n button.sim.on { background:var(--ink); border-color:var(--ink); color:#fdf9f2; }\n a.sim { display:inline-flex; align-items:center; text-decoration:none; }\n a.sim:hover { border-color:var(--ink-ghost); color:var(--ink); }\n\n /* plan strip : les sous-tâches, l'avancement d'un coup d'œil */\n .plan { display:flex; gap:6px; overflow-x:auto; padding:11px 0 12px; scrollbar-width:none; }\n .plan::-webkit-scrollbar { display:none; }\n .pt { flex:none; display:flex; align-items:center; gap:6px; font-size:11.5px; font-weight:700; padding:5px 11px;\n border-radius:999px; border:1px solid var(--line); background:var(--bg); color:var(--ink-mute); cursor:pointer; white-space:nowrap; }\n .pt:hover { border-color:var(--ink-ghost); }\n .pt .st { font-size:10px; }\n .pt.done { color:var(--accent); background:var(--accent-soft); border-color:transparent; }\n .pt.cur { color:var(--gold); background:var(--gold-soft); border-color:transparent; }\n .pt.cur .st { animation:blink 1.6s infinite; }\n @keyframes blink { 50% { opacity:.35; } }\n\n /* ── corps ── */\n .body { flex:1; display:flex; min-height:0; }\n .feedcol { flex:1; min-width:320px; display:flex; flex-direction:column; }\n .feed { flex:1; overflow-y:auto; padding:4px 14px 10px; display:flex; flex-direction:column; scroll-behavior:smooth; }\n .fspacer { flex:1; }\n\n /* chapitres */\n .chap { flex:none; position:sticky; top:0; z-index:5; margin:8px -4px 2px; padding:7px 12px; display:flex; align-items:center; gap:9px;\n background:color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter:blur(6px);\n border-radius:9px; cursor:pointer; font-size:11.5px; font-weight:800; letter-spacing:.03em; color:var(--ink-mute); }\n .chap:hover { color:var(--ink); }\n .chap .cst { flex:none; width:17px; height:17px; border-radius:50%; display:grid; place-items:center; font-size:9.5px; font-weight:900; }\n .chap.done .cst { background:var(--accent-soft); color:var(--accent); }\n .chap.cur .cst { background:var(--gold-soft); color:var(--gold); }\n .chap .cnum { color:var(--ink-ghost); font-weight:700; }\n .chap .csum { flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n .chap .cmeta { flex:none; font-size:10.5px; color:var(--ink-ghost); font-weight:600; }\n .chap .cchev { flex:none; color:var(--ink-ghost); transition:transform .15s; }\n .chap.open .cchev { transform:rotate(90deg); }\n\n .row { flex:none; min-height:44px; margin:1px 0 1px 10px; padding:5px 12px 5px 10px; display:flex; align-items:center; gap:11px;\n cursor:pointer; border-radius:10px; border-left:3px solid transparent; transition:background .12s; }\n .row:hover { background:var(--line-soft); }\n .row[aria-selected=\"true\"] { background:var(--sel); border-left-color:var(--gold); }\n .row .ico { width:24px; height:24px; border-radius:8px; flex:none; display:grid; place-items:center; font-size:11.5px; font-weight:800; }\n .ico.k-text { background:var(--blue-soft); color:var(--blue); }\n .ico.k-edit { background:var(--gold-soft); color:var(--gold); }\n .ico.k-bash { background:var(--accent-soft); color:var(--accent); }\n .ico.k-read { background:var(--violet-soft); color:var(--violet); }\n .ico.k-user { background:var(--ink); color:#fdf9f2; }\n .row .mid { flex:1; min-width:0; }\n .row .sum { display:block; font-size:13.5px; line-height:1.35; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }\n .row .raw1 { display:block; font-size:10.5px; color:var(--ink-faint); font-family:ui-monospace,Menlo,monospace;\n white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-top:1px; }\n body:not(.tech) .row .raw1 { display:none; }\n .row .route { display:inline-block; font-size:10px; font-weight:800; padding:1px 8px; border-radius:999px; margin-top:2px; }\n .route.cont { background:var(--blue-soft); color:var(--blue); }\n .route.newt { background:var(--gold-soft); color:var(--gold); }\n .row .cnt { flex:none; font-size:10px; font-weight:800; color:var(--gold); background:var(--gold-soft); padding:2px 7px; border-radius:999px; }\n .row .ts { flex:none; font-size:10.5px; color:var(--ink-ghost); font-variant-numeric:tabular-nums; }\n @keyframes slidein { from { opacity:0; transform:translateY(6px); } }\n .row.new { animation:slidein .25s ease-out; }\n\n /* ── panneau ancré ── */\n .panel { flex:none; width:0; overflow:hidden; border-left:1px solid transparent; background:var(--panel);\n display:flex; flex-direction:column; transition:width .22s ease, border-color .22s; }\n .panel.open { width:min(430px,46vw); border-left-color:var(--line); }\n .panel-inner { width:min(430px,46vw); flex:1; display:flex; flex-direction:column; min-height:0; }\n .phead { flex:none; padding:14px 16px 12px; border-bottom:1px solid var(--line); display:flex; align-items:flex-start; gap:11px; }\n .phead .ico { width:28px; height:28px; font-size:13px; border-radius:9px; }\n .phead .tt { min-width:0; flex:1; }\n .phead .t { font-size:14px; font-weight:700; line-height:1.4; }\n .phead .s { font-size:11px; color:var(--ink-faint); margin-top:3px; font-variant-numeric:tabular-nums; }\n .pnav { display:flex; gap:4px; flex:none; }\n .pnav button { width:26px; height:26px; border:1px solid var(--line); background:var(--panel); border-radius:8px;\n color:var(--ink-mute); font-size:12px; cursor:pointer; display:grid; place-items:center; }\n .pnav button:disabled { opacity:.3; cursor:default; }\n .pbody { flex:1; overflow-y:auto; padding:16px; display:flex; flex-direction:column; gap:14px; }\n .plain { font-size:14px; line-height:1.7; }\n .plain .why { margin-top:8px; font-size:12.5px; color:var(--ink-mute); line-height:1.6; }\n .facts { display:flex; flex-wrap:wrap; gap:6px; }\n .fact { font-size:11px; font-weight:700; background:var(--bg); border:1px solid var(--line); color:var(--ink-mute); padding:4px 10px; border-radius:999px; }\n .fact.ok { background:var(--accent-soft); border-color:transparent; color:var(--accent); }\n details.techbox { border:1px solid var(--line); border-radius:12px; background:var(--bg); overflow:hidden; }\n details.techbox summary { list-style:none; cursor:pointer; padding:10px 14px; font-size:11.5px; font-weight:800;\n letter-spacing:.04em; text-transform:uppercase; color:var(--ink-faint); display:flex; align-items:center; gap:8px; }\n details.techbox summary::-webkit-details-marker { display:none; }\n details.techbox summary::after { content:\"▸\"; margin-left:auto; transition:transform .15s; }\n details.techbox[open] summary::after { transform:rotate(90deg); }\n .techlist { padding:0 12px 12px; display:flex; flex-direction:column; gap:8px; }\n .titem { border:1px solid var(--line); border-radius:10px; background:var(--panel); overflow:hidden; }\n .titem .th { padding:8px 12px; font-size:11.5px; font-weight:700; color:var(--ink-mute); display:flex; align-items:center; gap:8px; }\n .titem .th .copy { margin-left:auto; border:none; background:none; color:var(--ink-ghost); font-size:11px; cursor:pointer; padding:2px 4px; border-radius:5px; }\n .titem .th .copy:hover { color:var(--ink); background:var(--line-soft); }\n .titem pre { margin:0; border-top:1px solid var(--line-soft); font-family:ui-monospace,Menlo,monospace; font-size:11.5px;\n line-height:1.55; color:#4a4236; padding:9px 12px; overflow:auto; max-height:240px; white-space:pre-wrap; word-break:break-word; }\n .d-text { font-size:13.5px; line-height:1.7; }\n .d-text p { margin:0 0 8px; }\n .d-text p:last-child { margin-bottom:0; }\n .d-text h1, .d-text h2, .d-text h3, .d-text h4, .d-text h5, .d-text h6 { margin:12px 0 6px; line-height:1.3; }\n .d-text h1:first-child, .d-text h2:first-child, .d-text h3:first-child { margin-top:0; }\n .d-text ul, .d-text ol { margin:0 0 8px; padding-left:20px; }\n .d-text code { font-family:ui-monospace,Menlo,monospace; font-size:12.5px; background:var(--bg); border-radius:4px; padding:1px 5px; }\n .d-text pre { margin:0 0 8px; background:var(--bg); border:1px solid var(--line); border-radius:8px; padding:9px 12px; overflow:auto; }\n .d-text pre code { background:none; border-radius:0; padding:0; }\n .d-text table { border-collapse:collapse; margin:0 0 8px; font-size:12.5px; }\n .d-text th, .d-text td { border:1px solid var(--line); padding:4px 8px; text-align:left; }\n .d-text a { color:var(--blue); }\n .pfoot { flex:none; border-top:1px solid var(--line); padding:9px 16px; font-size:11px; color:var(--ink-ghost); display:flex; gap:10px; }\n .kbd { font-family:ui-monospace,Menlo,monospace; font-size:10px; border:1px solid var(--line); border-bottom-width:2px;\n border-radius:5px; padding:1px 5px; background:var(--panel); color:var(--ink-mute); }\n\n /* ── boîte d'envoi + routage IA ── */\n .composer { flex:none; border-top:1px solid var(--line); background:var(--panel); padding:10px 14px; }\n .composer .cbar { display:flex; gap:8px; }\n .composer textarea { flex:1; resize:none; border:1px solid var(--line); border-radius:10px; padding:9px 12px; font:inherit; font-size:13px; max-height:110px; background:var(--bg); }\n .composer textarea:focus { outline:2px solid #241f1a22; }\n .composer textarea:disabled { opacity:.5; cursor:not-allowed; }\n .composer button { border:none; border-radius:10px; padding:0 16px; background:var(--ink); color:#fdf9f2; font-weight:700; font-size:13px; cursor:pointer; }\n .composer button:disabled { opacity:.4; cursor:not-allowed; }\n .composer .routing { font-size:11px; color:var(--ink-faint); padding:6px 2px 0; min-height:22px; }\n .composer .routing .r-cont { color:var(--blue); font-weight:700; }\n .composer .routing .r-newt { color:var(--gold); font-weight:700; }\n #statusbar { flex:none; padding:4px 20px; font-size:10.5px; color:var(--ink-ghost); border-top:1px solid var(--line-soft); }\n</style>\n</head>\n<body>\n<div id=\"pickerScreen\">\n <h1>Choisis une session</h1>\n <div id=\"pickerList\"><div class=\"pk-empty\">Connexion…</div></div>\n</div>\n\n<div class=\"app hidden\" id=\"storyScreen\">\n <div class=\"hero\">\n <div class=\"hero-top\">\n <span class=\"pulse\" id=\"pulse\"></span>\n <div class=\"who\">\n <div class=\"h1\" id=\"heroTitle\"></div>\n <div class=\"h2\" id=\"heroSub\"></div>\n </div>\n <div class=\"modewrap\"><button id=\"modeSimple\" class=\"on\" type=\"button\">Simple</button><button id=\"modeTech\" type=\"button\">Tech</button></div>\n <a class=\"sim\" id=\"fullPanelLink\" href=\"#\" target=\"_blank\" rel=\"noopener\" title=\"Ouvrir le panneau complet (Terminal/Chat/JSON/TTY)\">↗ panneau complet</a>\n <button class=\"sim\" id=\"switchBtn\" type=\"button\">⇆ changer</button>\n </div>\n <div class=\"plan\" id=\"plan\"></div>\n </div>\n\n <div class=\"body\">\n <div class=\"feedcol\">\n <div class=\"feed\" id=\"feed\"><div class=\"fspacer\"></div><div id=\"rows\"></div></div>\n <div class=\"composer\">\n <div class=\"cbar\">\n <textarea id=\"msgBox\" rows=\"1\" placeholder=\"Écris à l'agent… (la surcouche classe ton message : suite de la sous-tâche ou nouvelle sous-tâche)\"></textarea>\n <button id=\"sendBtn\" type=\"button\">Envoyer</button>\n </div>\n <div class=\"routing\" id=\"routing\"></div>\n </div>\n </div>\n\n <aside class=\"panel\" id=\"panel\" aria-label=\"Détail de l'étape\">\n <div class=\"panel-inner\">\n <div class=\"phead\">\n <span class=\"ico\" id=\"pIco\"></span>\n <div class=\"tt\"><div class=\"t\" id=\"pTitle\"></div><div class=\"s\" id=\"pSub\"></div></div>\n <div class=\"pnav\"><button id=\"pPrev\" type=\"button\">↑</button><button id=\"pNext\" type=\"button\">↓</button><button id=\"pClose\" type=\"button\">✕</button></div>\n </div>\n <div class=\"pbody\" id=\"pBody\"></div>\n <div class=\"pfoot\"><span class=\"kbd\">↑</span><span class=\"kbd\">↓</span> naviguer · <span class=\"kbd\">Esc</span> fermer</div>\n </div>\n </aside>\n </div>\n <div id=\"statusbar\"></div>\n</div>\n\n<script>\nvar $=function(id){ return document.getElementById(id); };\nvar esc=function(s){ return String(s==null?\"\":s).replace(/[&<>]/g,function(c){ return {\"&\":\"&\",\"<\":\"<\",\">\":\">\"}[c]; }); };\n\n// ============================================================\n// renderMd — vanilla-JS port of markdown-lite.ts. Kept\n// function-for-function identical so the two are easy to diff (same\n// self-contained-panel constraint as buildStoryJs below): headers,\n// bold/italic, inline/fenced code, bullet/numbered lists, pipe tables and\n// links, with every raw text run HTML-escaped before any generated tag\n// wraps it.\n// ============================================================\nfunction escHtmlMd(s){ return String(s).replace(/[&<>\"]/g,function(c){ if(c==='&') return '&'; if(c==='<') return '<'; if(c==='>') return '>'; return '"'; }); }\nfunction renderInlineMd(text){\n var out=escHtmlMd(text);\n out=out.replace(/\\`([^\\`]+)\\`/g,function(_m,code){ return '<code>'+code+'</code>'; });\n out=out.replace(/\\\\[([^\\\\]]+)\\\\]\\\\((https?:\\\\/\\\\/[^\\\\s)]+)\\\\)/g,function(_m,label,url){ return '<a href=\"'+url+'\" target=\"_blank\" rel=\"noopener noreferrer\">'+label+'</a>'; });\n out=out.replace(/\\\\*\\\\*([^*]+)\\\\*\\\\*/g,'<strong>$1</strong>');\n out=out.replace(/(^|[^*])\\\\*([^*]+)\\\\*(?!\\\\*)/g,'$1<em>$2</em>');\n return out;\n}\nfunction isTableSepMd(line){ return /^\\\\s*\\\\|?\\\\s*:?-+:?\\\\s*(\\\\|\\\\s*:?-+:?\\\\s*)+\\\\|?\\\\s*$/.test(line); }\nfunction splitRowMd(line){ return line.trim().replace(/^\\\\|/,'').replace(/\\\\|$/,'').split('|').map(function(c){ return c.trim(); }); }\nfunction renderMd(md){\n var lines=String(md==null?'':md).replace(/\\\\r\\\\n?/g,'\\\\n').split('\\\\n');\n var out=[], para=[], list=null;\n function flushPara(){ if(para.length){ out.push('<p>'+para.map(renderInlineMd).join('<br>')+'</p>'); para=[]; } }\n function flushList(){ if(list){ var tag=list.ordered?'ol':'ul'; out.push('<'+tag+'>'+list.items.map(function(i){ return '<li>'+renderInlineMd(i)+'</li>'; }).join('')+'</'+tag+'>'); list=null; } }\n function flushAll(){ flushPara(); flushList(); }\n var i=0;\n while(i<lines.length){\n var line=lines[i];\n if(/^\\\\s*\\`\\`\\`/.test(line)){\n flushAll();\n var code=[]; i+=1;\n while(i<lines.length && !/^\\\\s*\\`\\`\\`/.test(lines[i])){ code.push(lines[i]); i+=1; }\n i+=1;\n out.push('<pre><code>'+escHtmlMd(code.join('\\\\n'))+'</code></pre>');\n continue;\n }\n var header=line.match(/^(#{1,6})\\\\s+(.*)$/);\n if(header){\n flushAll();\n var level=header[1].length;\n out.push('<h'+level+'>'+renderInlineMd(header[2].trim())+'</h'+level+'>');\n i+=1;\n continue;\n }\n if(/^\\\\s*\\\\|/.test(line) && i+1<lines.length && isTableSepMd(lines[i+1])){\n flushAll();\n var headCells=splitRowMd(line);\n i+=2;\n var bodyRows=[];\n while(i<lines.length && /^\\\\s*\\\\|/.test(lines[i])){ bodyRows.push(splitRowMd(lines[i])); i+=1; }\n out.push('<table><thead><tr>'+headCells.map(function(c){ return '<th>'+renderInlineMd(c)+'</th>'; }).join('')+'</tr></thead><tbody>'\n +bodyRows.map(function(r){ return '<tr>'+r.map(function(c){ return '<td>'+renderInlineMd(c)+'</td>'; }).join('')+'</tr>'; }).join('')+'</tbody></table>');\n continue;\n }\n var bullet=line.match(/^\\\\s*[-*+]\\\\s+(.*)$/);\n var numbered=line.match(/^\\\\s*\\\\d+\\\\.\\\\s+(.*)$/);\n if(bullet || numbered){\n flushPara();\n var ordered=!!numbered;\n var item=(bullet||numbered)[1];\n if(!list || list.ordered!==ordered){ flushList(); list={ordered:ordered,items:[]}; }\n list.items.push(item);\n i+=1;\n continue;\n }\n if(line.trim()===''){ flushAll(); i+=1; continue; }\n flushList();\n para.push(line);\n i+=1;\n }\n flushAll();\n return out.join('');\n}\n\n${panelBridgeScript(\"agentproto-session-story-panel\")}\n// Best-effort: some hosts forward the triggering tool call's arguments as\n// a notification so the panel can auto-open the right session. Purely\n// additive — the session picker is the reliable path when this never\n// arrives.\nvar pendingSessionId=null;\nonHostNotification(function(method, params){\n if(/tool-input|tool-call/.test(method)){\n var args=(params && (params.arguments || params.input)) || {};\n if(args && args.sessionId) pendingSessionId=args.sessionId;\n }\n});\n\n// ============================================================\n// buildStory — vanilla-JS port of session-story.ts. Kept\n// function-for-function identical so the two are easy to diff; the panel\n// resource must be fully self-contained (no bundler/dynamic import), so it\n// cannot import the TS module directly.\n// ============================================================\n\nvar SALIENT_KEYS=[\"file_path\",\"path\",\"filePath\",\"file\",\"command\",\"pattern\",\"query\",\"q\",\"url\",\"todos\",\"description\",\"prompt\"];\nfunction truncateStr(v,max){ var o=String(v).replace(/\\\\s+/g,' ').trim(); return o.length>max? o.slice(0,max-1)+'…':o; }\nfunction formatArgValue(v){\n if(typeof v==='string') return v;\n if(Array.isArray(v)) return v.length+' item'+(v.length===1?'':'s');\n if(v && typeof v==='object') return JSON.stringify(v);\n return String(v);\n}\nfunction pickSalient(args){\n for(var i=0;i<SALIENT_KEYS.length;i++){\n var k=SALIENT_KEYS[i], v=args[k];\n if(v!==undefined && v!==null && v!=='') return formatArgValue(v);\n }\n return null;\n}\nfunction formatToolCall(name,args){\n name=name||'tool';\n args=(args && typeof args==='object' && !Array.isArray(args)) ? args : {};\n var salient=pickSalient(args);\n if(salient!==null){\n if(name.toLowerCase().indexOf(salient.toLowerCase())>=0) return truncateStr(name,120);\n return truncateStr(name+' '+salient,120);\n }\n if(Object.keys(args).length===0) return name;\n return truncateStr(name+' '+JSON.stringify(args),120);\n}\nfunction extractText(v){\n if(v==null) return null;\n if(typeof v==='string') return v;\n if(Array.isArray(v)){\n var parts=v.map(extractText).filter(function(x){ return x!=null; });\n return parts.length? parts.join('\\\\n') : null;\n }\n if(typeof v==='object'){\n if(typeof v.text==='string') return v.text;\n if(typeof v.message==='string') return v.message;\n if(Array.isArray(v.content)) return extractText(v.content);\n if(typeof v.error==='string') return v.error;\n if(v.error && typeof v.error==='object' && typeof v.error.message==='string') return v.error.message;\n return null;\n }\n return null;\n}\nfunction formatToolResult(toolName,result,isError){\n var text=extractText(result);\n if(isError){\n var message=text!=null? text : (result!=null? JSON.stringify(result) : 'failed');\n var firstLine=String(message).split(/\\\\r?\\\\n/)[0] || message;\n return truncateStr(firstLine,160);\n }\n if(text==null) return null;\n var trimmed=text.trim();\n if(!trimmed) return null;\n var lines=trimmed.split(/\\\\r?\\\\n/);\n if(lines.length>1){\n var bytes=new TextEncoder().encode(trimmed).length;\n return lines.length+' lines, '+bytes+'B';\n }\n return truncateStr(lines[0],160);\n}\n\nfunction classifyKind(toolCalls){\n if(!toolCalls || toolCalls.length===0) return 'text';\n var names=toolCalls.map(function(t){ return t.name.toLowerCase(); });\n if(names.some(function(n){ return /edit|write/.test(n); })) return 'edit';\n if(names.some(function(n){ return /bash|terminal|command/.test(n); })) return 'bash';\n if(names.some(function(n){ return /read|grep|glob/.test(n); })) return 'read';\n return 'text';\n}\nvar NEW_CHAPTER_RE=/\\\\b(aussi|autre|ensuite|nouveau|nouvelle|plut[oô]t|maintenant|apr[eè]s ça|il faudrait|peux[- ]tu|on pourrait|ajoute|g[eè]re)\\\\b/iu;\nfunction classifyRoute(text){\n var newt=NEW_CHAPTER_RE.test(text);\n if(!newt) return {route:'cont'};\n var title=text.replace(/[.?!].*$/,'').slice(0,42);\n return {route:'newt', title:title};\n}\nfunction formatTsJs(ts){\n if(ts===undefined || ts===null || isNaN(ts)) return '';\n return new Date(ts).toISOString().slice(11,19);\n}\nfunction firstMeaningfulLine(text){\n if(!text) return undefined;\n var lines=text.split('\\\\n').map(function(l){ return l.trim(); }).filter(function(l){ return l.length>0; });\n return lines[0];\n}\nfunction lineCountOf(text){\n var n=(text||'').split('\\\\n').filter(function(l){ return l.trim().length>0; }).length;\n return n||1;\n}\nfunction parseArgsJson(s){ try{ return JSON.parse(s); }catch(e){ return {}; } }\n\nfunction foldToolStep(assistant,toolResults){\n var toolCalls=assistant.toolCalls||[];\n var kind=classifyKind(toolCalls);\n var count=toolCalls.length||1;\n var items=[], facts=[];\n if(assistant.text && assistant.text.trim()) items.push({text:assistant.text.trim()});\n toolCalls.forEach(function(tc,i){\n var args=parseArgsJson(tc.args);\n var h=formatToolCall(tc.name,args);\n var resultMsg=toolResults[i];\n var resultText=(resultMsg && resultMsg.text) || '';\n var isError=resultText.indexOf('[error]')===0;\n var r=isError? resultText.slice(7).trim() : resultText;\n items.push({h:h,r:r});\n var fact=formatToolResult(tc.name,r,isError);\n if(fact) facts.push(fact);\n });\n var firstLine=firstMeaningfulLine(assistant.text);\n var firstToolCall=toolCalls[0];\n var sum=firstLine!==undefined? firstLine : (firstToolCall? formatToolCall(firstToolCall.name,parseArgsJson(firstToolCall.args)) : '…');\n var raw1;\n if(toolCalls.length===0) raw1='assistant · '+lineCountOf(assistant.text)+' ligne(s)';\n else if(toolCalls.length===1) raw1=formatToolCall(firstToolCall.name,parseArgsJson(firstToolCall.args));\n else raw1=(firstToolCall? firstToolCall.name : 'tool')+' ×'+toolCalls.length;\n return {kind:kind, ts:formatTsJs(assistant.ts), sum:sum, raw1:raw1, count:count, facts:facts, items:items};\n}\nfunction foldUserStep(msg){\n var text=msg.text||'';\n return {kind:'user', ts:formatTsJs(msg.ts), sum:'« '+truncateStr(text,80)+' »', raw1:'user · '+lineCountOf(text)+' ligne(s)', count:1, facts:[], items:[{text:text}], userText:text};\n}\nfunction foldOrphanToolStep(msg){\n var text=msg.text||'';\n var isError=text.indexOf('[error]')===0;\n var r=isError? text.slice(7).trim() : text;\n var name=msg.toolName||'tool';\n var fact=formatToolResult(name,r,isError);\n return {kind:classifyKind([{name:name}]), ts:formatTsJs(msg.ts), sum: msg.toolName? (msg.toolName+' · résultat') : \"Résultat d'outil\", raw1: msg.toolName||'tool', count:1, facts: fact?[fact]:[], items:[{h:name,r:r}]};\n}\nfunction foldSystemStep(msg){\n var text=msg.text||'';\n var line=firstMeaningfulLine(text);\n return {kind:'text', ts:formatTsJs(msg.ts), sum: line!==undefined? line : text, raw1:'system', count:1, facts:[], items: text?[{text:text}]:[]};\n}\nfunction foldMessages(messages){\n var steps=[], i=0;\n while(i<messages.length){\n var msg=messages[i];\n if(msg.role==='user'){ steps.push(foldUserStep(msg)); i+=1; continue; }\n if(msg.role==='assistant'){\n var j=i+1, toolResults=[];\n while(j<messages.length && messages[j].role==='tool'){ toolResults.push(messages[j]); j+=1; }\n steps.push(foldToolStep(msg,toolResults)); i=j; continue;\n }\n if(msg.role==='tool'){ steps.push(foldOrphanToolStep(msg)); i+=1; continue; }\n steps.push(foldSystemStep(msg)); i+=1;\n }\n return steps;\n}\nfunction buildStoryJs(messages){\n var folded=foldMessages(messages||[]);\n var chapters=[], steps=[];\n var currentChapterId, sawFirstUser=false;\n function closeCurrent(){ var cur=chapters.filter(function(c){ return c.id===currentChapterId; })[0]; if(cur) cur.status='done'; }\n function openChapter(title){ var id='c'+(chapters.length+1); chapters.push({id:id,title:title,status:'cur'}); return id; }\n folded.forEach(function(step){\n var route;\n if(step.kind==='user' && step.userText!==undefined){\n if(!sawFirstUser){ sawFirstUser=true; currentChapterId=openChapter('Cadrage'); }\n else {\n var verdict=classifyRoute(step.userText);\n route=verdict.route;\n if(verdict.route==='newt'){ closeCurrent(); currentChapterId=openChapter(verdict.title||'Nouvelle sous-tâche'); }\n }\n } else if(currentChapterId===undefined){ currentChapterId=openChapter('Cadrage'); }\n var out={chap:currentChapterId, kind:step.kind, ts:step.ts, sum:step.sum, raw1:step.raw1, count:step.count, facts:step.facts, items:step.items};\n if(route) out.route=route;\n steps.push(out);\n });\n return {chapters:chapters, steps:steps};\n}\n\n// ============================================================\n// App state\n// ============================================================\nvar sessions=[];\nvar activeSessionId=null;\nvar story={chapters:[], steps:[]};\nvar open={};\nvar selected=-1;\nvar lastSeenOutputAt=null;\nvar pollTimer=null, polling=false;\nvar ICONS={text:[\"k-text\",\"A\"],edit:[\"k-edit\",\"✎\"],bash:[\"k-bash\",\"▸\"],read:[\"k-read\",\"⌕\"],user:[\"k-user\",\"T\"]};\nvar icoSpec=function(k){ return ICONS[k]||ICONS.text; };\nvar chapOf=function(id){ return story.chapters.filter(function(c){ return c.id===id; })[0]; };\nvar curChap=function(){ return story.chapters.filter(function(c){ return c.status==='cur'; })[0] || story.chapters[story.chapters.length-1]; };\n\nfunction setStatus(msg){ $('statusbar').textContent=msg; }\nfunction nowTs(){ return new Date().toLocaleTimeString('fr-FR',{hour:'2-digit',minute:'2-digit',second:'2-digit'}); }\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\n// ============================================================\n// Picker screen\n// ============================================================\nfunction renderPicker(){\n var el=$('pickerList');\n if(!sessions.length){ el.innerHTML='<div class=\"pk-empty\">Aucune session</div>'; return; }\n var html='';\n sessions.forEach(function(s){\n html+='<div class=\"pk-item\" data-id=\"'+esc(s.id)+'\">'\n + '<span class=\"pk-name\">'+esc(titleOf(s))+'</span>'\n + '<span class=\"badge '+esc(s.status)+'\">'+esc(s.status)+'</span>'\n + '<span class=\"pk-meta\">'+esc(s.kind||'')+'</span>'\n + '</div>';\n });\n el.innerHTML=html;\n Array.prototype.forEach.call(el.querySelectorAll('.pk-item'), function(row){\n row.onclick=function(){ openSession(row.getAttribute('data-id')); };\n });\n}\n\nfunction showPicker(){\n activeSessionId=null;\n $('pickerScreen').classList.remove('hidden');\n $('storyScreen').classList.add('hidden');\n renderPicker();\n}\n\nfunction openSession(id){\n activeSessionId=id;\n story={chapters:[], steps:[]};\n open={};\n selected=-1;\n lastSeenOutputAt=null;\n $('pickerScreen').classList.add('hidden');\n $('storyScreen').classList.remove('hidden');\n $('fullPanelLink').href='https://cli.agentproto.sh/panel?session='+encodeURIComponent(id);\n closePanel();\n loadStory().then(renderAll);\n}\n\n// ============================================================\n// Story loading\n// ============================================================\nfunction activeSession(){ return sessions.filter(function(s){ return s.id===activeSessionId; })[0]; }\n\nfunction loadStory(){\n return callTool('agent_export', {sessionId:activeSessionId, format:'json'}).then(function(data){\n var messages=(data && data.messages) || [];\n story=buildStoryJs(messages);\n // Default open state: only the last (current) chapter is expanded.\n var last=story.chapters[story.chapters.length-1];\n if(last && !(last.id in open)) open[last.id]=true;\n }).catch(function(e){\n setStatus('Erreur export : '+e.message);\n });\n}\n\nfunction canSend(){\n var s=activeSession();\n return !!s && s.kind==='agent-cli' && s.status==='running';\n}\n\nfunction renderComposer(){\n var s=activeSession();\n var box=$('msgBox'), btn=$('sendBtn');\n var enabled=canSend();\n box.disabled=!enabled;\n btn.disabled=!enabled;\n if(!s){ box.placeholder='Session introuvable.'; }\n else if(!enabled) box.placeholder='Lecture seule — session '+esc(s.status)+'.';\n else box.placeholder=\"Écris à l'agent… (la surcouche classe ton message : suite de la sous-tâche ou nouvelle sous-tâche)\";\n}\n\nfunction renderHero(){\n var s=activeSession();\n $('heroTitle').textContent=s? titleOf(s) : (activeSessionId||'');\n var firstUser=story.steps.filter(function(st){ return st.kind==='user'; })[0];\n var mission=firstUser? firstUser.userText || (firstUser.items[0] && firstUser.items[0].text) : null;\n $('heroSub').textContent=mission? truncateStr(mission,200) : 'Aucun message pour le moment.';\n var p=$('pulse');\n p.classList.toggle('off', !(s && (s.status==='running' || s.status==='starting')));\n renderComposer();\n}\n\nfunction renderAll(){\n renderHero();\n renderPlan();\n renderRows('bottom');\n}\n\n// ============================================================\n// big picture strip\n// ============================================================\nfunction renderPlan(){\n var done=story.chapters.filter(function(c){ return c.status==='done'; }).length;\n $('plan').innerHTML=story.chapters.map(function(c,i){\n return '<span class=\"pt '+c.status+'\" data-c=\"'+esc(c.id)+'\"><span class=\"st\">'+(c.status==='done'?'✓':'●')+'</span>'+(i+1)+'. '+esc(c.title)+'</span>';\n }).join('') + '<span class=\"pt\" style=\"cursor:default\"><b>'+done+'/'+story.chapters.length+'</b> faites</span>';\n Array.prototype.forEach.call($('plan').querySelectorAll('.pt[data-c]'), function(el){\n el.onclick=function(){ open[el.getAttribute('data-c')]=true; renderRows(); jumpToChap(el.getAttribute('data-c')); };\n });\n}\nfunction jumpToChap(cid){\n var el=document.querySelector('.chap[data-c=\"'+cid+'\"]');\n if(el) el.scrollIntoView({block:'start',behavior:'smooth'});\n}\n\n// ============================================================\n// feed segmenté par chapitres\n// ============================================================\nfunction rowHtml(s,i,isNew){\n var spec=icoSpec(s.kind), cls=spec[0], ch=spec[1];\n var route=s.route? '<span class=\"route '+(s.route==='newt'?'newt':'cont')+'\">'+(s.route==='newt'?'★ nouvelle sous-tâche':'↳ suite')+'</span>' : '';\n return '<div class=\"row '+(isNew?'new':'')+'\" aria-selected=\"'+(i===selected)+'\" data-i=\"'+i+'\">'\n + '<span class=\"ico '+cls+'\">'+ch+'</span>'\n + '<span class=\"mid\"><span class=\"sum\">'+esc(s.sum)+'</span><span class=\"raw1\">'+esc(s.raw1||'')+'</span>'+route+'</span>'\n + (s.count>1? '<span class=\"cnt\">×'+s.count+'</span>':'') + '<span class=\"ts\">'+esc(s.ts||'')+'</span>'\n + '</div>';\n}\nfunction renderRows(keepScroll,newIdx){\n var feed=$('feed');\n var prevH=feed.scrollHeight, prevTop=feed.scrollTop;\n var html='';\n story.chapters.forEach(function(c,ci){\n var chapSteps=[];\n story.steps.forEach(function(s,i){ if(s.chap===c.id) chapSteps.push({s:s,i:i}); });\n if(!chapSteps.length) return;\n var isOpen=!!open[c.id];\n html+='<div class=\"chap '+c.status+' '+(isOpen?'open':'')+'\" data-c=\"'+esc(c.id)+'\">'\n + '<span class=\"cst\">'+(c.status==='done'?'✓':'●')+'</span><span class=\"cnum\">'+(ci+1)+'.</span>'\n + '<span class=\"csum\">'+esc(c.title)+'</span>'\n + '<span class=\"cmeta\">'+chapSteps.length+' étape'+(chapSteps.length>1?'s':'')+'</span><span class=\"cchev\">▸</span>'\n + '</div>';\n if(isOpen) html += chapSteps.map(function(x){ return rowHtml(x.s,x.i,x.i===newIdx); }).join('');\n });\n $('rows').innerHTML=html;\n Array.prototype.forEach.call(document.querySelectorAll('.row'), function(el){\n el.onclick=function(){ selectStep(Number(el.getAttribute('data-i'))); };\n });\n Array.prototype.forEach.call(document.querySelectorAll('.chap'), function(el){\n el.onclick=function(){ var c=el.getAttribute('data-c'); open[c]=!open[c]; renderRows(); };\n });\n if(keepScroll==='bottom') feed.scrollTop=feed.scrollHeight;\n else if(keepScroll==='preserve') feed.scrollTop=feed.scrollHeight-prevH+prevTop;\n}\n\n// ============================================================\n// panneau ancré\n// ============================================================\nfunction selectStep(i){\n selected=i;\n var s=story.steps[i]; if(!s) return;\n open[s.chap]=true;\n $('panel').classList.add('open');\n var spec=icoSpec(s.kind), cls=spec[0], ch=spec[1];\n var ico=$('pIco'); ico.className='ico '+cls; ico.textContent=ch;\n $('pTitle').textContent=s.sum;\n var c=chapOf(s.chap);\n $('pSub').textContent=(s.ts? s.ts+' · ':'')+(c? ('sous-tâche : '+c.title) : '');\n var facts=(s.facts||[]).map(function(f){\n return '<span class=\"fact '+(/✓|exit 0|passed|0 match/.test(f)?'ok':'')+'\">'+esc(f)+'</span>';\n }).join('');\n var tech=(s.items||[]).map(function(it,k){\n return it.text!==undefined\n ? '<div class=\"d-text\">'+renderMd(it.text)+'</div>'\n : '<div class=\"titem\"><div class=\"th\">'+esc(it.h)+'<button class=\"copy\" data-k=\"'+k+'\" type=\"button\">⧉</button></div><pre>'+esc(it.r)+'</pre></div>';\n }).join('');\n $('pBody').innerHTML=''\n + '<div class=\"plain\"><div>'+esc(s.sum)+'.</div><div class=\"why\">'+esc(s.why||'')+'</div></div>'\n + (facts? '<div class=\"facts\">'+facts+'</div>':'')\n + '<details class=\"techbox\" '+(document.body.classList.contains('tech')?'open':'')+'>'\n + '<summary>Détail technique · '+(s.items||[]).length+'</summary><div class=\"techlist\">'+tech+'</div>'\n + '</details>';\n Array.prototype.forEach.call($('pBody').querySelectorAll('.copy'), function(btn){\n btn.onclick=function(e){\n e.stopPropagation();\n var it=(s.items||[])[Number(btn.getAttribute('data-k'))];\n var payload=(it.h||'')+'\\\\n'+(it.r||it.text||'');\n if(navigator.clipboard) navigator.clipboard.writeText(payload).catch(function(){});\n btn.textContent='✓'; setTimeout(function(){ btn.textContent='⧉'; },900);\n };\n });\n $('pPrev').disabled=i<=0; $('pNext').disabled=i>=story.steps.length-1;\n renderRows();\n var el=document.querySelector('.row[data-i=\"'+i+'\"]');\n if(el) el.scrollIntoView({block:'nearest',behavior:'smooth'});\n}\nfunction closePanel(){ selected=-1; var p=$('panel'); if(p) p.classList.remove('open'); renderRows(); }\n$('pClose').addEventListener('click',closePanel);\n$('pPrev').addEventListener('click',function(){ if(selected>0) selectStep(selected-1); });\n$('pNext').addEventListener('click',function(){ if(selected<story.steps.length-1) selectStep(selected+1); });\ndocument.addEventListener('keydown',function(e){\n if(e.key==='Escape'){ closePanel(); return; }\n if($('storyScreen').classList.contains('hidden')) return;\n if(selected<0 || e.target.tagName==='TEXTAREA') return;\n if(e.key==='ArrowUp'){ e.preventDefault(); if(selected>0) selectStep(selected-1); }\n if(e.key==='ArrowDown'){ e.preventDefault(); if(selected<story.steps.length-1) selectStep(selected+1); }\n});\n\n// ============================================================\n// Simple / Tech modes\n// ============================================================\nfunction setMode(tech){\n document.body.classList.toggle('tech',tech);\n $('modeTech').classList.toggle('on',tech);\n $('modeSimple').classList.toggle('on',!tech);\n if(selected>=0) selectStep(selected);\n}\n$('modeSimple').addEventListener('click',function(){ setMode(false); });\n$('modeTech').addEventListener('click',function(){ setMode(true); });\n$('switchBtn').addEventListener('click', function(){ if(pollTimer) clearTimeout(pollTimer); showPicker(); doPoll(); });\n\n// ============================================================\n// composer — local chapter-routing classification + agent_prompt\n// ============================================================\n$('sendBtn').addEventListener('click', sendMsg);\n$('msgBox').addEventListener('keydown', function(e){\n if(e.key==='Enter' && !e.shiftKey){ e.preventDefault(); sendMsg(); }\n});\nfunction sendMsg(){\n if(!canSend()) return;\n var box=$('msgBox'), text=box.value.trim();\n if(!text) return;\n box.value='';\n var cur=curChap();\n var r=cur? classifyRoute(text) : {route:'cont'};\n var chapId=cur? cur.id : null;\n if(r.route==='newt' && cur){\n cur.status='done';\n var id='c'+(story.chapters.length+1);\n story.chapters.push({id:id, title:r.title||'Nouvelle sous-tâche', status:'cur'});\n chapId=id; open[id]=true;\n $('routing').innerHTML='✦ classé : <span class=\"r-newt\">★ nouvelle sous-tâche « '+esc(r.title||'')+' »</span>';\n } else {\n $('routing').innerHTML='✦ classé : <span class=\"r-cont\">↳ suite de « '+esc(cur? cur.title : '')+' »</span>';\n }\n setTimeout(function(){ $('routing').textContent=''; },5000);\n story.steps.push({\n chap:chapId, kind:'user', ts:nowTs(),\n sum:'« '+text.slice(0,80)+(text.length>80?'…':'')+' »',\n raw1:'user · '+text.split('\\\\n').length+' ligne(s)',\n route:r.route, facts:[], items:[{text:text}], userText:text,\n });\n renderPlan(); renderRows('bottom', story.steps.length-1);\n callTool('agent_prompt', {sessionId:activeSessionId, prompt:text}).catch(function(e){\n setStatus('Envoi échoué : '+e.message);\n });\n}\n\n// ============================================================\n// Poll — session_list every ~5s; re-fetch agent_export only on turn\n// boundaries (lastOutputAt changed for the active session).\n// ============================================================\nvar POLL_MS=5000;\nfunction loadSessions(){\n return callTool('session_list', {kind:'all'}).then(function(data){\n sessions=data.sessions||[];\n if($('pickerScreen') && !$('pickerScreen').classList.contains('hidden')) renderPicker();\n }).catch(function(e){ setStatus('Erreur : '+e.message); });\n}\nfunction doPoll(){\n if(polling) return;\n polling=true;\n loadSessions().then(function(){\n if(!activeSessionId){ polling=false; pollTimer=setTimeout(doPoll,POLL_MS); return; }\n var s=activeSession();\n if(!s){ polling=false; pollTimer=setTimeout(doPoll,POLL_MS); return; }\n renderHero();\n var changed=s.lastOutputAt && s.lastOutputAt!==lastSeenOutputAt;\n if(changed){\n lastSeenOutputAt=s.lastOutputAt;\n loadStory().then(function(){ renderPlan(); renderRows('bottom'); polling=false; pollTimer=setTimeout(doPoll,POLL_MS); });\n } else {\n polling=false; pollTimer=setTimeout(doPoll,POLL_MS);\n }\n }).catch(function(){ polling=false; pollTimer=setTimeout(doPoll,POLL_MS); });\n}\n\n// ============================================================\n// Boot\n// ============================================================\ninitBridge().then(loadSessions).then(function(){\n setTimeout(function(){\n var target=pendingSessionId && sessions.some(function(s){ return s.id===pendingSessionId; })\n ? pendingSessionId\n : null;\n if(!target){\n var agentSessions=sessions.filter(function(s){ return s.kind==='agent-cli'; });\n if(agentSessions.length===1) target=agentSessions[0].id;\n }\n if(target) openSession(target); else showPicker();\n pollTimer=setTimeout(doPoll,POLL_MS);\n }, 50);\n}).catch(function(e){\n setStatus('Bridge : '+e.message);\n $('pickerList').innerHTML='<div class=\"pk-empty\">Échec connexion bridge : '+esc(e.message)+'</div>';\n});\n</script>\n</body>\n</html>`\n"]}
|
package/dist/session-story.d.ts
CHANGED
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
* source:
|
|
4
4
|
*
|
|
5
5
|
* native — the adapter's OWN persistence, re-read after the fact:
|
|
6
|
-
* claude-code
|
|
7
|
-
* hermes
|
|
6
|
+
* claude-code — ~/.claude/projects/<cwd-encoded>/<sessionId>.jsonl
|
|
7
|
+
* hermes — ~/.hermes/state.db via node:sqlite (read-only)
|
|
8
|
+
* codex — $CODEX_HOME/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl
|
|
9
|
+
* opencode — ~/.local/share/opencode/opencode.db via node:sqlite
|
|
10
|
+
* mastracode-inprocess — ~/.agentproto/mastracode-inprocess/storage.db (Mastra libsql)
|
|
11
|
+
* pi — ~/.pi/agent/sessions/<cwd-slug>/<ts>_<uuid>.jsonl
|
|
8
12
|
* daemon — agentproto's own capture, `~/.agentproto/sessions/<id>/events.jsonl`
|
|
9
13
|
* (written by transcript-writer.ts at the same point `projectEvent`
|
|
10
14
|
* flattens events into the ANSI ring buffer, ahead of that flattening).
|
|
@@ -48,6 +52,12 @@ interface ExportedSessionMeta {
|
|
|
48
52
|
};
|
|
49
53
|
costUsd?: number;
|
|
50
54
|
source?: string;
|
|
55
|
+
/** Provenance carried over from the session descriptor's own
|
|
56
|
+
* `origin`/`callerSessionId` (see sessions.ts) — distinct from `source`,
|
|
57
|
+
* which names the EXPORTER that produced this payload (e.g.
|
|
58
|
+
* "daemon-events", "codex"), not who invoked the underlying session. */
|
|
59
|
+
origin?: string;
|
|
60
|
+
callerSessionId?: string;
|
|
51
61
|
}
|
|
52
62
|
interface ExportedSession {
|
|
53
63
|
meta: ExportedSessionMeta;
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { CatalogProvider } from '@agentproto/model-catalog';
|
|
2
|
+
import { C as ContextContinuityPolicy } from './context-continuity-B9n0t0v-.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pure resolver for `~/.agentproto/config.json`'s `defaults` block —
|
|
6
|
+
* computes the effective `skills` + `options` + `auth` for an `agent_start`
|
|
7
|
+
* spawn before adapter-specific normalization. No fs, no adapter I/O, so
|
|
8
|
+
* it's unit-testable in isolation from `session-spawn.ts` (which owns the
|
|
9
|
+
* fs read + the adapter-manifest lookup).
|
|
10
|
+
*
|
|
11
|
+
* Precedence (lowest → highest): global `defaults` < `defaults.adapters.
|
|
12
|
+
* <slug>` < the explicit `agent_start` call.
|
|
13
|
+
* - `options`: shallow-merged maps, later (higher-precedence) keys win.
|
|
14
|
+
* - `skills`: global ∪ per-adapter when the caller didn't pass `skills`
|
|
15
|
+
* at all; an explicit `skills` (even `[]`) REPLACES the union rather
|
|
16
|
+
* than merging into it — a deliberate exact set, mirroring how an
|
|
17
|
+
* explicit `mcpServers: []` opts out of the hermes default in
|
|
18
|
+
* `session-spawn.ts`.
|
|
19
|
+
* - `auth`: surfaces the RAW billing-auth material (requested mode, both
|
|
20
|
+
* candidate credentials, per-spawn provider pin, and the `explicit`
|
|
21
|
+
* signal) so the descriptor-aware resolver ({@link resolveAuthSpec},
|
|
22
|
+
* which also needs the adapter's provider/subscription descriptor +
|
|
23
|
+
* providers.json) can decide the final mode, env var, scrub set, and
|
|
24
|
+
* credential source. Credentials are named in config or the provider
|
|
25
|
+
* store — never read from the ambient shell env.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Deterministic billing-auth config for one adapter slug (today, only
|
|
30
|
+
* claude-code interprets it — see `AgentCliStartOptions.auth` in
|
|
31
|
+
* `@agentproto/driver-agent-cli`). EXPLICIT credential selection, not
|
|
32
|
+
* scrub-by-absence: `token`/`apiKey` are the actual secret values, named
|
|
33
|
+
* here (or supplied per-spawn) rather than inherited from the launching
|
|
34
|
+
* shell. Never logged; only a fingerprint (see {@link credentialFingerprint})
|
|
35
|
+
* is ever surfaced back to a caller.
|
|
36
|
+
*/
|
|
37
|
+
interface DefaultsAdapterAuthConfig {
|
|
38
|
+
/** `"subscription"` or `"api-key"`. Omitted ⇒ the resolver picks by
|
|
39
|
+
* ordered preference (subscription first for adapters that support it —
|
|
40
|
+
* see {@link resolveAuthSpec}), never a hardcoded default. */
|
|
41
|
+
mode?: "subscription" | "api-key";
|
|
42
|
+
/** The subscription bearer token for `"subscription"` mode — minted via
|
|
43
|
+
* `claude setup-token` (bills the Max/Pro subscription, not API credits),
|
|
44
|
+
* SET to the adapter's `authSubscription.setEnv`. */
|
|
45
|
+
token?: string;
|
|
46
|
+
/** Opt-in SELF-REFRESHING subscription source (Mode 3). When set to
|
|
47
|
+
* `"claude-code-oauth"` and no static `token` is supplied per-spawn, the
|
|
48
|
+
* subscription bearer is read FRESH on every spawn from the local Claude
|
|
49
|
+
* Code login (macOS Keychain `Claude Code-credentials` → jsonPath
|
|
50
|
+
* `claudeAiOauth.accessToken`, falling back to `~/.claude/.credentials.json`)
|
|
51
|
+
* via the `claude-code-oauth` provision recipe — because Claude Code keeps
|
|
52
|
+
* that item refreshed, agentproto gets a fresh token each spawn. Absent ⇒
|
|
53
|
+
* today's static-bearer behavior (Mode 2). Only `"claude-code-oauth"` is
|
|
54
|
+
* understood; any other value fails LOUD (never a silent fallthrough). See
|
|
55
|
+
* {@link resolveSubscriptionCredential} for the precedence with `token`. */
|
|
56
|
+
source?: string;
|
|
57
|
+
/** Explicit API key for `"api-key"` mode, SET to `providerEnvVar(provider)`.
|
|
58
|
+
* Wins over the `providers.json` store key for the same provider. */
|
|
59
|
+
apiKey?: string;
|
|
60
|
+
/** Per-spawn provider PIN — overrides the adapter's fixed provider and the
|
|
61
|
+
* model-derived provider (the sharp edge for by-model routers whose config
|
|
62
|
+
* routes a catalog-"anthropic" model elsewhere). A `CatalogProvider` id. */
|
|
63
|
+
provider?: CatalogProvider;
|
|
64
|
+
}
|
|
65
|
+
interface DefaultsAdapterConfig {
|
|
66
|
+
skills?: string[];
|
|
67
|
+
options?: Record<string, boolean | number | string>;
|
|
68
|
+
auth?: DefaultsAdapterAuthConfig;
|
|
69
|
+
contextContinuity?: ContextContinuityPolicy;
|
|
70
|
+
}
|
|
71
|
+
/** Shape of `config.json`'s top-level `defaults` block. */
|
|
72
|
+
interface SpawnDefaultsConfig {
|
|
73
|
+
skills?: string[];
|
|
74
|
+
options?: Record<string, boolean | number | string>;
|
|
75
|
+
adapters?: Record<string, DefaultsAdapterConfig>;
|
|
76
|
+
contextContinuity?: ContextContinuityPolicy;
|
|
77
|
+
/** Depth cutoff for the role-derived default (see `resolveRole` in
|
|
78
|
+
* `role.ts`) applied when an `agent_start` call omits `role`:
|
|
79
|
+
* `depth < cutoff` → supervisor, `depth >= cutoff` → executor.
|
|
80
|
+
* Default 1 (root spawns keep today's unrestricted behaviour; any
|
|
81
|
+
* spawn made THROUGH an orchestrator defaults to executor). Tune
|
|
82
|
+
* this up (e.g. to a large number) to restore the old permissive
|
|
83
|
+
* behaviour for existing deep spawns wholesale. */
|
|
84
|
+
defaultRoleDepthCutoff?: number;
|
|
85
|
+
/** Trust-boundary cap on pack-carried roles (see `role-registry.ts`'s
|
|
86
|
+
* `loadRoleRegistry`): a role pack whose `toolPolicy.delegation` is
|
|
87
|
+
* `"allow"` at a level ABOVE this cap has it forced to `"deny"` —
|
|
88
|
+
* the pack can still declare the intent, the daemon just refuses to
|
|
89
|
+
* grant it. Lets an operator install third-party role packs without
|
|
90
|
+
* trusting every one of them to self-grant delegation. Undefined
|
|
91
|
+
* (default) ⇒ no cap, any pack-declared level may carry
|
|
92
|
+
* `delegation: "allow"` (back-compat: #214 had no such knob). */
|
|
93
|
+
maxGrantableDelegation?: number;
|
|
94
|
+
/** Default per-session Langfuse tracing opt-in when an `agent_start` call
|
|
95
|
+
* omits `trace`. Default false — sessions trace only when they opt in or
|
|
96
|
+
* this is on. See `filterSessionObserver` / `SpawnAgentInput.trace`. */
|
|
97
|
+
langfuseTracing?: boolean;
|
|
98
|
+
/** Redactor slug applied to traced session content before it's sent to
|
|
99
|
+
* Langfuse (see `@agentproto/redaction`'s registry). Default "secrets"
|
|
100
|
+
* (deny-list by key + value-scan for secret shapes). */
|
|
101
|
+
traceRedactor?: string;
|
|
102
|
+
}
|
|
103
|
+
interface ResolveSpawnDefaultsInput {
|
|
104
|
+
/** Explicit-call `skills`. Undefined ⇒ caller expressed no preference,
|
|
105
|
+
* fall through to the config union. Provided ⇒ replaces it outright. */
|
|
106
|
+
skills?: string[];
|
|
107
|
+
/** Explicit-call AIP-45 `options` map — wins per-key over both the
|
|
108
|
+
* global and per-adapter config defaults. */
|
|
109
|
+
options?: Record<string, boolean | number | string>;
|
|
110
|
+
/** Explicit-call `agent_start.auth` override. `mode` wins over
|
|
111
|
+
* `defaults.adapters.<slug>.auth.mode`; the credential field matching the
|
|
112
|
+
* RESOLVED mode wins over the matching config field. Undefined ⇒ falls
|
|
113
|
+
* through entirely to the per-adapter config default. */
|
|
114
|
+
auth?: DefaultsAdapterAuthConfig;
|
|
115
|
+
/** Explicit-call context-continuity policy — wins over global and
|
|
116
|
+
* per-adapter config defaults. */
|
|
117
|
+
contextContinuity?: ContextContinuityPolicy;
|
|
118
|
+
}
|
|
119
|
+
interface ResolvedSpawnDefaults {
|
|
120
|
+
skills: string[];
|
|
121
|
+
options: Record<string, boolean | number | string>;
|
|
122
|
+
/** RAW billing-auth material (config precedence applied) — fed to the
|
|
123
|
+
* descriptor-aware {@link resolveAuthSpec}, which owns the final mode /
|
|
124
|
+
* env / scrub / credential-source decision. Both candidate credentials
|
|
125
|
+
* are surfaced (NOT collapsed to one), since the ordered-mode selection
|
|
126
|
+
* needs to know which are available before it picks the mode. */
|
|
127
|
+
auth: ResolvedSpawnAuthMaterial;
|
|
128
|
+
contextContinuity?: ContextContinuityPolicy;
|
|
129
|
+
}
|
|
130
|
+
interface ResolvedSpawnAuthMaterial {
|
|
131
|
+
/** Operator-requested mode (per-spawn > per-adapter config), or undefined
|
|
132
|
+
* ⇒ let the resolver pick by ordered preference. */
|
|
133
|
+
requestedMode?: "subscription" | "api-key";
|
|
134
|
+
/** True when the operator explicitly configured `auth` (per-spawn OR in
|
|
135
|
+
* `defaults.adapters.<slug>.auth`). The ONLY way to tell "set mode, no
|
|
136
|
+
* key" (fail-fast) from "set nothing" (ambient) — both give no credential.
|
|
137
|
+
* DECISION 5. */
|
|
138
|
+
explicit: boolean;
|
|
139
|
+
/** Subscription bearer token (per-spawn > config), if configured. STATIC
|
|
140
|
+
* material only — the self-refreshing {@link subscriptionSource} is resolved
|
|
141
|
+
* separately (and impurely) by the caller. */
|
|
142
|
+
subscriptionCredential?: string;
|
|
143
|
+
/** Opt-in self-refreshing subscription source (per-spawn > config), if
|
|
144
|
+
* configured — e.g. `"claude-code-oauth"`. Surfaced RAW; the impure caller
|
|
145
|
+
* resolves it to a fresh token via {@link resolveSubscriptionCredential}. */
|
|
146
|
+
subscriptionSource?: string;
|
|
147
|
+
/** Explicit API key (per-spawn > config), if configured — distinct from the
|
|
148
|
+
* providers.json store key the resolver fetches separately. */
|
|
149
|
+
apiKeyCredential?: string;
|
|
150
|
+
/** Per-spawn provider pin, if given. */
|
|
151
|
+
provider?: CatalogProvider;
|
|
152
|
+
}
|
|
153
|
+
declare function resolveSpawnDefaults(defaults: SpawnDefaultsConfig | undefined, adapterSlug: string, input: ResolveSpawnDefaultsInput): ResolvedSpawnDefaults;
|
|
154
|
+
/**
|
|
155
|
+
* Derive a SAFE, non-secret fingerprint for a resolved auth credential —
|
|
156
|
+
* NEVER the raw value — for recording on the session descriptor / surfacing
|
|
157
|
+
* in `agentproto sessions --watch` and `agent_sessions_list` (the
|
|
158
|
+
* "verifiability" requirement: answer "what was used" without exposing the
|
|
159
|
+
* secret). Format: `<mode> · <shape-prefix>…<last4>` when the shape is known
|
|
160
|
+
* (e.g. `subscription · sk-ant-oat…3f9c`), else `<mode> · …<last4>`.
|
|
161
|
+
*
|
|
162
|
+
* The shape marker is matched from a PUBLIC key-prefix table (longest-match),
|
|
163
|
+
* not derived from `mode` — see {@link CREDENTIAL_FINGERPRINT_PREFIXES}. Only
|
|
164
|
+
* the matched prefix + the last 4 characters are ever surfaced, never the
|
|
165
|
+
* middle (mirrors GitHub's `ghp_…abcd` style).
|
|
166
|
+
*/
|
|
167
|
+
declare function credentialFingerprint(mode: "subscription" | "api-key", credential: string): string;
|
|
168
|
+
/**
|
|
169
|
+
* The adapter's billing-auth capability, projected from its AIP-45 manifest
|
|
170
|
+
* (`provider` / `authEnforce` / `authSubscription`) by the host resolver. The
|
|
171
|
+
* runtime reads THIS, never the manifest directly — keeping the LLM-catalog
|
|
172
|
+
* coupling in the runtime and the driver mechanical.
|
|
173
|
+
*/
|
|
174
|
+
interface AdapterAuthDescriptor {
|
|
175
|
+
/** FIXED provider for a single-provider adapter; omitted for by-model
|
|
176
|
+
* routers (provider then derives from the requested model). */
|
|
177
|
+
provider?: CatalogProvider;
|
|
178
|
+
/** Enforcement policy — `"always"` engages every spawn (claude-code's
|
|
179
|
+
* #312 fail-fast); `"when-configured"` (default) only when `explicit`. */
|
|
180
|
+
authEnforce?: "always" | "when-configured";
|
|
181
|
+
/** Subscription (OAuth/bearer) support. Presence ⇒ the adapter supports
|
|
182
|
+
* `"subscription"` mode. Mirrors the driver's `AgentCliAuthSubscription`.
|
|
183
|
+
* `external: true` (codex/gemini) ⇒ file-based: the CLI reads its own
|
|
184
|
+
* local-login file, so `setEnv` is absent and the runtime injects NO
|
|
185
|
+
* bearer — it only scrubs the conflicting api-key vars. */
|
|
186
|
+
authSubscription?: {
|
|
187
|
+
setEnv?: string;
|
|
188
|
+
external?: boolean;
|
|
189
|
+
conflictEnv?: string[];
|
|
190
|
+
unsetEnvAdd?: string[];
|
|
191
|
+
};
|
|
192
|
+
/** True when the adapter's api-key auth is derived from the requested
|
|
193
|
+
* model rather than a fixed provider (e.g. `pi`, `opencode`). When set,
|
|
194
|
+
* the adapter supports `"api-key"` on the model-derived direct endpoint,
|
|
195
|
+
* and `spawnEligibilityManifest` includes it for direct routes. */
|
|
196
|
+
modelDerivedApiKey?: boolean;
|
|
197
|
+
/** How THIS adapter receives a GATEWAY-routed bearer credential — see
|
|
198
|
+
* `AgentCliDefinition.gatewayAuth` (the manifest field this is projected
|
|
199
|
+
* from). Distinct from a gateway preset's `keyEnv` (the providers-store
|
|
200
|
+
* lookup key) and from `authSubscription.setEnv` (this adapter's own
|
|
201
|
+
* native bearer). When a gateway route resolves, `resolveAuthSpec` injects
|
|
202
|
+
* the credential into `gatewayAuth.setEnv` instead of the preset's
|
|
203
|
+
* `keyEnv` — e.g. claude-code/claude-sdk read `ANTHROPIC_AUTH_TOKEN`.
|
|
204
|
+
* Omit when the adapter reads the preset's own `keyEnv` directly (hermes). */
|
|
205
|
+
gatewayAuth?: {
|
|
206
|
+
setEnv: string;
|
|
207
|
+
};
|
|
208
|
+
/** Model id → the BILLING provider THIS adapter itself declares for it
|
|
209
|
+
* (`AgentCliModelEntry.provider`, projected from `models.allowed`) —
|
|
210
|
+
* authoritative for a model-derived-api-key adapter (`pi`, `opencode`),
|
|
211
|
+
* whose `provider` above is absent so the eligibility projection would
|
|
212
|
+
* otherwise fall through to the GLOBAL catalog's own (possibly different)
|
|
213
|
+
* routing for the same model id. Consulted by `spawnEligibilityManifest`
|
|
214
|
+
* BEFORE the catalog fallback; absent/no-match ⇒ catalog derivation,
|
|
215
|
+
* unchanged. */
|
|
216
|
+
modelProviders?: Readonly<Record<string, CatalogProvider>>;
|
|
217
|
+
}
|
|
218
|
+
/** The fully-resolved spec the driver applies mechanically. Structurally
|
|
219
|
+
* matches `@agentproto/driver-agent-cli`'s `ResolvedAuthSpec` (each package
|
|
220
|
+
* owns its own copy; the object flows across the boundary by shape). */
|
|
221
|
+
interface ResolvedAuthSpec {
|
|
222
|
+
mode: "subscription" | "api-key";
|
|
223
|
+
credential?: string;
|
|
224
|
+
setEnv: string;
|
|
225
|
+
unsetEnv: string[];
|
|
226
|
+
explicit: boolean;
|
|
227
|
+
enforce: "always" | "when-configured";
|
|
228
|
+
/** True when NEITHER subscription nor api-key had any credential
|
|
229
|
+
* available and `mode` above is therefore an arbitrary fallback pick —
|
|
230
|
+
* never true when `mode` came from an explicit request or an actually-
|
|
231
|
+
* available credential. Optional (undefined ⇒ false) so existing
|
|
232
|
+
* callers/fixtures that predate this field keep working unchanged.
|
|
233
|
+
* Exists so a fail-fast message can enumerate BOTH auth paths instead of
|
|
234
|
+
* presenting the fallback mode as though the user configured it — the
|
|
235
|
+
* root cause of a zero-credential user being told to buy a subscription
|
|
236
|
+
* they never asked for. */
|
|
237
|
+
neitherConfigured?: boolean;
|
|
238
|
+
/** File-based subscription (see `AdapterAuthDescriptor.authSubscription.
|
|
239
|
+
* external`): the CLI reads its OWN local-login file, so the driver injects
|
|
240
|
+
* NO credential — it only applies {@link unsetEnv} and does NOT fail-fast on
|
|
241
|
+
* a missing `credential`. Money-safe: no bearer ever reaches an env var. */
|
|
242
|
+
externalCredential?: boolean;
|
|
243
|
+
/** Non-authenticating hint: true when `providers.json` HAS a key for the
|
|
244
|
+
* resolved provider that is currently being ignored because auth isn't
|
|
245
|
+
* explicitly configured (no `defaults.adapters.<slug>.auth` block — the
|
|
246
|
+
* `explicit` gate above, PR #321). NEVER used to authenticate — set by
|
|
247
|
+
* the caller (session-spawn.ts) as a read-only peek, purely so the
|
|
248
|
+
* fail-fast message can say "you already have a key, it's just not
|
|
249
|
+
* wired in" instead of staying silent about it. `resolveAuthSpec` itself
|
|
250
|
+
* never sets this (it does no I/O). */
|
|
251
|
+
ignoredApiKeyInStore?: boolean;
|
|
252
|
+
/** When a gateway preset or custom route was matched, the `base_url` to
|
|
253
|
+
* inject into adapter options so the client hits the gateway endpoint. */
|
|
254
|
+
baseUrl?: string;
|
|
255
|
+
}
|
|
256
|
+
/** Where the resolved credential came from — the observable billing axis
|
|
257
|
+
* (DECISION 10②), never inferred. `"claude-code-oauth"` is the Mode-3
|
|
258
|
+
* self-refreshing source: the subscription bearer was read fresh from the
|
|
259
|
+
* local Claude Code login via the `claude-code-oauth` provision recipe.
|
|
260
|
+
* `"cli-local-login"` is the file-based (external) subscription: the CLI
|
|
261
|
+
* (codex/gemini) reads its OWN local-login file — the runtime injected no
|
|
262
|
+
* bearer, it only verified the login is present and scrubbed api-key vars. */
|
|
263
|
+
type CredentialSource = "explicit-config" | "providers-store" | "claude-code-oauth" | "cli-local-login" | "none";
|
|
264
|
+
/**
|
|
265
|
+
* The OBSERVABLE echo (DECISION 9③ / 10②) — recorded on the session
|
|
266
|
+
* descriptor so a verifier checks the RESOLUTION, never the model's
|
|
267
|
+
* self-report. Never carries the raw credential (only its fingerprint).
|
|
268
|
+
*/
|
|
269
|
+
interface AuthEcho {
|
|
270
|
+
/** Billing endpoint / provider recorded for observability. Kept as `string`
|
|
271
|
+
* (not the catalog enum) so gateway preset ids (e.g. "moonshot",
|
|
272
|
+
* "openai-direct") that are not `CatalogProvider` values can still be
|
|
273
|
+
* echoed on the session descriptor. */
|
|
274
|
+
provider: string;
|
|
275
|
+
authMode: "subscription" | "api-key";
|
|
276
|
+
credentialSource: CredentialSource;
|
|
277
|
+
setEnv: string;
|
|
278
|
+
fingerprint?: string;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Thrown when the operator requested a billing mode the adapter can't serve —
|
|
282
|
+
* today only `"subscription"` on an adapter with no `authSubscription`. A
|
|
283
|
+
* LOUD, distinct failure (DECISION 4②), never a silent downgrade to api-key.
|
|
284
|
+
*/
|
|
285
|
+
declare class AuthResolutionError extends Error {
|
|
286
|
+
readonly code = "unsupported_auth_mode";
|
|
287
|
+
constructor(message: string);
|
|
288
|
+
}
|
|
289
|
+
interface ResolveAuthSpecInput {
|
|
290
|
+
descriptor: AdapterAuthDescriptor;
|
|
291
|
+
/** `input.model ?? adapter default model` — for model-derived provider. */
|
|
292
|
+
model?: string;
|
|
293
|
+
/** Per-spawn provider pin (`input.auth.provider`). */
|
|
294
|
+
requestedProvider?: CatalogProvider;
|
|
295
|
+
/** Operator-requested mode; undefined ⇒ ordered preference. */
|
|
296
|
+
requestedMode?: "subscription" | "api-key";
|
|
297
|
+
/** Operator explicitly configured `auth` (DECISION 5). */
|
|
298
|
+
explicit: boolean;
|
|
299
|
+
/** Subscription bearer credential, if configured (or resolved fresh by the
|
|
300
|
+
* caller from a self-refreshing source). */
|
|
301
|
+
subscriptionCredential?: string;
|
|
302
|
+
/** Observable ORIGIN label for `subscriptionCredential`, when it did not come
|
|
303
|
+
* from a plain static config token — today only `"claude-code-oauth"` (the
|
|
304
|
+
* caller resolved it fresh from the local Claude Code login). Purely a label
|
|
305
|
+
* for the echo; NEVER affects mode/credential selection. Omitted ⇒ the
|
|
306
|
+
* subscription credential (if any) is treated as `"explicit-config"`. */
|
|
307
|
+
subscriptionCredentialSource?: CredentialSource;
|
|
308
|
+
/** File-based subscription only: the caller (impure) verified the CLI's OWN
|
|
309
|
+
* local-login file is present (e.g. `~/.codex/auth.json` has a subscription
|
|
310
|
+
* token) and fails LOUD before reaching here if not. Makes an `external`
|
|
311
|
+
* `authSubscription` count as an available subscription for ordered-mode
|
|
312
|
+
* selection — the login file IS the credential, even though the runtime
|
|
313
|
+
* injects no bearer. Ignored for a non-external (bearer) authSubscription. */
|
|
314
|
+
externalSubscriptionVerified?: boolean;
|
|
315
|
+
/** Explicit api-key credential from config, if configured. */
|
|
316
|
+
apiKeyConfigCredential?: string;
|
|
317
|
+
/** api-key credential from `providers.json` (fetched by the caller). */
|
|
318
|
+
apiKeyStoreCredential?: string;
|
|
319
|
+
/** Explicit gateway route from `SessionConfig.route.gateway`. When this
|
|
320
|
+
* matches a `ProviderPreset` or a registered custom route, the route's
|
|
321
|
+
* `baseUrl`/`keyEnv`/`scrubEnv` drive resolution instead of the model-
|
|
322
|
+
* derived or fixed provider. */
|
|
323
|
+
routeGateway?: string;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* THE billing-auth resolver (DECISIONS 4, 6, 9, 10). Pure: given the adapter
|
|
327
|
+
* descriptor + raw config material + (caller-fetched) store key, it decides
|
|
328
|
+
* the provider, the mode (ordered — subscription over api-key when a
|
|
329
|
+
* subscription credential is present; a requested-but-unsupported mode throws
|
|
330
|
+
* `unsupported_auth_mode`), the env var to SET, the derived SCRUB set, and the
|
|
331
|
+
* credential + its source. Returns the driver `spec` + the observable `echo`,
|
|
332
|
+
* or `undefined` when no provider resolves (⇒ ambient, no injection — never
|
|
333
|
+
* guess). NEVER falls back to a default provider/model. Fail-loud on a
|
|
334
|
+
* configured-but-missing credential is deferred to the driver's mechanical
|
|
335
|
+
* apply (it engages then throws `missing_auth_credential`), so the `explicit`
|
|
336
|
+
* / `enforce` signals are carried through on the spec.
|
|
337
|
+
*/
|
|
338
|
+
declare function resolveAuthSpec(input: ResolveAuthSpecInput): {
|
|
339
|
+
spec: ResolvedAuthSpec;
|
|
340
|
+
echo: AuthEcho;
|
|
341
|
+
} | undefined;
|
|
342
|
+
/** The only `auth.source` value understood today (Mode 3): read the
|
|
343
|
+
* subscription bearer fresh from the local Claude Code login via the
|
|
344
|
+
* `claude-code-oauth` provision recipe. */
|
|
345
|
+
declare const CLAUDE_CODE_OAUTH_SOURCE = "claude-code-oauth";
|
|
346
|
+
/**
|
|
347
|
+
* Raised when `auth.source` is configured but cannot yield a credential — an
|
|
348
|
+
* unknown source value (`unsupported_auth_source`) or the recipe resolving to
|
|
349
|
+
* nothing / not-logged-in (`auth_source_unresolved`). A LOUD, actionable
|
|
350
|
+
* failure surfaced as a spawn error, NEVER a silent fallthrough to a static or
|
|
351
|
+
* ambient credential (mirrors {@link AuthResolutionError}'s discipline).
|
|
352
|
+
*/
|
|
353
|
+
declare class SubscriptionSourceError extends Error {
|
|
354
|
+
readonly code: "unsupported_auth_source" | "auth_source_unresolved";
|
|
355
|
+
constructor(code: "unsupported_auth_source" | "auth_source_unresolved", message: string);
|
|
356
|
+
}
|
|
357
|
+
interface ResolveSubscriptionCredentialInput {
|
|
358
|
+
/** Per-spawn explicit static token (`input.auth.token`) — wins over source. */
|
|
359
|
+
explicitToken?: string;
|
|
360
|
+
/** Effective opt-in source (`input.auth.source ?? config auth.source`). */
|
|
361
|
+
source?: string;
|
|
362
|
+
/** Config static token (`defaults.adapters.<slug>.auth.token`) — the
|
|
363
|
+
* lowest-precedence fallback, used only when NEITHER an explicit per-spawn
|
|
364
|
+
* token nor a source applies. */
|
|
365
|
+
fallbackStaticToken?: string;
|
|
366
|
+
}
|
|
367
|
+
interface SubscriptionCredentialResolution {
|
|
368
|
+
/** Resolved subscription bearer, or undefined ⇒ nothing configured (the
|
|
369
|
+
* driver's fail-fast `missing_auth_credential` still owns that case). */
|
|
370
|
+
credential?: string;
|
|
371
|
+
/** Observable origin of {@link credential} for the echo. */
|
|
372
|
+
source?: CredentialSource;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Resolve the subscription (oauth-bearer) credential + its observable origin,
|
|
376
|
+
* placing the self-refreshing `source` (Mode 3) BETWEEN the two static tokens
|
|
377
|
+
* (SPEC §2):
|
|
378
|
+
* a. explicit per-spawn `input.auth.token` (static) — wins over everything.
|
|
379
|
+
* b. else `source: "claude-code-oauth"` → resolve FRESH via the injected
|
|
380
|
+
* recipe resolver (Keychain / credentials file) ⇒ origin
|
|
381
|
+
* `"claude-code-oauth"`.
|
|
382
|
+
* c. else config.json static `auth.token` ⇒ origin `"explicit-config"`.
|
|
383
|
+
* d. else nothing (⇒ driver fail-fast `missing_auth_credential`, unchanged).
|
|
384
|
+
*
|
|
385
|
+
* The recipe I/O is INJECTED (`resolveSourceToken`) so this stays pure and
|
|
386
|
+
* unit-testable without touching the real Keychain — the impure resolver lives
|
|
387
|
+
* in `claude-code-oauth-source.ts`. Fails LOUD ({@link SubscriptionSourceError})
|
|
388
|
+
* on an unknown source or an empty recipe; never a silent fallthrough. Only
|
|
389
|
+
* touches the subscription path — api-key mode never reaches here.
|
|
390
|
+
*/
|
|
391
|
+
declare function resolveSubscriptionCredential(input: ResolveSubscriptionCredentialInput, resolveSourceToken: (source: string) => Promise<string>): Promise<SubscriptionCredentialResolution>;
|
|
392
|
+
/** Manifest-declared AIP-45 option id + type, the minimum an adapter
|
|
393
|
+
* resolver needs to expose for `normalizeSkillsOption` below. Mirrors
|
|
394
|
+
* `AgentCliOption`'s `id`/`type` fields without importing
|
|
395
|
+
* `@agentproto/driver-agent-cli` into the runtime package. */
|
|
396
|
+
interface DeclaredAdapterOption {
|
|
397
|
+
id: string;
|
|
398
|
+
type: "boolean" | "integer" | "string" | "enum";
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Fold the resolved `skills` list into `options.skills` using whatever
|
|
402
|
+
* shape the adapter's manifest declares for that option id (today, only
|
|
403
|
+
* `type: "string"` exists for a skills-shaped option — e.g. hermes'
|
|
404
|
+
* comma-joined `--skills a,b`). Adapters with no declared `skills` option
|
|
405
|
+
* (e.g. claude-code, which auto-discovers from `~/.claude/skills`) are a
|
|
406
|
+
* documented no-op — the effective skills list has nowhere to go, so it's
|
|
407
|
+
* dropped rather than guessing a flag the manifest didn't declare.
|
|
408
|
+
*
|
|
409
|
+
* An `options.skills` already present (from config defaults or the
|
|
410
|
+
* explicit call) is respected as-is and never overwritten here.
|
|
411
|
+
*/
|
|
412
|
+
declare function normalizeSkillsOption(skills: string[], options: Record<string, boolean | number | string>, declaredOptions: readonly DeclaredAdapterOption[] | undefined): Record<string, boolean | number | string>;
|
|
413
|
+
|
|
414
|
+
export { type AdapterAuthDescriptor as A, CLAUDE_CODE_OAUTH_SOURCE as C, type DeclaredAdapterOption as D, type ResolvedAuthSpec as R, type SpawnDefaultsConfig as S, type AuthEcho as a, AuthResolutionError as b, type CredentialSource as c, type DefaultsAdapterAuthConfig as d, type DefaultsAdapterConfig as e, type ResolveSubscriptionCredentialInput as f, type ResolvedSpawnAuthMaterial as g, type ResolvedSpawnDefaults as h, type SubscriptionCredentialResolution as i, SubscriptionSourceError as j, credentialFingerprint as k, resolveSpawnDefaults as l, resolveSubscriptionCredential as m, normalizeSkillsOption as n, resolveAuthSpec as r };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow public ingress proxy for Telegram (and other) bot webhooks.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY: the daemon's port (default 18790) exposes the entire control
|
|
5
|
+
* plane — `session_list`, `agent_start`, `agent_kill`, etc. A bare public
|
|
6
|
+
* tunnel to that port would expose full daemon control to anyone who finds
|
|
7
|
+
* the URL. This proxy listens on its own port and forwards ONLY
|
|
8
|
+
* `POST /inbound/*` to the daemon. Every other path or method is rejected
|
|
9
|
+
* with 404/405.
|
|
10
|
+
*/
|
|
11
|
+
interface TelegramProxyOptions {
|
|
12
|
+
targetBaseUrl: string;
|
|
13
|
+
listenPort: number;
|
|
14
|
+
log?: (msg: string) => void;
|
|
15
|
+
}
|
|
16
|
+
interface TelegramProxy {
|
|
17
|
+
start(): Promise<{
|
|
18
|
+
url: string;
|
|
19
|
+
port: number;
|
|
20
|
+
}>;
|
|
21
|
+
stop(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a narrow reverse-proxy that forwards ONLY `POST /inbound/*`
|
|
25
|
+
* to a target base URL. Every other path or method is rejected.
|
|
26
|
+
*
|
|
27
|
+
* SECURITY: the URL path is decoded, normalized (resolving `.` and `..`),
|
|
28
|
+
* and then checked. Only paths whose normalized form literally begins with
|
|
29
|
+
* `/inbound/` are forwarded. Path-traversal attempts such as
|
|
30
|
+
* `/inbound/../sessions/foo` decode and normalize to `/sessions/foo`, which
|
|
31
|
+
* does not start with `/inbound/` and is rejected with 404.
|
|
32
|
+
*/
|
|
33
|
+
declare function createTelegramProxy(opts: TelegramProxyOptions): TelegramProxy;
|
|
34
|
+
|
|
35
|
+
export { type TelegramProxy, type TelegramProxyOptions, createTelegramProxy };
|