agentix-cli 0.22.0 → 0.25.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.
Files changed (187) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +102 -103
  3. package/agentx.example.json +9 -3
  4. package/dist/App-6Y7B3YQH.js +8 -0
  5. package/dist/App-6Y7B3YQH.js.map +1 -0
  6. package/dist/ChatApp-KIEUJ2DS.js +7 -0
  7. package/dist/ChatApp-KIEUJ2DS.js.map +1 -0
  8. package/dist/{agent-Y4U4R3ZZ.js → agent-QGZIAZMY.js} +2 -2
  9. package/dist/architect-SEPWMWS4.js +2 -0
  10. package/dist/{board-dashboard-2CPBWFOB.js → board-dashboard-CQ5F4GPD.js} +3756 -1010
  11. package/dist/board-dashboard-CQ5F4GPD.js.map +1 -0
  12. package/dist/builtin-FRKAQMFS.js +2 -0
  13. package/dist/chunk-2WVX3LEC.js +2 -0
  14. package/dist/chunk-2WVX3LEC.js.map +1 -0
  15. package/dist/chunk-3WQFCIB6.js +24 -0
  16. package/dist/chunk-3WQFCIB6.js.map +1 -0
  17. package/dist/chunk-53GCFZPR.js +116 -0
  18. package/dist/chunk-53GCFZPR.js.map +1 -0
  19. package/dist/{chunk-IKBHHTIT.js → chunk-5U35WAZF.js} +2 -2
  20. package/dist/chunk-67Q4DCPS.js +54 -0
  21. package/dist/chunk-67Q4DCPS.js.map +1 -0
  22. package/dist/chunk-76Z4VHDX.js +8 -0
  23. package/dist/chunk-76Z4VHDX.js.map +1 -0
  24. package/dist/{chunk-7P5QRX5W.js → chunk-BFXONUUN.js} +2 -2
  25. package/dist/{chunk-P3TJHGO5.js → chunk-FDMWOPEL.js} +2 -2
  26. package/dist/chunk-FDWOECJY.js +54 -0
  27. package/dist/chunk-FDWOECJY.js.map +1 -0
  28. package/dist/chunk-G57RZCXM.js +5 -0
  29. package/dist/chunk-G57RZCXM.js.map +1 -0
  30. package/dist/{chunk-NOF4HSGK.js → chunk-H5D7ATXS.js} +2 -2
  31. package/dist/chunk-HA3XI7Z2.js +155 -0
  32. package/dist/chunk-HA3XI7Z2.js.map +1 -0
  33. package/dist/chunk-HR4JSRZS.js +3 -0
  34. package/dist/{chunk-A6BKSZQA.js.map → chunk-HR4JSRZS.js.map} +1 -1
  35. package/dist/chunk-I3SIXTJO.js +44 -0
  36. package/dist/chunk-I3SIXTJO.js.map +1 -0
  37. package/dist/chunk-ISEBLHWX.js +118 -0
  38. package/dist/chunk-ISEBLHWX.js.map +1 -0
  39. package/dist/{chunk-4ITQP3XL.js → chunk-JP5IEXR7.js} +2 -2
  40. package/dist/chunk-JXWAGMI2.js +13 -0
  41. package/dist/chunk-JXWAGMI2.js.map +1 -0
  42. package/dist/chunk-KUCCW2VG.js +431 -0
  43. package/dist/chunk-KUCCW2VG.js.map +1 -0
  44. package/dist/chunk-LDHPMJ7M.js +257 -0
  45. package/dist/chunk-LDHPMJ7M.js.map +1 -0
  46. package/dist/{chunk-WHP4I44J.js → chunk-LNXCJWOU.js} +29 -30
  47. package/dist/chunk-LNXCJWOU.js.map +1 -0
  48. package/dist/chunk-M4AMWP3H.js +178 -0
  49. package/dist/chunk-M4AMWP3H.js.map +1 -0
  50. package/dist/chunk-NGSIRE4X.js +126 -0
  51. package/dist/chunk-NGSIRE4X.js.map +1 -0
  52. package/dist/{chunk-CNNLREHD.js → chunk-P4VWIVKN.js} +268 -410
  53. package/dist/chunk-P4VWIVKN.js.map +1 -0
  54. package/dist/chunk-Q6YO7LYE.js +47 -0
  55. package/dist/chunk-Q6YO7LYE.js.map +1 -0
  56. package/dist/chunk-TMKLHTCG.js +971 -0
  57. package/dist/chunk-TMKLHTCG.js.map +1 -0
  58. package/dist/chunk-TRJTTBE2.js +7 -0
  59. package/dist/chunk-TRJTTBE2.js.map +1 -0
  60. package/dist/chunk-U2CKL755.js +4 -0
  61. package/dist/chunk-U2CKL755.js.map +1 -0
  62. package/dist/chunk-UCQ2N3KO.js +9 -0
  63. package/dist/chunk-UCQ2N3KO.js.map +1 -0
  64. package/dist/chunk-W4YR2RO2.js +2 -0
  65. package/dist/chunk-W4YR2RO2.js.map +1 -0
  66. package/dist/cli.js +1 -1
  67. package/dist/cli.js.map +1 -1
  68. package/dist/client-GGVVLOOF.js +2 -0
  69. package/dist/{compaction-BTUJJ3LJ.js → compaction-NWEMOSHA.js} +3 -3
  70. package/dist/config-7FSZFRTQ.js +2 -0
  71. package/dist/config-mutator-POCOYYJ4.js +2 -0
  72. package/dist/{context-planner-I7ID7XF3.js → context-planner-45GEWKGL.js} +2 -2
  73. package/dist/doctor-DHLBEKA4.js +2 -0
  74. package/dist/heal-HZ3EQD52.js +2 -0
  75. package/dist/index.d.ts +1244 -225
  76. package/dist/index.js +12 -12
  77. package/dist/index.js.map +1 -1
  78. package/dist/memory-extract-AENFVVO5.js +2 -0
  79. package/dist/processes-UIC4XXA6.js +2 -0
  80. package/dist/program-WVAVJFND.js +2 -0
  81. package/dist/projects-api-KRME2YKZ.js +2 -0
  82. package/dist/projects-api-KRME2YKZ.js.map +1 -0
  83. package/dist/projects-mutate-3NHVG437.js +7 -0
  84. package/dist/projects-mutate-3NHVG437.js.map +1 -0
  85. package/dist/providers-L27BJ5A6.js +2 -0
  86. package/dist/registry-T36ZIAWV.js +2 -0
  87. package/dist/rotation-memo-B3EXXSEH.js +13 -0
  88. package/dist/rotation-memo-B3EXXSEH.js.map +1 -0
  89. package/dist/scheduler-GH7TIKYS.js +2 -0
  90. package/dist/surface-usage-PRL6Z4U3.js +2 -0
  91. package/dist/wiki-M6YNRFC4.js +2 -0
  92. package/dist/workflows/templates/branching.yaml +75 -0
  93. package/dist/workflows/templates/extract.yaml +59 -0
  94. package/dist/workflows/templates/linear.yaml +37 -0
  95. package/dist/workflows/templates/retry.yaml +61 -0
  96. package/dist/workspace-setup-TBQAHYHJ.js +2 -0
  97. package/package.json +11 -3
  98. package/dist/board-dashboard-2CPBWFOB.js.map +0 -1
  99. package/dist/chunk-2HXSJYAJ.js +0 -15
  100. package/dist/chunk-2HXSJYAJ.js.map +0 -1
  101. package/dist/chunk-5F3J55LG.js +0 -2
  102. package/dist/chunk-5F3J55LG.js.map +0 -1
  103. package/dist/chunk-A6BKSZQA.js +0 -3
  104. package/dist/chunk-ABVZHJUO.js +0 -7
  105. package/dist/chunk-ABVZHJUO.js.map +0 -1
  106. package/dist/chunk-BHSPAP4U.js +0 -44
  107. package/dist/chunk-BHSPAP4U.js.map +0 -1
  108. package/dist/chunk-CDNWCYF7.js +0 -2
  109. package/dist/chunk-CDNWCYF7.js.map +0 -1
  110. package/dist/chunk-CNNLREHD.js.map +0 -1
  111. package/dist/chunk-CO4YCIIA.js +0 -13
  112. package/dist/chunk-CO4YCIIA.js.map +0 -1
  113. package/dist/chunk-EW27SCCC.js +0 -2
  114. package/dist/chunk-EW27SCCC.js.map +0 -1
  115. package/dist/chunk-GOLZ6NEL.js +0 -111
  116. package/dist/chunk-GOLZ6NEL.js.map +0 -1
  117. package/dist/chunk-JGXE2YNV.js +0 -46
  118. package/dist/chunk-JGXE2YNV.js.map +0 -1
  119. package/dist/chunk-KX626FN4.js +0 -6
  120. package/dist/chunk-KX626FN4.js.map +0 -1
  121. package/dist/chunk-MTRWXRFF.js +0 -380
  122. package/dist/chunk-MTRWXRFF.js.map +0 -1
  123. package/dist/chunk-N67JZAXU.js +0 -2
  124. package/dist/chunk-N67JZAXU.js.map +0 -1
  125. package/dist/chunk-OPKQNLGX.js +0 -450
  126. package/dist/chunk-OPKQNLGX.js.map +0 -1
  127. package/dist/chunk-OZOQKPJV.js +0 -102
  128. package/dist/chunk-OZOQKPJV.js.map +0 -1
  129. package/dist/chunk-VYLZ7UYM.js +0 -118
  130. package/dist/chunk-VYLZ7UYM.js.map +0 -1
  131. package/dist/chunk-WHP4I44J.js.map +0 -1
  132. package/dist/chunk-WJXU5F4Z.js +0 -53
  133. package/dist/chunk-WJXU5F4Z.js.map +0 -1
  134. package/dist/chunk-X5D5AV5N.js +0 -833
  135. package/dist/chunk-X5D5AV5N.js.map +0 -1
  136. package/dist/config-BLOKGMER.js +0 -2
  137. package/dist/config-mutator-RG7QY2EL.js +0 -2
  138. package/dist/doctor-ZTECTUHX.js +0 -2
  139. package/dist/editor-chat-RVLCKKB7.js +0 -137
  140. package/dist/editor-chat-RVLCKKB7.js.map +0 -1
  141. package/dist/heal-YEQYWX2L.js +0 -2
  142. package/dist/inbox-BIN2S5XN.js +0 -2
  143. package/dist/memory-extract-KRCHD3OP.js +0 -2
  144. package/dist/processes-RJ6RDP32.js +0 -2
  145. package/dist/program-GZOJIWTB.js +0 -2
  146. package/dist/providers-WVCMIW67.js +0 -2
  147. package/dist/registry-QZIUSQ5J.js +0 -2
  148. package/dist/rotation-memo-GMTZPMNE.js +0 -13
  149. package/dist/rotation-memo-GMTZPMNE.js.map +0 -1
  150. package/dist/scheduler-B2BQPV4M.js +0 -2
  151. package/dist/slack-6P7ZJ32Y.js +0 -3
  152. package/dist/slack-6P7ZJ32Y.js.map +0 -1
  153. package/dist/store-BAPDZHXE.js +0 -2
  154. package/dist/task-store-RHISEU6V.js +0 -2
  155. package/dist/telegram-PIYDRM46.js +0 -3
  156. package/dist/telegram-PIYDRM46.js.map +0 -1
  157. package/dist/types-ZRLHHCGB.js +0 -2
  158. package/dist/web/workflow-editor.global.js +0 -1452
  159. package/dist/web/workflow-editor.global.js.map +0 -1
  160. package/dist/whatsapp-PFRDMUMT.js +0 -3
  161. package/dist/whatsapp-PFRDMUMT.js.map +0 -1
  162. package/dist/wiki-53W5NLZV.js +0 -2
  163. package/dist/workspace-setup-DQCZX2NE.js +0 -2
  164. /package/dist/{agent-Y4U4R3ZZ.js.map → agent-QGZIAZMY.js.map} +0 -0
  165. /package/dist/{config-BLOKGMER.js.map → architect-SEPWMWS4.js.map} +0 -0
  166. /package/dist/{config-mutator-RG7QY2EL.js.map → builtin-FRKAQMFS.js.map} +0 -0
  167. /package/dist/{chunk-IKBHHTIT.js.map → chunk-5U35WAZF.js.map} +0 -0
  168. /package/dist/{chunk-7P5QRX5W.js.map → chunk-BFXONUUN.js.map} +0 -0
  169. /package/dist/{chunk-P3TJHGO5.js.map → chunk-FDMWOPEL.js.map} +0 -0
  170. /package/dist/{chunk-NOF4HSGK.js.map → chunk-H5D7ATXS.js.map} +0 -0
  171. /package/dist/{chunk-4ITQP3XL.js.map → chunk-JP5IEXR7.js.map} +0 -0
  172. /package/dist/{doctor-ZTECTUHX.js.map → client-GGVVLOOF.js.map} +0 -0
  173. /package/dist/{compaction-BTUJJ3LJ.js.map → compaction-NWEMOSHA.js.map} +0 -0
  174. /package/dist/{heal-YEQYWX2L.js.map → config-7FSZFRTQ.js.map} +0 -0
  175. /package/dist/{inbox-BIN2S5XN.js.map → config-mutator-POCOYYJ4.js.map} +0 -0
  176. /package/dist/{context-planner-I7ID7XF3.js.map → context-planner-45GEWKGL.js.map} +0 -0
  177. /package/dist/{memory-extract-KRCHD3OP.js.map → doctor-DHLBEKA4.js.map} +0 -0
  178. /package/dist/{processes-RJ6RDP32.js.map → heal-HZ3EQD52.js.map} +0 -0
  179. /package/dist/{program-GZOJIWTB.js.map → memory-extract-AENFVVO5.js.map} +0 -0
  180. /package/dist/{providers-WVCMIW67.js.map → processes-UIC4XXA6.js.map} +0 -0
  181. /package/dist/{registry-QZIUSQ5J.js.map → program-WVAVJFND.js.map} +0 -0
  182. /package/dist/{scheduler-B2BQPV4M.js.map → providers-L27BJ5A6.js.map} +0 -0
  183. /package/dist/{store-BAPDZHXE.js.map → registry-T36ZIAWV.js.map} +0 -0
  184. /package/dist/{task-store-RHISEU6V.js.map → scheduler-GH7TIKYS.js.map} +0 -0
  185. /package/dist/{types-ZRLHHCGB.js.map → surface-usage-PRL6Z4U3.js.map} +0 -0
  186. /package/dist/{wiki-53W5NLZV.js.map → wiki-M6YNRFC4.js.map} +0 -0
  187. /package/dist/{workspace-setup-DQCZX2NE.js.map → workspace-setup-TBQAHYHJ.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { Command } from \"commander\"\nimport chalk from \"chalk\"\nimport { execFileSync } from \"child_process\"\nimport { existsSync, readFileSync, readdirSync, statSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { loadDaemonConfig } from \"@/daemon/config\"\n\n// --- agentx doctor ---\n//\n// Pre-flight health check. Catches the common \"why isn't it working\" reasons\n// before the operator hits them at runtime:\n// - wrong Node version\n// - invalid / missing agentx.json\n// - agents with claude-code tier but no claude CLI installed\n// - ${FOO} references in agentx.json that have no matching env var\n// - agent workspace dirs that don't exist\n// - daemon reachable? (when --check-running, default true)\n//\n// Exit code: 0 if clean, 1 if any error, 0 if only warnings (so CI won't block\n// on advisory checks).\n\nexport type Severity = \"ok\" | \"warn\" | \"fail\"\n\nexport interface Check {\n severity: Severity\n group: string\n title: string\n detail?: string\n fix?: string\n}\n\n/** Run every preflight check and return the result as data. Exposed so\n * the dashboard's /admin/doctor route can render the same checks the\n * `agentx doctor` CLI prints — without spawning a child process. */\nexport async function runDoctorChecks(\n opts: { running?: boolean } = {},\n): Promise<{ checks: Check[]; summary: { errors: number; warnings: number; ok: number } }> {\n const checks: Check[] = []\n await runEnvChecks(checks)\n const cfg = runConfigChecks(checks)\n if (cfg) runReferenceChecks(checks, cfg)\n if (cfg) runWorkspaceChecks(checks, cfg)\n if (cfg) runWorkspaceSettingsChecks(checks, cfg)\n if (cfg) runRoutingChecks(checks, cfg)\n if (opts.running !== false && cfg) await runRuntimeChecks(checks, cfg)\n return { checks, summary: summarize(checks) }\n}\n\nexport const doctor = new Command()\n .name(\"doctor\")\n .description(\"pre-flight health check: Node, config, credentials, daemon\")\n .option(\"--no-running\", \"skip the live daemon probe\")\n .option(\"--json\", \"emit machine-readable JSON (stable shape for CI)\")\n .action(async (opts) => {\n const { checks, summary } = await runDoctorChecks({ running: opts.running !== false })\n if (opts.json) {\n process.stdout.write(JSON.stringify({ checks, summary }, null, 2) + \"\\n\")\n } else {\n printHumanReport(checks)\n }\n process.exit(summary.errors > 0 ? 1 : 0)\n })\n\n// ---------------------------------------------------------------------------\n// Checks\n// ---------------------------------------------------------------------------\n\nasync function runEnvChecks(checks: Check[]): Promise<void> {\n const nodeMajor = Number(process.versions.node.split(\".\")[0])\n checks.push({\n severity: nodeMajor >= 20 ? \"ok\" : \"fail\",\n group: \"Environment\",\n title: `Node.js ${process.versions.node}`,\n detail: nodeMajor >= 20 ? undefined : \"AgentX needs Node 20 or newer.\",\n fix: nodeMajor >= 20 ? undefined : \"Install a newer Node (nvm install 20 && nvm use 20)\",\n })\n\n const which = (bin: string): string | null => {\n try { return execFileSync(process.platform === \"win32\" ? \"where\" : \"which\", [bin], { encoding: \"utf-8\" }).trim().split(\"\\n\")[0] }\n catch { return null }\n }\n const npmPath = which(\"npm\")\n checks.push({\n severity: npmPath ? \"ok\" : \"fail\",\n group: \"Environment\",\n title: npmPath ? \"npm on PATH\" : \"npm not found\",\n detail: npmPath || undefined,\n })\n\n // Claude CLI is only required if the config references claude-code agents;\n // we report it advisory here and promote to fail in the config step below.\n const claudePath = which(\"claude\")\n if (claudePath) {\n let ver: string | undefined\n try { ver = execFileSync(\"claude\", [\"--version\"], { encoding: \"utf-8\", timeout: 3000 }).trim() } catch { /* */ }\n checks.push({ severity: \"ok\", group: \"Environment\", title: `claude CLI ${ver || \"installed\"}`, detail: claudePath })\n } else {\n checks.push({\n severity: \"warn\",\n group: \"Environment\",\n title: \"claude CLI not on PATH\",\n detail: \"Only required for agents on the claude-code tier.\",\n fix: \"See https://docs.anthropic.com/en/docs/claude-code\",\n })\n }\n}\n\nfunction runConfigChecks(checks: Check[]): any {\n const cfgPath = resolve(process.cwd(), \"agentx.json\")\n if (!existsSync(cfgPath)) {\n checks.push({\n severity: \"fail\",\n group: \"Config\",\n title: \"agentx.json not found\",\n detail: `Looked in ${cfgPath}`,\n fix: \"Run `agentx setup` to create one via the web wizard.\",\n })\n return null\n }\n let cfg: any\n try { cfg = JSON.parse(readFileSync(cfgPath, \"utf-8\")) }\n catch (e: any) {\n checks.push({ severity: \"fail\", group: \"Config\", title: \"agentx.json is not valid JSON\", detail: e.message })\n return null\n }\n try {\n // Full schema validation — catches missing required fields.\n loadDaemonConfig(cfgPath)\n checks.push({ severity: \"ok\", group: \"Config\", title: `agentx.json valid (${Object.keys(cfg.agents || {}).length} agents, ${Object.keys(cfg.crons || {}).length} schedules)` })\n } catch (e: any) {\n checks.push({\n severity: \"fail\",\n group: \"Config\",\n title: \"agentx.json failed schema validation\",\n detail: e.message,\n fix: \"Open /admin → Advanced tab to edit and re-save.\",\n })\n }\n const claudeCodeAgents = Object.entries(cfg.agents || {}).filter(([, a]: [string, any]) => a.tier === \"claude-code\")\n if (claudeCodeAgents.length > 0) {\n const hasClaude = (() => {\n try { execFileSync(process.platform === \"win32\" ? \"where\" : \"which\", [\"claude\"], { stdio: \"ignore\" }); return true } catch { return false }\n })()\n if (!hasClaude) {\n checks.push({\n severity: \"fail\",\n group: \"Config\",\n title: `${claudeCodeAgents.length} agent(s) use claude-code tier but claude CLI is missing`,\n detail: claudeCodeAgents.map(([id]) => id).join(\", \"),\n fix: \"Install Claude Code (https://docs.anthropic.com/en/docs/claude-code) or switch these agents to tier=sdk.\",\n })\n }\n }\n return cfg\n}\n\nfunction runReferenceChecks(checks: Check[], cfg: any): void {\n // Parse ${VAR} references out of the config (recursively) and verify each\n // one has a matching value in .env or process.env.\n const refs = new Set<string>()\n collectEnvRefs(cfg, refs)\n if (refs.size === 0) {\n checks.push({ severity: \"ok\", group: \"Secrets\", title: \"No ${VAR} references in agentx.json\" })\n return\n }\n const envFile = resolve(process.cwd(), \".env\")\n const envFromFile: Record<string, string> = {}\n if (existsSync(envFile)) {\n for (const line of readFileSync(envFile, \"utf-8\").split(/\\r?\\n/)) {\n const m = line.match(/^\\s*([A-Z_][A-Z0-9_]*)\\s*=\\s*(.*)$/i)\n if (m) envFromFile[m[1]] = m[2]\n }\n }\n const missing: string[] = []\n const empty: string[] = []\n for (const name of refs) {\n const raw = process.env[name] ?? envFromFile[name]\n if (raw === undefined) missing.push(name)\n else if (!raw.trim() || /^[\"']?\\s*[\"']?$/.test(raw)) empty.push(name)\n }\n if (missing.length === 0 && empty.length === 0) {\n checks.push({ severity: \"ok\", group: \"Secrets\", title: `${refs.size} env reference(s) resolved` })\n } else {\n if (missing.length > 0) {\n checks.push({\n severity: \"fail\",\n group: \"Secrets\",\n title: `${missing.length} env var(s) referenced in agentx.json but missing`,\n detail: missing.join(\", \"),\n fix: `Add to .env — e.g. ${missing[0]}=<value>`,\n })\n }\n if (empty.length > 0) {\n checks.push({\n severity: \"warn\",\n group: \"Secrets\",\n title: `${empty.length} env var(s) set but empty`,\n detail: empty.join(\", \"),\n })\n }\n }\n}\n\nfunction collectEnvRefs(node: unknown, into: Set<string>): void {\n if (typeof node === \"string\") {\n for (const m of node.matchAll(/\\$\\{([A-Z_][A-Z0-9_]*)\\}/g)) into.add(m[1])\n } else if (Array.isArray(node)) {\n for (const item of node) collectEnvRefs(item, into)\n } else if (node && typeof node === \"object\") {\n for (const v of Object.values(node)) collectEnvRefs(v, into)\n }\n}\n\nfunction runWorkspaceChecks(checks: Check[], cfg: any): void {\n const missing: string[] = []\n for (const [id, a] of Object.entries(cfg.agents || {}) as [string, any][]) {\n if (!a.workspace) continue\n const abs = a.workspace.startsWith(\"/\") ? a.workspace : resolve(process.cwd(), a.workspace)\n try {\n const st = statSync(abs)\n if (!st.isDirectory()) missing.push(`${id}: ${abs} (exists but not a directory)`)\n } catch { missing.push(`${id}: ${abs}`) }\n }\n if (missing.length === 0) {\n checks.push({ severity: \"ok\", group: \"Workspaces\", title: \"All agent workspace folders exist\" })\n } else {\n checks.push({\n severity: \"warn\",\n group: \"Workspaces\",\n title: `${missing.length} agent workspace(s) missing`,\n detail: missing.join(\"\\n \"),\n fix: \"The daemon will create missing folders on first task, but you won't see CLAUDE.md / skills until you populate them.\",\n })\n }\n}\n\n// Audit each agent's .claude/settings.json for known traps:\n// - unexpanded ${VAR} literals in env values (Claude Code does NOT expand them)\n// - env.PATH missing /usr/bin or /bin (basic utils unreachable)\n// - JSON syntax errors\n// - permissions.allow: [] explicitly empty alongside non-empty deny\n// Each finding is FAIL severity for the ${VAR} / PATH cases (these break the\n// agent's Bash tool entirely), WARN for the lockdown case.\nfunction runWorkspaceSettingsChecks(checks: Check[], cfg: any): void {\n const VAR_RE = /\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}/g\n const findings: Array<{ severity: Severity; agent: string; msg: string }> = []\n\n for (const [agentId, a] of Object.entries(cfg.agents || {}) as [string, any][]) {\n if (!a.workspace) continue\n const wsAbs = a.workspace.startsWith(\"/\") ? a.workspace : resolve(process.cwd(), a.workspace)\n for (const fname of [\"settings.json\", \"settings.local.json\"]) {\n const p = resolve(wsAbs, \".claude\", fname)\n if (!existsSync(p)) continue\n let data: any\n try {\n data = JSON.parse(readFileSync(p, \"utf-8\"))\n } catch (e: any) {\n findings.push({ severity: \"fail\", agent: agentId, msg: `${fname}: JSON parse error — ${e.message}` })\n continue\n }\n const env = (data.env || {}) as Record<string, unknown>\n for (const [k, v] of Object.entries(env)) {\n if (typeof v !== \"string\") continue\n const unexpanded = v.match(VAR_RE)\n if (unexpanded) {\n findings.push({\n severity: \"fail\",\n agent: agentId,\n msg: `${fname} env.${k} contains unexpanded ${[...new Set(unexpanded)].join(\",\")} — Claude Code does NOT expand \\${VAR} in settings.json (use a fully-literal value)`,\n })\n }\n }\n const pathVal = env.PATH\n if (typeof pathVal === \"string\") {\n const parts = pathVal.split(\":\").filter(Boolean)\n if (!parts.includes(\"/usr/bin\")) {\n findings.push({ severity: \"fail\", agent: agentId, msg: `${fname} env.PATH missing /usr/bin — cat/head/sh/which will fail` })\n }\n if (!parts.includes(\"/bin\")) {\n findings.push({ severity: \"fail\", agent: agentId, msg: `${fname} env.PATH missing /bin — ls/cp/mv will fail` })\n }\n }\n const perms = data.permissions\n if (perms && typeof perms === \"object\" && \"allow\" in perms) {\n const allow = perms.allow\n const deny = perms.deny\n if (Array.isArray(allow) && allow.length === 0 && Array.isArray(deny) && deny.length > 0) {\n findings.push({\n severity: \"warn\",\n agent: agentId,\n msg: `${fname} permissions.allow is explicitly [] with ${deny.length} deny rules — agent may be locked out (omit the key to use defaults instead)`,\n })\n }\n }\n }\n }\n\n if (findings.length === 0) {\n checks.push({ severity: \"ok\", group: \"Workspace settings\", title: `All ${Object.keys(cfg.agents || {}).length} agent .claude/settings.json files clean` })\n return\n }\n for (const f of findings) {\n checks.push({\n severity: f.severity,\n group: \"Workspace settings\",\n title: `${f.agent}: ${f.msg}`,\n fix: f.severity === \"fail\" ? \"Edit .claude/settings.json — replace ${VAR} with the literal value or omit the key to inherit.\" : \"Either remove the empty allow list or populate it.\",\n })\n }\n}\n\n// Surface routing / dispatch fragilities the recurring-patches plan\n// cataloged: webhook entries that route many event-types to a single\n// handler (github, gitlab), trigger maps pointing at workflow ids that\n// don't exist, mesh `node` fields naming peers that aren't configured,\n// gitlab agentMappings referencing unknown agents, and the very common\n// \"I edited agentx.json but didn't reload\" pitfall.\nfunction runRoutingChecks(checks: Check[], cfg: any): void {\n const webhooks = (cfg.webhooks ?? []) as Array<{\n id: string\n source: string\n agentId: string\n enabled: boolean\n node?: string\n secretEnv?: string\n triggers?: Record<string, string>\n defaultWorkflow?: string\n }>\n const findings: Array<{ severity: Severity; title: string; fix?: string }> = []\n\n // Build the set of known workflow ids. Workflows live as JSON files under\n // `workflows.dir` (default .agentx/workflows/). The id is the filename\n // sans extension; we don't need to parse the contents for this check.\n const workflowDir = resolve(process.cwd(), cfg.workflows?.dir || \".agentx/workflows\")\n const knownWorkflows = new Set<string>()\n try {\n if (existsSync(workflowDir)) {\n for (const f of readdirSync(workflowDir)) {\n if (f.endsWith(\".json\") && !f.startsWith(\"_\") && !f.endsWith(\".disabled.json\")) {\n knownWorkflows.add(f.replace(/\\.json$/, \"\"))\n }\n }\n }\n } catch { /* directory not yet populated; checks below tolerate empty set */ }\n\n // 1. Multi-event-type sources (github, gitlab) without a triggers map.\n // Without it, every inbound event hits the same agent and the\n // duplicate-firing class of bug returns. Surface as REVIEW (warn) —\n // operators may genuinely want a single-handler setup, but they\n // should opt in by setting `defaultWorkflow` explicitly.\n const multiEventSources = new Set([\"github\", \"gitlab\"])\n for (const w of webhooks) {\n if (!w.enabled) continue\n if (!multiEventSources.has(w.source)) continue\n const hasTriggers = w.triggers && Object.keys(w.triggers).length > 0\n if (!hasTriggers && !w.defaultWorkflow) {\n findings.push({\n severity: \"warn\",\n title: `webhook \"${w.id}\" (${w.source} -> ${w.agentId}) has no triggers map; every event-type collapses to the bound agent`,\n fix: `Add a triggers map: { \"issues.opened\": \"wf-id\", \"pull_request.synchronize\": \"wf-id\" } — or set defaultWorkflow.`,\n })\n }\n }\n\n // 2. Trigger map references workflow ids that don't exist on disk.\n // Hard-fails the dispatch at runtime (and silently — dispatcher just\n // logs \"workflow not found\" and bails). Surface here as FAIL.\n for (const w of webhooks) {\n if (!w.enabled || !w.triggers) continue\n for (const [eventType, workflowId] of Object.entries(w.triggers)) {\n if (knownWorkflows.size > 0 && !knownWorkflows.has(workflowId)) {\n findings.push({\n severity: \"fail\",\n title: `webhook \"${w.id}\".triggers[\"${eventType}\"] -> \"${workflowId}\" — no such workflow under ${cfg.workflows?.dir || \".agentx/workflows\"}`,\n fix: `Create the workflow file or fix the id in agentx.json`,\n })\n }\n }\n if (w.defaultWorkflow && knownWorkflows.size > 0 && !knownWorkflows.has(w.defaultWorkflow)) {\n findings.push({\n severity: \"fail\",\n title: `webhook \"${w.id}\".defaultWorkflow -> \"${w.defaultWorkflow}\" — no such workflow`,\n fix: `Create the workflow file or fix the id in agentx.json`,\n })\n }\n }\n\n // 3. Mesh-routed webhooks naming peers that don't exist.\n const peers = new Set<string>(((cfg.mesh?.peers ?? []) as Array<{ name: string }>).map(p => p.name))\n for (const w of webhooks) {\n if (!w.enabled || !w.node) continue\n if (!peers.has(w.node)) {\n findings.push({\n severity: \"fail\",\n title: `webhook \"${w.id}\" routes to mesh peer \"${w.node}\" which is not in mesh.peers`,\n fix: `Add the peer to mesh.peers or remove the node field on the webhook.`,\n })\n }\n }\n\n // 4. Two enabled webhook entries collide on (source, agentId): the\n // Phase 3 dispatcher takes the first match, the second is silently\n // ignored — operators rarely realize.\n const seen = new Map<string, string>()\n for (const w of webhooks) {\n if (!w.enabled) continue\n const key = `${w.source}:${w.agentId}`\n const prior = seen.get(key)\n if (prior) {\n findings.push({\n severity: \"warn\",\n title: `webhook entries \"${prior}\" and \"${w.id}\" both enabled for ${w.source} -> ${w.agentId}; only the first is dispatched`,\n fix: `Disable one entry, or merge their triggers maps into a single entry.`,\n })\n } else {\n seen.set(key, w.id)\n }\n }\n\n // 5. GitLab agentMappings referencing agent ids that aren't in agents.\n // A mapping with `node:` set is a cross-mesh route — the agent lives\n // on a remote peer, not locally — so missing-from-agents is correct\n // and not a finding. We still warn when `node` names a peer that\n // isn't configured (caught by check #3 above for webhooks; same\n // invariant should hold for agentMappings).\n const agentIds = new Set(Object.keys(cfg.agents ?? {}))\n for (const m of (cfg.channels?.gitlab?.agentMappings ?? [])) {\n if (!m?.agentId) continue\n if (m.node) {\n // Cross-mesh route — verify the named peer exists.\n if (!peers.has(m.node)) {\n findings.push({\n severity: \"fail\",\n title: `gitlab.agentMappings entry agentId=\"${m.agentId}\" routes to mesh peer \"${m.node}\" which is not in mesh.peers`,\n fix: `Add the peer to mesh.peers or drop the node field on the mapping.`,\n })\n }\n continue\n }\n if (!agentIds.has(m.agentId)) {\n findings.push({\n severity: \"warn\",\n title: `gitlab.agentMappings entry agentId=\"${m.agentId}\" — no such agent in agents.* (and no node field, so it's not a cross-mesh route)`,\n fix: `Add the agent to agents.*, set node:\"<peer>\" if the agent lives remotely, or remove the mapping.`,\n })\n }\n }\n\n // 6. Telegram accounts without an agentBinding silently drop every DM.\n for (const [acctId, acct] of Object.entries((cfg.channels?.telegram?.accounts ?? {}) as Record<string, { agentBinding?: string }>)) {\n if (!acct.agentBinding) {\n findings.push({\n severity: \"fail\",\n title: `telegram account \"${acctId}\" has no agentBinding; every DM to this bot will drop`,\n fix: `Set agentBinding to a valid agent id.`,\n })\n } else if (!agentIds.has(acct.agentBinding)) {\n findings.push({\n severity: \"fail\",\n title: `telegram account \"${acctId}\".agentBinding -> \"${acct.agentBinding}\" — no such agent in agents.*`,\n fix: `Fix the agentBinding or define the agent in agents.*.`,\n })\n }\n }\n\n // (Removed: the \"agentx.json newer than daemon pid\" check produced\n // false positives because the pid file is only rewritten on start,\n // not on hot-reload. The signal-to-noise was too low. A future check\n // can query /reload's `lastReload` timestamp via the daemon API\n // instead of using filesystem mtimes.)\n\n if (findings.length === 0) {\n checks.push({\n severity: \"ok\",\n group: \"Routing\",\n title: `${webhooks.length} webhook(s), ${Object.keys(cfg.channels?.telegram?.accounts ?? {}).length} telegram account(s) — all routing references resolve`,\n })\n return\n }\n for (const f of findings) {\n checks.push({ severity: f.severity, group: \"Routing\", title: f.title, fix: f.fix })\n }\n}\n\nasync function runRuntimeChecks(checks: Check[], cfg: any): Promise<void> {\n const url = cfg.dashboard?.daemonUrl?.replace(/\\/+$/, \"\") || \"http://127.0.0.1:18800\"\n const headers: Record<string, string> = {}\n if (cfg.dashboard?.token) headers[\"Authorization\"] = `Bearer ${cfg.dashboard.token}`\n try {\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), 2000)\n const r = await fetch(url + \"/health\", { headers, signal: ac.signal })\n clearTimeout(timer)\n if (!r.ok) {\n checks.push({ severity: \"warn\", group: \"Runtime\", title: `Daemon responded ${r.status}`, detail: url })\n return\n }\n const body: any = await r.json()\n checks.push({\n severity: \"ok\",\n group: \"Runtime\",\n title: `Daemon healthy — ${body.agents?.length ?? 0} agents live, uptime ${Math.round((body.uptime ?? 0) / 60)}m`,\n detail: url,\n })\n } catch {\n checks.push({\n severity: \"warn\",\n group: \"Runtime\",\n title: \"Daemon not reachable (not running?)\",\n detail: url,\n fix: \"Run `agentx daemon start`. Skip this check with `agentx doctor --no-running`.\",\n })\n }\n}\n\n// ---------------------------------------------------------------------------\n// Output\n// ---------------------------------------------------------------------------\n\nfunction summarize(checks: Check[]): { errors: number; warnings: number; ok: number } {\n return {\n errors: checks.filter((c) => c.severity === \"fail\").length,\n warnings: checks.filter((c) => c.severity === \"warn\").length,\n ok: checks.filter((c) => c.severity === \"ok\").length,\n }\n}\n\nfunction printHumanReport(checks: Check[]): void {\n console.log()\n console.log(chalk.bold(\" agentx doctor\"))\n console.log()\n\n const groups = new Map<string, Check[]>()\n for (const c of checks) {\n if (!groups.has(c.group)) groups.set(c.group, [])\n groups.get(c.group)!.push(c)\n }\n for (const [group, items] of groups) {\n console.log(chalk.bold(\" \" + group))\n for (const c of items) {\n const marker = c.severity === \"ok\" ? chalk.green(\"✓\")\n : c.severity === \"warn\" ? chalk.yellow(\"!\")\n : chalk.red(\"✗\")\n console.log(` ${marker} ${c.title}`)\n if (c.detail) console.log(chalk.dim(\" \" + c.detail.replace(/\\n/g, \"\\n \")))\n if (c.fix) console.log(chalk.dim(\" → \" + c.fix))\n }\n console.log()\n }\n\n const s = summarize(checks)\n if (s.errors > 0) {\n console.log(chalk.red.bold(` ${s.errors} error${s.errors === 1 ? \"\" : \"s\"}`) +\n (s.warnings ? chalk.yellow(`, ${s.warnings} warning${s.warnings === 1 ? \"\" : \"s\"}`) : \"\") +\n `. Fix the error${s.errors === 1 ? \"\" : \"s\"} and rerun.`)\n } else if (s.warnings > 0) {\n console.log(chalk.yellow.bold(` ${s.warnings} warning${s.warnings === 1 ? \"\" : \"s\"}.`) + chalk.dim(\" Review but not blocking.\"))\n } else {\n console.log(chalk.green.bold(` All checks passed (${s.ok}).`))\n }\n console.log()\n}\n"],"mappings":"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","names":["Command","chalk","execFileSync","existsSync","readFileSync","readdirSync","statSync","resolve","runDoctorChecks","opts","checks","runEnvChecks","cfg","runConfigChecks","runReferenceChecks","runWorkspaceChecks","runWorkspaceSettingsChecks","runRoutingChecks","runRuntimeChecks","summarize","doctor","Command","summary","printHumanReport","nodeMajor","which","bin","execFileSync","npmPath","claudePath","ver","cfgPath","resolve","existsSync","readFileSync","e","loadDaemonConfig","claudeCodeAgents","a","id","refs","collectEnvRefs","envFile","envFromFile","line","m","missing","empty","name","raw","node","into","item","v","abs","statSync","VAR_RE","findings","agentId","wsAbs","fname","p","data","env","k","unexpanded","pathVal","parts","perms","allow","deny","f","webhooks","workflowDir","knownWorkflows","readdirSync","multiEventSources","w","eventType","workflowId","peers","seen","key","prior","agentIds","acctId","acct","url","headers","ac","timer","r","body","c","chalk","groups","group","items","marker","s"]}
