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,47 @@
1
+ /**
2
+ * Adapt an MCP tool into a harness `ToolDefinition`.
3
+ *
4
+ * Two things make this small:
5
+ * - `validateToolArguments` already accepts a plain JSON Schema, so an MCP
6
+ * `inputSchema` is passed straight through instead of being re-modelled in
7
+ * TypeBox.
8
+ * - MCP content blocks are already text/image, which is exactly what the
9
+ * harness renders.
10
+ *
11
+ * Naming: tools are exposed as `<server>__<tool>` so two servers can ship a
12
+ * `search` without colliding, and so a model can tell where a tool came from.
13
+ */
14
+ import type { ImageContent, TextContent } from "omk-ai";
15
+ import type { TSchema } from "typebox";
16
+ import type { ToolDefinition } from "../extensions/types.ts";
17
+ import type { McpClient, McpContentBlock, McpToolSchema } from "./client.ts";
18
+ /** Tool-name separator between the server label and the server's own tool name. */
19
+ export declare const MCP_TOOL_NAME_SEPARATOR = "__";
20
+ /** Providers reject long tool names; this is the common ceiling. */
21
+ export declare const MAX_TOOL_NAME_LENGTH = 64;
22
+ export interface McpToolDetails {
23
+ readonly server: string;
24
+ readonly tool: string;
25
+ readonly isError: boolean;
26
+ readonly structuredContent?: unknown;
27
+ }
28
+ /** Reduce an arbitrary label to the character set providers accept for tool names. */
29
+ export declare function sanitizeToolNameSegment(segment: string): string;
30
+ /**
31
+ * Build the exposed tool name. Over-long names keep the server prefix and
32
+ * truncate the tool segment, because the prefix is what disambiguates.
33
+ */
34
+ export declare function buildMcpToolName(serverName: string, toolName: string): string;
35
+ /** Map MCP content blocks onto the harness content union, dropping unrenderable kinds. */
36
+ export declare function mapMcpContent(blocks: readonly McpContentBlock[]): (TextContent | ImageContent)[];
37
+ export interface CreateMcpToolDefinitionOptions {
38
+ /** Per-call deadline. Falls back to the client's default. */
39
+ readonly callTimeoutMs?: number;
40
+ }
41
+ /**
42
+ * Wrap one MCP tool. The returned definition executes in `parallel` mode: MCP
43
+ * tools declare no filesystem claims, so the DAG scheduler cannot prove a
44
+ * conflict and must not serialize them by default.
45
+ */
46
+ export declare function createMcpToolDefinition(serverName: string, client: McpClient, tool: McpToolSchema, options?: CreateMcpToolDefinitionOptions): ToolDefinition<TSchema, McpToolDetails>;
47
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7E,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAC5C,oEAAoE;AACpE,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAKvC,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAQ7E;AAED,0FAA0F;AAC1F,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,CAsBhG;AAED,MAAM,WAAW,8BAA8B;IAC9C,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACtC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,8BAAmC,GAC1C,cAAc,CAAC,OAAO,EAAE,cAAc,CAAC,CAiCzC","sourcesContent":["/**\n * Adapt an MCP tool into a harness `ToolDefinition`.\n *\n * Two things make this small:\n * - `validateToolArguments` already accepts a plain JSON Schema, so an MCP\n * `inputSchema` is passed straight through instead of being re-modelled in\n * TypeBox.\n * - MCP content blocks are already text/image, which is exactly what the\n * harness renders.\n *\n * Naming: tools are exposed as `<server>__<tool>` so two servers can ship a\n * `search` without colliding, and so a model can tell where a tool came from.\n */\n\nimport type { ImageContent, TextContent } from \"omk-ai\";\nimport type { TSchema } from \"typebox\";\nimport type { AgentToolResult, ToolDefinition } from \"../extensions/types.ts\";\nimport type { McpClient, McpContentBlock, McpToolSchema } from \"./client.ts\";\n\n/** Tool-name separator between the server label and the server's own tool name. */\nexport const MCP_TOOL_NAME_SEPARATOR = \"__\";\n/** Providers reject long tool names; this is the common ceiling. */\nexport const MAX_TOOL_NAME_LENGTH = 64;\n\n/** Empty-object schema used when a server omits `inputSchema`. */\nconst EMPTY_OBJECT_SCHEMA = { type: \"object\", properties: {} } as const;\n\nexport interface McpToolDetails {\n\treadonly server: string;\n\treadonly tool: string;\n\treadonly isError: boolean;\n\treadonly structuredContent?: unknown;\n}\n\n/** Reduce an arbitrary label to the character set providers accept for tool names. */\nexport function sanitizeToolNameSegment(segment: string): string {\n\treturn segment.replace(/[^a-zA-Z0-9_-]/gu, \"_\").replace(/_{2,}/gu, \"_\");\n}\n\n/**\n * Build the exposed tool name. Over-long names keep the server prefix and\n * truncate the tool segment, because the prefix is what disambiguates.\n */\nexport function buildMcpToolName(serverName: string, toolName: string): string {\n\tconst server = sanitizeToolNameSegment(serverName);\n\tconst tool = sanitizeToolNameSegment(toolName);\n\tconst full = `${server}${MCP_TOOL_NAME_SEPARATOR}${tool}`;\n\tif (full.length <= MAX_TOOL_NAME_LENGTH) return full;\n\tconst room = MAX_TOOL_NAME_LENGTH - server.length - MCP_TOOL_NAME_SEPARATOR.length;\n\tif (room <= 0) return full.slice(0, MAX_TOOL_NAME_LENGTH);\n\treturn `${server}${MCP_TOOL_NAME_SEPARATOR}${tool.slice(0, room)}`;\n}\n\n/** Map MCP content blocks onto the harness content union, dropping unrenderable kinds. */\nexport function mapMcpContent(blocks: readonly McpContentBlock[]): (TextContent | ImageContent)[] {\n\tconst out: (TextContent | ImageContent)[] = [];\n\tfor (const block of blocks) {\n\t\tif (block.type === \"text\" && typeof block.text === \"string\") {\n\t\t\tout.push({ type: \"text\", text: block.text });\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.type === \"image\" && typeof block.data === \"string\" && typeof block.mimeType === \"string\") {\n\t\t\tout.push({ type: \"image\", data: block.data, mimeType: block.mimeType });\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.type === \"resource\" && typeof block.resource === \"object\" && block.resource !== null) {\n\t\t\tconst resource = block.resource as Record<string, unknown>;\n\t\t\tif (typeof resource.text === \"string\") {\n\t\t\t\tout.push({ type: \"text\", text: resource.text });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\t// Unknown block kinds are summarized rather than dropped silently.\n\t\tout.push({ type: \"text\", text: `[unsupported MCP content block: ${String(block.type)}]` });\n\t}\n\treturn out;\n}\n\nexport interface CreateMcpToolDefinitionOptions {\n\t/** Per-call deadline. Falls back to the client's default. */\n\treadonly callTimeoutMs?: number;\n}\n\n/**\n * Wrap one MCP tool. The returned definition executes in `parallel` mode: MCP\n * tools declare no filesystem claims, so the DAG scheduler cannot prove a\n * conflict and must not serialize them by default.\n */\nexport function createMcpToolDefinition(\n\tserverName: string,\n\tclient: McpClient,\n\ttool: McpToolSchema,\n\toptions: CreateMcpToolDefinitionOptions = {},\n): ToolDefinition<TSchema, McpToolDetails> {\n\tconst exposedName = buildMcpToolName(serverName, tool.name);\n\tconst parameters = (tool.inputSchema ?? EMPTY_OBJECT_SCHEMA) as unknown as TSchema;\n\n\treturn {\n\t\tname: exposedName,\n\t\tlabel: tool.title ?? tool.name,\n\t\tdescription: tool.description ?? `MCP tool \"${tool.name}\" from server \"${serverName}\".`,\n\t\tparameters,\n\t\texecutionMode: \"parallel\",\n\t\tasync execute(_toolCallId, params): Promise<AgentToolResult<McpToolDetails>> {\n\t\t\ttry {\n\t\t\t\tconst result = await client.callTool(tool.name, params, options.callTimeoutMs);\n\t\t\t\tconst content = mapMcpContent(result.content);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: content.length > 0 ? content : [{ type: \"text\", text: \"(no content)\" }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tserver: serverName,\n\t\t\t\t\t\ttool: tool.name,\n\t\t\t\t\t\tisError: result.isError,\n\t\t\t\t\t\tstructuredContent: result.structuredContent,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// A dead or hung server degrades this one tool call, not the turn.\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: message }],\n\t\t\t\t\tdetails: { server: serverName, tool: tool.name, isError: true },\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Adapt an MCP tool into a harness `ToolDefinition`.
3
+ *
4
+ * Two things make this small:
5
+ * - `validateToolArguments` already accepts a plain JSON Schema, so an MCP
6
+ * `inputSchema` is passed straight through instead of being re-modelled in
7
+ * TypeBox.
8
+ * - MCP content blocks are already text/image, which is exactly what the
9
+ * harness renders.
10
+ *
11
+ * Naming: tools are exposed as `<server>__<tool>` so two servers can ship a
12
+ * `search` without colliding, and so a model can tell where a tool came from.
13
+ */
14
+ /** Tool-name separator between the server label and the server's own tool name. */
15
+ export const MCP_TOOL_NAME_SEPARATOR = "__";
16
+ /** Providers reject long tool names; this is the common ceiling. */
17
+ export const MAX_TOOL_NAME_LENGTH = 64;
18
+ /** Empty-object schema used when a server omits `inputSchema`. */
19
+ const EMPTY_OBJECT_SCHEMA = { type: "object", properties: {} };
20
+ /** Reduce an arbitrary label to the character set providers accept for tool names. */
21
+ export function sanitizeToolNameSegment(segment) {
22
+ return segment.replace(/[^a-zA-Z0-9_-]/gu, "_").replace(/_{2,}/gu, "_");
23
+ }
24
+ /**
25
+ * Build the exposed tool name. Over-long names keep the server prefix and
26
+ * truncate the tool segment, because the prefix is what disambiguates.
27
+ */
28
+ export function buildMcpToolName(serverName, toolName) {
29
+ const server = sanitizeToolNameSegment(serverName);
30
+ const tool = sanitizeToolNameSegment(toolName);
31
+ const full = `${server}${MCP_TOOL_NAME_SEPARATOR}${tool}`;
32
+ if (full.length <= MAX_TOOL_NAME_LENGTH)
33
+ return full;
34
+ const room = MAX_TOOL_NAME_LENGTH - server.length - MCP_TOOL_NAME_SEPARATOR.length;
35
+ if (room <= 0)
36
+ return full.slice(0, MAX_TOOL_NAME_LENGTH);
37
+ return `${server}${MCP_TOOL_NAME_SEPARATOR}${tool.slice(0, room)}`;
38
+ }
39
+ /** Map MCP content blocks onto the harness content union, dropping unrenderable kinds. */
40
+ export function mapMcpContent(blocks) {
41
+ const out = [];
42
+ for (const block of blocks) {
43
+ if (block.type === "text" && typeof block.text === "string") {
44
+ out.push({ type: "text", text: block.text });
45
+ continue;
46
+ }
47
+ if (block.type === "image" && typeof block.data === "string" && typeof block.mimeType === "string") {
48
+ out.push({ type: "image", data: block.data, mimeType: block.mimeType });
49
+ continue;
50
+ }
51
+ if (block.type === "resource" && typeof block.resource === "object" && block.resource !== null) {
52
+ const resource = block.resource;
53
+ if (typeof resource.text === "string") {
54
+ out.push({ type: "text", text: resource.text });
55
+ continue;
56
+ }
57
+ }
58
+ // Unknown block kinds are summarized rather than dropped silently.
59
+ out.push({ type: "text", text: `[unsupported MCP content block: ${String(block.type)}]` });
60
+ }
61
+ return out;
62
+ }
63
+ /**
64
+ * Wrap one MCP tool. The returned definition executes in `parallel` mode: MCP
65
+ * tools declare no filesystem claims, so the DAG scheduler cannot prove a
66
+ * conflict and must not serialize them by default.
67
+ */
68
+ export function createMcpToolDefinition(serverName, client, tool, options = {}) {
69
+ const exposedName = buildMcpToolName(serverName, tool.name);
70
+ const parameters = (tool.inputSchema ?? EMPTY_OBJECT_SCHEMA);
71
+ return {
72
+ name: exposedName,
73
+ label: tool.title ?? tool.name,
74
+ description: tool.description ?? `MCP tool "${tool.name}" from server "${serverName}".`,
75
+ parameters,
76
+ executionMode: "parallel",
77
+ async execute(_toolCallId, params) {
78
+ try {
79
+ const result = await client.callTool(tool.name, params, options.callTimeoutMs);
80
+ const content = mapMcpContent(result.content);
81
+ return {
82
+ content: content.length > 0 ? content : [{ type: "text", text: "(no content)" }],
83
+ details: {
84
+ server: serverName,
85
+ tool: tool.name,
86
+ isError: result.isError,
87
+ structuredContent: result.structuredContent,
88
+ },
89
+ };
90
+ }
91
+ catch (error) {
92
+ // A dead or hung server degrades this one tool call, not the turn.
93
+ const message = error instanceof Error ? error.message : String(error);
94
+ return {
95
+ content: [{ type: "text", text: message }],
96
+ details: { server: serverName, tool: tool.name, isError: true },
97
+ };
98
+ }
99
+ },
100
+ };
101
+ }
102
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../src/core/mcp/tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Adapt an MCP tool into a harness `ToolDefinition`.\n *\n * Two things make this small:\n * - `validateToolArguments` already accepts a plain JSON Schema, so an MCP\n * `inputSchema` is passed straight through instead of being re-modelled in\n * TypeBox.\n * - MCP content blocks are already text/image, which is exactly what the\n * harness renders.\n *\n * Naming: tools are exposed as `<server>__<tool>` so two servers can ship a\n * `search` without colliding, and so a model can tell where a tool came from.\n */\n\nimport type { ImageContent, TextContent } from \"omk-ai\";\nimport type { TSchema } from \"typebox\";\nimport type { AgentToolResult, ToolDefinition } from \"../extensions/types.ts\";\nimport type { McpClient, McpContentBlock, McpToolSchema } from \"./client.ts\";\n\n/** Tool-name separator between the server label and the server's own tool name. */\nexport const MCP_TOOL_NAME_SEPARATOR = \"__\";\n/** Providers reject long tool names; this is the common ceiling. */\nexport const MAX_TOOL_NAME_LENGTH = 64;\n\n/** Empty-object schema used when a server omits `inputSchema`. */\nconst EMPTY_OBJECT_SCHEMA = { type: \"object\", properties: {} } as const;\n\nexport interface McpToolDetails {\n\treadonly server: string;\n\treadonly tool: string;\n\treadonly isError: boolean;\n\treadonly structuredContent?: unknown;\n}\n\n/** Reduce an arbitrary label to the character set providers accept for tool names. */\nexport function sanitizeToolNameSegment(segment: string): string {\n\treturn segment.replace(/[^a-zA-Z0-9_-]/gu, \"_\").replace(/_{2,}/gu, \"_\");\n}\n\n/**\n * Build the exposed tool name. Over-long names keep the server prefix and\n * truncate the tool segment, because the prefix is what disambiguates.\n */\nexport function buildMcpToolName(serverName: string, toolName: string): string {\n\tconst server = sanitizeToolNameSegment(serverName);\n\tconst tool = sanitizeToolNameSegment(toolName);\n\tconst full = `${server}${MCP_TOOL_NAME_SEPARATOR}${tool}`;\n\tif (full.length <= MAX_TOOL_NAME_LENGTH) return full;\n\tconst room = MAX_TOOL_NAME_LENGTH - server.length - MCP_TOOL_NAME_SEPARATOR.length;\n\tif (room <= 0) return full.slice(0, MAX_TOOL_NAME_LENGTH);\n\treturn `${server}${MCP_TOOL_NAME_SEPARATOR}${tool.slice(0, room)}`;\n}\n\n/** Map MCP content blocks onto the harness content union, dropping unrenderable kinds. */\nexport function mapMcpContent(blocks: readonly McpContentBlock[]): (TextContent | ImageContent)[] {\n\tconst out: (TextContent | ImageContent)[] = [];\n\tfor (const block of blocks) {\n\t\tif (block.type === \"text\" && typeof block.text === \"string\") {\n\t\t\tout.push({ type: \"text\", text: block.text });\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.type === \"image\" && typeof block.data === \"string\" && typeof block.mimeType === \"string\") {\n\t\t\tout.push({ type: \"image\", data: block.data, mimeType: block.mimeType });\n\t\t\tcontinue;\n\t\t}\n\t\tif (block.type === \"resource\" && typeof block.resource === \"object\" && block.resource !== null) {\n\t\t\tconst resource = block.resource as Record<string, unknown>;\n\t\t\tif (typeof resource.text === \"string\") {\n\t\t\t\tout.push({ type: \"text\", text: resource.text });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\t// Unknown block kinds are summarized rather than dropped silently.\n\t\tout.push({ type: \"text\", text: `[unsupported MCP content block: ${String(block.type)}]` });\n\t}\n\treturn out;\n}\n\nexport interface CreateMcpToolDefinitionOptions {\n\t/** Per-call deadline. Falls back to the client's default. */\n\treadonly callTimeoutMs?: number;\n}\n\n/**\n * Wrap one MCP tool. The returned definition executes in `parallel` mode: MCP\n * tools declare no filesystem claims, so the DAG scheduler cannot prove a\n * conflict and must not serialize them by default.\n */\nexport function createMcpToolDefinition(\n\tserverName: string,\n\tclient: McpClient,\n\ttool: McpToolSchema,\n\toptions: CreateMcpToolDefinitionOptions = {},\n): ToolDefinition<TSchema, McpToolDetails> {\n\tconst exposedName = buildMcpToolName(serverName, tool.name);\n\tconst parameters = (tool.inputSchema ?? EMPTY_OBJECT_SCHEMA) as unknown as TSchema;\n\n\treturn {\n\t\tname: exposedName,\n\t\tlabel: tool.title ?? tool.name,\n\t\tdescription: tool.description ?? `MCP tool \"${tool.name}\" from server \"${serverName}\".`,\n\t\tparameters,\n\t\texecutionMode: \"parallel\",\n\t\tasync execute(_toolCallId, params): Promise<AgentToolResult<McpToolDetails>> {\n\t\t\ttry {\n\t\t\t\tconst result = await client.callTool(tool.name, params, options.callTimeoutMs);\n\t\t\t\tconst content = mapMcpContent(result.content);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: content.length > 0 ? content : [{ type: \"text\", text: \"(no content)\" }],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tserver: serverName,\n\t\t\t\t\t\ttool: tool.name,\n\t\t\t\t\t\tisError: result.isError,\n\t\t\t\t\t\tstructuredContent: result.structuredContent,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\t// A dead or hung server degrades this one tool call, not the turn.\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: message }],\n\t\t\t\t\tdetails: { server: serverName, tool: tool.name, isError: true },\n\t\t\t\t};\n\t\t\t}\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,3 @@
1
+ /** Extract registry tarball files only. Links and special entries fail closed. */
2
+ export declare function extractNpmPackageTarball(archivePath: string, destination: string): void;
3
+ //# sourceMappingURL=package-archive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-archive.d.ts","sourceRoot":"","sources":["../../src/core/package-archive.ts"],"names":[],"mappings":"AA2GA,kFAAkF;AAClF,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAwDvF","sourcesContent":["import {\n\tcloseSync,\n\tconstants,\n\texistsSync,\n\tlstatSync,\n\tmkdirSync,\n\topenSync,\n\treadFileSync,\n\trmSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { gunzipSync } from \"node:zlib\";\n\nconst TAR_BLOCK_BYTES = 512;\nconst MAX_ARCHIVE_BYTES = 64 * 1024 * 1024;\nconst MAX_EXPANDED_BYTES = 256 * 1024 * 1024;\nconst MAX_FILE_BYTES = 64 * 1024 * 1024;\nconst MAX_FILES = 10_000;\n\nfunction text(buffer: Buffer): string {\n\tconst end = buffer.indexOf(0);\n\treturn buffer.subarray(0, end === -1 ? buffer.length : end).toString(\"utf8\");\n}\n\nfunction octal(buffer: Buffer, label: string): number {\n\tconst value = text(buffer).trim();\n\tif (!/^[0-7]+$/u.test(value)) throw new Error(`Invalid tar ${label}`);\n\tconst parsed = Number.parseInt(value, 8);\n\tif (!Number.isSafeInteger(parsed) || parsed < 0) throw new Error(`Invalid tar ${label}`);\n\treturn parsed;\n}\n\nfunction checksum(header: Buffer): void {\n\tconst declared = octal(header.subarray(148, 156), \"checksum\");\n\tlet actual = 0;\n\tfor (let index = 0; index < header.length; index += 1) {\n\t\tactual += index >= 148 && index < 156 ? 0x20 : (header[index] ?? 0);\n\t}\n\tif (actual !== declared) throw new Error(\"Invalid tar checksum\");\n}\n\nfunction parsePax(payload: Buffer): Map<string, string> {\n\tconst fields = new Map<string, string>();\n\tlet offset = 0;\n\twhile (offset < payload.length) {\n\t\tconst space = payload.indexOf(0x20, offset);\n\t\tif (space < 0) throw new Error(\"Invalid tar PAX record\");\n\t\tconst lengthText = payload.subarray(offset, space).toString(\"ascii\");\n\t\tif (!/^\\d+$/u.test(lengthText)) throw new Error(\"Invalid tar PAX record length\");\n\t\tconst length = Number.parseInt(lengthText, 10);\n\t\tif (!Number.isSafeInteger(length) || length <= 0 || offset + length > payload.length) {\n\t\t\tthrow new Error(\"Invalid tar PAX record length\");\n\t\t}\n\t\tconst record = payload.subarray(space + 1, offset + length - 1).toString(\"utf8\");\n\t\tconst equals = record.indexOf(\"=\");\n\t\tif (equals > 0) fields.set(record.slice(0, equals), record.slice(equals + 1));\n\t\toffset += length;\n\t}\n\treturn fields;\n}\n\nfunction safePackagePath(rawPath: string): string[] {\n\tif (!rawPath || rawPath.includes(\"\\0\") || rawPath.includes(\"\\\\\") || rawPath.startsWith(\"/\")) {\n\t\tthrow new Error(\"Npm tarball contains an unsafe path\");\n\t}\n\tconst trimmed = rawPath.endsWith(\"/\") ? rawPath.slice(0, -1) : rawPath;\n\tconst segments = trimmed.split(\"/\");\n\tif (segments[0] !== \"package\") throw new Error(\"Npm tarball entries must stay under package/\");\n\tconst relativeSegments = segments.slice(1);\n\tif (relativeSegments.some((segment) => !segment || segment === \".\" || segment === \"..\")) {\n\t\tthrow new Error(\"Npm tarball contains an unsafe path\");\n\t}\n\treturn relativeSegments;\n}\n\nfunction assertInside(root: string, path: string): void {\n\tconst fromRoot = relative(root, path);\n\tif (!fromRoot || fromRoot === \"..\" || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {\n\t\tthrow new Error(\"Npm tarball contains an unsafe path\");\n\t}\n}\n\nfunction ensureDirectory(root: string, segments: string[]): string {\n\tlet current = root;\n\tfor (const segment of segments) {\n\t\tcurrent = join(current, segment);\n\t\tif (!existsSync(current)) mkdirSync(current, { mode: 0o700 });\n\t\tconst stats = lstatSync(current);\n\t\tif (!stats.isDirectory() || stats.isSymbolicLink()) throw new Error(\"Npm tarball path is not a safe directory\");\n\t}\n\treturn current;\n}\n\nfunction writeExclusiveFile(path: string, content: Buffer): void {\n\tconst descriptor = openSync(\n\t\tpath,\n\t\tconstants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | (constants.O_NOFOLLOW ?? 0),\n\t\t0o600,\n\t);\n\ttry {\n\t\twriteFileSync(descriptor, content);\n\t} finally {\n\t\tcloseSync(descriptor);\n\t}\n}\n\n/** Extract registry tarball files only. Links and special entries fail closed. */\nexport function extractNpmPackageTarball(archivePath: string, destination: string): void {\n\tif (existsSync(destination)) throw new Error(\"Npm package extraction destination already exists\");\n\tconst archive = readFileSync(archivePath);\n\tif (archive.length > MAX_ARCHIVE_BYTES) throw new Error(\"Npm package archive exceeds the size limit\");\n\tconst root = resolve(destination);\n\tlet expanded: Buffer;\n\ttry {\n\t\texpanded = gunzipSync(archive, { maxOutputLength: MAX_EXPANDED_BYTES });\n\t} catch {\n\t\tthrow new Error(\"Npm package archive is not a bounded gzip stream\");\n\t}\n\tmkdirSync(root, { recursive: true, mode: 0o700 });\n\tlet offset = 0;\n\tlet files = 0;\n\tlet nextPath: string | undefined;\n\ttry {\n\t\twhile (offset + TAR_BLOCK_BYTES <= expanded.length) {\n\t\t\tconst header = expanded.subarray(offset, offset + TAR_BLOCK_BYTES);\n\t\t\toffset += TAR_BLOCK_BYTES;\n\t\t\tif (header.every((byte) => byte === 0)) break;\n\t\t\tchecksum(header);\n\t\t\tconst size = octal(header.subarray(124, 136), \"entry size\");\n\t\t\tif (size > MAX_FILE_BYTES || offset + size > expanded.length)\n\t\t\t\tthrow new Error(\"Npm tarball entry exceeds the size limit\");\n\t\t\tconst payload = expanded.subarray(offset, offset + size);\n\t\t\toffset += Math.ceil(size / TAR_BLOCK_BYTES) * TAR_BLOCK_BYTES;\n\t\t\tconst type = String.fromCharCode(header[156] ?? 0);\n\t\t\tconst headerPath = [text(header.subarray(345, 500)), text(header.subarray(0, 100))].filter(Boolean).join(\"/\");\n\t\t\tif (type === \"x\") {\n\t\t\t\tnextPath = parsePax(payload).get(\"path\") ?? nextPath;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (type === \"g\") continue;\n\t\t\tif (type === \"L\") {\n\t\t\t\tnextPath = text(payload).replace(/\\n$/u, \"\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst segments = safePackagePath(nextPath ?? headerPath);\n\t\t\tnextPath = undefined;\n\t\t\tif (segments.length === 0) continue;\n\t\t\tconst path = resolve(root, ...segments);\n\t\t\tassertInside(root, path);\n\t\t\tif (type === \"5\") {\n\t\t\t\tensureDirectory(root, segments);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (type !== \"0\" && type !== \"\\0\") throw new Error(`Npm tarball contains unsupported entry type ${type}`);\n\t\t\tfiles += 1;\n\t\t\tif (files > MAX_FILES) throw new Error(\"Npm package contains too many files\");\n\t\t\tensureDirectory(root, segments.slice(0, -1));\n\t\t\twriteExclusiveFile(path, payload);\n\t\t}\n\t} catch (error) {\n\t\trmSync(root, { recursive: true, force: true });\n\t\tthrow error;\n\t}\n}\n"]}
@@ -0,0 +1,161 @@
1
+ import { closeSync, constants, existsSync, lstatSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
2
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import { gunzipSync } from "node:zlib";
4
+ const TAR_BLOCK_BYTES = 512;
5
+ const MAX_ARCHIVE_BYTES = 64 * 1024 * 1024;
6
+ const MAX_EXPANDED_BYTES = 256 * 1024 * 1024;
7
+ const MAX_FILE_BYTES = 64 * 1024 * 1024;
8
+ const MAX_FILES = 10_000;
9
+ function text(buffer) {
10
+ const end = buffer.indexOf(0);
11
+ return buffer.subarray(0, end === -1 ? buffer.length : end).toString("utf8");
12
+ }
13
+ function octal(buffer, label) {
14
+ const value = text(buffer).trim();
15
+ if (!/^[0-7]+$/u.test(value))
16
+ throw new Error(`Invalid tar ${label}`);
17
+ const parsed = Number.parseInt(value, 8);
18
+ if (!Number.isSafeInteger(parsed) || parsed < 0)
19
+ throw new Error(`Invalid tar ${label}`);
20
+ return parsed;
21
+ }
22
+ function checksum(header) {
23
+ const declared = octal(header.subarray(148, 156), "checksum");
24
+ let actual = 0;
25
+ for (let index = 0; index < header.length; index += 1) {
26
+ actual += index >= 148 && index < 156 ? 0x20 : (header[index] ?? 0);
27
+ }
28
+ if (actual !== declared)
29
+ throw new Error("Invalid tar checksum");
30
+ }
31
+ function parsePax(payload) {
32
+ const fields = new Map();
33
+ let offset = 0;
34
+ while (offset < payload.length) {
35
+ const space = payload.indexOf(0x20, offset);
36
+ if (space < 0)
37
+ throw new Error("Invalid tar PAX record");
38
+ const lengthText = payload.subarray(offset, space).toString("ascii");
39
+ if (!/^\d+$/u.test(lengthText))
40
+ throw new Error("Invalid tar PAX record length");
41
+ const length = Number.parseInt(lengthText, 10);
42
+ if (!Number.isSafeInteger(length) || length <= 0 || offset + length > payload.length) {
43
+ throw new Error("Invalid tar PAX record length");
44
+ }
45
+ const record = payload.subarray(space + 1, offset + length - 1).toString("utf8");
46
+ const equals = record.indexOf("=");
47
+ if (equals > 0)
48
+ fields.set(record.slice(0, equals), record.slice(equals + 1));
49
+ offset += length;
50
+ }
51
+ return fields;
52
+ }
53
+ function safePackagePath(rawPath) {
54
+ if (!rawPath || rawPath.includes("\0") || rawPath.includes("\\") || rawPath.startsWith("/")) {
55
+ throw new Error("Npm tarball contains an unsafe path");
56
+ }
57
+ const trimmed = rawPath.endsWith("/") ? rawPath.slice(0, -1) : rawPath;
58
+ const segments = trimmed.split("/");
59
+ if (segments[0] !== "package")
60
+ throw new Error("Npm tarball entries must stay under package/");
61
+ const relativeSegments = segments.slice(1);
62
+ if (relativeSegments.some((segment) => !segment || segment === "." || segment === "..")) {
63
+ throw new Error("Npm tarball contains an unsafe path");
64
+ }
65
+ return relativeSegments;
66
+ }
67
+ function assertInside(root, path) {
68
+ const fromRoot = relative(root, path);
69
+ if (!fromRoot || fromRoot === ".." || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {
70
+ throw new Error("Npm tarball contains an unsafe path");
71
+ }
72
+ }
73
+ function ensureDirectory(root, segments) {
74
+ let current = root;
75
+ for (const segment of segments) {
76
+ current = join(current, segment);
77
+ if (!existsSync(current))
78
+ mkdirSync(current, { mode: 0o700 });
79
+ const stats = lstatSync(current);
80
+ if (!stats.isDirectory() || stats.isSymbolicLink())
81
+ throw new Error("Npm tarball path is not a safe directory");
82
+ }
83
+ return current;
84
+ }
85
+ function writeExclusiveFile(path, content) {
86
+ const descriptor = openSync(path, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | (constants.O_NOFOLLOW ?? 0), 0o600);
87
+ try {
88
+ writeFileSync(descriptor, content);
89
+ }
90
+ finally {
91
+ closeSync(descriptor);
92
+ }
93
+ }
94
+ /** Extract registry tarball files only. Links and special entries fail closed. */
95
+ export function extractNpmPackageTarball(archivePath, destination) {
96
+ if (existsSync(destination))
97
+ throw new Error("Npm package extraction destination already exists");
98
+ const archive = readFileSync(archivePath);
99
+ if (archive.length > MAX_ARCHIVE_BYTES)
100
+ throw new Error("Npm package archive exceeds the size limit");
101
+ const root = resolve(destination);
102
+ let expanded;
103
+ try {
104
+ expanded = gunzipSync(archive, { maxOutputLength: MAX_EXPANDED_BYTES });
105
+ }
106
+ catch {
107
+ throw new Error("Npm package archive is not a bounded gzip stream");
108
+ }
109
+ mkdirSync(root, { recursive: true, mode: 0o700 });
110
+ let offset = 0;
111
+ let files = 0;
112
+ let nextPath;
113
+ try {
114
+ while (offset + TAR_BLOCK_BYTES <= expanded.length) {
115
+ const header = expanded.subarray(offset, offset + TAR_BLOCK_BYTES);
116
+ offset += TAR_BLOCK_BYTES;
117
+ if (header.every((byte) => byte === 0))
118
+ break;
119
+ checksum(header);
120
+ const size = octal(header.subarray(124, 136), "entry size");
121
+ if (size > MAX_FILE_BYTES || offset + size > expanded.length)
122
+ throw new Error("Npm tarball entry exceeds the size limit");
123
+ const payload = expanded.subarray(offset, offset + size);
124
+ offset += Math.ceil(size / TAR_BLOCK_BYTES) * TAR_BLOCK_BYTES;
125
+ const type = String.fromCharCode(header[156] ?? 0);
126
+ const headerPath = [text(header.subarray(345, 500)), text(header.subarray(0, 100))].filter(Boolean).join("/");
127
+ if (type === "x") {
128
+ nextPath = parsePax(payload).get("path") ?? nextPath;
129
+ continue;
130
+ }
131
+ if (type === "g")
132
+ continue;
133
+ if (type === "L") {
134
+ nextPath = text(payload).replace(/\n$/u, "");
135
+ continue;
136
+ }
137
+ const segments = safePackagePath(nextPath ?? headerPath);
138
+ nextPath = undefined;
139
+ if (segments.length === 0)
140
+ continue;
141
+ const path = resolve(root, ...segments);
142
+ assertInside(root, path);
143
+ if (type === "5") {
144
+ ensureDirectory(root, segments);
145
+ continue;
146
+ }
147
+ if (type !== "0" && type !== "\0")
148
+ throw new Error(`Npm tarball contains unsupported entry type ${type}`);
149
+ files += 1;
150
+ if (files > MAX_FILES)
151
+ throw new Error("Npm package contains too many files");
152
+ ensureDirectory(root, segments.slice(0, -1));
153
+ writeExclusiveFile(path, payload);
154
+ }
155
+ }
156
+ catch (error) {
157
+ rmSync(root, { recursive: true, force: true });
158
+ throw error;
159
+ }
160
+ }
161
+ //# sourceMappingURL=package-archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-archive.js","sourceRoot":"","sources":["../../src/core/package-archive.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tcloseSync,\n\tconstants,\n\texistsSync,\n\tlstatSync,\n\tmkdirSync,\n\topenSync,\n\treadFileSync,\n\trmSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { isAbsolute, join, relative, resolve, sep } from \"node:path\";\nimport { gunzipSync } from \"node:zlib\";\n\nconst TAR_BLOCK_BYTES = 512;\nconst MAX_ARCHIVE_BYTES = 64 * 1024 * 1024;\nconst MAX_EXPANDED_BYTES = 256 * 1024 * 1024;\nconst MAX_FILE_BYTES = 64 * 1024 * 1024;\nconst MAX_FILES = 10_000;\n\nfunction text(buffer: Buffer): string {\n\tconst end = buffer.indexOf(0);\n\treturn buffer.subarray(0, end === -1 ? buffer.length : end).toString(\"utf8\");\n}\n\nfunction octal(buffer: Buffer, label: string): number {\n\tconst value = text(buffer).trim();\n\tif (!/^[0-7]+$/u.test(value)) throw new Error(`Invalid tar ${label}`);\n\tconst parsed = Number.parseInt(value, 8);\n\tif (!Number.isSafeInteger(parsed) || parsed < 0) throw new Error(`Invalid tar ${label}`);\n\treturn parsed;\n}\n\nfunction checksum(header: Buffer): void {\n\tconst declared = octal(header.subarray(148, 156), \"checksum\");\n\tlet actual = 0;\n\tfor (let index = 0; index < header.length; index += 1) {\n\t\tactual += index >= 148 && index < 156 ? 0x20 : (header[index] ?? 0);\n\t}\n\tif (actual !== declared) throw new Error(\"Invalid tar checksum\");\n}\n\nfunction parsePax(payload: Buffer): Map<string, string> {\n\tconst fields = new Map<string, string>();\n\tlet offset = 0;\n\twhile (offset < payload.length) {\n\t\tconst space = payload.indexOf(0x20, offset);\n\t\tif (space < 0) throw new Error(\"Invalid tar PAX record\");\n\t\tconst lengthText = payload.subarray(offset, space).toString(\"ascii\");\n\t\tif (!/^\\d+$/u.test(lengthText)) throw new Error(\"Invalid tar PAX record length\");\n\t\tconst length = Number.parseInt(lengthText, 10);\n\t\tif (!Number.isSafeInteger(length) || length <= 0 || offset + length > payload.length) {\n\t\t\tthrow new Error(\"Invalid tar PAX record length\");\n\t\t}\n\t\tconst record = payload.subarray(space + 1, offset + length - 1).toString(\"utf8\");\n\t\tconst equals = record.indexOf(\"=\");\n\t\tif (equals > 0) fields.set(record.slice(0, equals), record.slice(equals + 1));\n\t\toffset += length;\n\t}\n\treturn fields;\n}\n\nfunction safePackagePath(rawPath: string): string[] {\n\tif (!rawPath || rawPath.includes(\"\\0\") || rawPath.includes(\"\\\\\") || rawPath.startsWith(\"/\")) {\n\t\tthrow new Error(\"Npm tarball contains an unsafe path\");\n\t}\n\tconst trimmed = rawPath.endsWith(\"/\") ? rawPath.slice(0, -1) : rawPath;\n\tconst segments = trimmed.split(\"/\");\n\tif (segments[0] !== \"package\") throw new Error(\"Npm tarball entries must stay under package/\");\n\tconst relativeSegments = segments.slice(1);\n\tif (relativeSegments.some((segment) => !segment || segment === \".\" || segment === \"..\")) {\n\t\tthrow new Error(\"Npm tarball contains an unsafe path\");\n\t}\n\treturn relativeSegments;\n}\n\nfunction assertInside(root: string, path: string): void {\n\tconst fromRoot = relative(root, path);\n\tif (!fromRoot || fromRoot === \"..\" || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {\n\t\tthrow new Error(\"Npm tarball contains an unsafe path\");\n\t}\n}\n\nfunction ensureDirectory(root: string, segments: string[]): string {\n\tlet current = root;\n\tfor (const segment of segments) {\n\t\tcurrent = join(current, segment);\n\t\tif (!existsSync(current)) mkdirSync(current, { mode: 0o700 });\n\t\tconst stats = lstatSync(current);\n\t\tif (!stats.isDirectory() || stats.isSymbolicLink()) throw new Error(\"Npm tarball path is not a safe directory\");\n\t}\n\treturn current;\n}\n\nfunction writeExclusiveFile(path: string, content: Buffer): void {\n\tconst descriptor = openSync(\n\t\tpath,\n\t\tconstants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | (constants.O_NOFOLLOW ?? 0),\n\t\t0o600,\n\t);\n\ttry {\n\t\twriteFileSync(descriptor, content);\n\t} finally {\n\t\tcloseSync(descriptor);\n\t}\n}\n\n/** Extract registry tarball files only. Links and special entries fail closed. */\nexport function extractNpmPackageTarball(archivePath: string, destination: string): void {\n\tif (existsSync(destination)) throw new Error(\"Npm package extraction destination already exists\");\n\tconst archive = readFileSync(archivePath);\n\tif (archive.length > MAX_ARCHIVE_BYTES) throw new Error(\"Npm package archive exceeds the size limit\");\n\tconst root = resolve(destination);\n\tlet expanded: Buffer;\n\ttry {\n\t\texpanded = gunzipSync(archive, { maxOutputLength: MAX_EXPANDED_BYTES });\n\t} catch {\n\t\tthrow new Error(\"Npm package archive is not a bounded gzip stream\");\n\t}\n\tmkdirSync(root, { recursive: true, mode: 0o700 });\n\tlet offset = 0;\n\tlet files = 0;\n\tlet nextPath: string | undefined;\n\ttry {\n\t\twhile (offset + TAR_BLOCK_BYTES <= expanded.length) {\n\t\t\tconst header = expanded.subarray(offset, offset + TAR_BLOCK_BYTES);\n\t\t\toffset += TAR_BLOCK_BYTES;\n\t\t\tif (header.every((byte) => byte === 0)) break;\n\t\t\tchecksum(header);\n\t\t\tconst size = octal(header.subarray(124, 136), \"entry size\");\n\t\t\tif (size > MAX_FILE_BYTES || offset + size > expanded.length)\n\t\t\t\tthrow new Error(\"Npm tarball entry exceeds the size limit\");\n\t\t\tconst payload = expanded.subarray(offset, offset + size);\n\t\t\toffset += Math.ceil(size / TAR_BLOCK_BYTES) * TAR_BLOCK_BYTES;\n\t\t\tconst type = String.fromCharCode(header[156] ?? 0);\n\t\t\tconst headerPath = [text(header.subarray(345, 500)), text(header.subarray(0, 100))].filter(Boolean).join(\"/\");\n\t\t\tif (type === \"x\") {\n\t\t\t\tnextPath = parsePax(payload).get(\"path\") ?? nextPath;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (type === \"g\") continue;\n\t\t\tif (type === \"L\") {\n\t\t\t\tnextPath = text(payload).replace(/\\n$/u, \"\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst segments = safePackagePath(nextPath ?? headerPath);\n\t\t\tnextPath = undefined;\n\t\t\tif (segments.length === 0) continue;\n\t\t\tconst path = resolve(root, ...segments);\n\t\t\tassertInside(root, path);\n\t\t\tif (type === \"5\") {\n\t\t\t\tensureDirectory(root, segments);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (type !== \"0\" && type !== \"\\0\") throw new Error(`Npm tarball contains unsupported entry type ${type}`);\n\t\t\tfiles += 1;\n\t\t\tif (files > MAX_FILES) throw new Error(\"Npm package contains too many files\");\n\t\t\tensureDirectory(root, segments.slice(0, -1));\n\t\t\twriteExclusiveFile(path, payload);\n\t\t}\n\t} catch (error) {\n\t\trmSync(root, { recursive: true, force: true });\n\t\tthrow error;\n\t}\n}\n"]}
@@ -0,0 +1,13 @@
1
+ export declare const PACKAGE_DOCTOR_SUPPORTED_EVENTS: Set<string>;
2
+ export interface ScannedPackageSource {
3
+ file: string;
4
+ text: string;
5
+ }
6
+ export declare function scanPackageExtensionSources(root: string, extensions: string[]): {
7
+ sources: ScannedPackageSource[];
8
+ skipped: string[];
9
+ };
10
+ export declare function importedSpecifiers(text: string): string[];
11
+ export declare function subscribedEvents(text: string): string[];
12
+ export declare function sourceFilesMatching(sources: ScannedPackageSource[], predicate: (text: string) => boolean): string[];
13
+ //# sourceMappingURL=package-doctor-source-scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-doctor-source-scan.d.ts","sourceRoot":"","sources":["../../src/core/package-doctor-source-scan.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,+BAA+B,aA8B1C,CAAC;AAEH,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACb;AASD,wBAAgB,2BAA2B,CAC1C,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAAE,GAClB;IAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CA4CxD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAYzD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,EAAE,CAMnH","sourcesContent":["import { closeSync, constants, fstatSync, openSync, readSync, realpathSync, statSync } from \"node:fs\";\nimport { isAbsolute, relative, resolve, sep } from \"node:path\";\n\nconst MAX_EXTENSION_FILES = 256;\nconst MAX_EXTENSION_BYTES = 1_048_576;\n\nexport const PACKAGE_DOCTOR_SUPPORTED_EVENTS = new Set([\n\t\"resources_discover\",\n\t\"session_start\",\n\t\"session_before_switch\",\n\t\"session_before_fork\",\n\t\"session_before_compact\",\n\t\"session_compact\",\n\t\"session_shutdown\",\n\t\"session_before_tree\",\n\t\"session_tree\",\n\t\"context\",\n\t\"before_provider_request\",\n\t\"after_provider_response\",\n\t\"before_agent_start\",\n\t\"agent_start\",\n\t\"agent_end\",\n\t\"turn_start\",\n\t\"turn_end\",\n\t\"message_start\",\n\t\"message_update\",\n\t\"message_end\",\n\t\"tool_execution_start\",\n\t\"tool_execution_update\",\n\t\"tool_execution_end\",\n\t\"model_select\",\n\t\"thinking_level_select\",\n\t\"tool_call\",\n\t\"tool_result\",\n\t\"user_bash\",\n\t\"input\",\n]);\n\nexport interface ScannedPackageSource {\n\tfile: string;\n\ttext: string;\n}\n\nfunction packageRelativePath(root: string, path: string): string | null {\n\tconst candidate = relative(root, resolve(path));\n\tif (!candidate || candidate === \".\") return candidate || \".\";\n\tif (candidate === \"..\" || candidate.startsWith(`..${sep}`) || isAbsolute(candidate)) return null;\n\treturn candidate.replace(/\\\\/gu, \"/\");\n}\n\nexport function scanPackageExtensionSources(\n\troot: string,\n\textensions: string[],\n): { sources: ScannedPackageSource[]; skipped: string[] } {\n\tconst sources: ScannedPackageSource[] = [];\n\tconst skipped: string[] = [];\n\tconst realRoot = realpathSync(root);\n\tfor (const path of [...new Set(extensions)].slice(0, MAX_EXTENSION_FILES)) {\n\t\tconst file = packageRelativePath(root, path);\n\t\tif (file === null) {\n\t\t\tskipped.push(\"<outside-package>\");\n\t\t\tcontinue;\n\t\t}\n\t\tlet fd: number | undefined;\n\t\ttry {\n\t\t\tconst realPath = realpathSync(path);\n\t\t\tif (packageRelativePath(realRoot, realPath) === null) {\n\t\t\t\tskipped.push(\"<outside-package>\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst expected = statSync(realPath);\n\t\t\tfd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));\n\t\t\tconst opened = fstatSync(fd);\n\t\t\tif (!opened.isFile() || opened.dev !== expected.dev || opened.ino !== expected.ino) {\n\t\t\t\tskipped.push(file);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst buffer = Buffer.alloc(MAX_EXTENSION_BYTES + 1);\n\t\t\tlet offset = 0;\n\t\t\twhile (offset < buffer.length) {\n\t\t\t\tconst read = readSync(fd, buffer, offset, buffer.length - offset, offset);\n\t\t\t\tif (read === 0) break;\n\t\t\t\toffset += read;\n\t\t\t}\n\t\t\tif (offset > MAX_EXTENSION_BYTES) {\n\t\t\t\tskipped.push(file);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsources.push({ file, text: buffer.subarray(0, offset).toString(\"utf8\") });\n\t\t} catch {\n\t\t\tskipped.push(file);\n\t\t} finally {\n\t\t\tif (fd !== undefined) closeSync(fd);\n\t\t}\n\t}\n\tif (extensions.length > MAX_EXTENSION_FILES) skipped.push(\"<file-limit>\");\n\treturn { sources, skipped };\n}\n\nexport function importedSpecifiers(text: string): string[] {\n\tconst specifiers = new Set<string>();\n\tconst patterns = [\n\t\t/\\b(?:import|export)\\s+(?:[^\"']+?\\s+from\\s+)?[\"']([^\"']+)[\"']/gu,\n\t\t/\\b(?:import|require)\\s*\\(\\s*[\"']([^\"']+)[\"']\\s*\\)/gu,\n\t];\n\tfor (const pattern of patterns) {\n\t\tfor (const match of text.matchAll(pattern)) {\n\t\t\tif (match[1]) specifiers.add(match[1]);\n\t\t}\n\t}\n\treturn [...specifiers];\n}\n\nexport function subscribedEvents(text: string): string[] {\n\tconst events: string[] = [];\n\tfor (const match of text.matchAll(/\\b(?:api|omk|pi)\\.on\\(\\s*[\"']([^\"']+)[\"']/gu)) {\n\t\tif (match[1] !== undefined) events.push(match[1]);\n\t}\n\treturn events;\n}\n\nexport function sourceFilesMatching(sources: ScannedPackageSource[], predicate: (text: string) => boolean): string[] {\n\tconst files: string[] = [];\n\tfor (const source of sources) {\n\t\tif (predicate(source.text)) files.push(source.file);\n\t}\n\treturn files;\n}\n"]}
@@ -0,0 +1,124 @@
1
+ import { closeSync, constants, fstatSync, openSync, readSync, realpathSync, statSync } from "node:fs";
2
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3
+ const MAX_EXTENSION_FILES = 256;
4
+ const MAX_EXTENSION_BYTES = 1_048_576;
5
+ export const PACKAGE_DOCTOR_SUPPORTED_EVENTS = new Set([
6
+ "resources_discover",
7
+ "session_start",
8
+ "session_before_switch",
9
+ "session_before_fork",
10
+ "session_before_compact",
11
+ "session_compact",
12
+ "session_shutdown",
13
+ "session_before_tree",
14
+ "session_tree",
15
+ "context",
16
+ "before_provider_request",
17
+ "after_provider_response",
18
+ "before_agent_start",
19
+ "agent_start",
20
+ "agent_end",
21
+ "turn_start",
22
+ "turn_end",
23
+ "message_start",
24
+ "message_update",
25
+ "message_end",
26
+ "tool_execution_start",
27
+ "tool_execution_update",
28
+ "tool_execution_end",
29
+ "model_select",
30
+ "thinking_level_select",
31
+ "tool_call",
32
+ "tool_result",
33
+ "user_bash",
34
+ "input",
35
+ ]);
36
+ function packageRelativePath(root, path) {
37
+ const candidate = relative(root, resolve(path));
38
+ if (!candidate || candidate === ".")
39
+ return candidate || ".";
40
+ if (candidate === ".." || candidate.startsWith(`..${sep}`) || isAbsolute(candidate))
41
+ return null;
42
+ return candidate.replace(/\\/gu, "/");
43
+ }
44
+ export function scanPackageExtensionSources(root, extensions) {
45
+ const sources = [];
46
+ const skipped = [];
47
+ const realRoot = realpathSync(root);
48
+ for (const path of [...new Set(extensions)].slice(0, MAX_EXTENSION_FILES)) {
49
+ const file = packageRelativePath(root, path);
50
+ if (file === null) {
51
+ skipped.push("<outside-package>");
52
+ continue;
53
+ }
54
+ let fd;
55
+ try {
56
+ const realPath = realpathSync(path);
57
+ if (packageRelativePath(realRoot, realPath) === null) {
58
+ skipped.push("<outside-package>");
59
+ continue;
60
+ }
61
+ const expected = statSync(realPath);
62
+ fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
63
+ const opened = fstatSync(fd);
64
+ if (!opened.isFile() || opened.dev !== expected.dev || opened.ino !== expected.ino) {
65
+ skipped.push(file);
66
+ continue;
67
+ }
68
+ const buffer = Buffer.alloc(MAX_EXTENSION_BYTES + 1);
69
+ let offset = 0;
70
+ while (offset < buffer.length) {
71
+ const read = readSync(fd, buffer, offset, buffer.length - offset, offset);
72
+ if (read === 0)
73
+ break;
74
+ offset += read;
75
+ }
76
+ if (offset > MAX_EXTENSION_BYTES) {
77
+ skipped.push(file);
78
+ continue;
79
+ }
80
+ sources.push({ file, text: buffer.subarray(0, offset).toString("utf8") });
81
+ }
82
+ catch {
83
+ skipped.push(file);
84
+ }
85
+ finally {
86
+ if (fd !== undefined)
87
+ closeSync(fd);
88
+ }
89
+ }
90
+ if (extensions.length > MAX_EXTENSION_FILES)
91
+ skipped.push("<file-limit>");
92
+ return { sources, skipped };
93
+ }
94
+ export function importedSpecifiers(text) {
95
+ const specifiers = new Set();
96
+ const patterns = [
97
+ /\b(?:import|export)\s+(?:[^"']+?\s+from\s+)?["']([^"']+)["']/gu,
98
+ /\b(?:import|require)\s*\(\s*["']([^"']+)["']\s*\)/gu,
99
+ ];
100
+ for (const pattern of patterns) {
101
+ for (const match of text.matchAll(pattern)) {
102
+ if (match[1])
103
+ specifiers.add(match[1]);
104
+ }
105
+ }
106
+ return [...specifiers];
107
+ }
108
+ export function subscribedEvents(text) {
109
+ const events = [];
110
+ for (const match of text.matchAll(/\b(?:api|omk|pi)\.on\(\s*["']([^"']+)["']/gu)) {
111
+ if (match[1] !== undefined)
112
+ events.push(match[1]);
113
+ }
114
+ return events;
115
+ }
116
+ export function sourceFilesMatching(sources, predicate) {
117
+ const files = [];
118
+ for (const source of sources) {
119
+ if (predicate(source.text))
120
+ files.push(source.file);
121
+ }
122
+ return files;
123
+ }
124
+ //# sourceMappingURL=package-doctor-source-scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-doctor-source-scan.js","sourceRoot":"","sources":["../../src/core/package-doctor-source-scan.ts"],"names":[],"mappings":"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","sourcesContent":["import { closeSync, constants, fstatSync, openSync, readSync, realpathSync, statSync } from \"node:fs\";\nimport { isAbsolute, relative, resolve, sep } from \"node:path\";\n\nconst MAX_EXTENSION_FILES = 256;\nconst MAX_EXTENSION_BYTES = 1_048_576;\n\nexport const PACKAGE_DOCTOR_SUPPORTED_EVENTS = new Set([\n\t\"resources_discover\",\n\t\"session_start\",\n\t\"session_before_switch\",\n\t\"session_before_fork\",\n\t\"session_before_compact\",\n\t\"session_compact\",\n\t\"session_shutdown\",\n\t\"session_before_tree\",\n\t\"session_tree\",\n\t\"context\",\n\t\"before_provider_request\",\n\t\"after_provider_response\",\n\t\"before_agent_start\",\n\t\"agent_start\",\n\t\"agent_end\",\n\t\"turn_start\",\n\t\"turn_end\",\n\t\"message_start\",\n\t\"message_update\",\n\t\"message_end\",\n\t\"tool_execution_start\",\n\t\"tool_execution_update\",\n\t\"tool_execution_end\",\n\t\"model_select\",\n\t\"thinking_level_select\",\n\t\"tool_call\",\n\t\"tool_result\",\n\t\"user_bash\",\n\t\"input\",\n]);\n\nexport interface ScannedPackageSource {\n\tfile: string;\n\ttext: string;\n}\n\nfunction packageRelativePath(root: string, path: string): string | null {\n\tconst candidate = relative(root, resolve(path));\n\tif (!candidate || candidate === \".\") return candidate || \".\";\n\tif (candidate === \"..\" || candidate.startsWith(`..${sep}`) || isAbsolute(candidate)) return null;\n\treturn candidate.replace(/\\\\/gu, \"/\");\n}\n\nexport function scanPackageExtensionSources(\n\troot: string,\n\textensions: string[],\n): { sources: ScannedPackageSource[]; skipped: string[] } {\n\tconst sources: ScannedPackageSource[] = [];\n\tconst skipped: string[] = [];\n\tconst realRoot = realpathSync(root);\n\tfor (const path of [...new Set(extensions)].slice(0, MAX_EXTENSION_FILES)) {\n\t\tconst file = packageRelativePath(root, path);\n\t\tif (file === null) {\n\t\t\tskipped.push(\"<outside-package>\");\n\t\t\tcontinue;\n\t\t}\n\t\tlet fd: number | undefined;\n\t\ttry {\n\t\t\tconst realPath = realpathSync(path);\n\t\t\tif (packageRelativePath(realRoot, realPath) === null) {\n\t\t\t\tskipped.push(\"<outside-package>\");\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst expected = statSync(realPath);\n\t\t\tfd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));\n\t\t\tconst opened = fstatSync(fd);\n\t\t\tif (!opened.isFile() || opened.dev !== expected.dev || opened.ino !== expected.ino) {\n\t\t\t\tskipped.push(file);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst buffer = Buffer.alloc(MAX_EXTENSION_BYTES + 1);\n\t\t\tlet offset = 0;\n\t\t\twhile (offset < buffer.length) {\n\t\t\t\tconst read = readSync(fd, buffer, offset, buffer.length - offset, offset);\n\t\t\t\tif (read === 0) break;\n\t\t\t\toffset += read;\n\t\t\t}\n\t\t\tif (offset > MAX_EXTENSION_BYTES) {\n\t\t\t\tskipped.push(file);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsources.push({ file, text: buffer.subarray(0, offset).toString(\"utf8\") });\n\t\t} catch {\n\t\t\tskipped.push(file);\n\t\t} finally {\n\t\t\tif (fd !== undefined) closeSync(fd);\n\t\t}\n\t}\n\tif (extensions.length > MAX_EXTENSION_FILES) skipped.push(\"<file-limit>\");\n\treturn { sources, skipped };\n}\n\nexport function importedSpecifiers(text: string): string[] {\n\tconst specifiers = new Set<string>();\n\tconst patterns = [\n\t\t/\\b(?:import|export)\\s+(?:[^\"']+?\\s+from\\s+)?[\"']([^\"']+)[\"']/gu,\n\t\t/\\b(?:import|require)\\s*\\(\\s*[\"']([^\"']+)[\"']\\s*\\)/gu,\n\t];\n\tfor (const pattern of patterns) {\n\t\tfor (const match of text.matchAll(pattern)) {\n\t\t\tif (match[1]) specifiers.add(match[1]);\n\t\t}\n\t}\n\treturn [...specifiers];\n}\n\nexport function subscribedEvents(text: string): string[] {\n\tconst events: string[] = [];\n\tfor (const match of text.matchAll(/\\b(?:api|omk|pi)\\.on\\(\\s*[\"']([^\"']+)[\"']/gu)) {\n\t\tif (match[1] !== undefined) events.push(match[1]);\n\t}\n\treturn events;\n}\n\nexport function sourceFilesMatching(sources: ScannedPackageSource[], predicate: (text: string) => boolean): string[] {\n\tconst files: string[] = [];\n\tfor (const source of sources) {\n\t\tif (predicate(source.text)) files.push(source.file);\n\t}\n\treturn files;\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import { type PackageManifestKey } from "./package-manifest.ts";
2
+ export interface PackageDoctorResources {
3
+ extensions: string[];
4
+ skills: string[];
5
+ prompts: string[];
6
+ themes: string[];
7
+ }
8
+ export interface PackageDoctorInput {
9
+ source: string;
10
+ packageRoot: string;
11
+ resources: PackageDoctorResources;
12
+ }
13
+ export interface PackageDoctorCheck {
14
+ id: "manifest" | "resources" | "runtime-imports" | "storage-paths" | "lifecycle-events" | "headless-ui" | "resume-awareness" | "scan-coverage";
15
+ status: "pass" | "warning" | "error";
16
+ message: string;
17
+ files?: string[];
18
+ }
19
+ export interface PackageDoctorResult {
20
+ schemaVersion: 1;
21
+ source: string;
22
+ package: {
23
+ name?: string;
24
+ version?: string;
25
+ };
26
+ manifest: {
27
+ selected: PackageManifestKey | "convention";
28
+ shadowedPi: boolean;
29
+ diagnostics: Array<{
30
+ path: string;
31
+ message: string;
32
+ }>;
33
+ };
34
+ resources: Record<keyof PackageDoctorResources, number>;
35
+ inspectedFiles: string[];
36
+ checks: PackageDoctorCheck[];
37
+ compatible: boolean;
38
+ }
39
+ /** Static-only compatibility inspection. Extension modules are never imported or executed. */
40
+ export declare function inspectPackageCompatibility(input: PackageDoctorInput): PackageDoctorResult;
41
+ //# sourceMappingURL=package-doctor.d.ts.map