@agentproto/apps 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents-overview/panel.mjs +39 -67
- package/dist/agents-overview/panel.mjs.map +1 -1
- package/dist/agents-overview.mjs +38 -68
- package/dist/agents-overview.mjs.map +1 -1
- package/dist/bin/sync.d.ts.map +1 -1
- package/dist/bin/sync.mjs +2374 -2
- package/dist/bin/sync.mjs.map +1 -1
- package/dist/bureau-sessions/panel.mjs +39 -67
- package/dist/bureau-sessions/panel.mjs.map +1 -1
- package/dist/bureau-sessions.mjs +38 -68
- package/dist/bureau-sessions.mjs.map +1 -1
- package/dist/config/fragment.d.ts +50 -0
- package/dist/config/fragment.d.ts.map +1 -0
- package/dist/config/index.d.ts +23 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/ui.d.ts +43 -0
- package/dist/config/ui.d.ts.map +1 -0
- package/dist/config.mjs +2373 -0
- package/dist/config.mjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +3200 -150
- package/dist/index.mjs.map +1 -1
- package/dist/live-session/logic.d.ts +2 -0
- package/dist/live-session/logic.d.ts.map +1 -1
- package/dist/live-session/panel.d.ts.map +1 -1
- package/dist/live-session/panel.mjs +52 -74
- package/dist/live-session/panel.mjs.map +1 -1
- package/dist/live-session.mjs +51 -75
- package/dist/live-session.mjs.map +1 -1
- package/dist/panel-bridge.d.ts +11 -6
- package/dist/panel-bridge.d.ts.map +1 -1
- package/dist/session-chat/blob-embed.d.ts +53 -0
- package/dist/session-chat/blob-embed.d.ts.map +1 -0
- package/dist/session-chat/index.d.ts +13 -0
- package/dist/session-chat/index.d.ts.map +1 -1
- package/dist/session-chat/panel.d.ts +38 -2
- package/dist/session-chat/panel.d.ts.map +1 -1
- package/dist/session-chat/panel.mjs +556 -6
- package/dist/session-chat/panel.mjs.map +1 -1
- package/dist/session-chat.mjs +591 -14
- package/dist/session-chat.mjs.map +1 -1
- package/dist/session-story/panel.mjs +39 -67
- package/dist/session-story/panel.mjs.map +1 -1
- package/dist/session-story.mjs +38 -68
- package/dist/session-story.mjs.map +1 -1
- package/dist/session-viewer/ui.d.ts.map +1 -1
- package/dist/session-viewer.mjs +10 -0
- package/dist/session-viewer.mjs.map +1 -1
- package/dist/sessions-panel/panel.d.ts.map +1 -1
- package/dist/sessions-panel/panel.mjs +40 -67
- package/dist/sessions-panel/panel.mjs.map +1 -1
- package/dist/sessions-panel.mjs +39 -68
- package/dist/sessions-panel.mjs.map +1 -1
- package/dist/work-board/panel.d.ts +1 -1
- package/dist/work-board/panel.d.ts.map +1 -1
- package/dist/work-board/panel.generated.d.ts +1 -1
- package/dist/work-board/panel.generated.d.ts.map +1 -1
- package/dist/work-board/panel.mjs +318 -62
- package/dist/work-board/panel.mjs.map +1 -1
- package/dist/work-board.mjs +318 -62
- package/dist/work-board.mjs.map +1 -1
- package/package.json +12 -4
- package/repo-maintenance/.agentproto/APP.md +22 -0
- package/repo-maintenance/.agentproto/agents/repo-maintenance-reviewer/AGENT.md +142 -0
- package/repo-maintenance/.agentproto/workflows/maintain/WORKFLOW.md +262 -0
- package/repo-maintenance/.agentproto/workflows/maintain/entry.mjs +672 -0
- package/repo-maintenance/README.md +79 -0
- package/repo-maintenance/routines/repo-maintenance-daily/ROUTINE.md +77 -0
- package/repo-maintenance/routines/repo-maintenance-weekly/ROUTINE.md +82 -0
package/dist/bin/sync.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/code-team/agents/implementer.ts","../../src/code-team/agents/reviewer.ts","../../src/code-team/agents/fixer.ts","../../src/code-team/workflows/deliver-change.ts","../../src/code-team/index.ts","../../src/content-team/agents/researcher.ts","../../src/content-team/agents/writer.ts","../../src/content-team/agents/editor.ts","../../src/content-team/agents/illustrator.ts","../../src/content-team/workflows/produce-content.ts","../../src/content-team/workflows/produce-cover.ts","../../src/content-team/index.ts","../../src/mail-triage/agents/triager.ts","../../src/mail-triage/workflows/triage-inbox.ts","../../src/mail-triage/ui.ts","../../src/mail-triage/index.ts","../../src/media-viewer/agents/cataloger.ts","../../src/media-viewer/workflows/scan-media.ts","../../src/media-viewer/ui.ts","../../src/media-viewer/index.ts","../../src/ops-panel/agents/manager.ts","../../src/ops-panel/agents/watchdog.ts","../../src/ops-panel/ui.ts","../../src/ops-panel/index.ts","../../src/session-viewer/agents/narrator.ts","../../src/session-viewer/workflows/narrate-session.ts","../../src/session-viewer/ui.ts","../../src/session-viewer/index.ts","../../src/bin/sync.ts"],"names":["defineAgent","defineWorkflow","defineApp","APP_ID"],"mappings":"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A;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","file":"sync.mjs","sourcesContent":["import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Writes the requested change. */\nexport const implementer: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/implementer\",\n description: \"Implements the requested code change.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Change only what the task asks\", \"Match the surrounding style\"],\n tools: [\"list_dir\", \"read_file\", \"write_file\", \"edit_file\", \"run_command\"],\n workflows: [{ ref: \"deliver-change\" }],\n }),\n body:\n \"You implement the requested change. Read the task and the surrounding code \" +\n \"first, make the smallest correct change that satisfies it, then run the \" +\n \"tests. Do not touch anything the task didn't ask for.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Reviews the diff; reports only. */\nexport const reviewer: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/reviewer\",\n description: \"Reviews the diff and reports findings.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Report findings only\", \"Never edit files\", \"Never run gh pr merge\"],\n // Read-only + run_command (to inspect the diff / run tests). No write/edit\n // tools — the \"never edit files\" boundary is enforced by capability too.\n tools: [\"list_dir\", \"read_file\", \"run_command\"],\n workflows: [{ ref: \"deliver-change\" }],\n }),\n body:\n \"You are a rigorous reviewer. Read the diff, and report each real issue as \" +\n \"`file:line — why`. Change nothing; you only report.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Applies the reviewer's findings, precisely and minimally. */\nexport const fixer: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/fixer\",\n description: \"Applies the reviewer's findings, precisely and minimally.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Apply only the listed findings\", \"No unrelated refactors\"],\n tools: [\"list_dir\", \"read_file\", \"write_file\", \"edit_file\", \"run_command\"],\n workflows: [{ ref: \"deliver-change\" }],\n }),\n body:\n \"You apply the reviewer's findings. Address each listed finding exactly, add \" +\n \"nothing extra, and re-run the tests when done.\",\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/**\n * The one workflow the code team delivers a change through. Every team agent\n * references it; the app-kit attachment invariant keeps that honest.\n *\n * Three declarative `kind:\"agent\"` steps — implementer → reviewer → fixer —\n * one per team agent, each `agent.ref` resolving (at compile time, against\n * this app's own install record) to that agent's emitted AGENT.md. This\n * replaces an earlier `ToolStep`-based draft (`apply_patch`/`read_diff`/\n * `run_tests`) that dispatched daemon-side, where those tool ids don't\n * exist — the daemon has no `apply_patch` tool, only live agent sessions\n * that can edit files. Agent steps are the adoption fix (WP-B4): each step\n * spawns the named team agent as a real session and lets it act.\n */\nexport const deliverChange = defineWorkflow({\n id: \"deliver-change\",\n name: \"Deliver a code change\",\n description: \"Implement the change, review the diff, apply fixes, verify.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"implement\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/implementer\" },\n prompt: \"Implement the requested change.\",\n },\n {\n id: \"review\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/reviewer\" },\n prompt:\n \"Review the diff the implementer just produced. Report every real issue as `file:line — why`.\",\n },\n {\n id: \"fix\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/fixer\" },\n prompt: \"Apply the reviewer's findings, then re-run the tests.\",\n },\n ],\n})\n","/**\n * `code-team` — a ready-made agentproto app: a small team that ships a code\n * change. Three agents (implementer → reviewer → fixer) bound to one delivery\n * workflow, declared with `@agentproto/app-kit`.\n *\n * Generic on purpose — `@agentproto/…` ids, no product dependency, no home\n * workspace. A host imports it and uses any subset of its agents/workflows:\n * in-process via `codeTeam.toMastraAgents(...)`, or on disk via\n * `codeTeam.emit(dir)`.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { implementer } from \"./agents/implementer.js\"\nimport { reviewer } from \"./agents/reviewer.js\"\nimport { fixer } from \"./agents/fixer.js\"\nimport { deliverChange } from \"./workflows/deliver-change.js\"\n\nexport const codeTeam: AppHandle = defineApp({\n id: \"@agentproto/code-team\",\n name: \"Code Team\",\n version: \"0.1.0\",\n description: \"Implement, review, and fix code changes.\",\n agents: [implementer, reviewer, fixer],\n workflows: [deliverChange],\n})\n\nexport { implementer, reviewer, fixer, deliverChange }\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Gathers sources for the piece. */\nexport const researcher: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/researcher\",\n description: \"Reads the provided sources and summarizes them for the writer.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Cite every claim\", \"Prefer primary sources\"],\n tools: [\"list_dir\", \"read_file\", \"run_command\"],\n workflows: [{ ref: \"produce-content\" }],\n }),\n body:\n \"You read the source material already in the workspace (list_dir, then \" +\n \"read_file the sources), extract the key facts, and hand the writer a tight \" +\n \"brief with a citation for every claim. Use run_command only to fetch a URL \" +\n \"already named in the task; do not invent sources.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Drafts the piece from the brief. */\nexport const writer: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/writer\",\n description: \"Drafts the piece from the researcher's brief.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Stay on the brief\", \"One idea per paragraph\"],\n tools: [\"list_dir\", \"read_file\", \"write_file\", \"edit_file\"],\n workflows: [{ ref: \"produce-content\" }],\n }),\n body:\n \"You draft the piece from the brief and its sources. Lead with the point, \" +\n \"keep one idea per paragraph, and don't invent facts the brief doesn't have.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Tightens the draft. */\nexport const editor: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/editor\",\n description: \"Edits the draft for voice, clarity, and length.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Preserve the writer's meaning\", \"Cut, don't pad\"],\n tools: [\"read_file\", \"edit_file\"],\n workflows: [{ ref: \"produce-content\" }],\n }),\n body:\n \"You edit the draft. Tighten the voice, cut fluff, fix clarity — without \" +\n \"changing what the writer meant. Leave it shorter than you found it.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Art-directs the cover illustration for each article. */\nexport const illustrator: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/illustrator\",\n description: \"Art-directs the cover illustration for each article.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"One cover per article\", \"Text-free prompts only\"],\n tools: [\"read_file\", \"write_file\", \"run_command\"],\n workflows: [{ ref: \"produce-cover\" }],\n }),\n body:\n \"You art-direct a single cover illustration for the article. \" +\n \"Read the article (read_file) to extract the one visual idea that best \" +\n \"represents the piece, then write the art direction (write_file) as a \" +\n \"clean, text-free image prompt. Keep the visual discipline tight: bold \" +\n \"flat shapes, limited risograph-style palettes, strong negative space, and \" +\n \"a single focal subject. Never include text, lettering, logos, watermarks, \" +\n \"or UI chrome in the prompt. Produce exactly one cover per article; if the \" +\n \"article calls for a literal diagram or chart, fall back to a precise \" +\n \"gpt-image-1 style description and treat the diagram itself as the image \" +\n \"subject. Use run_command only to inspect generated assets or move files \" +\n \"during the upload step.\",\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/**\n * The one workflow the content team produces a piece through. Every team\n * agent references it (app-kit attachment invariant).\n *\n * Three declarative `kind:\"agent\"` steps — researcher → writer → editor —\n * one per team agent, each `agent.ref` resolving (at compile time, against\n * this app's own install record) to that agent's emitted AGENT.md. This\n * replaces an earlier `ToolStep`-based draft (`read_file`/`write_file`/\n * `edit_file`/`run_command`) that dispatched daemon-side, where those tool\n * ids don't exist — they're agent workspace tools (adapter-side), never\n * daemon tool ids, so `app_install` rejected them. Agent steps are the\n * adoption fix: each step spawns the named team agent as a real session,\n * which uses its own workspace tools to read, draft, and edit.\n */\nexport const produceContent = defineWorkflow({\n id: \"produce-content\",\n name: \"Produce a piece of content\",\n description: \"Research the sources, draft the piece, then edit it.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"research\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/researcher\" },\n prompt:\n \"Read the source material already in the workspace and hand off a tight \" +\n \"brief with a citation for every claim.\",\n },\n {\n id: \"draft\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/writer\" },\n prompt: \"Draft the piece from the researcher's brief and its sources.\",\n },\n {\n id: \"edit\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/editor\" },\n prompt: \"Edit the draft for voice, clarity, and length, then finalize it.\",\n },\n ],\n})\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/**\n * The one workflow the illustrator uses to produce a cover.\n *\n * A single declarative `kind:\"agent\"` step — `agent.ref` resolving (at\n * compile time, against this app's own install record) to the illustrator's\n * emitted AGENT.md. This replaces an earlier `ToolStep`-based draft\n * (`content.cover-concept`/`content.cover-base-gen`/`content.cover-cleanup`/\n * `content.cover-upload`) naming tools that don't exist anywhere — no driver\n * implements them. The illustrator agent folds all four phases (concept,\n * base generation, cleanup, upload) into one prompt for a single session\n * turn, matching the tools it already has (`read_file`, `write_file`,\n * `run_command`).\n */\nexport const produceCover = defineWorkflow({\n id: \"produce-cover\",\n name: \"Produce a cover illustration\",\n description:\n \"Read the article, derive the cover concept, generate the base image, \" +\n \"clean it up, and upload the final cover.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"illustrate\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/illustrator\" },\n prompt:\n \"Produce the cover for this article in one pass: (1) read the article \" +\n \"and derive the single visual concept that best represents it, (2) \" +\n \"write the art direction as a clean, text-free image prompt and \" +\n \"generate the base image from it, (3) clean up the generated image \" +\n \"(crop, color, artifacts), then (4) upload the final cover to the \" +\n \"workspace's output location.\",\n },\n ],\n})\n","/**\n * `content-team` — a ready-made agentproto app: a small team that produces a\n * piece of content. Three agents (researcher → writer → editor) bound to one\n * production workflow, declared with `@agentproto/app-kit`.\n *\n * Generic on purpose — `@agentproto/…` ids, no product dependency. A host\n * imports it and uses any subset of its agents/workflows.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { researcher } from \"./agents/researcher.js\"\nimport { writer } from \"./agents/writer.js\"\nimport { editor } from \"./agents/editor.js\"\nimport { illustrator } from \"./agents/illustrator.js\"\nimport { produceContent } from \"./workflows/produce-content.js\"\nimport { produceCover } from \"./workflows/produce-cover.js\"\n\nexport const contentTeam: AppHandle = defineApp({\n id: \"@agentproto/content-team\",\n name: \"Content Team\",\n version: \"0.1.0\",\n description: \"Research, draft, edit, and illustrate content.\",\n agents: [researcher, writer, editor, illustrator],\n workflows: [produceContent, produceCover],\n})\n\nexport { researcher, writer, editor, illustrator, produceContent, produceCover }\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Triages your inbox — finds unread mail, categorizes, labels, archives. */\nexport const triager: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/triager\",\n description: \"Triages your inbox — finds unread mail, categorizes, labels, archives.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Always show the triage plan before applying\",\n \"Never delete — only archive, label, or mark read\",\n \"Ask before applying bulk actions on more than 20 messages\",\n ],\n tools: [\n \"mailbox_list\",\n \"mailbox_search\",\n \"mailbox_list_threads\",\n \"mailbox_get_thread\",\n \"mailbox_labels_list\",\n \"mailbox_label_create\",\n \"mailbox_triage_plan\",\n \"mailbox_triage_apply\",\n ],\n workflows: [{ ref: \"triage-inbox\" }],\n }),\n body:\n \"You start by calling mailbox_list to discover connected mailboxes. Pick one \" +\n \"with `capabilities.triage: true` (triage requires the gmail.modify scope). \" +\n \"Every subsequent mailbox tool requires the `mailbox` parameter (UUID or alias \" +\n \"from this list).\\n\" +\n \"\\n\" +\n \"Scan the mailbox for unread messages using mailbox_search with \" +\n 'query=\"is:unread\" and the mailbox from step 1. Categorize them ' +\n \"(urgent, needs-reply, newsletter, notification, spam). Create one triage plan \" +\n \"per bulk action with mailbox_triage_plan — its exact contract is \" +\n '`{ mailbox, criteria: { query } | { messageIds }, action: { type: \"markRead\"|\"archive\"|\"trash\"|\"label\", addLabelIds?, removeLabelIds? } }` ' +\n \"— e.g. label each category, archive newsletters+notifications, keep \" +\n \"urgent+needs-reply in inbox. Each plan returns a `plan_id`, a `count`, and a \" +\n \"`sample`; it expires after 15 minutes. Show the plan before applying. Execute \" +\n \"only after user confirmation, with mailbox_triage_apply \" +\n \"`{ plan_id, confirm: true }` — that is the sole mutation path.\",\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/** Single-agent workflow: triage the inbox. */\nexport const triageInbox = defineWorkflow({\n id: \"triage-inbox\",\n name: \"Triage Inbox\",\n description: \"Scan inbox, categorize unread messages, create and apply triage plan.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"triage\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/triager\" },\n prompt:\n \"Call mailbox_list to discover connected mailboxes, then scan the \" +\n \"selected mailbox for unread messages using mailbox_search with \" +\n 'query=\"is:unread\". Categorize them (urgent, needs-reply, newsletter, ' +\n \"notification, spam). Create a triage plan: label each category, archive \" +\n \"newsletters+notifications, keep urgent+needs-reply in inbox. \" +\n \"Show me the plan before applying.\",\n },\n ],\n})\n","/**\n * Mail Triage — a self-contained MCP App dashboard for `mail-triage`.\n *\n * Talks to the daemon only through the `window.McpApp.connect()` bridge\n * (never a direct `fetch`): every action round-trips through the app's own\n * `app_tool_call` gateway tool, `{ appId, tool, args }`. Mailbox access goes\n * through an imported agentpush MCP server (`imported:<alias>/<tool>` ids);\n * running the triager agent itself goes through the daemon's own\n * `app_run` / `app_status` / `agent_output` — a second path alongside the\n * deterministic plan/apply tools, so the panel can either drive the mailbox\n * tools directly or hand the whole job to the agent and watch it work.\n *\n * The agentpush alias is NOT hardcoded to one server: `app_tool_call`\n * enforces an exact-match allowlist (`ui.tools`), so every candidate alias\n * must be baked into `MAIL_TRIAGE_TOOLS` at emit time. The candidate list\n * comes from `MAIL_TRIAGE_MCP_ALIASES` (overridable via the\n * `MAIL_TRIAGE_MCP_ALIASES` env var, comma-separated, read when this module\n * is evaluated — i.e. at emit). At runtime the panel probes each candidate's\n * `mailbox_list` and activates the first alias that answers with mailboxes;\n * a selector appears in the toolbar when several respond.\n */\n\nconst ALIAS_TOOL_NAMES = [\n \"mailbox_list\",\n \"mailbox_search\",\n \"mailbox_triage_plan\",\n \"mailbox_triage_apply\",\n] as const\n\nconst DEFAULT_MCP_ALIASES = [\"agentpush-prod\", \"agentpush\"] as const\n\nfunction envAliases(): string[] | null {\n if (typeof process === \"undefined\" || !process.env) return null\n const raw = process.env[\"MAIL_TRIAGE_MCP_ALIASES\"]\n if (!raw) return null\n const list = raw\n .split(\",\")\n .map(s => s.trim())\n .filter(Boolean)\n return list.length > 0 ? list : null\n}\n\n/** Candidate imported-MCP aliases the dashboard may reach mailboxes through. */\nexport const MAIL_TRIAGE_MCP_ALIASES: readonly string[] = envAliases() ?? [...DEFAULT_MCP_ALIASES]\n\nexport const MAIL_TRIAGE_TOOLS: readonly string[] = [\n ...MAIL_TRIAGE_MCP_ALIASES.flatMap(alias => ALIAS_TOOL_NAMES.map(t => `imported:${alias}/${t}`)),\n \"app_run\",\n \"app_status\",\n \"agent_output\",\n \"app_list\",\n]\n\nconst APP_ID = \"@agentproto/mail-triage\"\nconst AGENT_ID = \"@agentproto/triager\"\nconst CATEGORIES = [\"urgent\", \"needs-reply\", \"newsletter\", \"notification\"]\n\nexport const MAIL_TRIAGE_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Mail Triage</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\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;flex-direction:column;height:100%;overflow-y:auto}\n#toolbar{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2);flex-shrink:0;flex-wrap:wrap}\n#toolbar .title{font-size:13px;font-weight:600;flex:1}\nselect,input[type=text]{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 8px;border-radius:4px;font-size:12px;font-family:inherit}\n.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}\n.abtn:hover{background:var(--border)}\n.abtn:disabled{opacity:.4;cursor:default}\n.abtn.primary{background:var(--blue);border-color:var(--blue);color:#04101c}\n.abtn.primary:hover{opacity:.9}\n#caps{padding:6px 14px;display:flex;gap:6px;align-items:center;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}\n.badge{font-size:11px;padding:2px 8px;border-radius:10px;border:1px solid var(--border);color:var(--text2)}\n.badge.on{color:var(--green);border-color:var(--green)}\n.badge.off{opacity:.5;text-decoration:line-through}\n#content{padding:14px;display:flex;flex-direction:column;gap:16px}\nsection{background:var(--bg2);border:1px solid var(--border);border-radius:6px;padding:12px}\nsection h3{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px;display:flex;justify-content:space-between;align-items:center}\n#summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px}\n.tile{background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:10px;text-align:center}\n.tile .count{font-size:22px;font-weight:700}\n.tile .label{font-size:11px;color:var(--text2);text-transform:capitalize}\n.tile.unread .count{color:var(--green)}\n.tile.urgent .count{color:var(--red)}\n.tile.needs-reply .count{color:var(--yellow)}\n.tile.newsletter .count{color:var(--blue)}\n.tile.notification .count{color:var(--purple)}\n#plan-list,#search-results,#log-events,#runs-list,#connect-hint-list{max-height:260px;overflow-y:auto;font-size:12px}\n.plan-item{display:flex;justify-content:space-between;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)}\n.plan-item .subj{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.plan-item .tag{color:var(--text2);font-size:11px;white-space:nowrap}\n.runs-item{display:flex;gap:10px;align-items:center;padding:6px 0;border-bottom:1px solid var(--border);cursor:pointer}\n.runs-item:hover{background:var(--bg3)}\n.runs-item .rid{font-family:Menlo,Monaco,monospace;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}\n.runs-item .st{font-size:11px;white-space:nowrap}\n.runs-item .st.running{color:var(--yellow)}\n.runs-item .st.ended{color:var(--green)}\n.runs-item .meta{color:var(--text2);font-size:11px;white-space:nowrap}\n#log-tail{font-family:Menlo,Monaco,monospace;white-space:pre-wrap;word-break:break-word;background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:8px;max-height:260px;overflow-y:auto}\n.searchbar{display:flex;gap:8px;margin-bottom:8px}\n.searchbar input{flex:1}\n.actions{display:flex;gap:8px;margin-top:10px}\n#status{padding:6px 14px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n.empty{color:var(--text2);padding:8px 0}\n.mini{font-size:10px;text-transform:none;letter-spacing:0;cursor:pointer;color:var(--blue);background:none;border:none;font-family:inherit}\n.caps-hint{font-size:11px;color:var(--yellow)}\n.snippet{color:var(--text2);font-size:11px;margin-top:2px}\n.mail-checkbox{accent-color:var(--blue);cursor:pointer;flex-shrink:0;margin-top:1px}\n.mail-open{font-size:10px;cursor:pointer;color:var(--blue);background:none;border:none;font-family:inherit;white-space:nowrap;padding:0;flex-shrink:0}\n.mail-open:hover{text-decoration:underline}\n#selection-bar{display:none;margin-bottom:8px;gap:8px;align-items:center}\n#selection-bar.show{display:flex}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"toolbar\">\n <span class=\"title\">Mail Triage</span>\n <select id=\"alias-select\" style=\"display:none\" title=\"MCP server\"></select>\n <select id=\"mailbox-select\"></select>\n <button class=\"abtn\" id=\"refresh-btn\">Refresh</button>\n </div>\n <div id=\"caps\" style=\"display:none\"></div>\n <div id=\"content\">\n <section id=\"connect-hint\" style=\"display:none\">\n <h3>Connect a mailbox</h3>\n <div id=\"connect-hint-list\"></div>\n <div class=\"empty\">Import/start an agentpush MCP server and connect a Gmail account in agentpush, then retry.</div>\n <div class=\"actions\">\n <button class=\"abtn primary\" id=\"connect-retry-btn\">Retry</button>\n </div>\n </section>\n\n <section>\n <h3>Inbox summary</h3>\n <div id=\"summary\"></div>\n </section>\n\n <section>\n <h3>Search</h3>\n <div class=\"searchbar\">\n <input type=\"text\" id=\"search-input\" placeholder=\"e.g. is:unread\">\n <button class=\"abtn\" id=\"search-btn\">Search</button>\n </div>\n <div id=\"selection-bar\">\n <span id=\"sel-count\" style=\"font-size:12px;color:var(--text2)\">0 selected</span>\n <button class=\"abtn primary\" data-action=\"send-selection\">Send selection to Claude</button>\n </div>\n <div id=\"search-results\"><div class=\"empty\">No search run yet.</div></div>\n </section>\n\n <section>\n <h3>Triage plan</h3>\n <div class=\"searchbar\">\n <input type=\"text\" id=\"plan-query\" placeholder=\"criteria query\" value=\"is:unread category:promotions older_than:7d\">\n <select id=\"plan-action\">\n <option value=\"markRead\">mark read</option>\n <option value=\"archive\">archive</option>\n <option value=\"label\">label…</option>\n <option value=\"trash\">trash</option>\n </select>\n <input type=\"text\" id=\"plan-label\" placeholder=\"label name\" style=\"display:none\">\n </div>\n <div class=\"actions\">\n <button class=\"abtn primary\" id=\"triage-btn\">Build plan</button>\n <button class=\"abtn\" id=\"apply-btn\" disabled>Apply</button>\n </div>\n <div id=\"plan-list\" style=\"margin-top:10px\"><div class=\"empty\">No plan yet — click Build plan.</div></div>\n </section>\n\n <section>\n <h3>Triager agent</h3>\n <div class=\"actions\">\n <button class=\"abtn\" id=\"run-agent-btn\">Run Triager Agent</button>\n </div>\n <div id=\"log-events\" style=\"margin-top:10px\"><div class=\"empty\">Agent has not run yet.</div></div>\n <div id=\"log-tail\" style=\"margin-top:6px\"></div>\n </section>\n\n <section>\n <h3><span>Past runs</span><button class=\"mini\" id=\"runs-refresh-btn\">refresh</button></h3>\n <div id=\"runs-list\"><div class=\"empty\">No runs yet.</div></div>\n </section>\n </div>\n <div id=\"status\">Connecting…</div>\n</div>\n<script>\nvar APP_ID = ${JSON.stringify(APP_ID)};\nvar AGENT_ID = ${JSON.stringify(AGENT_ID)};\nvar CATEGORIES = ${JSON.stringify(CATEGORIES)};\nvar ALIASES = ${JSON.stringify(MAIL_TRIAGE_MCP_ALIASES)};\nvar POLL_TIMEOUT_MS = 10 * 60 * 1000;\nvar callTool = null;\nvar updateModelContext = null;\nvar openLink = null;\nvar onTeardown = null;\nvar currentPlan = null;\nvar pollHandle = null;\nvar planExpiryHandle = null;\nvar activeAlias = ALIASES[0];\nvar currentMailboxes = [];\nvar categoryCounts = null;\nvar unreadCount = null;\nvar aliasStates = {};\nvar selectedMessages = {};\n\nfunction setStatus(msg) {\n document.getElementById(\"status\").textContent = msg;\n}\n\n// See media-viewer/ui.ts for why this loop exists: app_tool_call wraps its\n// dispatch result in at least one more {content:[{type:\"text\",text:...}]}\n// shell than the underlying tool returned.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try {\n cur = JSON.parse(cur);\n continue;\n } catch (e) {\n return cur;\n }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n if (!callTool) {\n return Promise.reject(new Error(\"Not connected to host bridge (standalone mode).\"));\n }\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(function (result) {\n var value = unwrapText(result);\n if (value && typeof value === \"object\" && !Array.isArray(value) && typeof value.error === \"string\") {\n throw new Error(value.error);\n }\n return value;\n });\n}\n\nfunction aliasTool(name) {\n return \"imported:\" + activeAlias + \"/\" + name;\n}\n\n// Disables a button and swaps its label to an ellipsis while a call is in\n// flight, so double-submit is impossible; restores the original label after.\nfunction setBusy(btn, busy) {\n if (busy) {\n if (btn.getAttribute(\"data-label\") === null) btn.setAttribute(\"data-label\", btn.textContent);\n btn.disabled = true;\n btn.textContent = \"\\\\u2026\";\n } else {\n btn.disabled = false;\n var label = btn.getAttribute(\"data-label\");\n if (label !== null) btn.textContent = label;\n }\n}\n\nfunction isSelected(id) {\n return selectedMessages[id] ? true : false;\n}\n\nfunction toggleSelection(id, item) {\n if (selectedMessages[id]) {\n delete selectedMessages[id];\n } else {\n selectedMessages[id] = item;\n }\n updateSelectionBar();\n}\n\nfunction updateSelectionBar() {\n var bar = document.getElementById(\"selection-bar\");\n var countEl = document.getElementById(\"sel-count\");\n var count = 0;\n for (var k in selectedMessages) { if (selectedMessages.hasOwnProperty(k)) count++; }\n countEl.textContent = count + \" selected\";\n bar.className = count > 0 ? \"show\" : \"\";\n // Sync checkboxes\n var cbs = document.querySelectorAll(\"#search-results .mail-checkbox\");\n for (var i = 0; i < cbs.length; i++) {\n var mid = cbs[i].getAttribute(\"data-message-id\");\n cbs[i].checked = mid ? isSelected(mid) : false;\n }\n}\n\nfunction sendSelectionToClaude() {\n if (!updateModelContext) {\n setStatus(\"Host does not support updateModelContext.\");\n return;\n }\n var items = [];\n var names = [];\n for (var k in selectedMessages) {\n if (!selectedMessages.hasOwnProperty(k)) continue;\n var msg = selectedMessages[k];\n items.push({\n id: msg.id || k,\n threadId: msg.threadId || \"\",\n from: msg.from || \"\",\n subject: msg.subject || \"\",\n timestamp: msg.internalDate || msg.date || \"\"\n });\n if (msg.subject) names.push(msg.subject);\n }\n if (items.length === 0) {\n setStatus(\"No messages selected.\");\n return;\n }\n var summary = items.length + \" emails selected for triage: \" + names.join(\", \");\n setStatus(\"Sending selection to Claude\\\\u2026\");\n updateModelContext({\n content: [{ type: \"text\", text: summary }],\n structuredContent: { selection: items }\n }).then(function () {\n setStatus(\"Selection sent to Claude (\" + items.length + \" emails).\");\n }).catch(function (err) {\n setStatus(\"Failed to send selection: \" + (err && err.message ? err.message : String(err)));\n });\n}\n\nfunction clearPlanExpiry() {\n if (planExpiryHandle) {\n clearTimeout(planExpiryHandle);\n planExpiryHandle = null;\n }\n}\n\nfunction onPlanExpired() {\n planExpiryHandle = null;\n currentPlan = null;\n document.getElementById(\"apply-btn\").disabled = true;\n setStatus(\"Plan expired \\\\u2014 rebuild it.\");\n}\n\n// Arms a timeout that invalidates the plan at its server-declared expires_at\n// (plans expire after 15 minutes) so Apply never fires against a stale plan.\nfunction armPlanExpiry(expiresAt) {\n clearPlanExpiry();\n if (!expiresAt) return;\n var ms = new Date(expiresAt).getTime() - Date.now();\n if (isNaN(ms)) return;\n if (ms <= 0) {\n onPlanExpired();\n return;\n }\n planExpiryHandle = setTimeout(onPlanExpired, ms);\n}\n\nfunction asList(value, keys) {\n if (Array.isArray(value)) return value;\n if (value && typeof value === \"object\") {\n for (var i = 0; i < keys.length; i++) {\n if (Array.isArray(value[keys[i]])) return value[keys[i]];\n }\n }\n return [];\n}\n\nfunction renderSummary() {\n var el = document.getElementById(\"summary\");\n el.innerHTML = \"\";\n var tiles = [{ key: \"unread\", count: unreadCount }].concat(\n CATEGORIES.map(function (cat) {\n return { key: cat, count: categoryCounts ? categoryCounts[cat] : undefined };\n })\n );\n tiles.forEach(function (t) {\n var tile = document.createElement(\"div\");\n tile.className = \"tile \" + t.key;\n tile.innerHTML = '<div class=\"count\"></div><div class=\"label\"></div>';\n tile.querySelector(\".count\").textContent = t.count === undefined || t.count === null ? \"\\\\u2013\" : String(t.count);\n tile.querySelector(\".label\").textContent = t.key.replace(\"-\", \" \");\n el.appendChild(tile);\n });\n}\n\nfunction mailboxId(mb) {\n if (typeof mb === \"string\") return mb;\n return mb.id || mb.name || JSON.stringify(mb);\n}\n\nfunction mailboxText(mb) {\n if (typeof mb === \"string\") return mb;\n var name = mb.address || mb.label || mb.name || mb.id || JSON.stringify(mb);\n var caps = mb.capabilities;\n if (caps && typeof caps === \"object\") {\n return name + (caps.triage ? \" \\\\u2014 triage \\\\u2713\" : \" \\\\u2014 read-only\");\n }\n return name;\n}\n\nfunction selectedMailbox() {\n var id = document.getElementById(\"mailbox-select\").value;\n for (var i = 0; i < currentMailboxes.length; i++) {\n if (mailboxId(currentMailboxes[i]) === id) return currentMailboxes[i];\n }\n return null;\n}\n\nfunction renderCaps(mb) {\n var el = document.getElementById(\"caps\");\n var caps = mb && typeof mb === \"object\" ? mb.capabilities : null;\n if (!caps || typeof caps !== \"object\") {\n el.style.display = \"none\";\n return;\n }\n el.style.display = \"flex\";\n el.innerHTML = \"\";\n [\"read\", \"send\", \"triage\", \"compose\"].forEach(function (cap) {\n var b = document.createElement(\"span\");\n b.className = \"badge \" + (caps[cap] ? \"on\" : \"off\");\n b.textContent = cap;\n el.appendChild(b);\n });\n if (!caps.triage) {\n var hint = document.createElement(\"span\");\n hint.className = \"caps-hint\";\n hint.textContent = \"triage scope missing \\\\u2014 grant it via agentpush (mailbox_request_elevation)\";\n el.appendChild(hint);\n }\n}\n\n// Gmail category labels -> the dashboard's triage buckets. IMPORTANT wins,\n// so an important promo counts as urgent, not newsletter.\nfunction categoryOf(labelIds) {\n var l = labelIds || [];\n if (l.indexOf(\"IMPORTANT\") !== -1) return \"urgent\";\n if (l.indexOf(\"CATEGORY_PROMOTIONS\") !== -1) return \"newsletter\";\n if (l.indexOf(\"CATEGORY_UPDATES\") !== -1 || l.indexOf(\"CATEGORY_SOCIAL\") !== -1 || l.indexOf(\"CATEGORY_FORUMS\") !== -1) return \"notification\";\n return \"needs-reply\";\n}\n\nfunction onMailboxChange() {\n clearPlanExpiry();\n var mb = selectedMailbox();\n renderCaps(mb);\n var caps = mb && typeof mb === \"object\" ? mb.capabilities : null;\n var noTriage = !!(caps && typeof caps === \"object\" && !caps.triage);\n var scopeHint = noTriage ? \"Mailbox lacks triage capability \\\\u2014 grant it via agentpush (mailbox_request_elevation)\" : \"\";\n document.getElementById(\"triage-btn\").disabled = noTriage;\n document.getElementById(\"triage-btn\").title = scopeHint;\n document.getElementById(\"apply-btn\").disabled = true;\n document.getElementById(\"apply-btn\").title = scopeHint;\n currentPlan = null;\n categoryCounts = null;\n unreadCount = null;\n renderSummary();\n if (!mb) return;\n callApp(aliasTool(\"mailbox_search\"), { mailbox: mailboxId(mb), query: \"is:unread\", limit: 50 })\n .then(function (result) {\n var items = asList(result, [\"items\", \"messages\", \"results\"]);\n if (result && typeof result === \"object\" && typeof result.resultSizeEstimate === \"number\") {\n unreadCount = result.resultSizeEstimate;\n } else {\n unreadCount = items.length;\n }\n // Category breakdown over the newest page of unread — a sample when\n // the mailbox has more unread than one page.\n var counts = {};\n items.forEach(function (item) {\n var cat = categoryOf(item && item.labelIds);\n counts[cat] = (counts[cat] || 0) + 1;\n });\n categoryCounts = items.length > 0 ? counts : null;\n renderSummary();\n })\n .catch(function (err) {\n // KPI is best-effort: keep the dashes, but say why instead of silence.\n setStatus(\"Inbox summary unavailable: \" + err.message);\n });\n}\n\nfunction renderMailboxes(mailboxes) {\n currentMailboxes = mailboxes;\n var select = document.getElementById(\"mailbox-select\");\n select.innerHTML = \"\";\n if (mailboxes.length === 0) {\n var opt = document.createElement(\"option\");\n opt.textContent = \"no mailboxes\";\n opt.value = \"\";\n select.appendChild(opt);\n renderCaps(null);\n return;\n }\n mailboxes.forEach(function (mb) {\n var opt = document.createElement(\"option\");\n opt.value = mailboxId(mb);\n opt.textContent = mailboxText(mb);\n select.appendChild(opt);\n });\n onMailboxChange();\n}\n\nfunction renderAliasSelect(liveAliases) {\n var select = document.getElementById(\"alias-select\");\n select.innerHTML = \"\";\n var shown = liveAliases.length > 0 ? liveAliases : ALIASES;\n shown.forEach(function (alias) {\n var opt = document.createElement(\"option\");\n opt.value = alias;\n opt.textContent = alias;\n select.appendChild(opt);\n });\n select.value = activeAlias;\n select.style.display = shown.length > 1 ? \"\" : \"none\";\n}\n\nfunction loadMailboxes() {\n setStatus(\"Loading mailboxes from \" + activeAlias + \"\\\\u2026\");\n return callApp(aliasTool(\"mailbox_list\"), {})\n .then(function (result) {\n renderMailboxes(asList(result, [\"mailboxes\", \"items\"]));\n setStatus(\"Ready \\\\u2014 \" + activeAlias);\n })\n .catch(function (err) {\n setStatus(\"Error loading mailboxes: \" + err.message);\n });\n}\n\nfunction renderConnectHint() {\n var el = document.getElementById(\"connect-hint\");\n var anyLive = false;\n for (var i = 0; i < ALIASES.length; i++) {\n if (aliasStates[ALIASES[i]] && aliasStates[ALIASES[i]].state === \"live\") anyLive = true;\n }\n if (anyLive) {\n el.style.display = \"none\";\n return;\n }\n el.style.display = \"\";\n var list = document.getElementById(\"connect-hint-list\");\n list.innerHTML = \"\";\n ALIASES.forEach(function (alias) {\n var st = aliasStates[alias];\n var row = document.createElement(\"div\");\n row.className = \"plan-item\";\n var text;\n if (!st) text = alias + \" \\\\u2014 not probed yet\";\n else if (st.state === \"unreachable\") text = alias + \" \\\\u2014 unreachable: \" + st.message;\n else if (st.state === \"empty\") text = alias + \" \\\\u2014 connected, no mailboxes\";\n else text = alias + \" \\\\u2014 live\";\n row.textContent = text;\n list.appendChild(row);\n });\n}\n\nfunction probeAliases() {\n setStatus(\"Detecting mail servers\\\\u2026\");\n return Promise.all(\n ALIASES.map(function (alias) {\n return callApp(\"imported:\" + alias + \"/mailbox_list\", {})\n .then(function (result) {\n var mailboxes = asList(result, [\"mailboxes\", \"items\"]);\n aliasStates[alias] = { state: mailboxes.length > 0 ? \"live\" : \"empty\" };\n return { alias: alias, mailboxes: mailboxes };\n })\n .catch(function (err) {\n aliasStates[alias] = { state: \"unreachable\", message: err && err.message ? err.message : String(err) };\n return { alias: alias, mailboxes: [] };\n });\n })\n ).then(function (results) {\n var live = results.filter(function (r) { return r.mailboxes.length > 0; });\n if (live.length > 0) {\n activeAlias = live[0].alias;\n renderAliasSelect(live.map(function (r) { return r.alias; }));\n renderMailboxes(live[0].mailboxes);\n renderConnectHint();\n setStatus(\"Ready \\\\u2014 \" + activeAlias + (live.length > 1 ? \" (\" + live.length + \" servers available)\" : \"\"));\n } else {\n renderAliasSelect([]);\n renderMailboxes([]);\n renderConnectHint();\n setStatus(\"No mailboxes reachable \\\\u2014 see connection panel below.\");\n }\n });\n}\n\nfunction renderSearchResults(items, raw) {\n var el = document.getElementById(\"search-results\");\n el.innerHTML = \"\";\n if (raw && typeof raw === \"object\" && typeof raw.resultSizeEstimate === \"number\") {\n var count = document.createElement(\"div\");\n count.className = \"empty\";\n count.textContent = items.length + \" shown \\\\u00b7 ~\" + raw.resultSizeEstimate + \" total\";\n el.appendChild(count);\n }\n if (items.length === 0) {\n var none = document.createElement(\"div\");\n none.className = \"empty\";\n none.textContent = \"No results.\";\n el.appendChild(none);\n return;\n }\n items.forEach(function (item) {\n var row = document.createElement(\"div\");\n row.className = \"plan-item\";\n row.style.flexDirection = \"column\";\n row.style.alignItems = \"stretch\";\n var msgId = item && (item.id || item.messageId || \"\");\n var threadId = item && (item.threadId || \"\");\n var subj = typeof item === \"string\" ? item : item.subject || item.title || JSON.stringify(item);\n var from = item && item.from ? item.from : \"\";\n var snippet = item && item.snippet ? item.snippet : \"\";\n var top = document.createElement(\"div\");\n top.style.display = \"flex\";\n top.style.alignItems = \"flex-start\";\n top.style.gap = \"6px\";\n var cb = document.createElement(\"input\");\n cb.type = \"checkbox\";\n cb.className = \"mail-checkbox\";\n cb.setAttribute(\"data-action\", \"toggle-select\");\n if (msgId) cb.setAttribute(\"data-message-id\", msgId);\n if (isSelected(msgId)) cb.checked = true;\n top.appendChild(cb);\n var subjSpan = document.createElement(\"span\");\n subjSpan.className = \"subj\";\n subjSpan.style.flex = \"1\";\n subjSpan.textContent = subj;\n top.appendChild(subjSpan);\n var tagSpan = document.createElement(\"span\");\n tagSpan.className = \"tag\";\n tagSpan.textContent = from;\n top.appendChild(tagSpan);\n if (threadId) {\n var openBtn = document.createElement(\"button\");\n openBtn.className = \"mail-open\";\n openBtn.textContent = \"Open\";\n openBtn.setAttribute(\"data-action\", \"open-gmail\");\n openBtn.setAttribute(\"data-thread-id\", threadId);\n top.appendChild(openBtn);\n }\n row.appendChild(top);\n if (snippet) {\n var sn = document.createElement(\"div\");\n sn.className = \"snippet\";\n sn.textContent = snippet;\n row.appendChild(sn);\n }\n el.appendChild(row);\n });\n}\n\n// Renders a mailbox_triage_plan result: { plan_id, action, count, sample,\n// truncated, expires_at }. The sample rows are search-shaped messages.\nfunction renderPlan(plan) {\n var el = document.getElementById(\"plan-list\");\n el.innerHTML = \"\";\n if (!plan || typeof plan !== \"object\" || !plan.plan_id) {\n el.innerHTML = '<div class=\"empty\">Plan failed \\\\u2014 unexpected response.</div>';\n return;\n }\n var head = document.createElement(\"div\");\n head.className = \"plan-item\";\n head.innerHTML = '<span class=\"subj\"></span><span class=\"tag\"></span>';\n head.querySelector(\".subj\").textContent =\n plan.count + \" message\" + (plan.count === 1 ? \"\" : \"s\") + \" \\\\u2192 \" + (plan.action && plan.action.type);\n head.querySelector(\".tag\").textContent = plan.expires_at ? \"expires \" + fmtDate(plan.expires_at) : \"\";\n el.appendChild(head);\n var sample = asList(plan, [\"sample\"]);\n sample.forEach(function (item) {\n var row = document.createElement(\"div\");\n row.className = \"plan-item\";\n row.innerHTML = '<span class=\"subj\"></span><span class=\"tag\"></span>';\n row.querySelector(\".subj\").textContent = (item && (item.subject || item.title)) || JSON.stringify(item);\n row.querySelector(\".tag\").textContent = (item && item.from) || \"\";\n el.appendChild(row);\n });\n if (plan.truncated) {\n var more = document.createElement(\"div\");\n more.className = \"empty\";\n more.textContent = \"\\\\u2026sample truncated \\\\u2014 \" + plan.count + \" total.\";\n el.appendChild(more);\n }\n}\n\nfunction fmtDate(iso) {\n if (!iso) return \"\";\n var d = new Date(iso);\n return isNaN(d.getTime()) ? String(iso) : d.toLocaleString();\n}\n\nfunction renderRuns(runs) {\n var el = document.getElementById(\"runs-list\");\n el.innerHTML = \"\";\n if (runs.length === 0) {\n el.innerHTML = '<div class=\"empty\">No runs yet.</div>';\n return;\n }\n var shown = runs.slice(0, 20);\n shown.forEach(function (run) {\n var row = document.createElement(\"div\");\n row.className = \"runs-item\";\n row.title = \"Show sessions + output for \" + run.appRunId;\n row.innerHTML = '<span class=\"st\"></span><span class=\"rid\"></span><span class=\"meta\"></span>';\n var st = row.querySelector(\".st\");\n st.textContent = run.status || \"?\";\n st.className = \"st \" + (run.status || \"\");\n row.querySelector(\".rid\").textContent = run.appRunId;\n row.querySelector(\".meta\").textContent =\n fmtDate(run.startedAt) + (run.endedAt ? \" \\\\u2192 \" + fmtDate(run.endedAt) : \"\") + \" \\\\u00b7 \" + (run.sessions || 0) + \" session\" + (run.sessions === 1 ? \"\" : \"s\");\n row.addEventListener(\"click\", function () { viewRun(run.appRunId); });\n el.appendChild(row);\n });\n if (runs.length > 20) {\n var more = document.createElement(\"div\");\n more.className = \"empty\";\n more.textContent = \"showing 20 of \" + runs.length;\n el.appendChild(more);\n }\n}\n\nfunction loadRuns() {\n return callApp(\"app_list\", {})\n .then(function (result) {\n var apps = Array.isArray(result) ? result : [];\n var mine = null;\n for (var i = 0; i < apps.length; i++) {\n if (apps[i] && apps[i].appId === APP_ID) { mine = apps[i]; break; }\n }\n var runs = (mine && Array.isArray(mine.runs) ? mine.runs.slice() : []).sort(function (a, b) {\n return String(b.startedAt || \"\").localeCompare(String(a.startedAt || \"\"));\n });\n renderRuns(runs);\n })\n .catch(function (err) {\n setStatus(\"Could not load runs: \" + err.message);\n });\n}\n\nfunction viewRun(appRunId) {\n setStatus(\"Loading run \" + appRunId + \"\\\\u2026\");\n callApp(\"app_status\", { appRunId: appRunId })\n .then(function (status) {\n var sessions = (status && status.sessions) || [];\n appendLog(\"\\\\u2500\\\\u2500 run \" + appRunId + \" (\" + (status && status.status) + \") \\\\u2500\\\\u2500\");\n if (sessions.length === 0) {\n appendLog(\"(no sessions)\");\n setStatus(\"Ready \\\\u2014 \" + activeAlias);\n return;\n }\n sessions.forEach(function (s) {\n appendLog(\"session \" + s.sessionId + \" (\" + (s.agentId || \"?\") + \")\");\n });\n return callApp(\"agent_output\", { sessionId: sessions[0].sessionId, lastN: 200, clean: true })\n .then(function (output) {\n if (output) appendLog(typeof output === \"string\" ? output : JSON.stringify(output, null, 2));\n setStatus(\"Ready \\\\u2014 \" + activeAlias);\n });\n })\n .catch(function (err) {\n setStatus(\"Run status failed: \" + err.message);\n });\n}\n\n// Event delegation: search-results (checkboxes + open buttons)\ndocument.getElementById(\"search-results\").addEventListener(\"change\", function (e) {\n var target = e.target;\n if (target.getAttribute(\"data-action\") === \"toggle-select\") {\n var mid = target.getAttribute(\"data-message-id\");\n if (mid) {\n var item = null;\n var el = target.parentNode;\n while (el && el.parentNode && el !== document) {\n if (el.tagName === \"DIV\" && el.className.indexOf(\"plan-item\") !== -1) {\n var subjEl = el.querySelector(\".subj\");\n var tagEl = el.querySelector(\".tag\");\n var subjText = subjEl ? subjEl.textContent : \"\";\n var tagText = tagEl ? tagEl.textContent : \"\";\n // Reconstruct a minimal item from the DOM\n item = { id: mid, threadId: \"\", subject: subjText, from: tagText };\n break;\n }\n el = el.parentNode;\n }\n toggleSelection(mid, item || { id: mid });\n }\n }\n});\ndocument.getElementById(\"search-results\").addEventListener(\"click\", function (e) {\n var target = e.target;\n if (target.getAttribute(\"data-action\") === \"open-gmail\") {\n var tid = target.getAttribute(\"data-thread-id\");\n if (tid && openLink) {\n openLink(\"https://mail.google.com/mail/u/0/#inbox/\" + tid).catch(function (err) {\n setStatus(\"Failed to open link: \" + (err && err.message ? err.message : String(err)));\n });\n } else if (tid) {\n setStatus(\"Host does not support openLink.\");\n }\n }\n});\n// Event delegation: selection-bar (send-selection)\ndocument.getElementById(\"selection-bar\").addEventListener(\"click\", function (e) {\n if (e.target.getAttribute(\"data-action\") === \"send-selection\") {\n sendSelectionToClaude();\n }\n});\n\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () {\n probeAliases();\n loadRuns();\n});\n\ndocument.getElementById(\"connect-retry-btn\").addEventListener(\"click\", function () {\n probeAliases();\n loadRuns();\n});\n\ndocument.getElementById(\"alias-select\").addEventListener(\"change\", function () {\n activeAlias = this.value;\n clearPlanExpiry();\n currentPlan = null;\n document.getElementById(\"apply-btn\").disabled = true;\n loadMailboxes();\n});\n\ndocument.getElementById(\"mailbox-select\").addEventListener(\"change\", onMailboxChange);\n\ndocument.getElementById(\"runs-refresh-btn\").addEventListener(\"click\", loadRuns);\n\ndocument.getElementById(\"search-btn\").addEventListener(\"click\", function () {\n var btn = this;\n var query = document.getElementById(\"search-input\").value.trim() || \"is:unread\";\n var mailbox = document.getElementById(\"mailbox-select\").value;\n setBusy(btn, true);\n setStatus(\"Searching\\\\u2026\");\n callApp(aliasTool(\"mailbox_search\"), { mailbox: mailbox, query: query })\n .then(function (result) {\n renderSearchResults(asList(result, [\"items\", \"messages\", \"results\"]), result);\n setBusy(btn, false);\n setStatus(\"Ready \\\\u2014 \" + activeAlias);\n })\n .catch(function (err) {\n setBusy(btn, false);\n setStatus(\"Search failed: \" + err.message);\n });\n});\n\ndocument.getElementById(\"plan-action\").addEventListener(\"change\", function () {\n document.getElementById(\"plan-label\").style.display = this.value === \"label\" ? \"\" : \"none\";\n});\n\ndocument.getElementById(\"triage-btn\").addEventListener(\"click\", function () {\n var btn = this;\n var mailbox = document.getElementById(\"mailbox-select\").value;\n var query = document.getElementById(\"plan-query\").value.trim() || \"is:unread\";\n var actionType = document.getElementById(\"plan-action\").value;\n var action = { type: actionType };\n if (actionType === \"label\") {\n var label = document.getElementById(\"plan-label\").value.trim();\n if (!label) {\n setStatus(\"Enter a label name for the label action.\");\n return;\n }\n action.addLabelIds = [label];\n }\n setBusy(btn, true);\n setStatus(\"Building triage plan\\\\u2026\");\n document.getElementById(\"apply-btn\").disabled = true;\n clearPlanExpiry();\n currentPlan = null;\n callApp(aliasTool(\"mailbox_triage_plan\"), { mailbox: mailbox, criteria: { query: query }, action: action })\n .then(function (plan) {\n setBusy(btn, false);\n currentPlan = plan && plan.plan_id ? plan : null;\n renderPlan(plan);\n if (currentPlan && plan.count > 0) {\n document.getElementById(\"apply-btn\").disabled = false;\n armPlanExpiry(plan.expires_at);\n setStatus(\"Plan ready (\" + plan.count + \" messages) \\\\u2014 review, then Apply.\");\n } else if (currentPlan) {\n setStatus(\"Plan matches no messages \\\\u2014 nothing to apply.\");\n } else {\n setStatus(\"Triage plan failed \\\\u2014 see plan panel.\");\n }\n })\n .catch(function (err) {\n setBusy(btn, false);\n var msg = err && err.message ? err.message : String(err);\n if (msg.indexOf(\"mailbox_scope_missing\") !== -1) {\n setStatus(\"Triage scope missing \\\\u2014 grant it via agentpush (mailbox_request_elevation).\");\n } else {\n setStatus(\"Triage plan failed: \" + msg);\n }\n });\n});\n\ndocument.getElementById(\"apply-btn\").addEventListener(\"click\", function () {\n var btn = this;\n if (!currentPlan || !currentPlan.plan_id) return;\n setBusy(btn, true);\n setStatus(\"Applying triage plan\\\\u2026\");\n callApp(aliasTool(\"mailbox_triage_apply\"), { plan_id: currentPlan.plan_id, confirm: true })\n .then(function (result) {\n setBusy(btn, false);\n var count = currentPlan.count;\n var extra = [];\n if (result && typeof result === \"object\") {\n [\"applied\", \"modified\", \"count\"].forEach(function (key) {\n if (typeof result[key] === \"number\") extra.push(key + \": \" + result[key]);\n });\n }\n setStatus(\n extra.length > 0\n ? \"Plan applied \\\\u2014 \" + extra.join(\", \") + \".\"\n : \"Plan applied (\" + count + \" message\" + (count === 1 ? \"\" : \"s\") + \").\"\n );\n document.getElementById(\"apply-btn\").disabled = true;\n clearPlanExpiry();\n currentPlan = null;\n onMailboxChange();\n })\n .catch(function (err) {\n setBusy(btn, false);\n setStatus(\"Apply failed: \" + err.message);\n });\n});\n\nfunction appendLog(text) {\n var el = document.getElementById(\"log-events\");\n if (el.querySelector(\".empty\")) el.innerHTML = \"\";\n var line = document.createElement(\"div\");\n line.textContent = text;\n el.appendChild(line);\n el.scrollTop = el.scrollHeight;\n}\n\n// Replaces the whole tail on every poll tick (the daemon returns the last\n// N lines, not a delta) — appending it instead would duplicate the log.\nfunction renderLogTail(text) {\n var el = document.getElementById(\"log-tail\");\n el.textContent = text || \"\";\n el.scrollTop = el.scrollHeight;\n}\n\nfunction pollAgentRun(appRunId, startedAt) {\n if (pollHandle) {\n clearTimeout(pollHandle);\n pollHandle = null;\n }\n if (Date.now() - startedAt > POLL_TIMEOUT_MS) {\n setStatus(\"Still running \\\\u2014 check Past runs later.\");\n return;\n }\n callApp(\"app_status\", { appRunId: appRunId })\n .then(function (status) {\n var sessions = (status && status.sessions) || [];\n var sessionId = sessions[0] && sessions[0].sessionId;\n var tail = sessionId\n ? callApp(\"agent_output\", { sessionId: sessionId, lastN: 200, clean: true })\n : Promise.resolve(\"\");\n return tail.then(function (output) {\n renderLogTail(typeof output === \"string\" ? output : output ? JSON.stringify(output, null, 2) : \"\");\n var done = status && (status.status === \"ended\" || status.status === \"done\" || status.endedAt);\n if (!done) {\n pollHandle = setTimeout(function () { pollAgentRun(appRunId, startedAt); }, 2000);\n } else {\n pollHandle = null;\n setStatus(\"Agent run finished.\");\n loadRuns();\n }\n });\n })\n .catch(function (err) {\n setStatus(\"Agent status failed: \" + err.message);\n });\n}\n\ndocument.getElementById(\"run-agent-btn\").addEventListener(\"click\", function () {\n var btn = this;\n if (pollHandle) {\n clearTimeout(pollHandle);\n pollHandle = null;\n }\n document.getElementById(\"log-events\").innerHTML = \"\";\n document.getElementById(\"log-tail\").textContent = \"\";\n setBusy(btn, true);\n setStatus(\"Starting triager agent\\\\u2026\");\n callApp(\"app_run\", { appId: APP_ID, agents: [AGENT_ID], prompt: \"Triage my inbox.\" })\n .then(function (run) {\n setBusy(btn, false);\n var sessions = (run && run.sessions) || [];\n var errors = (run && run.errors) || [];\n if (sessions.length === 0 && errors.length > 0) {\n appendLog(\n \"Run failed to start: \" +\n errors.map(function (e) { return (e.agentId || \"?\") + \": \" + e.error; }).join(\"; \")\n );\n setStatus(\"Run failed to start.\");\n return;\n }\n appendLog(\"Started app run \" + (run && run.appRunId));\n loadRuns();\n pollAgentRun(run.appRunId, Date.now());\n })\n .catch(function (err) {\n setBusy(btn, false);\n setStatus(\"Run failed: \" + err.message);\n });\n});\n\n// window.McpApp is normally injected by the serving layer (postMessage bridge\n// on the MCP resource path, REST bridge on GET /apps/:id/ui). Guard anyway:\n// without it a missing bridge throws here and the panel dies silently on\n// \"Connecting…\" instead of reporting itself.\n(window.McpApp ? window.McpApp.connect() : Promise.reject(new Error(\"window.McpApp bridge missing\")))\n .then(function (bridge) {\n callTool = bridge.callTool;\n updateModelContext = bridge.updateModelContext;\n openLink = bridge.openLink;\n onTeardown = bridge.onTeardown;\n if (onTeardown) {\n onTeardown(function () {\n if (pollHandle) { clearTimeout(pollHandle); pollHandle = null; }\n if (planExpiryHandle) { clearTimeout(planExpiryHandle); planExpiryHandle = null; }\n });\n }\n return Promise.all([probeAliases(), loadRuns()]);\n })\n .catch(function (err) {\n setStatus(\"Standalone mode \\\\u2014 no host bridge (\" + err.message + \")\");\n renderSummary();\n });\n\nrenderSummary();\n</script>\n</body>\n</html>`\n","/**\n * `mail-triage` — a single-agent agentproto app for email triage.\n *\n * One agent (triager) scans inbox, categorizes unread mail, and applies\n * triage actions (label, archive). Proves the APP primitive works end-to-end\n * with a simple, useful real-world task.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { triager } from \"./agents/triager.js\"\nimport { triageInbox } from \"./workflows/triage-inbox.js\"\nimport { MAIL_TRIAGE_HTML, MAIL_TRIAGE_TOOLS } from \"./ui.js\"\n\nexport const mailTriage: AppHandle = defineApp({\n id: \"@agentproto/mail-triage\",\n name: \"Mail Triage\",\n version: \"0.1.0\",\n description: \"Triage your inbox — categorize, label, archive.\",\n agents: [triager],\n workflows: [triageInbox],\n ui: {\n html: MAIL_TRIAGE_HTML,\n title: \"Mail Triage\",\n tools: [...MAIL_TRIAGE_TOOLS],\n },\n})\n\nexport { triager, triageInbox }\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\nexport const cataloger: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/media-cataloger\",\n description:\n \"Scans a folder for media files (images, video, audio), collects metadata, and produces a structured catalog.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Read-only — never modify, rename, move, or delete files\",\n \"Only catalog files with recognized media extensions\",\n \"Report exact file sizes and paths, never estimate\",\n ],\n tools: [\"list_dir\", \"read_file\", \"file_info\"],\n workflows: [{ ref: \"scan-media\" }],\n }),\n body: [\n \"You scan a workspace folder for media files and produce a structured JSON catalog.\",\n \"\",\n \"Walk the target directory recursively using list_dir. For each file, check the\",\n \"extension against known media types:\",\n \"- Images: .jpg, .jpeg, .png, .gif, .webp, .svg, .bmp, .ico, .avif\",\n \"- Video: .mp4, .webm, .mov, .avi, .mkv\",\n \"- Audio: .mp3, .wav, .ogg, .flac, .aac, .m4a\",\n \"\",\n \"For each media file found, use file_info to get size and modification date.\",\n \"\",\n \"Output a JSON object with this shape:\",\n \"```json\",\n '{',\n ' \"folder\": \"<scanned path>\",',\n ' \"scannedAt\": \"<ISO timestamp>\",',\n ' \"summary\": { \"total\": N, \"images\": N, \"video\": N, \"audio\": N, \"totalSizeBytes\": N },',\n ' \"files\": [',\n ' {',\n ' \"path\": \"relative/path.jpg\",',\n ' \"name\": \"file.jpg\",',\n ' \"type\": \"image\",',\n ' \"extension\": \".jpg\",',\n ' \"sizeBytes\": 123456,',\n ' \"modifiedAt\": \"2026-08-01T12:00:00Z\"',\n \" }\",\n \" ]\",\n \"}\",\n \"```\",\n \"\",\n \"Sort files by type (images first, then video, then audio), then by name.\",\n ].join(\"\\n\"),\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\nexport const scanMedia = defineWorkflow({\n id: \"scan-media\",\n name: \"Scan Media\",\n description:\n \"Scan a folder for media files, collect metadata, and produce a structured catalog.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"catalog\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/media-cataloger\" },\n prompt:\n \"Scan the workspace root folder for media files (images, video, audio). \" +\n \"Walk all subdirectories recursively. For each media file, collect its path, \" +\n \"name, type, extension, size, and modification date. Output the full catalog \" +\n \"as a single JSON object.\",\n },\n ],\n})\n","/**\n * Media Viewer — a self-contained MCP App gallery widget for `media-viewer`.\n *\n * Talks to the daemon only through the `window.McpApp.connect()` bridge\n * (never a direct `fetch`): every action round-trips through the app's own\n * `app_tool_call` gateway tool, `{ appId, tool, args }`. `app_tool_call`\n * double-wraps its dispatch result (it returns a text-result whose `text` is\n * the JSON-stringified inner tool result, itself an MCP content array) — see\n * `unwrapText` below, which peels that apart regardless of how many layers\n * the host's bridge already stripped.\n */\n\nexport const MEDIA_VIEWER_TOOLS = [\"directory_list\", \"file_info\", \"file_read\"] as const\n\nconst APP_ID = \"@agentproto/media-viewer\"\n\nexport const MEDIA_VIEWER_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Media Viewer</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\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;flex-direction:column;height:100%}\n#toolbar{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;background:var(--bg2);flex-shrink:0}\n#toolbar .title{font-size:13px;font-weight:600;flex:1}\n#breadcrumb{font-size:12px;color:var(--text2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}\n.abtn:hover{background:var(--border)}\n#body{flex:1;display:flex;overflow:hidden}\n#grid-wrap{flex:1;overflow-y:auto;padding:14px}\n#grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px}\n.cell{display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 6px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);cursor:pointer;text-align:center}\n.cell:hover{background:var(--bg3);border-color:var(--blue)}\n.cell.selected{border-color:var(--blue);background:var(--bg3)}\n.icon{font-size:28px;line-height:1}\n.name{font-size:11px;color:var(--text);word-break:break-word;max-width:100%;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}\n#empty{color:var(--text2);padding:24px;text-align:center}\n#details{width:280px;border-left:1px solid var(--border);background:var(--bg2);padding:14px;overflow-y:auto;flex-shrink:0}\n#details h3{font-size:12px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}\n#details .row{display:flex;justify-content:space-between;gap:8px;padding:4px 0;border-bottom:1px solid var(--border);font-size:12px}\n#details .row span:first-child{color:var(--text2)}\n#preview{margin-top:10px;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;font-family:Menlo,Monaco,monospace;font-size:11px;white-space:pre-wrap;word-break:break-all;max-height:240px;overflow-y:auto}\n#status{padding:6px 14px;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=\"toolbar\">\n <button class=\"abtn\" id=\"up-btn\" title=\"Up one level\">↑</button>\n <span class=\"title\">Media Viewer</span>\n <span id=\"breadcrumb\">.</span>\n <button class=\"abtn\" id=\"refresh-btn\">Refresh</button>\n </div>\n <div id=\"body\">\n <div id=\"grid-wrap\">\n <div id=\"grid\"></div>\n <div id=\"empty\" style=\"display:none\">No files in this folder.</div>\n </div>\n <div id=\"details\" style=\"display:none\"></div>\n </div>\n <div id=\"status\">Connecting…</div>\n</div>\n<script>\nvar APP_ID = ${JSON.stringify(APP_ID)};\nvar currentPath = \".\";\nvar entries = [];\nvar callTool = null;\n\nfunction setStatus(msg) {\n document.getElementById(\"status\").textContent = msg;\n}\n\n// app_tool_call wraps its dispatch result at least once (its own\n// text-result body is the JSON-stringified inner MCP tool result); peel\n// back through nested {content:[{type:\"text\",text:...}]} shells until the\n// payload stops looking like one, so this works whichever layer the host's\n// own bridge already unwrapped.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try {\n cur = JSON.parse(cur);\n continue;\n } catch (e) {\n return cur;\n }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);\n}\n\nfunction parseDirectoryListing(raw) {\n var text = typeof raw === \"string\" ? raw : \"\";\n return text\n .split(\"\\\\n\")\n .map(function (line) { return line.trim(); })\n .filter(function (line) { return line.length > 0; })\n .map(function (line) {\n var isDir = line.indexOf(\"[DIR]\") === 0;\n var name = line.replace(/^\\\\[(FILE|DIR)\\\\]\\\\s*/, \"\");\n return { name: name, isDir: isDir };\n });\n}\n\nfunction iconFor(entry) {\n if (entry.isDir) return \"\\\\uD83D\\\\uDCC1\";\n var ext = (entry.name.split(\".\").pop() || \"\").toLowerCase();\n if ([\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\", \"bmp\", \"ico\", \"avif\"].indexOf(ext) !== -1) return \"\\\\uD83D\\\\uDDBC\\\\uFE0F\";\n if ([\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\"].indexOf(ext) !== -1) return \"\\\\uD83C\\\\uDFA5\";\n if ([\"mp3\", \"wav\", \"ogg\", \"flac\", \"aac\", \"m4a\"].indexOf(ext) !== -1) return \"\\\\uD83C\\\\uDFB5\";\n return \"\\\\uD83D\\\\uDCC4\";\n}\n\nfunction joinPath(base, name) {\n if (base === \".\" || base === \"\") return name;\n return base.replace(/\\\\/+$/, \"\") + \"/\" + name;\n}\n\nfunction parentPath(path) {\n if (path === \".\" || path.indexOf(\"/\") === -1) return \".\";\n return path.slice(0, path.lastIndexOf(\"/\")) || \".\";\n}\n\nfunction renderGrid() {\n document.getElementById(\"breadcrumb\").textContent = currentPath;\n var grid = document.getElementById(\"grid\");\n var empty = document.getElementById(\"empty\");\n grid.innerHTML = \"\";\n if (entries.length === 0) {\n empty.style.display = \"block\";\n return;\n }\n empty.style.display = \"none\";\n entries.forEach(function (entry, idx) {\n var cell = document.createElement(\"div\");\n cell.className = \"cell\";\n cell.dataset.idx = String(idx);\n cell.innerHTML = '<div class=\"icon\">' + iconFor(entry) + '</div><div class=\"name\"></div>';\n cell.querySelector(\".name\").textContent = entry.name;\n cell.addEventListener(\"click\", function () { onSelect(entry, cell); });\n grid.appendChild(cell);\n });\n}\n\nfunction onSelect(entry, cell) {\n Array.prototype.forEach.call(document.querySelectorAll(\".cell\"), function (c) {\n c.classList.remove(\"selected\");\n });\n cell.classList.add(\"selected\");\n if (entry.isDir) {\n loadDirectory(joinPath(currentPath, entry.name));\n return;\n }\n showFileDetails(joinPath(currentPath, entry.name));\n}\n\nfunction showFileDetails(path) {\n var details = document.getElementById(\"details\");\n details.style.display = \"block\";\n details.innerHTML = \"<h3>Loading…</h3>\";\n setStatus(\"Reading \" + path + \"\\\\u2026\");\n callApp(\"file_info\", { path: path })\n .then(function (info) {\n var rows = \"\";\n if (info && typeof info === \"object\") {\n [\"name\", \"type\", \"size\", \"modified\"].forEach(function (key) {\n if (info[key] !== undefined) {\n rows += '<div class=\"row\"><span>' + key + '</span><span></span></div>';\n }\n });\n }\n details.innerHTML = \"<h3>Details</h3>\" + rows + '<div id=\"preview\">Loading preview…</div>';\n var rowEls = details.querySelectorAll(\".row span:last-child\");\n var keys = [\"name\", \"type\", \"size\", \"modified\"].filter(function (key) {\n return info && info[key] !== undefined;\n });\n keys.forEach(function (key, i) {\n rowEls[i].textContent = String(info[key]);\n });\n return callApp(\"file_read\", { path: path });\n })\n .then(function (content) {\n var pre = document.getElementById(\"preview\");\n if (!pre) return;\n var text = typeof content === \"string\" ? content : JSON.stringify(content);\n pre.textContent = text.length > 2000 ? text.slice(0, 2000) + \"\\\\u2026 (truncated)\" : (text || \"(empty file)\");\n setStatus(\"Ready \\\\u00b7 \" + currentPath);\n })\n .catch(function (err) {\n var pre = document.getElementById(\"preview\");\n if (pre) pre.textContent = \"Preview unavailable (\" + err.message + \")\";\n setStatus(\"Ready \\\\u00b7 \" + currentPath);\n });\n}\n\nfunction loadDirectory(path) {\n document.getElementById(\"details\").style.display = \"none\";\n setStatus(\"Loading \" + path + \"\\\\u2026\");\n return callApp(\"directory_list\", { path: path })\n .then(function (raw) {\n currentPath = path;\n entries = parseDirectoryListing(raw).sort(function (a, b) {\n if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n renderGrid();\n setStatus(\"Ready \\\\u00b7 \" + entries.length + \" entries \\\\u00b7 \" + path);\n })\n .catch(function (err) {\n entries = [];\n renderGrid();\n setStatus(\"Error: \" + err.message);\n });\n}\n\ndocument.getElementById(\"up-btn\").addEventListener(\"click\", function () {\n loadDirectory(parentPath(currentPath));\n});\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () {\n loadDirectory(currentPath);\n});\n\nwindow.McpApp.connect()\n .then(function (bridge) {\n callTool = bridge.callTool;\n return loadDirectory(\".\");\n })\n .catch(function (err) {\n setStatus(\"Standalone mode \\\\u2014 no host bridge (\" + err.message + \")\");\n entries = [];\n renderGrid();\n });\n</script>\n</body>\n</html>`\n","/**\n * `media-viewer` — a single-agent agentproto app for media cataloging.\n *\n * One agent (cataloger) scans a workspace folder for images, video, and audio\n * files, collects metadata, and produces a structured JSON catalog. Paired\n * with a Cowork McpApp gallery widget for visual browsing.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { cataloger } from \"./agents/cataloger.js\"\nimport { scanMedia } from \"./workflows/scan-media.js\"\nimport { MEDIA_VIEWER_HTML, MEDIA_VIEWER_TOOLS } from \"./ui.js\"\n\nexport const mediaViewer: AppHandle = defineApp({\n id: \"@agentproto/media-viewer\",\n name: \"Media Viewer\",\n version: \"0.1.0\",\n description: \"Scan a folder for images, video, and audio — gallery view.\",\n agents: [cataloger],\n workflows: [scanMedia],\n ui: {\n html: MEDIA_VIEWER_HTML,\n title: \"Media Viewer\",\n tools: [...MEDIA_VIEWER_TOOLS],\n },\n})\n\nexport { cataloger, scanMedia }\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/**\n * The Sessions Manager — the durable \"master of the sessions\" coordinator:\n * the human talks to IT, it talks to the other sessions. Distilled from the\n * hand-run plateau-coordinator session this app productizes.\n *\n * Where the watchdog only OBSERVES (and never touches a live session), the\n * manager DRIVES: it re-prompts idle sessions onto their task, restarts dead\n * ones, and answers \"where is X at?\" by reading transcripts. The Ops Panel\n * spawns it via `agent_start` with {@link MANAGER_LABEL} and `keepAlive`, so\n * the idle-reaper never retires a legitimately-parked coordinator.\n */\n\n/** Session label the panel spawns under — how a live manager is recognized. */\nexport const MANAGER_LABEL = \"Sessions Manager\"\n\nexport const manager: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/sessions-manager\",\n description:\n \"Durable coordinator over the daemon's sessions — routes questions to the right session, re-prompts stalled work, restarts dead sessions, reports status.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Never kill a session unless the human explicitly asks for that session by id/label\",\n \"Never merge PRs or bypass a repo's declared review flow from a supervised session\",\n \"Prefer re-prompting an existing session over spawning a duplicate for the same task\",\n ],\n tools: [\n \"session_list\",\n \"conversation_read\",\n \"agent_prompt\",\n \"session_restart\",\n \"agent_start\",\n \"session_events_poll\",\n ],\n }),\n body: [\n \"You are the manager of this agentproto daemon's sessions: the human talks to you, and you talk\",\n \"to the other sessions. You are durable — the same session across many turns — so keep a working\",\n \"memory of who is doing what and don't re-derive it from scratch every turn.\",\n \"\",\n \"What you do:\",\n \"- Locate: given a task or question, find the relevant session (`session_list`, labels, cwd,\",\n \" recency) and read its transcript (`conversation_read`) before answering — never guess status.\",\n \"- Drive: re-prompt an idle/parked session onto its task with `agent_prompt`; restart a\",\n \" killed/exited session with `session_restart` when its work is clearly unfinished.\",\n \"- Spawn: only when no existing session owns the task — check for duplicates first.\",\n \"- Report: answer with short, concrete status (who, doing what, since when, blocked on what).\",\n \"\",\n \"Boundaries:\",\n \"- Never kill a session unless the human explicitly asks for that session.\",\n \"- A session's own repo conventions (review flow, merge policy) always win — you coordinate,\",\n \" you don't override.\",\n \"- If a session awaits a human decision, surface the question to the human instead of answering\",\n \" in their place.\",\n ].join(\"\\n\"),\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/**\n * The Session Watchdog — a durable daemon-health session, re-prompted on a\n * cron tick (~every 5 minutes). Distilled from the hand-run watchdog session\n * this app productizes: same classification rules, same hard boundaries\n * (never kills a live session, never spawns).\n *\n * The Ops Panel spawns it via `agent_start` with {@link WATCHDOG_LABEL} and\n * wires a `prompt-session` cron ({@link WATCHDOG_TICK_LABEL} /\n * {@link WATCHDOG_TICK_SCHEDULE}) pointing at the spawned session — see\n * `ui.ts`. The agent is also usable standalone via `app_run` for a one-shot\n * health pass.\n */\n\n/** Session label the panel spawns under — how a live watchdog is recognized. */\nexport const WATCHDOG_LABEL = \"Session Watchdog\"\n\n/** Label of the cron job that re-prompts the watchdog session. */\nexport const WATCHDOG_TICK_LABEL = \"watchdog-tick\"\n\n/** Every 5 minutes. */\nexport const WATCHDOG_TICK_SCHEDULE = \"*/5 * * * *\"\n\n/** The prompt each cron tick sends to the durable session. */\nexport const WATCHDOG_TICK_PROMPT = \"Run your maintenance check now.\"\n\nexport const watchdog: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/session-watchdog\",\n description:\n \"Durable daemon-health watchdog — classifies sessions (needs-you, stalled, duplicated, abandonable), corrects flags, archives dead rows. Never kills or spawns.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"NEVER kill a live session, message/interrupt another session's conversation, or spawn new sessions\",\n \"Housekeeping only on terminal-status sessions (exited/killed/error) — never touches a live one\",\n \"Report only what's NEW since the last tick — no padding, no repeats unless something got worse\",\n ],\n tools: [\n \"session_list\",\n \"session_events_poll\",\n \"session_flag_status\",\n \"session_archive\",\n \"session_gc\",\n ],\n }),\n body: [\n \"You are the Session Watchdog for this agentproto daemon. You run on a recurring cron tick\",\n \"(~every 5 minutes) as the SAME durable session, re-prompted each time — you retain memory of\",\n \"what you already flagged, so don't repeat an alert unless something got meaningfully worse or new.\",\n \"\",\n \"Each tick:\",\n \"1. Call `session_list` to see current session state (status, awaitingInput/awaitingQuestion,\",\n \" stalledSinceMs, busy, blockedOn, childrenBusy).\",\n \"2. Call `session_events_poll` for events since your last tick (stalled, exited, awaiting-input)\",\n \" to catch anything between ticks.\",\n \"3. Classify:\",\n \" - Needs you: `awaitingInput: true` with a real unanswered question, older than ~10 minutes.\",\n \" - Stalled: busy but silent well past normal turn latency, no legitimate blockedOn excuse.\",\n \" - Possibly duplicated: two or more live sessions share workspace/cwd and a highly similar\",\n \" recent prompt/label — use judgment, not exact string matching.\",\n \" - Looks done/abandonable: idle/parked with no plausible remaining purpose.\",\n \"4. Correct misclassifications when confident: if `session_flag_status` is available and you can\",\n \" tell a session is actually waiting on a human (or isn't, despite being flagged), call it with\",\n \" a clear `reason`. If that tool is unknown on this daemon build, skip silently.\",\n \"5. Autonomous housekeeping (reversible, so no need to ask): `session_archive` or `session_gc` on\",\n \" sessions already terminal-status (exited/killed/error) and old. Never touches a live session.\",\n \"6. NEVER: kill a live session, message/interrupt another session's conversation, or spawn new\",\n \" sessions. You only have these five tools on purpose — if you'd want another one, report the\",\n \" suggestion instead of working around it.\",\n \"7. Report only what's NEW since last tick — one line per finding. If nothing new, reply with a\",\n \" short \\\"nothing new\\\" — don't pad it.\",\n \"\",\n \"You are not a general coding assistant across ticks — stay scoped to this watchdog role.\",\n ].join(\"\\n\"),\n}\n","/**\n * Ops Panel — a self-contained MCP App panel for `ops-panel`.\n *\n * The daemon operations cockpit: special durable sessions (Session Watchdog,\n * Sessions Manager) as launchable cards, the session list filterable by\n * workspace with lifecycle actions, housekeeping (session GC, worktree GC)\n * and the cron ledger. Talks to the daemon only through the\n * `window.McpApp.connect()` bridge — every action round-trips through\n * `app_tool_call { appId, tool, args }`, gated by {@link OPS_PANEL_TOOLS}\n * (same convention as `session-viewer`).\n *\n * Special-session recognition is by spawn label (`Session Watchdog` /\n * `Sessions Manager` — see `agents/`): the panel finds the newest session\n * wearing the label, shows its status, and offers Launch / Restart / Kill.\n * Launching the watchdog also (re)wires its `watchdog-tick` cron — the\n * `prompt-session` job that re-prompts the durable session every 5 minutes —\n * deleting any stale tick first so a dead session id never keeps a failing\n * cron behind.\n */\n\nimport { manager, MANAGER_LABEL } from \"./agents/manager.js\"\nimport {\n watchdog,\n WATCHDOG_LABEL,\n WATCHDOG_TICK_LABEL,\n WATCHDOG_TICK_PROMPT,\n WATCHDOG_TICK_SCHEDULE,\n} from \"./agents/watchdog.js\"\n\nexport const OPS_PANEL_TOOLS = [\n \"daemon_health\",\n \"session_list\",\n \"agent_start\",\n \"agent_prompt\",\n \"agent_kill\",\n \"session_restart\",\n \"session_archive\",\n \"session_gc\",\n \"cron_list\",\n \"cron_create\",\n \"cron_delete\",\n \"cron_run\",\n \"worktree_gc\",\n] as const\n\nconst APP_ID = \"@agentproto/ops-panel\"\n\n/** What the panel needs to launch each special session, injected below. */\nconst SPECIALS = [\n {\n key: \"watchdog\",\n label: WATCHDOG_LABEL,\n tagline: \"Health ticks every 5 min — flags, archives, never kills.\",\n prompt: watchdog.body ?? \"\",\n cron: {\n label: WATCHDOG_TICK_LABEL,\n schedule: WATCHDOG_TICK_SCHEDULE,\n prompt: WATCHDOG_TICK_PROMPT,\n },\n },\n {\n key: \"manager\",\n label: MANAGER_LABEL,\n tagline: \"You talk to it, it drives the other sessions.\",\n prompt: manager.body ?? \"\",\n cron: null,\n },\n] as const\n\nexport const OPS_PANEL_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Ops Panel</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;--text3:#6e7681;\n --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;\n}\nhtml,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;flex-direction:column;height:100%;overflow:hidden}\n\n#topbar{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}\n#topbar h1{font-size:13px;font-weight:700;letter-spacing:.02em}\n.chip{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:600;background:var(--bg3);border:1px solid var(--border);color:var(--text2);padding:3px 9px;border-radius:999px}\n.chip .d{width:7px;height:7px;border-radius:50%;background:var(--text3);flex:none}\n.chip.ok .d{background:var(--green)}\n.chip.bad .d{background:var(--red)}\n#ws-select{margin-left:auto;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11.5px;font-family:inherit}\n#refresh-btn{background:none;border:1px solid var(--border);cursor:pointer;color:var(--text2);font-size:13px;line-height:1;padding:4px 8px;border-radius:6px;font-family:inherit}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n\n#body{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:16px}\n.sect h2{font-size:11px;font-weight:700;color:var(--text2);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;display:flex;align-items:center;gap:8px}\n.sect h2 .count{font-weight:600;color:var(--text3);text-transform:none;letter-spacing:0}\n\n#specials{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px}\n.card{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:12px}\n.card .ct{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700}\n.card .tagline{font-size:11px;color:var(--text2);margin-top:3px}\n.card .meta{font-size:10.5px;color:var(--text3);margin-top:7px;display:flex;flex-wrap:wrap;gap:5px 10px}\n.card .cronline{font-size:10.5px;margin-top:7px;display:flex;align-items:center;gap:6px;color:var(--text2)}\n.card .actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\n\n.badge{display:inline-block;padding:1px 6px;border-radius:10px;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}\n.b-run{background:rgba(63,185,80,.15);color:var(--green)}\n.b-busy{background:rgba(63,185,80,.25);color:var(--green)}\n.b-wait{background:rgba(88,166,255,.15);color:var(--blue)}\n.b-idle{background:rgba(139,148,158,.12);color:var(--text2)}\n.b-end{background:rgba(139,148,158,.1);color:var(--text3)}\n.b-err{background:rgba(248,81,73,.15);color:var(--red)}\n.b-warn{background:rgba(210,153,34,.15);color:var(--yellow)}\n\nbutton.act{background:var(--bg3);border:1px solid var(--border);color:var(--text);font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;cursor:pointer;font-family:inherit}\nbutton.act:hover{border-color:var(--text3)}\nbutton.act.primary{background:rgba(88,166,255,.15);border-color:rgba(88,166,255,.4);color:var(--blue)}\nbutton.act.danger{color:var(--red)}\nbutton.act.danger:hover{border-color:var(--red)}\nbutton.act.confirm{background:rgba(248,81,73,.2);border-color:var(--red);color:var(--red)}\nbutton.act:disabled{opacity:.45;cursor:default}\n\ntable{width:100%;border-collapse:collapse}\nth{font-size:10px;font-weight:700;color:var(--text3);text-transform:uppercase;letter-spacing:.04em;text-align:left;padding:4px 8px;border-bottom:1px solid var(--border)}\ntd{font-size:11.5px;padding:6px 8px;border-bottom:1px solid var(--bg3);vertical-align:middle}\ntr:hover td{background:var(--bg2)}\ntd .sn{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:260px;display:block}\ntd .sid{font-size:10px;color:var(--text3);font-family:Menlo,Monaco,monospace}\n.ws-tag{display:inline-block;font-size:10px;font-weight:600;background:var(--bg3);border:1px solid var(--border);border-radius:5px;padding:1px 6px;color:var(--text2)}\ntd.actions{white-space:nowrap;text-align:right}\ntd.actions button{margin-left:4px}\n\n.hk{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px}\n.hk .card .nums{display:flex;gap:14px;margin-top:8px}\n.hk .num{display:flex;flex-direction:column}\n.hk .num b{font-size:17px;font-weight:700}\n.hk .num span{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}\n\n#toast{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:8px 14px;font-size:11.5px;display:none;max-width:80%;z-index:10}\n#toast.err{border-color:var(--red);color:var(--red)}\n.empty{color:var(--text3);font-size:11.5px;padding:8px}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"topbar\">\n <h1>Agentproto Ops</h1>\n <span class=\"chip\" id=\"daemon-chip\"><span class=\"d\"></span><span id=\"daemon-txt\">daemon…</span></span>\n <select id=\"ws-select\"><option value=\"\">all workspaces</option></select>\n <button id=\"refresh-btn\" title=\"Refresh\" type=\"button\">↻ refresh</button>\n </div>\n <div id=\"body\">\n <div class=\"sect\">\n <h2>Special sessions</h2>\n <div id=\"specials\"></div>\n </div>\n <div class=\"sect\">\n <h2>Sessions <span class=\"count\" id=\"sess-count\"></span></h2>\n <div id=\"sessions\"></div>\n </div>\n <div class=\"sect\">\n <h2>Housekeeping</h2>\n <div class=\"hk\" id=\"housekeeping\"></div>\n </div>\n <div class=\"sect\">\n <h2>Crons <span class=\"count\" id=\"cron-count\"></span></h2>\n <div id=\"crons\"></div>\n </div>\n </div>\n</div>\n<div id=\"toast\"></div>\n<script>\nvar callTool = null;\nvar APP_ID = ${JSON.stringify(APP_ID)};\nvar SPECIALS = ${JSON.stringify(SPECIALS)};\n\n// app_tool_call wraps its dispatch result at least once (its own text-result\n// body is the JSON-stringified inner MCP tool result); peel back through\n// nested {content:[{type:\"text\",text:...}]} shells until the payload stops\n// looking like one — same convention as session-viewer.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try { cur = JSON.parse(cur); continue; } catch (e) { return cur; }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);\n}\n\nfunction escHtml(s) {\n return String(s == null ? \"\" : s).replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\nfunction fmtRelative(iso) {\n if (!iso) return \"\";\n var ms = Date.now() - new Date(iso).getTime();\n if (!isFinite(ms)) return \"\";\n var s = Math.round(ms / 1000);\n if (s < 5) return \"just now\";\n if (s < 60) return s + \"s ago\";\n var m = Math.round(s / 60);\n if (m < 60) return m + \"m ago\";\n var h = Math.round(m / 60);\n if (h < 24) return h + \"h ago\";\n return Math.round(h / 24) + \"d ago\";\n}\n\nvar toastTimer = null;\nfunction toast(msg, isErr) {\n var el = document.getElementById(\"toast\");\n el.textContent = msg;\n el.className = isErr ? \"err\" : \"\";\n el.style.display = \"block\";\n if (toastTimer) clearTimeout(toastTimer);\n toastTimer = setTimeout(function () { el.style.display = \"none\"; }, isErr ? 6000 : 3000);\n}\n\n// Two-click confirm: first click arms the button, second within 4s fires.\nvar armed = {};\nfunction confirmClick(key, btn, fn) {\n if (armed[key]) {\n clearTimeout(armed[key].t);\n delete armed[key];\n fn();\n return;\n }\n var prev = btn.textContent;\n btn.classList.add(\"confirm\");\n btn.textContent = \"sure?\";\n armed[key] = { t: setTimeout(function () {\n delete armed[key];\n btn.classList.remove(\"confirm\");\n btn.textContent = prev;\n }, 4000) };\n}\n\n// ============================================================\n// state\n// ============================================================\n\nvar sessions = [];\nvar crons = [];\nvar wsFilter = \"\";\nvar pollTimer = null;\nvar busyActions = false;\n\nfunction isLive(s) { return !!s && (s.status === \"running\" || s.status === \"starting\"); }\n\nfunction displayBadge(s) {\n if (s.status === \"running\") {\n if (s.busy) return { label: \"working\", cls: \"b-busy\" };\n if (s.awaitingInput) return { label: \"waiting\", cls: \"b-wait\" };\n return { label: \"idle\", cls: \"b-idle\" };\n }\n if (s.status === \"starting\") return { label: \"starting\", cls: \"b-wait\" };\n if (s.status === \"error\" || s.status === \"killed\") return { label: s.status, cls: \"b-err\" };\n return { label: s.status || \"exited\", cls: \"b-end\" };\n}\n\nfunction labelOf(s) {\n return s.label || s.title || s.name || (s.command ? String(s.command).split(\"/\").pop() : null) || s.id;\n}\n\nfunction specialSessionFor(sp) {\n var best = null;\n for (var i = 0; i < sessions.length; i++) {\n var s = sessions[i];\n if ((s.label || s.title) !== sp.label) continue;\n if (!best) { best = s; continue; }\n // Prefer a live one; among equals prefer the newest.\n if (isLive(s) && !isLive(best)) { best = s; continue; }\n if (isLive(s) === isLive(best) && String(s.startedAt) > String(best.startedAt)) best = s;\n }\n return best;\n}\n\nfunction cronByLabel(label) {\n var hits = [];\n for (var i = 0; i < crons.length; i++) if (crons[i].label === label) hits.push(crons[i]);\n return hits;\n}\n\n// ============================================================\n// data loading\n// ============================================================\n\nfunction loadAll() {\n return Promise.all([\n callApp(\"session_list\", { kind: \"all\" }).then(function (d) {\n sessions = (d && d.sessions) || (Array.isArray(d) ? d : []);\n }),\n callApp(\"cron_list\", {}).then(function (d) {\n crons = Array.isArray(d) ? d : (d && d.jobs) || [];\n }).catch(function () { crons = []; }),\n ]).then(function () {\n renderWorkspaces();\n renderSpecials();\n renderSessions();\n renderCrons();\n updateHousekeepingNums();\n });\n}\n\nfunction loadHealth() {\n return callApp(\"daemon_health\", {}).then(function (d) {\n var chip = document.getElementById(\"daemon-chip\");\n chip.className = \"chip ok\";\n var v = (d && (d.version || (d.build && d.build.sha) || d.status)) || \"ok\";\n document.getElementById(\"daemon-txt\").textContent = \"daemon \" + String(v).slice(0, 16);\n }).catch(function (e) {\n var chip = document.getElementById(\"daemon-chip\");\n chip.className = \"chip bad\";\n document.getElementById(\"daemon-txt\").textContent = \"daemon unreachable\";\n });\n}\n\n// Housekeeping card is built ONCE (so the worktree plan text and armed\n// confirm buttons survive the 5s poll); only the counters re-render — see\n// updateHousekeepingNums. The worktree plan is a separate dry-run call, on\n// demand only (it walks git state — not something to hammer on every poll).\nfunction buildHousekeeping() {\n var el = document.getElementById(\"housekeeping\");\n el.innerHTML =\n '<div class=\"card\"><div class=\"ct\">Sessions GC</div>'\n + '<div class=\"nums\" id=\"gc-nums\"></div>'\n + '<div class=\"actions\">'\n + '<button class=\"act\" id=\"gc-archive\">Archive >7d</button>'\n + '<button class=\"act danger\" id=\"gc-forget\">Forget >30d</button>'\n + '</div></div>'\n + '<div class=\"card\"><div class=\"ct\">Worktrees</div>'\n + '<div class=\"meta\" id=\"wt-plan\">No plan yet — run a dry run.</div>'\n + '<div class=\"actions\">'\n + '<button class=\"act\" id=\"wt-dry\">Dry run</button>'\n + '<button class=\"act danger\" id=\"wt-apply\" disabled>Reclaim now</button>'\n + '</div></div>';\n\n document.getElementById(\"gc-archive\").addEventListener(\"click\", function () {\n runAction(\"session_gc\", { olderThanDays: 7 }, \"GC: archived old terminal sessions\");\n });\n var forgetBtn = document.getElementById(\"gc-forget\");\n forgetBtn.addEventListener(\"click\", function () {\n confirmClick(\"gc-forget\", forgetBtn, function () {\n runAction(\"session_gc\", { olderThanDays: 30, forget: true }, \"GC: forgot >30d terminal sessions\");\n });\n });\n document.getElementById(\"wt-dry\").addEventListener(\"click\", function () { worktreePlan(false); });\n var applyBtn = document.getElementById(\"wt-apply\");\n applyBtn.addEventListener(\"click\", function () {\n confirmClick(\"wt-apply\", applyBtn, function () { worktreePlan(true); });\n });\n updateHousekeepingNums();\n}\n\nfunction updateHousekeepingNums() {\n var el = document.getElementById(\"gc-nums\");\n if (!el) return;\n var terminal = 0, live = 0;\n for (var i = 0; i < sessions.length; i++) {\n if (isLive(sessions[i])) live++;\n else terminal++;\n }\n el.innerHTML =\n '<div class=\"num\"><b>' + live + '</b><span>live</span></div>'\n + '<div class=\"num\"><b>' + terminal + '</b><span>terminal</span></div>';\n}\n\nfunction worktreePlan(apply) {\n var el = document.getElementById(\"wt-plan\");\n el.textContent = apply ? \"reclaiming\\\\u2026\" : \"planning\\\\u2026\";\n callApp(\"worktree_gc\", apply ? { apply: true } : {}).then(function (d) {\n var entries = (d && (d.plan || d.entries || d.worktrees)) || [];\n var counts = { reclaim: 0, salvage: 0, hold: 0 };\n for (var i = 0; i < entries.length; i++) {\n var c = entries[i].classification || entries[i].class || entries[i].action;\n if (counts[c] != null) counts[c]++;\n }\n el.textContent = (apply ? \"applied — \" : \"dry run — \")\n + counts.reclaim + \" reclaim / \" + counts.salvage + \" salvage / \" + counts.hold + \" hold\"\n + (entries.length === 0 ? \" (no linked worktrees)\" : \"\");\n document.getElementById(\"wt-apply\").disabled = counts.reclaim === 0 || apply;\n if (apply) toast(\"Worktree GC applied\");\n }).catch(function (e) {\n el.textContent = \"worktree_gc: \" + e.message;\n });\n}\n\n// ============================================================\n// rendering\n// ============================================================\n\nfunction renderWorkspaces() {\n var sel = document.getElementById(\"ws-select\");\n var seen = {};\n var slugs = [];\n for (var i = 0; i < sessions.length; i++) {\n var w = sessions[i].workspaceSlug;\n if (w && !seen[w]) { seen[w] = true; slugs.push(w); }\n }\n slugs.sort();\n var cur = wsFilter;\n var html = '<option value=\"\">all workspaces</option>';\n for (var j = 0; j < slugs.length; j++) {\n html += '<option value=\"' + escHtml(slugs[j]) + '\"' + (slugs[j] === cur ? \" selected\" : \"\") + '>' + escHtml(slugs[j]) + \"</option>\";\n }\n sel.innerHTML = html;\n}\n\nfunction renderSpecials() {\n var el = document.getElementById(\"specials\");\n var html = \"\";\n for (var i = 0; i < SPECIALS.length; i++) {\n var sp = SPECIALS[i];\n var s = specialSessionFor(sp);\n var badge = s ? displayBadge(s) : { label: \"not running\", cls: \"b-end\" };\n var meta = \"\";\n if (s) {\n meta += \"<span>\" + escHtml(s.id) + \"</span>\";\n if (s.startedAt) meta += \"<span>started \" + escHtml(fmtRelative(s.startedAt)) + \"</span>\";\n if (s.costUsd != null) meta += \"<span>$\" + Number(s.costUsd).toFixed(2) + \"</span>\";\n }\n var cronline = \"\";\n if (sp.cron) {\n var jobs = cronByLabel(sp.cron.label);\n if (jobs.length === 0) {\n cronline = '<span class=\"badge b-end\">no cron</span> tick not scheduled';\n } else {\n var job = jobs[0];\n var ok = !job.lastResult || job.lastResult.ok;\n var pointsAt = job.action && job.action.sessionId;\n var stale = s && isLive(s) && pointsAt && pointsAt !== s.id;\n cronline = '<span class=\"badge ' + (job.active ? (ok && !stale ? \"b-run\" : \"b-warn\") : \"b-end\") + '\">'\n + (job.active ? (ok && !stale ? \"tick ok\" : (stale ? \"tick stale\" : \"tick failing\")) : \"tick off\") + \"</span> \"\n + escHtml(job.schedule) + (pointsAt ? \" → \" + escHtml(pointsAt) : \"\")\n + (!ok && job.lastResult && job.lastResult.summary ? \" · \" + escHtml(String(job.lastResult.summary).slice(0, 60)) : \"\");\n }\n }\n var actions = \"\";\n if (!s || !isLive(s)) {\n actions += '<button class=\"act primary\" data-sp=\"' + sp.key + '\" data-do=\"launch\">Launch</button>';\n if (s) actions += '<button class=\"act\" data-sp=\"' + sp.key + '\" data-do=\"restart\">Restart last</button>';\n } else {\n if (sp.cron) {\n actions += '<button class=\"act\" data-sp=\"' + sp.key + '\" data-do=\"tick\">Tick now</button>';\n actions += '<button class=\"act\" data-sp=\"' + sp.key + '\" data-do=\"fixcron\">Re-wire cron</button>';\n } else {\n actions += '<button class=\"act\" data-sp=\"' + sp.key + '\" data-do=\"tick\">Ping</button>';\n }\n actions += '<button class=\"act danger\" data-sp=\"' + sp.key + '\" data-do=\"kill\">Kill</button>';\n }\n html += '<div class=\"card\">'\n + '<div class=\"ct\">' + escHtml(sp.label) + ' <span class=\"badge ' + badge.cls + '\">' + escHtml(badge.label) + \"</span></div>\"\n + '<div class=\"tagline\">' + escHtml(sp.tagline) + \"</div>\"\n + (meta ? '<div class=\"meta\">' + meta + \"</div>\" : \"\")\n + (cronline ? '<div class=\"cronline\">' + cronline + \"</div>\" : \"\")\n + '<div class=\"actions\">' + actions + \"</div>\"\n + \"</div>\";\n }\n el.innerHTML = html;\n var btns = el.querySelectorAll(\"button[data-sp]\");\n for (var k = 0; k < btns.length; k++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () { specialAction(btn.getAttribute(\"data-sp\"), btn.getAttribute(\"data-do\"), btn); });\n })(btns[k]);\n }\n}\n\nfunction renderSessions() {\n var el = document.getElementById(\"sessions\");\n var rows = [];\n for (var i = 0; i < sessions.length; i++) {\n var s = sessions[i];\n if (wsFilter && s.workspaceSlug !== wsFilter) continue;\n rows.push(s);\n }\n rows.sort(function (a, b) {\n var la = isLive(a) ? 1 : 0, lb = isLive(b) ? 1 : 0;\n if (la !== lb) return lb - la;\n return String(b.startedAt || \"\").localeCompare(String(a.startedAt || \"\"));\n });\n var liveCount = 0;\n for (var c = 0; c < rows.length; c++) if (isLive(rows[c])) liveCount++;\n document.getElementById(\"sess-count\").textContent = liveCount + \" live / \" + rows.length + \" shown\";\n if (rows.length === 0) {\n el.innerHTML = '<div class=\"empty\">No sessions' + (wsFilter ? \" in \" + escHtml(wsFilter) : \"\") + \".</div>\";\n return;\n }\n var html = \"<table><thead><tr><th>Session</th><th>Workspace</th><th>Status</th><th>Started</th><th>Cost</th><th></th></tr></thead><tbody>\";\n var shown = rows.slice(0, 80);\n for (var j = 0; j < shown.length; j++) {\n var s2 = shown[j];\n var db = displayBadge(s2);\n var acts = \"\";\n if (isLive(s2)) {\n acts += '<button class=\"act danger\" data-id=\"' + escHtml(s2.id) + '\" data-do=\"kill\">Kill</button>';\n } else {\n acts += '<button class=\"act\" data-id=\"' + escHtml(s2.id) + '\" data-do=\"restart\">Restart</button>';\n if (!s2.archived) acts += '<button class=\"act\" data-id=\"' + escHtml(s2.id) + '\" data-do=\"archive\">Archive</button>';\n }\n html += \"<tr>\"\n + '<td><span class=\"sn\" title=\"' + escHtml(labelOf(s2)) + '\">' + escHtml(labelOf(s2)) + '</span><span class=\"sid\">' + escHtml(s2.id) + \"</span></td>\"\n + \"<td>\" + (s2.workspaceSlug ? '<span class=\"ws-tag\">' + escHtml(s2.workspaceSlug) + \"</span>\" : \"\") + \"</td>\"\n + '<td><span class=\"badge ' + db.cls + '\">' + escHtml(db.label) + \"</span></td>\"\n + \"<td>\" + escHtml(fmtRelative(s2.startedAt)) + \"</td>\"\n + \"<td>\" + (s2.costUsd != null ? \"$\" + Number(s2.costUsd).toFixed(2) : \"\") + \"</td>\"\n + '<td class=\"actions\">' + acts + \"</td>\"\n + \"</tr>\";\n }\n html += \"</tbody></table>\";\n if (rows.length > shown.length) html += '<div class=\"empty\">… ' + (rows.length - shown.length) + \" more (narrow with the workspace filter)</div>\";\n el.innerHTML = html;\n var btns = el.querySelectorAll(\"button[data-id]\");\n for (var k = 0; k < btns.length; k++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () { sessionAction(btn.getAttribute(\"data-id\"), btn.getAttribute(\"data-do\"), btn); });\n })(btns[k]);\n }\n}\n\nfunction renderCrons() {\n var el = document.getElementById(\"crons\");\n document.getElementById(\"cron-count\").textContent = crons.length ? crons.length + \" jobs\" : \"\";\n if (crons.length === 0) {\n el.innerHTML = '<div class=\"empty\">No cron jobs.</div>';\n return;\n }\n var html = \"<table><thead><tr><th>Label</th><th>Schedule</th><th>State</th><th>Last</th><th>Next</th><th></th></tr></thead><tbody>\";\n for (var i = 0; i < crons.length; i++) {\n var c = crons[i];\n var last = c.lastResult == null ? \"\" : (c.lastResult.ok ? \"ok\" : \"failed\");\n var lastCls = c.lastResult == null ? \"b-end\" : (c.lastResult.ok ? \"b-run\" : \"b-err\");\n html += \"<tr>\"\n + '<td><span class=\"sn\">' + escHtml(c.label || c.id) + \"</span></td>\"\n + \"<td>\" + escHtml(c.schedule) + \"</td>\"\n + '<td><span class=\"badge ' + (c.active ? \"b-run\" : \"b-end\") + '\">' + (c.active ? \"active\" : \"off\") + \"</span></td>\"\n + \"<td>\" + (last ? '<span class=\"badge ' + lastCls + '\" title=\"' + escHtml((c.lastResult && c.lastResult.summary) || \"\") + '\">' + last + \"</span>\" : \"\") + \"</td>\"\n + \"<td>\" + escHtml(c.nextRunAt ? fmtRelative(c.nextRunAt).replace(\" ago\", \"\") : \"\") + \"</td>\"\n + '<td class=\"actions\">'\n + '<button class=\"act\" data-cron=\"' + escHtml(c.id) + '\" data-do=\"run\">Run</button>'\n + '<button class=\"act danger\" data-cron=\"' + escHtml(c.id) + '\" data-do=\"delete\">Delete</button>'\n + \"</td></tr>\";\n }\n html += \"</tbody></table>\";\n el.innerHTML = html;\n var btns = el.querySelectorAll(\"button[data-cron]\");\n for (var k = 0; k < btns.length; k++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () { cronAction(btn.getAttribute(\"data-cron\"), btn.getAttribute(\"data-do\"), btn); });\n })(btns[k]);\n }\n}\n\n// ============================================================\n// actions\n// ============================================================\n\nfunction runAction(tool, args, okMsg) {\n if (busyActions) return Promise.resolve();\n busyActions = true;\n return callApp(tool, args).then(function (d) {\n busyActions = false;\n if (okMsg) toast(okMsg);\n return loadAll().then(function () { return d; });\n }).catch(function (e) {\n busyActions = false;\n toast(tool + \": \" + e.message, true);\n throw e;\n });\n}\n\nfunction findSpecial(key) {\n for (var i = 0; i < SPECIALS.length; i++) if (SPECIALS[i].key === key) return SPECIALS[i];\n return null;\n}\n\nfunction rewireTickCron(sp, sessionId) {\n if (!sp.cron) return Promise.resolve();\n var stale = cronByLabel(sp.cron.label);\n var chain = Promise.resolve();\n for (var i = 0; i < stale.length; i++) {\n (function (jobId) {\n chain = chain.then(function () { return callApp(\"cron_delete\", { jobId: jobId }); });\n })(stale[i].id);\n }\n return chain.then(function () {\n return callApp(\"cron_create\", {\n schedule: sp.cron.schedule,\n recurring: true,\n label: sp.cron.label,\n action: { kind: \"prompt-session\", sessionId: sessionId, prompt: sp.cron.prompt },\n });\n });\n}\n\nfunction specialAction(key, what, btn) {\n var sp = findSpecial(key);\n if (!sp) return;\n var s = specialSessionFor(sp);\n if (what === \"launch\") {\n btn.disabled = true;\n callApp(\"agent_start\", {\n adapter: \"claude-code\",\n label: sp.label,\n prompt: sp.prompt,\n keepAlive: true,\n }).then(function (d) {\n var id = d && d.id;\n toast(sp.label + \" launched\" + (id ? \" (\" + id + \")\" : \"\"));\n return id && sp.cron ? rewireTickCron(sp, id) : null;\n }).then(function () { return loadAll(); })\n .catch(function (e) { toast(\"launch: \" + e.message, true); btn.disabled = false; });\n return;\n }\n if (what === \"restart\" && s) {\n btn.disabled = true;\n callApp(\"session_restart\", { idOrName: s.id }).then(function (d) {\n var id = (d && d.id) || s.id;\n toast(sp.label + \" restarted\");\n return sp.cron ? rewireTickCron(sp, id) : null;\n }).then(function () { return loadAll(); })\n .catch(function (e) { toast(\"restart: \" + e.message, true); btn.disabled = false; });\n return;\n }\n if (what === \"tick\" && s) {\n var prompt = sp.cron ? sp.cron.prompt : \"Status check: report where things stand, briefly.\";\n runAction(\"agent_prompt\", { sessionId: s.id, prompt: prompt }, sp.label + \" prompted\");\n return;\n }\n if (what === \"fixcron\" && s) {\n btn.disabled = true;\n rewireTickCron(sp, s.id).then(function () {\n toast(\"Cron re-wired to \" + s.id);\n return loadAll();\n }).catch(function (e) { toast(\"cron: \" + e.message, true); btn.disabled = false; });\n return;\n }\n if (what === \"kill\" && s) {\n confirmClick(\"kill-\" + s.id, btn, function () {\n runAction(\"agent_kill\", { sessionId: s.id }, sp.label + \" killed\");\n });\n }\n}\n\nfunction sessionAction(id, what, btn) {\n if (what === \"kill\") {\n confirmClick(\"kill-\" + id, btn, function () {\n runAction(\"agent_kill\", { sessionId: id }, \"Killed \" + id);\n });\n return;\n }\n if (what === \"restart\") { runAction(\"session_restart\", { idOrName: id }, \"Restarted \" + id); return; }\n if (what === \"archive\") { runAction(\"session_archive\", { idOrName: id }, \"Archived \" + id); }\n}\n\nfunction cronAction(jobId, what, btn) {\n if (what === \"run\") { runAction(\"cron_run\", { jobId: jobId }, \"Cron fired\"); return; }\n if (what === \"delete\") {\n confirmClick(\"cron-\" + jobId, btn, function () {\n runAction(\"cron_delete\", { jobId: jobId }, \"Cron deleted\");\n });\n }\n}\n\n// ============================================================\n// boot\n// ============================================================\n\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () { loadHealth(); loadAll(); });\ndocument.getElementById(\"ws-select\").addEventListener(\"change\", function (e) {\n wsFilter = e.target.value;\n renderSessions();\n});\n\nwindow.McpApp.connect()\n .then(function (bridge) {\n callTool = bridge.callTool;\n buildHousekeeping();\n loadHealth();\n return loadAll();\n })\n .then(function () {\n pollTimer = setInterval(loadAll, 5000);\n })\n .catch(function (err) {\n document.getElementById(\"body\").innerHTML =\n '<div class=\"empty\">Standalone mode \\\\u2014 no host bridge (' + escHtml(err.message) + \")</div>\";\n });\n</script>\n</body>\n</html>`\n","/**\n * `ops-panel` — the agentproto daemon operations cockpit as an app.\n *\n * Two durable \"special sessions\" ship as agents — the Session Watchdog\n * (cron-ticked health checks: classify, flag, archive; never kills) and the\n * Sessions Manager (the coordinator the human talks to, which drives the\n * other sessions). The panel (`ui.ts`) launches/restarts them by label,\n * wires the watchdog's `watchdog-tick` cron, lists every session filterable\n * by workspace with lifecycle actions, and fronts housekeeping: session GC,\n * worktree GC (dry-run first), and the cron ledger.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { manager } from \"./agents/manager.js\"\nimport { watchdog } from \"./agents/watchdog.js\"\nimport { OPS_PANEL_HTML, OPS_PANEL_TOOLS } from \"./ui.js\"\n\nexport const opsPanel: AppHandle = defineApp({\n id: \"@agentproto/ops-panel\",\n name: \"Ops Panel\",\n version: \"0.1.0\",\n description: \"Daemon ops cockpit — watchdog & manager sessions, workspace-filtered session list, GC and cron housekeeping.\",\n agents: [watchdog, manager],\n ui: {\n html: OPS_PANEL_HTML,\n title: \"Ops Panel\",\n tools: [...OPS_PANEL_TOOLS],\n },\n})\n\nexport { watchdog, manager }\nexport { WATCHDOG_LABEL, WATCHDOG_TICK_LABEL, WATCHDOG_TICK_PROMPT, WATCHDOG_TICK_SCHEDULE } from \"./agents/watchdog.js\"\nexport { MANAGER_LABEL } from \"./agents/manager.js\"\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\nexport const narrator: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/session-narrator\",\n description:\n \"Reads a daemon session's transcript and narrates it in plain English — what happened, what was decided, what's left.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Read-only — never spawn, kill, resume, or write to any session\",\n \"Narrate only what the transcript actually shows, never speculate about intent\",\n ],\n tools: [\"conversation_read\", \"session_list\"],\n workflows: [{ ref: \"narrate-session\" }],\n }),\n body: [\n \"You narrate a daemon session's conversation for a human who doesn't want to read the raw transcript.\",\n \"\",\n \"Given a session id or name, call `conversation_read` with `format: \\\"json\\\"` to get the\",\n \"structured turn list (`meta` + `messages`, each `{ role, text, toolCalls?, ts }`). If the\",\n \"session id is ambiguous or unknown, call `session_list` first to resolve it by label.\",\n \"\",\n \"Write a short plain-English narration:\",\n \"- What the user asked for.\",\n \"- What the assistant did, grouped into a few natural beats (not a line-by-line tool log).\",\n \"- Any open question, decision point, or unfinished step at the end of the transcript.\",\n \"\",\n \"Skip tool-call plumbing details unless they're the point (e.g. a failing command, a real\",\n \"decision). If `conversation.conversation` comes back null, say plainly that this session has\",\n \"no readable conversation (e.g. a bare shell PTY) instead of guessing.\",\n ].join(\"\\n\"),\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\nexport const narrateSession = defineWorkflow({\n id: \"narrate-session\",\n name: \"Narrate Session\",\n description: \"Read a daemon session's transcript and narrate it in plain English.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"narrate\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/session-narrator\" },\n prompt:\n \"Narrate the session named in the run input (or the most recently started session if \" +\n \"none was given): what the user asked for, what the assistant did, and what — if \" +\n \"anything — is still open at the end of the transcript.\",\n },\n ],\n})\n","/**\n * Session Viewer — a self-contained MCP App panel for `session-viewer`.\n *\n * Talks to the daemon only through the `window.McpApp.connect()` bridge\n * (never a direct `fetch`): every action round-trips through the app's own\n * `app_tool_call` gateway tool, `{ appId, tool, args }` — same convention as\n * `media-viewer`/`mail-triage`. Read-only by design: `session_list` picks a\n * session (live or finished), `conversation_read` renders it as a clean\n * conversation — no spawn/kill/resume action lives here.\n *\n * Live sessions are polled (`conversation_read` has no push channel over\n * this bridge — see the PR's feasibility note on a real event/PTY stream);\n * a finished session is fetched once and left alone.\n */\n\nexport const SESSION_VIEWER_TOOLS = [\"session_list\", \"conversation_read\"] as const\n\nconst APP_ID = \"@agentproto/session-viewer\"\n\nexport const SESSION_VIEWER_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Session Viewer</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;--text3:#6e7681;\n --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;\n}\nhtml,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;height:100%;overflow:hidden}\n\n/* sidebar */\n#sidebar{width:250px;min-width:190px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}\n#sidebar-hdr{padding:10px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px}\n#sidebar-hdr h1{font-size:11px;font-weight:700;color:var(--text2);text-transform:uppercase;letter-spacing:.06em;flex:1}\n#archived-toggle{display:flex;align-items:center;gap:5px;font-size:10.5px;color:var(--text2);padding:6px 12px;border-bottom:1px solid var(--border);cursor:pointer;user-select:none}\n#archived-toggle input{cursor:pointer}\n#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:14px;line-height:1;padding:2px 4px;border-radius:4px;font-family:inherit}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n#session-list{flex:1;overflow-y:auto;padding:4px 0}\n.si{padding:8px 12px;cursor:pointer;border-left:2px solid transparent}\n.si:hover{background:var(--bg3)}\n.si.active{background:var(--bg3);border-left-color:var(--blue)}\n.si .sn{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.si .sm{font-size:10.5px;color:var(--text2);margin-top:3px;display:flex;gap:5px;align-items:center;flex-wrap:wrap}\n.badge{display:inline-block;padding:1px 6px;border-radius:10px;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}\n.b-run{background:rgba(63,185,80,.15);color:var(--green)}\n.b-wait{background:rgba(88,166,255,.15);color:var(--blue)}\n.b-idle{background:rgba(139,148,158,.12);color:var(--text2)}\n.b-end{background:rgba(139,148,158,.1);color:var(--text3)}\n.b-err{background:rgba(248,81,73,.15);color:var(--red)}\n#empty-sidebar{padding:14px 12px;color:var(--text2);font-size:11.5px}\n\n/* main */\n#main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}\n#header{padding:12px 18px;border-bottom:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n#header .htitle{font-size:14px;font-weight:700;display:flex;align-items:center;gap:8px}\n.pulse{width:7px;height:7px;border-radius:50%;background:var(--green);flex:none;box-shadow:0 0 0 0 rgba(63,185,80,.4);animation:pulse 2s infinite}\n@keyframes pulse{70%{box-shadow:0 0 0 6px rgba(63,185,80,0)}100%{box-shadow:0 0 0 0 rgba(63,185,80,0)}}\n#header .hfacts{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}\n.fact{font-size:10.5px;font-weight:600;background:var(--bg3);border:1px solid var(--border);color:var(--text2);padding:3px 9px;border-radius:999px}\n#empty-main,#loading-main,#error-main{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text2);gap:8px;text-align:center;padding:24px}\n#empty-main .ico,#loading-main .ico{font-size:30px}\n#error-main{color:var(--red)}\n\n/* transcript */\n#transcript{flex:1;overflow-y:auto;padding:14px 18px;display:flex;flex-direction:column;gap:10px}\n.msg{display:flex;flex-direction:column;max-width:78%}\n.msg.role-user{align-self:flex-end;align-items:flex-end}\n.msg.role-assistant,.msg.role-tool{align-self:flex-start;align-items:flex-start}\n.msg.role-system{align-self:center;max-width:90%}\n.bubble{border-radius:10px;padding:9px 12px;font-size:13px;line-height:1.55;white-space:pre-wrap;word-break:break-word}\n.role-user .bubble{background:var(--blue);color:#04101c;border-bottom-right-radius:3px}\n.role-assistant .bubble{background:var(--bg2);border:1px solid var(--border);border-bottom-left-radius:3px}\n.role-system .bubble{background:transparent;border:1px dashed var(--border);color:var(--text2);font-size:11.5px;text-align:center}\n.mrow{display:flex;align-items:center;gap:6px;margin-bottom:3px}\n.mrole{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--text3)}\n.mtime{font-size:10px;color:var(--text3);font-variant-numeric:tabular-nums}\n\n.chip{margin-top:6px;border:1px solid var(--border);border-radius:8px;background:var(--bg3);overflow:hidden;cursor:pointer;width:100%}\n.chip .ch{padding:6px 10px;font-size:11.5px;display:flex;align-items:center;gap:7px;color:var(--text2)}\n.chip .ch .ci{color:var(--yellow);flex:none}\n.chip.tool-result .ch .ci{color:var(--purple)}\n.chip .ch .cname{font-weight:600;color:var(--text)}\n.chip .ch .cprev{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text3)}\n.chip .ch .cchev{flex:none;transition:transform .12s;color:var(--text3);font-size:10px}\n.chip.open .ch .cchev{transform:rotate(90deg)}\n.chip pre{display:none;margin:0;border-top:1px solid var(--border);padding:9px 11px;font-family:Menlo,Monaco,'Courier New',monospace;font-size:11px;line-height:1.5;color:var(--text2);white-space:pre-wrap;word-break:break-word;max-height:320px;overflow:auto}\n.chip.open pre{display:block}\n.reasoning{margin-bottom:6px;font-size:11.5px;font-style:italic;color:var(--text2);border-left:2px solid var(--border);padding-left:8px}\n\n#statusbar{padding:5px 18px;font-size:10.5px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0;display:flex;justify-content:space-between}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"sidebar\">\n <div id=\"sidebar-hdr\">\n <h1>Sessions</h1>\n <button id=\"refresh-btn\" title=\"Refresh\" type=\"button\">↻</button>\n </div>\n <label id=\"archived-toggle\"><input type=\"checkbox\" id=\"archived-check\"> show archived</label>\n <div id=\"session-list\"></div>\n </div>\n <div id=\"main\">\n <div id=\"header\" style=\"display:none\"></div>\n <div id=\"empty-main\"><div class=\"ico\">💬</div><div>Pick a session to read its conversation</div></div>\n <div id=\"transcript\" style=\"display:none\"></div>\n <div id=\"statusbar\" style=\"display:none\">\n <span id=\"status-left\"></span>\n <span id=\"status-right\"></span>\n </div>\n </div>\n</div>\n<script>\nvar callTool = null;\nvar APP_ID = ${JSON.stringify(APP_ID)};\n\n// app_tool_call wraps its dispatch result at least once (its own text-result\n// body is the JSON-stringified inner MCP tool result); peel back through\n// nested {content:[{type:\"text\",text:...}]} shells until the payload stops\n// looking like one — works whichever layer the host's bridge already\n// unwrapped. With conversation_read's format:\"json\" this resolves straight\n// to the { meta, messages } object; with session_list, straight to\n// { sessions: [...] }.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try { cur = JSON.parse(cur); continue; } catch (e) { return cur; }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);\n}\n\nfunction escHtml(s) {\n return String(s == null ? \"\" : s).replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\nfunction fmtTime(ts) {\n if (!ts) return \"\";\n try { return new Date(ts).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\" }); }\n catch (e) { return \"\"; }\n}\n\nfunction fmtClock(iso) {\n if (!iso) return \"\";\n try { return new Date(iso).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" }); }\n catch (e) { return \"\"; }\n}\n\nfunction fmtRelative(iso) {\n if (!iso) return \"\";\n var ms = Date.now() - new Date(iso).getTime();\n if (!isFinite(ms)) return \"\";\n var s = Math.round(ms / 1000);\n if (s < 5) return \"just now\";\n if (s < 60) return s + \"s ago\";\n var m = Math.round(s / 60);\n if (m < 60) return m + \"m ago\";\n var h = Math.round(m / 60);\n if (h < 24) return h + \"h ago\";\n return Math.round(h / 24) + \"d ago\";\n}\n\nfunction fmtDuration(startIso, endIso) {\n var end = endIso ? new Date(endIso).getTime() : Date.now();\n var start = new Date(startIso).getTime();\n if (!isFinite(start) || !isFinite(end)) return \"\";\n var s = Math.max(0, Math.round((end - start) / 1000));\n if (s < 60) return s + \"s\";\n var m = Math.floor(s / 60);\n if (m < 60) return m + \"m \" + (s % 60) + \"s\";\n var h = Math.floor(m / 60);\n return h + \"h \" + (m % 60) + \"m\";\n}\n\nfunction prettyArgs(raw) {\n if (raw == null) return \"\";\n if (typeof raw !== \"string\") return JSON.stringify(raw, null, 2);\n try { return JSON.stringify(JSON.parse(raw), null, 2); } catch (e) { return raw; }\n}\n\nfunction firstLine(s, max) {\n var t = String(s == null ? \"\" : s).split(\"\\\\n\")[0];\n return t.length > max ? t.slice(0, max) + \"\\\\u2026\" : t;\n}\n\n// ============================================================\n// state\n// ============================================================\n\nvar sessions = [];\nvar activeId = null;\nvar includeArchived = false;\nvar currentMeta = null;\nvar lastMessageCount = -1;\nvar sidebarTimer = null;\nvar convoTimer = null;\n\nfunction findSession(id) {\n for (var i = 0; i < sessions.length; i++) if (sessions[i].id === id) return sessions[i];\n return null;\n}\n\nfunction isLive(s) {\n return !!s && (s.status === \"running\" || s.status === \"starting\");\n}\n\n// ============================================================\n// sidebar\n// ============================================================\n\nfunction displayBadge(s) {\n if (s.status === \"running\") {\n if (s.busy) return { label: \"working\", cls: \"b-run\" };\n if (s.awaitingInput) return { label: \"waiting\", cls: \"b-wait\" };\n return { label: \"idle\", cls: \"b-idle\" };\n }\n if (s.status === \"starting\") return { label: \"starting\", cls: \"b-wait\" };\n if (s.status === \"error\" || s.status === \"killed\") return { label: s.status, cls: \"b-err\" };\n return { label: s.status || \"exited\", cls: \"b-end\" };\n}\n\nfunction renderSidebar() {\n var el = document.getElementById(\"session-list\");\n if (sessions.length === 0) {\n el.innerHTML = '<div id=\"empty-sidebar\">No sessions yet.</div>';\n return;\n }\n var html = \"\";\n for (var i = 0; i < sessions.length; i++) {\n var s = sessions[i];\n var label = s.label || s.title || s.name || (s.command ? String(s.command).split(\"/\").pop() : null) || s.id.slice(0, 10);\n var db = displayBadge(s);\n var active = s.id === activeId ? \" active\" : \"\";\n html += '<div class=\"si' + active + '\" data-id=\"' + escHtml(s.id) + '\">'\n + '<div class=\"sn\">' + escHtml(label) + \"</div>\"\n + '<div class=\"sm\">'\n + '<span class=\"badge ' + db.cls + '\">' + escHtml(db.label) + \"</span>\"\n + \"<span>\" + escHtml(s.kind || \"\") + \"</span>\"\n + \"<span>\" + escHtml(fmtRelative(s.startedAt)) + \"</span>\"\n + \"</div></div>\";\n }\n el.innerHTML = html;\n var rows = el.querySelectorAll(\".si\");\n for (var j = 0; j < rows.length; j++) {\n rows[j].addEventListener(\"click\", (function (id) { return function () { selectSession(id); }; })(rows[j].getAttribute(\"data-id\")));\n }\n}\n\nfunction loadSessions() {\n return callApp(\"session_list\", { kind: \"all\", includeArchived: includeArchived }).then(function (data) {\n sessions = (data && data.sessions) || [];\n renderSidebar();\n if (activeId) {\n var hdr = document.getElementById(\"header\");\n if (hdr.style.display !== \"none\") renderHeader();\n }\n }).catch(function (e) {\n document.getElementById(\"status-left\").textContent = \"sessions: \" + e.message;\n });\n}\n\n// ============================================================\n// header\n// ============================================================\n\nfunction renderHeader() {\n var s = findSession(activeId);\n var meta = currentMeta || {};\n var el = document.getElementById(\"header\");\n el.style.display = \"block\";\n\n var title = meta.title || (s && (s.label || s.title)) || activeId;\n var live = s && isLive(s);\n var pulse = live ? '<span class=\"pulse\"></span>' : \"\";\n\n var facts = [];\n if (meta.model || (s && s.model)) facts.push(escHtml(meta.model || (s && s.model)));\n if (s && s.adapterSlug) facts.push(escHtml(s.adapterSlug));\n if (s && s.kind) facts.push(escHtml(s.kind));\n if (s) { var db = displayBadge(s); facts.push(escHtml(db.label)); }\n if (s && s.cwd) facts.push('<span title=\"' + escHtml(s.cwd) + '\">' + escHtml(s.cwd.length > 46 ? \"\\\\u2026\" + s.cwd.slice(-44) : s.cwd) + \"</span>\");\n var started = (meta.startedAt || (s && s.startedAt));\n if (started) facts.push(\"started \" + escHtml(fmtClock(started)) + \" (\" + escHtml(fmtRelative(started)) + \")\");\n var ended = meta.endedAt || (s && s.endedAt);\n if (ended) facts.push(\"ran \" + escHtml(fmtDuration(started, ended)));\n else if (started) facts.push(\"running \" + escHtml(fmtDuration(started)));\n if (meta.messageCount != null) facts.push(meta.messageCount + \" messages\");\n if (meta.toolCallCount != null) facts.push(meta.toolCallCount + \" tool calls\");\n if (meta.tokens && (meta.tokens.input != null || meta.tokens.output != null)) {\n facts.push(\"tokens \" + (meta.tokens.input || 0) + \"\\\\u2192\" + (meta.tokens.output || 0));\n }\n if (meta.costUsd != null) facts.push(\"$\" + Number(meta.costUsd).toFixed(4));\n\n el.innerHTML = '<div class=\"htitle\">' + pulse + escHtml(title) + \"</div>\"\n + '<div class=\"hfacts\">' + facts.map(function (f) { return '<span class=\"fact\">' + f + \"</span>\"; }).join(\"\") + \"</div>\";\n}\n\n// ============================================================\n// transcript\n// ============================================================\n\nfunction renderToolCallChip(tc) {\n var args = prettyArgs(tc.args);\n return '<div class=\"chip tool-call\"><div class=\"ch\"><span class=\"ci\">▸</span>'\n + '<span class=\"cname\">' + escHtml(tc.name || \"tool\") + \"</span>\"\n + '<span class=\"cprev\">' + escHtml(firstLine(args, 64)) + \"</span>\"\n + '<span class=\"cchev\">▸</span></div><pre>' + escHtml(args) + \"</pre></div>\";\n}\n\nfunction renderToolResultChip(m) {\n var text = m.text || \"\";\n return '<div class=\"chip tool-result\"><div class=\"ch\"><span class=\"ci\">🔧</span>'\n + '<span class=\"cname\">' + escHtml(m.toolName || \"result\") + \"</span>\"\n + '<span class=\"cprev\">' + escHtml(firstLine(text, 64)) + \"</span>\"\n + '<span class=\"cchev\">▸</span></div><pre>' + escHtml(text) + \"</pre></div>\";\n}\n\nfunction renderMessage(m) {\n var time = m.ts ? '<span class=\"mtime\">' + fmtTime(m.ts) + \"</span>\" : \"\";\n if (m.role === \"system\") {\n return '<div class=\"msg role-system\"><div class=\"bubble\">' + escHtml(m.text || \"\") + \"</div></div>\";\n }\n if (m.role === \"tool\") {\n return '<div class=\"msg role-tool\">' + renderToolResultChip(m) + \"</div>\";\n }\n if (m.role === \"user\") {\n return '<div class=\"msg role-user\"><div class=\"mrow\"><span class=\"mrole\">You</span>' + time + \"</div>\"\n + '<div class=\"bubble\">' + escHtml(m.text || \"\") + \"</div></div>\";\n }\n // assistant\n var inner = \"\";\n if (m.reasoning) inner += '<div class=\"reasoning\">' + escHtml(m.reasoning) + \"</div>\";\n if (m.text) inner += escHtml(m.text);\n var body = inner ? '<div class=\"bubble\">' + inner + \"</div>\" : \"\";\n var chips = (m.toolCalls || []).map(renderToolCallChip).join(\"\");\n return '<div class=\"msg role-assistant\"><div class=\"mrow\"><span class=\"mrole\">Assistant</span>' + time + \"</div>\"\n + body + chips + \"</div>\";\n}\n\nfunction renderTranscript(messages) {\n var el = document.getElementById(\"transcript\");\n var atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;\n el.style.display = \"flex\";\n document.getElementById(\"empty-main\").style.display = \"none\";\n var errEl = document.getElementById(\"error-main\");\n if (errEl) errEl.style.display = \"none\";\n if (messages.length === 0) {\n el.innerHTML = '<div style=\"color:var(--text2);text-align:center;padding:20px\">(empty conversation)</div>';\n return;\n }\n el.innerHTML = messages.map(renderMessage).join(\"\");\n if (atBottom) el.scrollTop = el.scrollHeight;\n\n var chips = el.querySelectorAll(\".chip\");\n for (var i = 0; i < chips.length; i++) {\n chips[i].querySelector(\".ch\").addEventListener(\"click\", (function (chip) {\n return function () { chip.classList.toggle(\"open\"); };\n })(chips[i]));\n }\n}\n\nfunction renderNoConversation(reason) {\n document.getElementById(\"transcript\").style.display = \"none\";\n document.getElementById(\"empty-main\").style.display = \"none\";\n var el = document.getElementById(\"error-main\");\n el.style.display = \"flex\";\n el.innerHTML = '<div class=\"ico\">🚫</div><div>No readable conversation for this session'\n + (reason ? '<br><span style=\"font-size:11px;color:var(--text3)\">' + escHtml(reason) + \"</span>\" : \"\")\n + \"</div>\";\n}\n\n// ============================================================\n// selection + polling\n// ============================================================\n\nfunction clearConvoPoll() {\n if (convoTimer) { clearTimeout(convoTimer); convoTimer = null; }\n}\n\nfunction scheduleConvoPoll() {\n clearConvoPoll();\n var s = findSession(activeId);\n if (!isLive(s)) return;\n convoTimer = setTimeout(function () {\n loadConversation(activeId, { silent: true });\n }, 3500);\n}\n\nfunction loadConversation(id, opts) {\n opts = opts || {};\n var requestedId = id;\n if (!opts.silent) {\n document.getElementById(\"transcript\").style.display = \"none\";\n document.getElementById(\"empty-main\").style.display = \"none\";\n var errEl = document.getElementById(\"error-main\");\n errEl.style.display = \"flex\";\n errEl.innerHTML = '<div class=\"ico\">⌛</div><div>Loading\\\\u2026</div>';\n }\n document.getElementById(\"status-left\").textContent = \"loading\\\\u2026\";\n return callApp(\"conversation_read\", { idOrName: id, format: \"json\" }).then(function (data) {\n if (activeId !== requestedId) return; // switched away meanwhile\n if (!data || !Array.isArray(data.messages)) {\n currentMeta = null;\n renderHeader();\n renderNoConversation(data && data.reason);\n clearConvoPoll();\n document.getElementById(\"status-left\").textContent = \"no conversation\";\n return;\n }\n currentMeta = data.meta || {};\n lastMessageCount = data.messages.length;\n renderHeader();\n renderTranscript(data.messages);\n document.getElementById(\"status-left\").textContent = data.messages.length + \" messages\";\n document.getElementById(\"status-right\").textContent = \"updated \" + fmtTime(Date.now());\n scheduleConvoPoll();\n }).catch(function (e) {\n if (activeId !== requestedId) return;\n document.getElementById(\"status-left\").textContent = \"error: \" + e.message;\n if (!opts.silent) renderNoConversation(e.message);\n });\n}\n\nfunction selectSession(id) {\n if (activeId === id) return;\n activeId = id;\n currentMeta = null;\n lastMessageCount = -1;\n clearConvoPoll();\n renderSidebar();\n document.getElementById(\"statusbar\").style.display = \"flex\";\n renderHeader();\n loadConversation(id, {});\n}\n\n// ============================================================\n// boot\n// ============================================================\n\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () {\n loadSessions();\n if (activeId) loadConversation(activeId, {});\n});\ndocument.getElementById(\"archived-check\").addEventListener(\"change\", function (e) {\n includeArchived = !!e.target.checked;\n loadSessions();\n});\n\nwindow.McpApp.connect()\n .then(function (bridge) {\n callTool = bridge.callTool;\n return loadSessions();\n })\n .then(function () {\n sidebarTimer = setInterval(loadSessions, 5000);\n if (!activeId && sessions.length > 0) selectSession(sessions[0].id);\n })\n .catch(function (err) {\n document.getElementById(\"empty-main\").innerHTML =\n '<div class=\"ico\">⚠</div><div>Standalone mode \\\\u2014 no host bridge (' + escHtml(err.message) + \")</div>\";\n });\n</script>\n</body>\n</html>`\n","/**\n * `session-viewer` — a single-agent agentproto app for reading daemon\n * sessions as a clean conversation.\n *\n * The panel (`ui.ts`) picks a session — live or finished — from\n * `session_list` and renders its transcript via `conversation_read`: user/\n * assistant turns, collapsible tool calls, timestamps, a metadata header.\n * Read-only, no spawn/kill/resume action. The bundled `session-narrator`\n * agent is a complementary plain-English narrator over the same\n * `conversation_read` data — usable standalone via `app_run`, independent\n * of the panel.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { narrator } from \"./agents/narrator.js\"\nimport { narrateSession } from \"./workflows/narrate-session.js\"\nimport { SESSION_VIEWER_HTML, SESSION_VIEWER_TOOLS } from \"./ui.js\"\n\nexport const sessionViewer: AppHandle = defineApp({\n id: \"@agentproto/session-viewer\",\n name: \"Session Viewer\",\n version: \"0.1.0\",\n description: \"Read a daemon session as a clean conversation — turns, tool calls, timestamps.\",\n agents: [narrator],\n workflows: [narrateSession],\n ui: {\n html: SESSION_VIEWER_HTML,\n title: \"Session Viewer\",\n tools: [...SESSION_VIEWER_TOOLS],\n },\n})\n\nexport { narrator, narrateSession }\n","#!/usr/bin/env node\n/**\n * `agentproto-apps-sync` — emit every bundled `@agentproto/apps` app to disk\n * and write a catalog of what landed where.\n *\n * Each app's `AGENT.md` / `WORKFLOW.md` / `APP.md` manifests are written\n * under `<baseDir>/<slug>` via `AppHandle.emit` (see `@agentproto/app-kit`'s\n * `emitApp`); the catalog is a flat index a host can read without walking\n * every app's directory (`app_install`'s own discovery path already reads\n * each `APP.md` individually — this is a cheaper \"what's on disk\" summary\n * for the sync bin's own log, not a replacement for that).\n */\n\nimport { mkdir, writeFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join, dirname } from \"node:path\"\nimport { parseArgs } from \"node:util\"\nimport type { AppHandle } from \"@agentproto/app-kit\"\nimport { codeTeam } from \"../code-team/index.js\"\nimport { contentTeam } from \"../content-team/index.js\"\nimport { mailTriage } from \"../mail-triage/index.js\"\nimport { mediaViewer } from \"../media-viewer/index.js\"\nimport { opsPanel } from \"../ops-panel/index.js\"\nimport { sessionViewer } from \"../session-viewer/index.js\"\n\ninterface CatalogEntry {\n readonly appId: string\n readonly name: string\n readonly description: string\n readonly dir: string\n readonly category: string\n}\n\nconst APPS: readonly { readonly slug: string; readonly category: string; readonly handle: AppHandle }[] = [\n { slug: \"code-team\", category: \"team\", handle: codeTeam },\n { slug: \"content-team\", category: \"team\", handle: contentTeam },\n { slug: \"mail-triage\", category: \"app\", handle: mailTriage },\n { slug: \"media-viewer\", category: \"app\", handle: mediaViewer },\n { slug: \"ops-panel\", category: \"app\", handle: opsPanel },\n { slug: \"session-viewer\", category: \"app\", handle: sessionViewer },\n]\n\nexport async function syncApps(baseDir: string): Promise<CatalogEntry[]> {\n const catalog: CatalogEntry[] = []\n for (const { slug, category, handle } of APPS) {\n const dir = join(baseDir, slug)\n await handle.emit(dir)\n catalog.push({\n appId: handle.id ?? slug,\n name: handle.name ?? slug,\n description: handle.description ?? \"\",\n dir,\n category,\n })\n console.log(`emitted ${handle.id ?? slug} -> ${dir}`)\n }\n return catalog\n}\n\nexport async function writeCatalog(baseDir: string, catalog: readonly CatalogEntry[]): Promise<string> {\n const catalogPath = join(baseDir, \"..\", \"app-catalog.json\")\n await mkdir(dirname(catalogPath), { recursive: true })\n await writeFile(catalogPath, JSON.stringify({ apps: catalog }, null, 2) + \"\\n\", \"utf8\")\n return catalogPath\n}\n\nasync function main(argv: readonly string[]): Promise<void> {\n const { values } = parseArgs({\n args: [...argv],\n allowPositionals: false,\n strict: true,\n options: { \"base-dir\": { type: \"string\" } },\n })\n const baseDir = values[\"base-dir\"] ?? join(homedir(), \".agentproto\", \"apps\")\n\n await mkdir(baseDir, { recursive: true })\n const catalog = await syncApps(baseDir)\n const catalogPath = await writeCatalog(baseDir, catalog)\n console.log(`wrote catalog (${catalog.length} apps) -> ${catalogPath}`)\n}\n\nif (import.meta.url === `file://${process.argv[1]}`) {\n main(process.argv.slice(2)).catch(err => {\n console.error(err instanceof Error ? err.message : String(err))\n process.exitCode = 1\n })\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/code-team/agents/implementer.ts","../../src/code-team/agents/reviewer.ts","../../src/code-team/agents/fixer.ts","../../src/code-team/workflows/deliver-change.ts","../../src/code-team/index.ts","../../src/content-team/agents/researcher.ts","../../src/content-team/agents/writer.ts","../../src/content-team/agents/editor.ts","../../src/content-team/agents/illustrator.ts","../../src/content-team/workflows/produce-content.ts","../../src/content-team/workflows/produce-cover.ts","../../src/content-team/index.ts","../../src/mail-triage/agents/triager.ts","../../src/mail-triage/workflows/triage-inbox.ts","../../src/mail-triage/ui.ts","../../src/mail-triage/index.ts","../../src/media-viewer/agents/cataloger.ts","../../src/media-viewer/workflows/scan-media.ts","../../src/media-viewer/ui.ts","../../src/media-viewer/index.ts","../../src/ops-panel/agents/manager.ts","../../src/ops-panel/agents/watchdog.ts","../../src/ops-panel/ui.ts","../../src/ops-panel/index.ts","../../src/config/fragment.ts","../../src/config/ui.ts","../../src/config/index.ts","../../src/session-viewer/agents/narrator.ts","../../src/session-viewer/workflows/narrate-session.ts","../../src/session-viewer/ui.ts","../../src/session-viewer/index.ts","../../src/bin/sync.ts"],"names":["defineAgent","defineWorkflow","defineApp","APP_ID","SECTIONS"],"mappings":"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A;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","file":"sync.mjs","sourcesContent":["import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Writes the requested change. */\nexport const implementer: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/implementer\",\n description: \"Implements the requested code change.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Change only what the task asks\", \"Match the surrounding style\"],\n tools: [\"list_dir\", \"read_file\", \"write_file\", \"edit_file\", \"run_command\"],\n workflows: [{ ref: \"deliver-change\" }],\n }),\n body:\n \"You implement the requested change. Read the task and the surrounding code \" +\n \"first, make the smallest correct change that satisfies it, then run the \" +\n \"tests. Do not touch anything the task didn't ask for.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Reviews the diff; reports only. */\nexport const reviewer: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/reviewer\",\n description: \"Reviews the diff and reports findings.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Report findings only\", \"Never edit files\", \"Never run gh pr merge\"],\n // Read-only + run_command (to inspect the diff / run tests). No write/edit\n // tools — the \"never edit files\" boundary is enforced by capability too.\n tools: [\"list_dir\", \"read_file\", \"run_command\"],\n workflows: [{ ref: \"deliver-change\" }],\n }),\n body:\n \"You are a rigorous reviewer. Read the diff, and report each real issue as \" +\n \"`file:line — why`. Change nothing; you only report.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Applies the reviewer's findings, precisely and minimally. */\nexport const fixer: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/fixer\",\n description: \"Applies the reviewer's findings, precisely and minimally.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Apply only the listed findings\", \"No unrelated refactors\"],\n tools: [\"list_dir\", \"read_file\", \"write_file\", \"edit_file\", \"run_command\"],\n workflows: [{ ref: \"deliver-change\" }],\n }),\n body:\n \"You apply the reviewer's findings. Address each listed finding exactly, add \" +\n \"nothing extra, and re-run the tests when done.\",\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/**\n * The one workflow the code team delivers a change through. Every team agent\n * references it; the app-kit attachment invariant keeps that honest.\n *\n * Three declarative `kind:\"agent\"` steps — implementer → reviewer → fixer —\n * one per team agent, each `agent.ref` resolving (at compile time, against\n * this app's own install record) to that agent's emitted AGENT.md. This\n * replaces an earlier `ToolStep`-based draft (`apply_patch`/`read_diff`/\n * `run_tests`) that dispatched daemon-side, where those tool ids don't\n * exist — the daemon has no `apply_patch` tool, only live agent sessions\n * that can edit files. Agent steps are the adoption fix (WP-B4): each step\n * spawns the named team agent as a real session and lets it act.\n */\nexport const deliverChange = defineWorkflow({\n id: \"deliver-change\",\n name: \"Deliver a code change\",\n description: \"Implement the change, review the diff, apply fixes, verify.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"implement\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/implementer\" },\n prompt: \"Implement the requested change.\",\n },\n {\n id: \"review\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/reviewer\" },\n prompt:\n \"Review the diff the implementer just produced. Report every real issue as `file:line — why`.\",\n },\n {\n id: \"fix\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/fixer\" },\n prompt: \"Apply the reviewer's findings, then re-run the tests.\",\n },\n ],\n})\n","/**\n * `code-team` — a ready-made agentproto app: a small team that ships a code\n * change. Three agents (implementer → reviewer → fixer) bound to one delivery\n * workflow, declared with `@agentproto/app-kit`.\n *\n * Generic on purpose — `@agentproto/…` ids, no product dependency, no home\n * workspace. A host imports it and uses any subset of its agents/workflows:\n * in-process via `codeTeam.toMastraAgents(...)`, or on disk via\n * `codeTeam.emit(dir)`.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { implementer } from \"./agents/implementer.js\"\nimport { reviewer } from \"./agents/reviewer.js\"\nimport { fixer } from \"./agents/fixer.js\"\nimport { deliverChange } from \"./workflows/deliver-change.js\"\n\nexport const codeTeam: AppHandle = defineApp({\n id: \"@agentproto/code-team\",\n name: \"Code Team\",\n version: \"0.1.0\",\n description: \"Implement, review, and fix code changes.\",\n agents: [implementer, reviewer, fixer],\n workflows: [deliverChange],\n})\n\nexport { implementer, reviewer, fixer, deliverChange }\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Gathers sources for the piece. */\nexport const researcher: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/researcher\",\n description: \"Reads the provided sources and summarizes them for the writer.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Cite every claim\", \"Prefer primary sources\"],\n tools: [\"list_dir\", \"read_file\", \"run_command\"],\n workflows: [{ ref: \"produce-content\" }],\n }),\n body:\n \"You read the source material already in the workspace (list_dir, then \" +\n \"read_file the sources), extract the key facts, and hand the writer a tight \" +\n \"brief with a citation for every claim. Use run_command only to fetch a URL \" +\n \"already named in the task; do not invent sources.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Drafts the piece from the brief. */\nexport const writer: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/writer\",\n description: \"Drafts the piece from the researcher's brief.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Stay on the brief\", \"One idea per paragraph\"],\n tools: [\"list_dir\", \"read_file\", \"write_file\", \"edit_file\"],\n workflows: [{ ref: \"produce-content\" }],\n }),\n body:\n \"You draft the piece from the brief and its sources. Lead with the point, \" +\n \"keep one idea per paragraph, and don't invent facts the brief doesn't have.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Tightens the draft. */\nexport const editor: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/editor\",\n description: \"Edits the draft for voice, clarity, and length.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"Preserve the writer's meaning\", \"Cut, don't pad\"],\n tools: [\"read_file\", \"edit_file\"],\n workflows: [{ ref: \"produce-content\" }],\n }),\n body:\n \"You edit the draft. Tighten the voice, cut fluff, fix clarity — without \" +\n \"changing what the writer meant. Leave it shorter than you found it.\",\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Art-directs the cover illustration for each article. */\nexport const illustrator: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/illustrator\",\n description: \"Art-directs the cover illustration for each article.\",\n model: \"claude-sonnet-5\",\n boundaries: [\"One cover per article\", \"Text-free prompts only\"],\n tools: [\"read_file\", \"write_file\", \"run_command\"],\n workflows: [{ ref: \"produce-cover\" }],\n }),\n body:\n \"You art-direct a single cover illustration for the article. \" +\n \"Read the article (read_file) to extract the one visual idea that best \" +\n \"represents the piece, then write the art direction (write_file) as a \" +\n \"clean, text-free image prompt. Keep the visual discipline tight: bold \" +\n \"flat shapes, limited risograph-style palettes, strong negative space, and \" +\n \"a single focal subject. Never include text, lettering, logos, watermarks, \" +\n \"or UI chrome in the prompt. Produce exactly one cover per article; if the \" +\n \"article calls for a literal diagram or chart, fall back to a precise \" +\n \"gpt-image-1 style description and treat the diagram itself as the image \" +\n \"subject. Use run_command only to inspect generated assets or move files \" +\n \"during the upload step.\",\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/**\n * The one workflow the content team produces a piece through. Every team\n * agent references it (app-kit attachment invariant).\n *\n * Three declarative `kind:\"agent\"` steps — researcher → writer → editor —\n * one per team agent, each `agent.ref` resolving (at compile time, against\n * this app's own install record) to that agent's emitted AGENT.md. This\n * replaces an earlier `ToolStep`-based draft (`read_file`/`write_file`/\n * `edit_file`/`run_command`) that dispatched daemon-side, where those tool\n * ids don't exist — they're agent workspace tools (adapter-side), never\n * daemon tool ids, so `app_install` rejected them. Agent steps are the\n * adoption fix: each step spawns the named team agent as a real session,\n * which uses its own workspace tools to read, draft, and edit.\n */\nexport const produceContent = defineWorkflow({\n id: \"produce-content\",\n name: \"Produce a piece of content\",\n description: \"Research the sources, draft the piece, then edit it.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"research\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/researcher\" },\n prompt:\n \"Read the source material already in the workspace and hand off a tight \" +\n \"brief with a citation for every claim.\",\n },\n {\n id: \"draft\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/writer\" },\n prompt: \"Draft the piece from the researcher's brief and its sources.\",\n },\n {\n id: \"edit\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/editor\" },\n prompt: \"Edit the draft for voice, clarity, and length, then finalize it.\",\n },\n ],\n})\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/**\n * The one workflow the illustrator uses to produce a cover.\n *\n * A single declarative `kind:\"agent\"` step — `agent.ref` resolving (at\n * compile time, against this app's own install record) to the illustrator's\n * emitted AGENT.md. This replaces an earlier `ToolStep`-based draft\n * (`content.cover-concept`/`content.cover-base-gen`/`content.cover-cleanup`/\n * `content.cover-upload`) naming tools that don't exist anywhere — no driver\n * implements them. The illustrator agent folds all four phases (concept,\n * base generation, cleanup, upload) into one prompt for a single session\n * turn, matching the tools it already has (`read_file`, `write_file`,\n * `run_command`).\n */\nexport const produceCover = defineWorkflow({\n id: \"produce-cover\",\n name: \"Produce a cover illustration\",\n description:\n \"Read the article, derive the cover concept, generate the base image, \" +\n \"clean it up, and upload the final cover.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"illustrate\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/illustrator\" },\n prompt:\n \"Produce the cover for this article in one pass: (1) read the article \" +\n \"and derive the single visual concept that best represents it, (2) \" +\n \"write the art direction as a clean, text-free image prompt and \" +\n \"generate the base image from it, (3) clean up the generated image \" +\n \"(crop, color, artifacts), then (4) upload the final cover to the \" +\n \"workspace's output location.\",\n },\n ],\n})\n","/**\n * `content-team` — a ready-made agentproto app: a small team that produces a\n * piece of content. Three agents (researcher → writer → editor) bound to one\n * production workflow, declared with `@agentproto/app-kit`.\n *\n * Generic on purpose — `@agentproto/…` ids, no product dependency. A host\n * imports it and uses any subset of its agents/workflows.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { researcher } from \"./agents/researcher.js\"\nimport { writer } from \"./agents/writer.js\"\nimport { editor } from \"./agents/editor.js\"\nimport { illustrator } from \"./agents/illustrator.js\"\nimport { produceContent } from \"./workflows/produce-content.js\"\nimport { produceCover } from \"./workflows/produce-cover.js\"\n\nexport const contentTeam: AppHandle = defineApp({\n id: \"@agentproto/content-team\",\n name: \"Content Team\",\n version: \"0.1.0\",\n description: \"Research, draft, edit, and illustrate content.\",\n agents: [researcher, writer, editor, illustrator],\n workflows: [produceContent, produceCover],\n})\n\nexport { researcher, writer, editor, illustrator, produceContent, produceCover }\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/** Triages your inbox — finds unread mail, categorizes, labels, archives. */\nexport const triager: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/triager\",\n description: \"Triages your inbox — finds unread mail, categorizes, labels, archives.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Always show the triage plan before applying\",\n \"Never delete — only archive, label, or mark read\",\n \"Ask before applying bulk actions on more than 20 messages\",\n ],\n tools: [\n \"mailbox_list\",\n \"mailbox_search\",\n \"mailbox_list_threads\",\n \"mailbox_get_thread\",\n \"mailbox_labels_list\",\n \"mailbox_label_create\",\n \"mailbox_triage_plan\",\n \"mailbox_triage_apply\",\n ],\n workflows: [{ ref: \"triage-inbox\" }],\n }),\n body:\n \"You start by calling mailbox_list to discover connected mailboxes. Pick one \" +\n \"with `capabilities.triage: true` (triage requires the gmail.modify scope). \" +\n \"Every subsequent mailbox tool requires the `mailbox` parameter (UUID or alias \" +\n \"from this list).\\n\" +\n \"\\n\" +\n \"Scan the mailbox for unread messages using mailbox_search with \" +\n 'query=\"is:unread\" and the mailbox from step 1. Categorize them ' +\n \"(urgent, needs-reply, newsletter, notification, spam). Create one triage plan \" +\n \"per bulk action with mailbox_triage_plan — its exact contract is \" +\n '`{ mailbox, criteria: { query } | { messageIds }, action: { type: \"markRead\"|\"archive\"|\"trash\"|\"label\", addLabelIds?, removeLabelIds? } }` ' +\n \"— e.g. label each category, archive newsletters+notifications, keep \" +\n \"urgent+needs-reply in inbox. Each plan returns a `plan_id`, a `count`, and a \" +\n \"`sample`; it expires after 15 minutes. Show the plan before applying. Execute \" +\n \"only after user confirmation, with mailbox_triage_apply \" +\n \"`{ plan_id, confirm: true }` — that is the sole mutation path.\",\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\n/** Single-agent workflow: triage the inbox. */\nexport const triageInbox = defineWorkflow({\n id: \"triage-inbox\",\n name: \"Triage Inbox\",\n description: \"Scan inbox, categorize unread messages, create and apply triage plan.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"triage\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/triager\" },\n prompt:\n \"Call mailbox_list to discover connected mailboxes, then scan the \" +\n \"selected mailbox for unread messages using mailbox_search with \" +\n 'query=\"is:unread\". Categorize them (urgent, needs-reply, newsletter, ' +\n \"notification, spam). Create a triage plan: label each category, archive \" +\n \"newsletters+notifications, keep urgent+needs-reply in inbox. \" +\n \"Show me the plan before applying.\",\n },\n ],\n})\n","/**\n * Mail Triage — a self-contained MCP App dashboard for `mail-triage`.\n *\n * Talks to the daemon only through the `window.McpApp.connect()` bridge\n * (never a direct `fetch`): every action round-trips through the app's own\n * `app_tool_call` gateway tool, `{ appId, tool, args }`. Mailbox access goes\n * through an imported agentpush MCP server (`imported:<alias>/<tool>` ids);\n * running the triager agent itself goes through the daemon's own\n * `app_run` / `app_status` / `agent_output` — a second path alongside the\n * deterministic plan/apply tools, so the panel can either drive the mailbox\n * tools directly or hand the whole job to the agent and watch it work.\n *\n * The agentpush alias is NOT hardcoded to one server: `app_tool_call`\n * enforces an exact-match allowlist (`ui.tools`), so every candidate alias\n * must be baked into `MAIL_TRIAGE_TOOLS` at emit time. The candidate list\n * comes from `MAIL_TRIAGE_MCP_ALIASES` (overridable via the\n * `MAIL_TRIAGE_MCP_ALIASES` env var, comma-separated, read when this module\n * is evaluated — i.e. at emit). At runtime the panel probes each candidate's\n * `mailbox_list` and activates the first alias that answers with mailboxes;\n * a selector appears in the toolbar when several respond.\n */\n\nconst ALIAS_TOOL_NAMES = [\n \"mailbox_list\",\n \"mailbox_search\",\n \"mailbox_triage_plan\",\n \"mailbox_triage_apply\",\n] as const\n\nconst DEFAULT_MCP_ALIASES = [\"agentpush-prod\", \"agentpush\"] as const\n\nfunction envAliases(): string[] | null {\n if (typeof process === \"undefined\" || !process.env) return null\n const raw = process.env[\"MAIL_TRIAGE_MCP_ALIASES\"]\n if (!raw) return null\n const list = raw\n .split(\",\")\n .map(s => s.trim())\n .filter(Boolean)\n return list.length > 0 ? list : null\n}\n\n/** Candidate imported-MCP aliases the dashboard may reach mailboxes through. */\nexport const MAIL_TRIAGE_MCP_ALIASES: readonly string[] = envAliases() ?? [...DEFAULT_MCP_ALIASES]\n\nexport const MAIL_TRIAGE_TOOLS: readonly string[] = [\n ...MAIL_TRIAGE_MCP_ALIASES.flatMap(alias => ALIAS_TOOL_NAMES.map(t => `imported:${alias}/${t}`)),\n \"app_run\",\n \"app_status\",\n \"agent_output\",\n \"app_list\",\n]\n\nconst APP_ID = \"@agentproto/mail-triage\"\nconst AGENT_ID = \"@agentproto/triager\"\nconst CATEGORIES = [\"urgent\", \"needs-reply\", \"newsletter\", \"notification\"]\n\nexport const MAIL_TRIAGE_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Mail Triage</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\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;flex-direction:column;height:100%;overflow-y:auto}\n#toolbar{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2);flex-shrink:0;flex-wrap:wrap}\n#toolbar .title{font-size:13px;font-weight:600;flex:1}\nselect,input[type=text]{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 8px;border-radius:4px;font-size:12px;font-family:inherit}\n.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}\n.abtn:hover{background:var(--border)}\n.abtn:disabled{opacity:.4;cursor:default}\n.abtn.primary{background:var(--blue);border-color:var(--blue);color:#04101c}\n.abtn.primary:hover{opacity:.9}\n#caps{padding:6px 14px;display:flex;gap:6px;align-items:center;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}\n.badge{font-size:11px;padding:2px 8px;border-radius:10px;border:1px solid var(--border);color:var(--text2)}\n.badge.on{color:var(--green);border-color:var(--green)}\n.badge.off{opacity:.5;text-decoration:line-through}\n#content{padding:14px;display:flex;flex-direction:column;gap:16px}\nsection{background:var(--bg2);border:1px solid var(--border);border-radius:6px;padding:12px}\nsection h3{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px;display:flex;justify-content:space-between;align-items:center}\n#summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px}\n.tile{background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:10px;text-align:center}\n.tile .count{font-size:22px;font-weight:700}\n.tile .label{font-size:11px;color:var(--text2);text-transform:capitalize}\n.tile.unread .count{color:var(--green)}\n.tile.urgent .count{color:var(--red)}\n.tile.needs-reply .count{color:var(--yellow)}\n.tile.newsletter .count{color:var(--blue)}\n.tile.notification .count{color:var(--purple)}\n#plan-list,#search-results,#log-events,#runs-list,#connect-hint-list{max-height:260px;overflow-y:auto;font-size:12px}\n.plan-item{display:flex;justify-content:space-between;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)}\n.plan-item .subj{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.plan-item .tag{color:var(--text2);font-size:11px;white-space:nowrap}\n.runs-item{display:flex;gap:10px;align-items:center;padding:6px 0;border-bottom:1px solid var(--border);cursor:pointer}\n.runs-item:hover{background:var(--bg3)}\n.runs-item .rid{font-family:Menlo,Monaco,monospace;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}\n.runs-item .st{font-size:11px;white-space:nowrap}\n.runs-item .st.running{color:var(--yellow)}\n.runs-item .st.ended{color:var(--green)}\n.runs-item .meta{color:var(--text2);font-size:11px;white-space:nowrap}\n#log-tail{font-family:Menlo,Monaco,monospace;white-space:pre-wrap;word-break:break-word;background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:8px;max-height:260px;overflow-y:auto}\n.searchbar{display:flex;gap:8px;margin-bottom:8px}\n.searchbar input{flex:1}\n.actions{display:flex;gap:8px;margin-top:10px}\n#status{padding:6px 14px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n.empty{color:var(--text2);padding:8px 0}\n.mini{font-size:10px;text-transform:none;letter-spacing:0;cursor:pointer;color:var(--blue);background:none;border:none;font-family:inherit}\n.caps-hint{font-size:11px;color:var(--yellow)}\n.snippet{color:var(--text2);font-size:11px;margin-top:2px}\n.mail-checkbox{accent-color:var(--blue);cursor:pointer;flex-shrink:0;margin-top:1px}\n.mail-open{font-size:10px;cursor:pointer;color:var(--blue);background:none;border:none;font-family:inherit;white-space:nowrap;padding:0;flex-shrink:0}\n.mail-open:hover{text-decoration:underline}\n#selection-bar{display:none;margin-bottom:8px;gap:8px;align-items:center}\n#selection-bar.show{display:flex}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"toolbar\">\n <span class=\"title\">Mail Triage</span>\n <select id=\"alias-select\" style=\"display:none\" title=\"MCP server\"></select>\n <select id=\"mailbox-select\"></select>\n <button class=\"abtn\" id=\"refresh-btn\">Refresh</button>\n </div>\n <div id=\"caps\" style=\"display:none\"></div>\n <div id=\"content\">\n <section id=\"connect-hint\" style=\"display:none\">\n <h3>Connect a mailbox</h3>\n <div id=\"connect-hint-list\"></div>\n <div class=\"empty\">Import/start an agentpush MCP server and connect a Gmail account in agentpush, then retry.</div>\n <div class=\"actions\">\n <button class=\"abtn primary\" id=\"connect-retry-btn\">Retry</button>\n </div>\n </section>\n\n <section>\n <h3>Inbox summary</h3>\n <div id=\"summary\"></div>\n </section>\n\n <section>\n <h3>Search</h3>\n <div class=\"searchbar\">\n <input type=\"text\" id=\"search-input\" placeholder=\"e.g. is:unread\">\n <button class=\"abtn\" id=\"search-btn\">Search</button>\n </div>\n <div id=\"selection-bar\">\n <span id=\"sel-count\" style=\"font-size:12px;color:var(--text2)\">0 selected</span>\n <button class=\"abtn primary\" data-action=\"send-selection\">Send selection to Claude</button>\n </div>\n <div id=\"search-results\"><div class=\"empty\">No search run yet.</div></div>\n </section>\n\n <section>\n <h3>Triage plan</h3>\n <div class=\"searchbar\">\n <input type=\"text\" id=\"plan-query\" placeholder=\"criteria query\" value=\"is:unread category:promotions older_than:7d\">\n <select id=\"plan-action\">\n <option value=\"markRead\">mark read</option>\n <option value=\"archive\">archive</option>\n <option value=\"label\">label…</option>\n <option value=\"trash\">trash</option>\n </select>\n <input type=\"text\" id=\"plan-label\" placeholder=\"label name\" style=\"display:none\">\n </div>\n <div class=\"actions\">\n <button class=\"abtn primary\" id=\"triage-btn\">Build plan</button>\n <button class=\"abtn\" id=\"apply-btn\" disabled>Apply</button>\n </div>\n <div id=\"plan-list\" style=\"margin-top:10px\"><div class=\"empty\">No plan yet — click Build plan.</div></div>\n </section>\n\n <section>\n <h3>Triager agent</h3>\n <div class=\"actions\">\n <button class=\"abtn\" id=\"run-agent-btn\">Run Triager Agent</button>\n </div>\n <div id=\"log-events\" style=\"margin-top:10px\"><div class=\"empty\">Agent has not run yet.</div></div>\n <div id=\"log-tail\" style=\"margin-top:6px\"></div>\n </section>\n\n <section>\n <h3><span>Past runs</span><button class=\"mini\" id=\"runs-refresh-btn\">refresh</button></h3>\n <div id=\"runs-list\"><div class=\"empty\">No runs yet.</div></div>\n </section>\n </div>\n <div id=\"status\">Connecting…</div>\n</div>\n<script>\nvar APP_ID = ${JSON.stringify(APP_ID)};\nvar AGENT_ID = ${JSON.stringify(AGENT_ID)};\nvar CATEGORIES = ${JSON.stringify(CATEGORIES)};\nvar ALIASES = ${JSON.stringify(MAIL_TRIAGE_MCP_ALIASES)};\nvar POLL_TIMEOUT_MS = 10 * 60 * 1000;\nvar callTool = null;\nvar updateModelContext = null;\nvar openLink = null;\nvar onTeardown = null;\nvar currentPlan = null;\nvar pollHandle = null;\nvar planExpiryHandle = null;\nvar activeAlias = ALIASES[0];\nvar currentMailboxes = [];\nvar categoryCounts = null;\nvar unreadCount = null;\nvar aliasStates = {};\nvar selectedMessages = {};\n\nfunction setStatus(msg) {\n document.getElementById(\"status\").textContent = msg;\n}\n\n// See media-viewer/ui.ts for why this loop exists: app_tool_call wraps its\n// dispatch result in at least one more {content:[{type:\"text\",text:...}]}\n// shell than the underlying tool returned.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try {\n cur = JSON.parse(cur);\n continue;\n } catch (e) {\n return cur;\n }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n if (!callTool) {\n return Promise.reject(new Error(\"Not connected to host bridge (standalone mode).\"));\n }\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(function (result) {\n var value = unwrapText(result);\n if (value && typeof value === \"object\" && !Array.isArray(value) && typeof value.error === \"string\") {\n throw new Error(value.error);\n }\n return value;\n });\n}\n\nfunction aliasTool(name) {\n return \"imported:\" + activeAlias + \"/\" + name;\n}\n\n// Disables a button and swaps its label to an ellipsis while a call is in\n// flight, so double-submit is impossible; restores the original label after.\nfunction setBusy(btn, busy) {\n if (busy) {\n if (btn.getAttribute(\"data-label\") === null) btn.setAttribute(\"data-label\", btn.textContent);\n btn.disabled = true;\n btn.textContent = \"\\\\u2026\";\n } else {\n btn.disabled = false;\n var label = btn.getAttribute(\"data-label\");\n if (label !== null) btn.textContent = label;\n }\n}\n\nfunction isSelected(id) {\n return selectedMessages[id] ? true : false;\n}\n\nfunction toggleSelection(id, item) {\n if (selectedMessages[id]) {\n delete selectedMessages[id];\n } else {\n selectedMessages[id] = item;\n }\n updateSelectionBar();\n}\n\nfunction updateSelectionBar() {\n var bar = document.getElementById(\"selection-bar\");\n var countEl = document.getElementById(\"sel-count\");\n var count = 0;\n for (var k in selectedMessages) { if (selectedMessages.hasOwnProperty(k)) count++; }\n countEl.textContent = count + \" selected\";\n bar.className = count > 0 ? \"show\" : \"\";\n // Sync checkboxes\n var cbs = document.querySelectorAll(\"#search-results .mail-checkbox\");\n for (var i = 0; i < cbs.length; i++) {\n var mid = cbs[i].getAttribute(\"data-message-id\");\n cbs[i].checked = mid ? isSelected(mid) : false;\n }\n}\n\nfunction sendSelectionToClaude() {\n if (!updateModelContext) {\n setStatus(\"Host does not support updateModelContext.\");\n return;\n }\n var items = [];\n var names = [];\n for (var k in selectedMessages) {\n if (!selectedMessages.hasOwnProperty(k)) continue;\n var msg = selectedMessages[k];\n items.push({\n id: msg.id || k,\n threadId: msg.threadId || \"\",\n from: msg.from || \"\",\n subject: msg.subject || \"\",\n timestamp: msg.internalDate || msg.date || \"\"\n });\n if (msg.subject) names.push(msg.subject);\n }\n if (items.length === 0) {\n setStatus(\"No messages selected.\");\n return;\n }\n var summary = items.length + \" emails selected for triage: \" + names.join(\", \");\n setStatus(\"Sending selection to Claude\\\\u2026\");\n updateModelContext({\n content: [{ type: \"text\", text: summary }],\n structuredContent: { selection: items }\n }).then(function () {\n setStatus(\"Selection sent to Claude (\" + items.length + \" emails).\");\n }).catch(function (err) {\n setStatus(\"Failed to send selection: \" + (err && err.message ? err.message : String(err)));\n });\n}\n\nfunction clearPlanExpiry() {\n if (planExpiryHandle) {\n clearTimeout(planExpiryHandle);\n planExpiryHandle = null;\n }\n}\n\nfunction onPlanExpired() {\n planExpiryHandle = null;\n currentPlan = null;\n document.getElementById(\"apply-btn\").disabled = true;\n setStatus(\"Plan expired \\\\u2014 rebuild it.\");\n}\n\n// Arms a timeout that invalidates the plan at its server-declared expires_at\n// (plans expire after 15 minutes) so Apply never fires against a stale plan.\nfunction armPlanExpiry(expiresAt) {\n clearPlanExpiry();\n if (!expiresAt) return;\n var ms = new Date(expiresAt).getTime() - Date.now();\n if (isNaN(ms)) return;\n if (ms <= 0) {\n onPlanExpired();\n return;\n }\n planExpiryHandle = setTimeout(onPlanExpired, ms);\n}\n\nfunction asList(value, keys) {\n if (Array.isArray(value)) return value;\n if (value && typeof value === \"object\") {\n for (var i = 0; i < keys.length; i++) {\n if (Array.isArray(value[keys[i]])) return value[keys[i]];\n }\n }\n return [];\n}\n\nfunction renderSummary() {\n var el = document.getElementById(\"summary\");\n el.innerHTML = \"\";\n var tiles = [{ key: \"unread\", count: unreadCount }].concat(\n CATEGORIES.map(function (cat) {\n return { key: cat, count: categoryCounts ? categoryCounts[cat] : undefined };\n })\n );\n tiles.forEach(function (t) {\n var tile = document.createElement(\"div\");\n tile.className = \"tile \" + t.key;\n tile.innerHTML = '<div class=\"count\"></div><div class=\"label\"></div>';\n tile.querySelector(\".count\").textContent = t.count === undefined || t.count === null ? \"\\\\u2013\" : String(t.count);\n tile.querySelector(\".label\").textContent = t.key.replace(\"-\", \" \");\n el.appendChild(tile);\n });\n}\n\nfunction mailboxId(mb) {\n if (typeof mb === \"string\") return mb;\n return mb.id || mb.name || JSON.stringify(mb);\n}\n\nfunction mailboxText(mb) {\n if (typeof mb === \"string\") return mb;\n var name = mb.address || mb.label || mb.name || mb.id || JSON.stringify(mb);\n var caps = mb.capabilities;\n if (caps && typeof caps === \"object\") {\n return name + (caps.triage ? \" \\\\u2014 triage \\\\u2713\" : \" \\\\u2014 read-only\");\n }\n return name;\n}\n\nfunction selectedMailbox() {\n var id = document.getElementById(\"mailbox-select\").value;\n for (var i = 0; i < currentMailboxes.length; i++) {\n if (mailboxId(currentMailboxes[i]) === id) return currentMailboxes[i];\n }\n return null;\n}\n\nfunction renderCaps(mb) {\n var el = document.getElementById(\"caps\");\n var caps = mb && typeof mb === \"object\" ? mb.capabilities : null;\n if (!caps || typeof caps !== \"object\") {\n el.style.display = \"none\";\n return;\n }\n el.style.display = \"flex\";\n el.innerHTML = \"\";\n [\"read\", \"send\", \"triage\", \"compose\"].forEach(function (cap) {\n var b = document.createElement(\"span\");\n b.className = \"badge \" + (caps[cap] ? \"on\" : \"off\");\n b.textContent = cap;\n el.appendChild(b);\n });\n if (!caps.triage) {\n var hint = document.createElement(\"span\");\n hint.className = \"caps-hint\";\n hint.textContent = \"triage scope missing \\\\u2014 grant it via agentpush (mailbox_request_elevation)\";\n el.appendChild(hint);\n }\n}\n\n// Gmail category labels -> the dashboard's triage buckets. IMPORTANT wins,\n// so an important promo counts as urgent, not newsletter.\nfunction categoryOf(labelIds) {\n var l = labelIds || [];\n if (l.indexOf(\"IMPORTANT\") !== -1) return \"urgent\";\n if (l.indexOf(\"CATEGORY_PROMOTIONS\") !== -1) return \"newsletter\";\n if (l.indexOf(\"CATEGORY_UPDATES\") !== -1 || l.indexOf(\"CATEGORY_SOCIAL\") !== -1 || l.indexOf(\"CATEGORY_FORUMS\") !== -1) return \"notification\";\n return \"needs-reply\";\n}\n\nfunction onMailboxChange() {\n clearPlanExpiry();\n var mb = selectedMailbox();\n renderCaps(mb);\n var caps = mb && typeof mb === \"object\" ? mb.capabilities : null;\n var noTriage = !!(caps && typeof caps === \"object\" && !caps.triage);\n var scopeHint = noTriage ? \"Mailbox lacks triage capability \\\\u2014 grant it via agentpush (mailbox_request_elevation)\" : \"\";\n document.getElementById(\"triage-btn\").disabled = noTriage;\n document.getElementById(\"triage-btn\").title = scopeHint;\n document.getElementById(\"apply-btn\").disabled = true;\n document.getElementById(\"apply-btn\").title = scopeHint;\n currentPlan = null;\n categoryCounts = null;\n unreadCount = null;\n renderSummary();\n if (!mb) return;\n callApp(aliasTool(\"mailbox_search\"), { mailbox: mailboxId(mb), query: \"is:unread\", limit: 50 })\n .then(function (result) {\n var items = asList(result, [\"items\", \"messages\", \"results\"]);\n if (result && typeof result === \"object\" && typeof result.resultSizeEstimate === \"number\") {\n unreadCount = result.resultSizeEstimate;\n } else {\n unreadCount = items.length;\n }\n // Category breakdown over the newest page of unread — a sample when\n // the mailbox has more unread than one page.\n var counts = {};\n items.forEach(function (item) {\n var cat = categoryOf(item && item.labelIds);\n counts[cat] = (counts[cat] || 0) + 1;\n });\n categoryCounts = items.length > 0 ? counts : null;\n renderSummary();\n })\n .catch(function (err) {\n // KPI is best-effort: keep the dashes, but say why instead of silence.\n setStatus(\"Inbox summary unavailable: \" + err.message);\n });\n}\n\nfunction renderMailboxes(mailboxes) {\n currentMailboxes = mailboxes;\n var select = document.getElementById(\"mailbox-select\");\n select.innerHTML = \"\";\n if (mailboxes.length === 0) {\n var opt = document.createElement(\"option\");\n opt.textContent = \"no mailboxes\";\n opt.value = \"\";\n select.appendChild(opt);\n renderCaps(null);\n return;\n }\n mailboxes.forEach(function (mb) {\n var opt = document.createElement(\"option\");\n opt.value = mailboxId(mb);\n opt.textContent = mailboxText(mb);\n select.appendChild(opt);\n });\n onMailboxChange();\n}\n\nfunction renderAliasSelect(liveAliases) {\n var select = document.getElementById(\"alias-select\");\n select.innerHTML = \"\";\n var shown = liveAliases.length > 0 ? liveAliases : ALIASES;\n shown.forEach(function (alias) {\n var opt = document.createElement(\"option\");\n opt.value = alias;\n opt.textContent = alias;\n select.appendChild(opt);\n });\n select.value = activeAlias;\n select.style.display = shown.length > 1 ? \"\" : \"none\";\n}\n\nfunction loadMailboxes() {\n setStatus(\"Loading mailboxes from \" + activeAlias + \"\\\\u2026\");\n return callApp(aliasTool(\"mailbox_list\"), {})\n .then(function (result) {\n renderMailboxes(asList(result, [\"mailboxes\", \"items\"]));\n setStatus(\"Ready \\\\u2014 \" + activeAlias);\n })\n .catch(function (err) {\n setStatus(\"Error loading mailboxes: \" + err.message);\n });\n}\n\nfunction renderConnectHint() {\n var el = document.getElementById(\"connect-hint\");\n var anyLive = false;\n for (var i = 0; i < ALIASES.length; i++) {\n if (aliasStates[ALIASES[i]] && aliasStates[ALIASES[i]].state === \"live\") anyLive = true;\n }\n if (anyLive) {\n el.style.display = \"none\";\n return;\n }\n el.style.display = \"\";\n var list = document.getElementById(\"connect-hint-list\");\n list.innerHTML = \"\";\n ALIASES.forEach(function (alias) {\n var st = aliasStates[alias];\n var row = document.createElement(\"div\");\n row.className = \"plan-item\";\n var text;\n if (!st) text = alias + \" \\\\u2014 not probed yet\";\n else if (st.state === \"unreachable\") text = alias + \" \\\\u2014 unreachable: \" + st.message;\n else if (st.state === \"empty\") text = alias + \" \\\\u2014 connected, no mailboxes\";\n else text = alias + \" \\\\u2014 live\";\n row.textContent = text;\n list.appendChild(row);\n });\n}\n\nfunction probeAliases() {\n setStatus(\"Detecting mail servers\\\\u2026\");\n return Promise.all(\n ALIASES.map(function (alias) {\n return callApp(\"imported:\" + alias + \"/mailbox_list\", {})\n .then(function (result) {\n var mailboxes = asList(result, [\"mailboxes\", \"items\"]);\n aliasStates[alias] = { state: mailboxes.length > 0 ? \"live\" : \"empty\" };\n return { alias: alias, mailboxes: mailboxes };\n })\n .catch(function (err) {\n aliasStates[alias] = { state: \"unreachable\", message: err && err.message ? err.message : String(err) };\n return { alias: alias, mailboxes: [] };\n });\n })\n ).then(function (results) {\n var live = results.filter(function (r) { return r.mailboxes.length > 0; });\n if (live.length > 0) {\n activeAlias = live[0].alias;\n renderAliasSelect(live.map(function (r) { return r.alias; }));\n renderMailboxes(live[0].mailboxes);\n renderConnectHint();\n setStatus(\"Ready \\\\u2014 \" + activeAlias + (live.length > 1 ? \" (\" + live.length + \" servers available)\" : \"\"));\n } else {\n renderAliasSelect([]);\n renderMailboxes([]);\n renderConnectHint();\n setStatus(\"No mailboxes reachable \\\\u2014 see connection panel below.\");\n }\n });\n}\n\nfunction renderSearchResults(items, raw) {\n var el = document.getElementById(\"search-results\");\n el.innerHTML = \"\";\n if (raw && typeof raw === \"object\" && typeof raw.resultSizeEstimate === \"number\") {\n var count = document.createElement(\"div\");\n count.className = \"empty\";\n count.textContent = items.length + \" shown \\\\u00b7 ~\" + raw.resultSizeEstimate + \" total\";\n el.appendChild(count);\n }\n if (items.length === 0) {\n var none = document.createElement(\"div\");\n none.className = \"empty\";\n none.textContent = \"No results.\";\n el.appendChild(none);\n return;\n }\n items.forEach(function (item) {\n var row = document.createElement(\"div\");\n row.className = \"plan-item\";\n row.style.flexDirection = \"column\";\n row.style.alignItems = \"stretch\";\n var msgId = item && (item.id || item.messageId || \"\");\n var threadId = item && (item.threadId || \"\");\n var subj = typeof item === \"string\" ? item : item.subject || item.title || JSON.stringify(item);\n var from = item && item.from ? item.from : \"\";\n var snippet = item && item.snippet ? item.snippet : \"\";\n var top = document.createElement(\"div\");\n top.style.display = \"flex\";\n top.style.alignItems = \"flex-start\";\n top.style.gap = \"6px\";\n var cb = document.createElement(\"input\");\n cb.type = \"checkbox\";\n cb.className = \"mail-checkbox\";\n cb.setAttribute(\"data-action\", \"toggle-select\");\n if (msgId) cb.setAttribute(\"data-message-id\", msgId);\n if (isSelected(msgId)) cb.checked = true;\n top.appendChild(cb);\n var subjSpan = document.createElement(\"span\");\n subjSpan.className = \"subj\";\n subjSpan.style.flex = \"1\";\n subjSpan.textContent = subj;\n top.appendChild(subjSpan);\n var tagSpan = document.createElement(\"span\");\n tagSpan.className = \"tag\";\n tagSpan.textContent = from;\n top.appendChild(tagSpan);\n if (threadId) {\n var openBtn = document.createElement(\"button\");\n openBtn.className = \"mail-open\";\n openBtn.textContent = \"Open\";\n openBtn.setAttribute(\"data-action\", \"open-gmail\");\n openBtn.setAttribute(\"data-thread-id\", threadId);\n top.appendChild(openBtn);\n }\n row.appendChild(top);\n if (snippet) {\n var sn = document.createElement(\"div\");\n sn.className = \"snippet\";\n sn.textContent = snippet;\n row.appendChild(sn);\n }\n el.appendChild(row);\n });\n}\n\n// Renders a mailbox_triage_plan result: { plan_id, action, count, sample,\n// truncated, expires_at }. The sample rows are search-shaped messages.\nfunction renderPlan(plan) {\n var el = document.getElementById(\"plan-list\");\n el.innerHTML = \"\";\n if (!plan || typeof plan !== \"object\" || !plan.plan_id) {\n el.innerHTML = '<div class=\"empty\">Plan failed \\\\u2014 unexpected response.</div>';\n return;\n }\n var head = document.createElement(\"div\");\n head.className = \"plan-item\";\n head.innerHTML = '<span class=\"subj\"></span><span class=\"tag\"></span>';\n head.querySelector(\".subj\").textContent =\n plan.count + \" message\" + (plan.count === 1 ? \"\" : \"s\") + \" \\\\u2192 \" + (plan.action && plan.action.type);\n head.querySelector(\".tag\").textContent = plan.expires_at ? \"expires \" + fmtDate(plan.expires_at) : \"\";\n el.appendChild(head);\n var sample = asList(plan, [\"sample\"]);\n sample.forEach(function (item) {\n var row = document.createElement(\"div\");\n row.className = \"plan-item\";\n row.innerHTML = '<span class=\"subj\"></span><span class=\"tag\"></span>';\n row.querySelector(\".subj\").textContent = (item && (item.subject || item.title)) || JSON.stringify(item);\n row.querySelector(\".tag\").textContent = (item && item.from) || \"\";\n el.appendChild(row);\n });\n if (plan.truncated) {\n var more = document.createElement(\"div\");\n more.className = \"empty\";\n more.textContent = \"\\\\u2026sample truncated \\\\u2014 \" + plan.count + \" total.\";\n el.appendChild(more);\n }\n}\n\nfunction fmtDate(iso) {\n if (!iso) return \"\";\n var d = new Date(iso);\n return isNaN(d.getTime()) ? String(iso) : d.toLocaleString();\n}\n\nfunction renderRuns(runs) {\n var el = document.getElementById(\"runs-list\");\n el.innerHTML = \"\";\n if (runs.length === 0) {\n el.innerHTML = '<div class=\"empty\">No runs yet.</div>';\n return;\n }\n var shown = runs.slice(0, 20);\n shown.forEach(function (run) {\n var row = document.createElement(\"div\");\n row.className = \"runs-item\";\n row.title = \"Show sessions + output for \" + run.appRunId;\n row.innerHTML = '<span class=\"st\"></span><span class=\"rid\"></span><span class=\"meta\"></span>';\n var st = row.querySelector(\".st\");\n st.textContent = run.status || \"?\";\n st.className = \"st \" + (run.status || \"\");\n row.querySelector(\".rid\").textContent = run.appRunId;\n row.querySelector(\".meta\").textContent =\n fmtDate(run.startedAt) + (run.endedAt ? \" \\\\u2192 \" + fmtDate(run.endedAt) : \"\") + \" \\\\u00b7 \" + (run.sessions || 0) + \" session\" + (run.sessions === 1 ? \"\" : \"s\");\n row.addEventListener(\"click\", function () { viewRun(run.appRunId); });\n el.appendChild(row);\n });\n if (runs.length > 20) {\n var more = document.createElement(\"div\");\n more.className = \"empty\";\n more.textContent = \"showing 20 of \" + runs.length;\n el.appendChild(more);\n }\n}\n\nfunction loadRuns() {\n return callApp(\"app_list\", {})\n .then(function (result) {\n var apps = Array.isArray(result) ? result : [];\n var mine = null;\n for (var i = 0; i < apps.length; i++) {\n if (apps[i] && apps[i].appId === APP_ID) { mine = apps[i]; break; }\n }\n var runs = (mine && Array.isArray(mine.runs) ? mine.runs.slice() : []).sort(function (a, b) {\n return String(b.startedAt || \"\").localeCompare(String(a.startedAt || \"\"));\n });\n renderRuns(runs);\n })\n .catch(function (err) {\n setStatus(\"Could not load runs: \" + err.message);\n });\n}\n\nfunction viewRun(appRunId) {\n setStatus(\"Loading run \" + appRunId + \"\\\\u2026\");\n callApp(\"app_status\", { appRunId: appRunId })\n .then(function (status) {\n var sessions = (status && status.sessions) || [];\n appendLog(\"\\\\u2500\\\\u2500 run \" + appRunId + \" (\" + (status && status.status) + \") \\\\u2500\\\\u2500\");\n if (sessions.length === 0) {\n appendLog(\"(no sessions)\");\n setStatus(\"Ready \\\\u2014 \" + activeAlias);\n return;\n }\n sessions.forEach(function (s) {\n appendLog(\"session \" + s.sessionId + \" (\" + (s.agentId || \"?\") + \")\");\n });\n return callApp(\"agent_output\", { sessionId: sessions[0].sessionId, lastN: 200, clean: true })\n .then(function (output) {\n if (output) appendLog(typeof output === \"string\" ? output : JSON.stringify(output, null, 2));\n setStatus(\"Ready \\\\u2014 \" + activeAlias);\n });\n })\n .catch(function (err) {\n setStatus(\"Run status failed: \" + err.message);\n });\n}\n\n// Event delegation: search-results (checkboxes + open buttons)\ndocument.getElementById(\"search-results\").addEventListener(\"change\", function (e) {\n var target = e.target;\n if (target.getAttribute(\"data-action\") === \"toggle-select\") {\n var mid = target.getAttribute(\"data-message-id\");\n if (mid) {\n var item = null;\n var el = target.parentNode;\n while (el && el.parentNode && el !== document) {\n if (el.tagName === \"DIV\" && el.className.indexOf(\"plan-item\") !== -1) {\n var subjEl = el.querySelector(\".subj\");\n var tagEl = el.querySelector(\".tag\");\n var subjText = subjEl ? subjEl.textContent : \"\";\n var tagText = tagEl ? tagEl.textContent : \"\";\n // Reconstruct a minimal item from the DOM\n item = { id: mid, threadId: \"\", subject: subjText, from: tagText };\n break;\n }\n el = el.parentNode;\n }\n toggleSelection(mid, item || { id: mid });\n }\n }\n});\ndocument.getElementById(\"search-results\").addEventListener(\"click\", function (e) {\n var target = e.target;\n if (target.getAttribute(\"data-action\") === \"open-gmail\") {\n var tid = target.getAttribute(\"data-thread-id\");\n if (tid && openLink) {\n openLink(\"https://mail.google.com/mail/u/0/#inbox/\" + tid).catch(function (err) {\n setStatus(\"Failed to open link: \" + (err && err.message ? err.message : String(err)));\n });\n } else if (tid) {\n setStatus(\"Host does not support openLink.\");\n }\n }\n});\n// Event delegation: selection-bar (send-selection)\ndocument.getElementById(\"selection-bar\").addEventListener(\"click\", function (e) {\n if (e.target.getAttribute(\"data-action\") === \"send-selection\") {\n sendSelectionToClaude();\n }\n});\n\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () {\n probeAliases();\n loadRuns();\n});\n\ndocument.getElementById(\"connect-retry-btn\").addEventListener(\"click\", function () {\n probeAliases();\n loadRuns();\n});\n\ndocument.getElementById(\"alias-select\").addEventListener(\"change\", function () {\n activeAlias = this.value;\n clearPlanExpiry();\n currentPlan = null;\n document.getElementById(\"apply-btn\").disabled = true;\n loadMailboxes();\n});\n\ndocument.getElementById(\"mailbox-select\").addEventListener(\"change\", onMailboxChange);\n\ndocument.getElementById(\"runs-refresh-btn\").addEventListener(\"click\", loadRuns);\n\ndocument.getElementById(\"search-btn\").addEventListener(\"click\", function () {\n var btn = this;\n var query = document.getElementById(\"search-input\").value.trim() || \"is:unread\";\n var mailbox = document.getElementById(\"mailbox-select\").value;\n setBusy(btn, true);\n setStatus(\"Searching\\\\u2026\");\n callApp(aliasTool(\"mailbox_search\"), { mailbox: mailbox, query: query })\n .then(function (result) {\n renderSearchResults(asList(result, [\"items\", \"messages\", \"results\"]), result);\n setBusy(btn, false);\n setStatus(\"Ready \\\\u2014 \" + activeAlias);\n })\n .catch(function (err) {\n setBusy(btn, false);\n setStatus(\"Search failed: \" + err.message);\n });\n});\n\ndocument.getElementById(\"plan-action\").addEventListener(\"change\", function () {\n document.getElementById(\"plan-label\").style.display = this.value === \"label\" ? \"\" : \"none\";\n});\n\ndocument.getElementById(\"triage-btn\").addEventListener(\"click\", function () {\n var btn = this;\n var mailbox = document.getElementById(\"mailbox-select\").value;\n var query = document.getElementById(\"plan-query\").value.trim() || \"is:unread\";\n var actionType = document.getElementById(\"plan-action\").value;\n var action = { type: actionType };\n if (actionType === \"label\") {\n var label = document.getElementById(\"plan-label\").value.trim();\n if (!label) {\n setStatus(\"Enter a label name for the label action.\");\n return;\n }\n action.addLabelIds = [label];\n }\n setBusy(btn, true);\n setStatus(\"Building triage plan\\\\u2026\");\n document.getElementById(\"apply-btn\").disabled = true;\n clearPlanExpiry();\n currentPlan = null;\n callApp(aliasTool(\"mailbox_triage_plan\"), { mailbox: mailbox, criteria: { query: query }, action: action })\n .then(function (plan) {\n setBusy(btn, false);\n currentPlan = plan && plan.plan_id ? plan : null;\n renderPlan(plan);\n if (currentPlan && plan.count > 0) {\n document.getElementById(\"apply-btn\").disabled = false;\n armPlanExpiry(plan.expires_at);\n setStatus(\"Plan ready (\" + plan.count + \" messages) \\\\u2014 review, then Apply.\");\n } else if (currentPlan) {\n setStatus(\"Plan matches no messages \\\\u2014 nothing to apply.\");\n } else {\n setStatus(\"Triage plan failed \\\\u2014 see plan panel.\");\n }\n })\n .catch(function (err) {\n setBusy(btn, false);\n var msg = err && err.message ? err.message : String(err);\n if (msg.indexOf(\"mailbox_scope_missing\") !== -1) {\n setStatus(\"Triage scope missing \\\\u2014 grant it via agentpush (mailbox_request_elevation).\");\n } else {\n setStatus(\"Triage plan failed: \" + msg);\n }\n });\n});\n\ndocument.getElementById(\"apply-btn\").addEventListener(\"click\", function () {\n var btn = this;\n if (!currentPlan || !currentPlan.plan_id) return;\n setBusy(btn, true);\n setStatus(\"Applying triage plan\\\\u2026\");\n callApp(aliasTool(\"mailbox_triage_apply\"), { plan_id: currentPlan.plan_id, confirm: true })\n .then(function (result) {\n setBusy(btn, false);\n var count = currentPlan.count;\n var extra = [];\n if (result && typeof result === \"object\") {\n [\"applied\", \"modified\", \"count\"].forEach(function (key) {\n if (typeof result[key] === \"number\") extra.push(key + \": \" + result[key]);\n });\n }\n setStatus(\n extra.length > 0\n ? \"Plan applied \\\\u2014 \" + extra.join(\", \") + \".\"\n : \"Plan applied (\" + count + \" message\" + (count === 1 ? \"\" : \"s\") + \").\"\n );\n document.getElementById(\"apply-btn\").disabled = true;\n clearPlanExpiry();\n currentPlan = null;\n onMailboxChange();\n })\n .catch(function (err) {\n setBusy(btn, false);\n setStatus(\"Apply failed: \" + err.message);\n });\n});\n\nfunction appendLog(text) {\n var el = document.getElementById(\"log-events\");\n if (el.querySelector(\".empty\")) el.innerHTML = \"\";\n var line = document.createElement(\"div\");\n line.textContent = text;\n el.appendChild(line);\n el.scrollTop = el.scrollHeight;\n}\n\n// Replaces the whole tail on every poll tick (the daemon returns the last\n// N lines, not a delta) — appending it instead would duplicate the log.\nfunction renderLogTail(text) {\n var el = document.getElementById(\"log-tail\");\n el.textContent = text || \"\";\n el.scrollTop = el.scrollHeight;\n}\n\nfunction pollAgentRun(appRunId, startedAt) {\n if (pollHandle) {\n clearTimeout(pollHandle);\n pollHandle = null;\n }\n if (Date.now() - startedAt > POLL_TIMEOUT_MS) {\n setStatus(\"Still running \\\\u2014 check Past runs later.\");\n return;\n }\n callApp(\"app_status\", { appRunId: appRunId })\n .then(function (status) {\n var sessions = (status && status.sessions) || [];\n var sessionId = sessions[0] && sessions[0].sessionId;\n var tail = sessionId\n ? callApp(\"agent_output\", { sessionId: sessionId, lastN: 200, clean: true })\n : Promise.resolve(\"\");\n return tail.then(function (output) {\n renderLogTail(typeof output === \"string\" ? output : output ? JSON.stringify(output, null, 2) : \"\");\n var done = status && (status.status === \"ended\" || status.status === \"done\" || status.endedAt);\n if (!done) {\n pollHandle = setTimeout(function () { pollAgentRun(appRunId, startedAt); }, 2000);\n } else {\n pollHandle = null;\n setStatus(\"Agent run finished.\");\n loadRuns();\n }\n });\n })\n .catch(function (err) {\n setStatus(\"Agent status failed: \" + err.message);\n });\n}\n\ndocument.getElementById(\"run-agent-btn\").addEventListener(\"click\", function () {\n var btn = this;\n if (pollHandle) {\n clearTimeout(pollHandle);\n pollHandle = null;\n }\n document.getElementById(\"log-events\").innerHTML = \"\";\n document.getElementById(\"log-tail\").textContent = \"\";\n setBusy(btn, true);\n setStatus(\"Starting triager agent\\\\u2026\");\n callApp(\"app_run\", { appId: APP_ID, agents: [AGENT_ID], prompt: \"Triage my inbox.\" })\n .then(function (run) {\n setBusy(btn, false);\n var sessions = (run && run.sessions) || [];\n var errors = (run && run.errors) || [];\n if (sessions.length === 0 && errors.length > 0) {\n appendLog(\n \"Run failed to start: \" +\n errors.map(function (e) { return (e.agentId || \"?\") + \": \" + e.error; }).join(\"; \")\n );\n setStatus(\"Run failed to start.\");\n return;\n }\n appendLog(\"Started app run \" + (run && run.appRunId));\n loadRuns();\n pollAgentRun(run.appRunId, Date.now());\n })\n .catch(function (err) {\n setBusy(btn, false);\n setStatus(\"Run failed: \" + err.message);\n });\n});\n\n// window.McpApp is normally injected by the serving layer (postMessage bridge\n// on the MCP resource path, REST bridge on GET /apps/:id/ui). Guard anyway:\n// without it a missing bridge throws here and the panel dies silently on\n// \"Connecting…\" instead of reporting itself.\n(window.McpApp ? window.McpApp.connect() : Promise.reject(new Error(\"window.McpApp bridge missing\")))\n .then(function (bridge) {\n callTool = bridge.callTool;\n updateModelContext = bridge.updateModelContext;\n openLink = bridge.openLink;\n onTeardown = bridge.onTeardown;\n if (onTeardown) {\n onTeardown(function () {\n if (pollHandle) { clearTimeout(pollHandle); pollHandle = null; }\n if (planExpiryHandle) { clearTimeout(planExpiryHandle); planExpiryHandle = null; }\n });\n }\n return Promise.all([probeAliases(), loadRuns()]);\n })\n .catch(function (err) {\n setStatus(\"Standalone mode \\\\u2014 no host bridge (\" + err.message + \")\");\n renderSummary();\n });\n\nrenderSummary();\n</script>\n</body>\n</html>`\n","/**\n * `mail-triage` — a single-agent agentproto app for email triage.\n *\n * One agent (triager) scans inbox, categorizes unread mail, and applies\n * triage actions (label, archive). Proves the APP primitive works end-to-end\n * with a simple, useful real-world task.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { triager } from \"./agents/triager.js\"\nimport { triageInbox } from \"./workflows/triage-inbox.js\"\nimport { MAIL_TRIAGE_HTML, MAIL_TRIAGE_TOOLS } from \"./ui.js\"\n\nexport const mailTriage: AppHandle = defineApp({\n id: \"@agentproto/mail-triage\",\n name: \"Mail Triage\",\n version: \"0.1.0\",\n description: \"Triage your inbox — categorize, label, archive.\",\n agents: [triager],\n workflows: [triageInbox],\n ui: {\n html: MAIL_TRIAGE_HTML,\n title: \"Mail Triage\",\n tools: [...MAIL_TRIAGE_TOOLS],\n },\n})\n\nexport { triager, triageInbox }\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\nexport const cataloger: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/media-cataloger\",\n description:\n \"Scans a folder for media files (images, video, audio), collects metadata, and produces a structured catalog.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Read-only — never modify, rename, move, or delete files\",\n \"Only catalog files with recognized media extensions\",\n \"Report exact file sizes and paths, never estimate\",\n ],\n tools: [\"list_dir\", \"read_file\", \"file_info\"],\n workflows: [{ ref: \"scan-media\" }],\n }),\n body: [\n \"You scan a workspace folder for media files and produce a structured JSON catalog.\",\n \"\",\n \"Walk the target directory recursively using list_dir. For each file, check the\",\n \"extension against known media types:\",\n \"- Images: .jpg, .jpeg, .png, .gif, .webp, .svg, .bmp, .ico, .avif\",\n \"- Video: .mp4, .webm, .mov, .avi, .mkv\",\n \"- Audio: .mp3, .wav, .ogg, .flac, .aac, .m4a\",\n \"\",\n \"For each media file found, use file_info to get size and modification date.\",\n \"\",\n \"Output a JSON object with this shape:\",\n \"```json\",\n '{',\n ' \"folder\": \"<scanned path>\",',\n ' \"scannedAt\": \"<ISO timestamp>\",',\n ' \"summary\": { \"total\": N, \"images\": N, \"video\": N, \"audio\": N, \"totalSizeBytes\": N },',\n ' \"files\": [',\n ' {',\n ' \"path\": \"relative/path.jpg\",',\n ' \"name\": \"file.jpg\",',\n ' \"type\": \"image\",',\n ' \"extension\": \".jpg\",',\n ' \"sizeBytes\": 123456,',\n ' \"modifiedAt\": \"2026-08-01T12:00:00Z\"',\n \" }\",\n \" ]\",\n \"}\",\n \"```\",\n \"\",\n \"Sort files by type (images first, then video, then audio), then by name.\",\n ].join(\"\\n\"),\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\nexport const scanMedia = defineWorkflow({\n id: \"scan-media\",\n name: \"Scan Media\",\n description:\n \"Scan a folder for media files, collect metadata, and produce a structured catalog.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"catalog\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/media-cataloger\" },\n prompt:\n \"Scan the workspace root folder for media files (images, video, audio). \" +\n \"Walk all subdirectories recursively. For each media file, collect its path, \" +\n \"name, type, extension, size, and modification date. Output the full catalog \" +\n \"as a single JSON object.\",\n },\n ],\n})\n","/**\n * Media Viewer — a self-contained MCP App gallery widget for `media-viewer`.\n *\n * Talks to the daemon only through the `window.McpApp.connect()` bridge\n * (never a direct `fetch`): every action round-trips through the app's own\n * `app_tool_call` gateway tool, `{ appId, tool, args }`. `app_tool_call`\n * double-wraps its dispatch result (it returns a text-result whose `text` is\n * the JSON-stringified inner tool result, itself an MCP content array) — see\n * `unwrapText` below, which peels that apart regardless of how many layers\n * the host's bridge already stripped.\n */\n\nexport const MEDIA_VIEWER_TOOLS = [\"directory_list\", \"file_info\", \"file_read\"] as const\n\nconst APP_ID = \"@agentproto/media-viewer\"\n\nexport const MEDIA_VIEWER_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Media Viewer</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\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;flex-direction:column;height:100%}\n#toolbar{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;background:var(--bg2);flex-shrink:0}\n#toolbar .title{font-size:13px;font-weight:600;flex:1}\n#breadcrumb{font-size:12px;color:var(--text2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}\n.abtn:hover{background:var(--border)}\n#body{flex:1;display:flex;overflow:hidden}\n#grid-wrap{flex:1;overflow-y:auto;padding:14px}\n#grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px}\n.cell{display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 6px;border:1px solid var(--border);border-radius:6px;background:var(--bg2);cursor:pointer;text-align:center}\n.cell:hover{background:var(--bg3);border-color:var(--blue)}\n.cell.selected{border-color:var(--blue);background:var(--bg3)}\n.icon{font-size:28px;line-height:1}\n.name{font-size:11px;color:var(--text);word-break:break-word;max-width:100%;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}\n#empty{color:var(--text2);padding:24px;text-align:center}\n#details{width:280px;border-left:1px solid var(--border);background:var(--bg2);padding:14px;overflow-y:auto;flex-shrink:0}\n#details h3{font-size:12px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px}\n#details .row{display:flex;justify-content:space-between;gap:8px;padding:4px 0;border-bottom:1px solid var(--border);font-size:12px}\n#details .row span:first-child{color:var(--text2)}\n#preview{margin-top:10px;padding:8px;background:var(--bg);border:1px solid var(--border);border-radius:4px;font-family:Menlo,Monaco,monospace;font-size:11px;white-space:pre-wrap;word-break:break-all;max-height:240px;overflow-y:auto}\n#status{padding:6px 14px;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=\"toolbar\">\n <button class=\"abtn\" id=\"up-btn\" title=\"Up one level\">↑</button>\n <span class=\"title\">Media Viewer</span>\n <span id=\"breadcrumb\">.</span>\n <button class=\"abtn\" id=\"refresh-btn\">Refresh</button>\n </div>\n <div id=\"body\">\n <div id=\"grid-wrap\">\n <div id=\"grid\"></div>\n <div id=\"empty\" style=\"display:none\">No files in this folder.</div>\n </div>\n <div id=\"details\" style=\"display:none\"></div>\n </div>\n <div id=\"status\">Connecting…</div>\n</div>\n<script>\nvar APP_ID = ${JSON.stringify(APP_ID)};\nvar currentPath = \".\";\nvar entries = [];\nvar callTool = null;\n\nfunction setStatus(msg) {\n document.getElementById(\"status\").textContent = msg;\n}\n\n// app_tool_call wraps its dispatch result at least once (its own\n// text-result body is the JSON-stringified inner MCP tool result); peel\n// back through nested {content:[{type:\"text\",text:...}]} shells until the\n// payload stops looking like one, so this works whichever layer the host's\n// own bridge already unwrapped.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try {\n cur = JSON.parse(cur);\n continue;\n } catch (e) {\n return cur;\n }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);\n}\n\nfunction parseDirectoryListing(raw) {\n var text = typeof raw === \"string\" ? raw : \"\";\n return text\n .split(\"\\\\n\")\n .map(function (line) { return line.trim(); })\n .filter(function (line) { return line.length > 0; })\n .map(function (line) {\n var isDir = line.indexOf(\"[DIR]\") === 0;\n var name = line.replace(/^\\\\[(FILE|DIR)\\\\]\\\\s*/, \"\");\n return { name: name, isDir: isDir };\n });\n}\n\nfunction iconFor(entry) {\n if (entry.isDir) return \"\\\\uD83D\\\\uDCC1\";\n var ext = (entry.name.split(\".\").pop() || \"\").toLowerCase();\n if ([\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\", \"bmp\", \"ico\", \"avif\"].indexOf(ext) !== -1) return \"\\\\uD83D\\\\uDDBC\\\\uFE0F\";\n if ([\"mp4\", \"webm\", \"mov\", \"avi\", \"mkv\"].indexOf(ext) !== -1) return \"\\\\uD83C\\\\uDFA5\";\n if ([\"mp3\", \"wav\", \"ogg\", \"flac\", \"aac\", \"m4a\"].indexOf(ext) !== -1) return \"\\\\uD83C\\\\uDFB5\";\n return \"\\\\uD83D\\\\uDCC4\";\n}\n\nfunction joinPath(base, name) {\n if (base === \".\" || base === \"\") return name;\n return base.replace(/\\\\/+$/, \"\") + \"/\" + name;\n}\n\nfunction parentPath(path) {\n if (path === \".\" || path.indexOf(\"/\") === -1) return \".\";\n return path.slice(0, path.lastIndexOf(\"/\")) || \".\";\n}\n\nfunction renderGrid() {\n document.getElementById(\"breadcrumb\").textContent = currentPath;\n var grid = document.getElementById(\"grid\");\n var empty = document.getElementById(\"empty\");\n grid.innerHTML = \"\";\n if (entries.length === 0) {\n empty.style.display = \"block\";\n return;\n }\n empty.style.display = \"none\";\n entries.forEach(function (entry, idx) {\n var cell = document.createElement(\"div\");\n cell.className = \"cell\";\n cell.dataset.idx = String(idx);\n cell.innerHTML = '<div class=\"icon\">' + iconFor(entry) + '</div><div class=\"name\"></div>';\n cell.querySelector(\".name\").textContent = entry.name;\n cell.addEventListener(\"click\", function () { onSelect(entry, cell); });\n grid.appendChild(cell);\n });\n}\n\nfunction onSelect(entry, cell) {\n Array.prototype.forEach.call(document.querySelectorAll(\".cell\"), function (c) {\n c.classList.remove(\"selected\");\n });\n cell.classList.add(\"selected\");\n if (entry.isDir) {\n loadDirectory(joinPath(currentPath, entry.name));\n return;\n }\n showFileDetails(joinPath(currentPath, entry.name));\n}\n\nfunction showFileDetails(path) {\n var details = document.getElementById(\"details\");\n details.style.display = \"block\";\n details.innerHTML = \"<h3>Loading…</h3>\";\n setStatus(\"Reading \" + path + \"\\\\u2026\");\n callApp(\"file_info\", { path: path })\n .then(function (info) {\n var rows = \"\";\n if (info && typeof info === \"object\") {\n [\"name\", \"type\", \"size\", \"modified\"].forEach(function (key) {\n if (info[key] !== undefined) {\n rows += '<div class=\"row\"><span>' + key + '</span><span></span></div>';\n }\n });\n }\n details.innerHTML = \"<h3>Details</h3>\" + rows + '<div id=\"preview\">Loading preview…</div>';\n var rowEls = details.querySelectorAll(\".row span:last-child\");\n var keys = [\"name\", \"type\", \"size\", \"modified\"].filter(function (key) {\n return info && info[key] !== undefined;\n });\n keys.forEach(function (key, i) {\n rowEls[i].textContent = String(info[key]);\n });\n return callApp(\"file_read\", { path: path });\n })\n .then(function (content) {\n var pre = document.getElementById(\"preview\");\n if (!pre) return;\n var text = typeof content === \"string\" ? content : JSON.stringify(content);\n pre.textContent = text.length > 2000 ? text.slice(0, 2000) + \"\\\\u2026 (truncated)\" : (text || \"(empty file)\");\n setStatus(\"Ready \\\\u00b7 \" + currentPath);\n })\n .catch(function (err) {\n var pre = document.getElementById(\"preview\");\n if (pre) pre.textContent = \"Preview unavailable (\" + err.message + \")\";\n setStatus(\"Ready \\\\u00b7 \" + currentPath);\n });\n}\n\nfunction loadDirectory(path) {\n document.getElementById(\"details\").style.display = \"none\";\n setStatus(\"Loading \" + path + \"\\\\u2026\");\n return callApp(\"directory_list\", { path: path })\n .then(function (raw) {\n currentPath = path;\n entries = parseDirectoryListing(raw).sort(function (a, b) {\n if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;\n return a.name.localeCompare(b.name);\n });\n renderGrid();\n setStatus(\"Ready \\\\u00b7 \" + entries.length + \" entries \\\\u00b7 \" + path);\n })\n .catch(function (err) {\n entries = [];\n renderGrid();\n setStatus(\"Error: \" + err.message);\n });\n}\n\ndocument.getElementById(\"up-btn\").addEventListener(\"click\", function () {\n loadDirectory(parentPath(currentPath));\n});\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () {\n loadDirectory(currentPath);\n});\n\nwindow.McpApp.connect()\n .then(function (bridge) {\n callTool = bridge.callTool;\n return loadDirectory(\".\");\n })\n .catch(function (err) {\n setStatus(\"Standalone mode \\\\u2014 no host bridge (\" + err.message + \")\");\n entries = [];\n renderGrid();\n });\n</script>\n</body>\n</html>`\n","/**\n * `media-viewer` — a single-agent agentproto app for media cataloging.\n *\n * One agent (cataloger) scans a workspace folder for images, video, and audio\n * files, collects metadata, and produces a structured JSON catalog. Paired\n * with a Cowork McpApp gallery widget for visual browsing.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { cataloger } from \"./agents/cataloger.js\"\nimport { scanMedia } from \"./workflows/scan-media.js\"\nimport { MEDIA_VIEWER_HTML, MEDIA_VIEWER_TOOLS } from \"./ui.js\"\n\nexport const mediaViewer: AppHandle = defineApp({\n id: \"@agentproto/media-viewer\",\n name: \"Media Viewer\",\n version: \"0.1.0\",\n description: \"Scan a folder for images, video, and audio — gallery view.\",\n agents: [cataloger],\n workflows: [scanMedia],\n ui: {\n html: MEDIA_VIEWER_HTML,\n title: \"Media Viewer\",\n tools: [...MEDIA_VIEWER_TOOLS],\n },\n})\n\nexport { cataloger, scanMedia }\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/**\n * The Sessions Manager — the durable \"master of the sessions\" coordinator:\n * the human talks to IT, it talks to the other sessions. Distilled from the\n * hand-run plateau-coordinator session this app productizes.\n *\n * Where the watchdog only OBSERVES (and never touches a live session), the\n * manager DRIVES: it re-prompts idle sessions onto their task, restarts dead\n * ones, and answers \"where is X at?\" by reading transcripts. The Ops Panel\n * spawns it via `agent_start` with {@link MANAGER_LABEL} and `keepAlive`, so\n * the idle-reaper never retires a legitimately-parked coordinator.\n */\n\n/** Session label the panel spawns under — how a live manager is recognized. */\nexport const MANAGER_LABEL = \"Sessions Manager\"\n\nexport const manager: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/sessions-manager\",\n description:\n \"Durable coordinator over the daemon's sessions — routes questions to the right session, re-prompts stalled work, restarts dead sessions, reports status.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Never kill a session unless the human explicitly asks for that session by id/label\",\n \"Never merge PRs or bypass a repo's declared review flow from a supervised session\",\n \"Prefer re-prompting an existing session over spawning a duplicate for the same task\",\n ],\n tools: [\n \"session_list\",\n \"conversation_read\",\n \"agent_prompt\",\n \"session_restart\",\n \"agent_start\",\n \"session_events_poll\",\n ],\n }),\n body: [\n \"You are the manager of this agentproto daemon's sessions: the human talks to you, and you talk\",\n \"to the other sessions. You are durable — the same session across many turns — so keep a working\",\n \"memory of who is doing what and don't re-derive it from scratch every turn.\",\n \"\",\n \"What you do:\",\n \"- Locate: given a task or question, find the relevant session (`session_list`, labels, cwd,\",\n \" recency) and read its transcript (`conversation_read`) before answering — never guess status.\",\n \"- Drive: re-prompt an idle/parked session onto its task with `agent_prompt`; restart a\",\n \" killed/exited session with `session_restart` when its work is clearly unfinished.\",\n \"- Spawn: only when no existing session owns the task — check for duplicates first.\",\n \"- Report: answer with short, concrete status (who, doing what, since when, blocked on what).\",\n \"\",\n \"Boundaries:\",\n \"- Never kill a session unless the human explicitly asks for that session.\",\n \"- A session's own repo conventions (review flow, merge policy) always win — you coordinate,\",\n \" you don't override.\",\n \"- If a session awaits a human decision, surface the question to the human instead of answering\",\n \" in their place.\",\n ].join(\"\\n\"),\n}\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\n/**\n * The Session Watchdog — a durable daemon-health session, re-prompted on a\n * cron tick (~every 5 minutes). Distilled from the hand-run watchdog session\n * this app productizes: same classification rules, same hard boundaries\n * (never kills a live session, never spawns).\n *\n * The Ops Panel spawns it via `agent_start` with {@link WATCHDOG_LABEL} and\n * wires a `prompt-session` cron ({@link WATCHDOG_TICK_LABEL} /\n * {@link WATCHDOG_TICK_SCHEDULE}) pointing at the spawned session — see\n * `ui.ts`. The agent is also usable standalone via `app_run` for a one-shot\n * health pass.\n */\n\n/** Session label the panel spawns under — how a live watchdog is recognized. */\nexport const WATCHDOG_LABEL = \"Session Watchdog\"\n\n/** Label of the cron job that re-prompts the watchdog session. */\nexport const WATCHDOG_TICK_LABEL = \"watchdog-tick\"\n\n/** Every 5 minutes. */\nexport const WATCHDOG_TICK_SCHEDULE = \"*/5 * * * *\"\n\n/** The prompt each cron tick sends to the durable session. */\nexport const WATCHDOG_TICK_PROMPT = \"Run your maintenance check now.\"\n\nexport const watchdog: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/session-watchdog\",\n description:\n \"Durable daemon-health watchdog — classifies sessions (needs-you, stalled, duplicated, abandonable), corrects flags, archives dead rows. Never kills or spawns.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"NEVER kill a live session, message/interrupt another session's conversation, or spawn new sessions\",\n \"Housekeeping only on terminal-status sessions (exited/killed/error) — never touches a live one\",\n \"Report only what's NEW since the last tick — no padding, no repeats unless something got worse\",\n ],\n tools: [\n \"session_list\",\n \"session_events_poll\",\n \"session_flag_status\",\n \"session_archive\",\n \"session_gc\",\n ],\n }),\n body: [\n \"You are the Session Watchdog for this agentproto daemon. You run on a recurring cron tick\",\n \"(~every 5 minutes) as the SAME durable session, re-prompted each time — you retain memory of\",\n \"what you already flagged, so don't repeat an alert unless something got meaningfully worse or new.\",\n \"\",\n \"Each tick:\",\n \"1. Call `session_list` to see current session state (status, awaitingInput/awaitingQuestion,\",\n \" stalledSinceMs, busy, blockedOn, childrenBusy).\",\n \"2. Call `session_events_poll` for events since your last tick (stalled, exited, awaiting-input)\",\n \" to catch anything between ticks.\",\n \"3. Classify:\",\n \" - Needs you: `awaitingInput: true` with a real unanswered question, older than ~10 minutes.\",\n \" - Stalled: busy but silent well past normal turn latency, no legitimate blockedOn excuse.\",\n \" - Possibly duplicated: two or more live sessions share workspace/cwd and a highly similar\",\n \" recent prompt/label — use judgment, not exact string matching.\",\n \" - Looks done/abandonable: idle/parked with no plausible remaining purpose.\",\n \"4. Correct misclassifications when confident: if `session_flag_status` is available and you can\",\n \" tell a session is actually waiting on a human (or isn't, despite being flagged), call it with\",\n \" a clear `reason`. If that tool is unknown on this daemon build, skip silently.\",\n \"5. Autonomous housekeeping (reversible, so no need to ask): `session_archive` or `session_gc` on\",\n \" sessions already terminal-status (exited/killed/error) and old. Never touches a live session.\",\n \"6. NEVER: kill a live session, message/interrupt another session's conversation, or spawn new\",\n \" sessions. You only have these five tools on purpose — if you'd want another one, report the\",\n \" suggestion instead of working around it.\",\n \"7. Report only what's NEW since last tick — one line per finding. If nothing new, reply with a\",\n \" short \\\"nothing new\\\" — don't pad it.\",\n \"\",\n \"You are not a general coding assistant across ticks — stay scoped to this watchdog role.\",\n ].join(\"\\n\"),\n}\n","/**\n * Ops Panel — a self-contained MCP App panel for `ops-panel`.\n *\n * The daemon operations cockpit: special durable sessions (Session Watchdog,\n * Sessions Manager) as launchable cards, the session list filterable by\n * workspace with lifecycle actions, housekeeping (session GC, worktree GC)\n * and the cron ledger. Talks to the daemon only through the\n * `window.McpApp.connect()` bridge — every action round-trips through\n * `app_tool_call { appId, tool, args }`, gated by {@link OPS_PANEL_TOOLS}\n * (same convention as `session-viewer`).\n *\n * Special-session recognition is by spawn label (`Session Watchdog` /\n * `Sessions Manager` — see `agents/`): the panel finds the newest session\n * wearing the label, shows its status, and offers Launch / Restart / Kill.\n * Launching the watchdog also (re)wires its `watchdog-tick` cron — the\n * `prompt-session` job that re-prompts the durable session every 5 minutes —\n * deleting any stale tick first so a dead session id never keeps a failing\n * cron behind.\n */\n\nimport { manager, MANAGER_LABEL } from \"./agents/manager.js\"\nimport {\n watchdog,\n WATCHDOG_LABEL,\n WATCHDOG_TICK_LABEL,\n WATCHDOG_TICK_PROMPT,\n WATCHDOG_TICK_SCHEDULE,\n} from \"./agents/watchdog.js\"\n\nexport const OPS_PANEL_TOOLS = [\n \"daemon_health\",\n \"session_list\",\n \"agent_start\",\n \"agent_prompt\",\n \"agent_kill\",\n \"session_restart\",\n \"session_archive\",\n \"session_gc\",\n \"cron_list\",\n \"cron_create\",\n \"cron_delete\",\n \"cron_run\",\n \"worktree_gc\",\n] as const\n\nconst APP_ID = \"@agentproto/ops-panel\"\n\n/** What the panel needs to launch each special session, injected below. */\nconst SPECIALS = [\n {\n key: \"watchdog\",\n label: WATCHDOG_LABEL,\n tagline: \"Health ticks every 5 min — flags, archives, never kills.\",\n prompt: watchdog.body ?? \"\",\n cron: {\n label: WATCHDOG_TICK_LABEL,\n schedule: WATCHDOG_TICK_SCHEDULE,\n prompt: WATCHDOG_TICK_PROMPT,\n },\n },\n {\n key: \"manager\",\n label: MANAGER_LABEL,\n tagline: \"You talk to it, it drives the other sessions.\",\n prompt: manager.body ?? \"\",\n cron: null,\n },\n] as const\n\nexport const OPS_PANEL_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Ops Panel</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;--text3:#6e7681;\n --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;\n}\nhtml,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;flex-direction:column;height:100%;overflow:hidden}\n\n#topbar{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}\n#topbar h1{font-size:13px;font-weight:700;letter-spacing:.02em}\n.chip{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:600;background:var(--bg3);border:1px solid var(--border);color:var(--text2);padding:3px 9px;border-radius:999px}\n.chip .d{width:7px;height:7px;border-radius:50%;background:var(--text3);flex:none}\n.chip.ok .d{background:var(--green)}\n.chip.bad .d{background:var(--red)}\n#ws-select{margin-left:auto;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11.5px;font-family:inherit}\n#refresh-btn{background:none;border:1px solid var(--border);cursor:pointer;color:var(--text2);font-size:13px;line-height:1;padding:4px 8px;border-radius:6px;font-family:inherit}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n\n#body{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:16px}\n.sect h2{font-size:11px;font-weight:700;color:var(--text2);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;display:flex;align-items:center;gap:8px}\n.sect h2 .count{font-weight:600;color:var(--text3);text-transform:none;letter-spacing:0}\n\n#specials{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px}\n.card{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:12px}\n.card .ct{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700}\n.card .tagline{font-size:11px;color:var(--text2);margin-top:3px}\n.card .meta{font-size:10.5px;color:var(--text3);margin-top:7px;display:flex;flex-wrap:wrap;gap:5px 10px}\n.card .cronline{font-size:10.5px;margin-top:7px;display:flex;align-items:center;gap:6px;color:var(--text2)}\n.card .actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}\n\n.badge{display:inline-block;padding:1px 6px;border-radius:10px;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}\n.b-run{background:rgba(63,185,80,.15);color:var(--green)}\n.b-busy{background:rgba(63,185,80,.25);color:var(--green)}\n.b-wait{background:rgba(88,166,255,.15);color:var(--blue)}\n.b-idle{background:rgba(139,148,158,.12);color:var(--text2)}\n.b-end{background:rgba(139,148,158,.1);color:var(--text3)}\n.b-err{background:rgba(248,81,73,.15);color:var(--red)}\n.b-warn{background:rgba(210,153,34,.15);color:var(--yellow)}\n\nbutton.act{background:var(--bg3);border:1px solid var(--border);color:var(--text);font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;cursor:pointer;font-family:inherit}\nbutton.act:hover{border-color:var(--text3)}\nbutton.act.primary{background:rgba(88,166,255,.15);border-color:rgba(88,166,255,.4);color:var(--blue)}\nbutton.act.danger{color:var(--red)}\nbutton.act.danger:hover{border-color:var(--red)}\nbutton.act.confirm{background:rgba(248,81,73,.2);border-color:var(--red);color:var(--red)}\nbutton.act:disabled{opacity:.45;cursor:default}\n\ntable{width:100%;border-collapse:collapse}\nth{font-size:10px;font-weight:700;color:var(--text3);text-transform:uppercase;letter-spacing:.04em;text-align:left;padding:4px 8px;border-bottom:1px solid var(--border)}\ntd{font-size:11.5px;padding:6px 8px;border-bottom:1px solid var(--bg3);vertical-align:middle}\ntr:hover td{background:var(--bg2)}\ntd .sn{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:260px;display:block}\ntd .sid{font-size:10px;color:var(--text3);font-family:Menlo,Monaco,monospace}\n.ws-tag{display:inline-block;font-size:10px;font-weight:600;background:var(--bg3);border:1px solid var(--border);border-radius:5px;padding:1px 6px;color:var(--text2)}\ntd.actions{white-space:nowrap;text-align:right}\ntd.actions button{margin-left:4px}\n\n.hk{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px}\n.hk .card .nums{display:flex;gap:14px;margin-top:8px}\n.hk .num{display:flex;flex-direction:column}\n.hk .num b{font-size:17px;font-weight:700}\n.hk .num span{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}\n\n#toast{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:8px 14px;font-size:11.5px;display:none;max-width:80%;z-index:10}\n#toast.err{border-color:var(--red);color:var(--red)}\n.empty{color:var(--text3);font-size:11.5px;padding:8px}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"topbar\">\n <h1>Agentproto Ops</h1>\n <span class=\"chip\" id=\"daemon-chip\"><span class=\"d\"></span><span id=\"daemon-txt\">daemon…</span></span>\n <select id=\"ws-select\"><option value=\"\">all workspaces</option></select>\n <button id=\"refresh-btn\" title=\"Refresh\" type=\"button\">↻ refresh</button>\n </div>\n <div id=\"body\">\n <div class=\"sect\">\n <h2>Special sessions</h2>\n <div id=\"specials\"></div>\n </div>\n <div class=\"sect\">\n <h2>Sessions <span class=\"count\" id=\"sess-count\"></span></h2>\n <div id=\"sessions\"></div>\n </div>\n <div class=\"sect\">\n <h2>Housekeeping</h2>\n <div class=\"hk\" id=\"housekeeping\"></div>\n </div>\n <div class=\"sect\">\n <h2>Crons <span class=\"count\" id=\"cron-count\"></span></h2>\n <div id=\"crons\"></div>\n </div>\n </div>\n</div>\n<div id=\"toast\"></div>\n<script>\nvar callTool = null;\nvar APP_ID = ${JSON.stringify(APP_ID)};\nvar SPECIALS = ${JSON.stringify(SPECIALS)};\n\n// app_tool_call wraps its dispatch result at least once (its own text-result\n// body is the JSON-stringified inner MCP tool result); peel back through\n// nested {content:[{type:\"text\",text:...}]} shells until the payload stops\n// looking like one — same convention as session-viewer.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try { cur = JSON.parse(cur); continue; } catch (e) { return cur; }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);\n}\n\nfunction escHtml(s) {\n return String(s == null ? \"\" : s).replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\nfunction fmtRelative(iso) {\n if (!iso) return \"\";\n var ms = Date.now() - new Date(iso).getTime();\n if (!isFinite(ms)) return \"\";\n var s = Math.round(ms / 1000);\n if (s < 5) return \"just now\";\n if (s < 60) return s + \"s ago\";\n var m = Math.round(s / 60);\n if (m < 60) return m + \"m ago\";\n var h = Math.round(m / 60);\n if (h < 24) return h + \"h ago\";\n return Math.round(h / 24) + \"d ago\";\n}\n\nvar toastTimer = null;\nfunction toast(msg, isErr) {\n var el = document.getElementById(\"toast\");\n el.textContent = msg;\n el.className = isErr ? \"err\" : \"\";\n el.style.display = \"block\";\n if (toastTimer) clearTimeout(toastTimer);\n toastTimer = setTimeout(function () { el.style.display = \"none\"; }, isErr ? 6000 : 3000);\n}\n\n// Two-click confirm: first click arms the button, second within 4s fires.\nvar armed = {};\nfunction confirmClick(key, btn, fn) {\n if (armed[key]) {\n clearTimeout(armed[key].t);\n delete armed[key];\n fn();\n return;\n }\n var prev = btn.textContent;\n btn.classList.add(\"confirm\");\n btn.textContent = \"sure?\";\n armed[key] = { t: setTimeout(function () {\n delete armed[key];\n btn.classList.remove(\"confirm\");\n btn.textContent = prev;\n }, 4000) };\n}\n\n// ============================================================\n// state\n// ============================================================\n\nvar sessions = [];\nvar crons = [];\nvar wsFilter = \"\";\nvar pollTimer = null;\nvar busyActions = false;\n\nfunction isLive(s) { return !!s && (s.status === \"running\" || s.status === \"starting\"); }\n\nfunction displayBadge(s) {\n if (s.status === \"running\") {\n if (s.busy) return { label: \"working\", cls: \"b-busy\" };\n if (s.awaitingInput) return { label: \"waiting\", cls: \"b-wait\" };\n return { label: \"idle\", cls: \"b-idle\" };\n }\n if (s.status === \"starting\") return { label: \"starting\", cls: \"b-wait\" };\n if (s.status === \"error\" || s.status === \"killed\") return { label: s.status, cls: \"b-err\" };\n return { label: s.status || \"exited\", cls: \"b-end\" };\n}\n\nfunction labelOf(s) {\n return s.label || s.title || s.name || (s.command ? String(s.command).split(\"/\").pop() : null) || s.id;\n}\n\nfunction specialSessionFor(sp) {\n var best = null;\n for (var i = 0; i < sessions.length; i++) {\n var s = sessions[i];\n if ((s.label || s.title) !== sp.label) continue;\n if (!best) { best = s; continue; }\n // Prefer a live one; among equals prefer the newest.\n if (isLive(s) && !isLive(best)) { best = s; continue; }\n if (isLive(s) === isLive(best) && String(s.startedAt) > String(best.startedAt)) best = s;\n }\n return best;\n}\n\nfunction cronByLabel(label) {\n var hits = [];\n for (var i = 0; i < crons.length; i++) if (crons[i].label === label) hits.push(crons[i]);\n return hits;\n}\n\n// ============================================================\n// data loading\n// ============================================================\n\nfunction loadAll() {\n return Promise.all([\n callApp(\"session_list\", { kind: \"all\" }).then(function (d) {\n sessions = (d && d.sessions) || (Array.isArray(d) ? d : []);\n }),\n callApp(\"cron_list\", {}).then(function (d) {\n crons = Array.isArray(d) ? d : (d && d.jobs) || [];\n }).catch(function () { crons = []; }),\n ]).then(function () {\n renderWorkspaces();\n renderSpecials();\n renderSessions();\n renderCrons();\n updateHousekeepingNums();\n });\n}\n\nfunction loadHealth() {\n return callApp(\"daemon_health\", {}).then(function (d) {\n var chip = document.getElementById(\"daemon-chip\");\n chip.className = \"chip ok\";\n var v = (d && (d.version || (d.build && d.build.sha) || d.status)) || \"ok\";\n document.getElementById(\"daemon-txt\").textContent = \"daemon \" + String(v).slice(0, 16);\n }).catch(function (e) {\n var chip = document.getElementById(\"daemon-chip\");\n chip.className = \"chip bad\";\n document.getElementById(\"daemon-txt\").textContent = \"daemon unreachable\";\n });\n}\n\n// Housekeeping card is built ONCE (so the worktree plan text and armed\n// confirm buttons survive the 5s poll); only the counters re-render — see\n// updateHousekeepingNums. The worktree plan is a separate dry-run call, on\n// demand only (it walks git state — not something to hammer on every poll).\nfunction buildHousekeeping() {\n var el = document.getElementById(\"housekeeping\");\n el.innerHTML =\n '<div class=\"card\"><div class=\"ct\">Sessions GC</div>'\n + '<div class=\"nums\" id=\"gc-nums\"></div>'\n + '<div class=\"actions\">'\n + '<button class=\"act\" id=\"gc-archive\">Archive >7d</button>'\n + '<button class=\"act danger\" id=\"gc-forget\">Forget >30d</button>'\n + '</div></div>'\n + '<div class=\"card\"><div class=\"ct\">Worktrees</div>'\n + '<div class=\"meta\" id=\"wt-plan\">No plan yet — run a dry run.</div>'\n + '<div class=\"actions\">'\n + '<button class=\"act\" id=\"wt-dry\">Dry run</button>'\n + '<button class=\"act danger\" id=\"wt-apply\" disabled>Reclaim now</button>'\n + '</div></div>';\n\n document.getElementById(\"gc-archive\").addEventListener(\"click\", function () {\n runAction(\"session_gc\", { olderThanDays: 7 }, \"GC: archived old terminal sessions\");\n });\n var forgetBtn = document.getElementById(\"gc-forget\");\n forgetBtn.addEventListener(\"click\", function () {\n confirmClick(\"gc-forget\", forgetBtn, function () {\n runAction(\"session_gc\", { olderThanDays: 30, forget: true }, \"GC: forgot >30d terminal sessions\");\n });\n });\n document.getElementById(\"wt-dry\").addEventListener(\"click\", function () { worktreePlan(false); });\n var applyBtn = document.getElementById(\"wt-apply\");\n applyBtn.addEventListener(\"click\", function () {\n confirmClick(\"wt-apply\", applyBtn, function () { worktreePlan(true); });\n });\n updateHousekeepingNums();\n}\n\nfunction updateHousekeepingNums() {\n var el = document.getElementById(\"gc-nums\");\n if (!el) return;\n var terminal = 0, live = 0;\n for (var i = 0; i < sessions.length; i++) {\n if (isLive(sessions[i])) live++;\n else terminal++;\n }\n el.innerHTML =\n '<div class=\"num\"><b>' + live + '</b><span>live</span></div>'\n + '<div class=\"num\"><b>' + terminal + '</b><span>terminal</span></div>';\n}\n\nfunction worktreePlan(apply) {\n var el = document.getElementById(\"wt-plan\");\n el.textContent = apply ? \"reclaiming\\\\u2026\" : \"planning\\\\u2026\";\n callApp(\"worktree_gc\", apply ? { apply: true } : {}).then(function (d) {\n var entries = (d && (d.plan || d.entries || d.worktrees)) || [];\n var counts = { reclaim: 0, salvage: 0, hold: 0 };\n for (var i = 0; i < entries.length; i++) {\n var c = entries[i].classification || entries[i].class || entries[i].action;\n if (counts[c] != null) counts[c]++;\n }\n el.textContent = (apply ? \"applied — \" : \"dry run — \")\n + counts.reclaim + \" reclaim / \" + counts.salvage + \" salvage / \" + counts.hold + \" hold\"\n + (entries.length === 0 ? \" (no linked worktrees)\" : \"\");\n document.getElementById(\"wt-apply\").disabled = counts.reclaim === 0 || apply;\n if (apply) toast(\"Worktree GC applied\");\n }).catch(function (e) {\n el.textContent = \"worktree_gc: \" + e.message;\n });\n}\n\n// ============================================================\n// rendering\n// ============================================================\n\nfunction renderWorkspaces() {\n var sel = document.getElementById(\"ws-select\");\n var seen = {};\n var slugs = [];\n for (var i = 0; i < sessions.length; i++) {\n var w = sessions[i].workspaceSlug;\n if (w && !seen[w]) { seen[w] = true; slugs.push(w); }\n }\n slugs.sort();\n var cur = wsFilter;\n var html = '<option value=\"\">all workspaces</option>';\n for (var j = 0; j < slugs.length; j++) {\n html += '<option value=\"' + escHtml(slugs[j]) + '\"' + (slugs[j] === cur ? \" selected\" : \"\") + '>' + escHtml(slugs[j]) + \"</option>\";\n }\n sel.innerHTML = html;\n}\n\nfunction renderSpecials() {\n var el = document.getElementById(\"specials\");\n var html = \"\";\n for (var i = 0; i < SPECIALS.length; i++) {\n var sp = SPECIALS[i];\n var s = specialSessionFor(sp);\n var badge = s ? displayBadge(s) : { label: \"not running\", cls: \"b-end\" };\n var meta = \"\";\n if (s) {\n meta += \"<span>\" + escHtml(s.id) + \"</span>\";\n if (s.startedAt) meta += \"<span>started \" + escHtml(fmtRelative(s.startedAt)) + \"</span>\";\n if (s.costUsd != null) meta += \"<span>$\" + Number(s.costUsd).toFixed(2) + \"</span>\";\n }\n var cronline = \"\";\n if (sp.cron) {\n var jobs = cronByLabel(sp.cron.label);\n if (jobs.length === 0) {\n cronline = '<span class=\"badge b-end\">no cron</span> tick not scheduled';\n } else {\n var job = jobs[0];\n var ok = !job.lastResult || job.lastResult.ok;\n var pointsAt = job.action && job.action.sessionId;\n var stale = s && isLive(s) && pointsAt && pointsAt !== s.id;\n cronline = '<span class=\"badge ' + (job.active ? (ok && !stale ? \"b-run\" : \"b-warn\") : \"b-end\") + '\">'\n + (job.active ? (ok && !stale ? \"tick ok\" : (stale ? \"tick stale\" : \"tick failing\")) : \"tick off\") + \"</span> \"\n + escHtml(job.schedule) + (pointsAt ? \" → \" + escHtml(pointsAt) : \"\")\n + (!ok && job.lastResult && job.lastResult.summary ? \" · \" + escHtml(String(job.lastResult.summary).slice(0, 60)) : \"\");\n }\n }\n var actions = \"\";\n if (!s || !isLive(s)) {\n actions += '<button class=\"act primary\" data-sp=\"' + sp.key + '\" data-do=\"launch\">Launch</button>';\n if (s) actions += '<button class=\"act\" data-sp=\"' + sp.key + '\" data-do=\"restart\">Restart last</button>';\n } else {\n if (sp.cron) {\n actions += '<button class=\"act\" data-sp=\"' + sp.key + '\" data-do=\"tick\">Tick now</button>';\n actions += '<button class=\"act\" data-sp=\"' + sp.key + '\" data-do=\"fixcron\">Re-wire cron</button>';\n } else {\n actions += '<button class=\"act\" data-sp=\"' + sp.key + '\" data-do=\"tick\">Ping</button>';\n }\n actions += '<button class=\"act danger\" data-sp=\"' + sp.key + '\" data-do=\"kill\">Kill</button>';\n }\n html += '<div class=\"card\">'\n + '<div class=\"ct\">' + escHtml(sp.label) + ' <span class=\"badge ' + badge.cls + '\">' + escHtml(badge.label) + \"</span></div>\"\n + '<div class=\"tagline\">' + escHtml(sp.tagline) + \"</div>\"\n + (meta ? '<div class=\"meta\">' + meta + \"</div>\" : \"\")\n + (cronline ? '<div class=\"cronline\">' + cronline + \"</div>\" : \"\")\n + '<div class=\"actions\">' + actions + \"</div>\"\n + \"</div>\";\n }\n el.innerHTML = html;\n var btns = el.querySelectorAll(\"button[data-sp]\");\n for (var k = 0; k < btns.length; k++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () { specialAction(btn.getAttribute(\"data-sp\"), btn.getAttribute(\"data-do\"), btn); });\n })(btns[k]);\n }\n}\n\nfunction renderSessions() {\n var el = document.getElementById(\"sessions\");\n var rows = [];\n for (var i = 0; i < sessions.length; i++) {\n var s = sessions[i];\n if (wsFilter && s.workspaceSlug !== wsFilter) continue;\n rows.push(s);\n }\n rows.sort(function (a, b) {\n var la = isLive(a) ? 1 : 0, lb = isLive(b) ? 1 : 0;\n if (la !== lb) return lb - la;\n return String(b.startedAt || \"\").localeCompare(String(a.startedAt || \"\"));\n });\n var liveCount = 0;\n for (var c = 0; c < rows.length; c++) if (isLive(rows[c])) liveCount++;\n document.getElementById(\"sess-count\").textContent = liveCount + \" live / \" + rows.length + \" shown\";\n if (rows.length === 0) {\n el.innerHTML = '<div class=\"empty\">No sessions' + (wsFilter ? \" in \" + escHtml(wsFilter) : \"\") + \".</div>\";\n return;\n }\n var html = \"<table><thead><tr><th>Session</th><th>Workspace</th><th>Status</th><th>Started</th><th>Cost</th><th></th></tr></thead><tbody>\";\n var shown = rows.slice(0, 80);\n for (var j = 0; j < shown.length; j++) {\n var s2 = shown[j];\n var db = displayBadge(s2);\n var acts = \"\";\n if (isLive(s2)) {\n acts += '<button class=\"act danger\" data-id=\"' + escHtml(s2.id) + '\" data-do=\"kill\">Kill</button>';\n } else {\n acts += '<button class=\"act\" data-id=\"' + escHtml(s2.id) + '\" data-do=\"restart\">Restart</button>';\n if (!s2.archived) acts += '<button class=\"act\" data-id=\"' + escHtml(s2.id) + '\" data-do=\"archive\">Archive</button>';\n }\n html += \"<tr>\"\n + '<td><span class=\"sn\" title=\"' + escHtml(labelOf(s2)) + '\">' + escHtml(labelOf(s2)) + '</span><span class=\"sid\">' + escHtml(s2.id) + \"</span></td>\"\n + \"<td>\" + (s2.workspaceSlug ? '<span class=\"ws-tag\">' + escHtml(s2.workspaceSlug) + \"</span>\" : \"\") + \"</td>\"\n + '<td><span class=\"badge ' + db.cls + '\">' + escHtml(db.label) + \"</span></td>\"\n + \"<td>\" + escHtml(fmtRelative(s2.startedAt)) + \"</td>\"\n + \"<td>\" + (s2.costUsd != null ? \"$\" + Number(s2.costUsd).toFixed(2) : \"\") + \"</td>\"\n + '<td class=\"actions\">' + acts + \"</td>\"\n + \"</tr>\";\n }\n html += \"</tbody></table>\";\n if (rows.length > shown.length) html += '<div class=\"empty\">… ' + (rows.length - shown.length) + \" more (narrow with the workspace filter)</div>\";\n el.innerHTML = html;\n var btns = el.querySelectorAll(\"button[data-id]\");\n for (var k = 0; k < btns.length; k++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () { sessionAction(btn.getAttribute(\"data-id\"), btn.getAttribute(\"data-do\"), btn); });\n })(btns[k]);\n }\n}\n\nfunction renderCrons() {\n var el = document.getElementById(\"crons\");\n document.getElementById(\"cron-count\").textContent = crons.length ? crons.length + \" jobs\" : \"\";\n if (crons.length === 0) {\n el.innerHTML = '<div class=\"empty\">No cron jobs.</div>';\n return;\n }\n var html = \"<table><thead><tr><th>Label</th><th>Schedule</th><th>State</th><th>Last</th><th>Next</th><th></th></tr></thead><tbody>\";\n for (var i = 0; i < crons.length; i++) {\n var c = crons[i];\n var last = c.lastResult == null ? \"\" : (c.lastResult.ok ? \"ok\" : \"failed\");\n var lastCls = c.lastResult == null ? \"b-end\" : (c.lastResult.ok ? \"b-run\" : \"b-err\");\n html += \"<tr>\"\n + '<td><span class=\"sn\">' + escHtml(c.label || c.id) + \"</span></td>\"\n + \"<td>\" + escHtml(c.schedule) + \"</td>\"\n + '<td><span class=\"badge ' + (c.active ? \"b-run\" : \"b-end\") + '\">' + (c.active ? \"active\" : \"off\") + \"</span></td>\"\n + \"<td>\" + (last ? '<span class=\"badge ' + lastCls + '\" title=\"' + escHtml((c.lastResult && c.lastResult.summary) || \"\") + '\">' + last + \"</span>\" : \"\") + \"</td>\"\n + \"<td>\" + escHtml(c.nextRunAt ? fmtRelative(c.nextRunAt).replace(\" ago\", \"\") : \"\") + \"</td>\"\n + '<td class=\"actions\">'\n + '<button class=\"act\" data-cron=\"' + escHtml(c.id) + '\" data-do=\"run\">Run</button>'\n + '<button class=\"act danger\" data-cron=\"' + escHtml(c.id) + '\" data-do=\"delete\">Delete</button>'\n + \"</td></tr>\";\n }\n html += \"</tbody></table>\";\n el.innerHTML = html;\n var btns = el.querySelectorAll(\"button[data-cron]\");\n for (var k = 0; k < btns.length; k++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () { cronAction(btn.getAttribute(\"data-cron\"), btn.getAttribute(\"data-do\"), btn); });\n })(btns[k]);\n }\n}\n\n// ============================================================\n// actions\n// ============================================================\n\nfunction runAction(tool, args, okMsg) {\n if (busyActions) return Promise.resolve();\n busyActions = true;\n return callApp(tool, args).then(function (d) {\n busyActions = false;\n if (okMsg) toast(okMsg);\n return loadAll().then(function () { return d; });\n }).catch(function (e) {\n busyActions = false;\n toast(tool + \": \" + e.message, true);\n throw e;\n });\n}\n\nfunction findSpecial(key) {\n for (var i = 0; i < SPECIALS.length; i++) if (SPECIALS[i].key === key) return SPECIALS[i];\n return null;\n}\n\nfunction rewireTickCron(sp, sessionId) {\n if (!sp.cron) return Promise.resolve();\n var stale = cronByLabel(sp.cron.label);\n var chain = Promise.resolve();\n for (var i = 0; i < stale.length; i++) {\n (function (jobId) {\n chain = chain.then(function () { return callApp(\"cron_delete\", { jobId: jobId }); });\n })(stale[i].id);\n }\n return chain.then(function () {\n return callApp(\"cron_create\", {\n schedule: sp.cron.schedule,\n recurring: true,\n label: sp.cron.label,\n action: { kind: \"prompt-session\", sessionId: sessionId, prompt: sp.cron.prompt },\n });\n });\n}\n\nfunction specialAction(key, what, btn) {\n var sp = findSpecial(key);\n if (!sp) return;\n var s = specialSessionFor(sp);\n if (what === \"launch\") {\n btn.disabled = true;\n callApp(\"agent_start\", {\n adapter: \"claude-code\",\n label: sp.label,\n prompt: sp.prompt,\n keepAlive: true,\n }).then(function (d) {\n var id = d && d.id;\n toast(sp.label + \" launched\" + (id ? \" (\" + id + \")\" : \"\"));\n return id && sp.cron ? rewireTickCron(sp, id) : null;\n }).then(function () { return loadAll(); })\n .catch(function (e) { toast(\"launch: \" + e.message, true); btn.disabled = false; });\n return;\n }\n if (what === \"restart\" && s) {\n btn.disabled = true;\n callApp(\"session_restart\", { idOrName: s.id }).then(function (d) {\n var id = (d && d.id) || s.id;\n toast(sp.label + \" restarted\");\n return sp.cron ? rewireTickCron(sp, id) : null;\n }).then(function () { return loadAll(); })\n .catch(function (e) { toast(\"restart: \" + e.message, true); btn.disabled = false; });\n return;\n }\n if (what === \"tick\" && s) {\n var prompt = sp.cron ? sp.cron.prompt : \"Status check: report where things stand, briefly.\";\n runAction(\"agent_prompt\", { sessionId: s.id, prompt: prompt }, sp.label + \" prompted\");\n return;\n }\n if (what === \"fixcron\" && s) {\n btn.disabled = true;\n rewireTickCron(sp, s.id).then(function () {\n toast(\"Cron re-wired to \" + s.id);\n return loadAll();\n }).catch(function (e) { toast(\"cron: \" + e.message, true); btn.disabled = false; });\n return;\n }\n if (what === \"kill\" && s) {\n confirmClick(\"kill-\" + s.id, btn, function () {\n runAction(\"agent_kill\", { sessionId: s.id }, sp.label + \" killed\");\n });\n }\n}\n\nfunction sessionAction(id, what, btn) {\n if (what === \"kill\") {\n confirmClick(\"kill-\" + id, btn, function () {\n runAction(\"agent_kill\", { sessionId: id }, \"Killed \" + id);\n });\n return;\n }\n if (what === \"restart\") { runAction(\"session_restart\", { idOrName: id }, \"Restarted \" + id); return; }\n if (what === \"archive\") { runAction(\"session_archive\", { idOrName: id }, \"Archived \" + id); }\n}\n\nfunction cronAction(jobId, what, btn) {\n if (what === \"run\") { runAction(\"cron_run\", { jobId: jobId }, \"Cron fired\"); return; }\n if (what === \"delete\") {\n confirmClick(\"cron-\" + jobId, btn, function () {\n runAction(\"cron_delete\", { jobId: jobId }, \"Cron deleted\");\n });\n }\n}\n\n// ============================================================\n// boot\n// ============================================================\n\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () { loadHealth(); loadAll(); });\ndocument.getElementById(\"ws-select\").addEventListener(\"change\", function (e) {\n wsFilter = e.target.value;\n renderSessions();\n});\n\nwindow.McpApp.connect()\n .then(function (bridge) {\n callTool = bridge.callTool;\n buildHousekeeping();\n loadHealth();\n return loadAll();\n })\n .then(function () {\n pollTimer = setInterval(loadAll, 5000);\n })\n .catch(function (err) {\n document.getElementById(\"body\").innerHTML =\n '<div class=\"empty\">Standalone mode \\\\u2014 no host bridge (' + escHtml(err.message) + \")</div>\";\n });\n</script>\n</body>\n</html>`\n","/**\n * `ops-panel` — the agentproto daemon operations cockpit as an app.\n *\n * Two durable \"special sessions\" ship as agents — the Session Watchdog\n * (cron-ticked health checks: classify, flag, archive; never kills) and the\n * Sessions Manager (the coordinator the human talks to, which drives the\n * other sessions). The panel (`ui.ts`) launches/restarts them by label,\n * wires the watchdog's `watchdog-tick` cron, lists every session filterable\n * by workspace with lifecycle actions, and fronts housekeeping: session GC,\n * worktree GC (dry-run first), and the cron ledger.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { manager } from \"./agents/manager.js\"\nimport { watchdog } from \"./agents/watchdog.js\"\nimport { OPS_PANEL_HTML, OPS_PANEL_TOOLS } from \"./ui.js\"\n\nexport const opsPanel: AppHandle = defineApp({\n id: \"@agentproto/ops-panel\",\n name: \"Ops Panel\",\n version: \"0.1.0\",\n description: \"Daemon ops cockpit — watchdog & manager sessions, workspace-filtered session list, GC and cron housekeeping.\",\n agents: [watchdog, manager],\n ui: {\n html: OPS_PANEL_HTML,\n title: \"Ops Panel\",\n tools: [...OPS_PANEL_TOOLS],\n },\n})\n\nexport { watchdog, manager }\nexport { WATCHDOG_LABEL, WATCHDOG_TICK_LABEL, WATCHDOG_TICK_PROMPT, WATCHDOG_TICK_SCHEDULE } from \"./agents/watchdog.js\"\nexport { MANAGER_LABEL } from \"./agents/manager.js\"\n","/**\n * The `@agentproto/config` deep-link fragment grammar (plan §3.4):\n *\n * #<section>[/<id>[/<sub>]]\n * section := wallets | harnesses | models | defaults | remote | advanced\n * id := encodeURIComponent(profileId | adapterSlug | modelRef | configKey | \"pairing\")\n *\n * Written in plain ES5-safe syntax (`var`/`function`, no arrows, no\n * destructuring) and inlined verbatim into the panel's `<script>` via\n * `parseConfigFragment.toString()` / `buildConfigFragment.toString()`\n * (see `ui.ts`) — this is the ONE copy of the parser, tested directly here\n * and run unmodified in the browser. Every free variable it needs (the\n * section list) is declared INSIDE the function body so the embedded\n * source has no dependency on this module's closure.\n */\n\nexport const CONFIG_SECTIONS = [\"wallets\", \"harnesses\", \"models\", \"defaults\", \"remote\", \"advanced\"] as const\n\nexport type ConfigSection = (typeof CONFIG_SECTIONS)[number]\n\nexport interface ParsedConfigFragment {\n section: ConfigSection\n id?: string\n sub?: string\n}\n\n/** Unknown section -> first section (`wallets`). Unknown/absent id or sub is\n * simply omitted — never guessed, never a blank crash. A malformed\n * percent-encoding in `id`/`sub` falls back to the raw segment rather than\n * throwing. */\nexport function parseConfigFragment(hash: string): ParsedConfigFragment {\n var SECTIONS = [\"wallets\", \"harnesses\", \"models\", \"defaults\", \"remote\", \"advanced\"]\n var raw = String(hash || \"\").replace(/^#/, \"\")\n var parts = raw.split(\"/\").filter(function (p) {\n return p.length > 0\n })\n var sectionRaw = parts[0] || \"\"\n var section = SECTIONS.indexOf(sectionRaw) !== -1 ? sectionRaw : SECTIONS[0]!\n var result: { section: string; id?: string; sub?: string } = { section: section }\n var idPart = parts[1]\n if (idPart !== undefined) {\n try {\n result.id = decodeURIComponent(idPart)\n } catch (e) {\n result.id = idPart\n }\n }\n var subPart = parts[2]\n if (subPart !== undefined) {\n try {\n result.sub = decodeURIComponent(subPart)\n } catch (e) {\n result.sub = subPart\n }\n }\n return result as ParsedConfigFragment\n}\n\n/** Inverse of {@link parseConfigFragment} — builds the hash string (with the\n * leading `#`) the panel writes back to `location.hash` on navigation. */\nexport function buildConfigFragment(section: string, id?: string, sub?: string): string {\n var out = \"#\" + section\n if (id !== undefined && id !== null && id !== \"\") out += \"/\" + encodeURIComponent(id)\n if (sub !== undefined && sub !== null && sub !== \"\") out += \"/\" + encodeURIComponent(sub)\n return out\n}\n\n/**\n * MCP-hosted deep links (plan §3.4, PR-5): a browser tab resolves\n * `location.hash` directly, but an MCP host has no URL bar for this panel —\n * it opens `app_ui_config { view }` instead, and the bridge surfaces that as\n * a `ui/notifications/tool-input` (or `-result`) arrival (see `ui.ts`'s\n * `handleToolInput`).\n *\n * Precedence: `location.hash` wins on the panel's very first route — an\n * actual browser deep link must not be overridden by a stale/default tool\n * call. Every arrival after the first is a live \"go to this view\" command\n * from the host and always applies, regardless of the current hash.\n *\n * `arrivalIndex` is 1-based (the 1st, 2nd, ... tool-input notification this\n * boot has seen); `hashPresentAtLoad` is whether `location.hash` was\n * non-empty when the panel's script started running, captured once before\n * any routing happens. Pure — no DOM — so it is unit-testable without a\n * browser or jsdom, same as {@link parseConfigFragment}.\n */\nexport function shouldApplyIncomingView(arrivalIndex: number, hashPresentAtLoad: boolean): boolean {\n return !(arrivalIndex === 1 && hashPresentAtLoad)\n}\n","/**\n * `@agentproto/config` — a self-contained MCP App panel for daemon-wide\n * configuration. Its own app, not a section of `ops-panel` — that app is\n * referenced only for the mechanics this panel shares (\n * `window.McpApp.connect()` -> `app_tool_call { appId, tool, args }`, the\n * nested-envelope `unwrapText`, one card per entity, two-click confirm). See\n * its `ui.ts` for the pattern this file follows.\n *\n * Six sections, each independently loaded/rendered so one failing card never\n * blanks the others: Wallets (auth profiles + spend), Harnesses (adapters +\n * capabilities + presets + roles), Models (the vendor/product/route\n * catalog), Defaults & messaging (daemon config knobs), Remote & pairing,\n * and Advanced (raw config dump). This PR adds the write flows: wallet\n * lifecycle + curation, harness preset CRUD, a generic config_get/config_set\n * editor for Defaults/per-harness defaults/titler.model, and remote/pairing\n * lifecycle.\n *\n * Three daemon tools this panel wants (`config_get`, `config_set`, and\n * every write tool below) may not exist yet on the daemon it talks to\n * (parallel PRs) — `loadTool` turns an \"unknown daemon tool\" failure into a\n * muted, section-local notice instead of an error, and the Defaults section\n * falls back to `daemon_health`'s five effective knobs when `config_get` is\n * absent.\n *\n * Secret discipline: a credential (auth_profile_create's `credential`, a\n * remote bearer, a pairing offer URL) is read from a form field, used ONCE\n * in the same handler, and the DOM field is cleared before the async call\n * even starts. Nothing secret is ever stored in section state, written to\n * `updateModelContext`, or logged.\n *\n * Deep-link contract (plan section 3.4): `#<section>[/<id>[/<sub>]]`, parsed\n * by `parseConfigFragment` (`fragment.ts`) — embedded here via\n * `.toString()` so the exact function tested in `fragment.test.ts` is the\n * one that runs in the browser, not a second hand-copied version. A browser\n * tab resolves this from `location.hash`; an MCP host has no URL bar, so it\n * opens `app_ui_config { view }` instead — `handleToolInput` below turns\n * that into the same fragment via the bridge's `onToolInput` (app-ui-apps.ts).\n * `location.hash` wins when the panel first loads with one already set;\n * every tool-input notification after that re-routes unconditionally.\n */\n\nimport { parseConfigFragment, buildConfigFragment, shouldApplyIncomingView } from \"./fragment.js\"\n\nexport const CONFIG_TOOLS = [\n \"auth_profile_list\",\n \"auth_profile_create\",\n \"auth_profile_delete\",\n \"auth_profile_set_enabled\",\n \"auth_profile_set_models\",\n \"auth_profile_refresh_models\",\n \"auth_profile_import\",\n \"auth_discover_credentials\",\n \"auth_profile_update\",\n \"adapter_list\",\n \"harness_capabilities\",\n \"harness_preset_list\",\n \"harness_preset_create\",\n \"harness_preset_delete\",\n \"harness_preset_set_default\",\n \"catalog_models\",\n \"catalog_provider_models\",\n \"role_list\",\n \"usage_rollup\",\n \"config_get\",\n \"config_set\",\n \"daemon_health\",\n \"remote_status\",\n \"remote_enable\",\n \"remote_disable\",\n \"pair_list\",\n \"pair_offer\",\n \"pair_revoke\",\n \"tunnel_list\",\n \"tunnel_status\",\n] as const\n\nconst APP_ID = \"@agentproto/config\"\n\nconst SECTIONS: ReadonlyArray<{ id: string; label: string }> = [\n { id: \"wallets\", label: \"Wallets\" },\n { id: \"harnesses\", label: \"Harnesses\" },\n { id: \"models\", label: \"Models\" },\n { id: \"defaults\", label: \"Defaults\" },\n { id: \"remote\", label: \"Remote\" },\n { id: \"advanced\", label: \"Advanced\" },\n]\n\nexport const CONFIG_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>agentproto config</title>\n<style>\n/* Theme tokens: prefer a live --vscode-* var (VS Code webview host), else\n the brand-neutral standalone default, split light/dark by\n prefers-color-scheme — same mapping convention as work-board/ui/style.css.\n No JS theme detection: a host that swaps its theme live is never frozen. */\n:root{\n --cfg-bg: var(--vscode-editor-background, #f5f6f3);\n --cfg-surface: var(--vscode-sideBar-background, #ffffff);\n --cfg-surface-2: var(--vscode-editorWidget-background, #eef0ec);\n --cfg-ink: var(--vscode-editor-foreground, #171a1e);\n --cfg-ink-faint: var(--vscode-descriptionForeground, #6b7280);\n --cfg-border: var(--vscode-panel-border, #dde0da);\n --cfg-accent: var(--vscode-button-background, #0f62fe);\n --cfg-warning: var(--vscode-charts-yellow, #b45309);\n --cfg-danger: var(--vscode-charts-red, #b91c1c);\n --cfg-success: var(--vscode-charts-green, #15803d);\n --cfg-muted-bg: var(--vscode-input-background, #e5e7eb);\n}\n@media (prefers-color-scheme: dark){\n :root{\n --cfg-bg: var(--vscode-editor-background, #0f1115);\n --cfg-surface: var(--vscode-sideBar-background, #161a20);\n --cfg-surface-2: var(--vscode-editorWidget-background, #1c2129);\n --cfg-ink: var(--vscode-editor-foreground, #e6e9ee);\n --cfg-ink-faint: var(--vscode-descriptionForeground, #8b93a1);\n --cfg-border: var(--vscode-panel-border, #2a2f38);\n --cfg-accent: var(--vscode-button-background, #4589ff);\n --cfg-warning: var(--vscode-charts-yellow, #d29922);\n --cfg-danger: var(--vscode-charts-red, #f85149);\n --cfg-success: var(--vscode-charts-green, #3fb950);\n --cfg-muted-bg: var(--vscode-input-background, #262b33);\n }\n}\n*{box-sizing:border-box;margin:0;padding:0}\nhtml,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;font-size:13px;background:var(--cfg-bg);color:var(--cfg-ink)}\n#app{display:flex;flex-direction:column;height:100%;overflow:hidden}\n\n#topbar{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--cfg-surface);border-bottom:1px solid var(--cfg-border);flex-shrink:0}\n#topbar h1{font-size:13px;font-weight:700;letter-spacing:.02em}\n.chip{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:600;background:var(--cfg-surface-2);border:1px solid var(--cfg-border);color:var(--cfg-ink-faint);padding:3px 9px;border-radius:999px}\n.chip .d{width:7px;height:7px;border-radius:50%;background:var(--cfg-ink-faint);flex:none}\n.chip.ok .d{background:var(--cfg-success)}\n.chip.bad .d{background:var(--cfg-danger)}\n#refresh-btn{margin-left:auto;background:none;border:1px solid var(--cfg-border);cursor:pointer;color:var(--cfg-ink-faint);font-size:13px;line-height:1;padding:4px 8px;border-radius:6px;font-family:inherit}\n#refresh-btn:hover{color:var(--cfg-ink);background:var(--cfg-surface-2)}\n\n#nav{display:flex;gap:4px;padding:8px 14px 0;background:var(--cfg-surface);border-bottom:1px solid var(--cfg-border);flex-shrink:0}\n#nav button{background:none;border:none;border-bottom:2px solid transparent;color:var(--cfg-ink-faint);font-size:12px;font-weight:600;font-family:inherit;padding:7px 10px;cursor:pointer}\n#nav button:hover{color:var(--cfg-ink)}\n#nav button.active{color:var(--cfg-ink);border-bottom-color:var(--cfg-accent)}\n\n#pending-banner{margin:8px 14px 0;padding:6px 10px;background:var(--cfg-surface-2);border:1px solid var(--cfg-warning);color:var(--cfg-warning);border-radius:6px;font-size:11px;flex-shrink:0}\n\n#body{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:12px}\n.section{display:none;flex-direction:column;gap:12px}\n.section.active{display:flex}\n\n.notice{font-size:11.5px;color:var(--cfg-warning);background:var(--cfg-surface-2);border:1px solid var(--cfg-border);border-radius:6px;padding:6px 10px}\n.muted{color:var(--cfg-ink-faint)}\n.muted-note{color:var(--cfg-ink-faint);font-size:11px;padding:6px 10px}\n.empty{color:var(--cfg-ink-faint);font-size:11.5px;padding:8px}\n\n.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:10px}\n.card{background:var(--cfg-surface);border:1px solid var(--cfg-border);border-radius:8px;overflow:hidden;scroll-margin-top:12px}\n.card.hl{outline:2px solid var(--cfg-accent);outline-offset:-1px}\n.card .title{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 10px;font-size:12px;font-weight:700;background:var(--cfg-surface-2);border-bottom:1px solid var(--cfg-border)}\n.card .title .aside{font-weight:400;color:var(--cfg-ink-faint);font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:50%}\n.card .body{padding:8px 10px}\n.card dl{display:grid;grid-template-columns:auto minmax(0,1fr);gap:4px 12px;align-items:baseline}\n.card dt{color:var(--cfg-ink-faint);white-space:nowrap;font-size:11.5px}\n.card dd{text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-variant-numeric:tabular-nums;font-size:11.5px}\n.bar{grid-column:1/3;height:4px;background:var(--cfg-muted-bg);border-radius:2px;overflow:hidden;margin:2px 0 4px}\n.bar>span{display:block;height:100%;background:var(--cfg-accent)}\n.bar.warn>span{background:var(--cfg-warning)}\n\n.sect-h{font-size:11px;font-weight:700;color:var(--cfg-ink-faint);text-transform:uppercase;letter-spacing:.06em;display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.pillbar{display:flex;gap:4px}\n.pillbar button{background:var(--cfg-surface-2);border:1px solid var(--cfg-border);color:var(--cfg-ink-faint);font-size:10.5px;font-weight:600;padding:2px 8px;border-radius:999px;cursor:pointer;font-family:inherit}\n.pillbar button.active{color:var(--cfg-ink);border-color:var(--cfg-accent)}\nlabel.inline{display:inline-flex;align-items:center;gap:5px;font-size:11px;color:var(--cfg-ink-faint)}\nselect,input[type=text],input[type=password],input[type=number],textarea{background:var(--cfg-surface-2);color:var(--cfg-ink);border:1px solid var(--cfg-border);border-radius:5px;padding:3px 7px;font-size:11px;font-family:inherit}\ninput:disabled,select:disabled,textarea:disabled{opacity:.5;cursor:not-allowed}\n\n.tag{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;background:var(--cfg-surface-2);border:1px solid var(--cfg-border);border-radius:5px;padding:1px 6px;color:var(--cfg-ink-faint)}\n.tag.ok{color:var(--cfg-success);border-color:var(--cfg-success)}\n.tag.bad{color:var(--cfg-danger);border-color:var(--cfg-danger)}\n.tag.warn-tag{color:var(--cfg-warning);border-color:var(--cfg-warning)}\na.link{color:var(--cfg-accent);text-decoration:none;cursor:pointer}\na.link:hover{text-decoration:underline}\n\ntable{width:100%;border-collapse:collapse}\nth{font-size:10px;font-weight:700;color:var(--cfg-ink-faint);text-transform:uppercase;letter-spacing:.04em;text-align:left;padding:4px 8px;border-bottom:1px solid var(--cfg-border)}\ntd{font-size:11.5px;padding:5px 8px;border-bottom:1px solid var(--cfg-surface-2);vertical-align:middle}\ntr.hl td{background:var(--cfg-surface-2)}\ntd.matrix-cell{text-align:center;cursor:pointer}\ntd.matrix-cell.disabled{opacity:.35;cursor:default}\n\n.btn-sm{background:var(--cfg-surface-2);border:1px solid var(--cfg-border);color:var(--cfg-ink);font-size:10.5px;font-weight:600;padding:3px 8px;border-radius:5px;cursor:pointer;font-family:inherit}\n.btn-sm:hover{border-color:var(--cfg-accent)}\n.btn-sm:disabled{opacity:.5;cursor:not-allowed}\n.btn-sm.danger{color:var(--cfg-danger);border-color:var(--cfg-danger)}\n.btn-sm.confirm{background:var(--cfg-danger);color:#fff;border-color:var(--cfg-danger)}\n.btn-sm.primary{color:var(--cfg-accent);border-color:var(--cfg-accent)}\n\n.cfg-row{display:contents}\n.cfg-help{font-size:10.5px}\n.cfg-err{color:var(--cfg-danger);font-size:10.5px}\n\n.form-panel{background:var(--cfg-surface-2);border:1px solid var(--cfg-border);border-radius:8px;padding:10px;margin-top:6px;display:flex;flex-direction:column;gap:6px}\n.form-panel .field{display:flex;flex-direction:column;gap:2px;font-size:11px}\n.form-panel .field label{color:var(--cfg-ink-faint);font-size:10.5px}\n.form-row{display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end}\n\n.reveal-box{background:var(--cfg-surface-2);border:1px dashed var(--cfg-warning);border-radius:8px;padding:10px;font-size:11px;display:flex;flex-direction:column;gap:6px}\n.reveal-box code{display:block;word-break:break-all;background:var(--cfg-muted-bg);padding:4px 6px;border-radius:4px;font-family:Menlo,Monaco,monospace;font-size:10.5px}\n\n.mm-list{max-height:160px;overflow-y:auto;margin-top:4px;padding:4px;background:var(--cfg-muted-bg);border-radius:5px}\n.wallet-card-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}\n\n#toast{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);background:var(--cfg-surface-2);border:1px solid var(--cfg-border);border-radius:8px;padding:8px 14px;font-size:11.5px;display:none;max-width:80%;z-index:10}\n#toast.err{border-color:var(--cfg-danger);color:var(--cfg-danger)}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"topbar\">\n <h1>agentproto config</h1>\n <span class=\"chip\" id=\"daemon-chip\"><span class=\"d\"></span><span id=\"daemon-txt\">daemon…</span></span>\n <button id=\"refresh-btn\" title=\"Refresh\" type=\"button\">↻ refresh</button>\n </div>\n <div id=\"nav\"></div>\n <div class=\"notice\" id=\"pending-banner\" style=\"display:none\"></div>\n <div id=\"body\">\n <div class=\"notice\" id=\"notfound\" style=\"display:none\"></div>\n <div class=\"section\" data-section=\"wallets\" id=\"sec-wallets\"></div>\n <div class=\"section\" data-section=\"harnesses\" id=\"sec-harnesses\"></div>\n <div class=\"section\" data-section=\"models\" id=\"sec-models\"></div>\n <div class=\"section\" data-section=\"defaults\" id=\"sec-defaults\"></div>\n <div class=\"section\" data-section=\"remote\" id=\"sec-remote\"></div>\n <div class=\"section\" data-section=\"advanced\" id=\"sec-advanced\"></div>\n </div>\n</div>\n<div id=\"toast\"></div>\n<script>\nvar callTool = null;\nvar updateModelContext = null;\nvar onTeardownFn = null;\nvar APP_ID = ${JSON.stringify(APP_ID)};\nvar SECTIONS = ${JSON.stringify(SECTIONS)};\n\n// ── deep-link fragment parser (fragment.ts, embedded verbatim so the\n// browser runs the exact function fragment.test.ts exercises) ──\n${parseConfigFragment.toString()}\n${buildConfigFragment.toString()}\n${shouldApplyIncomingView.toString()}\n\n// app_tool_call wraps its dispatch result at least once (its own text-result\n// body is the JSON-stringified inner MCP tool result); peel back through\n// nested {content:[{type:\"text\",text:...}]} shells until the payload stops\n// looking like one — same convention as ops-panel/session-viewer.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try { cur = JSON.parse(cur); continue; } catch (e) { return cur; }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);\n}\n\n// Never throws: every card loads independently, so one failing tool must\n// never crash another card's render. An \"unknown daemon tool\" failure (a\n// write tool, or config_get/config_set, on a daemon that predates them) is\n// distinguished from any other failure so the caller can render a muted\n// feature-detect note instead of an error. Used for BOTH reads and writes:\n// every write tool here answers either a JSON object (success) or a plain\n// string (its own error text), the same two shapes a read tool answers.\nfunction loadTool(tool, args) {\n return callApp(tool, args).then(function (d) {\n if (typeof d === \"string\") {\n return { ok: false, message: d, unknown: /unknown daemon tool/.test(d) };\n }\n if (d && typeof d === \"object\" && typeof d.error === \"string\") {\n return { ok: false, message: d.error, unknown: /unknown daemon tool/.test(d.error) };\n }\n return { ok: true, data: d };\n }).catch(function (e) {\n var msg = (e && e.message) || String(e);\n return { ok: false, message: msg, unknown: false };\n });\n}\n\nfunction escHtml(s) {\n return String(s == null ? \"\" : s).replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\nfunction humanize(s) { return String(s || \"\").replace(/[-_]+/g, \" \"); }\n\nfunction formatTokens(n) {\n if (n == null || !isFinite(n)) return \"\";\n var trim = function (t) { return t.replace(/\\\\.0$/, \"\"); };\n if (n >= 1000000) return trim((n / 1000000).toFixed(1)) + \"M\";\n if (n >= 1000) return trim((n / 1000).toFixed(1)) + \"k\";\n return String(n);\n}\n\nfunction formatCost(usd) {\n if (usd == null || !isFinite(usd)) return \"\";\n if (usd > 0 && usd < 0.01) return \"<$0.01\";\n return \"$\" + usd.toFixed(2);\n}\n\nfunction formatDate(iso) {\n if (!iso) return \"\";\n var d = new Date(iso);\n if (isNaN(d.getTime())) return \"\";\n return d.toLocaleString();\n}\n\nfunction formatMs(ms) {\n if (ms == null || !isFinite(ms)) return \"\";\n if (ms >= 60000) return Math.round(ms / 60000) + \"m\";\n if (ms >= 1000) return Math.round(ms / 1000) + \"s\";\n return ms + \"ms\";\n}\n\nfunction fmtRelative(iso) {\n if (!iso) return \"\";\n var ms = Date.now() - new Date(iso).getTime();\n if (!isFinite(ms)) return \"\";\n var s = Math.round(ms / 1000);\n if (s < 5) return \"just now\";\n if (s < 60) return s + \"s ago\";\n var m = Math.round(s / 60);\n if (m < 60) return m + \"m ago\";\n var h = Math.round(m / 60);\n if (h < 24) return h + \"h ago\";\n return Math.round(h / 24) + \"d ago\";\n}\n\nvar BILLING_LABELS = { \"oauth-bearer\": \"Subscription\", \"api-key\": \"API key\" };\n\n// ── card builders (session-header-cards.tsx visual language: card surface,\n// title strip w/ muted aside, two-col dl grid, 4px progress bar) ──\nfunction cardOpen(id, title, aside) {\n return '<div class=\"card\" data-card-id=\"' + escHtml(id) + '\">'\n + '<div class=\"title\"><span>' + escHtml(title) + '</span>'\n + (aside ? '<span class=\"aside\">' + aside + '</span>' : '')\n + '</div><div class=\"body\"><dl>';\n}\nfunction cardClose() { return '</dl></div></div>'; }\nfunction row(label, value, title) {\n return '<dt>' + escHtml(label) + '</dt><dd' + (title ? ' title=\"' + escHtml(title) + '\"' : '') + '>' + value + '</dd>';\n}\nfunction bar(pct) {\n var p = Math.max(0, Math.min(100, pct));\n return '<div class=\"bar' + (p >= 80 ? ' warn' : '') + '\"><span style=\"width:' + p + '%\"></span></div>';\n}\n\nvar toastTimer = null;\nfunction toast(msg, isErr) {\n var el = document.getElementById(\"toast\");\n el.textContent = msg;\n el.className = isErr ? \"err\" : \"\";\n el.style.display = \"block\";\n if (toastTimer) clearTimeout(toastTimer);\n toastTimer = setTimeout(function () { el.style.display = \"none\"; }, isErr ? 6000 : 3000);\n}\n\n// Two-click confirm (ops-panel's pattern): first click arms the button\n// (\"sure?\"), a second click within 4s fires the callback; anything else\n// disarms it. Shared by every destructive action in this panel (delete\n// wallet, delete preset, disable remote, revoke pairing).\nvar armed = {};\nfunction confirmClick(key, btn, fn) {\n if (armed[key]) {\n clearTimeout(armed[key].t);\n delete armed[key];\n fn();\n return;\n }\n var prev = btn.textContent;\n btn.classList.add(\"confirm\");\n btn.textContent = \"sure?\";\n armed[key] = { t: setTimeout(function () {\n delete armed[key];\n btn.classList.remove(\"confirm\");\n btn.textContent = prev;\n }, 4000) };\n}\n\nfunction copyToClipboard(text) {\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(text).then(function () { toast(\"Copied.\", false); }, function () { toast(\"Copy failed.\", true); });\n return;\n }\n try {\n var ta = document.createElement(\"textarea\");\n ta.value = text;\n ta.style.position = \"fixed\";\n ta.style.opacity = \"0\";\n document.body.appendChild(ta);\n ta.focus();\n ta.select();\n document.execCommand(\"copy\");\n document.body.removeChild(ta);\n toast(\"Copied.\", false);\n } catch (e) {\n toast(\"Copy failed.\", true);\n }\n}\nfunction wireCopyButtons(container, valuesByKey) {\n var btns = container.querySelectorAll(\"[data-copy]\");\n for (var i = 0; i < btns.length; i++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n var key = btn.getAttribute(\"data-copy\");\n var value = valuesByKey[key];\n if (value) copyToClipboard(value);\n });\n })(btns[i]);\n }\n}\n\n// ============================================================\n// generic config_get / config_set field editor — shared by Defaults,\n// per-harness adapter defaults, and titler.model (Models section).\n// ============================================================\n\n// Last revision seen from ANY config_get/config_set response, reused on\n// every write (the spec: \"always pass the last revision\"). A daemon that\n// predates config_get/config_set never sets this; config_set is simply\n// called without a revision then (server treats that as unconditional).\nvar configRevision = null;\n\n// path -> true for every currently-known pending-restart key, merged from\n// every config_get response seen anywhere in the app. Drives the persistent\n// top banner — never restarts anything itself, only tells the user to.\nvar pendingRestartMap = {};\n\nfunction notePendingRestart(rows) {\n var changed = false;\n for (var i = 0; i < (rows || []).length; i++) {\n var r = rows[i];\n if (r.pendingRestart) {\n if (!pendingRestartMap[r.path]) changed = true;\n pendingRestartMap[r.path] = true;\n } else if (pendingRestartMap[r.path]) {\n delete pendingRestartMap[r.path];\n changed = true;\n }\n }\n if (changed) renderPendingBanner();\n}\n\nfunction renderPendingBanner() {\n var el = document.getElementById(\"pending-banner\");\n if (!el) return;\n var keys = [];\n for (var k in pendingRestartMap) if (Object.prototype.hasOwnProperty.call(pendingRestartMap, k)) keys.push(k);\n if (keys.length === 0) {\n el.style.display = \"none\";\n el.textContent = \"\";\n return;\n }\n keys.sort();\n el.style.display = \"block\";\n el.textContent = \"Restart the daemon yourself to apply: \" + keys.join(\", \") + \". This app never restarts it for you.\";\n}\n\n// Type inference for a config_get row: config-tools.ts's describeKey\n// spreads deriveValueType(entry.schema) onto every row, so row.valueType\n// (\"boolean\" | \"number\" | \"integer\" | \"string\" | \"enum\" | \"string[]\" |\n// \"object\" | \"unknown\") plus row.enum / row.min / row.max are the registry's\n// OWN answer and win outright. The JS-type inference below is only a\n// fallback for a daemon old enough to predate that field entirely (absent,\n// not merely \"unknown\" — \"unknown\" is itself a real, present answer for a\n// schema deriveValueType can't type, e.g. a mixed union, and must NOT be\n// re-guessed from the current value).\nfunction inferValueType(row) {\n if (row.valueType) return row.valueType;\n var v = row.effective !== undefined ? row.effective : row.value;\n if (v === undefined) v = row.default;\n if (typeof v === \"boolean\") return \"boolean\";\n if (typeof v === \"number\") return \"number\";\n if (typeof v === \"string\") return \"string\";\n if (Array.isArray(v)) {\n var allStr = true;\n for (var i = 0; i < v.length; i++) if (typeof v[i] !== \"string\") allStr = false;\n return allStr ? \"string[]\" : \"object\";\n }\n if (v && typeof v === \"object\") return \"object\";\n return \"unknown\";\n}\n\n// defaults.spawn.browser is a union of the literals \"headless\" and false\n// (config-schema.ts) - deriveValueType's enum-from-union heuristic requires\n// EVERY literal to be a string, and false isn't one, so this one specific\n// key comes back \"unknown\" rather than \"enum\" (verified against\n// packages/runtime/src/config-tools.ts's deriveValueType). A checkbox whose\n// two states are not plain true/false is still the right control for it, so\n// it keeps its own tiny special case rather than a general boolean-like-enum\n// mechanism nothing else needs.\nvar BOOL_LIKE_FIELDS = {\n \"defaults.spawn.browser\": { on: \"headless\", off: false, onLabel: \"headless\" },\n};\n\nfunction pathId(path) { return \"cfg-\" + String(path).replace(/[^a-zA-Z0-9]/g, \"-\"); }\n\nfunction writableReasonText(row) {\n if (row.secret) return \"secret, set via wallets or the CLI\";\n return \"read-only, edit the config file by hand\";\n}\n\nfunction configDisplayValue(row) {\n if (row.secret) {\n if (row.secret.set === false) return \"not set\";\n if (typeof row.secret.set === \"boolean\") {\n var bits = [];\n if (row.secret.fingerprint) bits.push(row.secret.fingerprint);\n if (row.secret.last4) bits.push(row.secret.last4);\n return bits.length ? bits.join(\" / \") : \"set\";\n }\n var parts = [];\n for (var k in row.secret) {\n if (Object.prototype.hasOwnProperty.call(row.secret, k)) {\n parts.push(k + \": \" + (row.secret[k] && row.secret[k].set ? \"set\" : \"not set\"));\n }\n }\n return parts.length ? parts.join(\", \") : \"no keys\";\n }\n return row.effective !== undefined ? row.effective : row.value;\n}\n\nfunction numberBoundAttrs(row) {\n var attrs = \"\";\n if (typeof row.min === \"number\") attrs += ' min=\"' + row.min + '\"';\n if (typeof row.max === \"number\") attrs += ' max=\"' + row.max + '\"';\n return attrs;\n}\n\nfunction configControlHtml(row) {\n var vt = inferValueType(row);\n var val = row.effective !== undefined ? row.effective : (row.value !== undefined ? row.value : row.default);\n var boolLike = BOOL_LIKE_FIELDS[row.path];\n var disabledAttr = row.writable ? \"\" : \" disabled\";\n var titleAttr = row.writable ? \"\" : ' title=\"' + escHtml(writableReasonText(row)) + '\"';\n if (boolLike) {\n var checked = val === boolLike.on;\n return '<label class=\"inline\"><input type=\"checkbox\" data-cfg-bool-like=\"' + escHtml(row.path) + '\"' + (checked ? \" checked\" : \"\") + disabledAttr + titleAttr + '> ' + escHtml(boolLike.onLabel || \"on\") + '</label>';\n }\n if (vt === \"boolean\") {\n return '<input type=\"checkbox\" data-cfg-auto=\"' + escHtml(row.path) + '\"' + (val ? \" checked\" : \"\") + disabledAttr + titleAttr + '>';\n }\n if (vt === \"enum\") {\n var enumOpts = row.enum || [];\n var out = '<select data-cfg-auto=\"' + escHtml(row.path) + '\"' + disabledAttr + titleAttr + '>';\n for (var i = 0; i < enumOpts.length; i++) {\n out += '<option value=\"' + escHtml(enumOpts[i]) + '\"' + (enumOpts[i] === val ? \" selected\" : \"\") + '>' + escHtml(enumOpts[i]) + '</option>';\n }\n return out + '</select>';\n }\n if (vt === \"number\" || vt === \"integer\") {\n return '<input type=\"number\" data-cfg-auto=\"' + escHtml(row.path) + '\" value=\"' + escHtml(val == null ? \"\" : String(val)) + '\"'\n + (vt === \"integer\" ? ' step=\"1\"' : \"\") + numberBoundAttrs(row) + disabledAttr + titleAttr + '>';\n }\n if (vt === \"string[]\") {\n return '<input type=\"text\" data-cfg-input=\"' + escHtml(row.path) + '\" value=\"' + escHtml((val || []).join(\", \")) + '\" placeholder=\"comma, separated, list\"' + disabledAttr + titleAttr + '>'\n + ' <button type=\"button\" class=\"btn-sm\" data-cfg-save=\"' + escHtml(row.path) + '\"' + disabledAttr + '>save</button>';\n }\n if (vt === \"object\" || vt === \"unknown\") {\n // \"unknown\" is a real, present answer (a schema deriveValueType can't\n // type, e.g. a mixed boolean/object union) — a raw JSON editor is the\n // one control that can represent any of those shapes without guessing.\n return '<div><textarea data-cfg-input=\"' + escHtml(row.path) + '\" rows=\"4\" style=\"width:100%;font-family:Menlo,Monaco,monospace;font-size:11px\"' + disabledAttr + titleAttr + '>' + escHtml(JSON.stringify(val === undefined ? null : val, null, 2)) + '</textarea>'\n + ' <button type=\"button\" class=\"btn-sm\" data-cfg-save=\"' + escHtml(row.path) + '\"' + disabledAttr + '>save</button></div>';\n }\n return '<input type=\"text\" data-cfg-auto=\"' + escHtml(row.path) + '\" value=\"' + escHtml(val == null ? \"\" : String(val)) + '\"' + disabledAttr + titleAttr + '>';\n}\n\n// One dl \"row\" for a config key. Wrapped in a display:contents div so the\n// dt/dd pairs stay direct grid children (the 2-col dl grid) while still\n// being addressable as one group for wiring/lookup. A writable:false row\n// still renders a real control (checkbox/select/input), just disabled,\n// with the reason in its title, never a second, ad hoc read-only widget.\nfunction configFieldHtml(row) {\n var label = row.label || humanize(String(row.path).split(\".\").slice(-1)[0]);\n var badge = '<span class=\"tag\">' + escHtml(row.apply) + '</span>';\n var extra = \"\";\n if (row.source === \"env\" && row.envOverride) extra += ' <span class=\"tag bad\">env ' + escHtml(row.envOverride) + '</span>';\n if (row.pendingRestart) extra += ' <span class=\"tag warn-tag\">restart pending</span>';\n\n var dd = \"\";\n if (row.secret) {\n dd += escHtml(String(configDisplayValue(row)));\n } else {\n dd += configControlHtml(row);\n }\n dd += \" \" + badge + extra;\n if (row.writable && !row.secret && row.value !== undefined) {\n dd += ' <button type=\"button\" class=\"btn-sm\" data-cfg-reset=\"' + escHtml(row.path) + '\" title=\"Reset to default\">reset</button>';\n }\n\n var html = '<div class=\"cfg-row\" data-cfg-path=\"' + escHtml(row.path) + '\">'\n + '<dt title=\"' + escHtml(row.path) + '\">' + escHtml(label) + '</dt><dd>' + dd + '</dd>';\n if (row.help) html += '<dt></dt><dd class=\"cfg-help muted\">' + escHtml(row.help) + '</dd>';\n html += '<dt></dt><dd class=\"cfg-err\" data-cfg-err=\"' + escHtml(row.path) + '\" style=\"display:none\"></dd>';\n html += '</div>';\n return html;\n}\n\nfunction buildConfigCardHtml(cardId, title, rows) {\n var body = \"\";\n for (var i = 0; i < rows.length; i++) body += configFieldHtml(rows[i]);\n return '<div class=\"card\" data-card-id=\"' + escHtml(cardId) + '\"><div class=\"body\"><dl>' + body + '</dl></div></div>';\n}\n\nfunction extractConfigSetErrorCode(message) {\n var s = String(message || \"\");\n var start = s.indexOf(\"[\");\n var end = start === -1 ? -1 : s.indexOf(\"]\", start);\n if (start === -1 || end === -1) return \"\";\n return s.slice(start + 1, end);\n}\n\nfunction describeConfigSetError(message) {\n var code = extractConfigSetErrorCode(message);\n var HUMAN = {\n invalid_input: \"That request was not valid.\",\n unknown_key: \"This daemon does not know that setting.\",\n not_writable: \"This setting cannot be changed here.\",\n invalid_value: \"That value is not valid for this setting.\",\n invalid_config: \"That change would leave the config file invalid.\",\n stale_revision: \"This section changed elsewhere. Reloading.\",\n };\n return HUMAN[code] || message;\n}\n\n// Attach listeners for every config control inside \"container\". rowsByPath\n// supplies the row metadata a save/reset needs (valueType for parsing).\n// onSaved(path) fires after a successful write (path just written);\n// onStale() fires on a stale_revision rejection (reload the section).\nfunction wireConfigRows(container, rowsByPath, onSaved, onStale) {\n function submit(path, value, isUnset) {\n var args = isUnset ? { key: path, unset: true, revision: configRevision } : { key: path, value: value, revision: configRevision };\n return loadTool(\"config_set\", args).then(function (r) {\n if (!r.ok) {\n var stale = extractConfigSetErrorCode(r.message) === \"stale_revision\";\n toast(describeConfigSetError(r.message), true);\n if (stale && onStale) onStale();\n return;\n }\n var data = r.data;\n if (data && data.revision) configRevision = data.revision;\n var note = \"Saved.\";\n if (data && data.applied === \"restart-required\") note = \"Saved. Restart required to apply.\";\n if (data && data.shadowedByEnv) note += \" Overridden by \" + data.shadowedByEnv + \".\";\n toast(note, false);\n if (onSaved) onSaved(path);\n });\n }\n\n var autos = container.querySelectorAll(\"[data-cfg-auto]\");\n for (var i = 0; i < autos.length; i++) {\n (function (el) {\n el.addEventListener(\"change\", function () {\n var path = el.getAttribute(\"data-cfg-auto\");\n var value;\n if (el.type === \"checkbox\") value = el.checked;\n else if (el.type === \"number\") value = el.value === \"\" ? null : Number(el.value);\n else value = el.value;\n submit(path, value, false);\n });\n })(autos[i]);\n }\n\n var boolLikes = container.querySelectorAll(\"[data-cfg-bool-like]\");\n for (var b = 0; b < boolLikes.length; b++) {\n (function (el) {\n el.addEventListener(\"change\", function () {\n var path = el.getAttribute(\"data-cfg-bool-like\");\n var spec = BOOL_LIKE_FIELDS[path];\n submit(path, el.checked ? spec.on : spec.off, false);\n });\n })(boolLikes[b]);\n }\n\n var saves = container.querySelectorAll(\"[data-cfg-save]\");\n for (var s = 0; s < saves.length; s++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n var path = btn.getAttribute(\"data-cfg-save\");\n var row = rowsByPath[path];\n var input = container.querySelector('[data-cfg-input=\"' + cssEscape(path) + '\"]');\n var errEl = container.querySelector('[data-cfg-err=\"' + cssEscape(path) + '\"]');\n if (errEl) { errEl.style.display = \"none\"; errEl.textContent = \"\"; }\n var vt = row ? inferValueType(row) : \"string\";\n var value;\n if (vt === \"string[]\") {\n value = input.value.split(\",\").map(function (x) { return x.trim(); }).filter(function (x) { return x.length > 0; });\n } else if (vt === \"object\" || vt === \"unknown\") {\n try {\n value = JSON.parse(input.value);\n } catch (e) {\n if (errEl) { errEl.style.display = \"block\"; errEl.textContent = \"Invalid JSON: \" + e.message; }\n return;\n }\n } else {\n value = input.value;\n }\n submit(path, value, false);\n });\n })(saves[s]);\n }\n\n var resets = container.querySelectorAll(\"[data-cfg-reset]\");\n for (var r = 0; r < resets.length; r++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n submit(btn.getAttribute(\"data-cfg-reset\"), undefined, true);\n });\n })(resets[r]);\n }\n}\n\n// ============================================================\n// nav + deep-link routing\n// ============================================================\n\nvar current = { section: SECTIONS[0].id, id: undefined, sub: undefined };\nvar loaded = {}; // section -> true once its data has been fetched at least once\n\nfunction renderNav() {\n var el = document.getElementById(\"nav\");\n var html = \"\";\n for (var i = 0; i < SECTIONS.length; i++) {\n html += '<button data-section=\"' + SECTIONS[i].id + '\" class=\"' + (SECTIONS[i].id === current.section ? \"active\" : \"\") + '\">' + escHtml(SECTIONS[i].label) + '</button>';\n }\n el.innerHTML = html;\n var btns = el.querySelectorAll(\"button[data-section]\");\n for (var k = 0; k < btns.length; k++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n location.hash = buildConfigFragment(btn.getAttribute(\"data-section\"));\n });\n })(btns[k]);\n }\n}\n\nfunction showSection(sectionId) {\n var els = document.querySelectorAll(\".section\");\n for (var i = 0; i < els.length; i++) {\n els[i].classList.toggle(\"active\", els[i].getAttribute(\"data-section\") === sectionId);\n }\n var btns = document.querySelectorAll(\"#nav button[data-section]\");\n for (var j = 0; j < btns.length; j++) {\n btns[j].classList.toggle(\"active\", btns[j].getAttribute(\"data-section\") === sectionId);\n }\n}\n\n// Scroll to + highlight the card matching 'id' within the active section's\n// container. Unknown id means the \"not found\" banner (never a blank page).\nfunction applyDeepLink(sectionId, id, sub) {\n var notfound = document.getElementById(\"notfound\");\n notfound.style.display = \"none\";\n var prevHl = document.querySelectorAll(\".card.hl, tr.hl\");\n for (var p = 0; p < prevHl.length; p++) prevHl[p].classList.remove(\"hl\");\n if (id === undefined) return;\n var container = document.getElementById(\"sec-\" + sectionId);\n if (!container) return;\n var targetId = sectionId === \"remote\" && id === \"pairing\" ? \"pairing\" : id;\n var target = container.querySelector('[data-card-id=\"' + cssEscape(targetId) + '\"]');\n if (!target) {\n notfound.textContent = \"not found: \" + id + (sub ? \"/\" + sub : \"\");\n notfound.style.display = \"block\";\n return;\n }\n target.classList.add(\"hl\");\n scrollIntoViewSafe(target);\n if (sub) {\n var row = target.querySelector('[data-row-id=\"' + cssEscape(sub) + '\"]');\n if (row) {\n row.classList.add(\"hl\");\n scrollIntoViewSafe(row);\n }\n }\n}\n\n// Not every embedding host implements scrollIntoView on a sandboxed/minimal\n// DOM — highlighting must still work even when scrolling can't.\nfunction scrollIntoViewSafe(el) {\n if (el && typeof el.scrollIntoView === \"function\") el.scrollIntoView({ block: \"nearest\" });\n}\n\n// CSS.escape isn't guaranteed in every sandboxed webview — a minimal,\n// dependency-free fallback for the characters our own ids ever contain.\nfunction cssEscape(s) {\n return String(s).replace(/[^a-zA-Z0-9_-]/g, function (c) {\n return \"\\\\\\\\\" + c.charCodeAt(0).toString(16) + \" \";\n });\n}\n\n// Clears every secret this panel ever holds only in memory (a remote-enable\n// reveal, a pairing offer) — called on navigating away from #remote and on\n// host teardown. Never persisted, never in updateModelContext.\nfunction clearRemoteSecrets() {\n remoteReveal = null;\n pairReveal = null;\n clearPairCountdown();\n // The #sec-remote DOM may still show a rendered reveal box from before\n // this call — clearing the variable alone does not touch it. Force the\n // next visit to re-fetch and re-render from scratch (remoteReveal is\n // null by then, so no reveal box comes back) instead of leaving the\n // stale secret sitting in a hidden .section until some other event\n // happens to re-render it.\n loaded.remote = false;\n}\n\nfunction route(pushBack) {\n var parsed = parseConfigFragment(location.hash);\n if (current.section === \"remote\" && parsed.section !== \"remote\") {\n clearRemoteSecrets();\n }\n current = parsed;\n showSection(parsed.section);\n renderNav();\n ensureLoaded(parsed.section).then(function () {\n applyDeepLink(parsed.section, parsed.id, parsed.sub);\n });\n if (pushBack) location.hash = buildConfigFragment(parsed.section, parsed.id, parsed.sub);\n}\n\nwindow.addEventListener(\"hashchange\", function () { route(false); });\n\n// ── MCP-hosted deep links ──\n// A browser tab navigates via location.hash directly; an MCP host (Claude\n// Desktop, Codex, VS Code) has no URL bar for this panel, so it opens\n// app_ui_config with a view argument instead (plan section 3.4). The bridge\n// (app-ui-apps.ts) surfaces that as tool input; this turns it into the same\n// #<section>/<id>/<sub> fragment a browser deep link would use.\n//\n// location.hash still wins on the very first route — an actual browser deep\n// link must not be overridden by a stale/default tool call. Every\n// subsequent tool-input notification (a fresh app_ui_config invocation while\n// the panel is already open) is a live \"go to this view\" command and always\n// re-routes.\nvar toolInputCount = 0;\nvar hashPresentAtLoad = !!(location.hash && location.hash !== \"#\");\n\nfunction routeToView(view) {\n if (typeof view !== \"string\" || !view) return;\n var parsed = parseConfigFragment(view);\n var frag = buildConfigFragment(parsed.section, parsed.id, parsed.sub);\n if (location.hash === frag) { route(false); } else { location.hash = frag; }\n}\n\nfunction handleToolInput(input) {\n toolInputCount += 1;\n if (!shouldApplyIncomingView(toolInputCount, hashPresentAtLoad)) return;\n routeToView(input && input.view);\n}\n\n// ============================================================\n// section data + render dispatch\n// ============================================================\n\nvar SECTION_LOADERS = {\n wallets: loadWallets,\n harnesses: loadHarnesses,\n models: loadModels,\n defaults: loadDefaults,\n remote: loadRemote,\n advanced: loadAdvanced,\n};\n\nfunction ensureLoaded(sectionId) {\n if (loaded[sectionId]) return Promise.resolve();\n var fn = SECTION_LOADERS[sectionId];\n if (!fn) return Promise.resolve();\n return fn().then(function () { loaded[sectionId] = true; });\n}\n\nfunction reloadCurrent() {\n loaded[current.section] = false;\n return ensureLoaded(current.section).then(function () {\n applyDeepLink(current.section, current.id, current.sub);\n });\n}\n\n// ============================================================\n// Wallets\n// ============================================================\n\nvar walletsState = {\n window: \"7d\",\n profilesResult: null,\n rollupResult: null,\n presetsResult: null,\n discoveredResult: null,\n showAddForm: false,\n showDiscovered: false,\n expanded: {}, // profileId -> { edit: bool, curate: bool }\n providerModelsByEndpoint: {}, // endpoint -> catalog_provider_models rows\n};\n\nfunction loadWallets() {\n return Promise.all([\n loadTool(\"auth_profile_list\", { full: true }),\n loadTool(\"usage_rollup\", { window: walletsState.window, groupBy: [\"profile\"] }),\n loadTool(\"harness_preset_list\", {}),\n ]).then(function (results) {\n walletsState.profilesResult = results[0];\n walletsState.rollupResult = results[1];\n walletsState.presetsResult = results[2];\n renderWallets();\n });\n}\n\nfunction reloadWalletsWindow(win) {\n walletsState.window = win;\n fetchRollup(false);\n}\n\nfunction fetchRollup(probe) {\n return loadTool(\"usage_rollup\", { window: walletsState.window, groupBy: [\"profile\"], probe: !!probe }).then(function (r) {\n walletsState.rollupResult = r;\n renderWallets();\n });\n}\n\nfunction byProfileMap(rollupResult) {\n var map = {};\n if (rollupResult.ok && rollupResult.data && Array.isArray(rollupResult.data.byProfile)) {\n for (var i = 0; i < rollupResult.data.byProfile.length; i++) {\n map[rollupResult.data.byProfile[i].profileRef] = rollupResult.data.byProfile[i];\n }\n }\n return map;\n}\n\nfunction presetsReferencing(profileId) {\n var out = [];\n var pr = walletsState.presetsResult;\n if (!pr || !pr.ok) return out;\n var presets = (pr.data && pr.data.presets) || [];\n for (var i = 0; i < presets.length; i++) {\n if (presets[i].profileRef === profileId) out.push(presets[i]);\n }\n return out;\n}\n\nfunction getWalletExpanded(id) {\n if (!walletsState.expanded[id]) walletsState.expanded[id] = { edit: false, curate: false, curateMode: undefined };\n return walletsState.expanded[id];\n}\n\nfunction replaceProfileInState(profile) {\n var pr = walletsState.profilesResult;\n if (!pr || !pr.ok || !pr.data || !Array.isArray(pr.data.profiles)) return;\n var list = pr.data.profiles;\n for (var i = 0; i < list.length; i++) {\n if (list[i].id === profile.id) { list[i] = profile; return; }\n }\n list.push(profile);\n}\n\nfunction removeProfileFromState(id) {\n var pr = walletsState.profilesResult;\n if (!pr || !pr.ok || !pr.data || !Array.isArray(pr.data.profiles)) return;\n pr.data.profiles = pr.data.profiles.filter(function (p) { return p.id !== id; });\n}\n\nfunction renderWallets() {\n var el = document.getElementById(\"sec-wallets\");\n var rr = walletsState.rollupResult;\n var html = '<div class=\"sect-h\">Spend<span class=\"pillbar\" id=\"wallet-window\"></span>'\n + '<button type=\"button\" class=\"btn-sm\" id=\"spend-refresh-live\">refresh live</button></div>'\n + '<div class=\"muted-note\" style=\"padding:0 0 4px\">Refreshing live consumes a small sliver of the rate budget for that wallet.</div>';\n\n if (rr && rr.ok) {\n var t = rr.data.total || {};\n html += '<div class=\"card\"><div class=\"body\"><dl>'\n + row(\"Window\", escHtml(rr.data.window))\n + (typeof t.spentUsd === \"number\" ? row(\"Spend\", formatCost(t.spentUsd)) : \"\")\n + (typeof t.tokensIn === \"number\" || typeof t.tokensOut === \"number\"\n ? row(\"Tokens\", (typeof t.tokensIn === \"number\" ? formatTokens(t.tokensIn) + \" in\" : \"\") + (typeof t.tokensIn === \"number\" && typeof t.tokensOut === \"number\" ? \" · \" : \"\") + (typeof t.tokensOut === \"number\" ? formatTokens(t.tokensOut) + \" out\" : \"\"))\n : \"\")\n + (typeof t.unpricedTokens === \"number\" && t.unpricedTokens > 0 ? row(\"Unpriced tokens\", formatTokens(t.unpricedTokens)) : \"\")\n + '</dl></div></div>';\n } else if (rr) {\n html += '<div class=\"muted-note\">usage_rollup: ' + escHtml(rr.message) + '</div>';\n }\n\n html += '<div class=\"sect-h\">Wallets'\n + '<button type=\"button\" class=\"btn-sm\" id=\"wallet-add-toggle\">' + (walletsState.showAddForm ? \"close\" : \"+ add wallet\") + '</button>'\n + '<button type=\"button\" class=\"btn-sm\" id=\"wallet-scan-toggle\">' + (walletsState.showDiscovered ? \"close\" : \"find credentials\") + '</button>'\n + '</div>';\n\n if (walletsState.showAddForm) html += addWalletFormHtml();\n if (walletsState.showDiscovered) html += discoveredCredentialsHtml();\n\n html += '<div class=\"grid\" id=\"wallet-cards\"></div>';\n\n el.innerHTML = html;\n\n var pillEl = document.getElementById(\"wallet-window\");\n var windows = [\"5h\", \"7d\", \"30d\"];\n var pillHtml = \"\";\n for (var w = 0; w < windows.length; w++) {\n pillHtml += '<button data-win=\"' + windows[w] + '\" class=\"' + (windows[w] === walletsState.window ? \"active\" : \"\") + '\">' + windows[w] + '</button>';\n }\n pillEl.innerHTML = pillHtml;\n var pillBtns = pillEl.querySelectorAll(\"button\");\n for (var pb = 0; pb < pillBtns.length; pb++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () { reloadWalletsWindow(btn.getAttribute(\"data-win\")); });\n })(pillBtns[pb]);\n }\n document.getElementById(\"spend-refresh-live\").addEventListener(\"click\", function () { fetchRollup(true); });\n\n document.getElementById(\"wallet-add-toggle\").addEventListener(\"click\", function () {\n walletsState.showAddForm = !walletsState.showAddForm;\n renderWallets();\n });\n document.getElementById(\"wallet-scan-toggle\").addEventListener(\"click\", function () {\n walletsState.showDiscovered = !walletsState.showDiscovered;\n if (walletsState.showDiscovered && !walletsState.discoveredResult) {\n loadTool(\"auth_discover_credentials\", {}).then(function (r) {\n walletsState.discoveredResult = r;\n renderWallets();\n });\n return;\n }\n renderWallets();\n });\n\n wireAddWalletForm(el);\n wireDiscoveredCredentials(el);\n renderWalletCards();\n}\n\nfunction addWalletFormHtml() {\n return '<div class=\"form-panel\" id=\"wallet-add-form\">'\n + '<div class=\"form-row\">'\n + '<div class=\"field\"><label>Id</label><input type=\"text\" id=\"wf-id\" placeholder=\"my-anthropic-key\"></div>'\n + '<div class=\"field\"><label>Endpoint</label><input type=\"text\" id=\"wf-endpoint\" list=\"wf-endpoint-list\" placeholder=\"anthropic\"><datalist id=\"wf-endpoint-list\">'\n + '<option value=\"anthropic\"><option value=\"openrouter\"><option value=\"moonshot\"><option value=\"openai\"></datalist></div>'\n + '<div class=\"field\"><label>Method</label><select id=\"wf-method\"><option value=\"api-key\">API key</option><option value=\"oauth-bearer\">Subscription</option></select></div>'\n + '<div class=\"field\"><label>Label (optional)</label><input type=\"text\" id=\"wf-label\"></div>'\n + '</div>'\n + '<div class=\"form-row\">'\n + '<div class=\"field\"><label>Credential</label><input type=\"password\" id=\"wf-credential\" placeholder=\"paste secret\"></div>'\n + '<div class=\"field\" id=\"wf-source-field\" style=\"display:none\"><label>Self-refreshing source (alternative)</label><input type=\"text\" id=\"wf-source\" placeholder=\"claude-code-oauth\"></div>'\n + '<button type=\"button\" class=\"btn-sm primary\" id=\"wf-submit\">Create wallet</button>'\n + '</div>'\n + '<div class=\"cfg-err\" id=\"wf-err\" style=\"display:none\"></div>'\n + '</div>';\n}\n\nfunction wireAddWalletForm(container) {\n var methodEl = container.querySelector(\"#wf-method\");\n var sourceField = container.querySelector(\"#wf-source-field\");\n if (!methodEl) return;\n methodEl.addEventListener(\"change\", function () {\n sourceField.style.display = methodEl.value === \"oauth-bearer\" ? \"\" : \"none\";\n });\n container.querySelector(\"#wf-submit\").addEventListener(\"click\", function () {\n var errEl = container.querySelector(\"#wf-err\");\n errEl.style.display = \"none\";\n var id = container.querySelector(\"#wf-id\").value.trim();\n var endpoint = container.querySelector(\"#wf-endpoint\").value.trim();\n var method = methodEl.value;\n var label = container.querySelector(\"#wf-label\").value.trim();\n var credentialInput = container.querySelector(\"#wf-credential\");\n var sourceInput = container.querySelector(\"#wf-source\");\n var credential = credentialInput.value;\n var source = method === \"oauth-bearer\" ? sourceInput.value.trim() : \"\";\n // Clear the secret from the DOM and this local scope's reference to it\n // immediately, before the async call even starts.\n credentialInput.value = \"\";\n if (!id || !endpoint) {\n errEl.style.display = \"block\";\n errEl.textContent = \"Id and endpoint are required.\";\n credential = \"\";\n return;\n }\n if (!credential && !source) {\n errEl.style.display = \"block\";\n errEl.textContent = \"Give either a credential or a self-refreshing source.\";\n credential = \"\";\n return;\n }\n var args = { id: id, endpoint: endpoint, method: method };\n if (label) args.label = label;\n if (credential) args.credential = credential;\n else if (source) args.source = source;\n credential = \"\";\n loadTool(\"auth_profile_create\", args).then(function (r) {\n if (!r.ok) {\n errEl.style.display = \"block\";\n errEl.textContent = r.message;\n return;\n }\n toast(\"Wallet created.\", false);\n walletsState.showAddForm = false;\n loadWallets();\n });\n });\n}\n\nfunction discoveredCredentialsHtml() {\n var dr = walletsState.discoveredResult;\n var html = '<div class=\"card\"><div class=\"body\">';\n if (!dr) {\n html += '<div class=\"muted-note\">scanning…</div>';\n } else if (!dr.ok) {\n html += '<div class=\"muted-note\">auth_discover_credentials: ' + escHtml(dr.message) + '</div>';\n } else {\n var creds = (dr.data && dr.data.credentials) || [];\n if (creds.length === 0) {\n html += '<div class=\"empty\">No importable credentials found on this host.</div>';\n } else {\n html += '<table><thead><tr><th>Endpoint</th><th>Method</th><th>Origin</th><th>Hint</th><th></th></tr></thead><tbody>';\n for (var i = 0; i < creds.length; i++) {\n var c = creds[i];\n var defaultId = c.origin + \"-\" + c.endpoint;\n html += '<tr><td>' + escHtml(c.endpoint) + '</td><td>' + escHtml(BILLING_LABELS[c.method] || c.method) + '</td><td>' + escHtml(c.origin) + '</td><td>' + escHtml(c.hint) + '</td>'\n + '<td><input type=\"text\" class=\"dc-id\" data-dc-idx=\"' + i + '\" value=\"' + escHtml(defaultId) + '\" style=\"width:120px\">'\n + ' <button type=\"button\" class=\"btn-sm\" data-dc-import=\"' + i + '\">import</button></td></tr>';\n }\n html += '</tbody></table>';\n }\n }\n html += '</div></div>';\n return html;\n}\n\nfunction wireDiscoveredCredentials(container) {\n var btns = container.querySelectorAll(\"[data-dc-import]\");\n for (var i = 0; i < btns.length; i++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n var idx = Number(btn.getAttribute(\"data-dc-import\"));\n var dr = walletsState.discoveredResult;\n var c = dr && dr.ok && dr.data.credentials[idx];\n if (!c) return;\n var idInput = container.querySelector('.dc-id[data-dc-idx=\"' + idx + '\"]');\n var id = (idInput && idInput.value.trim()) || (c.origin + \"-\" + c.endpoint);\n loadTool(\"auth_profile_import\", { origin: c.origin, endpoint: c.endpoint, id: id }).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n toast(\"Imported.\", false);\n loadWallets();\n });\n });\n })(btns[i]);\n }\n}\n\nfunction renderWalletCards() {\n var cardsEl = document.getElementById(\"wallet-cards\");\n if (!cardsEl) return;\n var pr = walletsState.profilesResult;\n if (!pr || !pr.ok) {\n cardsEl.innerHTML = '<div class=\"empty\">' + (pr ? escHtml(pr.message) : \"loading…\") + '</div>';\n return;\n }\n var profiles = (pr.data && pr.data.profiles) || [];\n if (profiles.length === 0) {\n cardsEl.innerHTML = '<div class=\"empty\">No auth profiles configured on this host.</div>';\n return;\n }\n var rr = walletsState.rollupResult;\n var spend = rr && rr.ok ? byProfileMap(rr) : {};\n var out = \"\";\n for (var i = 0; i < profiles.length; i++) {\n out += walletCardHtml(profiles[i], spend[profiles[i].id]);\n }\n cardsEl.innerHTML = out;\n wireWalletCards(cardsEl);\n}\n\nfunction walletCardHtml(p, spend) {\n var title = p.label || p.id;\n var exp = getWalletExpanded(p.id);\n var out = cardOpen(p.id, title, escHtml(p.endpoint));\n out += row(\"Method\", escHtml(BILLING_LABELS[p.method] || humanize(p.method)));\n var keyLine = p.keyStatus === \"stored\"\n ? [p.fingerprint, p.last4].filter(Boolean).join(\" · \") || \"stored\"\n : p.keyStatus === \"self-refreshing\" ? \"self-refreshing\" : \"unavailable\";\n out += row(\"Key\", escHtml(keyLine));\n if (p.origin) out += row(\"Imported from\", escHtml(p.origin));\n out += '<dt>Enabled</dt><dd><label class=\"inline\"><input type=\"checkbox\" data-wallet-enabled=\"' + escHtml(p.id) + '\"' + (p.disabled ? \"\" : \" checked\") + '></label></dd>';\n var curated = !p.models || p.models.mode === \"all\" ? \"All eligible\" : (p.models.ids || []).length + \" curated\";\n out += row(\"Models\", escHtml(curated));\n if (spend) {\n if (typeof spend.spentUsd === \"number\") out += row(\"Spend (window)\", formatCost(spend.spentUsd));\n if (p.costBudget && typeof p.costBudget.maxCostUsd === \"number\") {\n var pct = p.costBudget.maxCostUsd > 0 ? ((spend.spentUsd || 0) / p.costBudget.maxCostUsd) * 100 : 0;\n out += row(\"Budget\", formatCost(p.costBudget.maxCostUsd) + \" / \" + escHtml(p.costBudget.window));\n out += bar(pct);\n }\n if (spend.remaining) {\n out += row(\"Remaining quota\", String(spend.remaining.remaining), \"resets \" + formatDate(spend.remaining.resetsAt));\n }\n if (spend.credits && typeof spend.credits.balanceUsd === \"number\") {\n out += row(\"Credit balance\", formatCost(spend.credits.balanceUsd), spend.credits.asOf ? \"as of \" + formatDate(spend.credits.asOf) : undefined);\n }\n } else if (p.costBudget && typeof p.costBudget.maxCostUsd === \"number\") {\n out += row(\"Budget\", formatCost(p.costBudget.maxCostUsd) + \" / \" + escHtml(p.costBudget.window));\n }\n out += '<div class=\"wallet-card-actions\">'\n + '<button type=\"button\" class=\"btn-sm\" data-wallet-edit-toggle=\"' + escHtml(p.id) + '\">' + (exp.edit ? \"close\" : \"rename / budget\") + '</button>'\n + '<button type=\"button\" class=\"btn-sm\" data-wallet-curate-toggle=\"' + escHtml(p.id) + '\">' + (exp.curate ? \"close\" : \"manage models\") + '</button>'\n + '<button type=\"button\" class=\"btn-sm danger\" data-wallet-delete=\"' + escHtml(p.id) + '\">delete</button>'\n + '</div>';\n if (exp.edit) out += walletEditPanelHtml(p);\n if (exp.curate) out += walletModelsEditorHtml(p, walletsState.providerModelsByEndpoint[p.endpoint], exp.curateMode);\n out += cardClose();\n return out;\n}\n\nfunction walletEditPanelHtml(p) {\n var hasBudget = !!(p.costBudget && typeof p.costBudget.maxCostUsd === \"number\");\n return '<div class=\"form-panel\" data-wallet-edit-panel=\"' + escHtml(p.id) + '\">'\n + '<div class=\"field\"><label>Label</label><input type=\"text\" class=\"we-label\" value=\"' + escHtml(p.label || \"\") + '\"></div>'\n + '<label class=\"inline\"><input type=\"checkbox\" class=\"we-no-budget\"' + (hasBudget ? \"\" : \" checked\") + '> no spend cap</label>'\n + '<div class=\"form-row we-budget-fields\" style=\"' + (hasBudget ? \"\" : \"display:none\") + '\">'\n + '<div class=\"field\"><label>Max cost (USD)</label><input type=\"number\" class=\"we-max-cost\" value=\"' + escHtml(hasBudget ? String(p.costBudget.maxCostUsd) : \"\") + '\"></div>'\n + '<div class=\"field\"><label>Window</label><input type=\"text\" class=\"we-window\" value=\"' + escHtml(hasBudget ? p.costBudget.window : \"7d\") + '\"></div>'\n + '<div class=\"field\"><label>Scope</label><select class=\"we-scope\"><option value=\"profile\"' + (hasBudget && p.costBudget.scope === \"profile\" ? \" selected\" : \"\") + '>profile</option><option value=\"session\"' + (hasBudget && p.costBudget.scope === \"session\" ? \" selected\" : \"\") + '>session</option></select></div>'\n + '</div>'\n + '<button type=\"button\" class=\"btn-sm primary\" data-wallet-edit-save=\"' + escHtml(p.id) + '\">save</button>'\n + '</div>';\n}\n\n// pendingMode is the panel's OWN pending selection (getWalletExpanded's\n// curateMode), not necessarily what the server has stored yet: switching to\n// \"curate\" must reveal the checklist right away so there is something to\n// check, without writing a destructive \"curate to nothing\" the instant the\n// radio moves. The first checked model is what actually persists the\n// mode=allow switch (see the .mm-id change handler).\nfunction walletModelsEditorHtml(p, catalogRows, pendingMode) {\n var mode = pendingMode || (p.models && p.models.mode) || \"all\";\n var ids = (p.models && p.models.ids) || [];\n var html = '<div class=\"form-panel\" data-wallet-models-panel=\"' + escHtml(p.id) + '\">';\n html += '<label class=\"inline\"><input type=\"radio\" name=\"mm-mode-' + escHtml(p.id) + '\" value=\"all\" class=\"mm-mode\" data-mm-profile=\"' + escHtml(p.id) + '\"' + (mode === \"all\" ? \" checked\" : \"\") + '> service all eligible</label> ';\n html += '<label class=\"inline\"><input type=\"radio\" name=\"mm-mode-' + escHtml(p.id) + '\" value=\"allow\" class=\"mm-mode\" data-mm-profile=\"' + escHtml(p.id) + '\"' + (mode === \"allow\" ? \" checked\" : \"\") + '> curate</label>';\n if (mode === \"allow\") html += ' <button type=\"button\" class=\"btn-sm\" data-mm-refresh=\"' + escHtml(p.id) + '\">refresh models</button>';\n html += '<div class=\"mm-list\" data-mm-list=\"' + escHtml(p.id) + '\" style=\"' + (mode === \"allow\" ? \"\" : \"display:none\") + '\">';\n if (mode === \"allow\") {\n if (!catalogRows) {\n html += '<div class=\"muted-note\">loading models…</div>';\n } else if (catalogRows.length === 0) {\n html += '<div class=\"empty\">No models known for this endpoint.</div>';\n } else {\n for (var i = 0; i < catalogRows.length; i++) {\n var m = catalogRows[i];\n var checked = ids.indexOf(m.id) !== -1;\n html += '<label class=\"inline\" style=\"display:block;margin:2px 0\"><input type=\"checkbox\" class=\"mm-id\" data-mm-profile=\"' + escHtml(p.id) + '\" value=\"' + escHtml(m.id) + '\"' + (checked ? \" checked\" : \"\") + '> ' + escHtml(m.label || m.id) + '</label>';\n }\n }\n }\n html += '</div></div>';\n return html;\n}\n\nfunction wireWalletCards(container) {\n var enabledToggles = container.querySelectorAll(\"[data-wallet-enabled]\");\n for (var i = 0; i < enabledToggles.length; i++) {\n (function (el) {\n el.addEventListener(\"change\", function () {\n var id = el.getAttribute(\"data-wallet-enabled\");\n var wantEnabled = el.checked;\n el.disabled = true;\n loadTool(\"auth_profile_set_enabled\", { id: id, enabled: wantEnabled }).then(function (r) {\n el.disabled = false;\n if (!r.ok) { toast(r.message, true); el.checked = !wantEnabled; return; }\n replaceProfileInState(r.data.profile);\n toast(wantEnabled ? \"Wallet enabled.\" : \"Wallet disabled.\", false);\n renderWalletCards();\n });\n });\n })(enabledToggles[i]);\n }\n\n var editToggles = container.querySelectorAll(\"[data-wallet-edit-toggle]\");\n for (var e = 0; e < editToggles.length; e++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n var id = btn.getAttribute(\"data-wallet-edit-toggle\");\n var exp = getWalletExpanded(id);\n exp.edit = !exp.edit;\n renderWalletCards();\n });\n })(editToggles[e]);\n }\n\n var editSaves = container.querySelectorAll(\"[data-wallet-edit-save]\");\n for (var s = 0; s < editSaves.length; s++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n var id = btn.getAttribute(\"data-wallet-edit-save\");\n var panel = container.querySelector('[data-wallet-edit-panel=\"' + cssEscape(id) + '\"]');\n var label = panel.querySelector(\".we-label\").value.trim();\n var noBudget = panel.querySelector(\".we-no-budget\").checked;\n var patch = { id: id, label: label === \"\" ? null : label };\n if (noBudget) {\n patch.costBudget = null;\n } else {\n patch.costBudget = {\n maxCostUsd: Number(panel.querySelector(\".we-max-cost\").value) || 0,\n window: panel.querySelector(\".we-window\").value.trim() || \"7d\",\n scope: panel.querySelector(\".we-scope\").value,\n };\n }\n loadTool(\"auth_profile_update\", patch).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n replaceProfileInState(r.data.profile);\n toast(\"Wallet updated.\", false);\n getWalletExpanded(id).edit = false;\n renderWalletCards();\n });\n });\n })(editSaves[s]);\n }\n\n var noBudgetToggles = container.querySelectorAll(\".we-no-budget\");\n for (var nb = 0; nb < noBudgetToggles.length; nb++) {\n (function (el) {\n el.addEventListener(\"change\", function () {\n var fields = el.parentElement.querySelector(\".we-budget-fields\");\n if (fields) fields.style.display = el.checked ? \"none\" : \"\";\n });\n })(noBudgetToggles[nb]);\n }\n\n var curateToggles = container.querySelectorAll(\"[data-wallet-curate-toggle]\");\n for (var c = 0; c < curateToggles.length; c++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n var id = btn.getAttribute(\"data-wallet-curate-toggle\");\n var exp = getWalletExpanded(id);\n exp.curate = !exp.curate;\n if (exp.curate) {\n var profile = findProfile(id);\n if (profile && !walletsState.providerModelsByEndpoint[profile.endpoint]) {\n loadTool(\"catalog_provider_models\", { endpoint: profile.endpoint, limit: 200 }).then(function (r) {\n walletsState.providerModelsByEndpoint[profile.endpoint] = r.ok ? (r.data.models || []) : [];\n renderWalletCards();\n });\n return;\n }\n }\n renderWalletCards();\n });\n })(curateToggles[c]);\n }\n\n var mmModes = container.querySelectorAll(\".mm-mode\");\n for (var mm = 0; mm < mmModes.length; mm++) {\n (function (el) {\n el.addEventListener(\"change\", function () {\n var id = el.getAttribute(\"data-mm-profile\");\n if (el.value === \"all\") {\n getWalletExpanded(id).curateMode = undefined;\n loadTool(\"auth_profile_set_models\", { id: id, mode: \"all\" }).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n replaceProfileInState(r.data.profile);\n renderWalletCards();\n });\n } else {\n // Reveal the checklist locally (a pending selection, independent\n // of what the server has stored); nothing is written until a\n // model is actually checked, so switching this radio alone can\n // never silently curate the wallet down to nothing.\n getWalletExpanded(id).curateMode = \"allow\";\n renderWalletCards();\n }\n });\n })(mmModes[mm]);\n }\n\n var mmIds = container.querySelectorAll(\".mm-id\");\n for (var mi = 0; mi < mmIds.length; mi++) {\n (function (el) {\n el.addEventListener(\"change\", function () {\n var id = el.getAttribute(\"data-mm-profile\");\n var list = container.querySelector('[data-mm-list=\"' + cssEscape(id) + '\"]');\n var checked = list.querySelectorAll(\".mm-id:checked\");\n var ids = [];\n for (var i2 = 0; i2 < checked.length; i2++) ids.push(checked[i2].value);\n loadTool(\"auth_profile_set_models\", { id: id, mode: \"allow\", ids: ids }).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n replaceProfileInState(r.data.profile);\n });\n });\n })(mmIds[mi]);\n }\n\n var mmRefresh = container.querySelectorAll(\"[data-mm-refresh]\");\n for (var mr = 0; mr < mmRefresh.length; mr++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n var id = btn.getAttribute(\"data-mm-refresh\");\n loadTool(\"auth_profile_refresh_models\", { id: id }).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n replaceProfileInState(r.data.profile);\n var added = (r.data.added || []).length;\n var removed = (r.data.removed || []).length;\n toast(\"Refreshed models (+\" + added + \" / -\" + removed + \").\", false);\n renderWalletCards();\n });\n });\n })(mmRefresh[mr]);\n }\n\n var deleteBtns = container.querySelectorAll(\"[data-wallet-delete]\");\n for (var d = 0; d < deleteBtns.length; d++) {\n (function (btn) {\n var id = btn.getAttribute(\"data-wallet-delete\");\n btn.addEventListener(\"click\", function () {\n confirmClick(\"wallet-delete-\" + id, btn, function () {\n var blockedBy = presetsReferencing(id);\n if (blockedBy.length > 0) {\n var names = blockedBy.map(function (p) { return p.name || p.id; }).join(\", \");\n toast(\"Cannot delete: still used by harness preset(s) \" + names + \".\", true);\n return;\n }\n loadTool(\"auth_profile_delete\", { id: id }).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n removeProfileFromState(id);\n toast(\"Wallet deleted.\", false);\n renderWalletCards();\n });\n });\n });\n })(deleteBtns[d]);\n }\n}\n\nfunction findProfile(id) {\n var pr = walletsState.profilesResult;\n if (!pr || !pr.ok) return null;\n var list = (pr.data && pr.data.profiles) || [];\n for (var i = 0; i < list.length; i++) if (list[i].id === id) return list[i];\n return null;\n}\n\n// ============================================================\n// Harnesses\n// ============================================================\n\nvar harnessesState = {\n adaptersResult: null,\n capsResult: null,\n presetsResult: null,\n rolesResult: null,\n profilesResult: null,\n expandedDefaults: {}, // slug -> bool\n defaultsCache: {}, // slug -> row[]\n showPresetForm: false,\n};\n\nfunction loadHarnesses() {\n return Promise.all([\n loadTool(\"adapter_list\", {}),\n loadTool(\"harness_capabilities\", {}),\n loadTool(\"harness_preset_list\", {}),\n loadTool(\"role_list\", {}),\n loadTool(\"auth_profile_list\", {}),\n ]).then(function (results) {\n harnessesState.adaptersResult = results[0];\n harnessesState.capsResult = results[1];\n harnessesState.presetsResult = results[2];\n harnessesState.rolesResult = results[3];\n harnessesState.profilesResult = results[4];\n renderHarnesses();\n });\n}\n\nfunction adapterDefaultsPaths(slug) {\n return [\n \"defaults.adapters.\" + slug + \".skills\",\n \"defaults.adapters.\" + slug + \".options\",\n \"defaults.adapters.\" + slug + \".contextContinuity\",\n \"defaults.adapters.\" + slug + \".auth.mode\",\n \"defaults.adapters.\" + slug + \".auth.source\",\n \"defaults.adapters.\" + slug + \".auth.provider\",\n \"defaults.adapters.\" + slug + \".auth.token\",\n \"defaults.adapters.\" + slug + \".auth.apiKey\",\n ];\n}\n\nfunction renderHarnesses() {\n var el = document.getElementById(\"sec-harnesses\");\n var adaptersR = harnessesState.adaptersResult;\n var capsR = harnessesState.capsResult;\n var presetsR = harnessesState.presetsResult;\n var rolesR = harnessesState.rolesResult;\n if (!adaptersR.ok) {\n el.innerHTML = '<div class=\"empty\">adapter_list: ' + escHtml(adaptersR.message) + '</div>';\n return;\n }\n var adapters = (adaptersR.data && adaptersR.data.adapters) || [];\n var capsBySlug = {};\n if (capsR.ok) {\n var caps = (capsR.data && capsR.data.capabilities) || [];\n for (var i = 0; i < caps.length; i++) capsBySlug[caps[i].adapter] = caps[i];\n }\n var presetsBySlug = {};\n var allPresets = [];\n if (presetsR.ok) {\n allPresets = (presetsR.data && presetsR.data.presets) || [];\n for (var j = 0; j < allPresets.length; j++) {\n var slug = allPresets[j].harnessSlug;\n if (!presetsBySlug[slug]) presetsBySlug[slug] = [];\n presetsBySlug[slug].push(allPresets[j]);\n }\n }\n\n var html = '<div class=\"sect-h\">Adapters</div>';\n if (!capsR.ok && capsR.unknown) html += '<div class=\"muted-note\">Capability detail needs a newer agentproto daemon.</div>';\n else if (!capsR.ok) html += '<div class=\"muted-note\">harness_capabilities: ' + escHtml(capsR.message) + '</div>';\n if (!presetsR.ok) html += '<div class=\"muted-note\">harness_preset_list: ' + escHtml(presetsR.message) + '</div>';\n\n html += '<div class=\"grid\">';\n if (adapters.length === 0) {\n html += '<div class=\"empty\">No adapters installed on this host.</div>';\n }\n for (var k = 0; k < adapters.length; k++) {\n var a = adapters[k];\n var cap = capsBySlug[a.slug];\n var slugPresets = presetsBySlug[a.slug] || [];\n var def = null;\n for (var d = 0; d < slugPresets.length; d++) if (slugPresets[d].isDefault) def = slugPresets[d];\n html += cardOpen(a.slug, a.name || a.slug, escHtml(a.slug));\n if (a.version) html += row(\"Version\", escHtml(a.version));\n if (a.protocol) html += row(\"Protocol\", escHtml(a.protocol));\n html += row(\"Models\", String((a.models || []).length));\n if (cap && Array.isArray(cap.providers) && cap.providers.length > 0) {\n var chips = \"\";\n for (var pi = 0; pi < cap.providers.length; pi++) {\n var prov = cap.providers[pi];\n chips += '<span class=\"tag ' + (prov.cred && prov.cred.present ? \"ok\" : \"\") + '\">' + escHtml(prov.billingEndpoint) + '</span> ';\n }\n html += '<dt>Billing</dt><dd>' + chips + '</dd>';\n }\n if (def) {\n html += row(\"Default preset\", escHtml(def.name) + ' → <a class=\"link\" data-goto=\"wallets/' + encodeURIComponent(def.profileRef) + '\">' + escHtml(def.profileRef) + '</a> / ' + escHtml(def.defaultModel));\n } else if (slugPresets.length > 0) {\n html += row(\"Presets\", String(slugPresets.length) + \" (no default)\");\n }\n html += '<dt></dt><dd><button type=\"button\" class=\"btn-sm\" data-adapter-defaults-toggle=\"' + escHtml(a.slug) + '\">' + (harnessesState.expandedDefaults[a.slug] ? \"hide defaults\" : \"spawn defaults\") + '</button></dd>';\n html += '<div id=\"adapter-defaults-' + escHtml(a.slug) + '\"></div>';\n html += cardClose();\n }\n html += '</div>';\n\n html += '<div class=\"sect-h\">Presets<button type=\"button\" class=\"btn-sm\" id=\"preset-add-toggle\">' + (harnessesState.showPresetForm ? \"close\" : \"+ new preset\") + '</button></div>';\n if (harnessesState.showPresetForm) html += presetFormHtml(adapters);\n html += presetsTableHtml(allPresets);\n\n html += '<div class=\"sect-h\">Roles</div>';\n if (rolesR.ok) {\n var roles = (rolesR.data && rolesR.data.roles) || [];\n if (roles.length === 0) {\n html += '<div class=\"empty\">No roles registered.</div>';\n } else {\n html += '<div class=\"card\"><div class=\"body\"><table><thead><tr><th>Role</th><th>Level</th><th>Delegation</th><th>Can spawn</th></tr></thead><tbody>';\n for (var r = 0; r < roles.length; r++) {\n var role = roles[r];\n html += '<tr><td>' + escHtml(role.name) + '</td><td>' + escHtml(String(role.level)) + '</td><td>' + escHtml(role.delegation) + '</td><td>' + escHtml((role.spawnable || []).join(\", \")) + '</td></tr>';\n }\n html += '</tbody></table></div></div>';\n }\n } else {\n html += '<div class=\"muted-note\">role_list: ' + escHtml(rolesR.message) + '</div>';\n }\n\n el.innerHTML = html;\n wireGotoLinks(el);\n wireAdapterDefaultsToggles(el);\n wirePresetForm(el, adapters);\n wirePresetsTable(el);\n\n for (var slug2 in harnessesState.expandedDefaults) {\n if (harnessesState.expandedDefaults[slug2] && harnessesState.defaultsCache[slug2]) {\n renderAdapterDefaultsCard(slug2);\n }\n }\n}\n\nfunction presetsTableHtml(presets) {\n if (presets.length === 0) return '<div class=\"empty\">No harness presets configured.</div>';\n var html = '<div class=\"card\"><div class=\"body\"><table><thead><tr><th>Id</th><th>Harness</th><th>Name</th><th>Wallet</th><th>Model</th><th>Default</th><th></th></tr></thead><tbody>';\n for (var i = 0; i < presets.length; i++) {\n var p = presets[i];\n html += '<tr data-row-id=\"' + escHtml(p.id) + '\"><td>' + escHtml(p.id) + '</td><td>' + escHtml(p.harnessSlug) + '</td><td>' + escHtml(p.name) + '</td>'\n + '<td><a class=\"link\" data-goto=\"wallets/' + encodeURIComponent(p.profileRef) + '\">' + escHtml(p.profileRef) + '</a>' + (p.profileDisabled ? ' <span class=\"tag bad\">disabled/missing</span>' : '') + '</td>'\n + '<td>' + escHtml(p.defaultModel) + '</td>'\n + '<td>' + (p.isDefault ? '<span class=\"tag ok\">default</span>' : '<button type=\"button\" class=\"btn-sm\" data-preset-set-default=\"' + escHtml(p.id) + '\" data-preset-harness=\"' + escHtml(p.harnessSlug) + '\">set default</button>') + '</td>'\n + '<td><button type=\"button\" class=\"btn-sm danger\" data-preset-delete=\"' + escHtml(p.id) + '\">delete</button></td></tr>';\n }\n html += '</tbody></table></div></div>';\n return html;\n}\n\nfunction presetFormHtml(adapters) {\n var profiles = (harnessesState.profilesResult && harnessesState.profilesResult.ok && harnessesState.profilesResult.data.profiles) || [];\n var html = '<div class=\"form-panel\">'\n + '<div class=\"form-row\">'\n + '<div class=\"field\"><label>Id</label><input type=\"text\" id=\"pf-id\"></div>'\n + '<div class=\"field\"><label>Harness</label><select id=\"pf-harness\">';\n for (var i = 0; i < adapters.length; i++) html += '<option value=\"' + escHtml(adapters[i].slug) + '\">' + escHtml(adapters[i].slug) + '</option>';\n html += '</select></div>'\n + '<div class=\"field\"><label>Name</label><input type=\"text\" id=\"pf-name\"></div>'\n + '</div><div class=\"form-row\">'\n + '<div class=\"field\"><label>Wallet</label><select id=\"pf-profile\">';\n for (var j = 0; j < profiles.length; j++) html += '<option value=\"' + escHtml(profiles[j].id) + '\">' + escHtml(profiles[j].label || profiles[j].id) + '</option>';\n html += '</select></div>'\n + '<div class=\"field\"><label>Default model</label><input type=\"text\" id=\"pf-model\" placeholder=\"vendor/model\"></div>'\n + '<label class=\"inline\"><input type=\"checkbox\" id=\"pf-default\"> make default for this harness</label>'\n + '<button type=\"button\" class=\"btn-sm primary\" id=\"pf-submit\">Create preset</button>'\n + '</div><div class=\"cfg-err\" id=\"pf-err\" style=\"display:none\"></div></div>';\n return html;\n}\n\nfunction wirePresetForm(container, adapters) {\n var toggle = container.querySelector(\"#preset-add-toggle\");\n if (toggle) toggle.addEventListener(\"click\", function () {\n harnessesState.showPresetForm = !harnessesState.showPresetForm;\n renderHarnesses();\n });\n var submit = container.querySelector(\"#pf-submit\");\n if (!submit) return;\n submit.addEventListener(\"click\", function () {\n var errEl = container.querySelector(\"#pf-err\");\n errEl.style.display = \"none\";\n var id = container.querySelector(\"#pf-id\").value.trim();\n var harnessSlug = container.querySelector(\"#pf-harness\").value;\n var name = container.querySelector(\"#pf-name\").value.trim();\n var profileRef = container.querySelector(\"#pf-profile\").value;\n var defaultModel = container.querySelector(\"#pf-model\").value.trim();\n var isDefault = container.querySelector(\"#pf-default\").checked;\n if (!id || !name || !defaultModel || !profileRef) {\n errEl.style.display = \"block\";\n errEl.textContent = \"Id, name, wallet, and default model are required.\";\n return;\n }\n loadTool(\"harness_preset_create\", { id: id, harnessSlug: harnessSlug, name: name, profileRef: profileRef, defaultModel: defaultModel, isDefault: isDefault }).then(function (r) {\n if (!r.ok) { errEl.style.display = \"block\"; errEl.textContent = r.message; return; }\n toast(\"Preset saved.\", false);\n harnessesState.showPresetForm = false;\n loadHarnesses();\n });\n });\n}\n\nfunction wirePresetsTable(container) {\n var setDefaults = container.querySelectorAll(\"[data-preset-set-default]\");\n for (var i = 0; i < setDefaults.length; i++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n var presetId = btn.getAttribute(\"data-preset-set-default\");\n var harnessSlug = btn.getAttribute(\"data-preset-harness\");\n loadTool(\"harness_preset_set_default\", { harnessSlug: harnessSlug, presetId: presetId }).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n toast(\"Default preset updated.\", false);\n loadHarnesses();\n });\n });\n })(setDefaults[i]);\n }\n var deletes = container.querySelectorAll(\"[data-preset-delete]\");\n for (var d = 0; d < deletes.length; d++) {\n (function (btn) {\n var presetId = btn.getAttribute(\"data-preset-delete\");\n btn.addEventListener(\"click\", function () {\n confirmClick(\"preset-delete-\" + presetId, btn, function () {\n loadTool(\"harness_preset_delete\", { id: presetId }).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n toast(\"Preset deleted.\", false);\n loadHarnesses();\n });\n });\n });\n })(deletes[d]);\n }\n}\n\nfunction wireAdapterDefaultsToggles(container) {\n var toggles = container.querySelectorAll(\"[data-adapter-defaults-toggle]\");\n for (var i = 0; i < toggles.length; i++) {\n (function (btn) {\n btn.addEventListener(\"click\", function () {\n var slug = btn.getAttribute(\"data-adapter-defaults-toggle\");\n harnessesState.expandedDefaults[slug] = !harnessesState.expandedDefaults[slug];\n if (harnessesState.expandedDefaults[slug] && !harnessesState.defaultsCache[slug]) {\n loadTool(\"config_get\", { keys: adapterDefaultsPaths(slug) }).then(function (r) {\n if (r.ok && r.data.revision) configRevision = r.data.revision;\n harnessesState.defaultsCache[slug] = r.ok ? (r.data.keys || []) : [];\n if (r.ok) notePendingRestart(r.data.keys);\n if (!r.ok && r.unknown) {\n var host = document.getElementById(\"adapter-defaults-\" + slug);\n if (host) host.innerHTML = '<div class=\"muted-note\">Per-harness defaults need a newer agentproto daemon.</div>';\n return;\n }\n if (!r.ok) {\n var host2 = document.getElementById(\"adapter-defaults-\" + slug);\n if (host2) host2.innerHTML = '<div class=\"muted-note\">config_get: ' + escHtml(r.message) + '</div>';\n return;\n }\n renderAdapterDefaultsCard(slug);\n });\n }\n btn.textContent = harnessesState.expandedDefaults[slug] ? \"hide defaults\" : \"spawn defaults\";\n var host3 = document.getElementById(\"adapter-defaults-\" + slug);\n if (host3 && !harnessesState.expandedDefaults[slug]) host3.innerHTML = \"\";\n else if (host3 && harnessesState.defaultsCache[slug]) renderAdapterDefaultsCard(slug);\n });\n })(toggles[i]);\n }\n}\n\nfunction renderAdapterDefaultsCard(slug) {\n var host = document.getElementById(\"adapter-defaults-\" + slug);\n if (!host) return;\n var rows = harnessesState.defaultsCache[slug] || [];\n var rowsByPath = {};\n for (var i = 0; i < rows.length; i++) rowsByPath[rows[i].path] = rows[i];\n host.innerHTML = '<dl>' + rows.map(configFieldHtml).join(\"\") + '</dl>';\n wireConfigRows(host, rowsByPath, function (path) {\n loadTool(\"config_get\", { keys: adapterDefaultsPaths(slug) }).then(function (r) {\n if (!r.ok) { toast(\"config_get: \" + r.message, true); return; }\n if (r.data.revision) configRevision = r.data.revision;\n harnessesState.defaultsCache[slug] = r.data.keys || [];\n notePendingRestart(r.data.keys);\n renderAdapterDefaultsCard(slug);\n });\n }, function () {\n loadHarnesses();\n });\n}\n\nfunction wireGotoLinks(container) {\n var links = container.querySelectorAll(\"a[data-goto]\");\n for (var i = 0; i < links.length; i++) {\n (function (a) {\n a.addEventListener(\"click\", function (e) {\n e.preventDefault();\n location.hash = \"#\" + a.getAttribute(\"data-goto\");\n });\n })(links[i]);\n }\n}\n\n// ============================================================\n// Models\n// ============================================================\n\nvar modelsState = { routes: [], runnableOnly: false, walletFilter: \"\", titlerRow: null };\n\nfunction loadModels() {\n return Promise.all([\n loadTool(\"catalog_models\", { full: true }),\n loadTool(\"auth_profile_list\", { full: true }),\n loadTool(\"config_get\", { keys: [\"titler.model\"] }),\n ]).then(function (results) {\n modelsState.routesResult = results[0];\n modelsState.profilesResult = results[1];\n modelsState.titlerResult = results[2];\n if (results[2].ok && results[2].data.revision) configRevision = results[2].data.revision;\n if (results[2].ok) notePendingRestart(results[2].data.keys);\n renderModels();\n });\n}\n\nfunction renderModels() {\n var el = document.getElementById(\"sec-models\");\n var rr = modelsState.routesResult;\n if (!rr.ok) {\n el.innerHTML = '<div class=\"empty\">catalog_models: ' + escHtml(rr.message) + '</div>';\n return;\n }\n var routes = (rr.data && rr.data.routes) || [];\n var profiles = (modelsState.profilesResult && modelsState.profilesResult.ok && modelsState.profilesResult.data && modelsState.profilesResult.data.profiles) || [];\n\n var html = '<div class=\"sect-h\">Titler model</div><div id=\"titler-model-card\"></div>';\n\n html += '<div class=\"sect-h\">Catalog<label class=\"inline\"><input type=\"checkbox\" id=\"runnable-only\"' + (modelsState.runnableOnly ? \" checked\" : \"\") + '> runnable only</label>'\n + '<label class=\"inline\">wallet <select id=\"wallet-filter\"><option value=\"\">any</option>';\n for (var i = 0; i < profiles.length; i++) {\n html += '<option value=\"' + escHtml(profiles[i].id) + '\"' + (profiles[i].id === modelsState.walletFilter ? \" selected\" : \"\") + '>' + escHtml(profiles[i].label || profiles[i].id) + '</option>';\n }\n html += '</select></label></div>';\n\n var filtered = routes.filter(function (r) {\n if (modelsState.runnableOnly && !r.runnable) return false;\n if (modelsState.walletFilter && (!r.eligibleProfiles || r.eligibleProfiles.indexOf(modelsState.walletFilter) === -1)) return false;\n return true;\n });\n\n var byVendor = {};\n var vendorOrder = [];\n for (var f = 0; f < filtered.length; f++) {\n var v = filtered[f].vendor;\n if (!byVendor[v]) { byVendor[v] = []; vendorOrder.push(v); }\n byVendor[v].push(filtered[f]);\n }\n vendorOrder.sort();\n\n if (vendorOrder.length === 0) {\n html += '<div class=\"empty\">No models match the current filters.</div>';\n }\n for (var vi = 0; vi < vendorOrder.length; vi++) {\n var vendor = vendorOrder[vi];\n var vendorRows = byVendor[vendor];\n var byProduct = {};\n var productOrder = [];\n for (var pr2 = 0; pr2 < vendorRows.length; pr2++) {\n var prod = vendorRows[pr2].product;\n if (!byProduct[prod]) { byProduct[prod] = []; productOrder.push(prod); }\n byProduct[prod].push(vendorRows[pr2]);\n }\n productOrder.sort();\n html += '<div class=\"sect-h\">' + escHtml(vendor) + '</div><div class=\"grid\">';\n for (var pi2 = 0; pi2 < productOrder.length; pi2++) {\n var product = productOrder[pi2];\n var productRoutes = byProduct[product];\n var modelRef = vendor + \"/\" + product;\n html += cardOpen(modelRef, product, \"\");\n for (var ri = 0; ri < productRoutes.length; ri++) {\n var route = productRoutes[ri];\n var priceStr = route.pricing ? \"$\" + route.pricing.inPer1M + \"/$\" + route.pricing.outPer1M + \" per 1M\" : \"\";\n var elig = (route.eligibleProfiles || []).map(function (id) {\n return '<a class=\"link\" data-goto=\"wallets/' + encodeURIComponent(id) + '\">' + escHtml(id) + '</a>';\n }).join(\" \");\n html += row(route.route, (route.runnable ? '<span class=\"tag ok\">runnable</span>' : '<span class=\"tag\">not runnable</span>') + (route.curated ? ' <span class=\"tag\">curated</span>' : \"\"));\n if (priceStr) html += row(\"Price\", escHtml(priceStr));\n if (route.contextWindow) html += row(\"Context\", escHtml(route.contextWindow) + (route.maxOutput ? \" / \" + escHtml(route.maxOutput) + \" out\" : \"\"));\n if (elig) html += '<dt>Wallets</dt><dd>' + elig + '</dd>';\n }\n html += cardClose();\n }\n html += '</div>';\n }\n\n html += '<div class=\"sect-h\">Wallet × model matrix</div>' + walletModelMatrixHtml(routes, profiles);\n\n el.innerHTML = html;\n wireGotoLinks(el);\n wireModelMatrix(el, routes, profiles);\n renderTitlerModelCard();\n\n var runnableEl = document.getElementById(\"runnable-only\");\n if (runnableEl) runnableEl.addEventListener(\"change\", function (e) { modelsState.runnableOnly = e.target.checked; renderModels(); });\n var walletEl = document.getElementById(\"wallet-filter\");\n if (walletEl) walletEl.addEventListener(\"change\", function (e) { modelsState.walletFilter = e.target.value; renderModels(); });\n}\n\nfunction walletModelMatrixHtml(routes, profiles) {\n if (profiles.length === 0) return '<div class=\"empty\">No wallets configured.</div>';\n var eligibleRoutes = routes.filter(function (r) { return r.eligibleProfiles && r.eligibleProfiles.length > 0; }).slice(0, 40);\n if (eligibleRoutes.length === 0) return '<div class=\"empty\">No wallet-reachable models yet.</div>';\n var html = '<div class=\"card\"><div class=\"body\" style=\"overflow-x:auto\"><table><thead><tr><th>Wallet</th>';\n for (var c = 0; c < eligibleRoutes.length; c++) html += '<th title=\"' + escHtml(eligibleRoutes[c].route) + '\">' + escHtml(eligibleRoutes[c].product) + '</th>';\n html += '</tr></thead><tbody>';\n for (var i = 0; i < profiles.length; i++) {\n var p = profiles[i];\n html += '<tr><td>' + escHtml(p.label || p.id) + '</td>';\n for (var j = 0; j < eligibleRoutes.length; j++) {\n var route = eligibleRoutes[j];\n var modelId = route.ref || (route.vendor + \"/\" + route.product);\n var reachable = route.eligibleProfiles.indexOf(p.id) !== -1;\n if (!reachable) {\n html += '<td class=\"matrix-cell disabled\">–</td>';\n continue;\n }\n var mode = (p.models && p.models.mode) || \"all\";\n var checked = mode === \"all\" || (p.models.ids || []).indexOf(modelId) !== -1;\n html += '<td class=\"matrix-cell\" data-matrix-profile=\"' + escHtml(p.id) + '\" data-matrix-model=\"' + escHtml(modelId) + '\" data-matrix-mode=\"' + escHtml(mode) + '\">' + (checked ? \"✓\" : \"–\") + '</td>';\n }\n html += '</tr>';\n }\n html += '</tbody></table></div></div><div class=\"muted-note\">Click a checked cell in an \"all eligible\" wallet to start curating it; click a cell to toggle curation once curating.</div>';\n return html;\n}\n\nfunction wireModelMatrix(container, routes, profiles) {\n var cells = container.querySelectorAll(\"[data-matrix-profile]\");\n for (var i = 0; i < cells.length; i++) {\n (function (cell) {\n cell.addEventListener(\"click\", function () {\n var id = cell.getAttribute(\"data-matrix-profile\");\n var modelId = cell.getAttribute(\"data-matrix-model\");\n var p = null;\n for (var k = 0; k < profiles.length; k++) if (profiles[k].id === id) p = profiles[k];\n if (!p) return;\n var mode = (p.models && p.models.mode) || \"all\";\n var currentIds = (p.models && p.models.ids) || [];\n var nextIds;\n if (mode === \"all\") {\n // Start curating from just this one model.\n nextIds = [modelId];\n } else if (currentIds.indexOf(modelId) !== -1) {\n nextIds = currentIds.filter(function (x) { return x !== modelId; });\n } else {\n nextIds = currentIds.concat([modelId]);\n }\n loadTool(\"auth_profile_set_models\", { id: id, mode: \"allow\", ids: nextIds }).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n for (var k2 = 0; k2 < profiles.length; k2++) if (profiles[k2].id === id) profiles[k2] = r.data.profile;\n if (modelsState.profilesResult && modelsState.profilesResult.ok) {\n for (var k3 = 0; k3 < modelsState.profilesResult.data.profiles.length; k3++) {\n if (modelsState.profilesResult.data.profiles[k3].id === id) modelsState.profilesResult.data.profiles[k3] = r.data.profile;\n }\n }\n renderModels();\n });\n });\n })(cells[i]);\n }\n}\n\nfunction renderTitlerModelCard() {\n var host = document.getElementById(\"titler-model-card\");\n if (!host) return;\n var tr = modelsState.titlerResult;\n if (!tr.ok && tr.unknown) {\n host.innerHTML = '<div class=\"muted-note\">Editing titler.model needs a newer agentproto daemon (config_get).</div>';\n return;\n }\n if (!tr.ok) {\n host.innerHTML = '<div class=\"muted-note\">config_get: ' + escHtml(tr.message) + '</div>';\n return;\n }\n var rows = tr.data.keys || [];\n var rowsByPath = {};\n for (var i = 0; i < rows.length; i++) rowsByPath[rows[i].path] = rows[i];\n host.innerHTML = '<div class=\"card\"><div class=\"body\"><dl>' + rows.map(configFieldHtml).join(\"\") + '</dl></div></div>';\n wireConfigRows(host, rowsByPath, function () {\n loadTool(\"config_get\", { keys: [\"titler.model\"] }).then(function (r) {\n if (!r.ok) { toast(\"config_get: \" + r.message, true); return; }\n if (r.data.revision) configRevision = r.data.revision;\n modelsState.titlerResult = r;\n notePendingRestart(r.data.keys);\n renderTitlerModelCard();\n });\n }, function () {\n loadModels();\n });\n}\n\n// ============================================================\n// Defaults & messaging\n// ============================================================\n\nvar DEFAULTS_GROUPS = [\n {\n id: \"messaging\", title: \"Messaging and prompts\",\n match: function (p) {\n return p.indexOf(\"defaults.messaging.\") === 0 || p === \"defaults.agentPromptInterrupt\"\n || p === \"defaults.backgroundTaskWake.enabled\" || p === \"defaults.backgroundTaskWake.graceMs\";\n },\n },\n {\n id: \"spawn\", title: \"Spawn\",\n match: function (p) {\n return p.indexOf(\"spawn.\") === 0 || p.indexOf(\"worktrees.\") === 0 || p.indexOf(\"defaults.spawn.\") === 0\n || p === \"defaults.skills\" || p === \"defaults.options\" || p === \"defaults.defaultRoleDepthCutoff\"\n || p === \"defaults.maxGrantableDelegation\" || p === \"provenance.wrapGh\" || p === \"agentsMd.inlineMaxKb\";\n },\n },\n { id: \"context-continuity\", title: \"Context continuity\", match: function (p) { return p === \"defaults.contextContinuity\"; } },\n {\n id: \"observability\", title: \"Observability\",\n match: function (p) {\n return p.indexOf(\"titler.\") === 0 || p === \"defaults.langfuseTracing\" || p === \"defaults.traceRedactor\"\n || p === \"sessions.attentionDelaySec\" || p === \"defaults.mcp.deferredTools\";\n },\n },\n {\n id: \"daemon\", title: \"Daemon\",\n match: function (p) {\n return p.indexOf(\"daemon.\") === 0 || p === \"sessions.eventsDir\" || p === \"features.pty\" || p === \"features.llmEndpoint\"\n || p === \"profiles\" || p === \"activeProfile\";\n },\n },\n];\n\nfunction classifyDefaultsGroup(path) {\n for (var i = 0; i < DEFAULTS_GROUPS.length; i++) if (DEFAULTS_GROUPS[i].match(path)) return DEFAULTS_GROUPS[i];\n return { id: \"other\", title: \"Other\" };\n}\n\nvar defaultsState = { rowsByPath: {}, healthFallback: null };\n\nfunction loadDefaults() {\n return Promise.all([\n loadTool(\"config_get\", { section: \"defaults\" }),\n loadTool(\"config_get\", { section: \"daemon\" }),\n loadTool(\"daemon_health\", {}),\n ]).then(function (results) {\n renderDefaults(results[0], results[1], results[2]);\n });\n}\n\nfunction renderDefaults(defR, daemonR, healthR) {\n var el = document.getElementById(\"sec-defaults\");\n\n if (!defR.ok && defR.unknown) {\n // Feature-detect fallback for a daemon that predates config_get: the\n // five effective knobs daemon_health already reports, read-only.\n var html = '<div class=\"notice\">Full defaults editor needs a newer agentproto daemon (config_get). Showing the knobs daemon_health reports.</div>';\n if (healthR.ok) {\n var hd = healthR.data;\n var restartBadge = ' <span class=\"tag\">restart</span>';\n html += '<div class=\"card\" data-card-id=\"daemon\"><div class=\"title\"><span>Daemon</span>'\n + (hd.version ? '<span class=\"aside\">' + escHtml(hd.version) + '</span>' : '') + '</div><div class=\"body\"><dl>'\n + row(\"Resume sessions on boot\", (hd.resumeSessionsOnBoot ? \"Yes\" : \"No\") + restartBadge)\n + row(\"Idle reap after\", (hd.idleReapAfterMs ? formatMs(hd.idleReapAfterMs) : \"off\") + restartBadge)\n + row(\"Crash detect interval\", (hd.crashDetectIntervalMs ? formatMs(hd.crashDetectIntervalMs) : \"off\") + restartBadge)\n + row(\"Restart sweep interval\", (hd.restartSweepIntervalMs ? formatMs(hd.restartSweepIntervalMs) : \"off\") + restartBadge)\n + row(\"Turn stall after\", (hd.turnStallAfterMs ? formatMs(hd.turnStallAfterMs) : \"off\") + restartBadge)\n + (hd.build && hd.build.sha ? row(\"Build\", escHtml(hd.build.sha)) : \"\")\n + '</dl></div></div>';\n } else {\n html += '<div class=\"muted-note\">daemon_health: ' + escHtml(healthR.message) + '</div>';\n }\n el.innerHTML = html;\n return;\n }\n if (!defR.ok) {\n el.innerHTML = '<div class=\"empty\">config_get: ' + escHtml(defR.message) + '</div>';\n return;\n }\n\n if (defR.data.revision) configRevision = defR.data.revision;\n var rows = (defR.data.keys || []).concat(daemonR.ok ? (daemonR.data.keys || []) : []);\n notePendingRestart(rows);\n defaultsState.rowsByPath = {};\n for (var i = 0; i < rows.length; i++) defaultsState.rowsByPath[rows[i].path] = rows[i];\n\n el.innerHTML = renderDefaultsGroupsHtml();\n wireConfigRows(el, defaultsState.rowsByPath, function (path) {\n refreshDefaultsGroupFor(path);\n }, function () {\n loadDefaults();\n });\n}\n\nfunction objectValues(obj) {\n var out = [];\n for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k)) out.push(obj[k]);\n return out;\n}\n\nfunction renderDefaultsGroupsHtml() {\n var byId = {};\n var order = [];\n var rows = objectValues(defaultsState.rowsByPath);\n for (var i = 0; i < rows.length; i++) {\n var g = classifyDefaultsGroup(rows[i].path);\n if (!byId[g.id]) { byId[g.id] = { id: g.id, title: g.title, rows: [] }; order.push(g.id); }\n byId[g.id].rows.push(rows[i]);\n }\n var html = \"\";\n for (var j = 0; j < order.length; j++) {\n var group = byId[order[j]];\n html += '<div class=\"sect-h\">' + escHtml(group.title) + '</div>' + buildConfigCardHtml(group.id, group.title, group.rows);\n }\n return html;\n}\n\nfunction refreshDefaultsGroupFor(path) {\n var group = classifyDefaultsGroup(path);\n var groupPaths = [];\n for (var k in defaultsState.rowsByPath) {\n if (Object.prototype.hasOwnProperty.call(defaultsState.rowsByPath, k) && classifyDefaultsGroup(k).id === group.id) groupPaths.push(k);\n }\n loadTool(\"config_get\", { keys: groupPaths }).then(function (r) {\n if (!r.ok) { toast(\"config_get: \" + r.message, true); return; }\n if (r.data.revision) configRevision = r.data.revision;\n var newRows = r.data.keys || [];\n for (var i = 0; i < newRows.length; i++) defaultsState.rowsByPath[newRows[i].path] = newRows[i];\n notePendingRestart(newRows);\n var el = document.getElementById(\"sec-defaults\");\n var cardEl = el.querySelector('[data-card-id=\"' + cssEscape(group.id) + '\"]');\n if (!cardEl) return;\n cardEl.outerHTML = buildConfigCardHtml(group.id, group.title, newRows);\n var freshEl = el.querySelector('[data-card-id=\"' + cssEscape(group.id) + '\"]');\n wireConfigRows(freshEl, defaultsState.rowsByPath, function (p2) { refreshDefaultsGroupFor(p2); }, function () { loadDefaults(); });\n });\n}\n\n// ============================================================\n// Remote & pairing\n// ============================================================\n\n// Held ONLY in memory, never in section state that survives a re-render\n// across sections, never in updateModelContext. Cleared on navigating away\n// from #remote (route()) and on host teardown (see boot, below).\nvar remoteReveal = null;\nvar pairReveal = null;\nvar pairCountdownTimer = null;\n\nfunction clearPairCountdown() {\n if (pairCountdownTimer) { clearInterval(pairCountdownTimer); pairCountdownTimer = null; }\n}\n\nfunction startPairCountdown(expiresAtIso) {\n clearPairCountdown();\n pairCountdownTimer = setInterval(function () {\n var el = document.getElementById(\"pair-offer-countdown\");\n if (!el) { clearPairCountdown(); return; }\n var ms = new Date(expiresAtIso).getTime() - Date.now();\n if (ms <= 0) { el.textContent = \"expired\"; clearPairCountdown(); return; }\n el.textContent = Math.ceil(ms / 1000) + \"s remaining\";\n }, 1000);\n}\n\nfunction loadRemote() {\n return Promise.all([\n loadTool(\"remote_status\", {}),\n loadTool(\"pair_list\", {}),\n loadTool(\"tunnel_list\", {}),\n ]).then(function (results) {\n renderRemote(results[0], results[1], results[2]);\n });\n}\n\nfunction renderRemote(remoteR, pairR, tunnelR) {\n var el = document.getElementById(\"sec-remote\");\n var html = '<div class=\"sect-h\">Remote gateway</div>';\n if (remoteR.ok) {\n var rs = remoteR.data;\n html += '<div class=\"card\" data-card-id=\"remote\"><div class=\"body\"><dl>'\n + row(\"Enabled\", rs.enabled ? \"Yes\" : \"No\")\n + (rs.provider ? row(\"Provider\", escHtml(rs.provider)) : \"\")\n + (rs.publicUrl ? row(\"Public URL\", escHtml(rs.publicUrl)) : \"\")\n + (rs.pid != null ? row(\"PID\", String(rs.pid)) : \"\")\n + (rs.createdAt ? row(\"Created\", fmtRelative(rs.createdAt)) : \"\")\n + (rs.lastError ? row(\"Last error\", escHtml(rs.lastError)) : \"\")\n + '</dl>'\n + '<div class=\"wallet-card-actions\">'\n + (rs.enabled\n ? '<button type=\"button\" class=\"btn-sm danger\" id=\"remote-disable-btn\">disable</button>'\n : '<button type=\"button\" class=\"btn-sm primary\" id=\"remote-enable-btn\">enable</button>')\n + '</div>';\n if (remoteReveal) {\n html += '<div class=\"reveal-box\">'\n + '<div>Shown once. Store it now, it will not be shown again.</div>'\n + (remoteReveal.bearerToken ? '<div>Bearer token<code>' + escHtml(remoteReveal.bearerToken) + '</code><button type=\"button\" class=\"btn-sm\" data-copy=\"bearer\">copy token</button></div>' : '')\n + (remoteReveal.phoneUrl ? '<div>Phone link<code>' + escHtml(remoteReveal.phoneUrl) + '</code><button type=\"button\" class=\"btn-sm\" data-copy=\"phoneUrl\">copy link</button></div>' : '')\n + (remoteReveal.mcpConfigSnippet ? '<div>.mcp.json<code>' + escHtml(remoteReveal.mcpConfigSnippet) + '</code><button type=\"button\" class=\"btn-sm\" data-copy=\"snippet\">copy snippet</button></div>' : '')\n + '</div>';\n }\n html += '</div></div>';\n } else {\n html += '<div class=\"muted-note\">remote_status: ' + escHtml(remoteR.message) + '</div>';\n }\n\n html += '<div class=\"sect-h\">Pairing<button type=\"button\" class=\"btn-sm\" id=\"pair-offer-btn\">+ new pairing</button></div>';\n if (pairReveal) {\n html += '<div class=\"reveal-box\">'\n + '<div>Offer URL. Shown once, expires <span id=\"pair-offer-countdown\"></span>.</div>'\n + '<code>' + escHtml(pairReveal.url) + '</code>'\n + '<button type=\"button\" class=\"btn-sm\" data-copy=\"pairOffer\">copy URL</button>'\n + '<div class=\"muted\">fingerprint ' + escHtml(pairReveal.fingerprint) + '</div>'\n + '</div>';\n }\n if (pairR.ok) {\n var pairings = (pairR.data && pairR.data.pairings) || [];\n html += '<div class=\"card\" data-card-id=\"pairing\"><div class=\"body\">';\n if (pairings.length === 0) {\n html += '<div class=\"empty\">No paired clients.</div>';\n } else {\n html += '<table><thead><tr><th>Name</th><th>Fingerprint</th><th>Created</th><th>Last seen</th><th>Rendezvous</th><th></th></tr></thead><tbody>';\n for (var i = 0; i < pairings.length; i++) {\n var p = pairings[i];\n html += '<tr data-row-id=\"' + escHtml(p.fingerprint) + '\"><td>' + escHtml(p.name) + '</td><td>' + escHtml(p.fingerprint) + '</td>'\n + '<td>' + escHtml(fmtRelative(p.createdAt)) + '</td><td>' + escHtml(fmtRelative(p.lastSeen)) + '</td><td>' + escHtml(p.rendezvous || \"\") + '</td>'\n + '<td><button type=\"button\" class=\"btn-sm danger\" data-pair-revoke=\"' + escHtml(p.fingerprint) + '\">revoke</button></td></tr>';\n }\n html += '</tbody></table>';\n }\n html += '</div></div>';\n } else {\n html += '<div class=\"muted-note\">pair_list: ' + escHtml(pairR.message) + '</div>';\n }\n\n html += '<div class=\"sect-h\">Tunnels</div>';\n if (tunnelR.ok) {\n var tunnels = (tunnelR.data && tunnelR.data.tunnels) || [];\n if (tunnels.length === 0) {\n html += '<div class=\"empty\">No tunnels tracked by this daemon.</div>';\n } else {\n html += '<div class=\"card\"><div class=\"body\"><table><thead><tr><th>Name</th><th>Provider</th><th>Port</th><th>URL</th><th>Status</th></tr></thead><tbody>';\n for (var t = 0; t < tunnels.length; t++) {\n var tn = tunnels[t];\n html += '<tr><td>' + escHtml(tn.label || tn.name) + '</td><td>' + escHtml(tn.provider) + '</td><td>' + escHtml(String(tn.targetPort)) + '</td>'\n + '<td>' + escHtml(tn.publicUrl || \"\") + '</td><td>' + escHtml(tn.status) + '</td></tr>';\n }\n html += '</tbody></table></div></div>';\n }\n } else {\n html += '<div class=\"muted-note\">tunnel_list: ' + escHtml(tunnelR.message) + '</div>';\n }\n\n el.innerHTML = html;\n wireRemote(el);\n if (remoteReveal) {\n wireCopyButtons(el, { bearer: remoteReveal.bearerToken, phoneUrl: remoteReveal.phoneUrl, snippet: remoteReveal.mcpConfigSnippet });\n }\n if (pairReveal) {\n wireCopyButtons(el, { pairOffer: pairReveal.url });\n startPairCountdown(pairReveal.expiresAt);\n }\n}\n\nfunction wireRemote(container) {\n var enableBtn = container.querySelector(\"#remote-enable-btn\");\n if (enableBtn) {\n enableBtn.addEventListener(\"click\", function () {\n confirmClick(\"remote-enable\", enableBtn, function () {\n loadTool(\"remote_enable\", {}).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n remoteReveal = { bearerToken: r.data.bearerToken, phoneUrl: r.data.phoneUrl, mcpConfigSnippet: r.data.mcpConfigSnippet };\n toast(\"Remote enabled.\", false);\n loadRemote();\n });\n });\n });\n }\n var disableBtn = container.querySelector(\"#remote-disable-btn\");\n if (disableBtn) {\n disableBtn.addEventListener(\"click\", function () {\n confirmClick(\"remote-disable\", disableBtn, function () {\n loadTool(\"remote_disable\", {}).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n remoteReveal = null;\n toast(\"Remote disabled.\", false);\n loadRemote();\n });\n });\n });\n }\n var offerBtn = container.querySelector(\"#pair-offer-btn\");\n if (offerBtn) {\n offerBtn.addEventListener(\"click\", function () {\n loadTool(\"pair_offer\", {}).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n pairReveal = { url: r.data.url, fingerprint: r.data.fingerprint, expiresAt: r.data.expiresAt };\n toast(\"Pairing offer created.\", false);\n loadRemote();\n });\n });\n }\n var revokeBtns = container.querySelectorAll(\"[data-pair-revoke]\");\n for (var i = 0; i < revokeBtns.length; i++) {\n (function (btn) {\n var fp = btn.getAttribute(\"data-pair-revoke\");\n btn.addEventListener(\"click\", function () {\n confirmClick(\"pair-revoke-\" + fp, btn, function () {\n loadTool(\"pair_revoke\", { target: fp }).then(function (r) {\n if (!r.ok) { toast(r.message, true); return; }\n toast(\"Pairing revoked.\", false);\n pairReveal = null;\n loadRemote();\n });\n });\n });\n })(revokeBtns[i]);\n }\n}\n\n// ============================================================\n// Advanced\n// ============================================================\n\nfunction loadAdvanced() {\n return Promise.all([\n loadTool(\"config_get\", {}),\n loadTool(\"daemon_health\", {}),\n ]).then(function (results) {\n renderAdvanced(results[0], results[1]);\n });\n}\n\nfunction renderAdvanced(configR, healthR) {\n var el = document.getElementById(\"sec-advanced\");\n var html = '<div class=\"sect-h\">Raw config dump</div>';\n if (configR.ok) {\n html += '<div class=\"card\"><div class=\"body\"><pre style=\"white-space:pre-wrap;font-size:11px;font-family:Menlo,Monaco,monospace\">' + escHtml(JSON.stringify(configR.data, null, 2)) + '</pre></div></div>';\n } else if (configR.unknown) {\n html += '<div class=\"notice\">Full config dump needs a newer agentproto daemon (config_get). See Defaults for the knobs daemon_health reports.</div>';\n } else {\n html += '<div class=\"muted-note\">config_get: ' + escHtml(configR.message) + '</div>';\n }\n html += '<div class=\"sect-h\">Daemon health</div>';\n if (healthR.ok) {\n html += '<div class=\"card\"><div class=\"body\"><pre style=\"white-space:pre-wrap;font-size:11px;font-family:Menlo,Monaco,monospace\">' + escHtml(JSON.stringify(healthR.data, null, 2)) + '</pre></div></div>';\n } else {\n html += '<div class=\"muted-note\">daemon_health: ' + escHtml(healthR.message) + '</div>';\n }\n el.innerHTML = html;\n}\n\n// ============================================================\n// boot\n// ============================================================\n\nfunction loadHealthChip() {\n return loadTool(\"daemon_health\", {}).then(function (r) {\n var chip = document.getElementById(\"daemon-chip\");\n if (r.ok) {\n chip.className = \"chip ok\";\n var v = r.data.version || (r.data.build && r.data.build.sha) || r.data.status || \"ok\";\n document.getElementById(\"daemon-txt\").textContent = \"daemon \" + String(v).slice(0, 16);\n } else {\n chip.className = \"chip bad\";\n document.getElementById(\"daemon-txt\").textContent = \"daemon unreachable\";\n }\n });\n}\n\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () {\n loadHealthChip();\n reloadCurrent().catch(function (e) { toast(String(e && e.message || e), true); });\n});\n\nwindow.McpApp.connect()\n .then(function (bridge) {\n callTool = bridge.callTool;\n updateModelContext = bridge.updateModelContext;\n if (bridge.onTeardown) {\n onTeardownFn = bridge.onTeardown;\n onTeardownFn(function () { clearRemoteSecrets(); });\n }\n renderNav();\n loadHealthChip();\n if (bridge.onToolInput) bridge.onToolInput(handleToolInput);\n route(true);\n })\n .catch(function (err) {\n document.getElementById(\"body\").innerHTML =\n '<div class=\"empty\">Standalone mode: no host bridge (' + escHtml(err.message) + ')</div>';\n });\n</script>\n</body>\n</html>`\n","/**\n * `@agentproto/config` — daemon-wide configuration as its own standalone app.\n *\n * A separate app from `ops-panel` (not a section of it): ops-panel is the\n * daemon operations cockpit (sessions, crons, housekeeping); this app is the\n * visual twin of `agentproto config` — wallets (auth profiles + spend),\n * harnesses, the model catalog, defaults/messaging knobs, remote/pairing,\n * and a raw config escape hatch. `ops-panel` is referenced only for its app\n * mechanics (`defineApp`, self-contained panel HTML, `app_tool_call` gated\n * by a `ui.tools` allowlist) — see `ui.ts`.\n *\n * `agents: []`: UI only, no durable sessions to launch. Edit flows live\n * behind explicit confirm/save actions in `ui.ts`; every mutating tool is\n * feature-detected the same way the read tools already are, so this app\n * works against a daemon that predates a given write tool (it just shows a\n * muted \"needs a newer agentproto daemon\" note for that one control).\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { CONFIG_HTML, CONFIG_TOOLS } from \"./ui.js\"\n\nexport const configApp: AppHandle = defineApp({\n id: \"@agentproto/config\",\n name: \"agentproto config\",\n version: \"0.1.0\",\n description:\n \"Daemon-wide configuration: wallets (auth profiles + spend), harnesses, \" +\n \"the model catalog, defaults & messaging knobs, remote & pairing, and a raw config dump.\",\n agents: [],\n ui: {\n html: CONFIG_HTML,\n title: \"agentproto config\",\n tools: [...CONFIG_TOOLS],\n },\n})\n\nexport { CONFIG_TOOLS } from \"./ui.js\"\nexport { parseConfigFragment, buildConfigFragment, CONFIG_SECTIONS } from \"./fragment.js\"\nexport type { ConfigSection, ParsedConfigFragment } from \"./fragment.js\"\n","import { defineAgent } from \"@agentproto/agent\"\nimport type { AgentEntry } from \"@agentproto/app-kit\"\n\nexport const narrator: AgentEntry = {\n agent: defineAgent({\n schema: \"agent/v1\",\n id: \"@agentproto/session-narrator\",\n description:\n \"Reads a daemon session's transcript and narrates it in plain English — what happened, what was decided, what's left.\",\n model: \"claude-sonnet-5\",\n boundaries: [\n \"Read-only — never spawn, kill, resume, or write to any session\",\n \"Narrate only what the transcript actually shows, never speculate about intent\",\n ],\n tools: [\"conversation_read\", \"session_list\"],\n workflows: [{ ref: \"narrate-session\" }],\n }),\n body: [\n \"You narrate a daemon session's conversation for a human who doesn't want to read the raw transcript.\",\n \"\",\n \"Given a session id or name, call `conversation_read` with `format: \\\"json\\\"` to get the\",\n \"structured turn list (`meta` + `messages`, each `{ role, text, toolCalls?, ts }`). If the\",\n \"session id is ambiguous or unknown, call `session_list` first to resolve it by label.\",\n \"\",\n \"Write a short plain-English narration:\",\n \"- What the user asked for.\",\n \"- What the assistant did, grouped into a few natural beats (not a line-by-line tool log).\",\n \"- Any open question, decision point, or unfinished step at the end of the transcript.\",\n \"\",\n \"Skip tool-call plumbing details unless they're the point (e.g. a failing command, a real\",\n \"decision). If `conversation.conversation` comes back null, say plainly that this session has\",\n \"no readable conversation (e.g. a bare shell PTY) instead of guessing.\",\n ].join(\"\\n\"),\n}\n","import { defineWorkflow } from \"@agentproto/workflow\"\n\nexport const narrateSession = defineWorkflow({\n id: \"narrate-session\",\n name: \"Narrate Session\",\n description: \"Read a daemon session's transcript and narrate it in plain English.\",\n version: \"0.1.0\",\n inputs: {},\n outputs: {},\n steps: [\n {\n id: \"narrate\",\n kind: \"agent\",\n agent: { ref: \"@agentproto/session-narrator\" },\n prompt:\n \"Narrate the session named in the run input (or the most recently started session if \" +\n \"none was given): what the user asked for, what the assistant did, and what — if \" +\n \"anything — is still open at the end of the transcript.\",\n },\n ],\n})\n","/**\n * Session Viewer — a self-contained MCP App panel for `session-viewer`.\n *\n * Talks to the daemon only through the `window.McpApp.connect()` bridge\n * (never a direct `fetch`): every action round-trips through the app's own\n * `app_tool_call` gateway tool, `{ appId, tool, args }` — same convention as\n * `media-viewer`/`mail-triage`. Read-only by design: `session_list` picks a\n * session (live or finished), `conversation_read` renders it as a clean\n * conversation — no spawn/kill/resume action lives here.\n *\n * Live sessions are polled (`conversation_read` has no push channel over\n * this bridge — see the PR's feasibility note on a real event/PTY stream);\n * a finished session is fetched once and left alone.\n */\n\nexport const SESSION_VIEWER_TOOLS = [\"session_list\", \"conversation_read\"] as const\n\nconst APP_ID = \"@agentproto/session-viewer\"\n\nexport const SESSION_VIEWER_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Session Viewer</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;--text3:#6e7681;\n --green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;\n}\nhtml,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}\n#app{display:flex;height:100%;overflow:hidden}\n\n/* sidebar */\n#sidebar{width:250px;min-width:190px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}\n#sidebar-hdr{padding:10px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px}\n#sidebar-hdr h1{font-size:11px;font-weight:700;color:var(--text2);text-transform:uppercase;letter-spacing:.06em;flex:1}\n#archived-toggle{display:flex;align-items:center;gap:5px;font-size:10.5px;color:var(--text2);padding:6px 12px;border-bottom:1px solid var(--border);cursor:pointer;user-select:none}\n#archived-toggle input{cursor:pointer}\n#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:14px;line-height:1;padding:2px 4px;border-radius:4px;font-family:inherit}\n#refresh-btn:hover{color:var(--text);background:var(--bg3)}\n#session-list{flex:1;overflow-y:auto;padding:4px 0}\n.si{padding:8px 12px;cursor:pointer;border-left:2px solid transparent}\n.si:hover{background:var(--bg3)}\n.si.active{background:var(--bg3);border-left-color:var(--blue)}\n.si .sn{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.si .sm{font-size:10.5px;color:var(--text2);margin-top:3px;display:flex;gap:5px;align-items:center;flex-wrap:wrap}\n.badge{display:inline-block;padding:1px 6px;border-radius:10px;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}\n.b-run{background:rgba(63,185,80,.15);color:var(--green)}\n.b-wait{background:rgba(88,166,255,.15);color:var(--blue)}\n.b-idle{background:rgba(139,148,158,.12);color:var(--text2)}\n.b-end{background:rgba(139,148,158,.1);color:var(--text3)}\n.b-err{background:rgba(248,81,73,.15);color:var(--red)}\n#empty-sidebar{padding:14px 12px;color:var(--text2);font-size:11.5px}\n\n/* main */\n#main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}\n#header{padding:12px 18px;border-bottom:1px solid var(--border);background:var(--bg2);flex-shrink:0}\n#header .htitle{font-size:14px;font-weight:700;display:flex;align-items:center;gap:8px}\n.pulse{width:7px;height:7px;border-radius:50%;background:var(--green);flex:none;box-shadow:0 0 0 0 rgba(63,185,80,.4);animation:pulse 2s infinite}\n@keyframes pulse{70%{box-shadow:0 0 0 6px rgba(63,185,80,0)}100%{box-shadow:0 0 0 0 rgba(63,185,80,0)}}\n#header .hfacts{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}\n.fact{font-size:10.5px;font-weight:600;background:var(--bg3);border:1px solid var(--border);color:var(--text2);padding:3px 9px;border-radius:999px}\n#empty-main,#loading-main,#error-main{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text2);gap:8px;text-align:center;padding:24px}\n#empty-main .ico,#loading-main .ico{font-size:30px}\n#error-main{color:var(--red)}\n\n/* transcript */\n#transcript{flex:1;overflow-y:auto;padding:14px 18px;display:flex;flex-direction:column;gap:10px}\n.msg{display:flex;flex-direction:column;max-width:78%}\n.msg.role-user{align-self:flex-end;align-items:flex-end}\n.msg.role-assistant,.msg.role-tool{align-self:flex-start;align-items:flex-start}\n.msg.role-system{align-self:center;max-width:90%}\n.msg.role-message{align-self:flex-start;align-items:flex-start}\n.bubble{border-radius:10px;padding:9px 12px;font-size:13px;line-height:1.55;white-space:pre-wrap;word-break:break-word}\n.role-user .bubble{background:var(--blue);color:#04101c;border-bottom-right-radius:3px}\n.role-assistant .bubble{background:var(--bg2);border:1px solid var(--border);border-bottom-left-radius:3px}\n.role-message .bubble{background:var(--bg2);border:1px dashed var(--blue);border-bottom-left-radius:3px}\n.role-system .bubble{background:transparent;border:1px dashed var(--border);color:var(--text2);font-size:11.5px;text-align:center}\n.mrow{display:flex;align-items:center;gap:6px;margin-bottom:3px}\n.mrole{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--text3)}\n.mtime{font-size:10px;color:var(--text3);font-variant-numeric:tabular-nums}\n\n.chip{margin-top:6px;border:1px solid var(--border);border-radius:8px;background:var(--bg3);overflow:hidden;cursor:pointer;width:100%}\n.chip .ch{padding:6px 10px;font-size:11.5px;display:flex;align-items:center;gap:7px;color:var(--text2)}\n.chip .ch .ci{color:var(--yellow);flex:none}\n.chip.tool-result .ch .ci{color:var(--purple)}\n.chip .ch .cname{font-weight:600;color:var(--text)}\n.chip .ch .cprev{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text3)}\n.chip .ch .cchev{flex:none;transition:transform .12s;color:var(--text3);font-size:10px}\n.chip.open .ch .cchev{transform:rotate(90deg)}\n.chip pre{display:none;margin:0;border-top:1px solid var(--border);padding:9px 11px;font-family:Menlo,Monaco,'Courier New',monospace;font-size:11px;line-height:1.5;color:var(--text2);white-space:pre-wrap;word-break:break-word;max-height:320px;overflow:auto}\n.chip.open pre{display:block}\n.reasoning{margin-bottom:6px;font-size:11.5px;font-style:italic;color:var(--text2);border-left:2px solid var(--border);padding-left:8px}\n\n#statusbar{padding:5px 18px;font-size:10.5px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0;display:flex;justify-content:space-between}\n</style>\n</head>\n<body>\n<div id=\"app\">\n <div id=\"sidebar\">\n <div id=\"sidebar-hdr\">\n <h1>Sessions</h1>\n <button id=\"refresh-btn\" title=\"Refresh\" type=\"button\">↻</button>\n </div>\n <label id=\"archived-toggle\"><input type=\"checkbox\" id=\"archived-check\"> show archived</label>\n <div id=\"session-list\"></div>\n </div>\n <div id=\"main\">\n <div id=\"header\" style=\"display:none\"></div>\n <div id=\"empty-main\"><div class=\"ico\">💬</div><div>Pick a session to read its conversation</div></div>\n <div id=\"transcript\" style=\"display:none\"></div>\n <div id=\"statusbar\" style=\"display:none\">\n <span id=\"status-left\"></span>\n <span id=\"status-right\"></span>\n </div>\n </div>\n</div>\n<script>\nvar callTool = null;\nvar APP_ID = ${JSON.stringify(APP_ID)};\n\n// app_tool_call wraps its dispatch result at least once (its own text-result\n// body is the JSON-stringified inner MCP tool result); peel back through\n// nested {content:[{type:\"text\",text:...}]} shells until the payload stops\n// looking like one — works whichever layer the host's bridge already\n// unwrapped. With conversation_read's format:\"json\" this resolves straight\n// to the { meta, messages } object; with session_list, straight to\n// { sessions: [...] }.\nfunction unwrapText(result) {\n var cur = result;\n for (var i = 0; i < 4; i++) {\n if (typeof cur === \"string\") {\n try { cur = JSON.parse(cur); continue; } catch (e) { return cur; }\n }\n if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === \"string\") {\n cur = cur.content[0].text;\n continue;\n }\n break;\n }\n return cur;\n}\n\nfunction callApp(tool, args) {\n return callTool(\"app_tool_call\", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);\n}\n\nfunction escHtml(s) {\n return String(s == null ? \"\" : s).replace(/&/g, \"&\").replace(/</g, \"<\").replace(/>/g, \">\").replace(/\"/g, \""\");\n}\n\nfunction fmtTime(ts) {\n if (!ts) return \"\";\n try { return new Date(ts).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\", second: \"2-digit\" }); }\n catch (e) { return \"\"; }\n}\n\nfunction fmtClock(iso) {\n if (!iso) return \"\";\n try { return new Date(iso).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\" }); }\n catch (e) { return \"\"; }\n}\n\nfunction fmtRelative(iso) {\n if (!iso) return \"\";\n var ms = Date.now() - new Date(iso).getTime();\n if (!isFinite(ms)) return \"\";\n var s = Math.round(ms / 1000);\n if (s < 5) return \"just now\";\n if (s < 60) return s + \"s ago\";\n var m = Math.round(s / 60);\n if (m < 60) return m + \"m ago\";\n var h = Math.round(m / 60);\n if (h < 24) return h + \"h ago\";\n return Math.round(h / 24) + \"d ago\";\n}\n\nfunction fmtDuration(startIso, endIso) {\n var end = endIso ? new Date(endIso).getTime() : Date.now();\n var start = new Date(startIso).getTime();\n if (!isFinite(start) || !isFinite(end)) return \"\";\n var s = Math.max(0, Math.round((end - start) / 1000));\n if (s < 60) return s + \"s\";\n var m = Math.floor(s / 60);\n if (m < 60) return m + \"m \" + (s % 60) + \"s\";\n var h = Math.floor(m / 60);\n return h + \"h \" + (m % 60) + \"m\";\n}\n\nfunction prettyArgs(raw) {\n if (raw == null) return \"\";\n if (typeof raw !== \"string\") return JSON.stringify(raw, null, 2);\n try { return JSON.stringify(JSON.parse(raw), null, 2); } catch (e) { return raw; }\n}\n\nfunction firstLine(s, max) {\n var t = String(s == null ? \"\" : s).split(\"\\\\n\")[0];\n return t.length > max ? t.slice(0, max) + \"\\\\u2026\" : t;\n}\n\n// ============================================================\n// state\n// ============================================================\n\nvar sessions = [];\nvar activeId = null;\nvar includeArchived = false;\nvar currentMeta = null;\nvar lastMessageCount = -1;\nvar sidebarTimer = null;\nvar convoTimer = null;\n\nfunction findSession(id) {\n for (var i = 0; i < sessions.length; i++) if (sessions[i].id === id) return sessions[i];\n return null;\n}\n\nfunction isLive(s) {\n return !!s && (s.status === \"running\" || s.status === \"starting\");\n}\n\n// ============================================================\n// sidebar\n// ============================================================\n\nfunction displayBadge(s) {\n if (s.status === \"running\") {\n if (s.busy) return { label: \"working\", cls: \"b-run\" };\n if (s.awaitingInput) return { label: \"waiting\", cls: \"b-wait\" };\n return { label: \"idle\", cls: \"b-idle\" };\n }\n if (s.status === \"starting\") return { label: \"starting\", cls: \"b-wait\" };\n if (s.status === \"error\" || s.status === \"killed\") return { label: s.status, cls: \"b-err\" };\n return { label: s.status || \"exited\", cls: \"b-end\" };\n}\n\nfunction renderSidebar() {\n var el = document.getElementById(\"session-list\");\n if (sessions.length === 0) {\n el.innerHTML = '<div id=\"empty-sidebar\">No sessions yet.</div>';\n return;\n }\n var html = \"\";\n for (var i = 0; i < sessions.length; i++) {\n var s = sessions[i];\n var label = s.label || s.title || s.name || (s.command ? String(s.command).split(\"/\").pop() : null) || s.id.slice(0, 10);\n var db = displayBadge(s);\n var active = s.id === activeId ? \" active\" : \"\";\n html += '<div class=\"si' + active + '\" data-id=\"' + escHtml(s.id) + '\">'\n + '<div class=\"sn\">' + escHtml(label) + \"</div>\"\n + '<div class=\"sm\">'\n + '<span class=\"badge ' + db.cls + '\">' + escHtml(db.label) + \"</span>\"\n + \"<span>\" + escHtml(s.kind || \"\") + \"</span>\"\n + \"<span>\" + escHtml(fmtRelative(s.startedAt)) + \"</span>\"\n + \"</div></div>\";\n }\n el.innerHTML = html;\n var rows = el.querySelectorAll(\".si\");\n for (var j = 0; j < rows.length; j++) {\n rows[j].addEventListener(\"click\", (function (id) { return function () { selectSession(id); }; })(rows[j].getAttribute(\"data-id\")));\n }\n}\n\nfunction loadSessions() {\n return callApp(\"session_list\", { kind: \"all\", includeArchived: includeArchived }).then(function (data) {\n sessions = (data && data.sessions) || [];\n renderSidebar();\n if (activeId) {\n var hdr = document.getElementById(\"header\");\n if (hdr.style.display !== \"none\") renderHeader();\n }\n }).catch(function (e) {\n document.getElementById(\"status-left\").textContent = \"sessions: \" + e.message;\n });\n}\n\n// ============================================================\n// header\n// ============================================================\n\nfunction renderHeader() {\n var s = findSession(activeId);\n var meta = currentMeta || {};\n var el = document.getElementById(\"header\");\n el.style.display = \"block\";\n\n var title = meta.title || (s && (s.label || s.title)) || activeId;\n var live = s && isLive(s);\n var pulse = live ? '<span class=\"pulse\"></span>' : \"\";\n\n var facts = [];\n if (meta.model || (s && s.model)) facts.push(escHtml(meta.model || (s && s.model)));\n if (s && s.adapterSlug) facts.push(escHtml(s.adapterSlug));\n if (s && s.kind) facts.push(escHtml(s.kind));\n if (s) { var db = displayBadge(s); facts.push(escHtml(db.label)); }\n if (s && s.cwd) facts.push('<span title=\"' + escHtml(s.cwd) + '\">' + escHtml(s.cwd.length > 46 ? \"\\\\u2026\" + s.cwd.slice(-44) : s.cwd) + \"</span>\");\n var started = (meta.startedAt || (s && s.startedAt));\n if (started) facts.push(\"started \" + escHtml(fmtClock(started)) + \" (\" + escHtml(fmtRelative(started)) + \")\");\n var ended = meta.endedAt || (s && s.endedAt);\n if (ended) facts.push(\"ran \" + escHtml(fmtDuration(started, ended)));\n else if (started) facts.push(\"running \" + escHtml(fmtDuration(started)));\n if (meta.messageCount != null) facts.push(meta.messageCount + \" messages\");\n if (meta.toolCallCount != null) facts.push(meta.toolCallCount + \" tool calls\");\n if (meta.tokens && (meta.tokens.input != null || meta.tokens.output != null)) {\n facts.push(\"tokens \" + (meta.tokens.input || 0) + \"\\\\u2192\" + (meta.tokens.output || 0));\n }\n if (meta.costUsd != null) facts.push(\"$\" + Number(meta.costUsd).toFixed(4));\n\n el.innerHTML = '<div class=\"htitle\">' + pulse + escHtml(title) + \"</div>\"\n + '<div class=\"hfacts\">' + facts.map(function (f) { return '<span class=\"fact\">' + f + \"</span>\"; }).join(\"\") + \"</div>\";\n}\n\n// ============================================================\n// transcript\n// ============================================================\n\nfunction renderToolCallChip(tc) {\n var args = prettyArgs(tc.args);\n return '<div class=\"chip tool-call\"><div class=\"ch\"><span class=\"ci\">▸</span>'\n + '<span class=\"cname\">' + escHtml(tc.name || \"tool\") + \"</span>\"\n + '<span class=\"cprev\">' + escHtml(firstLine(args, 64)) + \"</span>\"\n + '<span class=\"cchev\">▸</span></div><pre>' + escHtml(args) + \"</pre></div>\";\n}\n\nfunction renderToolResultChip(m) {\n var text = m.text || \"\";\n return '<div class=\"chip tool-result\"><div class=\"ch\"><span class=\"ci\">🔧</span>'\n + '<span class=\"cname\">' + escHtml(m.toolName || \"result\") + \"</span>\"\n + '<span class=\"cprev\">' + escHtml(firstLine(text, 64)) + \"</span>\"\n + '<span class=\"cchev\">▸</span></div><pre>' + escHtml(text) + \"</pre></div>\";\n}\n\nfunction renderMessage(m) {\n var time = m.ts ? '<span class=\"mtime\">' + fmtTime(m.ts) + \"</span>\" : \"\";\n if (m.role === \"system\") {\n return '<div class=\"msg role-system\"><div class=\"bubble\">' + escHtml(m.text || \"\") + \"</div></div>\";\n }\n if (m.role === \"tool\") {\n return '<div class=\"msg role-tool\">' + renderToolResultChip(m) + \"</div>\";\n }\n if (m.role === \"user\" && m.from) {\n // A typed message from another session (session-message record) — never\n // the human: name the daemon-attested sender instead of \"You\".\n var who = m.from.label || (m.from.sessionId ? m.from.sessionId.slice(-6) : m.from.relation);\n return '<div class=\"msg role-message\"><div class=\"mrow\"><span class=\"mrole\">from ' + escHtml(m.from.relation)\n + \" \" + escHtml(who) + \" · \" + escHtml(m.from.kind) + \"</span>\" + time + \"</div>\"\n + '<div class=\"bubble\">' + escHtml(m.text || \"\") + \"</div></div>\";\n }\n if (m.role === \"user\") {\n return '<div class=\"msg role-user\"><div class=\"mrow\"><span class=\"mrole\">You</span>' + time + \"</div>\"\n + '<div class=\"bubble\">' + escHtml(m.text || \"\") + \"</div></div>\";\n }\n // assistant\n var inner = \"\";\n if (m.reasoning) inner += '<div class=\"reasoning\">' + escHtml(m.reasoning) + \"</div>\";\n if (m.text) inner += escHtml(m.text);\n var body = inner ? '<div class=\"bubble\">' + inner + \"</div>\" : \"\";\n var chips = (m.toolCalls || []).map(renderToolCallChip).join(\"\");\n return '<div class=\"msg role-assistant\"><div class=\"mrow\"><span class=\"mrole\">Assistant</span>' + time + \"</div>\"\n + body + chips + \"</div>\";\n}\n\nfunction renderTranscript(messages) {\n var el = document.getElementById(\"transcript\");\n var atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;\n el.style.display = \"flex\";\n document.getElementById(\"empty-main\").style.display = \"none\";\n var errEl = document.getElementById(\"error-main\");\n if (errEl) errEl.style.display = \"none\";\n if (messages.length === 0) {\n el.innerHTML = '<div style=\"color:var(--text2);text-align:center;padding:20px\">(empty conversation)</div>';\n return;\n }\n el.innerHTML = messages.map(renderMessage).join(\"\");\n if (atBottom) el.scrollTop = el.scrollHeight;\n\n var chips = el.querySelectorAll(\".chip\");\n for (var i = 0; i < chips.length; i++) {\n chips[i].querySelector(\".ch\").addEventListener(\"click\", (function (chip) {\n return function () { chip.classList.toggle(\"open\"); };\n })(chips[i]));\n }\n}\n\nfunction renderNoConversation(reason) {\n document.getElementById(\"transcript\").style.display = \"none\";\n document.getElementById(\"empty-main\").style.display = \"none\";\n var el = document.getElementById(\"error-main\");\n el.style.display = \"flex\";\n el.innerHTML = '<div class=\"ico\">🚫</div><div>No readable conversation for this session'\n + (reason ? '<br><span style=\"font-size:11px;color:var(--text3)\">' + escHtml(reason) + \"</span>\" : \"\")\n + \"</div>\";\n}\n\n// ============================================================\n// selection + polling\n// ============================================================\n\nfunction clearConvoPoll() {\n if (convoTimer) { clearTimeout(convoTimer); convoTimer = null; }\n}\n\nfunction scheduleConvoPoll() {\n clearConvoPoll();\n var s = findSession(activeId);\n if (!isLive(s)) return;\n convoTimer = setTimeout(function () {\n loadConversation(activeId, { silent: true });\n }, 3500);\n}\n\nfunction loadConversation(id, opts) {\n opts = opts || {};\n var requestedId = id;\n if (!opts.silent) {\n document.getElementById(\"transcript\").style.display = \"none\";\n document.getElementById(\"empty-main\").style.display = \"none\";\n var errEl = document.getElementById(\"error-main\");\n errEl.style.display = \"flex\";\n errEl.innerHTML = '<div class=\"ico\">⌛</div><div>Loading\\\\u2026</div>';\n }\n document.getElementById(\"status-left\").textContent = \"loading\\\\u2026\";\n return callApp(\"conversation_read\", { idOrName: id, format: \"json\" }).then(function (data) {\n if (activeId !== requestedId) return; // switched away meanwhile\n if (!data || !Array.isArray(data.messages)) {\n currentMeta = null;\n renderHeader();\n renderNoConversation(data && data.reason);\n clearConvoPoll();\n document.getElementById(\"status-left\").textContent = \"no conversation\";\n return;\n }\n currentMeta = data.meta || {};\n lastMessageCount = data.messages.length;\n renderHeader();\n renderTranscript(data.messages);\n document.getElementById(\"status-left\").textContent = data.messages.length + \" messages\";\n document.getElementById(\"status-right\").textContent = \"updated \" + fmtTime(Date.now());\n scheduleConvoPoll();\n }).catch(function (e) {\n if (activeId !== requestedId) return;\n document.getElementById(\"status-left\").textContent = \"error: \" + e.message;\n if (!opts.silent) renderNoConversation(e.message);\n });\n}\n\nfunction selectSession(id) {\n if (activeId === id) return;\n activeId = id;\n currentMeta = null;\n lastMessageCount = -1;\n clearConvoPoll();\n renderSidebar();\n document.getElementById(\"statusbar\").style.display = \"flex\";\n renderHeader();\n loadConversation(id, {});\n}\n\n// ============================================================\n// boot\n// ============================================================\n\ndocument.getElementById(\"refresh-btn\").addEventListener(\"click\", function () {\n loadSessions();\n if (activeId) loadConversation(activeId, {});\n});\ndocument.getElementById(\"archived-check\").addEventListener(\"change\", function (e) {\n includeArchived = !!e.target.checked;\n loadSessions();\n});\n\nwindow.McpApp.connect()\n .then(function (bridge) {\n callTool = bridge.callTool;\n return loadSessions();\n })\n .then(function () {\n sidebarTimer = setInterval(loadSessions, 5000);\n if (!activeId && sessions.length > 0) selectSession(sessions[0].id);\n })\n .catch(function (err) {\n document.getElementById(\"empty-main\").innerHTML =\n '<div class=\"ico\">⚠</div><div>Standalone mode \\\\u2014 no host bridge (' + escHtml(err.message) + \")</div>\";\n });\n</script>\n</body>\n</html>`\n","/**\n * `session-viewer` — a single-agent agentproto app for reading daemon\n * sessions as a clean conversation.\n *\n * The panel (`ui.ts`) picks a session — live or finished — from\n * `session_list` and renders its transcript via `conversation_read`: user/\n * assistant turns, collapsible tool calls, timestamps, a metadata header.\n * Read-only, no spawn/kill/resume action. The bundled `session-narrator`\n * agent is a complementary plain-English narrator over the same\n * `conversation_read` data — usable standalone via `app_run`, independent\n * of the panel.\n */\n\nimport { defineApp, type AppHandle } from \"@agentproto/app-kit\"\nimport { narrator } from \"./agents/narrator.js\"\nimport { narrateSession } from \"./workflows/narrate-session.js\"\nimport { SESSION_VIEWER_HTML, SESSION_VIEWER_TOOLS } from \"./ui.js\"\n\nexport const sessionViewer: AppHandle = defineApp({\n id: \"@agentproto/session-viewer\",\n name: \"Session Viewer\",\n version: \"0.1.0\",\n description: \"Read a daemon session as a clean conversation — turns, tool calls, timestamps.\",\n agents: [narrator],\n workflows: [narrateSession],\n ui: {\n html: SESSION_VIEWER_HTML,\n title: \"Session Viewer\",\n tools: [...SESSION_VIEWER_TOOLS],\n },\n})\n\nexport { narrator, narrateSession }\n","#!/usr/bin/env node\n/**\n * `agentproto-apps-sync` — emit every bundled `@agentproto/apps` app to disk\n * and write a catalog of what landed where.\n *\n * Each app's `AGENT.md` / `WORKFLOW.md` / `APP.md` manifests are written\n * under `<baseDir>/<slug>` via `AppHandle.emit` (see `@agentproto/app-kit`'s\n * `emitApp`); the catalog is a flat index a host can read without walking\n * every app's directory (`app_install`'s own discovery path already reads\n * each `APP.md` individually — this is a cheaper \"what's on disk\" summary\n * for the sync bin's own log, not a replacement for that).\n */\n\nimport { mkdir, writeFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join, dirname } from \"node:path\"\nimport { parseArgs } from \"node:util\"\nimport type { AppHandle } from \"@agentproto/app-kit\"\nimport { codeTeam } from \"../code-team/index.js\"\nimport { contentTeam } from \"../content-team/index.js\"\nimport { mailTriage } from \"../mail-triage/index.js\"\nimport { mediaViewer } from \"../media-viewer/index.js\"\nimport { opsPanel } from \"../ops-panel/index.js\"\nimport { configApp } from \"../config/index.js\"\nimport { sessionViewer } from \"../session-viewer/index.js\"\n\ninterface CatalogEntry {\n readonly appId: string\n readonly name: string\n readonly description: string\n readonly dir: string\n readonly category: string\n}\n\nconst APPS: readonly { readonly slug: string; readonly category: string; readonly handle: AppHandle }[] = [\n { slug: \"code-team\", category: \"team\", handle: codeTeam },\n { slug: \"content-team\", category: \"team\", handle: contentTeam },\n { slug: \"mail-triage\", category: \"app\", handle: mailTriage },\n { slug: \"media-viewer\", category: \"app\", handle: mediaViewer },\n { slug: \"ops-panel\", category: \"app\", handle: opsPanel },\n { slug: \"config\", category: \"app\", handle: configApp },\n { slug: \"session-viewer\", category: \"app\", handle: sessionViewer },\n]\n\nexport async function syncApps(baseDir: string): Promise<CatalogEntry[]> {\n const catalog: CatalogEntry[] = []\n for (const { slug, category, handle } of APPS) {\n const dir = join(baseDir, slug)\n await handle.emit(dir)\n catalog.push({\n appId: handle.id ?? slug,\n name: handle.name ?? slug,\n description: handle.description ?? \"\",\n dir,\n category,\n })\n console.log(`emitted ${handle.id ?? slug} -> ${dir}`)\n }\n return catalog\n}\n\nexport async function writeCatalog(baseDir: string, catalog: readonly CatalogEntry[]): Promise<string> {\n const catalogPath = join(baseDir, \"..\", \"app-catalog.json\")\n await mkdir(dirname(catalogPath), { recursive: true })\n await writeFile(catalogPath, JSON.stringify({ apps: catalog }, null, 2) + \"\\n\", \"utf8\")\n return catalogPath\n}\n\nasync function main(argv: readonly string[]): Promise<void> {\n const { values } = parseArgs({\n args: [...argv],\n allowPositionals: false,\n strict: true,\n options: { \"base-dir\": { type: \"string\" } },\n })\n const baseDir = values[\"base-dir\"] ?? join(homedir(), \".agentproto\", \"apps\")\n\n await mkdir(baseDir, { recursive: true })\n const catalog = await syncApps(baseDir)\n const catalogPath = await writeCatalog(baseDir, catalog)\n console.log(`wrote catalog (${catalog.length} apps) -> ${catalogPath}`)\n}\n\nif (import.meta.url === `file://${process.argv[1]}`) {\n main(process.argv.slice(2)).catch(err => {\n console.error(err instanceof Error ? err.message : String(err))\n process.exitCode = 1\n })\n}\n"]}
|