@namzu/sdk 57.0.0 → 58.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +3 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/agents/file-definitions.d.ts +35 -4
  6. package/dist/agents/file-definitions.d.ts.map +1 -1
  7. package/dist/agents/file-definitions.js +98 -8
  8. package/dist/agents/file-definitions.js.map +1 -1
  9. package/dist/authorization/protected-paths.d.ts +9 -0
  10. package/dist/authorization/protected-paths.d.ts.map +1 -1
  11. package/dist/authorization/protected-paths.js +29 -3
  12. package/dist/authorization/protected-paths.js.map +1 -1
  13. package/dist/compaction/index.d.ts +1 -0
  14. package/dist/compaction/index.d.ts.map +1 -1
  15. package/dist/compaction/index.js.map +1 -1
  16. package/dist/compaction/manual.d.ts +7 -0
  17. package/dist/compaction/manual.d.ts.map +1 -1
  18. package/dist/compaction/manual.js +31 -12
  19. package/dist/compaction/manual.js.map +1 -1
  20. package/dist/compaction/model-summary.d.ts +101 -0
  21. package/dist/compaction/model-summary.d.ts.map +1 -0
  22. package/dist/compaction/model-summary.js +333 -0
  23. package/dist/compaction/model-summary.js.map +1 -0
  24. package/dist/compaction/verifier.d.ts +6 -1
  25. package/dist/compaction/verifier.d.ts.map +1 -1
  26. package/dist/compaction/verifier.js +3 -3
  27. package/dist/compaction/verifier.js.map +1 -1
  28. package/dist/config/runtime.d.ts +62 -0
  29. package/dist/config/runtime.d.ts.map +1 -1
  30. package/dist/config/runtime.js +16 -0
  31. package/dist/config/runtime.js.map +1 -1
  32. package/dist/connector/mcp/client.d.ts +8 -0
  33. package/dist/connector/mcp/client.d.ts.map +1 -1
  34. package/dist/connector/mcp/client.js +75 -2
  35. package/dist/connector/mcp/client.js.map +1 -1
  36. package/dist/constants/mcp/index.d.ts +2 -0
  37. package/dist/constants/mcp/index.d.ts.map +1 -1
  38. package/dist/constants/mcp/index.js +2 -0
  39. package/dist/constants/mcp/index.js.map +1 -1
  40. package/dist/constants/plugin/index.d.ts +2 -0
  41. package/dist/constants/plugin/index.d.ts.map +1 -1
  42. package/dist/constants/plugin/index.js +2 -0
  43. package/dist/constants/plugin/index.js.map +1 -1
  44. package/dist/manager/agent/lifecycle.d.ts +1 -1
  45. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  46. package/dist/manager/agent/lifecycle.js +25 -4
  47. package/dist/manager/agent/lifecycle.js.map +1 -1
  48. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  49. package/dist/manager/session/turn-recorder.js +2 -1
  50. package/dist/manager/session/turn-recorder.js.map +1 -1
  51. package/dist/persona/assembler.d.ts.map +1 -1
  52. package/dist/persona/assembler.js +2 -2
  53. package/dist/persona/assembler.js.map +1 -1
  54. package/dist/plugin/index.d.ts +1 -1
  55. package/dist/plugin/index.d.ts.map +1 -1
  56. package/dist/plugin/lifecycle.d.ts +8 -1
  57. package/dist/plugin/lifecycle.d.ts.map +1 -1
  58. package/dist/plugin/lifecycle.js +9 -2
  59. package/dist/plugin/lifecycle.js.map +1 -1
  60. package/dist/plugin/shell-hook-http.d.ts +39 -0
  61. package/dist/plugin/shell-hook-http.d.ts.map +1 -0
  62. package/dist/plugin/shell-hook-http.js +163 -0
  63. package/dist/plugin/shell-hook-http.js.map +1 -0
  64. package/dist/plugin/shell-hook.d.ts +93 -11
  65. package/dist/plugin/shell-hook.d.ts.map +1 -1
  66. package/dist/plugin/shell-hook.js +223 -39
  67. package/dist/plugin/shell-hook.js.map +1 -1
  68. package/dist/prompt/coding-agent-doctrine.d.ts +1 -1
  69. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  70. package/dist/prompt/coding-agent-doctrine.js +2 -1
  71. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  72. package/dist/public-runtime.d.ts +3 -2
  73. package/dist/public-runtime.d.ts.map +1 -1
  74. package/dist/public-runtime.js +2 -2
  75. package/dist/public-runtime.js.map +1 -1
  76. package/dist/public-tools.d.ts +1 -0
  77. package/dist/public-tools.d.ts.map +1 -1
  78. package/dist/public-tools.js +2 -0
  79. package/dist/public-tools.js.map +1 -1
  80. package/dist/public-types.d.ts +1 -1
  81. package/dist/public-types.d.ts.map +1 -1
  82. package/dist/runtime/query/executor.d.ts +4 -0
  83. package/dist/runtime/query/executor.d.ts.map +1 -1
  84. package/dist/runtime/query/executor.js +47 -9
  85. package/dist/runtime/query/executor.js.map +1 -1
  86. package/dist/runtime/query/index.d.ts +8 -0
  87. package/dist/runtime/query/index.d.ts.map +1 -1
  88. package/dist/runtime/query/index.js +29 -1
  89. package/dist/runtime/query/index.js.map +1 -1
  90. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  91. package/dist/runtime/query/iteration/index.js +6 -2
  92. package/dist/runtime/query/iteration/index.js.map +1 -1
  93. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +18 -8
  94. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -1
  95. package/dist/runtime/query/iteration/phases/compaction-breaker.js +33 -9
  96. package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -1
  97. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  98. package/dist/runtime/query/iteration/phases/compaction.js +52 -5
  99. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  100. package/dist/runtime/query/iteration/phases/context.d.ts +5 -0
  101. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  102. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  103. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  104. package/dist/runtime/query/iteration/phases/tool-review.js +70 -3
  105. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  106. package/dist/runtime/query/path-escalation.d.ts +2 -0
  107. package/dist/runtime/query/path-escalation.d.ts.map +1 -1
  108. package/dist/runtime/query/path-escalation.js +7 -4
  109. package/dist/runtime/query/path-escalation.js.map +1 -1
  110. package/dist/runtime/query/plugin-hooks.js +2 -2
  111. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  112. package/dist/runtime/query/prepare-turn.d.ts +2 -0
  113. package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
  114. package/dist/runtime/query/prepare-turn.js +25 -0
  115. package/dist/runtime/query/prepare-turn.js.map +1 -1
  116. package/dist/runtime/query/review-policy.d.ts +3 -2
  117. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  118. package/dist/runtime/query/review-policy.js +32 -13
  119. package/dist/runtime/query/review-policy.js.map +1 -1
  120. package/dist/scheduler/local.js +1 -1
  121. package/dist/scheduler/local.js.map +1 -1
  122. package/dist/session/retention/archive.js +1 -1
  123. package/dist/session/retention/archive.js.map +1 -1
  124. package/dist/session/workspace/driver.d.ts +6 -0
  125. package/dist/session/workspace/driver.d.ts.map +1 -1
  126. package/dist/session/workspace/git-worktree.d.ts.map +1 -1
  127. package/dist/session/workspace/git-worktree.js +28 -0
  128. package/dist/session/workspace/git-worktree.js.map +1 -1
  129. package/dist/skills/index.d.ts +1 -1
  130. package/dist/skills/index.d.ts.map +1 -1
  131. package/dist/skills/index.js +1 -1
  132. package/dist/skills/index.js.map +1 -1
  133. package/dist/skills/loader.d.ts +7 -0
  134. package/dist/skills/loader.d.ts.map +1 -1
  135. package/dist/skills/loader.js +53 -0
  136. package/dist/skills/loader.js.map +1 -1
  137. package/dist/skills/registry.d.ts +1 -1
  138. package/dist/skills/registry.d.ts.map +1 -1
  139. package/dist/skills/registry.js +2 -1
  140. package/dist/skills/registry.js.map +1 -1
  141. package/dist/tools/builtins/bash-cwd.d.ts +52 -0
  142. package/dist/tools/builtins/bash-cwd.d.ts.map +1 -0
  143. package/dist/tools/builtins/bash-cwd.js +127 -0
  144. package/dist/tools/builtins/bash-cwd.js.map +1 -0
  145. package/dist/tools/builtins/bash.d.ts.map +1 -1
  146. package/dist/tools/builtins/bash.js +41 -6
  147. package/dist/tools/builtins/bash.js.map +1 -1
  148. package/dist/tools/builtins/bounded-file-read.d.ts +10 -0
  149. package/dist/tools/builtins/bounded-file-read.d.ts.map +1 -0
  150. package/dist/tools/builtins/bounded-file-read.js +67 -0
  151. package/dist/tools/builtins/bounded-file-read.js.map +1 -0
  152. package/dist/tools/builtins/grep.d.ts +6 -1
  153. package/dist/tools/builtins/grep.d.ts.map +1 -1
  154. package/dist/tools/builtins/grep.js +187 -24
  155. package/dist/tools/builtins/grep.js.map +1 -1
  156. package/dist/tools/builtins/read-file.d.ts +1 -0
  157. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  158. package/dist/tools/builtins/read-file.js +113 -28
  159. package/dist/tools/builtins/read-file.js.map +1 -1
  160. package/dist/tools/builtins/read-media.d.ts +68 -0
  161. package/dist/tools/builtins/read-media.d.ts.map +1 -0
  162. package/dist/tools/builtins/read-media.js +397 -0
  163. package/dist/tools/builtins/read-media.js.map +1 -0
  164. package/dist/tools/builtins/search-tools.js +1 -1
  165. package/dist/tools/builtins/search-tools.js.map +1 -1
  166. package/dist/tools/builtins/skill.d.ts +4 -2
  167. package/dist/tools/builtins/skill.d.ts.map +1 -1
  168. package/dist/tools/builtins/skill.js +61 -2
  169. package/dist/tools/builtins/skill.js.map +1 -1
  170. package/dist/tools/builtins/sleep-guard.d.ts +15 -0
  171. package/dist/tools/builtins/sleep-guard.d.ts.map +1 -0
  172. package/dist/tools/builtins/sleep-guard.js +33 -0
  173. package/dist/tools/builtins/sleep-guard.js.map +1 -0
  174. package/dist/tools/builtins/view-image.d.ts +23 -1
  175. package/dist/tools/builtins/view-image.d.ts.map +1 -1
  176. package/dist/tools/builtins/view-image.js +66 -58
  177. package/dist/tools/builtins/view-image.js.map +1 -1
  178. package/dist/tools/file-read-tracker.d.ts.map +1 -1
  179. package/dist/tools/file-read-tracker.js +31 -0
  180. package/dist/tools/file-read-tracker.js.map +1 -1
  181. package/dist/toolsets/manager.js +3 -3
  182. package/dist/toolsets/manager.js.map +1 -1
  183. package/dist/types/agent/base.d.ts +8 -0
  184. package/dist/types/agent/base.d.ts.map +1 -1
  185. package/dist/types/agent/query.d.ts +2 -0
  186. package/dist/types/agent/query.d.ts.map +1 -1
  187. package/dist/types/agent/scheduler.d.ts +8 -0
  188. package/dist/types/agent/scheduler.d.ts.map +1 -1
  189. package/dist/types/agent/task.d.ts +6 -2
  190. package/dist/types/agent/task.d.ts.map +1 -1
  191. package/dist/types/agent/task.js.map +1 -1
  192. package/dist/types/connector/mcp.d.ts +46 -0
  193. package/dist/types/connector/mcp.d.ts.map +1 -1
  194. package/dist/types/hitl/index.d.ts +1 -1
  195. package/dist/types/plugin/index.d.ts +74 -7
  196. package/dist/types/plugin/index.d.ts.map +1 -1
  197. package/dist/types/plugin/index.js +12 -1
  198. package/dist/types/plugin/index.js.map +1 -1
  199. package/dist/types/session/sub-session.d.ts +6 -2
  200. package/dist/types/session/sub-session.d.ts.map +1 -1
  201. package/dist/types/skills/index.d.ts +36 -0
  202. package/dist/types/skills/index.d.ts.map +1 -1
  203. package/dist/types/skills/index.js +9 -0
  204. package/dist/types/skills/index.js.map +1 -1
  205. package/dist/types/tool/index.d.ts +37 -0
  206. package/dist/types/tool/index.d.ts.map +1 -1
  207. package/dist/types/tool/index.js.map +1 -1
  208. package/dist/types/workspace/ref.d.ts +2 -0
  209. package/dist/types/workspace/ref.d.ts.map +1 -1
  210. package/dist/utils/frontmatter.d.ts +12 -0
  211. package/dist/utils/frontmatter.d.ts.map +1 -1
  212. package/dist/utils/frontmatter.js +6 -0
  213. package/dist/utils/frontmatter.js.map +1 -1
  214. package/package.json +1 -1
  215. package/src/agents/QueryAgent.ts +3 -0
  216. package/src/agents/file-definitions.ts +126 -6
  217. package/src/authorization/protected-paths.ts +30 -2
  218. package/src/compaction/index.ts +1 -0
  219. package/src/compaction/manual.ts +68 -30
  220. package/src/compaction/model-summary.ts +412 -0
  221. package/src/compaction/verifier.ts +3 -3
  222. package/src/config/runtime.ts +16 -0
  223. package/src/connector/mcp/client.ts +87 -1
  224. package/src/constants/mcp/index.ts +3 -0
  225. package/src/constants/plugin/index.ts +2 -0
  226. package/src/manager/agent/lifecycle.ts +24 -5
  227. package/src/manager/session/turn-recorder.ts +2 -1
  228. package/src/persona/assembler.ts +2 -2
  229. package/src/plugin/index.ts +5 -0
  230. package/src/plugin/lifecycle.ts +16 -2
  231. package/src/plugin/shell-hook-http.ts +181 -0
  232. package/src/plugin/shell-hook.ts +386 -89
  233. package/src/prompt/coding-agent-doctrine.ts +2 -1
  234. package/src/public-runtime.ts +6 -0
  235. package/src/public-tools.ts +2 -0
  236. package/src/public-types.ts +5 -0
  237. package/src/runtime/query/executor.ts +54 -10
  238. package/src/runtime/query/index.ts +44 -0
  239. package/src/runtime/query/iteration/index.ts +11 -3
  240. package/src/runtime/query/iteration/phases/compaction-breaker.ts +38 -9
  241. package/src/runtime/query/iteration/phases/compaction.ts +67 -5
  242. package/src/runtime/query/iteration/phases/context.ts +6 -0
  243. package/src/runtime/query/iteration/phases/tool-review.ts +74 -3
  244. package/src/runtime/query/path-escalation.ts +11 -5
  245. package/src/runtime/query/plugin-hooks.ts +2 -2
  246. package/src/runtime/query/prepare-turn.ts +33 -0
  247. package/src/runtime/query/review-policy.ts +45 -16
  248. package/src/scheduler/local.ts +1 -1
  249. package/src/session/retention/archive.ts +1 -1
  250. package/src/session/workspace/driver.ts +6 -0
  251. package/src/session/workspace/git-worktree.ts +31 -0
  252. package/src/skills/index.ts +6 -1
  253. package/src/skills/loader.ts +54 -0
  254. package/src/skills/registry.ts +7 -6
  255. package/src/tools/builtins/bash-cwd.ts +156 -0
  256. package/src/tools/builtins/bash.ts +79 -34
  257. package/src/tools/builtins/bounded-file-read.ts +80 -0
  258. package/src/tools/builtins/grep.ts +198 -28
  259. package/src/tools/builtins/read-file.ts +151 -30
  260. package/src/tools/builtins/read-media.ts +483 -0
  261. package/src/tools/builtins/search-tools.ts +1 -1
  262. package/src/tools/builtins/skill.ts +91 -2
  263. package/src/tools/builtins/sleep-guard.ts +31 -0
  264. package/src/tools/builtins/view-image.ts +74 -62
  265. package/src/tools/file-read-tracker.ts +30 -0
  266. package/src/toolsets/manager.ts +3 -3
  267. package/src/types/agent/base.ts +9 -0
  268. package/src/types/agent/query.ts +2 -0
  269. package/src/types/agent/scheduler.ts +9 -0
  270. package/src/types/agent/task.ts +6 -2
  271. package/src/types/connector/mcp.ts +48 -0
  272. package/src/types/hitl/index.ts +1 -1
  273. package/src/types/plugin/index.ts +102 -16
  274. package/src/types/session/sub-session.ts +6 -2
  275. package/src/types/skills/index.ts +48 -0
  276. package/src/types/tool/index.ts +38 -0
  277. package/src/types/workspace/ref.ts +2 -0
  278. package/src/utils/frontmatter.ts +21 -0
