agentix-cli 0.127.0 → 0.129.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 (147) hide show
  1. package/dist/{App-FF66N2KH.js → App-G37JNRBW.js} +2 -2
  2. package/dist/{ChatApp-HMHQZVVD.js → ChatApp-NCKC7C3L.js} +2 -2
  3. package/dist/absorb-eval-ZCWHWKAD.js +2 -0
  4. package/dist/{agent-UAWDO3I2.js → agent-LJ4SUQCC.js} +2 -2
  5. package/dist/{board-dashboard-Y2OECKDG.js → board-dashboard-LZJT4YWV.js} +251 -235
  6. package/dist/board-dashboard-LZJT4YWV.js.map +1 -0
  7. package/dist/{chunk-NUSROFTJ.js → chunk-4FMZTM3W.js} +2 -2
  8. package/dist/chunk-4O4GZBKZ.js +4 -0
  9. package/dist/chunk-4O4GZBKZ.js.map +1 -0
  10. package/dist/{chunk-I4DGMFQG.js → chunk-4ZFLYY5P.js} +2 -2
  11. package/dist/chunk-A347YHEW.js +4 -0
  12. package/dist/chunk-A347YHEW.js.map +1 -0
  13. package/dist/chunk-AMW2R676.js +36 -0
  14. package/dist/chunk-AMW2R676.js.map +1 -0
  15. package/dist/{chunk-HNBW5Q47.js → chunk-AOFTTIZZ.js} +2 -2
  16. package/dist/{chunk-O6FP3LPC.js → chunk-ARSVKSC4.js} +2 -2
  17. package/dist/{chunk-EHLTZ6IB.js → chunk-BCUR2TJ7.js} +2 -2
  18. package/dist/{chunk-PH43FUU7.js → chunk-BWSB7NOX.js} +2 -2
  19. package/dist/{chunk-QWQOI4G4.js → chunk-CDLJOGVZ.js} +31 -31
  20. package/dist/chunk-CDLJOGVZ.js.map +1 -0
  21. package/dist/{chunk-COWAAE54.js → chunk-CPMQZ6Z7.js} +2 -2
  22. package/dist/{chunk-YV22EOGL.js → chunk-DVR6PFCJ.js} +2 -2
  23. package/dist/chunk-EOLEKSN6.js +54 -0
  24. package/dist/chunk-EOLEKSN6.js.map +1 -0
  25. package/dist/{chunk-VBJTGTF5.js → chunk-I3RAUJCD.js} +2 -2
  26. package/dist/chunk-M7IJVOJA.js +55 -0
  27. package/dist/chunk-M7IJVOJA.js.map +1 -0
  28. package/dist/{chunk-6W74CHNW.js → chunk-MEFPXHQJ.js} +13 -13
  29. package/dist/chunk-MEFPXHQJ.js.map +1 -0
  30. package/dist/chunk-PVUNDNT3.js +17 -0
  31. package/dist/chunk-PVUNDNT3.js.map +1 -0
  32. package/dist/{chunk-3KYZOC7N.js → chunk-R6UCPHUD.js} +2 -2
  33. package/dist/{chunk-CKYPQMUD.js → chunk-SCC7HBFZ.js} +2 -2
  34. package/dist/{chunk-56KNKUFF.js → chunk-VD7IO653.js} +3 -3
  35. package/dist/{chunk-56KNKUFF.js.map → chunk-VD7IO653.js.map} +1 -1
  36. package/dist/{chunk-XYT6XY7G.js → chunk-VJRITDSQ.js} +2 -2
  37. package/dist/chunk-X5JTG52Q.js +6 -0
  38. package/dist/chunk-X5JTG52Q.js.map +1 -0
  39. package/dist/{chunk-LTH3AYXC.js → chunk-XIA2ACKX.js} +2 -2
  40. package/dist/{chunk-73WQ42PO.js → chunk-XOQNDWVW.js} +2 -2
  41. package/dist/{chunk-XX5QKBX4.js → chunk-XZN7QBRH.js} +2 -2
  42. package/dist/{chunk-HYRBBH6T.js → chunk-YHD4PHFX.js} +240 -240
  43. package/dist/chunk-YHD4PHFX.js.map +1 -0
  44. package/dist/{chunk-4WZGZ2Z6.js → chunk-Z5CBUZIW.js} +2 -2
  45. package/dist/{chunk-EJT3F6QM.js → chunk-ZF5U5KPB.js} +13 -13
  46. package/dist/chunk-ZF5U5KPB.js.map +1 -0
  47. package/dist/cli.js +1 -1
  48. package/dist/{client-2VVRJ3UF.js → client-A5OK3IFO.js} +2 -2
  49. package/dist/config-DQ5SILHX.js +2 -0
  50. package/dist/{config-mutate-47WZDZBO.js → config-mutate-62FCJ3S6.js} +2 -2
  51. package/dist/config-mutator-3ZL5YQSO.js +2 -0
  52. package/dist/contributions-L2YS4AH2.js +57 -0
  53. package/dist/contributions-L2YS4AH2.js.map +1 -0
  54. package/dist/{doctor-Q3QNZVLC.js → doctor-7TPSMMFA.js} +2 -2
  55. package/dist/{follow-up-tool-GKZVG4L2.js → follow-up-tool-FKWIRFPW.js} +2 -2
  56. package/dist/{hub-DNPINTIF.js → hub-EU5ECAJB.js} +2 -2
  57. package/dist/index.d.ts +301 -0
  58. package/dist/index.js +1 -1
  59. package/dist/note-reader-LPPIYYLT.js +8 -0
  60. package/dist/note-reader-LPPIYYLT.js.map +1 -0
  61. package/dist/{notes-YLTNRMB5.js → notes-2VC47RCK.js} +2 -2
  62. package/dist/program-UTSXNAHM.js +743 -0
  63. package/dist/program-UTSXNAHM.js.map +1 -0
  64. package/dist/{promote-PETKX7KR.js → promote-XBDWCHLU.js} +2 -2
  65. package/dist/query-UI6GQEL7.js +2 -0
  66. package/dist/{registry-2XDO53AJ.js → registry-JW6ETTBA.js} +2 -2
  67. package/dist/{request-planner-A3WJNPXO.js → request-planner-SEXYQN7D.js} +2 -2
  68. package/dist/{review-candidates-A6ID2RDX.js → review-candidates-PIJWPPHQ.js} +2 -2
  69. package/dist/{rotation-memo-6UW6DTBQ.js → rotation-memo-7VLREGS6.js} +2 -2
  70. package/dist/{routing-NPP3MEUW.js → routing-2V3Y6ZR5.js} +2 -2
  71. package/dist/schedule-tool-KFZ27JTH.js +2 -0
  72. package/dist/{scheduler-T6VLBM3U.js → scheduler-SVZKDKJV.js} +2 -2
  73. package/dist/score-KDNIUTNV.js +3 -0
  74. package/dist/score-KDNIUTNV.js.map +1 -0
  75. package/dist/{seat-D3DBYTSC.js → seat-C6TMCJWI.js} +2 -2
  76. package/dist/{select-RFY64RY7.js → select-DP64KWO4.js} +2 -2
  77. package/dist/{set-enabled-AU66DR5C.js → set-enabled-WCH6FU4N.js} +2 -2
  78. package/dist/{tool-PCHIVR27.js → tool-TWQRBV4Y.js} +2 -2
  79. package/dist/{tool-F3YA2ZZQ.js → tool-WUK3ZOWG.js} +2 -2
  80. package/dist/wiki-4LAJ36XR.js +2 -0
  81. package/package.json +1 -1
  82. package/dist/absorb-eval-ERKVVTGG.js +0 -2
  83. package/dist/board-dashboard-Y2OECKDG.js.map +0 -1
  84. package/dist/chunk-52SIHG45.js +0 -4
  85. package/dist/chunk-52SIHG45.js.map +0 -1
  86. package/dist/chunk-6W74CHNW.js.map +0 -1
  87. package/dist/chunk-EJT3F6QM.js.map +0 -1
  88. package/dist/chunk-HYRBBH6T.js.map +0 -1
  89. package/dist/chunk-PLIT25TH.js +0 -51
  90. package/dist/chunk-PLIT25TH.js.map +0 -1
  91. package/dist/chunk-QWQOI4G4.js.map +0 -1
  92. package/dist/chunk-TJ4P4WW2.js +0 -4
  93. package/dist/chunk-TJ4P4WW2.js.map +0 -1
  94. package/dist/chunk-UJXS4MS3.js +0 -49
  95. package/dist/chunk-UJXS4MS3.js.map +0 -1
  96. package/dist/chunk-Y27RUOD2.js +0 -54
  97. package/dist/chunk-Y27RUOD2.js.map +0 -1
  98. package/dist/config-YSKHOV44.js +0 -2
  99. package/dist/config-mutator-7DIEQUB5.js +0 -2
  100. package/dist/program-4QWUOJE3.js +0 -739
  101. package/dist/program-4QWUOJE3.js.map +0 -1
  102. package/dist/query-D44BUTH3.js +0 -2
  103. package/dist/schedule-tool-ZECUQMQK.js +0 -2
  104. package/dist/wiki-NI3ZPUZS.js +0 -2
  105. /package/dist/{App-FF66N2KH.js.map → App-G37JNRBW.js.map} +0 -0
  106. /package/dist/{ChatApp-HMHQZVVD.js.map → ChatApp-NCKC7C3L.js.map} +0 -0
  107. /package/dist/{absorb-eval-ERKVVTGG.js.map → absorb-eval-ZCWHWKAD.js.map} +0 -0
  108. /package/dist/{agent-UAWDO3I2.js.map → agent-LJ4SUQCC.js.map} +0 -0
  109. /package/dist/{chunk-NUSROFTJ.js.map → chunk-4FMZTM3W.js.map} +0 -0
  110. /package/dist/{chunk-I4DGMFQG.js.map → chunk-4ZFLYY5P.js.map} +0 -0
  111. /package/dist/{chunk-HNBW5Q47.js.map → chunk-AOFTTIZZ.js.map} +0 -0
  112. /package/dist/{chunk-O6FP3LPC.js.map → chunk-ARSVKSC4.js.map} +0 -0
  113. /package/dist/{chunk-EHLTZ6IB.js.map → chunk-BCUR2TJ7.js.map} +0 -0
  114. /package/dist/{chunk-PH43FUU7.js.map → chunk-BWSB7NOX.js.map} +0 -0
  115. /package/dist/{chunk-COWAAE54.js.map → chunk-CPMQZ6Z7.js.map} +0 -0
  116. /package/dist/{chunk-YV22EOGL.js.map → chunk-DVR6PFCJ.js.map} +0 -0
  117. /package/dist/{chunk-VBJTGTF5.js.map → chunk-I3RAUJCD.js.map} +0 -0
  118. /package/dist/{chunk-3KYZOC7N.js.map → chunk-R6UCPHUD.js.map} +0 -0
  119. /package/dist/{chunk-CKYPQMUD.js.map → chunk-SCC7HBFZ.js.map} +0 -0
  120. /package/dist/{chunk-XYT6XY7G.js.map → chunk-VJRITDSQ.js.map} +0 -0
  121. /package/dist/{chunk-LTH3AYXC.js.map → chunk-XIA2ACKX.js.map} +0 -0
  122. /package/dist/{chunk-73WQ42PO.js.map → chunk-XOQNDWVW.js.map} +0 -0
  123. /package/dist/{chunk-XX5QKBX4.js.map → chunk-XZN7QBRH.js.map} +0 -0
  124. /package/dist/{chunk-4WZGZ2Z6.js.map → chunk-Z5CBUZIW.js.map} +0 -0
  125. /package/dist/{client-2VVRJ3UF.js.map → client-A5OK3IFO.js.map} +0 -0
  126. /package/dist/{config-YSKHOV44.js.map → config-DQ5SILHX.js.map} +0 -0
  127. /package/dist/{config-mutate-47WZDZBO.js.map → config-mutate-62FCJ3S6.js.map} +0 -0
  128. /package/dist/{config-mutator-7DIEQUB5.js.map → config-mutator-3ZL5YQSO.js.map} +0 -0
  129. /package/dist/{doctor-Q3QNZVLC.js.map → doctor-7TPSMMFA.js.map} +0 -0
  130. /package/dist/{follow-up-tool-GKZVG4L2.js.map → follow-up-tool-FKWIRFPW.js.map} +0 -0
  131. /package/dist/{hub-DNPINTIF.js.map → hub-EU5ECAJB.js.map} +0 -0
  132. /package/dist/{notes-YLTNRMB5.js.map → notes-2VC47RCK.js.map} +0 -0
  133. /package/dist/{promote-PETKX7KR.js.map → promote-XBDWCHLU.js.map} +0 -0
  134. /package/dist/{query-D44BUTH3.js.map → query-UI6GQEL7.js.map} +0 -0
  135. /package/dist/{registry-2XDO53AJ.js.map → registry-JW6ETTBA.js.map} +0 -0
  136. /package/dist/{request-planner-A3WJNPXO.js.map → request-planner-SEXYQN7D.js.map} +0 -0
  137. /package/dist/{review-candidates-A6ID2RDX.js.map → review-candidates-PIJWPPHQ.js.map} +0 -0
  138. /package/dist/{rotation-memo-6UW6DTBQ.js.map → rotation-memo-7VLREGS6.js.map} +0 -0
  139. /package/dist/{routing-NPP3MEUW.js.map → routing-2V3Y6ZR5.js.map} +0 -0
  140. /package/dist/{schedule-tool-ZECUQMQK.js.map → schedule-tool-KFZ27JTH.js.map} +0 -0
  141. /package/dist/{scheduler-T6VLBM3U.js.map → scheduler-SVZKDKJV.js.map} +0 -0
  142. /package/dist/{seat-D3DBYTSC.js.map → seat-C6TMCJWI.js.map} +0 -0
  143. /package/dist/{select-RFY64RY7.js.map → select-DP64KWO4.js.map} +0 -0
  144. /package/dist/{set-enabled-AU66DR5C.js.map → set-enabled-WCH6FU4N.js.map} +0 -0
  145. /package/dist/{tool-PCHIVR27.js.map → tool-TWQRBV4Y.js.map} +0 -0
  146. /package/dist/{tool-F3YA2ZZQ.js.map → tool-WUK3ZOWG.js.map} +0 -0
  147. /package/dist/{wiki-NI3ZPUZS.js.map → wiki-4LAJ36XR.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/workspace-env.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// Loads per-agent secrets from the agent's workspace and tells the runtime\n// which keys the daemon's own .env injected, so we can strip those before\n// spawning the agent's claude process. Without this, every agent inherits\n// the daemon's GITLAB_TOKEN regardless of whether their workspace has its\n// own .env.gitlab — defeating per-agent identity boundaries (each agent\n// should commit/push as itself, not as the daemon's account).\n\n/**\n * Parse a dotenv-style file. Tolerates the `export KEY=VALUE` form used by\n * shell-sourced files like .env.gitlab on peer-server.\n */\nfunction parseDotEnv(content: string): Array<[string, string]> {\n const out: Array<[string, string]> = []\n for (const raw of content.split(\"\\n\")) {\n const trimmed = raw.trim()\n if (!trimmed || trimmed.startsWith(\"#\")) continue\n const stripped = trimmed.startsWith(\"export \") ? trimmed.slice(7).trim() : trimmed\n const eq = stripped.indexOf(\"=\")\n if (eq === -1) continue\n const key = stripped.slice(0, eq).trim()\n let value = stripped.slice(eq + 1).trim()\n if (value.length >= 2 && ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\")))) {\n value = value.slice(1, -1)\n }\n if (key) out.push([key, value])\n }\n return out\n}\n\n/**\n * Read .env then .env.gitlab from a workspace and return their merged keys.\n * .env.gitlab wins on collision — it's the more specific per-channel file.\n */\nexport function loadWorkspaceEnv(workspace: string): Record<string, string> {\n const merged: Record<string, string> = {}\n for (const fname of [\".env\", \".env.gitlab\"]) {\n const path = resolve(workspace, fname)\n if (!existsSync(path)) continue\n try {\n for (const [k, v] of parseDotEnv(readFileSync(path, \"utf-8\"))) {\n merged[k] = v\n }\n } catch { /* best-effort */ }\n }\n return merged\n}\n\n/**\n * Keys defined in the daemon's own .env file, cached per daemonCwd. The\n * runtime uses this to strip those keys from the child env before adding\n * the workspace overrides — so a daemon-level GITLAB_TOKEN never leaks to\n * an agent that hasn't put one in its own workspace.\n */\nconst daemonEnvKeysCache = new Map<string, Set<string>>()\n\nexport function getDaemonEnvKeys(daemonCwd: string = process.cwd()): Set<string> {\n const cached = daemonEnvKeysCache.get(daemonCwd)\n if (cached) return cached\n const path = resolve(daemonCwd, \".env\")\n let keys: Set<string>\n if (!existsSync(path)) {\n keys = new Set()\n } else {\n try {\n keys = new Set(parseDotEnv(readFileSync(path, \"utf-8\")).map(([k]) => k))\n } catch {\n keys = new Set()\n }\n }\n daemonEnvKeysCache.set(daemonCwd, keys)\n return keys\n}\n\n/** Test-only: clear the cache so a different daemonCwd is re-read. */\nexport function _resetDaemonEnvKeysCache(): void {\n daemonEnvKeysCache.clear()\n}\n\n/**\n * Build the env object passed to a spawned agent process. Starts from\n * parentEnv (default process.env), removes any keys that came from the\n * daemon's own .env (so daemon-level secrets don't leak), then layers in\n * workspace .env / .env.gitlab.\n *\n * - System env (PATH, HOME, USER, …) survives untouched.\n * - Daemon-level secrets (e.g. GITLAB_TOKEN in /home/peer/agentx/.env) are\n * stripped UNLESS the workspace provides its own value for that key.\n * - Workspace env (per-agent .env / .env.gitlab) wins on the final merge.\n */\nexport function buildAgentEnv(\n workspace: string,\n parentEnv: NodeJS.ProcessEnv = process.env,\n daemonCwd: string = process.cwd(),\n): NodeJS.ProcessEnv {\n const workspaceEnv = loadWorkspaceEnv(workspace)\n const daemonOwnedKeys = getDaemonEnvKeys(daemonCwd)\n const child: NodeJS.ProcessEnv = { ...parentEnv }\n for (const k of daemonOwnedKeys) {\n if (!(k in workspaceEnv)) delete child[k]\n }\n for (const [k, v] of Object.entries(workspaceEnv)) {\n child[k] = v\n }\n return child\n}\n\n// Force the `claude` CLI to use OAuth/subscription billing. When\n// ANTHROPIC_API_KEY is present (env, systemd EnvironmentFile, daemon .env not\n// stripped because the workspace also defines it), the CLI silently switches\n// to the API-key path and any usage caps on that key surface as\n// \"You have reached your specified API usage limits...\". This caused\n// claude-code-tier agents to bypass the user's subscription. The same\n// stripping pattern lives in src/agent/providers/claude-code.ts for the\n// in-process provider — keep them in sync.\nexport function stripAnthropicApiKey(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n delete env.ANTHROPIC_API_KEY\n delete env.ANTHROPIC_API_KEY_OLD\n return env\n}\n\n/** `env` with the folders the fleet's CLIs install to added to PATH. A\n * process started by launchd, or the MCP server Claude Code starts, does\n * not get a login shell's PATH, and `claude` usually lives in\n * ~/.local/bin. */\nexport function withToolPath(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const home = env.HOME || process.env.HOME\n const parts = [\n env.PATH || \"\",\n home ? `${home}/.local/bin` : \"\",\n \"/opt/homebrew/bin\",\n \"/usr/local/bin\",\n ]\n env.PATH = Array.from(new Set(parts.flatMap((p) => p.split(\":\")).filter(Boolean))).join(\":\")\n return env\n}\n\n/** The env for a one-shot `claude -p` call: subscription billing and a\n * PATH that can find the CLI. */\nexport function claudeCliEnv(): NodeJS.ProcessEnv {\n return withToolPath(stripAnthropicApiKey({ ...process.env }))\n}\n\n/** How an agent's `claude` CLI is billed. \"subscription\" (the default) is\n * the OAuth login shared by the fleet. \"api\" bills ANTHROPIC_API_KEY\n * instead, for agents that must not draw on that shared quota, such as\n * benchmark runs. */\nexport type ClaudeBilling = \"subscription\" | \"api\"\n\n/** Shape a `claude` spawn env for the agent's billing. Each mode removes the\n * other's credential, because the CLI silently uses whichever it finds. An\n * \"api\" agent with no key throws rather than falling back to the\n * subscription. */\nexport function claudeBillingEnv(\n env: NodeJS.ProcessEnv,\n billing: ClaudeBilling = \"subscription\",\n): NodeJS.ProcessEnv {\n if (billing !== \"api\") return stripAnthropicApiKey(env)\n if (!env.ANTHROPIC_API_KEY) {\n throw new Error('billing \"api\" needs ANTHROPIC_API_KEY in the agent\\'s environment')\n }\n delete env.ANTHROPIC_API_KEY_OLD\n delete env.CLAUDE_CODE_OAUTH_TOKEN\n return env\n}\n\n/**\n * Load a dotenv file into process.env for the CURRENT process. Variables\n * already present win, so a systemd/launchd override always beats the file.\n *\n * `agentx daemon start` has always done this; `agentx board serve` did not,\n * so `${MESH_TOKEN}` in agentx.json expanded to \"\" inside the dashboard and\n * every proxied mesh call went out with an empty Bearer (401 from peers).\n */\nexport function loadEnvFileIntoProcess(path: string): void {\n if (!existsSync(path)) return\n for (const [key, value] of parseDotEnv(readFileSync(path, \"utf-8\"))) {\n if (process.env[key] === undefined) process.env[key] = value\n }\n}\n"],"mappings":"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","names":["readFileSync","existsSync","resolve","parseDotEnv","content","out","raw","trimmed","stripped","eq","key","value","loadWorkspaceEnv","workspace","merged","fname","path","k","v","daemonEnvKeysCache","getDaemonEnvKeys","daemonCwd","cached","keys","buildAgentEnv","workspace","parentEnv","daemonCwd","workspaceEnv","loadWorkspaceEnv","daemonOwnedKeys","getDaemonEnvKeys","child","k","v","stripAnthropicApiKey","env","withToolPath","home","parts","p","claudeCliEnv","claudeBillingEnv","billing","loadEnvFileIntoProcess","path","existsSync","key","value","parseDotEnv","readFileSync"]}
1
+ {"version":3,"sources":["../src/utils/workspace-env.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\"\nimport { resolve } from \"path\"\n\n// Loads per-agent secrets from the agent's workspace and tells the runtime\n// which keys the daemon's own .env injected, so we can strip those before\n// spawning the agent's claude process. Without this, every agent inherits\n// the daemon's GITLAB_TOKEN regardless of whether their workspace has its\n// own .env.gitlab — defeating per-agent identity boundaries (each agent\n// should commit/push as itself, not as the daemon's account).\n\n/**\n * Parse a dotenv-style file. Tolerates the `export KEY=VALUE` form used by\n * shell-sourced files like .env.gitlab on peer-server.\n */\nfunction parseDotEnv(content: string): Array<[string, string]> {\n const out: Array<[string, string]> = []\n for (const raw of content.split(\"\\n\")) {\n const trimmed = raw.trim()\n if (!trimmed || trimmed.startsWith(\"#\")) continue\n const stripped = trimmed.startsWith(\"export \") ? trimmed.slice(7).trim() : trimmed\n const eq = stripped.indexOf(\"=\")\n if (eq === -1) continue\n const key = stripped.slice(0, eq).trim()\n let value = stripped.slice(eq + 1).trim()\n if (value.length >= 2 && ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\")))) {\n value = value.slice(1, -1)\n }\n if (key) out.push([key, value])\n }\n return out\n}\n\n/**\n * Read .env then .env.gitlab from a workspace and return their merged keys.\n * .env.gitlab wins on collision — it's the more specific per-channel file.\n */\nexport function loadWorkspaceEnv(workspace: string): Record<string, string> {\n const merged: Record<string, string> = {}\n for (const fname of [\".env\", \".env.gitlab\"]) {\n const path = resolve(workspace, fname)\n if (!existsSync(path)) continue\n try {\n for (const [k, v] of parseDotEnv(readFileSync(path, \"utf-8\"))) {\n merged[k] = v\n }\n } catch { /* best-effort */ }\n }\n return merged\n}\n\n/**\n * Keys defined in the daemon's own .env file, cached per daemonCwd. The\n * runtime uses this to strip those keys from the child env before adding\n * the workspace overrides — so a daemon-level GITLAB_TOKEN never leaks to\n * an agent that hasn't put one in its own workspace.\n */\nconst daemonEnvKeysCache = new Map<string, Set<string>>()\n\nexport function getDaemonEnvKeys(daemonCwd: string = process.cwd()): Set<string> {\n const cached = daemonEnvKeysCache.get(daemonCwd)\n if (cached) return cached\n const path = resolve(daemonCwd, \".env\")\n let keys: Set<string>\n if (!existsSync(path)) {\n keys = new Set()\n } else {\n try {\n keys = new Set(parseDotEnv(readFileSync(path, \"utf-8\")).map(([k]) => k))\n } catch {\n keys = new Set()\n }\n }\n daemonEnvKeysCache.set(daemonCwd, keys)\n return keys\n}\n\n/** Test-only: clear the cache so a different daemonCwd is re-read. */\nexport function _resetDaemonEnvKeysCache(): void {\n daemonEnvKeysCache.clear()\n}\n\n/**\n * Build the env object passed to a spawned agent process. Starts from\n * parentEnv (default process.env), removes any keys that came from the\n * daemon's own .env (so daemon-level secrets don't leak), then layers in\n * workspace .env / .env.gitlab.\n *\n * - System env (PATH, HOME, USER, …) survives untouched.\n * - Daemon-level secrets (e.g. GITLAB_TOKEN in /home/peer/agentx/.env) are\n * stripped UNLESS the workspace provides its own value for that key.\n * - Workspace env (per-agent .env / .env.gitlab) wins on the final merge.\n */\nexport function buildAgentEnv(\n workspace: string,\n parentEnv: NodeJS.ProcessEnv = process.env,\n daemonCwd: string = process.cwd(),\n): NodeJS.ProcessEnv {\n const workspaceEnv = loadWorkspaceEnv(workspace)\n const daemonOwnedKeys = getDaemonEnvKeys(daemonCwd)\n const child: NodeJS.ProcessEnv = { ...parentEnv }\n for (const k of daemonOwnedKeys) {\n if (!(k in workspaceEnv)) delete child[k]\n }\n for (const [k, v] of Object.entries(workspaceEnv)) {\n child[k] = v\n }\n return child\n}\n\n// Force the `claude` CLI to use OAuth/subscription billing. When\n// ANTHROPIC_API_KEY is present (env, systemd EnvironmentFile, daemon .env not\n// stripped because the workspace also defines it), the CLI silently switches\n// to the API-key path and any usage caps on that key surface as\n// \"You have reached your specified API usage limits...\". This caused\n// claude-code-tier agents to bypass the user's subscription. The same\n// stripping pattern lives in src/agent/providers/claude-code.ts for the\n// in-process provider — keep them in sync.\nexport function stripAnthropicApiKey(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n delete env.ANTHROPIC_API_KEY\n delete env.ANTHROPIC_API_KEY_OLD\n return env\n}\n\n/** `env` with the folders the fleet's CLIs install to added to PATH. A\n * process started by launchd, or the MCP server Claude Code starts, does\n * not get a login shell's PATH, and `claude` usually lives in\n * ~/.local/bin. */\nexport function withToolPath(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {\n const home = env.HOME || process.env.HOME\n const parts = [\n env.PATH || \"\",\n home ? `${home}/.local/bin` : \"\",\n \"/opt/homebrew/bin\",\n \"/usr/local/bin\",\n ]\n env.PATH = Array.from(new Set(parts.flatMap((p) => p.split(\":\")).filter(Boolean))).join(\":\")\n return env\n}\n\n/** The env for a one-shot `claude -p` call: subscription billing, a\n * PATH that can find the CLI, and no CLAUDECODE marker. Inside an\n * agent's session (the MCP server, an agent's own shell) the marker is\n * inherited and the CLI refuses to start a nested session, which is how\n * `agentx_wiki_patch` and `agentx_wiki_interview` failed at `claude -p`\n * (#824). session-monitor and talk-model drop it for the same reason. */\nexport function claudeCliEnv(): NodeJS.ProcessEnv {\n const env = withToolPath(stripAnthropicApiKey({ ...process.env }))\n delete env.CLAUDECODE\n return env\n}\n\n/** How an agent's `claude` CLI is billed. \"subscription\" (the default) is\n * the OAuth login shared by the fleet. \"api\" bills ANTHROPIC_API_KEY\n * instead, for agents that must not draw on that shared quota, such as\n * benchmark runs. */\nexport type ClaudeBilling = \"subscription\" | \"api\"\n\n/** Shape a `claude` spawn env for the agent's billing. Each mode removes the\n * other's credential, because the CLI silently uses whichever it finds. An\n * \"api\" agent with no key throws rather than falling back to the\n * subscription. */\nexport function claudeBillingEnv(\n env: NodeJS.ProcessEnv,\n billing: ClaudeBilling = \"subscription\",\n): NodeJS.ProcessEnv {\n if (billing !== \"api\") return stripAnthropicApiKey(env)\n if (!env.ANTHROPIC_API_KEY) {\n throw new Error('billing \"api\" needs ANTHROPIC_API_KEY in the agent\\'s environment')\n }\n delete env.ANTHROPIC_API_KEY_OLD\n delete env.CLAUDE_CODE_OAUTH_TOKEN\n return env\n}\n\n/**\n * Load a dotenv file into process.env for the CURRENT process. Variables\n * already present win, so a systemd/launchd override always beats the file.\n *\n * `agentx daemon start` has always done this; `agentx board serve` did not,\n * so `${MESH_TOKEN}` in agentx.json expanded to \"\" inside the dashboard and\n * every proxied mesh call went out with an empty Bearer (401 from peers).\n */\nexport function loadEnvFileIntoProcess(path: string): void {\n if (!existsSync(path)) return\n for (const [key, value] of parseDotEnv(readFileSync(path, \"utf-8\"))) {\n if (process.env[key] === undefined) process.env[key] = value\n }\n}\n"],"mappings":"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","names":["readFileSync","existsSync","resolve","parseDotEnv","content","out","raw","trimmed","stripped","eq","key","value","loadWorkspaceEnv","workspace","merged","fname","path","k","v","daemonEnvKeysCache","getDaemonEnvKeys","daemonCwd","cached","keys","buildAgentEnv","workspace","parentEnv","daemonCwd","workspaceEnv","loadWorkspaceEnv","daemonOwnedKeys","getDaemonEnvKeys","child","k","v","stripAnthropicApiKey","env","withToolPath","home","parts","p","claudeCliEnv","claudeBillingEnv","billing","loadEnvFileIntoProcess","path","existsSync","key","value","parseDotEnv","readFileSync"]}
@@ -1,2 +1,2 @@
1
- import{q as d}from"./chunk-COWAAE54.js";import{b as i,c as u,d as l,e as c}from"./chunk-PH43FUU7.js";import f from"cronstrue";var p=Intl.DateTimeFormat().resolvedOptions().timeZone,m=600,E='Try: "every morning at 9", "weekdays at 6pm", "every 15 minutes", "every monday at 10am", "1st of every month at noon", "daily at 9:30am", "every hour"',g=/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;function w(e){return g.test(e)}function x(e){try{return new Intl.DateTimeFormat("en-US",{timeZone:e}),!0}catch{return!1}}function $(e){return e?e.split(",").map(r=>r.trim().toLowerCase()).filter(r=>r==="log"||r==="notify"||r==="disable"):["log"]}function S(e){let r=[...e.onError??["log"]];e.notify&&!r.includes("notify")&&r.push("notify");let t={enabled:e.enabled??!0,schedule:e.parsed.cron,timezone:e.timezone||p,agent:e.agent,prompt:e.prompt,timeout:e.timeout??m,onError:r};return e.model&&(t.model=e.model),e.notify&&(t.notify=e.notify),e.notify&&e.deliverResult===!1&&(t.deliverResult=!1),e.createdBy&&(t.createdBy=e.createdBy),e.approval&&(t.approval=e.approval),t}function P(e){try{return f.toString(e,{use24HourTimeFormat:!1})}catch{return e}}function T(e,r,t=new Date){try{return d(e,t,r)}catch{return null}}function A(e,r){if(!e)return"unknown (no match within a year)";try{return`${new Intl.DateTimeFormat("en-GB",{timeZone:r,weekday:"short",day:"2-digit",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1}).format(e)} (${r})`}catch{return e.toISOString()}}function h(e){return e?.approval?.action==="create"}async function D(e,r={}){let t="",o=await i(n=>{let a=l(n,`crons.${e}`);if(!a)throw new Error(`cron "${e}" not found`);let s=a.approval;if(!s)throw new Error(`cron "${e}" has no pending request`);s.action==="create"?(delete a.approval,a.enabled=!0,t=`${e} approved and enabled (requested by ${s.requestedBy})`):(c(n,`crons.${e}`),t=`${e} deletion approved; job removed (requested by ${s.requestedBy})`)},{configPath:r.configPath,reload:r.reload});return o.success?{success:!0,message:t,result:o}:{success:!1,message:o.error||"failed",result:o}}async function I(e,r={}){let t="",o=await i(n=>{let a=l(n,`crons.${e}`);if(!a)throw new Error(`cron "${e}" not found`);let s=a.approval;if(!s)throw new Error(`cron "${e}" has no pending request`);s.action==="create"?(c(n,`crons.${e}`),t=`${e} rejected; pending job removed`):(delete a.approval,t=`${e} deletion rejected; job kept`)},{configPath:r.configPath,reload:r.reload});return o.success?{success:!0,message:t,result:o}:{success:!1,message:o.error||"failed",result:o}}async function O(e,r,t={}){return i(o=>{let n=l(o,`crons.${e}`);if(!n)throw new Error(`cron "${e}" not found`);if(r&&h(n))throw new Error(`cron "${e}" is awaiting approval; run \`agentx schedule approve ${e}\``);u(o,`crons.${e}.enabled`,r)},{configPath:t.configPath,reload:t.reload})}export{p as a,E as b,w as c,x as d,$ as e,S as f,P as g,T as h,A as i,h as j,D as k,I as l,O as m};
2
- //# sourceMappingURL=chunk-XYT6XY7G.js.map
1
+ import{q as d}from"./chunk-CPMQZ6Z7.js";import{b as i,c as u,d as l,e as c}from"./chunk-BWSB7NOX.js";import f from"cronstrue";var p=Intl.DateTimeFormat().resolvedOptions().timeZone,m=600,E='Try: "every morning at 9", "weekdays at 6pm", "every 15 minutes", "every monday at 10am", "1st of every month at noon", "daily at 9:30am", "every hour"',g=/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;function w(e){return g.test(e)}function x(e){try{return new Intl.DateTimeFormat("en-US",{timeZone:e}),!0}catch{return!1}}function $(e){return e?e.split(",").map(r=>r.trim().toLowerCase()).filter(r=>r==="log"||r==="notify"||r==="disable"):["log"]}function S(e){let r=[...e.onError??["log"]];e.notify&&!r.includes("notify")&&r.push("notify");let t={enabled:e.enabled??!0,schedule:e.parsed.cron,timezone:e.timezone||p,agent:e.agent,prompt:e.prompt,timeout:e.timeout??m,onError:r};return e.model&&(t.model=e.model),e.notify&&(t.notify=e.notify),e.notify&&e.deliverResult===!1&&(t.deliverResult=!1),e.createdBy&&(t.createdBy=e.createdBy),e.approval&&(t.approval=e.approval),t}function P(e){try{return f.toString(e,{use24HourTimeFormat:!1})}catch{return e}}function T(e,r,t=new Date){try{return d(e,t,r)}catch{return null}}function A(e,r){if(!e)return"unknown (no match within a year)";try{return`${new Intl.DateTimeFormat("en-GB",{timeZone:r,weekday:"short",day:"2-digit",month:"short",year:"numeric",hour:"2-digit",minute:"2-digit",hour12:!1}).format(e)} (${r})`}catch{return e.toISOString()}}function h(e){return e?.approval?.action==="create"}async function D(e,r={}){let t="",o=await i(n=>{let a=l(n,`crons.${e}`);if(!a)throw new Error(`cron "${e}" not found`);let s=a.approval;if(!s)throw new Error(`cron "${e}" has no pending request`);s.action==="create"?(delete a.approval,a.enabled=!0,t=`${e} approved and enabled (requested by ${s.requestedBy})`):(c(n,`crons.${e}`),t=`${e} deletion approved; job removed (requested by ${s.requestedBy})`)},{configPath:r.configPath,reload:r.reload});return o.success?{success:!0,message:t,result:o}:{success:!1,message:o.error||"failed",result:o}}async function I(e,r={}){let t="",o=await i(n=>{let a=l(n,`crons.${e}`);if(!a)throw new Error(`cron "${e}" not found`);let s=a.approval;if(!s)throw new Error(`cron "${e}" has no pending request`);s.action==="create"?(c(n,`crons.${e}`),t=`${e} rejected; pending job removed`):(delete a.approval,t=`${e} deletion rejected; job kept`)},{configPath:r.configPath,reload:r.reload});return o.success?{success:!0,message:t,result:o}:{success:!1,message:o.error||"failed",result:o}}async function O(e,r,t={}){return i(o=>{let n=l(o,`crons.${e}`);if(!n)throw new Error(`cron "${e}" not found`);if(r&&h(n))throw new Error(`cron "${e}" is awaiting approval; run \`agentx schedule approve ${e}\``);u(o,`crons.${e}.enabled`,r)},{configPath:t.configPath,reload:t.reload})}export{p as a,E as b,w as c,x as d,$ as e,S as f,P as g,T as h,A as i,h as j,D as k,I as l,O as m};
2
+ //# sourceMappingURL=chunk-VJRITDSQ.js.map
@@ -0,0 +1,6 @@
1
+ import{d as h}from"./chunk-PVUNDNT3.js";var d=["main contact","primary contact","point of contact","billing contact","technical contact","decision maker"],f=/[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g,w=/\b(role|title|position|job)\s*\**\s*[::]\s*\**\s*([^\n;|]+)/gi;function L(t,c,e=[]){let l=[c,...e].join(`
2
+ `),a=l.toLowerCase(),i=h(t,l);for(let s of p(t.match(f)??[]))a.includes(s.toLowerCase())||i.push(`email ${s}`);let m=t.toLowerCase();for(let s of d)m.includes(s)&&!a.includes(s)&&i.push(`marker "${s}"`);for(let s of t.matchAll(w)){let n=s[2].replace(/\*+/g,"").trim();n&&!a.includes(n.toLowerCase())&&i.push(`${s[1].toLowerCase()} "${n}"`)}return p(i)}function p(t){return[...new Set(t)]}var $=.9,g=/^\s*(?:wait\b|here(?:'s| is) the (?:updated|modified|patched|revised)|i (?:must|need to|will|should) (?:output|return|write)|(?:okay|ok|sure)[,.!]\s|let me\b|as requested\b)/im;function k(t,c){let e=[],l=t.split(`
3
+ `).filter(o=>o.trim()).length,a=c.split(`
4
+ `).filter(o=>o.trim()).length;l>=5&&a<Math.floor(l*$)&&e.push(`the article shrinks from ${l} to ${a} lines`);let i=c.match(g);i&&!t.match(g)&&e.push(`it contains the model's own commentary ("${i[0].trim().slice(0,40)}")`);let m=o=>{let r=new Map;for(let u of o.split(`
5
+ `))/^#{1,6} /.test(u)&&r.set(u.trim(),(r.get(u.trim())??0)+1);return r},s=m(t);for(let[o,r]of m(c))r>1&&r>(s.get(o)??0)&&e.push(`the heading "${o}" appears ${r} times`);let n=L(t,c);return n.length&&e.push(`it drops ${n.length} fact(s): ${n.slice(0,6).join(", ")}${n.length>6?", \u2026":""}`),e}export{L as a,k as b};
6
+ //# sourceMappingURL=chunk-X5JTG52Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/wiki/fact-guard.ts"],"sourcesContent":["import { droppedFacts } from \"./absorb-context\"\n\n// --- Fact-loss guard for edits to an existing article (#824) ---\n//\n// A fleet test found page rewrites had dropped a contact's phone number\n// and their \"main contact\" role. `droppedFacts` already guards absorb\n// against losing commits, links and numbers; this adds the structured\n// facts a person page carries in words: email addresses, roles and\n// contact markers. Every edit path that changes an existing article\n// (`wiki patch`, the daily contribution merge) runs it and holds the\n// change instead of saving it silently.\n\n/** Phrases that mark a person's standing; losing one loses a fact. */\nconst CONTACT_MARKERS = [\n \"main contact\",\n \"primary contact\",\n \"point of contact\",\n \"billing contact\",\n \"technical contact\",\n \"decision maker\",\n]\n\nconst EMAIL_RE = /[\\w.+-]+@[\\w-]+(?:\\.[\\w-]+)+/g\nconst ROLE_RE = /\\b(role|title|position|job)\\s*\\**\\s*[::]\\s*\\**\\s*([^\\n;|]+)/gi\n\n/**\n * Structured facts `before` carries that `after` no longer does.\n *\n * `superseded` lists values the edit replaces on purpose (a checked\n * correction keeps the old value in the fact ledger's history and the\n * page's version history), so they don't count as lost.\n */\nexport function lostStructuredFacts(before: string, after: string, superseded: string[] = []): string[] {\n const kept = [after, ...superseded].join(\"\\n\")\n const keptLower = kept.toLowerCase()\n const lost = droppedFacts(before, kept)\n\n for (const email of unique(before.match(EMAIL_RE) ?? [])) {\n if (!keptLower.includes(email.toLowerCase())) lost.push(`email ${email}`)\n }\n const beforeLower = before.toLowerCase()\n for (const marker of CONTACT_MARKERS) {\n if (beforeLower.includes(marker) && !keptLower.includes(marker)) lost.push(`marker \"${marker}\"`)\n }\n for (const m of before.matchAll(ROLE_RE)) {\n const role = m[2].replace(/\\*+/g, \"\").trim()\n if (role && !keptLower.includes(role.toLowerCase())) lost.push(`${m[1].toLowerCase()} \"${role}\"`)\n }\n return unique(lost)\n}\n\nfunction unique(list: string[]): string[] {\n return [...new Set(list)]\n}\n\n/** Below this share of the old line count, an edit is a rewrite. */\nconst MIN_KEPT_LINES = 0.9\n\n/** The model talking about its task instead of writing the article. */\nconst CHATTER_RE = /^\\s*(?:wait\\b|here(?:'s| is) the (?:updated|modified|patched|revised)|i (?:must|need to|will|should) (?:output|return|write)|(?:okay|ok|sure)[,.!]\\s|let me\\b|as requested\\b)/im\n\n/**\n * Why an LLM-applied patch to an existing article must not be saved\n * as it is. Empty means it looks like the small edit it was asked for.\n *\n * The fleet test behind #824 found `wiki patch` doing four things a\n * patch must not: replacing the whole body with just the new section\n * (33 → 4 lines), pasting its own reasoning into the page (\"Wait — I\n * must output the full article body\"), duplicating header lines, and\n * dropping facts. Each is checked here.\n */\nexport function patchProblems(before: string, after: string): string[] {\n const problems: string[] = []\n const oldLines = before.split(\"\\n\").filter((l) => l.trim()).length\n const newLines = after.split(\"\\n\").filter((l) => l.trim()).length\n if (oldLines >= 5 && newLines < Math.floor(oldLines * MIN_KEPT_LINES)) {\n problems.push(`the article shrinks from ${oldLines} to ${newLines} lines`)\n }\n const chatter = after.match(CHATTER_RE)\n if (chatter && !before.match(CHATTER_RE)) problems.push(`it contains the model's own commentary (\"${chatter[0].trim().slice(0, 40)}\")`)\n const headings = (text: string) => {\n const n = new Map<string, number>()\n for (const l of text.split(\"\\n\")) if (/^#{1,6} /.test(l)) n.set(l.trim(), (n.get(l.trim()) ?? 0) + 1)\n return n\n }\n const was = headings(before)\n for (const [h, count] of headings(after)) {\n if (count > 1 && count > (was.get(h) ?? 0)) problems.push(`the heading \"${h}\" appears ${count} times`)\n }\n const lost = lostStructuredFacts(before, after)\n if (lost.length) problems.push(`it drops ${lost.length} fact(s): ${lost.slice(0, 6).join(\", \")}${lost.length > 6 ? \", …\" : \"\"}`)\n return problems\n}\n"],"mappings":"wCAaA,IAAMA,EAAkB,CACtB,eACA,kBACA,mBACA,kBACA,oBACA,gBACF,EAEMC,EAAW,gCACXC,EAAU,gEAST,SAASC,EAAoBC,EAAgBC,EAAeC,EAAuB,CAAC,EAAa,CACtG,IAAMC,EAAO,CAACF,EAAO,GAAGC,CAAU,EAAE,KAAK;AAAA,CAAI,EACvCE,EAAYD,EAAK,YAAY,EAC7BE,EAAOC,EAAaN,EAAQG,CAAI,EAEtC,QAAWI,KAASC,EAAOR,EAAO,MAAMH,CAAQ,GAAK,CAAC,CAAC,EAChDO,EAAU,SAASG,EAAM,YAAY,CAAC,GAAGF,EAAK,KAAK,SAASE,GAAO,EAE1E,IAAME,EAAcT,EAAO,YAAY,EACvC,QAAWU,KAAUd,EACfa,EAAY,SAASC,CAAM,GAAK,CAACN,EAAU,SAASM,CAAM,GAAGL,EAAK,KAAK,WAAWK,IAAS,EAEjG,QAAWC,KAAKX,EAAO,SAASF,CAAO,EAAG,CACxC,IAAMc,EAAOD,EAAE,CAAC,EAAE,QAAQ,OAAQ,EAAE,EAAE,KAAK,EACvCC,GAAQ,CAACR,EAAU,SAASQ,EAAK,YAAY,CAAC,GAAGP,EAAK,KAAK,GAAGM,EAAE,CAAC,EAAE,YAAY,MAAMC,IAAO,EAElG,OAAOJ,EAAOH,CAAI,CACpB,CAEA,SAASG,EAAOK,EAA0B,CACxC,MAAO,CAAC,GAAG,IAAI,IAAIA,CAAI,CAAC,CAC1B,CAGA,IAAMC,EAAiB,GAGjBC,EAAa,kLAYZ,SAASC,EAAchB,EAAgBC,EAAyB,CACrE,IAAMgB,EAAqB,CAAC,EACtBC,EAAWlB,EAAO,MAAM;AAAA,CAAI,EAAE,OAAQmB,GAAMA,EAAE,KAAK,CAAC,EAAE,OACtDC,EAAWnB,EAAM,MAAM;AAAA,CAAI,EAAE,OAAQkB,GAAMA,EAAE,KAAK,CAAC,EAAE,OACvDD,GAAY,GAAKE,EAAW,KAAK,MAAMF,EAAWJ,CAAc,GAClEG,EAAS,KAAK,4BAA4BC,QAAeE,SAAgB,EAE3E,IAAMC,EAAUpB,EAAM,MAAMc,CAAU,EAClCM,GAAW,CAACrB,EAAO,MAAMe,CAAU,GAAGE,EAAS,KAAK,4CAA4CI,EAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,EAAG,EAAE,KAAK,EACtI,IAAMC,EAAYC,GAAiB,CACjC,IAAMC,EAAI,IAAI,IACd,QAAWL,KAAKI,EAAK,MAAM;AAAA,CAAI,EAAO,WAAW,KAAKJ,CAAC,GAAGK,EAAE,IAAIL,EAAE,KAAK,GAAIK,EAAE,IAAIL,EAAE,KAAK,CAAC,GAAK,GAAK,CAAC,EACpG,OAAOK,CACT,EACMC,EAAMH,EAAStB,CAAM,EAC3B,OAAW,CAAC0B,EAAGC,CAAK,IAAKL,EAASrB,CAAK,EACjC0B,EAAQ,GAAKA,GAASF,EAAI,IAAIC,CAAC,GAAK,IAAIT,EAAS,KAAK,gBAAgBS,cAAcC,SAAa,EAEvG,IAAMtB,EAAON,EAAoBC,EAAQC,CAAK,EAC9C,OAAII,EAAK,QAAQY,EAAS,KAAK,YAAYZ,EAAK,mBAAmBA,EAAK,MAAM,EAAG,CAAC,EAAE,KAAK,IAAI,IAAIA,EAAK,OAAS,EAAI,WAAQ,IAAI,EACxHY,CACT","names":["CONTACT_MARKERS","EMAIL_RE","ROLE_RE","lostStructuredFacts","before","after","superseded","kept","keptLower","lost","droppedFacts","email","unique","beforeLower","marker","m","role","list","MIN_KEPT_LINES","CHATTER_RE","patchProblems","problems","oldLines","l","newLines","chatter","headings","text","n","was","h","count"]}
@@ -1,6 +1,6 @@
1
- import{a as T,b as E,c as Y,d as j,e as G}from"./chunk-WPNRDEHW.js";import{v as R,y as O,z as U}from"./chunk-3KYZOC7N.js";import{a as V,b as C,f as B}from"./chunk-72SZI2UW.js";import{a as W,b as se}from"./chunk-MCI3DIWW.js";import{o as z,p as re}from"./chunk-2PJKGZWP.js";B();var xe="wiki-rerank",Se=30,ne=900;function Ne(a){let e=a.excerptChars??ne;return{query:a.query,candidates:a.candidates.map(t=>({id:t.id,title:t.title,tags:t.tags?.length?t.tags.join(", "):null,excerpt:ie(t.excerpt,e)}))}}function ke(a){let e={};for(let t of a)e[t.id]=t.title;return{best:C(e,"Which candidate answers the query?"),answerable:V("Do any of the candidates answer the query?")}}function Ie(a,e){let t=a.best,r={};for(let i of e)r[i.id]=t.probabilities[i.id]??0;let n=new Map(e.map((i,s)=>[i.id,s]));return{ranked:[...e.map(i=>i.id)].sort((i,s)=>{let c=r[s]-r[i];return c!==0?c:(n.get(i)??0)-(n.get(s)??0)}),scores:r,answerable:a.answerable.noul,confidence:t.confidence}}function ie(a,e){let t=String(a??"").replace(/\s+/g," ").trim();return t.length>e?t.slice(0,e):t}var x={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},$={code:"Work on source code: review a PR or MR, fix a bug, build a feature, refactor, write a spec, or answer a question about code. Includes changes to AgentX's own code.",ops:"Running systems: deploy, roll back, monitor, scale, restart a service, or handle a production incident.",support:"A person talking to an agent: a greeting, casual chat, thanks, a question to answer, a request for help, a follow-up or complaint about an earlier request, or routing a request to someone else. The default for conversation that is not itself one of the other kinds of work.",admin:"Changing how the agents are set up: configuration, schedules, tokens, roles, adding an agent or a channel. Not a question about the setup or a complaint that something did not run.",knowledge:"Writing or gathering knowledge to keep: documentation, wiki articles, research, summarising a thread.",social:"Content made for an audience other than the agent: posts, announcements, daily briefs, weekly reports. Not a conversation with the agent.",system:"Internal sub-calls made by the platform itself, such as the intent classifier. A message from a person is almost never this."},M=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as I,writeFileSync as _,appendFileSync as oe,existsSync as S,mkdirSync as D,renameSync as ae,statSync as Q}from"fs";import{resolve as A,dirname as Z}from"path";import{createHash as ce}from"crypto";var F=class{baseDir;log;parsed=new Map;constructor(e={}){this.baseDir=e.baseDir??A(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),D(this.baseDir,{recursive:!0})}schemaPath(){return A(this.baseDir,"schema.json")}nodesPath(){return A(this.baseDir,"nodes.json")}indexPath(){return A(this.baseDir,"index.json")}classificationsPath(){return A(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!S(e))return this.saveSchema(x),this.log("seeded starter schema at",e),x;let t=I(e,"utf-8"),r=T.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);if((r.data.version??1)<(x.version??2)){let n=e.replace(/\.json$/,`.v${r.data.version??1}.bak.json`);try{_(n,t),this.log(`migrated graph schema v${r.data.version??1} -> v${x.version}; old saved at ${n}`)}catch(i){this.log(`could not back up old schema: ${i?.message??i}`)}this.saveSchema(x);let o=this.nodesPath();if(S(o)){let i=o.replace(/\.json$/,".v1.bak.json");try{_(i,I(o,"utf-8")),this.log(`archived v1 nodes.json -> ${i}`)}catch(s){this.log(`could not back up old nodes: ${s?.message??s}`)}try{let s=K();this.saveNodes(s)}catch(s){this.log(`could not seed v2 nodes: ${s?.message??s}`)}}return x}return r.data}saveSchema(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(r=>r.message).join("; ")}`);X(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!S(e)){let t=K();return this.saveNodes(t),t}return structuredClone(this.readParsed(e,t=>{let r=j.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}))}saveNodes(e){let t=j.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.nodesPath(),t.data)}readParsed(e,t){let{mtimeMs:r,size:n}=Q(e),o=this.parsed.get(e);if(o&&o.mtimeMs===r&&o.size===n)return o.data;let i=t(I(e,"utf-8"));return this.parsed.set(e,{mtimeMs:r,size:n,data:i}),i}writeParsed(e,t){this.parsed.delete(e),X(e,t);let{mtimeMs:r,size:n}=Q(e);this.parsed.set(e,{mtimeMs:r,size:n,data:t})}validateNode(e,t,r){let n=t.levels.find(s=>s.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(s=>!s.optional).filter(s=>!(s.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(s=>s.name).join(", ")}`);for(let s of n.axes){let c=e.axes[s.name];if(c!=null){if(s.type==="enum"&&s.values&&!s.values.includes(c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" not in enum ${JSON.stringify(s.values)}`);if(s.type==="ref"){if(!r.some(l=>l.id===c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" refers to unknown node`);if(s.refLevel){let l=r.find(h=>h.id===c);if(l.level!==s.refLevel)throw new Error(`Node ${e.id}: axis ${s.name} expected level "${s.refLevel}" but got "${l.level}"`)}}}}let i=t.levels.findIndex(s=>s.id===e.level);if(i===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let s=r.find(l=>l.id===e.parentId);if(!s)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let c=t.levels.findIndex(l=>l.id===s.level);if(c<0||c>=i)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${s.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,r,n){let o=[],i=[],s=new Date().toISOString();for(let c=0;c<e.length;c++){let l=e[c],h=this.loadNodes().nodes;if(h.find(p=>p.id===l)){i.push(l);continue}let u=0;for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(w=>w.id===y)||o.find(w=>w.id===y);if(N){u=r.levels.findIndex(w=>w.id===N.level)+1;break}}let m=t[l]??{},d=null;for(let p=u;p<r.levels.length;p++){let y=r.levels[p];if(y.axes.filter(w=>!w.optional).filter(w=>!(w.name in m)).length===0){d=y.id,u=p+1;break}}if(!d&&u<r.levels.length&&(d=r.levels[u].id,u++),!d)throw new Error(`Can't place node ${l} \u2014 no schema level left (cursor=${u}, levels=${r.levels.length})`);let f=null,v=r.levels.findIndex(p=>p.id===d);for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(k=>k.id===y)||o.find(k=>k.id===y);if(!N)continue;let w=r.levels.findIndex(k=>k.id===N.level);if(w>=0&&w<v){f=y;break}}let g={id:l,level:d,parentId:f,axes:m,createdAt:s,createdBy:n};this.addNode(g),o.push(g)}return{added:o,skipped:i}}addNode(e){let t=this.loadSchema(),r=this.loadNodes();if(r.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...r.nodes,e]),r.nodes.push(e),this.saveNodes(r),e}appendClassification(e){let t=E.safeParse(e);if(!t.success){let o=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let r=JSON.stringify(t.data)+`
1
+ import{a as T,b as E,c as Y,d as j,e as G}from"./chunk-WPNRDEHW.js";import{v as R,y as O,z as U}from"./chunk-R6UCPHUD.js";import{a as V,b as C,f as B}from"./chunk-72SZI2UW.js";import{a as W,b as se}from"./chunk-MCI3DIWW.js";import{o as z,p as re}from"./chunk-2PJKGZWP.js";B();var xe="wiki-rerank",Se=30,ne=900;function Ne(a){let e=a.excerptChars??ne;return{query:a.query,candidates:a.candidates.map(t=>({id:t.id,title:t.title,tags:t.tags?.length?t.tags.join(", "):null,excerpt:ie(t.excerpt,e)}))}}function ke(a){let e={};for(let t of a)e[t.id]=t.title;return{best:C(e,"Which candidate answers the query?"),answerable:V("Do any of the candidates answer the query?")}}function Ie(a,e){let t=a.best,r={};for(let i of e)r[i.id]=t.probabilities[i.id]??0;let n=new Map(e.map((i,s)=>[i.id,s]));return{ranked:[...e.map(i=>i.id)].sort((i,s)=>{let c=r[s]-r[i];return c!==0?c:(n.get(i)??0)-(n.get(s)??0)}),scores:r,answerable:a.answerable.noul,confidence:t.confidence}}function ie(a,e){let t=String(a??"").replace(/\s+/g," ").trim();return t.length>e?t.slice(0,e):t}var x={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},$={code:"Work on source code: review a PR or MR, fix a bug, build a feature, refactor, write a spec, or answer a question about code. Includes changes to AgentX's own code.",ops:"Running systems: deploy, roll back, monitor, scale, restart a service, or handle a production incident.",support:"A person talking to an agent: a greeting, casual chat, thanks, a question to answer, a request for help, a follow-up or complaint about an earlier request, or routing a request to someone else. The default for conversation that is not itself one of the other kinds of work.",admin:"Changing how the agents are set up: configuration, schedules, tokens, roles, adding an agent or a channel. Not a question about the setup or a complaint that something did not run.",knowledge:"Writing or gathering knowledge to keep: documentation, wiki articles, research, summarising a thread.",social:"Content made for an audience other than the agent: posts, announcements, daily briefs, weekly reports. Not a conversation with the agent.",system:"Internal sub-calls made by the platform itself, such as the intent classifier. A message from a person is almost never this."},M=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as I,writeFileSync as _,appendFileSync as oe,existsSync as S,mkdirSync as D,renameSync as ae,statSync as Q}from"fs";import{resolve as A,dirname as Z}from"path";import{createHash as ce}from"crypto";var F=class{baseDir;log;parsed=new Map;constructor(e={}){this.baseDir=e.baseDir??A(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),D(this.baseDir,{recursive:!0})}schemaPath(){return A(this.baseDir,"schema.json")}nodesPath(){return A(this.baseDir,"nodes.json")}indexPath(){return A(this.baseDir,"index.json")}classificationsPath(){return A(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!S(e))return this.saveSchema(x),this.log("seeded starter schema at",e),x;let t=I(e,"utf-8"),r=T.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);if((r.data.version??1)<(x.version??2)){let n=e.replace(/\.json$/,`.v${r.data.version??1}.bak.json`);try{_(n,t),this.log(`migrated graph schema v${r.data.version??1} -> v${x.version}; old saved at ${n}`)}catch(i){this.log(`could not back up old schema: ${i?.message??i}`)}this.saveSchema(x);let o=this.nodesPath();if(S(o)){let i=o.replace(/\.json$/,".v1.bak.json");try{_(i,I(o,"utf-8")),this.log(`archived v1 nodes.json -> ${i}`)}catch(s){this.log(`could not back up old nodes: ${s?.message??s}`)}try{let s=K();this.saveNodes(s)}catch(s){this.log(`could not seed v2 nodes: ${s?.message??s}`)}}return x}return r.data}saveSchema(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(r=>r.message).join("; ")}`);X(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!S(e)){let t=K();return this.saveNodes(t),t}return structuredClone(this.readParsed(e,t=>{let r=j.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}))}saveNodes(e){let t=j.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.nodesPath(),t.data)}readParsed(e,t){let{mtimeMs:r,size:n}=Q(e),o=this.parsed.get(e);if(o&&o.mtimeMs===r&&o.size===n)return o.data;let i=t(I(e,"utf-8"));return this.parsed.set(e,{mtimeMs:r,size:n,data:i}),i}writeParsed(e,t){this.parsed.delete(e),X(e,t);let{mtimeMs:r,size:n}=Q(e);this.parsed.set(e,{mtimeMs:r,size:n,data:t})}validateNode(e,t,r){let n=t.levels.find(s=>s.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(s=>!s.optional).filter(s=>!(s.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(s=>s.name).join(", ")}`);for(let s of n.axes){let c=e.axes[s.name];if(c!=null){if(s.type==="enum"&&s.values&&!s.values.includes(c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" not in enum ${JSON.stringify(s.values)}`);if(s.type==="ref"){if(!r.some(l=>l.id===c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" refers to unknown node`);if(s.refLevel){let l=r.find(h=>h.id===c);if(l.level!==s.refLevel)throw new Error(`Node ${e.id}: axis ${s.name} expected level "${s.refLevel}" but got "${l.level}"`)}}}}let i=t.levels.findIndex(s=>s.id===e.level);if(i===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let s=r.find(l=>l.id===e.parentId);if(!s)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let c=t.levels.findIndex(l=>l.id===s.level);if(c<0||c>=i)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${s.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,r,n){let o=[],i=[],s=new Date().toISOString();for(let c=0;c<e.length;c++){let l=e[c],h=this.loadNodes().nodes;if(h.find(p=>p.id===l)){i.push(l);continue}let u=0;for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(w=>w.id===y)||o.find(w=>w.id===y);if(N){u=r.levels.findIndex(w=>w.id===N.level)+1;break}}let m=t[l]??{},d=null;for(let p=u;p<r.levels.length;p++){let y=r.levels[p];if(y.axes.filter(w=>!w.optional).filter(w=>!(w.name in m)).length===0){d=y.id,u=p+1;break}}if(!d&&u<r.levels.length&&(d=r.levels[u].id,u++),!d)throw new Error(`Can't place node ${l} \u2014 no schema level left (cursor=${u}, levels=${r.levels.length})`);let f=null,v=r.levels.findIndex(p=>p.id===d);for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(k=>k.id===y)||o.find(k=>k.id===y);if(!N)continue;let w=r.levels.findIndex(k=>k.id===N.level);if(w>=0&&w<v){f=y;break}}let g={id:l,level:d,parentId:f,axes:m,createdAt:s,createdBy:n};this.addNode(g),o.push(g)}return{added:o,skipped:i}}addNode(e){let t=this.loadSchema(),r=this.loadNodes();if(r.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...r.nodes,e]),r.nodes.push(e),this.saveNodes(r),e}appendClassification(e){let t=E.safeParse(e);if(!t.success){let o=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let r=JSON.stringify(t.data)+`
2
2
  `,n=this.classificationsPath();return S(n)||D(Z(n),{recursive:!0}),oe(n,r),!0}listPendingClassifications(e=200){return this.listByStatus("pending",e)}listAllClassifications(){return this.readAllCollapsed()}listByStatus(e,t=200){return this.readAllCollapsed().filter(n=>n.status===e).slice(0,t)}readAllCollapsed(){let e=this.classificationsPath();if(!S(e))return[];let r=I(e,"utf-8").split(`
3
3
  `).filter(Boolean),n=new Map;for(let o of r)try{let i=E.safeParse(JSON.parse(o));if(!i.success)continue;n.set(i.data.msgHash,i.data)}catch{}return Array.from(n.values()).sort((o,i)=>o.ts<i.ts?1:-1)}updateClassificationStatus(e,t){let n=this.readAllCollapsed().find(i=>i.msgHash===e);if(!n)return null;let o={...n,status:t.status,ts:new Date().toISOString()};if(t.reviewer||t.reviewReason){let i=` \xB7 ${t.reviewer?`reviewer=${t.reviewer}`:""}${t.reviewReason?` \xB7 ${t.reviewReason}`:""}`;o.preview=((n.preview||"")+i).slice(0,500)}return this.appendClassification(o),o}readRecentClassifications(e=50){let t=this.classificationsPath();if(!S(t))return[];let n=I(t,"utf-8").split(`
4
4
  `).filter(Boolean),o=[];for(let i=n.length-1;i>=0&&o.length<e;i--)try{let s=E.safeParse(JSON.parse(n[i]));s.success&&o.push(s.data)}catch{}return o}fingerprint(e){let r=[de(e.text),e.channel??""].join("");return ce("sha256").update(r).digest("hex").slice(0,32)}loadIndex(){return structuredClone(this.index())}saveIndex(e){let t=G.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid index: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.indexPath(),t.data)}index(){let e=this.indexPath();return S(e)?this.readParsed(e,t=>{let r=G.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}):{version:1,entries:{}}}getFingerprint(e){let t=this.index().entries[e];return t&&structuredClone(t)}setFingerprint(e,t){let r=Y.safeParse({...t,fingerprint:e,updatedAt:new Date().toISOString()});if(!r.success)throw new Error(`Refusing to save invalid index entry: ${r.error.issues.map(o=>o.message).join("; ")}`);let n=this.index();this.writeParsed(this.indexPath(),{...n,entries:{...n.entries,[e]:r.data}})}updateNodeAxes(e,t){let r=this.loadSchema(),n=this.loadNodes(),o=n.nodes.find(s=>s.id===e);if(!o)throw new Error(`Node not found: ${e}`);let i={...o,axes:t};return this.validateNode(i,r,n.nodes.map(s=>s.id===e?i:s)),n.nodes=n.nodes.map(s=>s.id===e?i:s),this.saveNodes(n),i}deleteNode(e){let t=this.loadNodes(),r=t.nodes.find(o=>o.parentId===e);if(r)throw new Error(`Cannot delete "${e}" \u2014 child node "${r.id}" still points to it`);let n=t.nodes.find(o=>Object.values(o.axes).some(i=>i===e));if(n)throw new Error(`Cannot delete "${e}" \u2014 node "${n.id}" references it via an axis`);t.nodes=t.nodes.filter(o=>o.id!==e),this.saveNodes(t)}approveClassification(e){if(this.getFingerprint(e.msgHash))return;let t=this.loadSchema(),r=new Set(this.loadNodes().nodes.map(n=>n.id));for(let n=0;n<e.path.length;n++){let o=e.path[n];if(r.has(o))continue;let i=t.levels[n]?.id;if(!i)break;let s=e.proposedAxes?.[o]??{},c=n===0?null:e.path[n-1];this.addNode({id:o,level:i,parentId:c,axes:s,createdAt:new Date().toISOString(),createdBy:e.agentId}),r.add(o)}this.setFingerprint(e.msgHash,{path:e.path,leaf:e.leaf}),this.appendClassification({...e,status:"approved",ts:new Date().toISOString()})}rejectClassification(e){this.appendClassification({...e,status:"rejected",ts:new Date().toISOString()})}};function X(a,e){D(Z(a),{recursive:!0});let t=`${a}.tmp-${process.pid}`;_(t,JSON.stringify(e,null,2)),ae(t,a)}function K(){let a=new Date().toISOString(),e=[],t=new Set;for(let r of M)t.has(r.category)||(e.push({id:r.category,level:"category",parentId:null,axes:{kind:r.category,description:$[r.category]},createdAt:a,createdBy:"starter"}),t.add(r.category));for(let r of M)e.push({id:r.verb,level:"verb",parentId:r.category,axes:{name:r.verb,...r.description?{description:r.description}:{}},createdAt:a,createdBy:"starter"});return{version:1,nodes:e}}function de(a){return a.toLowerCase().replace(/https?:\/\/\S+/g," url ").replace(/[@#][\w.\/-]+/g," ref ").replace(/\b[0-9a-f]{7,}\b/g," id ").replace(/\d+/g," num ").replace(/[^\p{L}\p{N}\s]+/gu," ").replace(/\s+/g," ").trim().slice(0,400)}se();re();U();import{createHash as ye}from"crypto";B();U();var P="intent-path",le=.5,pe=.3,ue=.45;function ge(a){return{message:ve(a.message,2e3),channel:a.channel??null,sender:a.sender??null,agent:a.agent??null}}function ee(a,e){let t={};for(let r of a)t[r.id]=me(r);return C(t,e)}function he(a){let e=a.map(t=>{let r=$[t.id];return t.axes?.description?.trim()||!r?t:{...t,axes:{...t.axes,description:r}}});return{category:ee(e,"Which category of work does this message ask for or report on? Pick the one a reviewer would file it under.")}}function fe(a){return{verb:ee(a,"Within that category, which specific action does the message ask for or report on? Prefer the most specific verb that fits; a casual or ambiguous message belongs to the most general one.")}}async function q(a,e,t={}){if(R(P)==="off")return null;let r=e.filter(m=>m.parentId===null);if(r.length<2)return null;let n=ge(a),o={agent:a.agent??"unknown",channel:a.channel??"unknown"},i=a.agent?[{kind:"agent",id:a.agent}]:void 0,s=await O(P,n,he(r),{incumbent:t.incumbent?.[0]?{category:t.incumbent[0]}:void 0,features:{...o,stage:"category"},links:i});if(!s)return null;let c=s.answers.category,l=c.pMax,h=c.pMax>=le,b=[c.choice],u=e.filter(m=>m.parentId===c.choice);if(u.length>=2){let m=t.incumbent?.[0]===c.choice,d=await O(P,{...n,category:c.choice},fe(u),{incumbent:m&&t.incumbent?.[1]?{verb:t.incumbent[1]}:void 0,features:{...o,stage:"verb",category:c.choice},links:i});if(!d)return null;let f=d.answers.verb;h=h&&f.pMax>=pe,(f.pMax>=ue||!h)&&(l=Math.min(l,f.pMax),b.push(f.choice))}else u.length===1&&b.push(u[0].id);return{path:b,confidence:l,confident:h,mode:s.mode}}function me(a){let e=a.axes?.description?.trim()||void 0,t=a.axes?.name?.trim(),r=t&&t!==a.id?t:void 0;return e&&r?`${r}: ${e}`:e??r??null}function ve(a,e){return a.length>e?`${a.slice(0,e)}\u2026`:a}var L=class{store;daemonUrl;token;draftAgent;autoApprove;autoApproveStructure;classifierModel;classifierProviderName;providerInstance;log;constructor(e){this.store=e.store,this.daemonUrl=e.daemonUrl.replace(/\/+$/,""),this.token=e.token,this.draftAgent=e.draftAgent,this.autoApprove=e.autoApproveConfidence??1,this.autoApproveStructure=e.autoApproveStructure??"extend-leaves",this.classifierModel=e.classifierModel??"claude-haiku-4-5-20251001",this.classifierProviderName=e.classifierProvider??"claude-code",this.log=e.log??console.error.bind(console,"[classifier]")}getProvider(){return this.providerInstance||(this.providerInstance=z(this.classifierProviderName)),this.providerInstance}async classify(e){let t=this.store.fingerprint({text:e.text,channel:e.channel,sender:e.sender}),r=this.store.getFingerprint(t);if(r)return this.store.appendClassification({ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:r.path,proposedAxes:{},leaf:r.leaf,source:"cache",status:"approved",preview:e.text.slice(0,200)}),{path:r.path,pathId:H(r.path),pathLabel:J(r.path,this.store.loadNodes().nodes),axes:{},leaf:r.leaf,source:"cache",status:"approved"};let n=this.store.loadSchema(),o=this.store.loadNodes().nodes,i=await this.propose(e,n,o);if(!i)return null;let{path:s,proposedAxes:c,confidence:l,leaf:h}=i;if(s.length===0)return null;let b={ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:s,proposedAxes:c,leaf:h,source:i.source,status:"pending",confidence:l,preview:e.text.slice(0,200)},u=new Set(o.map(g=>g.id)),m=s.map((g,p)=>u.has(g)?-1:p).filter(g=>g>=0),d=!1;if(this.autoApproveStructure==="any")d=!0;else if(this.autoApproveStructure==="extend-leaves"){let g=m.length===0,p=m.length===1&&m[0]===s.length-1;d=g||p}let f=l!==void 0&&l>=this.autoApprove,v="pending";if(d||f)try{this.commitNewNodes(s,c,n,o,e.sender),this.store.setFingerprint(t,{path:s,leaf:h}),v="approved"}catch(g){this.log("auto-approve failed \u2014 staying pending \xB7",`path=${JSON.stringify(s)}`,`reason=${(g?.message||g).toString().slice(0,200)}`)}return b.status=v,this.store.appendClassification(b),{path:s,pathId:H(s),pathLabel:J(s,this.store.loadNodes().nodes),axes:c,leaf:h,source:i.source,status:v,confidence:l}}async propose(e,t,r){let n=R(P),o={message:e.text,channel:e.channel,sender:e.sender,agent:e.agentId};if(n==="active"){let s=await q(o,r).catch(c=>(this.log("intent-path seat failed:",c?.message||c),null));if(s?.confident)return{path:s.path,proposedAxes:{},leaf:{},confidence:s.confidence,source:"seat"}}let i=await this.proposePath(e,t,r).catch(s=>(this.log("LLM proposal failed:",s?.message||s),null));return n==="shadow"&&i&&q(o,r,{incumbent:i.path}).catch(()=>{}),i?{...i,source:"llm"}:null}async proposePath(e,t,r){let n=r.map(d=>({id:d.id,level:d.level,parentId:d.parentId,axes:d.axes})),o=["You are the intent classifier for AgentX. You answer ONE question:",'"what kind of work does this message describe?" \u2014 verb-level only.',"","IMPORTANT \u2014 what NOT to encode in the path:","- Client / company / project / team names. Those are already on the event"," metadata (project, channel, agentId). Embedding them in the path causes"," duplicate nodes (one per client) for the same verb.","- The specific subject (issue number, file name, person name). Same reason.","- The agent's name or role. Pick the verb the *requester* intended.","","SCHEMA (fixed levels \u2014 pick one node per level):","```json",JSON.stringify(t,null,2),"```","","EXISTING NODES (prefer reusing these; only invent a new node when no","existing one fits):","```json",JSON.stringify(n,null,2),"```","","MESSAGE:","```",e.text.slice(0,2e3),"```",`channel: ${e.channel??"?"}`,`sender: ${e.sender??"?"}`,"","TASK:","1. Classify into 'category' (closed enum): code, ops, support, admin,"," knowledge, social, system. Pick the closest fit.","2. Pick or propose a 'verb' node. Verb ids are dot-namespaced lower-kebab,",' e.g. "review.merge-request", "deploy.staging", "investigate.error",',' "chat.greeting", "fix.bug". Reuse an existing verb when the message is'," the same kind of work as something already classified, even when the"," client / project / subject differs.","","Examples (right vs wrong):",'- "Please review MR #957 on globex/system" \u2192 ["code", "review.merge-request"]',' NOT ["business", "acme", "globex-v2", "review-mr-957-system"]','- "Deploy initech-v2 to staging please" \u2192 ["ops", "deploy.staging"]',' NOT ["business", "acme", "initech-v2", "deploy-initech-v2-to-staging"]','- "Hello Atlas" \u2192 ["support", "chat.greeting"]',"","NODE ID RULES (strict \u2014 invalid ids get dropped):","- lowercase-kebab + dots only: [a-z0-9][a-z0-9._-]*","- no spaces, no uppercase, no Arabic/other non-Latin",'- verb ids should use a "category.specific" or "verb.modifier" shape',"","Return ONE JSON object on one line, no prose, no fences:",' { "path": string[], "proposedAxes": { [nodeId]: { [axisName]: string } }, "leaf": { "input"?: string, "output"?: string }, "confidence": number }',"confidence in [0,1]. Prefer low confidence over guessing."].join(`
5
5
  `),i=await this.callProvider(o),s=W(i);if(!s||!Array.isArray(s.path))return null;let c=/^[a-z0-9][a-z0-9._-]*$/,l=new Map,h=s.path.filter(d=>typeof d=="string"&&d.length>0).map(d=>{let f=d.trim(),v=te(f);return f!==v&&l.set(f,v),v}).filter(d=>c.test(d));if(h.length===0)return null;let b={};if(s.proposedAxes&&typeof s.proposedAxes=="object")for(let[d,f]of Object.entries(s.proposedAxes)){if(!f||typeof f!="object")continue;let v=l.get(d)??te(d);if(!v)continue;let g={};for(let[p,y]of Object.entries(f))typeof y=="string"&&(g[p]=y);!g.name&&d!==v&&(g.name=d.trim()),b[v]=g}let u={};s.leaf&&typeof s.leaf=="object"&&(typeof s.leaf.input=="string"&&(u.input=s.leaf.input),typeof s.leaf.output=="string"&&(u.output=s.leaf.output));let m=typeof s.confidence=="number"?Math.max(0,Math.min(1,s.confidence)):void 0;return{path:h,proposedAxes:b,confidence:m,leaf:u}}commitNewNodes(e,t,r,n,o){this.store.commitNodesAlongPath(e,t,r,o)}async callProvider(e){let t=this.getProvider(),r=new AbortController,n=setTimeout(()=>r.abort(),3e4);try{return(await t.generate([{role:"system",content:"You are a taxonomy classifier. Respond with a single JSON object on one line. No prose, no code fences."},{role:"user",content:e}],{model:this.classifierModel,maxTokens:800,abortSignal:r.signal,bare:!0})).content||""}finally{clearTimeout(n)}}};function te(a){let e=a.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[-_]+|[-_]+$/g,"").replace(/-{2,}/g,"-");return e?/^[a-z0-9]/.test(e)?e:`n-${e}`.replace(/[-_]+$/,""):""}function H(a){return ye("sha1").update(a.join("")).digest("hex").slice(0,16)}function J(a,e){return a.map(t=>{let r=e.find(o=>o.id===t);return r&&(r.axes?.name||r.axes?.what)||t}).join(" \u203A ")}function we(a,e){if(a.length===0||e.length===0)return 0;let t=0;for(let n=0;n<Math.min(a.length,e.length)&&a[n]===e[n];n++)t++;if(t===0)return 0;let r=Math.max(a.length,e.length);return t/r}export{xe as a,Se as b,Ne as c,ke as d,Ie as e,x as f,F as g,L as h,J as i,we as j};
6
- //# sourceMappingURL=chunk-LTH3AYXC.js.map
6
+ //# sourceMappingURL=chunk-XIA2ACKX.js.map
@@ -1,5 +1,5 @@
1
- import{oa as m,ra as b}from"./chunk-Y27RUOD2.js";b();function w(t){let e=typeof t.node=="string"?t.node:"",n=typeof t.token=="string"?t.token:"";if(!e||!n)try{let o=m(t.config);e||(e=o.dashboard?.daemonUrl||""),n||(n=o.dashboard?.token||"")}catch{}return e||(e="http://localhost:18800"),{baseUrl:e.replace(/\/+$/,""),token:n}}async function d(t,e,n){let o={Accept:"application/json"};t.token&&(o.Authorization=`Bearer ${t.token}`);let r=await fetch(`${t.baseUrl}${e}`,{headers:o,signal:n});if(!r.ok)throw new Error(`GET ${e} \u2192 HTTP ${r.status}`);return await r.json()}function R(t,e){return d(t,"/agents",e)}async function S(t,e){return(await d(t,"/api/processes",e)).processes??[]}function P(t,e){return d(t,"/crons",e)}async function h(t,e,n,o){let r={Accept:"application/json","Content-Type":"application/json"};t.token&&(r.Authorization=`Bearer ${t.token}`);let s=await fetch(`${t.baseUrl}${e}`,{method:"POST",headers:r,body:JSON.stringify(n),signal:o}),a=await s.text(),i;try{i=a?JSON.parse(a):{}}catch{i={raw:a}}if(!s.ok)throw new Error(i?.error||`POST ${e} \u2192 HTTP ${s.status}`);return i}function A(t,e,n="tui"){return h(t,"/api/processes/kill",{...e,reason:n})}function C(t,e,n,o){return h(t,"/task",{agent:e,message:n,context:o??{channel:"tui",chatId:"tui:operator"}})}async function $(t,e,n,o={}){let r={Accept:"text/event-stream","Content-Type":"application/json"};t.token&&(r.Authorization=`Bearer ${t.token}`);let s=await fetch(`${t.baseUrl}/task`,{method:"POST",headers:r,body:JSON.stringify({agent:e,message:n,stream:!0,context:{channel:o.channel??"chat-cli",chatId:o.chatId??"chat-cli:operator"}}),signal:o.signal});if(!s.ok||!s.body){let g=await s.text().catch(()=>"");throw new Error(`POST /task (stream) \u2192 HTTP ${s.status}${g?`: ${g.slice(0,200)}`:""}`)}let a=s.body.getReader(),i=new TextDecoder,u={content:""},c="";try{for(;;){let{done:g,value:p}=await a.read();if(g)break;c+=i.decode(p,{stream:!0});let l;for(;(l=c.indexOf(`
1
+ import{oa as m,ua as b}from"./chunk-EOLEKSN6.js";b();function w(t){let e=typeof t.node=="string"?t.node:"",n=typeof t.token=="string"?t.token:"";if(!e||!n)try{let o=m(t.config);e||(e=o.dashboard?.daemonUrl||""),n||(n=o.dashboard?.token||"")}catch{}return e||(e="http://localhost:18800"),{baseUrl:e.replace(/\/+$/,""),token:n}}async function d(t,e,n){let o={Accept:"application/json"};t.token&&(o.Authorization=`Bearer ${t.token}`);let r=await fetch(`${t.baseUrl}${e}`,{headers:o,signal:n});if(!r.ok)throw new Error(`GET ${e} \u2192 HTTP ${r.status}`);return await r.json()}function R(t,e){return d(t,"/agents",e)}async function S(t,e){return(await d(t,"/api/processes",e)).processes??[]}function P(t,e){return d(t,"/crons",e)}async function h(t,e,n,o){let r={Accept:"application/json","Content-Type":"application/json"};t.token&&(r.Authorization=`Bearer ${t.token}`);let s=await fetch(`${t.baseUrl}${e}`,{method:"POST",headers:r,body:JSON.stringify(n),signal:o}),a=await s.text(),i;try{i=a?JSON.parse(a):{}}catch{i={raw:a}}if(!s.ok)throw new Error(i?.error||`POST ${e} \u2192 HTTP ${s.status}`);return i}function A(t,e,n="tui"){return h(t,"/api/processes/kill",{...e,reason:n})}function C(t,e,n,o){return h(t,"/task",{agent:e,message:n,context:o??{channel:"tui",chatId:"tui:operator"}})}async function $(t,e,n,o={}){let r={Accept:"text/event-stream","Content-Type":"application/json"};t.token&&(r.Authorization=`Bearer ${t.token}`);let s=await fetch(`${t.baseUrl}/task`,{method:"POST",headers:r,body:JSON.stringify({agent:e,message:n,stream:!0,context:{channel:o.channel??"chat-cli",chatId:o.chatId??"chat-cli:operator"}}),signal:o.signal});if(!s.ok||!s.body){let g=await s.text().catch(()=>"");throw new Error(`POST /task (stream) \u2192 HTTP ${s.status}${g?`: ${g.slice(0,200)}`:""}`)}let a=s.body.getReader(),i=new TextDecoder,u={content:""},c="";try{for(;;){let{done:g,value:p}=await a.read();if(g)break;c+=i.decode(p,{stream:!0});let l;for(;(l=c.indexOf(`
2
2
 
3
3
  `))>=0;){let k=c.slice(0,l);c=c.slice(l+2);let f=x(k);f&&T(f,o,u)}}}finally{try{a.releaseLock()}catch{}}return u}function T(t,e,n){let{event:o,data:r}=t;switch(o){case"text":r?.text&&(n.content+=r.text,e.onText?.(r.text));break;case"thinking":r?.text&&e.onThinking?.(r.text);break;case"tool":e.onTool?.(r);break;case"done":typeof r?.content=="string"&&(n.content=r.content),n.usage=r?.usage,n.duration=r?.duration;break;case"error":n.error=r?.error,n.errorKind=r?.errorKind;break}}function x(t){let e="message",n="";for(let r of t.split(`
4
4
  `))r.startsWith("event: ")?e=r.slice(7).trim():r.startsWith("data: ")&&(n=r.slice(6));if(!n)return null;let o;try{o=JSON.parse(n)}catch{o=n}return{event:e,data:o}}export{w as a,R as b,S as c,P as d,A as e,C as f,$ as g};
5
- //# sourceMappingURL=chunk-73WQ42PO.js.map
5
+ //# sourceMappingURL=chunk-XOQNDWVW.js.map
@@ -1,2 +1,2 @@
1
- import{y as l,z as T}from"./chunk-3KYZOC7N.js";import{a,f as w}from"./chunk-72SZI2UW.js";T();w();var d="task-tier";function u(e){return{task:b(e.message,2e3),agent:e.agent,channel:e.channel??null,continuesExistingSession:e.isFollowUp??!1,...e.toolsAvailable?{toolsAvailable:e.toolsAvailable}:{}}}var c={needsFlagship:a("This task needs the strongest available model to be done correctly.",{true:"Doing it well takes real reasoning: writing or reviewing code, diagnosing a failure, planning work across several steps, weighing a judgement, handling money, touching production or anything irreversible, following a long or ambiguous instruction, or work where being subtly wrong would not be noticed. Anything where a weaker model would produce something that LOOKS right belongs here.",false:"It is mechanical and its result is obvious on sight: acknowledging or confirming a message, restating or formatting something already written, a greeting, a status echo, reading one file and reporting what it says, a lookup with one right answer, a scheduled heartbeat with nothing to decide."})};function g(e){return e.needsFlagship.noul}function b(e,n){return e.length>n?`${e.slice(0,n)}\u2026`:e}var h=.2;function p(e){return e==="voice"||e==="desktop"||e==="opencode"}var f=60*60*1e3;function M(e,n){if(e==="claude-code"){let t=n?.cheapModels?.[e]??n?.cheapModel;return t&&/^(claude-|haiku$|sonnet$|opus$)/.test(t)?t:void 0}if(e==="codex-cli"){let t=n?.cheapModels?.[e];return t&&/^(gpt-|o\d)/.test(t)?t:void 0}}async function v(e){let n=(s,r=null)=>({downgraded:!1,needsFlagship:r,reason:s});if(p(e.channel))return n(`${e.channel} requests retain the configured model`);if(!e.cheapModel)return n("no cheaper model configured");if(e.isFollowUp){let s=e.sessionIdleMs??0,r=e.cacheTtlMs??f;if(s<r)return n(`follow-up on a warm cache (idle ${Math.round(s/1e3)}s) \u2014 swapping would cost more than it saves`)}let t;try{t=await l(d,u(e),c,{incumbent:{needsFlagship:1},features:{agent:e.agent,channel:e.channel??"unknown"},timeoutMs:1500})}catch{return n("routing seat threw \u2014 keeping the flagship")}if(!t)return n("routing seat unavailable \u2014 keeping the flagship");let o=g(t.answers);if(t.mode!=="active")return n(`shadow \u2014 would ${o<(e.threshold??h)?"downgrade":"keep"} (${o.toFixed(2)})`,o);let i=e.threshold??h;return o>=i?n(`needs the flagship (${o.toFixed(2)})`,o):{model:e.cheapModel,downgraded:!0,needsFlagship:o,reason:`mechanical task (${o.toFixed(2)} below ${i}) \u2192 ${e.cheapModel}`}}var A=["labeled","unlabeled","closed"];function y(e){let n=r=>({downgraded:!1,needsFlagship:null,reason:r});if(p(e.channel))return n(`${e.channel} requests retain the configured model`);let t=e.eventActions??[];if(!t.length)return n("not started by a platform event");let o=e.triage?.actions??A,i=t.find(r=>!o.includes(r));if(i)return n(`"${i}" is not a triage action`);let s=M(e.tier,{cheapModels:e.triage?.models});if(!s)return n(`no triage model set for ${e.tier}`);if(e.isFollowUp){let r=e.sessionIdleMs??0,m=e.cacheTtlMs??f;if(r<m)return n(`follow-up on a warm cache (idle ${Math.round(r/1e3)}s) \u2014 keeping the session's model`)}return{model:s,downgraded:!0,needsFlagship:null,reason:`triage event (${t.join(", ")}) \u2192 ${s}`}}export{h as a,p as b,M as c,v as d,A as e,y as f};
2
- //# sourceMappingURL=chunk-XX5QKBX4.js.map
1
+ import{y as l,z as T}from"./chunk-R6UCPHUD.js";import{a,f as w}from"./chunk-72SZI2UW.js";T();w();var d="task-tier";function u(e){return{task:b(e.message,2e3),agent:e.agent,channel:e.channel??null,continuesExistingSession:e.isFollowUp??!1,...e.toolsAvailable?{toolsAvailable:e.toolsAvailable}:{}}}var c={needsFlagship:a("This task needs the strongest available model to be done correctly.",{true:"Doing it well takes real reasoning: writing or reviewing code, diagnosing a failure, planning work across several steps, weighing a judgement, handling money, touching production or anything irreversible, following a long or ambiguous instruction, or work where being subtly wrong would not be noticed. Anything where a weaker model would produce something that LOOKS right belongs here.",false:"It is mechanical and its result is obvious on sight: acknowledging or confirming a message, restating or formatting something already written, a greeting, a status echo, reading one file and reporting what it says, a lookup with one right answer, a scheduled heartbeat with nothing to decide."})};function g(e){return e.needsFlagship.noul}function b(e,n){return e.length>n?`${e.slice(0,n)}\u2026`:e}var h=.2;function p(e){return e==="voice"||e==="desktop"||e==="opencode"}var f=60*60*1e3;function M(e,n){if(e==="claude-code"){let t=n?.cheapModels?.[e]??n?.cheapModel;return t&&/^(claude-|haiku$|sonnet$|opus$)/.test(t)?t:void 0}if(e==="codex-cli"){let t=n?.cheapModels?.[e];return t&&/^(gpt-|o\d)/.test(t)?t:void 0}}async function v(e){let n=(s,r=null)=>({downgraded:!1,needsFlagship:r,reason:s});if(p(e.channel))return n(`${e.channel} requests retain the configured model`);if(!e.cheapModel)return n("no cheaper model configured");if(e.isFollowUp){let s=e.sessionIdleMs??0,r=e.cacheTtlMs??f;if(s<r)return n(`follow-up on a warm cache (idle ${Math.round(s/1e3)}s) \u2014 swapping would cost more than it saves`)}let t;try{t=await l(d,u(e),c,{incumbent:{needsFlagship:1},features:{agent:e.agent,channel:e.channel??"unknown"},timeoutMs:1500})}catch{return n("routing seat threw \u2014 keeping the flagship")}if(!t)return n("routing seat unavailable \u2014 keeping the flagship");let o=g(t.answers);if(t.mode!=="active")return n(`shadow \u2014 would ${o<(e.threshold??h)?"downgrade":"keep"} (${o.toFixed(2)})`,o);let i=e.threshold??h;return o>=i?n(`needs the flagship (${o.toFixed(2)})`,o):{model:e.cheapModel,downgraded:!0,needsFlagship:o,reason:`mechanical task (${o.toFixed(2)} below ${i}) \u2192 ${e.cheapModel}`}}var A=["labeled","unlabeled","closed"];function y(e){let n=r=>({downgraded:!1,needsFlagship:null,reason:r});if(p(e.channel))return n(`${e.channel} requests retain the configured model`);let t=e.eventActions??[];if(!t.length)return n("not started by a platform event");let o=e.triage?.actions??A,i=t.find(r=>!o.includes(r));if(i)return n(`"${i}" is not a triage action`);let s=M(e.tier,{cheapModels:e.triage?.models});if(!s)return n(`no triage model set for ${e.tier}`);if(e.isFollowUp){let r=e.sessionIdleMs??0,m=e.cacheTtlMs??f;if(r<m)return n(`follow-up on a warm cache (idle ${Math.round(r/1e3)}s) \u2014 keeping the session's model`)}return{model:s,downgraded:!0,needsFlagship:null,reason:`triage event (${t.join(", ")}) \u2192 ${s}`}}export{h as a,p as b,M as c,v as d,A as e,y as f};
2
+ //# sourceMappingURL=chunk-XZN7QBRH.js.map