open-multi-agent-kit 0.95.0 → 0.95.2

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 (281) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/dist/approvals/approval-receipt-store.d.ts +18 -0
  4. package/dist/approvals/approval-receipt-store.d.ts.map +1 -0
  5. package/dist/approvals/approval-receipt-store.js +169 -0
  6. package/dist/approvals/approval-receipt-store.js.map +1 -0
  7. package/dist/approvals/approval-receipt.d.ts +43 -0
  8. package/dist/approvals/approval-receipt.d.ts.map +1 -0
  9. package/dist/approvals/approval-receipt.js +208 -0
  10. package/dist/approvals/approval-receipt.js.map +1 -0
  11. package/dist/approvals/approval-verifier.d.ts +17 -0
  12. package/dist/approvals/approval-verifier.d.ts.map +1 -0
  13. package/dist/approvals/approval-verifier.js +23 -0
  14. package/dist/approvals/approval-verifier.js.map +1 -0
  15. package/dist/approvals/plannotator-approval-bridge.d.ts +58 -0
  16. package/dist/approvals/plannotator-approval-bridge.d.ts.map +1 -0
  17. package/dist/approvals/plannotator-approval-bridge.js +169 -0
  18. package/dist/approvals/plannotator-approval-bridge.js.map +1 -0
  19. package/dist/cli/args.d.ts.map +1 -1
  20. package/dist/cli/args.js +2 -0
  21. package/dist/cli/args.js.map +1 -1
  22. package/dist/commands/package-doctor-cli.d.ts +13 -0
  23. package/dist/commands/package-doctor-cli.d.ts.map +1 -0
  24. package/dist/commands/package-doctor-cli.js +80 -0
  25. package/dist/commands/package-doctor-cli.js.map +1 -0
  26. package/dist/commands/router-feedback-cli.d.ts +11 -0
  27. package/dist/commands/router-feedback-cli.d.ts.map +1 -0
  28. package/dist/commands/router-feedback-cli.js +99 -0
  29. package/dist/commands/router-feedback-cli.js.map +1 -0
  30. package/dist/commands/stats-cli.d.ts +20 -0
  31. package/dist/commands/stats-cli.d.ts.map +1 -0
  32. package/dist/commands/stats-cli.js +99 -0
  33. package/dist/commands/stats-cli.js.map +1 -0
  34. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  35. package/dist/core/agent-session-runtime.js +4 -1
  36. package/dist/core/agent-session-runtime.js.map +1 -1
  37. package/dist/core/agent-session.d.ts +97 -29
  38. package/dist/core/agent-session.d.ts.map +1 -1
  39. package/dist/core/agent-session.js +583 -199
  40. package/dist/core/agent-session.js.map +1 -1
  41. package/dist/core/bash-executor.d.ts.map +1 -1
  42. package/dist/core/bash-executor.js +8 -2
  43. package/dist/core/bash-executor.js.map +1 -1
  44. package/dist/core/compaction/transaction.d.ts +9 -0
  45. package/dist/core/compaction/transaction.d.ts.map +1 -1
  46. package/dist/core/compaction/transaction.js +17 -2
  47. package/dist/core/compaction/transaction.js.map +1 -1
  48. package/dist/core/context-budget-governor-v2.d.ts +1 -1
  49. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  50. package/dist/core/context-budget-governor-v2.js +1 -1
  51. package/dist/core/context-budget-governor-v2.js.map +1 -1
  52. package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
  53. package/dist/core/context-budget-system-prompt.js +18 -2
  54. package/dist/core/context-budget-system-prompt.js.map +1 -1
  55. package/dist/core/context-budget-v2-cache-disk.d.ts +109 -0
  56. package/dist/core/context-budget-v2-cache-disk.d.ts.map +1 -0
  57. package/dist/core/context-budget-v2-cache-disk.js +343 -0
  58. package/dist/core/context-budget-v2-cache-disk.js.map +1 -0
  59. package/dist/core/context-budget-v2-cache-keys.d.ts +22 -0
  60. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  61. package/dist/core/context-budget-v2-cache-keys.js +4 -4
  62. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  63. package/dist/core/context-budget-v2-cache.d.ts +1 -0
  64. package/dist/core/context-budget-v2-cache.d.ts.map +1 -1
  65. package/dist/core/context-budget-v2-cache.js +1 -0
  66. package/dist/core/context-budget-v2-cache.js.map +1 -1
  67. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  68. package/dist/core/context-budget-v2-plan-cache-keys.js +39 -5
  69. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  70. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  71. package/dist/core/context-budget-v2-planner.js +17 -14
  72. package/dist/core/context-budget-v2-planner.js.map +1 -1
  73. package/dist/core/context-budget-v2-types.d.ts +1 -0
  74. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  75. package/dist/core/context-budget-v2-types.js.map +1 -1
  76. package/dist/core/extensions/loader.d.ts.map +1 -1
  77. package/dist/core/extensions/loader.js +45 -22
  78. package/dist/core/extensions/loader.js.bak-sharedjiti-20260812-111246 +553 -0
  79. package/dist/core/extensions/loader.js.map +1 -1
  80. package/dist/core/extensions/types.d.ts +4 -0
  81. package/dist/core/extensions/types.d.ts.map +1 -1
  82. package/dist/core/extensions/types.js.map +1 -1
  83. package/dist/core/headroom.d.ts.map +1 -1
  84. package/dist/core/headroom.js +7 -2
  85. package/dist/core/headroom.js.map +1 -1
  86. package/dist/core/mcp/client.d.ts +105 -0
  87. package/dist/core/mcp/client.d.ts.map +1 -0
  88. package/dist/core/mcp/client.js +198 -0
  89. package/dist/core/mcp/client.js.map +1 -0
  90. package/dist/core/mcp/config.d.ts +23 -0
  91. package/dist/core/mcp/config.d.ts.map +1 -0
  92. package/dist/core/mcp/config.js +89 -0
  93. package/dist/core/mcp/config.js.map +1 -0
  94. package/dist/core/mcp/index.d.ts +15 -0
  95. package/dist/core/mcp/index.d.ts.map +1 -0
  96. package/dist/core/mcp/index.js +15 -0
  97. package/dist/core/mcp/index.js.map +1 -0
  98. package/dist/core/mcp/manager.d.ts +84 -0
  99. package/dist/core/mcp/manager.d.ts.map +1 -0
  100. package/dist/core/mcp/manager.js +173 -0
  101. package/dist/core/mcp/manager.js.map +1 -0
  102. package/dist/core/mcp/protocol.d.ts +59 -0
  103. package/dist/core/mcp/protocol.d.ts.map +1 -0
  104. package/dist/core/mcp/protocol.js +103 -0
  105. package/dist/core/mcp/protocol.js.map +1 -0
  106. package/dist/core/mcp/stdio-transport.d.ts +63 -0
  107. package/dist/core/mcp/stdio-transport.d.ts.map +1 -0
  108. package/dist/core/mcp/stdio-transport.js +132 -0
  109. package/dist/core/mcp/stdio-transport.js.map +1 -0
  110. package/dist/core/mcp/tools.d.ts +47 -0
  111. package/dist/core/mcp/tools.d.ts.map +1 -0
  112. package/dist/core/mcp/tools.js +102 -0
  113. package/dist/core/mcp/tools.js.map +1 -0
  114. package/dist/core/package-archive.d.ts +3 -0
  115. package/dist/core/package-archive.d.ts.map +1 -0
  116. package/dist/core/package-archive.js +161 -0
  117. package/dist/core/package-archive.js.map +1 -0
  118. package/dist/core/package-doctor-source-scan.d.ts +13 -0
  119. package/dist/core/package-doctor-source-scan.d.ts.map +1 -0
  120. package/dist/core/package-doctor-source-scan.js +124 -0
  121. package/dist/core/package-doctor-source-scan.js.map +1 -0
  122. package/dist/core/package-doctor.d.ts +41 -0
  123. package/dist/core/package-doctor.d.ts.map +1 -0
  124. package/dist/core/package-doctor.js +145 -0
  125. package/dist/core/package-doctor.js.map +1 -0
  126. package/dist/core/package-manager.d.ts +10 -1
  127. package/dist/core/package-manager.d.ts.map +1 -1
  128. package/dist/core/package-manager.js +175 -42
  129. package/dist/core/package-manager.js.map +1 -1
  130. package/dist/core/package-manifest.d.ts +26 -0
  131. package/dist/core/package-manifest.d.ts.map +1 -0
  132. package/dist/core/package-manifest.js +85 -0
  133. package/dist/core/package-manifest.js.map +1 -0
  134. package/dist/core/pi-compat.d.ts +5 -0
  135. package/dist/core/pi-compat.d.ts.map +1 -0
  136. package/dist/core/pi-compat.js +18 -0
  137. package/dist/core/pi-compat.js.map +1 -0
  138. package/dist/core/reasoning-router-bias.d.ts +10 -14
  139. package/dist/core/reasoning-router-bias.d.ts.map +1 -1
  140. package/dist/core/reasoning-router-bias.js +22 -15
  141. package/dist/core/reasoning-router-bias.js.map +1 -1
  142. package/dist/core/reasoning-router-v4.d.ts +9 -1
  143. package/dist/core/reasoning-router-v4.d.ts.map +1 -1
  144. package/dist/core/reasoning-router-v4.js +20 -5
  145. package/dist/core/reasoning-router-v4.js.map +1 -1
  146. package/dist/core/redaction.d.ts +2 -5
  147. package/dist/core/redaction.d.ts.map +1 -1
  148. package/dist/core/redaction.js +14 -1
  149. package/dist/core/redaction.js.map +1 -1
  150. package/dist/core/repository-learning-scope.d.ts +10 -0
  151. package/dist/core/repository-learning-scope.d.ts.map +1 -0
  152. package/dist/core/repository-learning-scope.js +42 -0
  153. package/dist/core/repository-learning-scope.js.map +1 -0
  154. package/dist/core/resource-loader.d.ts.map +1 -1
  155. package/dist/core/resource-loader.js +16 -1
  156. package/dist/core/resource-loader.js.map +1 -1
  157. package/dist/core/router-feedback-collector.d.ts +2 -1
  158. package/dist/core/router-feedback-collector.d.ts.map +1 -1
  159. package/dist/core/router-feedback-collector.js +2 -2
  160. package/dist/core/router-feedback-collector.js.map +1 -1
  161. package/dist/core/runtime-provenance.d.ts +54 -0
  162. package/dist/core/runtime-provenance.d.ts.map +1 -0
  163. package/dist/core/runtime-provenance.js +115 -0
  164. package/dist/core/runtime-provenance.js.map +1 -0
  165. package/dist/core/sandbox/backend.d.ts.map +1 -1
  166. package/dist/core/sandbox/backend.js +39 -21
  167. package/dist/core/sandbox/backend.js.map +1 -1
  168. package/dist/core/sandbox/default-policy.d.ts +4 -5
  169. package/dist/core/sandbox/default-policy.d.ts.map +1 -1
  170. package/dist/core/sandbox/default-policy.js +9 -11
  171. package/dist/core/sandbox/default-policy.js.map +1 -1
  172. package/dist/core/session-bash-runtime.d.ts +4 -4
  173. package/dist/core/session-bash-runtime.d.ts.map +1 -1
  174. package/dist/core/session-bash-runtime.js +6 -5
  175. package/dist/core/session-bash-runtime.js.map +1 -1
  176. package/dist/core/session-compaction-service.d.ts.map +1 -1
  177. package/dist/core/session-compaction-service.js +7 -7
  178. package/dist/core/session-compaction-service.js.map +1 -1
  179. package/dist/core/session-manager.d.ts +4 -1
  180. package/dist/core/session-manager.d.ts.map +1 -1
  181. package/dist/core/session-manager.js +47 -7
  182. package/dist/core/session-manager.js.map +1 -1
  183. package/dist/core/settings-manager.d.ts +7 -4
  184. package/dist/core/settings-manager.d.ts.map +1 -1
  185. package/dist/core/settings-manager.js +11 -3
  186. package/dist/core/settings-manager.js.map +1 -1
  187. package/dist/core/system-prompt.d.ts +9 -1
  188. package/dist/core/system-prompt.d.ts.map +1 -1
  189. package/dist/core/system-prompt.js +10 -4
  190. package/dist/core/system-prompt.js.map +1 -1
  191. package/dist/core/tools/bash.d.ts +2 -2
  192. package/dist/core/tools/bash.d.ts.map +1 -1
  193. package/dist/core/tools/bash.js +2 -1
  194. package/dist/core/tools/bash.js.map +1 -1
  195. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  196. package/dist/core/tools/tool-definition-wrapper.js +15 -0
  197. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  198. package/dist/core/turn-metrics.d.ts +135 -0
  199. package/dist/core/turn-metrics.d.ts.map +1 -0
  200. package/dist/core/turn-metrics.js +261 -0
  201. package/dist/core/turn-metrics.js.map +1 -0
  202. package/dist/core/verified-bash-runtime.d.ts.map +1 -1
  203. package/dist/core/verified-bash-runtime.js +10 -4
  204. package/dist/core/verified-bash-runtime.js.map +1 -1
  205. package/dist/guardrails/replay-ledger-lock-owner.d.ts +7 -0
  206. package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
  207. package/dist/guardrails/replay-ledger-lock-owner.js +39 -0
  208. package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
  209. package/dist/index.d.ts +6 -1
  210. package/dist/index.d.ts.map +1 -1
  211. package/dist/index.js +6 -1
  212. package/dist/index.js.map +1 -1
  213. package/dist/main.d.ts.map +1 -1
  214. package/dist/main.js +18 -0
  215. package/dist/main.js.map +1 -1
  216. package/dist/modes/interactive/components/control-panel-layout.d.ts.map +1 -1
  217. package/dist/modes/interactive/components/control-panel-layout.js +2 -2
  218. package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
  219. package/dist/modes/interactive/components/footer.d.ts +3 -0
  220. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  221. package/dist/modes/interactive/components/footer.js +40 -12
  222. package/dist/modes/interactive/components/footer.js.map +1 -1
  223. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  224. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  225. package/dist/modes/interactive/components/settings-selector.js +10 -0
  226. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  227. package/dist/modes/interactive/components/status-sidebar.d.ts +14 -2
  228. package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
  229. package/dist/modes/interactive/components/status-sidebar.js +106 -19
  230. package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
  231. package/dist/modes/interactive/interactive-mode.d.ts +10 -1
  232. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  233. package/dist/modes/interactive/interactive-mode.js +104 -17
  234. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  235. package/dist/modes/interactive/theme/dark.json +1 -1
  236. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  237. package/dist/modes/interactive/theme/theme.js +1 -0
  238. package/dist/modes/interactive/theme/theme.js.map +1 -1
  239. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  240. package/dist/modes/rpc/rpc-mode.js +1 -1
  241. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  242. package/docs/compaction.md +25 -3
  243. package/docs/containerization.md +14 -2
  244. package/docs/docs.json +8 -0
  245. package/docs/environment-variables.md +1 -1
  246. package/docs/extensions.md +75 -16
  247. package/docs/mcp.md +97 -0
  248. package/docs/metrics.md +87 -0
  249. package/docs/packages.md +25 -4
  250. package/docs/providers.md +1 -1
  251. package/docs/sdk.md +25 -4
  252. package/docs/settings.md +8 -7
  253. package/docs/usage.md +13 -3
  254. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  255. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  256. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  257. package/examples/extensions/gondolin/package-lock.json +2 -2
  258. package/examples/extensions/gondolin/package.json +1 -1
  259. package/examples/extensions/plannotator-approval-bridge.ts +83 -0
  260. package/examples/extensions/sandbox/package-lock.json +2 -2
  261. package/examples/extensions/sandbox/package.json +1 -1
  262. package/examples/extensions/subagent/README.md +21 -11
  263. package/examples/extensions/subagent/adaptive-agent-runtime.ts +9 -3
  264. package/examples/extensions/subagent/agents/planner.md +1 -3
  265. package/examples/extensions/subagent/agents/reviewer.md +1 -3
  266. package/examples/extensions/subagent/agents/scout.md +1 -3
  267. package/examples/extensions/subagent/agents/worker.md +0 -1
  268. package/examples/extensions/subagent/agents.test.ts +9 -0
  269. package/examples/extensions/subagent/agents.ts +11 -4
  270. package/examples/extensions/subagent/deadline-budget.test.ts +32 -0
  271. package/examples/extensions/subagent/deadline-budget.ts +25 -1
  272. package/examples/extensions/subagent/fixtures/fake-omk-json.mjs +10 -0
  273. package/examples/extensions/subagent/index.ts +234 -55
  274. package/examples/extensions/subagent/subagent-execution-policy.test.ts +27 -0
  275. package/examples/extensions/subagent/subagent-extension-smoke.test.ts +167 -2
  276. package/examples/extensions/subagent/workflow-graph.test.ts +80 -0
  277. package/examples/extensions/subagent/workflow-graph.ts +89 -0
  278. package/examples/extensions/with-deps/package-lock.json +2 -2
  279. package/examples/extensions/with-deps/package.json +1 -1
  280. package/npm-shrinkwrap.json +253 -439
  281. package/package.json +8 -8
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-doctor-cli.d.ts","sourceRoot":"","sources":["../../src/commands/package-doctor-cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,iBAAiB,EAAyB,MAAM,4BAA4B,CAAC;AAIlH,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,4BAA4B;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACzD;AAqDD,wBAAsB,mBAAmB,CACxC,IAAI,EAAE,MAAM,EAAE,EACd,YAAY,GAAE,4BAAiC,GAC7C,OAAO,CAAC,uBAAuB,CAAC,CAiClC","sourcesContent":["import { APP_NAME, getAgentDir } from \"../config.ts\";\nimport { inspectPackageCompatibility, type PackageDoctorResult } from \"../core/package-doctor.ts\";\nimport { DefaultPackageManager, type PackageInspection, type ResolvedResource } from \"../core/package-manager.ts\";\nimport { redactSensitiveTextForced } from \"../core/redaction.ts\";\nimport { SettingsManager } from \"../core/settings-manager.ts\";\n\nexport interface PackageDoctorCliOutcome {\n\thandled: boolean;\n\texitCode: number;\n}\n\nexport interface PackageDoctorCliDependencies {\n\tcwd?: string;\n\tagentDir?: string;\n\twriteLine?: (line: string) => void;\n\tprepare?: (source: string) => Promise<PackageInspection>;\n}\n\nfunction usage(): string {\n\treturn `Usage: ${APP_NAME} package doctor <source>\\n\\nStatically inspects package.json manifest precedence and extension compatibility.\\nRemote npm packages are packed and boundedly extracted without dependency installation or lifecycle scripts.\\nThe doctor never imports or executes extension modules.`;\n}\n\nfunction writeJson(writeLine: (line: string) => void, value: unknown): void {\n\twriteLine(JSON.stringify(value, null, 2));\n}\n\nfunction sanitizeFailureMessage(message: string): string {\n\treturn redactSensitiveTextForced(message).replace(\n\t\t/\\b([a-z][a-z0-9+.-]*:\\/\\/)[^/@\\s]+@/giu,\n\t\t(_match, protocol: string) => `${protocol}[REDACTED]@`,\n\t);\n}\n\nfunction usageError(writeLine: (line: string) => void): PackageDoctorCliOutcome {\n\twriteJson(writeLine, {\n\t\tschemaVersion: 1,\n\t\terror: { code: \"cli-usage\", message: `Expected: ${APP_NAME} package doctor <source>` },\n\t});\n\treturn { handled: true, exitCode: 2 };\n}\n\nfunction enabledPaths(resources: ResolvedResource[]): string[] {\n\tconst paths: string[] = [];\n\tfor (const resource of resources) {\n\t\tif (resource.enabled) paths.push(resource.path);\n\t}\n\treturn paths;\n}\n\nfunction resourcePaths(inspection: PackageInspection): {\n\textensions: string[];\n\tskills: string[];\n\tprompts: string[];\n\tthemes: string[];\n} {\n\treturn {\n\t\textensions: enabledPaths(inspection.resources.extensions),\n\t\tskills: enabledPaths(inspection.resources.skills),\n\t\tprompts: enabledPaths(inspection.resources.prompts),\n\t\tthemes: enabledPaths(inspection.resources.themes),\n\t};\n}\n\nasync function defaultPrepare(source: string, cwd: string, agentDir: string): Promise<PackageInspection> {\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\tconst packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });\n\treturn packageManager.preparePackageInspection(source);\n}\n\nexport async function runPackageDoctorCli(\n\targs: string[],\n\tdependencies: PackageDoctorCliDependencies = {},\n): Promise<PackageDoctorCliOutcome> {\n\tif (args[0] !== \"package\" || args[1] !== \"doctor\") return { handled: false, exitCode: 0 };\n\tconst writeLine = dependencies.writeLine ?? ((line: string) => process.stdout.write(`${line}\\n`));\n\tconst rest = args.slice(2);\n\tif (rest.length === 1 && (rest[0] === \"--help\" || rest[0] === \"-h\")) {\n\t\twriteLine(usage());\n\t\treturn { handled: true, exitCode: 0 };\n\t}\n\tif (rest.length !== 1 || !rest[0] || rest[0].startsWith(\"-\")) return usageError(writeLine);\n\n\tconst source = rest[0];\n\tconst cwd = dependencies.cwd ?? process.cwd();\n\tconst agentDir = dependencies.agentDir ?? getAgentDir();\n\tlet inspection: PackageInspection | undefined;\n\ttry {\n\t\tinspection = await (dependencies.prepare ? dependencies.prepare(source) : defaultPrepare(source, cwd, agentDir));\n\t\tconst result: PackageDoctorResult = inspectPackageCompatibility({\n\t\t\tsource,\n\t\t\tpackageRoot: inspection.packageRoot,\n\t\t\tresources: resourcePaths(inspection),\n\t\t});\n\t\twriteJson(writeLine, result);\n\t\treturn { handled: true, exitCode: result.compatible ? 0 : 1 };\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"Package inspection failed\";\n\t\twriteJson(writeLine, {\n\t\t\tschemaVersion: 1,\n\t\t\terror: { code: \"package-doctor-failed\", message: sanitizeFailureMessage(message) },\n\t\t});\n\t\treturn { handled: true, exitCode: 1 };\n\t} finally {\n\t\tinspection?.cleanup?.();\n\t}\n}\n"]}
@@ -0,0 +1,80 @@
1
+ import { APP_NAME, getAgentDir } from "../config.js";
2
+ import { inspectPackageCompatibility } from "../core/package-doctor.js";
3
+ import { DefaultPackageManager } from "../core/package-manager.js";
4
+ import { redactSensitiveTextForced } from "../core/redaction.js";
5
+ import { SettingsManager } from "../core/settings-manager.js";
6
+ function usage() {
7
+ return `Usage: ${APP_NAME} package doctor <source>\n\nStatically inspects package.json manifest precedence and extension compatibility.\nRemote npm packages are packed and boundedly extracted without dependency installation or lifecycle scripts.\nThe doctor never imports or executes extension modules.`;
8
+ }
9
+ function writeJson(writeLine, value) {
10
+ writeLine(JSON.stringify(value, null, 2));
11
+ }
12
+ function sanitizeFailureMessage(message) {
13
+ return redactSensitiveTextForced(message).replace(/\b([a-z][a-z0-9+.-]*:\/\/)[^/@\s]+@/giu, (_match, protocol) => `${protocol}[REDACTED]@`);
14
+ }
15
+ function usageError(writeLine) {
16
+ writeJson(writeLine, {
17
+ schemaVersion: 1,
18
+ error: { code: "cli-usage", message: `Expected: ${APP_NAME} package doctor <source>` },
19
+ });
20
+ return { handled: true, exitCode: 2 };
21
+ }
22
+ function enabledPaths(resources) {
23
+ const paths = [];
24
+ for (const resource of resources) {
25
+ if (resource.enabled)
26
+ paths.push(resource.path);
27
+ }
28
+ return paths;
29
+ }
30
+ function resourcePaths(inspection) {
31
+ return {
32
+ extensions: enabledPaths(inspection.resources.extensions),
33
+ skills: enabledPaths(inspection.resources.skills),
34
+ prompts: enabledPaths(inspection.resources.prompts),
35
+ themes: enabledPaths(inspection.resources.themes),
36
+ };
37
+ }
38
+ async function defaultPrepare(source, cwd, agentDir) {
39
+ const settingsManager = SettingsManager.create(cwd, agentDir);
40
+ const packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });
41
+ return packageManager.preparePackageInspection(source);
42
+ }
43
+ export async function runPackageDoctorCli(args, dependencies = {}) {
44
+ if (args[0] !== "package" || args[1] !== "doctor")
45
+ return { handled: false, exitCode: 0 };
46
+ const writeLine = dependencies.writeLine ?? ((line) => process.stdout.write(`${line}\n`));
47
+ const rest = args.slice(2);
48
+ if (rest.length === 1 && (rest[0] === "--help" || rest[0] === "-h")) {
49
+ writeLine(usage());
50
+ return { handled: true, exitCode: 0 };
51
+ }
52
+ if (rest.length !== 1 || !rest[0] || rest[0].startsWith("-"))
53
+ return usageError(writeLine);
54
+ const source = rest[0];
55
+ const cwd = dependencies.cwd ?? process.cwd();
56
+ const agentDir = dependencies.agentDir ?? getAgentDir();
57
+ let inspection;
58
+ try {
59
+ inspection = await (dependencies.prepare ? dependencies.prepare(source) : defaultPrepare(source, cwd, agentDir));
60
+ const result = inspectPackageCompatibility({
61
+ source,
62
+ packageRoot: inspection.packageRoot,
63
+ resources: resourcePaths(inspection),
64
+ });
65
+ writeJson(writeLine, result);
66
+ return { handled: true, exitCode: result.compatible ? 0 : 1 };
67
+ }
68
+ catch (error) {
69
+ const message = error instanceof Error ? error.message : "Package inspection failed";
70
+ writeJson(writeLine, {
71
+ schemaVersion: 1,
72
+ error: { code: "package-doctor-failed", message: sanitizeFailureMessage(message) },
73
+ });
74
+ return { handled: true, exitCode: 1 };
75
+ }
76
+ finally {
77
+ inspection?.cleanup?.();
78
+ }
79
+ }
80
+ //# sourceMappingURL=package-doctor-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-doctor-cli.js","sourceRoot":"","sources":["../../src/commands/package-doctor-cli.ts"],"names":[],"mappings":"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","sourcesContent":["import { APP_NAME, getAgentDir } from \"../config.ts\";\nimport { inspectPackageCompatibility, type PackageDoctorResult } from \"../core/package-doctor.ts\";\nimport { DefaultPackageManager, type PackageInspection, type ResolvedResource } from \"../core/package-manager.ts\";\nimport { redactSensitiveTextForced } from \"../core/redaction.ts\";\nimport { SettingsManager } from \"../core/settings-manager.ts\";\n\nexport interface PackageDoctorCliOutcome {\n\thandled: boolean;\n\texitCode: number;\n}\n\nexport interface PackageDoctorCliDependencies {\n\tcwd?: string;\n\tagentDir?: string;\n\twriteLine?: (line: string) => void;\n\tprepare?: (source: string) => Promise<PackageInspection>;\n}\n\nfunction usage(): string {\n\treturn `Usage: ${APP_NAME} package doctor <source>\\n\\nStatically inspects package.json manifest precedence and extension compatibility.\\nRemote npm packages are packed and boundedly extracted without dependency installation or lifecycle scripts.\\nThe doctor never imports or executes extension modules.`;\n}\n\nfunction writeJson(writeLine: (line: string) => void, value: unknown): void {\n\twriteLine(JSON.stringify(value, null, 2));\n}\n\nfunction sanitizeFailureMessage(message: string): string {\n\treturn redactSensitiveTextForced(message).replace(\n\t\t/\\b([a-z][a-z0-9+.-]*:\\/\\/)[^/@\\s]+@/giu,\n\t\t(_match, protocol: string) => `${protocol}[REDACTED]@`,\n\t);\n}\n\nfunction usageError(writeLine: (line: string) => void): PackageDoctorCliOutcome {\n\twriteJson(writeLine, {\n\t\tschemaVersion: 1,\n\t\terror: { code: \"cli-usage\", message: `Expected: ${APP_NAME} package doctor <source>` },\n\t});\n\treturn { handled: true, exitCode: 2 };\n}\n\nfunction enabledPaths(resources: ResolvedResource[]): string[] {\n\tconst paths: string[] = [];\n\tfor (const resource of resources) {\n\t\tif (resource.enabled) paths.push(resource.path);\n\t}\n\treturn paths;\n}\n\nfunction resourcePaths(inspection: PackageInspection): {\n\textensions: string[];\n\tskills: string[];\n\tprompts: string[];\n\tthemes: string[];\n} {\n\treturn {\n\t\textensions: enabledPaths(inspection.resources.extensions),\n\t\tskills: enabledPaths(inspection.resources.skills),\n\t\tprompts: enabledPaths(inspection.resources.prompts),\n\t\tthemes: enabledPaths(inspection.resources.themes),\n\t};\n}\n\nasync function defaultPrepare(source: string, cwd: string, agentDir: string): Promise<PackageInspection> {\n\tconst settingsManager = SettingsManager.create(cwd, agentDir);\n\tconst packageManager = new DefaultPackageManager({ cwd, agentDir, settingsManager });\n\treturn packageManager.preparePackageInspection(source);\n}\n\nexport async function runPackageDoctorCli(\n\targs: string[],\n\tdependencies: PackageDoctorCliDependencies = {},\n): Promise<PackageDoctorCliOutcome> {\n\tif (args[0] !== \"package\" || args[1] !== \"doctor\") return { handled: false, exitCode: 0 };\n\tconst writeLine = dependencies.writeLine ?? ((line: string) => process.stdout.write(`${line}\\n`));\n\tconst rest = args.slice(2);\n\tif (rest.length === 1 && (rest[0] === \"--help\" || rest[0] === \"-h\")) {\n\t\twriteLine(usage());\n\t\treturn { handled: true, exitCode: 0 };\n\t}\n\tif (rest.length !== 1 || !rest[0] || rest[0].startsWith(\"-\")) return usageError(writeLine);\n\n\tconst source = rest[0];\n\tconst cwd = dependencies.cwd ?? process.cwd();\n\tconst agentDir = dependencies.agentDir ?? getAgentDir();\n\tlet inspection: PackageInspection | undefined;\n\ttry {\n\t\tinspection = await (dependencies.prepare ? dependencies.prepare(source) : defaultPrepare(source, cwd, agentDir));\n\t\tconst result: PackageDoctorResult = inspectPackageCompatibility({\n\t\t\tsource,\n\t\t\tpackageRoot: inspection.packageRoot,\n\t\t\tresources: resourcePaths(inspection),\n\t\t});\n\t\twriteJson(writeLine, result);\n\t\treturn { handled: true, exitCode: result.compatible ? 0 : 1 };\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : \"Package inspection failed\";\n\t\twriteJson(writeLine, {\n\t\t\tschemaVersion: 1,\n\t\t\terror: { code: \"package-doctor-failed\", message: sanitizeFailureMessage(message) },\n\t\t});\n\t\treturn { handled: true, exitCode: 1 };\n\t} finally {\n\t\tinspection?.cleanup?.();\n\t}\n}\n"]}
@@ -0,0 +1,11 @@
1
+ export interface RouterFeedbackCliOverrides {
2
+ readonly cwd?: string;
3
+ readonly writeLine?: (line: string) => void;
4
+ }
5
+ export interface RouterFeedbackCliOutcome {
6
+ readonly handled: boolean;
7
+ readonly exitCode: 0 | 1 | 2;
8
+ }
9
+ /** Compile the privacy-safe ledger without starting an interactive session. */
10
+ export declare function runRouterFeedbackCli(args: readonly string[], overrides?: RouterFeedbackCliOverrides): RouterFeedbackCliOutcome;
11
+ //# sourceMappingURL=router-feedback-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router-feedback-cli.d.ts","sourceRoot":"","sources":["../../src/commands/router-feedback-cli.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,0BAA0B;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC7B;AAkED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CACnC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,SAAS,GAAE,0BAA+B,GACxC,wBAAwB,CA6B1B","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { compileBiasSnapshot } from \"../core/reasoning-router-bias.ts\";\nimport { getRepositoryRouterLearningPaths } from \"../core/repository-learning-scope.ts\";\n\nconst USAGE = \"Usage: omk router-feedback compile-bias [--cwd <dir>] [--ledger <path>] [--out <path>]\";\n\nexport interface RouterFeedbackCliOverrides {\n\treadonly cwd?: string;\n\treadonly writeLine?: (line: string) => void;\n}\n\nexport interface RouterFeedbackCliOutcome {\n\treadonly handled: boolean;\n\treadonly exitCode: 0 | 1 | 2;\n}\n\ntype ParsedArgs =\n\t| { readonly kind: \"absent\" }\n\t| { readonly kind: \"help\" }\n\t| { readonly kind: \"error\"; readonly message: string }\n\t| {\n\t\t\treadonly kind: \"run\";\n\t\t\treadonly cwd?: string;\n\t\t\treadonly ledgerPath?: string;\n\t\t\treadonly outPath?: string;\n\t };\n\nfunction parseArgs(args: readonly string[]): ParsedArgs {\n\tif (args[0] !== \"router-feedback\") return { kind: \"absent\" };\n\tif (args[1] === \"--help\" || args[1] === \"-h\") return { kind: \"help\" };\n\tif (args[1] !== \"compile-bias\") {\n\t\treturn { kind: \"error\", message: args[1] ? `unknown subcommand: ${args[1]}` : \"missing subcommand\" };\n\t}\n\tlet cwd: string | undefined;\n\tlet ledgerPath: string | undefined;\n\tlet outPath: string | undefined;\n\tfor (let index = 2; index < args.length; index++) {\n\t\tconst arg = args[index];\n\t\tif (arg === \"--help\" || arg === \"-h\") return { kind: \"help\" };\n\t\tif (arg === \"--cwd\" || arg === \"--ledger\" || arg === \"--out\") {\n\t\t\tconst value = args[++index];\n\t\t\tif (!value) return { kind: \"error\", message: `${arg} requires a path` };\n\t\t\tif (arg === \"--cwd\") cwd = value;\n\t\t\telse if (arg === \"--ledger\") ledgerPath = value;\n\t\t\telse outPath = value;\n\t\t\tcontinue;\n\t\t}\n\t\treturn { kind: \"error\", message: `unknown argument: ${arg}` };\n\t}\n\treturn { kind: \"run\", cwd, ledgerPath, outPath };\n}\n\nfunction readLedgerEntries(ledgerPath: string): { readonly entries: unknown[]; readonly parseErrors: number } {\n\tif (!existsSync(ledgerPath)) return { entries: [], parseErrors: 0 };\n\tconst entries: unknown[] = [];\n\tlet parseErrors = 0;\n\tfor (const line of readFileSync(ledgerPath, \"utf8\").split(/\\r?\\n/u)) {\n\t\tif (!line.trim()) continue;\n\t\ttry {\n\t\t\tentries.push(JSON.parse(line));\n\t\t} catch {\n\t\t\tparseErrors++;\n\t\t}\n\t}\n\treturn { entries, parseErrors };\n}\n\nfunction writeSnapshotAtomically(outPath: string, snapshotJson: string): void {\n\tconst dir = dirname(outPath);\n\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\tconst tempPath = `${outPath}.${randomUUID()}.tmp`;\n\ttry {\n\t\twriteFileSync(tempPath, snapshotJson, { encoding: \"utf8\", flag: \"wx\", mode: 0o600 });\n\t\tchmodSync(tempPath, 0o600);\n\t\trenameSync(tempPath, outPath);\n\t} finally {\n\t\trmSync(tempPath, { force: true });\n\t}\n}\n\n/** Compile the privacy-safe ledger without starting an interactive session. */\nexport function runRouterFeedbackCli(\n\targs: readonly string[],\n\toverrides: RouterFeedbackCliOverrides = {},\n): RouterFeedbackCliOutcome {\n\tconst parsed = parseArgs(args);\n\tif (parsed.kind === \"absent\") return { handled: false, exitCode: 0 };\n\tconst writeLine = overrides.writeLine ?? ((line: string) => process.stderr.write(`${line}\\n`));\n\tif (parsed.kind === \"help\") {\n\t\twriteLine(USAGE);\n\t\treturn { handled: true, exitCode: 0 };\n\t}\n\tif (parsed.kind === \"error\") {\n\t\twriteLine(JSON.stringify({ status: \"refused\", error: parsed.message, usage: USAGE }));\n\t\treturn { handled: true, exitCode: 2 };\n\t}\n\n\ttry {\n\t\tconst cwd = parsed.cwd ?? overrides.cwd ?? process.cwd();\n\t\tconst scopedPaths = getRepositoryRouterLearningPaths(cwd, getAgentDir());\n\t\tconst ledgerPath = parsed.ledgerPath ?? scopedPaths.ledgerPath;\n\t\tconst outPath = parsed.outPath ?? scopedPaths.biasSnapshotPath;\n\t\tconst { entries, parseErrors } = readLedgerEntries(ledgerPath);\n\t\tconst snapshot = compileBiasSnapshot(entries);\n\t\twriteSnapshotAtomically(outPath, `${JSON.stringify(snapshot, null, 2)}\\n`);\n\t\twriteLine(\n\t\t\t`router-feedback compile: routerVersion=v4 considered=${snapshot.consideredCount} dropped=${snapshot.droppedCount} parseErrors=${parseErrors} biasCells=${snapshot.biasCells.length} out=${outPath}`,\n\t\t);\n\t\treturn { handled: true, exitCode: 0 };\n\t} catch (error: unknown) {\n\t\twriteLine(JSON.stringify({ status: \"error\", error: error instanceof Error ? error.message : String(error) }));\n\t\treturn { handled: true, exitCode: 1 };\n\t}\n}\n"]}
@@ -0,0 +1,99 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { getAgentDir } from "../config.js";
5
+ import { compileBiasSnapshot } from "../core/reasoning-router-bias.js";
6
+ import { getRepositoryRouterLearningPaths } from "../core/repository-learning-scope.js";
7
+ const USAGE = "Usage: omk router-feedback compile-bias [--cwd <dir>] [--ledger <path>] [--out <path>]";
8
+ function parseArgs(args) {
9
+ if (args[0] !== "router-feedback")
10
+ return { kind: "absent" };
11
+ if (args[1] === "--help" || args[1] === "-h")
12
+ return { kind: "help" };
13
+ if (args[1] !== "compile-bias") {
14
+ return { kind: "error", message: args[1] ? `unknown subcommand: ${args[1]}` : "missing subcommand" };
15
+ }
16
+ let cwd;
17
+ let ledgerPath;
18
+ let outPath;
19
+ for (let index = 2; index < args.length; index++) {
20
+ const arg = args[index];
21
+ if (arg === "--help" || arg === "-h")
22
+ return { kind: "help" };
23
+ if (arg === "--cwd" || arg === "--ledger" || arg === "--out") {
24
+ const value = args[++index];
25
+ if (!value)
26
+ return { kind: "error", message: `${arg} requires a path` };
27
+ if (arg === "--cwd")
28
+ cwd = value;
29
+ else if (arg === "--ledger")
30
+ ledgerPath = value;
31
+ else
32
+ outPath = value;
33
+ continue;
34
+ }
35
+ return { kind: "error", message: `unknown argument: ${arg}` };
36
+ }
37
+ return { kind: "run", cwd, ledgerPath, outPath };
38
+ }
39
+ function readLedgerEntries(ledgerPath) {
40
+ if (!existsSync(ledgerPath))
41
+ return { entries: [], parseErrors: 0 };
42
+ const entries = [];
43
+ let parseErrors = 0;
44
+ for (const line of readFileSync(ledgerPath, "utf8").split(/\r?\n/u)) {
45
+ if (!line.trim())
46
+ continue;
47
+ try {
48
+ entries.push(JSON.parse(line));
49
+ }
50
+ catch {
51
+ parseErrors++;
52
+ }
53
+ }
54
+ return { entries, parseErrors };
55
+ }
56
+ function writeSnapshotAtomically(outPath, snapshotJson) {
57
+ const dir = dirname(outPath);
58
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
59
+ const tempPath = `${outPath}.${randomUUID()}.tmp`;
60
+ try {
61
+ writeFileSync(tempPath, snapshotJson, { encoding: "utf8", flag: "wx", mode: 0o600 });
62
+ chmodSync(tempPath, 0o600);
63
+ renameSync(tempPath, outPath);
64
+ }
65
+ finally {
66
+ rmSync(tempPath, { force: true });
67
+ }
68
+ }
69
+ /** Compile the privacy-safe ledger without starting an interactive session. */
70
+ export function runRouterFeedbackCli(args, overrides = {}) {
71
+ const parsed = parseArgs(args);
72
+ if (parsed.kind === "absent")
73
+ return { handled: false, exitCode: 0 };
74
+ const writeLine = overrides.writeLine ?? ((line) => process.stderr.write(`${line}\n`));
75
+ if (parsed.kind === "help") {
76
+ writeLine(USAGE);
77
+ return { handled: true, exitCode: 0 };
78
+ }
79
+ if (parsed.kind === "error") {
80
+ writeLine(JSON.stringify({ status: "refused", error: parsed.message, usage: USAGE }));
81
+ return { handled: true, exitCode: 2 };
82
+ }
83
+ try {
84
+ const cwd = parsed.cwd ?? overrides.cwd ?? process.cwd();
85
+ const scopedPaths = getRepositoryRouterLearningPaths(cwd, getAgentDir());
86
+ const ledgerPath = parsed.ledgerPath ?? scopedPaths.ledgerPath;
87
+ const outPath = parsed.outPath ?? scopedPaths.biasSnapshotPath;
88
+ const { entries, parseErrors } = readLedgerEntries(ledgerPath);
89
+ const snapshot = compileBiasSnapshot(entries);
90
+ writeSnapshotAtomically(outPath, `${JSON.stringify(snapshot, null, 2)}\n`);
91
+ writeLine(`router-feedback compile: routerVersion=v4 considered=${snapshot.consideredCount} dropped=${snapshot.droppedCount} parseErrors=${parseErrors} biasCells=${snapshot.biasCells.length} out=${outPath}`);
92
+ return { handled: true, exitCode: 0 };
93
+ }
94
+ catch (error) {
95
+ writeLine(JSON.stringify({ status: "error", error: error instanceof Error ? error.message : String(error) }));
96
+ return { handled: true, exitCode: 1 };
97
+ }
98
+ }
99
+ //# sourceMappingURL=router-feedback-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router-feedback-cli.js","sourceRoot":"","sources":["../../src/commands/router-feedback-cli.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { compileBiasSnapshot } from \"../core/reasoning-router-bias.ts\";\nimport { getRepositoryRouterLearningPaths } from \"../core/repository-learning-scope.ts\";\n\nconst USAGE = \"Usage: omk router-feedback compile-bias [--cwd <dir>] [--ledger <path>] [--out <path>]\";\n\nexport interface RouterFeedbackCliOverrides {\n\treadonly cwd?: string;\n\treadonly writeLine?: (line: string) => void;\n}\n\nexport interface RouterFeedbackCliOutcome {\n\treadonly handled: boolean;\n\treadonly exitCode: 0 | 1 | 2;\n}\n\ntype ParsedArgs =\n\t| { readonly kind: \"absent\" }\n\t| { readonly kind: \"help\" }\n\t| { readonly kind: \"error\"; readonly message: string }\n\t| {\n\t\t\treadonly kind: \"run\";\n\t\t\treadonly cwd?: string;\n\t\t\treadonly ledgerPath?: string;\n\t\t\treadonly outPath?: string;\n\t };\n\nfunction parseArgs(args: readonly string[]): ParsedArgs {\n\tif (args[0] !== \"router-feedback\") return { kind: \"absent\" };\n\tif (args[1] === \"--help\" || args[1] === \"-h\") return { kind: \"help\" };\n\tif (args[1] !== \"compile-bias\") {\n\t\treturn { kind: \"error\", message: args[1] ? `unknown subcommand: ${args[1]}` : \"missing subcommand\" };\n\t}\n\tlet cwd: string | undefined;\n\tlet ledgerPath: string | undefined;\n\tlet outPath: string | undefined;\n\tfor (let index = 2; index < args.length; index++) {\n\t\tconst arg = args[index];\n\t\tif (arg === \"--help\" || arg === \"-h\") return { kind: \"help\" };\n\t\tif (arg === \"--cwd\" || arg === \"--ledger\" || arg === \"--out\") {\n\t\t\tconst value = args[++index];\n\t\t\tif (!value) return { kind: \"error\", message: `${arg} requires a path` };\n\t\t\tif (arg === \"--cwd\") cwd = value;\n\t\t\telse if (arg === \"--ledger\") ledgerPath = value;\n\t\t\telse outPath = value;\n\t\t\tcontinue;\n\t\t}\n\t\treturn { kind: \"error\", message: `unknown argument: ${arg}` };\n\t}\n\treturn { kind: \"run\", cwd, ledgerPath, outPath };\n}\n\nfunction readLedgerEntries(ledgerPath: string): { readonly entries: unknown[]; readonly parseErrors: number } {\n\tif (!existsSync(ledgerPath)) return { entries: [], parseErrors: 0 };\n\tconst entries: unknown[] = [];\n\tlet parseErrors = 0;\n\tfor (const line of readFileSync(ledgerPath, \"utf8\").split(/\\r?\\n/u)) {\n\t\tif (!line.trim()) continue;\n\t\ttry {\n\t\t\tentries.push(JSON.parse(line));\n\t\t} catch {\n\t\t\tparseErrors++;\n\t\t}\n\t}\n\treturn { entries, parseErrors };\n}\n\nfunction writeSnapshotAtomically(outPath: string, snapshotJson: string): void {\n\tconst dir = dirname(outPath);\n\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\tconst tempPath = `${outPath}.${randomUUID()}.tmp`;\n\ttry {\n\t\twriteFileSync(tempPath, snapshotJson, { encoding: \"utf8\", flag: \"wx\", mode: 0o600 });\n\t\tchmodSync(tempPath, 0o600);\n\t\trenameSync(tempPath, outPath);\n\t} finally {\n\t\trmSync(tempPath, { force: true });\n\t}\n}\n\n/** Compile the privacy-safe ledger without starting an interactive session. */\nexport function runRouterFeedbackCli(\n\targs: readonly string[],\n\toverrides: RouterFeedbackCliOverrides = {},\n): RouterFeedbackCliOutcome {\n\tconst parsed = parseArgs(args);\n\tif (parsed.kind === \"absent\") return { handled: false, exitCode: 0 };\n\tconst writeLine = overrides.writeLine ?? ((line: string) => process.stderr.write(`${line}\\n`));\n\tif (parsed.kind === \"help\") {\n\t\twriteLine(USAGE);\n\t\treturn { handled: true, exitCode: 0 };\n\t}\n\tif (parsed.kind === \"error\") {\n\t\twriteLine(JSON.stringify({ status: \"refused\", error: parsed.message, usage: USAGE }));\n\t\treturn { handled: true, exitCode: 2 };\n\t}\n\n\ttry {\n\t\tconst cwd = parsed.cwd ?? overrides.cwd ?? process.cwd();\n\t\tconst scopedPaths = getRepositoryRouterLearningPaths(cwd, getAgentDir());\n\t\tconst ledgerPath = parsed.ledgerPath ?? scopedPaths.ledgerPath;\n\t\tconst outPath = parsed.outPath ?? scopedPaths.biasSnapshotPath;\n\t\tconst { entries, parseErrors } = readLedgerEntries(ledgerPath);\n\t\tconst snapshot = compileBiasSnapshot(entries);\n\t\twriteSnapshotAtomically(outPath, `${JSON.stringify(snapshot, null, 2)}\\n`);\n\t\twriteLine(\n\t\t\t`router-feedback compile: routerVersion=v4 considered=${snapshot.consideredCount} dropped=${snapshot.droppedCount} parseErrors=${parseErrors} biasCells=${snapshot.biasCells.length} out=${outPath}`,\n\t\t);\n\t\treturn { handled: true, exitCode: 0 };\n\t} catch (error: unknown) {\n\t\twriteLine(JSON.stringify({ status: \"error\", error: error instanceof Error ? error.message : String(error) }));\n\t\treturn { handled: true, exitCode: 1 };\n\t}\n}\n"]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `omk stats` — read the per-turn metrics JSONL and print an aggregate.
3
+ *
4
+ * Answers the questions the harness previously had no data for: which tools
5
+ * fail, how long turns take, what they cost, and whether the context cache is
6
+ * doing anything. Read-only; it never starts a session.
7
+ */
8
+ import { type TurnMetricsSummary } from "../core/turn-metrics.ts";
9
+ export interface StatsCliOverrides {
10
+ readonly cwd?: string;
11
+ readonly writeLine?: (line: string) => void;
12
+ }
13
+ export interface StatsCliOutcome {
14
+ readonly handled: boolean;
15
+ readonly exitCode: 0 | 1 | 2;
16
+ }
17
+ /** Render the human-readable report. Pure, so its shape is directly testable. */
18
+ export declare function renderStatsReport(summary: TurnMetricsSummary, filePath: string): string[];
19
+ export declare function runStatsCli(args: readonly string[], overrides?: StatsCliOverrides): StatsCliOutcome;
20
+ //# sourceMappingURL=stats-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-cli.d.ts","sourceRoot":"","sources":["../../src/commands/stats-cli.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAI5F,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC7B;AA2CD,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAoCzF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,GAAE,iBAAsB,GAAG,eAAe,CAwBvG","sourcesContent":["/**\n * `omk stats` — read the per-turn metrics JSONL and print an aggregate.\n *\n * Answers the questions the harness previously had no data for: which tools\n * fail, how long turns take, what they cost, and whether the context cache is\n * doing anything. Read-only; it never starts a session.\n */\n\nimport { join, resolve } from \"node:path\";\nimport { summarizeTurnMetricsFile, type TurnMetricsSummary } from \"../core/turn-metrics.ts\";\n\nconst USAGE = \"Usage: omk stats [--dir <path>] [--json]\";\n\nexport interface StatsCliOverrides {\n\treadonly cwd?: string;\n\treadonly writeLine?: (line: string) => void;\n}\n\nexport interface StatsCliOutcome {\n\treadonly handled: boolean;\n\treadonly exitCode: 0 | 1 | 2;\n}\n\ntype ParsedArgs =\n\t| { kind: \"absent\" }\n\t| { kind: \"help\" }\n\t| { kind: \"error\"; message: string }\n\t| { kind: \"run\"; dir?: string; json: boolean };\n\nfunction parseArgs(args: readonly string[]): ParsedArgs {\n\tif (args[0] !== \"stats\") return { kind: \"absent\" };\n\tlet dir: string | undefined;\n\tlet json = false;\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst arg = args[index];\n\t\tif (arg === \"--help\" || arg === \"-h\") return { kind: \"help\" };\n\t\tif (arg === \"--json\") {\n\t\t\tjson = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--dir\") {\n\t\t\tconst value = args[++index];\n\t\t\tif (value === undefined) return { kind: \"error\", message: \"--dir requires a path\" };\n\t\t\tdir = value;\n\t\t\tcontinue;\n\t\t}\n\t\treturn { kind: \"error\", message: `unknown argument: ${arg}` };\n\t}\n\treturn { kind: \"run\", dir, json };\n}\n\nfunction formatMs(value: number): string {\n\tif (value >= 1000) return `${(value / 1000).toFixed(1)}s`;\n\treturn `${Math.round(value)}ms`;\n}\n\nfunction formatPercent(value: number | undefined): string {\n\treturn value === undefined ? \"n/a\" : `${(value * 100).toFixed(1)}%`;\n}\n\nfunction formatCount(value: number): string {\n\treturn value.toLocaleString(\"en-US\");\n}\n\n/** Render the human-readable report. Pure, so its shape is directly testable. */\nexport function renderStatsReport(summary: TurnMetricsSummary, filePath: string): string[] {\n\tif (summary.turns === 0) {\n\t\treturn [\n\t\t\t`No turn metrics recorded yet (${filePath}).`,\n\t\t\t\"Metrics are written as sessions run. Set OMK_TURN_METRICS=0 to disable.\",\n\t\t];\n\t}\n\n\tconst lines = [\n\t\t`Turn metrics — ${formatCount(summary.turns)} turns across ${formatCount(summary.sessions)} session(s)`,\n\t\t` models ${summary.models.length > 0 ? summary.models.join(\", \") : \"unknown\"}`,\n\t\t` turn duration p50 ${formatMs(summary.turnDurationP50Ms)} · p95 ${formatMs(summary.turnDurationP95Ms)}`,\n\t];\n\tif (summary.timeToFirstChunkP50Ms !== undefined) {\n\t\tlines.push(` first chunk p50 ${formatMs(summary.timeToFirstChunkP50Ms)}`);\n\t}\n\tlines.push(\n\t\t` input ${formatCount(summary.totals.input)} · output ${formatCount(summary.totals.output)} · cacheRead ${formatCount(summary.totals.cacheRead)} · cacheWrite ${formatCount(summary.totals.cacheWrite)}`,\n\t\t` cache read share ${formatPercent(summary.cacheReadShare)} of prompt-side usage`,\n\t\t` cost $${summary.totalCostUsd.toFixed(4)}`,\n\t\t` compactions ${formatCount(summary.compactions)} · failovers ${formatCount(summary.failovers)} · ctx plan hit ${formatPercent(summary.contextCachePlanHitRate)}`,\n\t);\n\n\tif (summary.tools.length > 0) {\n\t\tlines.push(\"\", \" tool calls fail% p50 p95 total\");\n\t\tfor (const tool of summary.tools.slice(0, 20)) {\n\t\t\tlines.push(\n\t\t\t\t` ${tool.name.padEnd(20).slice(0, 20)} ${String(tool.calls).padStart(5)} ${formatPercent(tool.failureRate).padStart(7)} ${formatMs(tool.p50Ms).padStart(7)} ${formatMs(tool.p95Ms).padStart(7)} ${formatMs(tool.totalMs).padStart(8)}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (summary.malformedLines > 0) {\n\t\tlines.push(\"\", ` ${formatCount(summary.malformedLines)} unparseable line(s) skipped`);\n\t}\n\treturn lines;\n}\n\nexport function runStatsCli(args: readonly string[], overrides: StatsCliOverrides = {}): StatsCliOutcome {\n\tconst parsed = parseArgs(args);\n\tif (parsed.kind === \"absent\") return { handled: false, exitCode: 0 };\n\tconst writeLine = overrides.writeLine ?? ((line: string) => console.log(line));\n\tif (parsed.kind === \"help\") {\n\t\twriteLine(USAGE);\n\t\treturn { handled: true, exitCode: 0 };\n\t}\n\tif (parsed.kind === \"error\") {\n\t\twriteLine(JSON.stringify({ status: \"refused\", error: parsed.message, usage: USAGE }));\n\t\treturn { handled: true, exitCode: 2 };\n\t}\n\n\tconst cwd = resolve(overrides.cwd ?? process.cwd());\n\tconst dir = parsed.dir ?? process.env.OMK_TURN_METRICS_DIR ?? join(cwd, \".omk\", \"metrics\");\n\tconst filePath = join(resolve(dir), \"turns.jsonl\");\n\tconst summary = summarizeTurnMetricsFile(filePath);\n\n\tif (parsed.json) {\n\t\twriteLine(JSON.stringify({ status: \"ok\", path: filePath, summary }));\n\t\treturn { handled: true, exitCode: 0 };\n\t}\n\tfor (const line of renderStatsReport(summary, filePath)) writeLine(line);\n\treturn { handled: true, exitCode: 0 };\n}\n"]}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * `omk stats` — read the per-turn metrics JSONL and print an aggregate.
3
+ *
4
+ * Answers the questions the harness previously had no data for: which tools
5
+ * fail, how long turns take, what they cost, and whether the context cache is
6
+ * doing anything. Read-only; it never starts a session.
7
+ */
8
+ import { join, resolve } from "node:path";
9
+ import { summarizeTurnMetricsFile } from "../core/turn-metrics.js";
10
+ const USAGE = "Usage: omk stats [--dir <path>] [--json]";
11
+ function parseArgs(args) {
12
+ if (args[0] !== "stats")
13
+ return { kind: "absent" };
14
+ let dir;
15
+ let json = false;
16
+ for (let index = 1; index < args.length; index++) {
17
+ const arg = args[index];
18
+ if (arg === "--help" || arg === "-h")
19
+ return { kind: "help" };
20
+ if (arg === "--json") {
21
+ json = true;
22
+ continue;
23
+ }
24
+ if (arg === "--dir") {
25
+ const value = args[++index];
26
+ if (value === undefined)
27
+ return { kind: "error", message: "--dir requires a path" };
28
+ dir = value;
29
+ continue;
30
+ }
31
+ return { kind: "error", message: `unknown argument: ${arg}` };
32
+ }
33
+ return { kind: "run", dir, json };
34
+ }
35
+ function formatMs(value) {
36
+ if (value >= 1000)
37
+ return `${(value / 1000).toFixed(1)}s`;
38
+ return `${Math.round(value)}ms`;
39
+ }
40
+ function formatPercent(value) {
41
+ return value === undefined ? "n/a" : `${(value * 100).toFixed(1)}%`;
42
+ }
43
+ function formatCount(value) {
44
+ return value.toLocaleString("en-US");
45
+ }
46
+ /** Render the human-readable report. Pure, so its shape is directly testable. */
47
+ export function renderStatsReport(summary, filePath) {
48
+ if (summary.turns === 0) {
49
+ return [
50
+ `No turn metrics recorded yet (${filePath}).`,
51
+ "Metrics are written as sessions run. Set OMK_TURN_METRICS=0 to disable.",
52
+ ];
53
+ }
54
+ const lines = [
55
+ `Turn metrics — ${formatCount(summary.turns)} turns across ${formatCount(summary.sessions)} session(s)`,
56
+ ` models ${summary.models.length > 0 ? summary.models.join(", ") : "unknown"}`,
57
+ ` turn duration p50 ${formatMs(summary.turnDurationP50Ms)} · p95 ${formatMs(summary.turnDurationP95Ms)}`,
58
+ ];
59
+ if (summary.timeToFirstChunkP50Ms !== undefined) {
60
+ lines.push(` first chunk p50 ${formatMs(summary.timeToFirstChunkP50Ms)}`);
61
+ }
62
+ lines.push(` input ${formatCount(summary.totals.input)} · output ${formatCount(summary.totals.output)} · cacheRead ${formatCount(summary.totals.cacheRead)} · cacheWrite ${formatCount(summary.totals.cacheWrite)}`, ` cache read share ${formatPercent(summary.cacheReadShare)} of prompt-side usage`, ` cost $${summary.totalCostUsd.toFixed(4)}`, ` compactions ${formatCount(summary.compactions)} · failovers ${formatCount(summary.failovers)} · ctx plan hit ${formatPercent(summary.contextCachePlanHitRate)}`);
63
+ if (summary.tools.length > 0) {
64
+ lines.push("", " tool calls fail% p50 p95 total");
65
+ for (const tool of summary.tools.slice(0, 20)) {
66
+ lines.push(` ${tool.name.padEnd(20).slice(0, 20)} ${String(tool.calls).padStart(5)} ${formatPercent(tool.failureRate).padStart(7)} ${formatMs(tool.p50Ms).padStart(7)} ${formatMs(tool.p95Ms).padStart(7)} ${formatMs(tool.totalMs).padStart(8)}`);
67
+ }
68
+ }
69
+ if (summary.malformedLines > 0) {
70
+ lines.push("", ` ${formatCount(summary.malformedLines)} unparseable line(s) skipped`);
71
+ }
72
+ return lines;
73
+ }
74
+ export function runStatsCli(args, overrides = {}) {
75
+ const parsed = parseArgs(args);
76
+ if (parsed.kind === "absent")
77
+ return { handled: false, exitCode: 0 };
78
+ const writeLine = overrides.writeLine ?? ((line) => console.log(line));
79
+ if (parsed.kind === "help") {
80
+ writeLine(USAGE);
81
+ return { handled: true, exitCode: 0 };
82
+ }
83
+ if (parsed.kind === "error") {
84
+ writeLine(JSON.stringify({ status: "refused", error: parsed.message, usage: USAGE }));
85
+ return { handled: true, exitCode: 2 };
86
+ }
87
+ const cwd = resolve(overrides.cwd ?? process.cwd());
88
+ const dir = parsed.dir ?? process.env.OMK_TURN_METRICS_DIR ?? join(cwd, ".omk", "metrics");
89
+ const filePath = join(resolve(dir), "turns.jsonl");
90
+ const summary = summarizeTurnMetricsFile(filePath);
91
+ if (parsed.json) {
92
+ writeLine(JSON.stringify({ status: "ok", path: filePath, summary }));
93
+ return { handled: true, exitCode: 0 };
94
+ }
95
+ for (const line of renderStatsReport(summary, filePath))
96
+ writeLine(line);
97
+ return { handled: true, exitCode: 0 };
98
+ }
99
+ //# sourceMappingURL=stats-cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats-cli.js","sourceRoot":"","sources":["../../src/commands/stats-cli.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `omk stats` — read the per-turn metrics JSONL and print an aggregate.\n *\n * Answers the questions the harness previously had no data for: which tools\n * fail, how long turns take, what they cost, and whether the context cache is\n * doing anything. Read-only; it never starts a session.\n */\n\nimport { join, resolve } from \"node:path\";\nimport { summarizeTurnMetricsFile, type TurnMetricsSummary } from \"../core/turn-metrics.ts\";\n\nconst USAGE = \"Usage: omk stats [--dir <path>] [--json]\";\n\nexport interface StatsCliOverrides {\n\treadonly cwd?: string;\n\treadonly writeLine?: (line: string) => void;\n}\n\nexport interface StatsCliOutcome {\n\treadonly handled: boolean;\n\treadonly exitCode: 0 | 1 | 2;\n}\n\ntype ParsedArgs =\n\t| { kind: \"absent\" }\n\t| { kind: \"help\" }\n\t| { kind: \"error\"; message: string }\n\t| { kind: \"run\"; dir?: string; json: boolean };\n\nfunction parseArgs(args: readonly string[]): ParsedArgs {\n\tif (args[0] !== \"stats\") return { kind: \"absent\" };\n\tlet dir: string | undefined;\n\tlet json = false;\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst arg = args[index];\n\t\tif (arg === \"--help\" || arg === \"-h\") return { kind: \"help\" };\n\t\tif (arg === \"--json\") {\n\t\t\tjson = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (arg === \"--dir\") {\n\t\t\tconst value = args[++index];\n\t\t\tif (value === undefined) return { kind: \"error\", message: \"--dir requires a path\" };\n\t\t\tdir = value;\n\t\t\tcontinue;\n\t\t}\n\t\treturn { kind: \"error\", message: `unknown argument: ${arg}` };\n\t}\n\treturn { kind: \"run\", dir, json };\n}\n\nfunction formatMs(value: number): string {\n\tif (value >= 1000) return `${(value / 1000).toFixed(1)}s`;\n\treturn `${Math.round(value)}ms`;\n}\n\nfunction formatPercent(value: number | undefined): string {\n\treturn value === undefined ? \"n/a\" : `${(value * 100).toFixed(1)}%`;\n}\n\nfunction formatCount(value: number): string {\n\treturn value.toLocaleString(\"en-US\");\n}\n\n/** Render the human-readable report. Pure, so its shape is directly testable. */\nexport function renderStatsReport(summary: TurnMetricsSummary, filePath: string): string[] {\n\tif (summary.turns === 0) {\n\t\treturn [\n\t\t\t`No turn metrics recorded yet (${filePath}).`,\n\t\t\t\"Metrics are written as sessions run. Set OMK_TURN_METRICS=0 to disable.\",\n\t\t];\n\t}\n\n\tconst lines = [\n\t\t`Turn metrics — ${formatCount(summary.turns)} turns across ${formatCount(summary.sessions)} session(s)`,\n\t\t` models ${summary.models.length > 0 ? summary.models.join(\", \") : \"unknown\"}`,\n\t\t` turn duration p50 ${formatMs(summary.turnDurationP50Ms)} · p95 ${formatMs(summary.turnDurationP95Ms)}`,\n\t];\n\tif (summary.timeToFirstChunkP50Ms !== undefined) {\n\t\tlines.push(` first chunk p50 ${formatMs(summary.timeToFirstChunkP50Ms)}`);\n\t}\n\tlines.push(\n\t\t` input ${formatCount(summary.totals.input)} · output ${formatCount(summary.totals.output)} · cacheRead ${formatCount(summary.totals.cacheRead)} · cacheWrite ${formatCount(summary.totals.cacheWrite)}`,\n\t\t` cache read share ${formatPercent(summary.cacheReadShare)} of prompt-side usage`,\n\t\t` cost $${summary.totalCostUsd.toFixed(4)}`,\n\t\t` compactions ${formatCount(summary.compactions)} · failovers ${formatCount(summary.failovers)} · ctx plan hit ${formatPercent(summary.contextCachePlanHitRate)}`,\n\t);\n\n\tif (summary.tools.length > 0) {\n\t\tlines.push(\"\", \" tool calls fail% p50 p95 total\");\n\t\tfor (const tool of summary.tools.slice(0, 20)) {\n\t\t\tlines.push(\n\t\t\t\t` ${tool.name.padEnd(20).slice(0, 20)} ${String(tool.calls).padStart(5)} ${formatPercent(tool.failureRate).padStart(7)} ${formatMs(tool.p50Ms).padStart(7)} ${formatMs(tool.p95Ms).padStart(7)} ${formatMs(tool.totalMs).padStart(8)}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (summary.malformedLines > 0) {\n\t\tlines.push(\"\", ` ${formatCount(summary.malformedLines)} unparseable line(s) skipped`);\n\t}\n\treturn lines;\n}\n\nexport function runStatsCli(args: readonly string[], overrides: StatsCliOverrides = {}): StatsCliOutcome {\n\tconst parsed = parseArgs(args);\n\tif (parsed.kind === \"absent\") return { handled: false, exitCode: 0 };\n\tconst writeLine = overrides.writeLine ?? ((line: string) => console.log(line));\n\tif (parsed.kind === \"help\") {\n\t\twriteLine(USAGE);\n\t\treturn { handled: true, exitCode: 0 };\n\t}\n\tif (parsed.kind === \"error\") {\n\t\twriteLine(JSON.stringify({ status: \"refused\", error: parsed.message, usage: USAGE }));\n\t\treturn { handled: true, exitCode: 2 };\n\t}\n\n\tconst cwd = resolve(overrides.cwd ?? process.cwd());\n\tconst dir = parsed.dir ?? process.env.OMK_TURN_METRICS_DIR ?? join(cwd, \".omk\", \"metrics\");\n\tconst filePath = join(resolve(dir), \"turns.jsonl\");\n\tconst summary = summarizeTurnMetricsFile(filePath);\n\n\tif (parsed.json) {\n\t\twriteLine(JSON.stringify({ status: \"ok\", path: filePath, summary }));\n\t\treturn { handled: true, exitCode: 0 };\n\t}\n\tfor (const line of renderStatsReport(summary, filePath)) writeLine(line);\n\treturn { handled: true, exitCode: 0 };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-session-runtime.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { copyFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport type { AgentSession } from \"./agent-session.ts\";\nimport type { AgentSessionRuntimeDiagnostic, AgentSessionServices } from \"./agent-session-services.ts\";\nimport type { ReplacedSessionContext, SessionShutdownEvent, SessionStartEvent } from \"./extensions/index.ts\";\nimport { emitSessionShutdownEvent } from \"./extensions/runner.ts\";\nimport type { CreateAgentSessionResult } from \"./sdk.ts\";\nimport { assertSessionCwdExists } from \"./session-cwd.ts\";\nimport { SessionManager } from \"./session-manager.ts\";\n\n/**\n * Result returned by runtime creation.\n *\n * The caller gets the created session, its cwd-bound services, and all\n * diagnostics collected during setup.\n */\nexport interface CreateAgentSessionRuntimeResult extends CreateAgentSessionResult {\n\tservices: AgentSessionServices;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n}\n\n/**\n * Creates a full runtime for a target cwd and session manager.\n *\n * The factory closes over process-global fixed inputs, recreates cwd-bound\n * services for the effective cwd, resolves session options against those\n * services, and finally creates the AgentSession.\n */\nexport type CreateAgentSessionRuntimeFactory = (options: {\n\tcwd: string;\n\tagentDir: string;\n\tsessionManager: SessionManager;\n\tsessionStartEvent?: SessionStartEvent;\n}) => Promise<CreateAgentSessionRuntimeResult>;\n\n/**\n * Thrown when /import references a JSONL file path that does not exist.\n */\nexport class SessionImportFileNotFoundError extends Error {\n\treadonly filePath: string;\n\n\tconstructor(filePath: string) {\n\t\tsuper(`File not found: ${filePath}`);\n\t\tthis.name = \"SessionImportFileNotFoundError\";\n\t\tthis.filePath = filePath;\n\t}\n}\n\nfunction extractUserMessageText(content: string | Array<{ type: string; text?: string }>): string {\n\tif (typeof content === \"string\") {\n\t\treturn content;\n\t}\n\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => part.type === \"text\" && typeof part.text === \"string\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\");\n}\n\n/**\n * Owns the current AgentSession plus its cwd-bound services.\n *\n * Session replacement methods tear down the current runtime first, then create\n * and apply the next runtime. If creation fails, the error is propagated to the\n * caller. The caller is responsible for user-facing error handling.\n */\nexport class AgentSessionRuntime {\n\tprivate rebindSession?: (session: AgentSession) => Promise<void>;\n\tprivate beforeSessionInvalidate?: () => void;\n\tprivate _session: AgentSession;\n\tprivate _services: AgentSessionServices;\n\tprivate readonly createRuntime: CreateAgentSessionRuntimeFactory;\n\tprivate _diagnostics: AgentSessionRuntimeDiagnostic[];\n\tprivate _modelFallbackMessage?: string;\n\n\tconstructor(\n\t\t_session: AgentSession,\n\t\t_services: AgentSessionServices,\n\t\tcreateRuntime: CreateAgentSessionRuntimeFactory,\n\t\t_diagnostics: AgentSessionRuntimeDiagnostic[] = [],\n\t\t_modelFallbackMessage?: string,\n\t) {\n\t\tthis._session = _session;\n\t\tthis._services = _services;\n\t\tthis.createRuntime = createRuntime;\n\t\tthis._diagnostics = _diagnostics;\n\t\tthis._modelFallbackMessage = _modelFallbackMessage;\n\t}\n\n\tget services(): AgentSessionServices {\n\t\treturn this._services;\n\t}\n\n\tget session(): AgentSession {\n\t\treturn this._session;\n\t}\n\n\tget cwd(): string {\n\t\treturn this._services.cwd;\n\t}\n\n\tget diagnostics(): readonly AgentSessionRuntimeDiagnostic[] {\n\t\treturn this._diagnostics;\n\t}\n\n\tget modelFallbackMessage(): string | undefined {\n\t\treturn this._modelFallbackMessage;\n\t}\n\n\tsetRebindSession(rebindSession?: (session: AgentSession) => Promise<void>): void {\n\t\tthis.rebindSession = rebindSession;\n\t}\n\n\t/**\n\t * Set a synchronous callback that runs after `session_shutdown` handlers finish\n\t * but before the current session is invalidated.\n\t *\n\t * This is for host-owned UI teardown that must not yield to the event loop,\n\t * such as detaching extension-provided TUI components before the old extension\n\t * context becomes stale.\n\t */\n\tsetBeforeSessionInvalidate(beforeSessionInvalidate?: () => void): void {\n\t\tthis.beforeSessionInvalidate = beforeSessionInvalidate;\n\t}\n\n\tprivate async emitBeforeSwitch(\n\t\treason: \"new\" | \"resume\",\n\t\ttargetSessionFile?: string,\n\t): Promise<{ cancelled: boolean }> {\n\t\tconst runner = this.session.extensionRunner;\n\t\tif (!runner.hasHandlers(\"session_before_switch\")) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\tconst result = await runner.emit({\n\t\t\ttype: \"session_before_switch\",\n\t\t\treason,\n\t\t\ttargetSessionFile,\n\t\t});\n\t\treturn { cancelled: result?.cancel === true };\n\t}\n\n\tprivate async emitBeforeFork(\n\t\tentryId: string,\n\t\toptions: { position: \"before\" | \"at\" },\n\t): Promise<{ cancelled: boolean }> {\n\t\tconst runner = this.session.extensionRunner;\n\t\tif (!runner.hasHandlers(\"session_before_fork\")) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\tconst result = await runner.emit({\n\t\t\ttype: \"session_before_fork\",\n\t\t\tentryId,\n\t\t\t...options,\n\t\t});\n\t\treturn { cancelled: result?.cancel === true };\n\t}\n\n\tprivate async teardownCurrent(reason: SessionShutdownEvent[\"reason\"], targetSessionFile?: string): Promise<void> {\n\t\tawait emitSessionShutdownEvent(this.session.extensionRunner, {\n\t\t\ttype: \"session_shutdown\",\n\t\t\treason,\n\t\t\ttargetSessionFile,\n\t\t});\n\t\tthis.beforeSessionInvalidate?.();\n\t\tthis.session.dispose();\n\t}\n\n\tprivate apply(result: CreateAgentSessionRuntimeResult): void {\n\t\tthis._session = result.session;\n\t\tthis._services = result.services;\n\t\tthis._diagnostics = result.diagnostics;\n\t\tthis._modelFallbackMessage = result.modelFallbackMessage;\n\t}\n\n\tprivate async finishSessionReplacement(withSession?: (ctx: ReplacedSessionContext) => Promise<void>): Promise<void> {\n\t\tif (this.rebindSession) {\n\t\t\tawait this.rebindSession(this.session);\n\t\t}\n\t\tif (withSession) {\n\t\t\tawait withSession(this.session.createReplacedSessionContext());\n\t\t}\n\t}\n\n\tasync switchSession(\n\t\tsessionPath: string,\n\t\toptions?: { cwdOverride?: string; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },\n\t): Promise<{ cancelled: boolean }> {\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"resume\", sessionPath);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tconst sessionManager = SessionManager.open(sessionPath, undefined, options?.cwdOverride);\n\t\tassertSessionCwdExists(sessionManager, this.cwd);\n\t\tawait this.teardownCurrent(\"resume\", sessionManager.getSessionFile());\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"resume\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\treturn { cancelled: false };\n\t}\n\n\tasync newSession(options?: {\n\t\tparentSession?: string;\n\t\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\t\twithSession?: (ctx: ReplacedSessionContext) => Promise<void>;\n\t}): Promise<{ cancelled: boolean }> {\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"new\");\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tconst sessionDir = this.session.sessionManager.getSessionDir();\n\t\tconst sessionManager = SessionManager.create(this.cwd, sessionDir);\n\t\tif (options?.parentSession) {\n\t\t\tsessionManager.newSession({ parentSession: options.parentSession });\n\t\t}\n\n\t\tawait this.teardownCurrent(\"new\", sessionManager.getSessionFile());\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"new\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\tif (options?.setup) {\n\t\t\tawait options.setup(this.session.sessionManager);\n\t\t\tthis.session.agent.state.messages = this.session.sessionManager.buildSessionContext().messages;\n\t\t}\n\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\treturn { cancelled: false };\n\t}\n\n\tasync fork(\n\t\tentryId: string,\n\t\toptions?: { position?: \"before\" | \"at\"; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },\n\t): Promise<{ cancelled: boolean; selectedText?: string }> {\n\t\tconst position = options?.position ?? \"before\";\n\t\tconst beforeResult = await this.emitBeforeFork(entryId, { position });\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn { cancelled: true };\n\t\t}\n\t\tlet targetLeafId: string | null;\n\t\tlet selectedText: string | undefined;\n\n\t\tconst selectedEntry = this.session.sessionManager.getEntry(entryId);\n\t\tif (!selectedEntry) {\n\t\t\tthrow new Error(\"Invalid entry ID for forking\");\n\t\t}\n\n\t\tif (position === \"at\") {\n\t\t\ttargetLeafId = selectedEntry.id;\n\t\t} else {\n\t\t\tif (selectedEntry.type !== \"message\" || selectedEntry.message.role !== \"user\") {\n\t\t\t\tthrow new Error(\"Invalid entry ID for forking\");\n\t\t\t}\n\t\t\ttargetLeafId = selectedEntry.parentId;\n\t\t\tselectedText = extractUserMessageText(selectedEntry.message.content);\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tif (this.session.sessionManager.isPersisted()) {\n\t\t\tconst currentSessionFile = this.session.sessionFile;\n\t\t\tif (!currentSessionFile) {\n\t\t\t\tthrow new Error(\"Persisted session is missing a session file\");\n\t\t\t}\n\t\t\tconst sessionDir = this.session.sessionManager.getSessionDir();\n\t\t\tif (!targetLeafId) {\n\t\t\t\tconst sessionManager = SessionManager.create(this.cwd, sessionDir);\n\t\t\t\tsessionManager.newSession({ parentSession: currentSessionFile });\n\t\t\t\tawait this.teardownCurrent(\"fork\", sessionManager.getSessionFile());\n\t\t\t\tthis.apply(\n\t\t\t\t\tawait this.createRuntime({\n\t\t\t\t\t\tcwd: this.cwd,\n\t\t\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\t\t\tsessionManager,\n\t\t\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\t\t\treturn { cancelled: false, selectedText };\n\t\t\t}\n\n\t\t\tconst sessionManager = SessionManager.open(currentSessionFile, sessionDir);\n\t\t\tconst forkedSessionPath = sessionManager.createBranchedSession(targetLeafId);\n\t\t\tif (!forkedSessionPath) {\n\t\t\t\tthrow new Error(\"Failed to create forked session\");\n\t\t\t}\n\t\t\tawait this.teardownCurrent(\"fork\", sessionManager.getSessionFile());\n\t\t\tthis.apply(\n\t\t\t\tawait this.createRuntime({\n\t\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\t\tsessionManager,\n\t\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\t\treturn { cancelled: false, selectedText };\n\t\t}\n\n\t\tconst sessionManager = this.session.sessionManager;\n\t\tif (!targetLeafId) {\n\t\t\tsessionManager.newSession({ parentSession: this.session.sessionFile });\n\t\t} else {\n\t\t\tsessionManager.createBranchedSession(targetLeafId);\n\t\t}\n\t\tawait this.teardownCurrent(\"fork\", sessionManager.getSessionFile());\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\treturn { cancelled: false, selectedText };\n\t}\n\n\t/**\n\t * Import a session JSONL file and switch runtime state to the imported session.\n\t *\n\t * @returns `{ cancelled: true }` when cancelled by `session_before_switch`, otherwise `{ cancelled: false }`.\n\t * @throws {SessionImportFileNotFoundError} When the input path does not exist.\n\t * @throws {MissingSessionCwdError} When the imported session cwd cannot be resolved and no override is provided.\n\t */\n\tasync importFromJsonl(inputPath: string, cwdOverride?: string): Promise<{ cancelled: boolean }> {\n\t\tconst resolvedPath = resolvePath(inputPath);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tthrow new SessionImportFileNotFoundError(resolvedPath);\n\t\t}\n\n\t\tconst sessionDir = this.session.sessionManager.getSessionDir();\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\n\t\tconst destinationPath = join(sessionDir, basename(resolvedPath));\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"resume\", destinationPath);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tif (resolve(destinationPath) !== resolvedPath) {\n\t\t\tcopyFileSync(resolvedPath, destinationPath);\n\t\t}\n\n\t\tconst sessionManager = SessionManager.open(destinationPath, sessionDir, cwdOverride);\n\t\tassertSessionCwdExists(sessionManager, this.cwd);\n\t\tawait this.teardownCurrent(\"resume\", sessionManager.getSessionFile());\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"resume\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\tawait this.finishSessionReplacement();\n\t\treturn { cancelled: false };\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tawait emitSessionShutdownEvent(this.session.extensionRunner, {\n\t\t\ttype: \"session_shutdown\",\n\t\t\treason: \"quit\",\n\t\t});\n\t\tthis.beforeSessionInvalidate?.();\n\t\tthis.session.dispose();\n\t}\n}\n\n/**\n * Create the initial runtime from a runtime factory and initial session target.\n *\n * The same factory is stored on the returned AgentSessionRuntime and reused for\n * later /new, /resume, /fork, and import flows.\n */\nexport async function createAgentSessionRuntime(\n\tcreateRuntime: CreateAgentSessionRuntimeFactory,\n\toptions: {\n\t\tcwd: string;\n\t\tagentDir: string;\n\t\tsessionManager: SessionManager;\n\t\tsessionStartEvent?: SessionStartEvent;\n\t},\n): Promise<AgentSessionRuntime> {\n\tassertSessionCwdExists(options.sessionManager, options.cwd);\n\tconst result = await createRuntime(options);\n\treturn new AgentSessionRuntime(\n\t\tresult.session,\n\t\tresult.services,\n\t\tcreateRuntime,\n\t\tresult.diagnostics,\n\t\tresult.modelFallbackMessage,\n\t);\n}\n\nexport {\n\ttype AgentSessionRuntimeDiagnostic,\n\ttype AgentSessionServices,\n\ttype CreateAgentSessionFromServicesOptions,\n\ttype CreateAgentSessionServicesOptions,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./agent-session-services.ts\";\n"]}
1
+ {"version":3,"file":"agent-session-runtime.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import { copyFileSync, existsSync, mkdirSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport type { AgentSession } from \"./agent-session.ts\";\nimport type { AgentSessionRuntimeDiagnostic, AgentSessionServices } from \"./agent-session-services.ts\";\nimport type { ReplacedSessionContext, SessionShutdownEvent, SessionStartEvent } from \"./extensions/index.ts\";\nimport { emitSessionShutdownEvent } from \"./extensions/runner.ts\";\nimport type { CreateAgentSessionResult } from \"./sdk.ts\";\nimport { assertSessionCwdExists } from \"./session-cwd.ts\";\nimport { SessionManager } from \"./session-manager.ts\";\n\n/**\n * Result returned by runtime creation.\n *\n * The caller gets the created session, its cwd-bound services, and all\n * diagnostics collected during setup.\n */\nexport interface CreateAgentSessionRuntimeResult extends CreateAgentSessionResult {\n\tservices: AgentSessionServices;\n\tdiagnostics: AgentSessionRuntimeDiagnostic[];\n}\n\n/**\n * Creates a full runtime for a target cwd and session manager.\n *\n * The factory closes over process-global fixed inputs, recreates cwd-bound\n * services for the effective cwd, resolves session options against those\n * services, and finally creates the AgentSession.\n */\nexport type CreateAgentSessionRuntimeFactory = (options: {\n\tcwd: string;\n\tagentDir: string;\n\tsessionManager: SessionManager;\n\tsessionStartEvent?: SessionStartEvent;\n}) => Promise<CreateAgentSessionRuntimeResult>;\n\n/**\n * Thrown when /import references a JSONL file path that does not exist.\n */\nexport class SessionImportFileNotFoundError extends Error {\n\treadonly filePath: string;\n\n\tconstructor(filePath: string) {\n\t\tsuper(`File not found: ${filePath}`);\n\t\tthis.name = \"SessionImportFileNotFoundError\";\n\t\tthis.filePath = filePath;\n\t}\n}\n\nfunction extractUserMessageText(content: string | Array<{ type: string; text?: string }>): string {\n\tif (typeof content === \"string\") {\n\t\treturn content;\n\t}\n\n\treturn content\n\t\t.filter((part): part is { type: \"text\"; text: string } => part.type === \"text\" && typeof part.text === \"string\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\");\n}\n\n/**\n * Owns the current AgentSession plus its cwd-bound services.\n *\n * Session replacement methods tear down the current runtime first, then create\n * and apply the next runtime. If creation fails, the error is propagated to the\n * caller. The caller is responsible for user-facing error handling.\n */\nexport class AgentSessionRuntime {\n\tprivate rebindSession?: (session: AgentSession) => Promise<void>;\n\tprivate beforeSessionInvalidate?: () => void;\n\tprivate _session: AgentSession;\n\tprivate _services: AgentSessionServices;\n\tprivate readonly createRuntime: CreateAgentSessionRuntimeFactory;\n\tprivate _diagnostics: AgentSessionRuntimeDiagnostic[];\n\tprivate _modelFallbackMessage?: string;\n\n\tconstructor(\n\t\t_session: AgentSession,\n\t\t_services: AgentSessionServices,\n\t\tcreateRuntime: CreateAgentSessionRuntimeFactory,\n\t\t_diagnostics: AgentSessionRuntimeDiagnostic[] = [],\n\t\t_modelFallbackMessage?: string,\n\t) {\n\t\tthis._session = _session;\n\t\tthis._services = _services;\n\t\tthis.createRuntime = createRuntime;\n\t\tthis._diagnostics = _diagnostics;\n\t\tthis._modelFallbackMessage = _modelFallbackMessage;\n\t}\n\n\tget services(): AgentSessionServices {\n\t\treturn this._services;\n\t}\n\n\tget session(): AgentSession {\n\t\treturn this._session;\n\t}\n\n\tget cwd(): string {\n\t\treturn this._services.cwd;\n\t}\n\n\tget diagnostics(): readonly AgentSessionRuntimeDiagnostic[] {\n\t\treturn this._diagnostics;\n\t}\n\n\tget modelFallbackMessage(): string | undefined {\n\t\treturn this._modelFallbackMessage;\n\t}\n\n\tsetRebindSession(rebindSession?: (session: AgentSession) => Promise<void>): void {\n\t\tthis.rebindSession = rebindSession;\n\t}\n\n\t/**\n\t * Set a synchronous callback that runs after `session_shutdown` handlers finish\n\t * but before the current session is invalidated.\n\t *\n\t * This is for host-owned UI teardown that must not yield to the event loop,\n\t * such as detaching extension-provided TUI components before the old extension\n\t * context becomes stale.\n\t */\n\tsetBeforeSessionInvalidate(beforeSessionInvalidate?: () => void): void {\n\t\tthis.beforeSessionInvalidate = beforeSessionInvalidate;\n\t}\n\n\tprivate async emitBeforeSwitch(\n\t\treason: \"new\" | \"resume\",\n\t\ttargetSessionFile?: string,\n\t): Promise<{ cancelled: boolean }> {\n\t\tconst runner = this.session.extensionRunner;\n\t\tif (!runner.hasHandlers(\"session_before_switch\")) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\tconst result = await runner.emit({\n\t\t\ttype: \"session_before_switch\",\n\t\t\treason,\n\t\t\ttargetSessionFile,\n\t\t});\n\t\treturn { cancelled: result?.cancel === true };\n\t}\n\n\tprivate async emitBeforeFork(\n\t\tentryId: string,\n\t\toptions: { position: \"before\" | \"at\" },\n\t): Promise<{ cancelled: boolean }> {\n\t\tconst runner = this.session.extensionRunner;\n\t\tif (!runner.hasHandlers(\"session_before_fork\")) {\n\t\t\treturn { cancelled: false };\n\t\t}\n\n\t\tconst result = await runner.emit({\n\t\t\ttype: \"session_before_fork\",\n\t\t\tentryId,\n\t\t\t...options,\n\t\t});\n\t\treturn { cancelled: result?.cancel === true };\n\t}\n\n\tprivate async teardownCurrent(reason: SessionShutdownEvent[\"reason\"], targetSessionFile?: string): Promise<void> {\n\t\tawait emitSessionShutdownEvent(this.session.extensionRunner, {\n\t\t\ttype: \"session_shutdown\",\n\t\t\treason,\n\t\t\ttargetSessionFile,\n\t\t});\n\t\tthis.beforeSessionInvalidate?.();\n\t\tthis.session.dispose();\n\t}\n\n\tprivate apply(result: CreateAgentSessionRuntimeResult): void {\n\t\tthis._session = result.session;\n\t\tthis._services = result.services;\n\t\tthis._diagnostics = result.diagnostics;\n\t\tthis._modelFallbackMessage = result.modelFallbackMessage;\n\t}\n\n\tprivate async finishSessionReplacement(withSession?: (ctx: ReplacedSessionContext) => Promise<void>): Promise<void> {\n\t\tif (this.rebindSession) {\n\t\t\tawait this.rebindSession(this.session);\n\t\t}\n\t\tif (withSession) {\n\t\t\tawait withSession(this.session.createReplacedSessionContext());\n\t\t}\n\t}\n\n\tasync switchSession(\n\t\tsessionPath: string,\n\t\toptions?: { cwdOverride?: string; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },\n\t): Promise<{ cancelled: boolean }> {\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"resume\", sessionPath);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tconst sessionManager = SessionManager.open(sessionPath, undefined, options?.cwdOverride);\n\t\tassertSessionCwdExists(sessionManager, this.cwd);\n\t\tawait this.teardownCurrent(\"resume\", sessionManager.getSessionFile());\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"resume\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\treturn { cancelled: false };\n\t}\n\n\tasync newSession(options?: {\n\t\tparentSession?: string;\n\t\tsetup?: (sessionManager: SessionManager) => Promise<void>;\n\t\twithSession?: (ctx: ReplacedSessionContext) => Promise<void>;\n\t}): Promise<{ cancelled: boolean }> {\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"new\");\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tconst sessionDir = this.session.sessionManager.getSessionDir();\n\t\tconst sessionManager = SessionManager.create(this.cwd, sessionDir);\n\t\tif (options?.parentSession) {\n\t\t\tsessionManager.newSession({ parentSession: options.parentSession });\n\t\t}\n\n\t\tawait this.teardownCurrent(\"new\", sessionManager.getSessionFile());\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"new\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\tif (options?.setup) {\n\t\t\tawait options.setup(this.session.sessionManager);\n\t\t\tthis.session.agent.state.messages = this.session.sessionManager.buildSessionContext().messages;\n\t\t}\n\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\treturn { cancelled: false };\n\t}\n\n\tasync fork(\n\t\tentryId: string,\n\t\toptions?: { position?: \"before\" | \"at\"; withSession?: (ctx: ReplacedSessionContext) => Promise<void> },\n\t): Promise<{ cancelled: boolean; selectedText?: string }> {\n\t\tconst position = options?.position ?? \"before\";\n\t\tconst beforeResult = await this.emitBeforeFork(entryId, { position });\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn { cancelled: true };\n\t\t}\n\t\tlet targetLeafId: string | null;\n\t\tlet selectedText: string | undefined;\n\n\t\tconst selectedEntry = this.session.sessionManager.getEntry(entryId);\n\t\tif (!selectedEntry) {\n\t\t\tthrow new Error(\"Invalid entry ID for forking\");\n\t\t}\n\n\t\tif (position === \"at\") {\n\t\t\ttargetLeafId = selectedEntry.id;\n\t\t} else {\n\t\t\tif (selectedEntry.type !== \"message\" || selectedEntry.message.role !== \"user\") {\n\t\t\t\tthrow new Error(\"Invalid entry ID for forking\");\n\t\t\t}\n\t\t\ttargetLeafId = selectedEntry.parentId;\n\t\t\tselectedText = extractUserMessageText(selectedEntry.message.content);\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tif (this.session.sessionManager.isPersisted()) {\n\t\t\tconst currentSessionFile = this.session.sessionFile;\n\t\t\tif (!currentSessionFile) {\n\t\t\t\tthrow new Error(\"Persisted session is missing a session file\");\n\t\t\t}\n\t\t\tconst sessionDir = this.session.sessionManager.getSessionDir();\n\t\t\tif (!targetLeafId) {\n\t\t\t\tconst sessionManager = SessionManager.create(this.cwd, sessionDir);\n\t\t\t\tsessionManager.newSession({ parentSession: currentSessionFile });\n\t\t\t\tawait this.teardownCurrent(\"fork\", sessionManager.getSessionFile());\n\t\t\t\tthis.apply(\n\t\t\t\t\tawait this.createRuntime({\n\t\t\t\t\t\tcwd: this.cwd,\n\t\t\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\t\t\tsessionManager,\n\t\t\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\t\t\treturn { cancelled: false, selectedText };\n\t\t\t}\n\n\t\t\tconst ownerLease = this.session.sessionManager.getOwnerLease();\n\t\t\tconst sessionManager = SessionManager.open(currentSessionFile, sessionDir, undefined, ownerLease);\n\t\t\tconst forkedSessionPath = sessionManager.createBranchedSession(targetLeafId);\n\t\t\tif (!forkedSessionPath) {\n\t\t\t\tthrow new Error(\"Failed to create forked session\");\n\t\t\t}\n\t\t\tawait this.teardownCurrent(\"fork\", sessionManager.getSessionFile());\n\t\t\tif (sessionManager.getOwnerLease() === ownerLease) sessionManager.setOwnerLease(undefined);\n\t\t\tthis.apply(\n\t\t\t\tawait this.createRuntime({\n\t\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\t\tsessionManager,\n\t\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t\t}),\n\t\t\t);\n\t\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\t\treturn { cancelled: false, selectedText };\n\t\t}\n\n\t\tconst sessionManager = this.session.sessionManager;\n\t\tif (!targetLeafId) {\n\t\t\tsessionManager.newSession({ parentSession: this.session.sessionFile });\n\t\t} else {\n\t\t\tsessionManager.createBranchedSession(targetLeafId);\n\t\t}\n\t\tawait this.teardownCurrent(\"fork\", sessionManager.getSessionFile());\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"fork\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\tawait this.finishSessionReplacement(options?.withSession);\n\t\treturn { cancelled: false, selectedText };\n\t}\n\n\t/**\n\t * Import a session JSONL file and switch runtime state to the imported session.\n\t *\n\t * @returns `{ cancelled: true }` when cancelled by `session_before_switch`, otherwise `{ cancelled: false }`.\n\t * @throws {SessionImportFileNotFoundError} When the input path does not exist.\n\t * @throws {MissingSessionCwdError} When the imported session cwd cannot be resolved and no override is provided.\n\t */\n\tasync importFromJsonl(inputPath: string, cwdOverride?: string): Promise<{ cancelled: boolean }> {\n\t\tconst resolvedPath = resolvePath(inputPath);\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tthrow new SessionImportFileNotFoundError(resolvedPath);\n\t\t}\n\n\t\tconst sessionDir = this.session.sessionManager.getSessionDir();\n\t\tif (!existsSync(sessionDir)) {\n\t\t\tmkdirSync(sessionDir, { recursive: true });\n\t\t}\n\n\t\tconst destinationPath = join(sessionDir, basename(resolvedPath));\n\t\tconst beforeResult = await this.emitBeforeSwitch(\"resume\", destinationPath);\n\t\tif (beforeResult.cancelled) {\n\t\t\treturn beforeResult;\n\t\t}\n\n\t\tconst previousSessionFile = this.session.sessionFile;\n\t\tif (resolve(destinationPath) !== resolvedPath) {\n\t\t\tcopyFileSync(resolvedPath, destinationPath);\n\t\t}\n\n\t\tconst sessionManager = SessionManager.open(destinationPath, sessionDir, cwdOverride);\n\t\tassertSessionCwdExists(sessionManager, this.cwd);\n\t\tawait this.teardownCurrent(\"resume\", sessionManager.getSessionFile());\n\t\tthis.apply(\n\t\t\tawait this.createRuntime({\n\t\t\t\tcwd: sessionManager.getCwd(),\n\t\t\t\tagentDir: this.services.agentDir,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent: { type: \"session_start\", reason: \"resume\", previousSessionFile },\n\t\t\t}),\n\t\t);\n\t\tawait this.finishSessionReplacement();\n\t\treturn { cancelled: false };\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tawait emitSessionShutdownEvent(this.session.extensionRunner, {\n\t\t\ttype: \"session_shutdown\",\n\t\t\treason: \"quit\",\n\t\t});\n\t\tthis.beforeSessionInvalidate?.();\n\t\tthis.session.dispose();\n\t}\n}\n\n/**\n * Create the initial runtime from a runtime factory and initial session target.\n *\n * The same factory is stored on the returned AgentSessionRuntime and reused for\n * later /new, /resume, /fork, and import flows.\n */\nexport async function createAgentSessionRuntime(\n\tcreateRuntime: CreateAgentSessionRuntimeFactory,\n\toptions: {\n\t\tcwd: string;\n\t\tagentDir: string;\n\t\tsessionManager: SessionManager;\n\t\tsessionStartEvent?: SessionStartEvent;\n\t},\n): Promise<AgentSessionRuntime> {\n\tassertSessionCwdExists(options.sessionManager, options.cwd);\n\tconst result = await createRuntime(options);\n\treturn new AgentSessionRuntime(\n\t\tresult.session,\n\t\tresult.services,\n\t\tcreateRuntime,\n\t\tresult.diagnostics,\n\t\tresult.modelFallbackMessage,\n\t);\n}\n\nexport {\n\ttype AgentSessionRuntimeDiagnostic,\n\ttype AgentSessionServices,\n\ttype CreateAgentSessionFromServicesOptions,\n\ttype CreateAgentSessionServicesOptions,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionServices,\n} from \"./agent-session-services.ts\";\n"]}
@@ -207,12 +207,15 @@ export class AgentSessionRuntime {
207
207
  await this.finishSessionReplacement(options?.withSession);
208
208
  return { cancelled: false, selectedText };
209
209
  }
210
- const sessionManager = SessionManager.open(currentSessionFile, sessionDir);
210
+ const ownerLease = this.session.sessionManager.getOwnerLease();
211
+ const sessionManager = SessionManager.open(currentSessionFile, sessionDir, undefined, ownerLease);
211
212
  const forkedSessionPath = sessionManager.createBranchedSession(targetLeafId);
212
213
  if (!forkedSessionPath) {
213
214
  throw new Error("Failed to create forked session");
214
215
  }
215
216
  await this.teardownCurrent("fork", sessionManager.getSessionFile());
217
+ if (sessionManager.getOwnerLease() === ownerLease)
218
+ sessionManager.setOwnerLease(undefined);
216
219
  this.apply(await this.createRuntime({
217
220
  cwd: sessionManager.getCwd(),
218
221
  agentDir: this.services.agentDir,