@@ -1,5 +1,5 @@
1
1
  import type { AgentPersona, OutputDiscipline } from '../types/persona/index.js'
2
- import { type Skill, isInvocableBy } from '../types/skills/index.js'
2
+ import { type Skill, isInvocableBy, skillModelDescription } from '../types/skills/index.js'
3
3
 
4
4
  export function assembleSystemPrompt(persona: AgentPersona, skills?: Skill[]): string {
5
5
  const sections: string[] = []
@@ -58,7 +58,7 @@ export function renderSkillsSection(skills?: Skill[]): string | null {
58
58
  const lines = [
59
59
  '<skill>',
60
60
  `<name>${s.metadata.name}</name>`,
61
- `<description>${s.metadata.description.trim()}</description>`,
61
+ `<description>${skillModelDescription(s.metadata)}</description>`,
62
62
  `<location>${s.dirPath}/SKILL.md</location>`,
63
63
  ]
64
64
  if (s.metadata.compatibility) {
@@ -17,8 +17,13 @@ export {
17
17
  shellHookVerdict,
18
18
  } from './shell-hook.js'
19
19
  export type {
20
+ HttpHookEntry,
21
+ PromptHookEntry,
20
22
  ShellHookEntry,
21
23
  ShellHookEvent,
24
+ ShellHookHandler,
25
+ ShellHookJudge,
26
+ ShellHookOnFailure,
22
27
  ShellHookInput,
23
28
  ShellHookOptions,
24
29
  ShellHookOutcome,
@@ -180,6 +180,8 @@ function immutableManifest(manifest: PluginDefinition['manifest']): PluginDefini
180
180
  ...(mcpServers ? { mcpServers: Object.freeze(mcpServers) } : {}),
181
181
  ...(manifest.connectors ? { connectors: Object.freeze([...manifest.connectors]) } : {}),
182
182
  ...(manifest.personas ? { personas: Object.freeze([...manifest.personas]) } : {}),
183
+ ...(manifest.commands ? { commands: Object.freeze([...manifest.commands]) } : {}),
184
+ ...(manifest.agents ? { agents: Object.freeze([...manifest.agents]) } : {}),
183
185
  })
184
186
  }
185
187
 
@@ -438,11 +440,23 @@ export class PluginLifecycleManager {
438
440
  if (index >= 0) this.listeners.splice(index, 1)
439
441
  }
440
442
 
441
- async install(pluginDir: string, scope: PluginScope): Promise<PluginDefinition> {
443
+ /**
444
+ * `options.authorityRoot` names the folder the plugin must stay inside when
445
+ * it is not the scope's own root: a plugin a person points at directly for
446
+ * one session is its own authority. It applies to this admission only.
447
+ */
448
+ async install(
449
+ pluginDir: string,
450
+ scope: PluginScope,
451
+ options: { readonly authorityRoot?: string } = {},
452
+ ): Promise<PluginDefinition> {
442
453
  // Admission and enable read the same constructor-owned capability. The
443
454
  // loader defaults this to false for direct callers; a manager that really
444
455
  // owns the registry is the authority that can opt the manifest in.
445
- const rootDir = await resolveWithinReal(this.scopeRoots[scope], pluginDir)
456
+ const rootDir = await resolveWithinReal(
457
+ options.authorityRoot ?? this.scopeRoots[scope],
458
+ pluginDir,
459
+ )
446
460
  const manifest = immutableManifest(
447
461
  await loadPluginManifest(rootDir, {
448
462
  skillsSupported: Boolean(this.skillRegistry),
@@ -0,0 +1,181 @@
1
+ /**
2
+ * An HTTP hook: the event as a JSON POST, the response as the hook's answer.
3
+ *
4
+ * A 2xx response is exit `0` with the body as stdout, so the JSON answer is
5
+ * read exactly as a command's is. Any other status, a network error and a
6
+ * deadline are the hook's own failure. The address is checked where the
7
+ * connection is made, not before it: a name that resolves to a private or
8
+ * link-local address (a cloud metadata service, a router) is refused, so a
9
+ * hook cannot be pointed at the machine's neighbours and a name cannot be
10
+ * re-resolved between the check and the connect. Loopback stays allowed for
11
+ * local development. Redirects are not followed.
12
+ */
13
+
14
+ import { type LookupAddress, lookup } from 'node:dns'
15
+ import { request as httpRequest } from 'node:http'
16
+ import { request as httpsRequest } from 'node:https'
17
+ import { isIP } from 'node:net'
18
+
19
+ /** Captured response bound. */
20
+ const MAX_BODY_BYTES = 64 * 1024
21
+
22
+ export interface HttpHookRequest {
23
+ readonly url: string
24
+ readonly body: string
25
+ readonly headers?: Readonly<Record<string, string>> | undefined
26
+ readonly allowedEnvVars?: readonly string[] | undefined
27
+ readonly timeoutMs: number
28
+ readonly env?: Readonly<Record<string, string | undefined>> | undefined
29
+ readonly signal?: AbortSignal | undefined
30
+ }
31
+
32
+ export interface HttpHookResponse {
33
+ readonly status: number | null
34
+ readonly body: string
35
+ readonly timedOut: boolean
36
+ readonly error?: string
37
+ }
38
+
39
+ /**
40
+ * Replace `$NAME` and `${NAME}` in a header value with the environment's
41
+ * value, but only for names the hook listed. Any other reference becomes the
42
+ * empty string, so a header can never carry a variable its author did not
43
+ * name. Line breaks and NUL are removed from the result, so a value cannot add
44
+ * a header of its own.
45
+ */
46
+ export function interpolateHookHeader(
47
+ value: string,
48
+ allowed: ReadonlySet<string>,
49
+ env: Readonly<Record<string, string | undefined>>,
50
+ ): string {
51
+ return value
52
+ .replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_all, braced, bare) => {
53
+ const name = (braced ?? bare) as string
54
+ return allowed.has(name) ? (env[name] ?? '') : ''
55
+ })
56
+ .replace(/[\r\n\0]/g, '')
57
+ }
58
+
59
+ /** Whether an address may be connected to: public, or loopback. */
60
+ export function isReachableHookAddress(address: string): boolean {
61
+ const family = isIP(address)
62
+ if (family === 4) {
63
+ const [a = 0, b = 0] = address.split('.').map(Number)
64
+ if (a === 127) return true
65
+ if (a === 0 || a === 10) return false
66
+ if (a === 169 && b === 254) return false
67
+ if (a === 172 && b >= 16 && b <= 31) return false
68
+ if (a === 192 && b === 168) return false
69
+ if (a === 100 && b >= 64 && b <= 127) return false
70
+ if (a >= 224) return false
71
+ return true
72
+ }
73
+ if (family === 6) {
74
+ const lower = address.toLowerCase()
75
+ if (lower === '::1') return true
76
+ if (lower === '::') return false
77
+ const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(lower)
78
+ if (mapped?.[1]) return isReachableHookAddress(mapped[1])
79
+ if (/^f[cd]/.test(lower) || /^fe[89ab]/.test(lower)) return false
80
+ return true
81
+ }
82
+ return false
83
+ }
84
+
85
+ /** `dns.lookup` that refuses an address a hook may not reach. */
86
+ function guardedLookup(
87
+ hostname: string,
88
+ options: { all?: boolean; family?: number | 'IPv4' | 'IPv6' },
89
+ callback: (error: Error | null, address?: string | LookupAddress[], family?: number) => void,
90
+ ): void {
91
+ lookup(hostname, { ...options, all: true }, (error: Error | null, addresses: LookupAddress[]) => {
92
+ if (error) {
93
+ callback(error)
94
+ return
95
+ }
96
+ const list = addresses
97
+ const refused = list.find((entry) => !isReachableHookAddress(entry.address))
98
+ if (refused || list.length === 0) {
99
+ callback(
100
+ new Error(
101
+ `${hostname} resolves to ${refused?.address ?? 'nothing'}, a private or link-local address`,
102
+ ),
103
+ )
104
+ return
105
+ }
106
+ if (options.all) {
107
+ callback(null, list)
108
+ return
109
+ }
110
+ const first = list[0] as LookupAddress
111
+ callback(null, first.address, first.family)
112
+ })
113
+ }
114
+
115
+ export function postHookJson(input: HttpHookRequest): Promise<HttpHookResponse> {
116
+ return new Promise((resolve) => {
117
+ let url: URL
118
+ try {
119
+ url = new URL(input.url)
120
+ } catch {
121
+ resolve({ status: null, body: '', timedOut: false, error: 'the url is not valid' })
122
+ return
123
+ }
124
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
125
+ resolve({ status: null, body: '', timedOut: false, error: 'the url must be http or https' })
126
+ return
127
+ }
128
+ // A literal address never reaches the resolver, so it is checked here.
129
+ const literal = url.hostname.replace(/^\[|\]$/g, '')
130
+ if (isIP(literal) !== 0 && !isReachableHookAddress(literal)) {
131
+ resolve({
132
+ status: null,
133
+ body: '',
134
+ timedOut: false,
135
+ error: `${literal} is a private or link-local address`,
136
+ })
137
+ return
138
+ }
139
+ const allowed = new Set(input.allowedEnvVars ?? [])
140
+ const env = input.env ?? process.env
141
+ const headers: Record<string, string> = {
142
+ 'Content-Type': 'application/json',
143
+ 'Content-Length': String(Buffer.byteLength(input.body)),
144
+ }
145
+ for (const [name, value] of Object.entries(input.headers ?? {})) {
146
+ headers[name] = interpolateHookHeader(value, allowed, env)
147
+ }
148
+ let settled = false
149
+ let timedOut = false
150
+ const finish = (response: HttpHookResponse) => {
151
+ if (settled) return
152
+ settled = true
153
+ clearTimeout(timer)
154
+ input.signal?.removeEventListener('abort', onAbort)
155
+ resolve(response)
156
+ }
157
+ const onAbort = () => {
158
+ req.destroy()
159
+ finish({ status: null, body: '', timedOut: false, error: 'cancelled' })
160
+ }
161
+ const send = url.protocol === 'https:' ? httpsRequest : httpRequest
162
+ const req = send(url, { method: 'POST', headers, lookup: guardedLookup as never }, (res) => {
163
+ let body = ''
164
+ res.setEncoding('utf8')
165
+ res.on('data', (chunk: string) => {
166
+ if (body.length < MAX_BODY_BYTES) body = (body + chunk).slice(0, MAX_BODY_BYTES)
167
+ })
168
+ res.on('end', () => finish({ status: res.statusCode ?? null, body, timedOut: false }))
169
+ res.on('error', (error) => finish({ status: null, body, timedOut, error: error.message }))
170
+ })
171
+ const timer = setTimeout(() => {
172
+ timedOut = true
173
+ req.destroy()
174
+ finish({ status: null, body: '', timedOut: true })
175
+ }, input.timeoutMs)
176
+ req.on('error', (error) => finish({ status: null, body: '', timedOut, error: error.message }))
177
+ if (input.signal?.aborted) onAbort()
178
+ else input.signal?.addEventListener('abort', onAbort, { once: true })
179
+ req.end(input.body)
180
+ })
181
+ }