@@ -1,380 +0,0 @@
1
- import{a as o,b as t}from"./chunk-CNNLREHD.js";function s(e={}){let a=`<div class="ax-inbox__root" data-actor="${t(e.actor??"")}">
2
- <aside class="ax-inbox__list">
3
- <header>
4
- <h2>Inbox</h2>
5
- <button id="inbox-refresh" class="ax-inbox__icon" title="Refresh (r)">\u21BB</button>
6
- </header>
7
- <div class="ax-inbox__actor">
8
- <label>Actor <input id="inbox-actor" type="text" placeholder="actor:<id>" value="${t(e.actor??"")}" autocomplete="off" /></label>
9
- </div>
10
- <ul id="inbox-tasks" class="ax-inbox__cards" aria-live="polite"></ul>
11
- <div id="inbox-empty" class="ax-inbox__empty" hidden>
12
- <p>No open tasks.</p>
13
- <p class="hint">Set the actor filter to see tasks assigned to you, or leave blank for all tasks in the system.</p>
14
- </div>
15
- </aside>
16
-
17
- <section class="ax-inbox__detail">
18
- <header id="inbox-detail-head">
19
- <span class="hint">Select a task to open its form.</span>
20
- <button id="inbox-history-toggle" type="button" style="margin-left:auto;font-size:11px;padding:4px 10px;border:1px solid var(--ax-border);border-radius:4px;background:var(--ax-bg);color:var(--ax-fg);cursor:pointer">Show completed history</button>
21
- </header>
22
- <div id="inbox-detail-body" class="ax-inbox__detail-body"></div>
23
- <div id="inbox-history" class="ax-inbox__detail-body" style="display:none;padding:14px 16px">
24
- <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
25
- <h3 style="margin:0;font-family:'IBM Plex Sans',sans-serif;font-size:13px;font-weight:600">Completed user-tasks (most recent first)</h3>
26
- <span style="font-size:11px;color:var(--ax-muted)">SLA breaches highlighted in red</span>
27
- </div>
28
- <table style="width:100%;border-collapse:collapse;font-size:11px;font-family:'IBM Plex Mono',monospace">
29
- <thead><tr style="text-align:left;color:var(--ax-muted);text-transform:uppercase;letter-spacing:0.04em;font-size:10px"><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">submitted</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">workflow</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">title</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">by</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">duration</th><th style="padding:5px 7px;border-bottom:1px solid var(--ax-border)">sla</th></tr></thead>
30
- <tbody id="inbox-history-rows"></tbody>
31
- </table>
32
- <p id="inbox-history-empty" style="font-size:11px;color:var(--ax-muted);font-style:italic;margin:14px 0 0;display:none">no completed user-tasks yet</p>
33
- </div>
34
- </section>
35
- </div>
36
-
37
- <div id="inbox-toast" class="ax-inbox__toast" hidden></div>`;return o({title:"AgentX \xB7 Inbox",activeTab:"workflows",subtitle:"Inbox",peers:e.peers,currentPeerId:e.currentPeerId,body:a,css:r,scripts:`<script>${i}</script>`})}var r=`
38
- .ax-inbox__root {
39
- display: grid;
40
- grid-template-columns: 340px 1fr;
41
- height: calc(100vh - var(--ax-topbar-h, 48px));
42
- background: var(--ax-bg);
43
- }
44
- .ax-inbox__list {
45
- border-right: 1px solid var(--ax-border);
46
- display: flex;
47
- flex-direction: column;
48
- min-height: 0;
49
- }
50
- .ax-inbox__list > header {
51
- display: flex;
52
- justify-content: space-between;
53
- align-items: center;
54
- padding: 12px 14px;
55
- border-bottom: 1px solid var(--ax-border);
56
- }
57
- .ax-inbox__list > header h2 { margin: 0; font-size: 14px; font-weight: 600; }
58
- .ax-inbox__actor {
59
- padding: 10px 14px;
60
- border-bottom: 1px solid var(--ax-border);
61
- }
62
- .ax-inbox__actor label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ax-muted); }
63
- .ax-inbox__actor input {
64
- font: inherit;
65
- padding: 6px 8px;
66
- border: 1px solid var(--ax-border);
67
- border-radius: 6px;
68
- background: var(--ax-bg);
69
- color: var(--ax-fg);
70
- }
71
- .ax-inbox__cards { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1; }
72
- .ax-inbox__card {
73
- padding: 10px 12px;
74
- border-radius: 6px;
75
- margin-bottom: 4px;
76
- cursor: pointer;
77
- border: 1px solid transparent;
78
- }
79
- .ax-inbox__card:hover { background: var(--ax-surface-2, rgba(127,127,127,0.08)); }
80
- .ax-inbox__card.is-active { background: var(--ax-surface-2); border-color: var(--ax-border); }
81
- .ax-inbox__card-title { font-weight: 600; font-size: 13px; margin-bottom: 2px; }
82
- .ax-inbox__card-meta { font-size: 11px; color: var(--ax-muted); }
83
- .ax-inbox__card-due { font-size: 11px; color: #d48a00; margin-top: 4px; }
84
- .ax-inbox__empty { padding: 20px; color: var(--ax-muted); text-align: center; font-size: 13px; }
85
- .ax-inbox__detail { display: flex; flex-direction: column; min-height: 0; }
86
- .ax-inbox__detail > header { padding: 14px 18px; border-bottom: 1px solid var(--ax-border); }
87
- .ax-inbox__detail-body { overflow-y: auto; padding: 18px; flex: 1; }
88
- .ax-inbox__form { max-width: 560px; display: grid; gap: 14px; }
89
- .ax-inbox__field { display: flex; flex-direction: column; gap: 4px; }
90
- .ax-inbox__field label { font-size: 12px; color: var(--ax-muted); font-weight: 600; }
91
- .ax-inbox__field input, .ax-inbox__field textarea, .ax-inbox__field select {
92
- font: inherit;
93
- padding: 8px 10px;
94
- border: 1px solid var(--ax-border);
95
- border-radius: 6px;
96
- background: var(--ax-bg);
97
- color: var(--ax-fg);
98
- }
99
- .ax-inbox__field textarea { min-height: 100px; resize: vertical; }
100
- .ax-inbox__field-hint { font-size: 11px; color: var(--ax-muted); }
101
- .ax-inbox__field-error { font-size: 11px; color: #c0392b; }
102
- .ax-inbox__actions { display: flex; gap: 8px; margin-top: 8px; }
103
- .ax-inbox__actions button {
104
- font: inherit;
105
- padding: 8px 14px;
106
- border: 1px solid var(--ax-border);
107
- border-radius: 6px;
108
- cursor: pointer;
109
- background: var(--ax-surface-2);
110
- color: var(--ax-fg);
111
- }
112
- .ax-inbox__actions button.is-primary {
113
- background: #2ecc71; color: white; border-color: #27ae60;
114
- }
115
- .ax-inbox__actions button:disabled { opacity: 0.5; cursor: not-allowed; }
116
- .ax-inbox__icon {
117
- background: none; border: 1px solid transparent; color: var(--ax-fg); cursor: pointer;
118
- font-size: 14px; padding: 4px 8px; border-radius: 4px;
119
- }
120
- .ax-inbox__icon:hover { background: var(--ax-surface-2); }
121
- .ax-inbox__toast {
122
- position: fixed; bottom: 16px; right: 16px; padding: 10px 14px;
123
- background: var(--ax-fg); color: var(--ax-bg); border-radius: 6px;
124
- font-size: 13px; z-index: 100;
125
- }
126
- `,i=`
127
- (function(){
128
- const $ = (sel) => document.querySelector(sel);
129
- const state = { tasks: [], selected: null, actor: document.querySelector('.ax-inbox__root').dataset.actor || '' };
130
- const toast = (msg) => {
131
- const el = $('#inbox-toast');
132
- el.textContent = msg;
133
- el.hidden = false;
134
- setTimeout(() => { el.hidden = true; }, 2500);
135
- };
136
-
137
- async function fetchTasks() {
138
- const qs = state.actor ? ('?actor=' + encodeURIComponent(state.actor)) : '';
139
- try {
140
- const r = await fetch('/api/workflows/tasks' + qs, { credentials: 'same-origin' });
141
- if (!r.ok) { toast('fetch failed: ' + r.status); return; }
142
- const data = await r.json();
143
- state.tasks = Array.isArray(data.tasks) ? data.tasks : [];
144
- renderList();
145
- if (state.selected && !state.tasks.find(t => t.id === state.selected)) {
146
- state.selected = null;
147
- renderDetail();
148
- }
149
- } catch (e) { toast('fetch error: ' + e.message); }
150
- }
151
-
152
- function renderList() {
153
- const ul = $('#inbox-tasks');
154
- const empty = $('#inbox-empty');
155
- ul.innerHTML = '';
156
- if (!state.tasks.length) { empty.hidden = false; return; }
157
- empty.hidden = true;
158
- for (const t of state.tasks) {
159
- const li = document.createElement('li');
160
- li.className = 'ax-inbox__card' + (t.id === state.selected ? ' is-active' : '');
161
- li.innerHTML =
162
- '<div class="ax-inbox__card-title">' + escHtml(t.title) + '</div>' +
163
- '<div class="ax-inbox__card-meta">' + escHtml(t.assignee) + ' \xB7 ' + escHtml(t.workflowId) + '</div>' +
164
- (t.dueAt ? ('<div class="ax-inbox__card-due">due ' + new Date(t.dueAt).toLocaleString() + '</div>') : '');
165
- li.addEventListener('click', () => { state.selected = t.id; renderList(); renderDetail(); });
166
- ul.appendChild(li);
167
- }
168
- }
169
-
170
- function renderDetail() {
171
- const head = $('#inbox-detail-head');
172
- const body = $('#inbox-detail-body');
173
- const t = state.tasks.find(x => x.id === state.selected);
174
- if (!t) {
175
- head.innerHTML = '<span class="hint">Select a task to open its form.</span>';
176
- body.innerHTML = '';
177
- return;
178
- }
179
- head.innerHTML = '<h2 style="margin:0;font-size:15px">' + escHtml(t.title) + '</h2>' +
180
- (t.description ? '<p class="hint" style="margin:6px 0 0">' + escHtml(t.description) + '</p>' : '');
181
- const form = document.createElement('form');
182
- form.className = 'ax-inbox__form';
183
- form.addEventListener('submit', (e) => { e.preventDefault(); submit(t, 'primary'); });
184
- for (const field of (t.form.fields || [])) {
185
- form.appendChild(renderField(field));
186
- }
187
- const actions = document.createElement('div');
188
- actions.className = 'ax-inbox__actions';
189
- const primary = document.createElement('button');
190
- primary.type = 'submit';
191
- primary.className = 'is-primary';
192
- primary.textContent = t.form.submitLabel || 'Submit';
193
- actions.appendChild(primary);
194
- if (t.form.secondaryAction) {
195
- const secondary = document.createElement('button');
196
- secondary.type = 'button';
197
- secondary.textContent = t.form.secondaryAction.label;
198
- secondary.addEventListener('click', () => submit(t, 'secondary'));
199
- actions.appendChild(secondary);
200
- }
201
- form.appendChild(actions);
202
- body.innerHTML = '';
203
- body.appendChild(form);
204
- }
205
-
206
- function renderField(field) {
207
- const wrap = document.createElement('div');
208
- wrap.className = 'ax-inbox__field';
209
- const label = document.createElement('label');
210
- label.textContent = field.label + (field.required ? ' *' : '');
211
- wrap.appendChild(label);
212
- let input;
213
- switch (field.type) {
214
- case 'long-text':
215
- input = document.createElement('textarea');
216
- break;
217
- case 'number':
218
- input = document.createElement('input'); input.type = 'number';
219
- break;
220
- case 'boolean':
221
- input = document.createElement('select');
222
- for (const v of ['', 'yes', 'no']) {
223
- const o = document.createElement('option'); o.value = v; o.textContent = v || '\u2014';
224
- input.appendChild(o);
225
- }
226
- break;
227
- case 'date':
228
- input = document.createElement('input'); input.type = 'date';
229
- break;
230
- case 'select':
231
- input = document.createElement('select');
232
- if (!field.required) { const o = document.createElement('option'); o.value = ''; o.textContent = '\u2014'; input.appendChild(o); }
233
- for (const opt of (field.options || [])) {
234
- const o = document.createElement('option'); o.value = opt; o.textContent = opt;
235
- input.appendChild(o);
236
- }
237
- break;
238
- case 'multi-select':
239
- input = document.createElement('select'); input.multiple = true; input.size = Math.min(6, (field.options||[]).length || 3);
240
- for (const opt of (field.options || [])) {
241
- const o = document.createElement('option'); o.value = opt; o.textContent = opt;
242
- input.appendChild(o);
243
- }
244
- break;
245
- case 'file':
246
- input = document.createElement('input'); input.type = 'text'; input.placeholder = 'URL or path';
247
- break;
248
- default:
249
- input = document.createElement('input'); input.type = 'text';
250
- }
251
- input.name = field.key;
252
- if (field.defaultValue !== undefined && field.type !== 'multi-select') input.value = String(field.defaultValue);
253
- wrap.appendChild(input);
254
- if (field.hint) {
255
- const hint = document.createElement('div');
256
- hint.className = 'ax-inbox__field-hint';
257
- hint.textContent = field.hint;
258
- wrap.appendChild(hint);
259
- }
260
- const err = document.createElement('div');
261
- err.className = 'ax-inbox__field-error';
262
- err.dataset.for = field.key;
263
- wrap.appendChild(err);
264
- return wrap;
265
- }
266
-
267
- async function submit(task, action) {
268
- const form = $('.ax-inbox__form');
269
- if (!form) return;
270
- const values = {};
271
- for (const field of (task.form.fields || [])) {
272
- const el = form.querySelector('[name="' + field.key + '"]');
273
- if (!el) continue;
274
- if (field.type === 'multi-select') {
275
- values[field.key] = Array.from(el.selectedOptions).map(o => o.value);
276
- } else if (field.type === 'boolean') {
277
- values[field.key] = el.value;
278
- } else {
279
- values[field.key] = el.value;
280
- }
281
- }
282
- form.querySelectorAll('.ax-inbox__field-error').forEach(e => { e.textContent = ''; });
283
- try {
284
- const r = await fetch('/api/workflows/tasks/' + encodeURIComponent(task.id) + '/submit', {
285
- method: 'POST',
286
- credentials: 'same-origin',
287
- headers: { 'Content-Type': 'application/json' },
288
- body: JSON.stringify({ submittedBy: state.actor || 'anonymous', submission: { action, values } }),
289
- });
290
- const data = await r.json().catch(() => ({}));
291
- if (!r.ok) {
292
- if (data.fieldErrors && Array.isArray(data.fieldErrors)) {
293
- for (const fe of data.fieldErrors) {
294
- const node = form.querySelector('[data-for="' + fe.field + '"]');
295
- if (node) node.textContent = fe.message;
296
- }
297
- toast('form has errors');
298
- } else {
299
- toast(data.error || 'submit failed');
300
- }
301
- return;
302
- }
303
- toast('submitted');
304
- state.selected = null;
305
- fetchTasks();
306
- } catch (e) { toast('submit error: ' + e.message); }
307
- }
308
-
309
- function escHtml(s) {
310
- return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]);
311
- }
312
-
313
- $('#inbox-actor').addEventListener('change', (e) => {
314
- state.actor = e.target.value.trim();
315
- state.selected = null;
316
- const url = new URL(window.location.href);
317
- if (state.actor) url.searchParams.set('actor', state.actor); else url.searchParams.delete('actor');
318
- history.replaceState({}, '', url.toString());
319
- fetchTasks();
320
- });
321
- $('#inbox-refresh').addEventListener('click', fetchTasks);
322
-
323
- // History toggle \u2014 surfaces the SLA-aware completion log.
324
- let historyOpen = false;
325
- function fmtDuration(ms) {
326
- if (ms == null) return '\u2014';
327
- const s = Math.floor(ms / 1000);
328
- if (s < 60) return s + 's';
329
- const m = Math.floor(s / 60);
330
- if (m < 60) return m + 'm ' + (s % 60) + 's';
331
- const h = Math.floor(m / 60);
332
- return h + 'h ' + (m % 60) + 'm';
333
- }
334
- function fmtTs(s) {
335
- if (!s) return '\u2014';
336
- return s.replace('T', ' ').slice(0, 16);
337
- }
338
- async function loadHistory() {
339
- try {
340
- const r = await fetch('/api/workflows/tasks/history?limit=100');
341
- const data = await r.json();
342
- const rows = data.rows || [];
343
- const tbody = $('#inbox-history-rows');
344
- const empty = $('#inbox-history-empty');
345
- if (rows.length === 0) { empty.style.display = ''; tbody.innerHTML = ''; return; }
346
- empty.style.display = 'none';
347
- tbody.innerHTML = rows.map(function(r){
348
- const slaCell = r.dueAt
349
- ? (r.breachedSla
350
- ? '<span style="color:var(--ax-err,#e74c3c)">breached</span>'
351
- : '<span style="color:#2ecc71">on time</span>')
352
- : '<span style="color:var(--ax-muted)">\u2014</span>';
353
- const rowStyle = r.breachedSla ? 'background:rgba(231,76,60,0.06)' : '';
354
- return '<tr style="' + rowStyle + '">' +
355
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(fmtTs(r.submittedAt)) + '</td>' +
356
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(r.workflowId || '') + '</td>' +
357
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml((r.title || '').slice(0, 60)) + '</td>' +
358
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(r.submittedBy || '') + '</td>' +
359
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + escHtml(fmtDuration(r.durationMs)) + '</td>' +
360
- '<td style="padding:4px 7px;border-bottom:1px solid var(--ax-border)">' + slaCell + '</td>' +
361
- '</tr>';
362
- }).join('');
363
- } catch (e) {
364
- $('#inbox-history-empty').textContent = 'failed to load: ' + e.message;
365
- $('#inbox-history-empty').style.display = '';
366
- }
367
- }
368
- $('#inbox-history-toggle').addEventListener('click', () => {
369
- historyOpen = !historyOpen;
370
- $('#inbox-history').style.display = historyOpen ? 'block' : 'none';
371
- $('#inbox-detail-body').style.display = historyOpen ? 'none' : '';
372
- $('#inbox-history-toggle').textContent = historyOpen ? 'Show open inbox' : 'Show completed history';
373
- if (historyOpen) loadHistory();
374
- });
375
-
376
- fetchTasks();
377
- setInterval(fetchTasks, 10000);
378
- })();
379
- `;export{s as a};
380
- //# sourceMappingURL=chunk-MTRWXRFF.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/daemon/ui/pages/inbox.ts"],"sourcesContent":["import { renderShell, esc, type TopbarPeer } from \"..\"\n\n// --- Inbox page ---\n//\n// Per-actor (or global) list of open user-tasks with an inline form\n// renderer. Reads /api/workflows/tasks; submits via\n// POST /api/workflows/tasks/:id/submit.\n//\n// Scope is intentionally tight for Phase 1:\n// - text / long-text / number / boolean / select / multi-select / date\n// - primary + optional secondary action buttons\n// - required-field validation is server-side; the UI just relays errors\n//\n// No channel-adapter coupling here — the form UI is the same whether the\n// renderer ran on Telegram or not.\n\nexport interface InboxPageOpts {\n peers?: TopbarPeer[]\n currentPeerId?: string\n /** Optional actor id to pre-filter. When unset, the page shows a prompt\n * asking the user to pick their actor id. */\n actor?: string\n}\n\nexport function renderInboxPage(opts: InboxPageOpts = {}): string {\n const body = `<div class=\"ax-inbox__root\" data-actor=\"${esc(opts.actor ?? \"\")}\">\n <aside class=\"ax-inbox__list\">\n <header>\n <h2>Inbox</h2>\n <button id=\"inbox-refresh\" class=\"ax-inbox__icon\" title=\"Refresh (r)\">↻</button>\n </header>\n <div class=\"ax-inbox__actor\">\n <label>Actor <input id=\"inbox-actor\" type=\"text\" placeholder=\"actor:<id>\" value=\"${esc(opts.actor ?? \"\")}\" autocomplete=\"off\" /></label>\n </div>\n <ul id=\"inbox-tasks\" class=\"ax-inbox__cards\" aria-live=\"polite\"></ul>\n <div id=\"inbox-empty\" class=\"ax-inbox__empty\" hidden>\n <p>No open tasks.</p>\n <p class=\"hint\">Set the actor filter to see tasks assigned to you, or leave blank for all tasks in the system.</p>\n </div>\n </aside>\n\n <section class=\"ax-inbox__detail\">\n <header id=\"inbox-detail-head\">\n <span class=\"hint\">Select a task to open its form.</span>\n <button id=\"inbox-history-toggle\" type=\"button\" style=\"margin-left:auto;font-size:11px;padding:4px 10px;border:1px solid var(--ax-border);border-radius:4px;background:var(--ax-bg);color:var(--ax-fg);cursor:pointer\">Show completed history</button>\n </header>\n <div id=\"inbox-detail-body\" class=\"ax-inbox__detail-body\"></div>\n <div id=\"inbox-history\" class=\"ax-inbox__detail-body\" style=\"display:none;padding:14px 16px\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:10px\">\n <h3 style=\"margin:0;font-family:'IBM Plex Sans',sans-serif;font-size:13px;font-weight:600\">Completed user-tasks (most recent first)</h3>\n <span style=\"font-size:11px;color:var(--ax-muted)\">SLA breaches highlighted in red</span>\n </div>\n <table style=\"width:100%;border-collapse:collapse;font-size:11px;font-family:'IBM Plex Mono',monospace\">\n <thead><tr style=\"text-align:left;color:var(--ax-muted);text-transform:uppercase;letter-spacing:0.04em;font-size:10px\"><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">submitted</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">workflow</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">title</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">by</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">duration</th><th style=\"padding:5px 7px;border-bottom:1px solid var(--ax-border)\">sla</th></tr></thead>\n <tbody id=\"inbox-history-rows\"></tbody>\n </table>\n <p id=\"inbox-history-empty\" style=\"font-size:11px;color:var(--ax-muted);font-style:italic;margin:14px 0 0;display:none\">no completed user-tasks yet</p>\n </div>\n </section>\n</div>\n\n<div id=\"inbox-toast\" class=\"ax-inbox__toast\" hidden></div>`\n\n return renderShell({\n title: \"AgentX · Inbox\",\n activeTab: \"workflows\",\n subtitle: \"Inbox\",\n peers: opts.peers,\n currentPeerId: opts.currentPeerId,\n body,\n css: INBOX_PAGE_CSS,\n scripts: `<script>${INBOX_PAGE_SCRIPT}</script>`,\n })\n}\n\nconst INBOX_PAGE_CSS = `\n.ax-inbox__root {\n display: grid;\n grid-template-columns: 340px 1fr;\n height: calc(100vh - var(--ax-topbar-h, 48px));\n background: var(--ax-bg);\n}\n.ax-inbox__list {\n border-right: 1px solid var(--ax-border);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n.ax-inbox__list > header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 14px;\n border-bottom: 1px solid var(--ax-border);\n}\n.ax-inbox__list > header h2 { margin: 0; font-size: 14px; font-weight: 600; }\n.ax-inbox__actor {\n padding: 10px 14px;\n border-bottom: 1px solid var(--ax-border);\n}\n.ax-inbox__actor label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ax-muted); }\n.ax-inbox__actor input {\n font: inherit;\n padding: 6px 8px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n background: var(--ax-bg);\n color: var(--ax-fg);\n}\n.ax-inbox__cards { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1; }\n.ax-inbox__card {\n padding: 10px 12px;\n border-radius: 6px;\n margin-bottom: 4px;\n cursor: pointer;\n border: 1px solid transparent;\n}\n.ax-inbox__card:hover { background: var(--ax-surface-2, rgba(127,127,127,0.08)); }\n.ax-inbox__card.is-active { background: var(--ax-surface-2); border-color: var(--ax-border); }\n.ax-inbox__card-title { font-weight: 600; font-size: 13px; margin-bottom: 2px; }\n.ax-inbox__card-meta { font-size: 11px; color: var(--ax-muted); }\n.ax-inbox__card-due { font-size: 11px; color: #d48a00; margin-top: 4px; }\n.ax-inbox__empty { padding: 20px; color: var(--ax-muted); text-align: center; font-size: 13px; }\n.ax-inbox__detail { display: flex; flex-direction: column; min-height: 0; }\n.ax-inbox__detail > header { padding: 14px 18px; border-bottom: 1px solid var(--ax-border); }\n.ax-inbox__detail-body { overflow-y: auto; padding: 18px; flex: 1; }\n.ax-inbox__form { max-width: 560px; display: grid; gap: 14px; }\n.ax-inbox__field { display: flex; flex-direction: column; gap: 4px; }\n.ax-inbox__field label { font-size: 12px; color: var(--ax-muted); font-weight: 600; }\n.ax-inbox__field input, .ax-inbox__field textarea, .ax-inbox__field select {\n font: inherit;\n padding: 8px 10px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n background: var(--ax-bg);\n color: var(--ax-fg);\n}\n.ax-inbox__field textarea { min-height: 100px; resize: vertical; }\n.ax-inbox__field-hint { font-size: 11px; color: var(--ax-muted); }\n.ax-inbox__field-error { font-size: 11px; color: #c0392b; }\n.ax-inbox__actions { display: flex; gap: 8px; margin-top: 8px; }\n.ax-inbox__actions button {\n font: inherit;\n padding: 8px 14px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n cursor: pointer;\n background: var(--ax-surface-2);\n color: var(--ax-fg);\n}\n.ax-inbox__actions button.is-primary {\n background: #2ecc71; color: white; border-color: #27ae60;\n}\n.ax-inbox__actions button:disabled { opacity: 0.5; cursor: not-allowed; }\n.ax-inbox__icon {\n background: none; border: 1px solid transparent; color: var(--ax-fg); cursor: pointer;\n font-size: 14px; padding: 4px 8px; border-radius: 4px;\n}\n.ax-inbox__icon:hover { background: var(--ax-surface-2); }\n.ax-inbox__toast {\n position: fixed; bottom: 16px; right: 16px; padding: 10px 14px;\n background: var(--ax-fg); color: var(--ax-bg); border-radius: 6px;\n font-size: 13px; z-index: 100;\n}\n`\n\n// Deliberately vanilla JS — no framework dep. Fetches task list, renders\n// per-field inputs, POSTs submission.\nconst INBOX_PAGE_SCRIPT = `\n(function(){\n const $ = (sel) => document.querySelector(sel);\n const state = { tasks: [], selected: null, actor: document.querySelector('.ax-inbox__root').dataset.actor || '' };\n const toast = (msg) => {\n const el = $('#inbox-toast');\n el.textContent = msg;\n el.hidden = false;\n setTimeout(() => { el.hidden = true; }, 2500);\n };\n\n async function fetchTasks() {\n const qs = state.actor ? ('?actor=' + encodeURIComponent(state.actor)) : '';\n try {\n const r = await fetch('/api/workflows/tasks' + qs, { credentials: 'same-origin' });\n if (!r.ok) { toast('fetch failed: ' + r.status); return; }\n const data = await r.json();\n state.tasks = Array.isArray(data.tasks) ? data.tasks : [];\n renderList();\n if (state.selected && !state.tasks.find(t => t.id === state.selected)) {\n state.selected = null;\n renderDetail();\n }\n } catch (e) { toast('fetch error: ' + e.message); }\n }\n\n function renderList() {\n const ul = $('#inbox-tasks');\n const empty = $('#inbox-empty');\n ul.innerHTML = '';\n if (!state.tasks.length) { empty.hidden = false; return; }\n empty.hidden = true;\n for (const t of state.tasks) {\n const li = document.createElement('li');\n li.className = 'ax-inbox__card' + (t.id === state.selected ? ' is-active' : '');\n li.innerHTML =\n '<div class=\"ax-inbox__card-title\">' + escHtml(t.title) + '</div>' +\n '<div class=\"ax-inbox__card-meta\">' + escHtml(t.assignee) + ' · ' + escHtml(t.workflowId) + '</div>' +\n (t.dueAt ? ('<div class=\"ax-inbox__card-due\">due ' + new Date(t.dueAt).toLocaleString() + '</div>') : '');\n li.addEventListener('click', () => { state.selected = t.id; renderList(); renderDetail(); });\n ul.appendChild(li);\n }\n }\n\n function renderDetail() {\n const head = $('#inbox-detail-head');\n const body = $('#inbox-detail-body');\n const t = state.tasks.find(x => x.id === state.selected);\n if (!t) {\n head.innerHTML = '<span class=\"hint\">Select a task to open its form.</span>';\n body.innerHTML = '';\n return;\n }\n head.innerHTML = '<h2 style=\"margin:0;font-size:15px\">' + escHtml(t.title) + '</h2>' +\n (t.description ? '<p class=\"hint\" style=\"margin:6px 0 0\">' + escHtml(t.description) + '</p>' : '');\n const form = document.createElement('form');\n form.className = 'ax-inbox__form';\n form.addEventListener('submit', (e) => { e.preventDefault(); submit(t, 'primary'); });\n for (const field of (t.form.fields || [])) {\n form.appendChild(renderField(field));\n }\n const actions = document.createElement('div');\n actions.className = 'ax-inbox__actions';\n const primary = document.createElement('button');\n primary.type = 'submit';\n primary.className = 'is-primary';\n primary.textContent = t.form.submitLabel || 'Submit';\n actions.appendChild(primary);\n if (t.form.secondaryAction) {\n const secondary = document.createElement('button');\n secondary.type = 'button';\n secondary.textContent = t.form.secondaryAction.label;\n secondary.addEventListener('click', () => submit(t, 'secondary'));\n actions.appendChild(secondary);\n }\n form.appendChild(actions);\n body.innerHTML = '';\n body.appendChild(form);\n }\n\n function renderField(field) {\n const wrap = document.createElement('div');\n wrap.className = 'ax-inbox__field';\n const label = document.createElement('label');\n label.textContent = field.label + (field.required ? ' *' : '');\n wrap.appendChild(label);\n let input;\n switch (field.type) {\n case 'long-text':\n input = document.createElement('textarea');\n break;\n case 'number':\n input = document.createElement('input'); input.type = 'number';\n break;\n case 'boolean':\n input = document.createElement('select');\n for (const v of ['', 'yes', 'no']) {\n const o = document.createElement('option'); o.value = v; o.textContent = v || '—';\n input.appendChild(o);\n }\n break;\n case 'date':\n input = document.createElement('input'); input.type = 'date';\n break;\n case 'select':\n input = document.createElement('select');\n if (!field.required) { const o = document.createElement('option'); o.value = ''; o.textContent = '—'; input.appendChild(o); }\n for (const opt of (field.options || [])) {\n const o = document.createElement('option'); o.value = opt; o.textContent = opt;\n input.appendChild(o);\n }\n break;\n case 'multi-select':\n input = document.createElement('select'); input.multiple = true; input.size = Math.min(6, (field.options||[]).length || 3);\n for (const opt of (field.options || [])) {\n const o = document.createElement('option'); o.value = opt; o.textContent = opt;\n input.appendChild(o);\n }\n break;\n case 'file':\n input = document.createElement('input'); input.type = 'text'; input.placeholder = 'URL or path';\n break;\n default:\n input = document.createElement('input'); input.type = 'text';\n }\n input.name = field.key;\n if (field.defaultValue !== undefined && field.type !== 'multi-select') input.value = String(field.defaultValue);\n wrap.appendChild(input);\n if (field.hint) {\n const hint = document.createElement('div');\n hint.className = 'ax-inbox__field-hint';\n hint.textContent = field.hint;\n wrap.appendChild(hint);\n }\n const err = document.createElement('div');\n err.className = 'ax-inbox__field-error';\n err.dataset.for = field.key;\n wrap.appendChild(err);\n return wrap;\n }\n\n async function submit(task, action) {\n const form = $('.ax-inbox__form');\n if (!form) return;\n const values = {};\n for (const field of (task.form.fields || [])) {\n const el = form.querySelector('[name=\"' + field.key + '\"]');\n if (!el) continue;\n if (field.type === 'multi-select') {\n values[field.key] = Array.from(el.selectedOptions).map(o => o.value);\n } else if (field.type === 'boolean') {\n values[field.key] = el.value;\n } else {\n values[field.key] = el.value;\n }\n }\n form.querySelectorAll('.ax-inbox__field-error').forEach(e => { e.textContent = ''; });\n try {\n const r = await fetch('/api/workflows/tasks/' + encodeURIComponent(task.id) + '/submit', {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ submittedBy: state.actor || 'anonymous', submission: { action, values } }),\n });\n const data = await r.json().catch(() => ({}));\n if (!r.ok) {\n if (data.fieldErrors && Array.isArray(data.fieldErrors)) {\n for (const fe of data.fieldErrors) {\n const node = form.querySelector('[data-for=\"' + fe.field + '\"]');\n if (node) node.textContent = fe.message;\n }\n toast('form has errors');\n } else {\n toast(data.error || 'submit failed');\n }\n return;\n }\n toast('submitted');\n state.selected = null;\n fetchTasks();\n } catch (e) { toast('submit error: ' + e.message); }\n }\n\n function escHtml(s) {\n return String(s).replace(/[&<>\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'})[c]);\n }\n\n $('#inbox-actor').addEventListener('change', (e) => {\n state.actor = e.target.value.trim();\n state.selected = null;\n const url = new URL(window.location.href);\n if (state.actor) url.searchParams.set('actor', state.actor); else url.searchParams.delete('actor');\n history.replaceState({}, '', url.toString());\n fetchTasks();\n });\n $('#inbox-refresh').addEventListener('click', fetchTasks);\n\n // History toggle — surfaces the SLA-aware completion log.\n let historyOpen = false;\n function fmtDuration(ms) {\n if (ms == null) return '—';\n const s = Math.floor(ms / 1000);\n if (s < 60) return s + 's';\n const m = Math.floor(s / 60);\n if (m < 60) return m + 'm ' + (s % 60) + 's';\n const h = Math.floor(m / 60);\n return h + 'h ' + (m % 60) + 'm';\n }\n function fmtTs(s) {\n if (!s) return '—';\n return s.replace('T', ' ').slice(0, 16);\n }\n async function loadHistory() {\n try {\n const r = await fetch('/api/workflows/tasks/history?limit=100');\n const data = await r.json();\n const rows = data.rows || [];\n const tbody = $('#inbox-history-rows');\n const empty = $('#inbox-history-empty');\n if (rows.length === 0) { empty.style.display = ''; tbody.innerHTML = ''; return; }\n empty.style.display = 'none';\n tbody.innerHTML = rows.map(function(r){\n const slaCell = r.dueAt\n ? (r.breachedSla\n ? '<span style=\"color:var(--ax-err,#e74c3c)\">breached</span>'\n : '<span style=\"color:#2ecc71\">on time</span>')\n : '<span style=\"color:var(--ax-muted)\">—</span>';\n const rowStyle = r.breachedSla ? 'background:rgba(231,76,60,0.06)' : '';\n return '<tr style=\"' + rowStyle + '\">' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(fmtTs(r.submittedAt)) + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(r.workflowId || '') + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml((r.title || '').slice(0, 60)) + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(r.submittedBy || '') + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + escHtml(fmtDuration(r.durationMs)) + '</td>' +\n '<td style=\"padding:4px 7px;border-bottom:1px solid var(--ax-border)\">' + slaCell + '</td>' +\n '</tr>';\n }).join('');\n } catch (e) {\n $('#inbox-history-empty').textContent = 'failed to load: ' + e.message;\n $('#inbox-history-empty').style.display = '';\n }\n }\n $('#inbox-history-toggle').addEventListener('click', () => {\n historyOpen = !historyOpen;\n $('#inbox-history').style.display = historyOpen ? 'block' : 'none';\n $('#inbox-detail-body').style.display = historyOpen ? 'none' : '';\n $('#inbox-history-toggle').textContent = historyOpen ? 'Show open inbox' : 'Show completed history';\n if (historyOpen) loadHistory();\n });\n\n fetchTasks();\n setInterval(fetchTasks, 10000);\n})();\n`\n"],"mappings":"+CAwBO,SAASA,EAAgBC,EAAsB,CAAC,EAAW,CAChE,IAAMC,EAAO,2CAA2CC,EAAIF,EAAK,OAAS,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yFAOWE,EAAIF,EAAK,OAAS,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6DA+B3G,OAAOG,EAAY,CACjB,MAAO,oBACP,UAAW,YACX,SAAU,QACV,MAAOH,EAAK,MACZ,cAAeA,EAAK,cACpB,KAAAC,EACA,IAAKG,EACL,QAAS,WAAWC,YACtB,CAAC,CACH,CAEA,IAAMD,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6FjBC,EAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":["renderInboxPage","opts","body","esc","renderShell","INBOX_PAGE_CSS","INBOX_PAGE_SCRIPT"]}
@@ -1,2 +0,0 @@
1
- import{e as b}from"./chunk-CDNWCYF7.js";import{existsSync as p,mkdirSync as k,readdirSync as A,readFileSync as m,unlinkSync as v,writeFileSync as T}from"fs";import{resolve as d}from"path";import{randomUUID as w}from"crypto";import{z as s}from"zod";var D=s.enum(["open","claimed","completed","canceled"]),f=s.object({id:s.string(),runId:s.string(),workflowId:s.string(),nodeId:s.string(),title:s.string(),description:s.string().optional(),assignee:s.string(),assignedTo:s.array(s.string()),form:b,status:D,delivered:s.array(s.object({channel:s.string(),handle:s.string(),messageId:s.string().optional(),at:s.string()})).default([]),dueAt:s.string().optional(),submittedBy:s.string().optional(),submittedAt:s.string().optional(),submittedValues:s.record(s.unknown()).optional(),submittedAction:s.enum(["primary","secondary"]).optional(),createdAt:s.string(),updatedAt:s.string()}),y=class{baseDir;archiveDir;constructor(t={}){let e=t.baseDir??d(process.cwd(),".agentx/workflows");this.baseDir=d(e,"_tasks"),this.archiveDir=d(this.baseDir,"_completed"),k(this.baseDir,{recursive:!0}),k(this.archiveDir,{recursive:!0})}pathFor(t){return d(this.baseDir,`${t}.json`)}archivePath(t){return d(this.archiveDir,`${t}.json`)}create(t){let e=new Date().toISOString(),r={id:w(),runId:t.runId,workflowId:t.workflowId,nodeId:t.nodeId,title:t.title,description:t.description,assignee:t.assignee,assignedTo:t.assignedTo,form:t.form,status:"open",delivered:[],dueAt:t.dueAt,createdAt:e,updatedAt:e};return T(this.pathFor(r.id),JSON.stringify(r,null,2)),r}get(t){let e=this.pathFor(t);if(p(e))try{let i=JSON.parse(m(e,"utf-8")),a=f.safeParse(i);return a.success?a.data:null}catch{return null}let r=this.archivePath(t);if(p(r))try{let i=JSON.parse(m(r,"utf-8")),a=f.safeParse(i);return a.success?a.data:null}catch{return null}return null}save(t){let e={...t,updatedAt:new Date().toISOString()},r=e.status==="completed"||e.status==="canceled"?this.archivePath(e.id):this.pathFor(e.id);return T(r,JSON.stringify(e,null,2)),r===this.archivePath(e.id)&&p(this.pathFor(e.id))&&v(this.pathFor(e.id)),e}listOpen(){if(!p(this.baseDir))return[];let t=[];for(let e of A(this.baseDir,{withFileTypes:!0}))if(!(!e.isFile()||!e.name.endsWith(".json")))try{let r=JSON.parse(m(d(this.baseDir,e.name),"utf-8")),i=f.safeParse(r);i.success&&i.data.status==="open"&&t.push(i.data)}catch{}return t.sort((e,r)=>e.createdAt<r.createdAt?1:-1)}listForActor(t){return this.listOpen().filter(e=>e.assignedTo.includes(t))}findByRunNode(t,e){return this.listOpen().find(r=>r.runId===t&&r.nodeId===e)??null}listArchived(){if(!p(this.archiveDir))return[];let t=[];for(let e of A(this.archiveDir,{withFileTypes:!0}))if(!(!e.isFile()||!e.name.endsWith(".json")))try{let r=JSON.parse(m(d(this.archiveDir,e.name),"utf-8")),i=f.safeParse(r);i.success&&t.push(i.data)}catch{}return t.sort((e,r)=>e.createdAt<r.createdAt?1:-1)}};function x(u){let t=u.listOpen(),e=u.listArchived(),r=new Map,i=o=>{let n=r.get(o);return n||(n={open:0,completed:0,durations:[],dueCount:0,breached:0},r.set(o,n)),n};for(let o of t)for(let n of o.assignedTo)i(n).open++;for(let o of e){if(o.status!=="completed"||!o.submittedAt)continue;let n=Date.parse(o.createdAt),l=Date.parse(o.submittedAt);if(Number.isNaN(n)||Number.isNaN(l))continue;let g=Math.max(0,l-n),S=o.submittedBy??o.assignedTo[0]??"unknown",h=i(S);h.completed++,h.durations.push(g),o.dueAt&&(h.dueCount++,l>Date.parse(o.dueAt)&&h.breached++)}let a=Array.from(r.entries()).map(([o,n])=>({actorId:o,openTasks:n.open,completedTasks:n.completed,avgDurationMs:n.durations.length?Math.round(n.durations.reduce((l,g)=>l+g,0)/n.durations.length):null,slaBreachRate:n.dueCount?n.breached/n.dueCount:null,breachedCount:n.breached})).sort((o,n)=>n.completedTasks-o.completedTasks||o.actorId.localeCompare(n.actorId)),c=I(r);return{byActor:a,totals:c,generatedAt:new Date().toISOString()}}function I(u){let t=0,e=0,r=[],i=0,a=0;for(let c of u.values())t+=c.open,e+=c.completed,r.push(...c.durations),i+=c.dueCount,a+=c.breached;return{actorId:"__total__",openTasks:t,completedTasks:e,avgDurationMs:r.length?Math.round(r.reduce((c,o)=>c+o,0)/r.length):null,slaBreachRate:i?a/i:null,breachedCount:a}}export{D as a,f as b,y as c,x as d};
2
- //# sourceMappingURL=chunk-N67JZAXU.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/workflows/task-store.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { randomUUID } from \"crypto\"\nimport { z } from \"zod\"\nimport { formSchemaSchema, type FormSchema } from \"../forms/types\"\n\n// --- UserTask store ---\n//\n// Persists open `userTask` records (the live inbox). One JSON file per task\n// under .agentx/workflows/_tasks/<taskId>.json. When a task is submitted the\n// record is moved to _tasks/_completed/ for historical lookup (keeps the\n// live inbox fast) and the paused run is resumed via the dispatcher.\n//\n// Task state machine:\n// open — freshly created, awaiting any assignee action\n// claimed — a specific assignee has started (optional in v1; reserved)\n// completed — submitted successfully, record archived\n// canceled — workflow canceled or timer-escalated past this task\n\nexport const taskStatusSchema = z.enum([\"open\", \"claimed\", \"completed\", \"canceled\"])\nexport type TaskStatus = z.infer<typeof taskStatusSchema>\n\nexport const userTaskRecordSchema = z.object({\n id: z.string(),\n runId: z.string(),\n workflowId: z.string(),\n nodeId: z.string(),\n title: z.string(),\n description: z.string().optional(),\n /** Assignee ref (\"actor:<id>\" or \"role:<id>\"). */\n assignee: z.string(),\n /** Concrete actors who currently see this task (resolved via role strategy). */\n assignedTo: z.array(z.string()),\n form: formSchemaSchema,\n status: taskStatusSchema,\n /** Channel-delivery tracking. Populated by the userTask handler after it\n * renders the form to the assignee's preferred channel(s). */\n delivered: z.array(z.object({\n channel: z.string(),\n handle: z.string(),\n messageId: z.string().optional(),\n at: z.string(),\n })).default([]),\n /** Optional due date (ISO-8601). Set by dueIn config. */\n dueAt: z.string().optional(),\n /** Set on completion. */\n submittedBy: z.string().optional(),\n submittedAt: z.string().optional(),\n submittedValues: z.record(z.unknown()).optional(),\n submittedAction: z.enum([\"primary\", \"secondary\"]).optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n})\nexport type UserTaskRecord = z.infer<typeof userTaskRecordSchema>\n\nexport interface TaskStoreOptions {\n baseDir?: string\n}\n\nexport class TaskStore {\n readonly baseDir: string\n readonly archiveDir: string\n\n constructor(opts: TaskStoreOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx/workflows\")\n this.baseDir = resolve(root, \"_tasks\")\n this.archiveDir = resolve(this.baseDir, \"_completed\")\n mkdirSync(this.baseDir, { recursive: true })\n mkdirSync(this.archiveDir, { recursive: true })\n }\n\n private pathFor(id: string): string {\n return resolve(this.baseDir, `${id}.json`)\n }\n\n private archivePath(id: string): string {\n return resolve(this.archiveDir, `${id}.json`)\n }\n\n create(args: {\n runId: string\n workflowId: string\n nodeId: string\n title: string\n description?: string\n assignee: string\n assignedTo: string[]\n form: FormSchema\n dueAt?: string\n }): UserTaskRecord {\n const now = new Date().toISOString()\n const record: UserTaskRecord = {\n id: randomUUID(),\n runId: args.runId,\n workflowId: args.workflowId,\n nodeId: args.nodeId,\n title: args.title,\n description: args.description,\n assignee: args.assignee,\n assignedTo: args.assignedTo,\n form: args.form,\n status: \"open\",\n delivered: [],\n dueAt: args.dueAt,\n createdAt: now,\n updatedAt: now,\n }\n writeFileSync(this.pathFor(record.id), JSON.stringify(record, null, 2))\n return record\n }\n\n get(id: string): UserTaskRecord | null {\n const live = this.pathFor(id)\n if (existsSync(live)) {\n try {\n const raw = JSON.parse(readFileSync(live, \"utf-8\"))\n const parsed = userTaskRecordSchema.safeParse(raw)\n return parsed.success ? parsed.data : null\n } catch { return null }\n }\n const archived = this.archivePath(id)\n if (existsSync(archived)) {\n try {\n const raw = JSON.parse(readFileSync(archived, \"utf-8\"))\n const parsed = userTaskRecordSchema.safeParse(raw)\n return parsed.success ? parsed.data : null\n } catch { return null }\n }\n return null\n }\n\n save(record: UserTaskRecord): UserTaskRecord {\n const updated = { ...record, updatedAt: new Date().toISOString() }\n const dest = updated.status === \"completed\" || updated.status === \"canceled\"\n ? this.archivePath(updated.id)\n : this.pathFor(updated.id)\n writeFileSync(dest, JSON.stringify(updated, null, 2))\n // If we just moved to archive, clean up the live file.\n if (dest === this.archivePath(updated.id) && existsSync(this.pathFor(updated.id))) {\n unlinkSync(this.pathFor(updated.id))\n }\n return updated\n }\n\n /** Open tasks only — drives the inbox page. */\n listOpen(): UserTaskRecord[] {\n if (!existsSync(this.baseDir)) return []\n const out: UserTaskRecord[] = []\n for (const entry of readdirSync(this.baseDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue\n try {\n const raw = JSON.parse(readFileSync(resolve(this.baseDir, entry.name), \"utf-8\"))\n const parsed = userTaskRecordSchema.safeParse(raw)\n if (parsed.success && parsed.data.status === \"open\") out.push(parsed.data)\n } catch { /* skip */ }\n }\n return out.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))\n }\n\n /** Open tasks assigned to a specific actor (directly or via role). */\n listForActor(actorId: string): UserTaskRecord[] {\n return this.listOpen().filter((t) => t.assignedTo.includes(actorId))\n }\n\n /** Look up the open task owned by a specific run/node pair. */\n findByRunNode(runId: string, nodeId: string): UserTaskRecord | null {\n return this.listOpen().find((t) => t.runId === runId && t.nodeId === nodeId) ?? null\n }\n\n /** Return every completed/canceled task record in the archive. Used by\n * KPI aggregation. Walks the archive directory once. */\n listArchived(): UserTaskRecord[] {\n if (!existsSync(this.archiveDir)) return []\n const out: UserTaskRecord[] = []\n for (const entry of readdirSync(this.archiveDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue\n try {\n const raw = JSON.parse(readFileSync(resolve(this.archiveDir, entry.name), \"utf-8\"))\n const parsed = userTaskRecordSchema.safeParse(raw)\n if (parsed.success) out.push(parsed.data)\n } catch { /* skip */ }\n }\n return out.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))\n }\n}\n\n// --- KPI aggregation ---\n//\n// Cheap on-demand stats. For v1 we re-read the fs on every call; cache\n// lands later if/when the dashboard polls aggressively. KPIs compute\n// over open + archived tasks combined (open contribute to pending count;\n// archived contribute to durations + breach rate).\n\nexport interface ActorKpi {\n actorId: string\n openTasks: number\n completedTasks: number\n /** Average milliseconds from createdAt → submittedAt (completed only). */\n avgDurationMs: number | null\n /** Fraction 0..1 of completed tasks submitted after dueAt. Null when\n * no completed tasks had a dueAt to measure against. */\n slaBreachRate: number | null\n /** Count of completed tasks where submittedAt > dueAt. */\n breachedCount: number\n}\n\nexport interface WorkflowKpis {\n byActor: ActorKpi[]\n totals: ActorKpi\n generatedAt: string\n}\n\nexport function computeKpis(store: TaskStore): WorkflowKpis {\n const open = store.listOpen()\n const archived = store.listArchived()\n const byActor = new Map<string, {\n open: number\n completed: number\n durations: number[]\n dueCount: number\n breached: number\n }>()\n\n const bucket = (id: string) => {\n let b = byActor.get(id)\n if (!b) { b = { open: 0, completed: 0, durations: [], dueCount: 0, breached: 0 }; byActor.set(id, b) }\n return b\n }\n\n for (const t of open) {\n for (const actor of t.assignedTo) bucket(actor).open++\n }\n for (const t of archived) {\n if (t.status !== \"completed\" || !t.submittedAt) continue\n const createdMs = Date.parse(t.createdAt)\n const submittedMs = Date.parse(t.submittedAt)\n if (Number.isNaN(createdMs) || Number.isNaN(submittedMs)) continue\n const dur = Math.max(0, submittedMs - createdMs)\n const submitter = t.submittedBy ?? t.assignedTo[0] ?? \"unknown\"\n const b = bucket(submitter)\n b.completed++\n b.durations.push(dur)\n if (t.dueAt) {\n b.dueCount++\n if (submittedMs > Date.parse(t.dueAt)) b.breached++\n }\n }\n\n const perActor: ActorKpi[] = Array.from(byActor.entries()).map(([actorId, b]) => ({\n actorId,\n openTasks: b.open,\n completedTasks: b.completed,\n avgDurationMs: b.durations.length ? Math.round(b.durations.reduce((a, n) => a + n, 0) / b.durations.length) : null,\n slaBreachRate: b.dueCount ? b.breached / b.dueCount : null,\n breachedCount: b.breached,\n })).sort((a, b) => b.completedTasks - a.completedTasks || a.actorId.localeCompare(b.actorId))\n\n const totals = aggregateTotals(byActor)\n return {\n byActor: perActor,\n totals,\n generatedAt: new Date().toISOString(),\n }\n}\n\nfunction aggregateTotals(byActor: Map<string, { open: number; completed: number; durations: number[]; dueCount: number; breached: number }>): ActorKpi {\n let open = 0, completed = 0, durations: number[] = [], dueCount = 0, breached = 0\n for (const b of byActor.values()) {\n open += b.open; completed += b.completed\n durations.push(...b.durations)\n dueCount += b.dueCount; breached += b.breached\n }\n return {\n actorId: \"__total__\",\n openTasks: open,\n completedTasks: completed,\n avgDurationMs: durations.length ? Math.round(durations.reduce((a, n) => a + n, 0) / durations.length) : null,\n slaBreachRate: dueCount ? breached / dueCount : null,\n breachedCount: breached,\n }\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","randomUUID","z","taskStatusSchema","z","userTaskRecordSchema","formSchemaSchema","TaskStore","opts","root","resolve","mkdirSync","id","args","now","record","randomUUID","writeFileSync","live","existsSync","raw","readFileSync","parsed","archived","updated","dest","unlinkSync","out","entry","readdirSync","a","b","actorId","t","runId","nodeId","computeKpis","store","open","byActor","bucket","actor","createdMs","submittedMs","dur","submitter","perActor","n","totals","aggregateTotals","completed","durations","dueCount","breached"]}