@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.
- package/CHANGELOG.md +62 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +3 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/file-definitions.d.ts +35 -4
- package/dist/agents/file-definitions.d.ts.map +1 -1
- package/dist/agents/file-definitions.js +98 -8
- package/dist/agents/file-definitions.js.map +1 -1
- package/dist/authorization/protected-paths.d.ts +9 -0
- package/dist/authorization/protected-paths.d.ts.map +1 -1
- package/dist/authorization/protected-paths.js +29 -3
- package/dist/authorization/protected-paths.js.map +1 -1
- package/dist/compaction/index.d.ts +1 -0
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/manual.d.ts +7 -0
- package/dist/compaction/manual.d.ts.map +1 -1
- package/dist/compaction/manual.js +31 -12
- package/dist/compaction/manual.js.map +1 -1
- package/dist/compaction/model-summary.d.ts +101 -0
- package/dist/compaction/model-summary.d.ts.map +1 -0
- package/dist/compaction/model-summary.js +333 -0
- package/dist/compaction/model-summary.js.map +1 -0
- package/dist/compaction/verifier.d.ts +6 -1
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +3 -3
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/config/runtime.d.ts +62 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +16 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/connector/mcp/client.d.ts +8 -0
- package/dist/connector/mcp/client.d.ts.map +1 -1
- package/dist/connector/mcp/client.js +75 -2
- package/dist/connector/mcp/client.js.map +1 -1
- package/dist/constants/mcp/index.d.ts +2 -0
- package/dist/constants/mcp/index.d.ts.map +1 -1
- package/dist/constants/mcp/index.js +2 -0
- package/dist/constants/mcp/index.js.map +1 -1
- package/dist/constants/plugin/index.d.ts +2 -0
- package/dist/constants/plugin/index.d.ts.map +1 -1
- package/dist/constants/plugin/index.js +2 -0
- package/dist/constants/plugin/index.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +25 -4
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +2 -1
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/persona/assembler.d.ts.map +1 -1
- package/dist/persona/assembler.js +2 -2
- package/dist/persona/assembler.js.map +1 -1
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/lifecycle.d.ts +8 -1
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +9 -2
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/plugin/shell-hook-http.d.ts +39 -0
- package/dist/plugin/shell-hook-http.d.ts.map +1 -0
- package/dist/plugin/shell-hook-http.js +163 -0
- package/dist/plugin/shell-hook-http.js.map +1 -0
- package/dist/plugin/shell-hook.d.ts +93 -11
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +223 -39
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +2 -1
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/public-runtime.d.ts +3 -2
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +2 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +1 -0
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +2 -0
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +1 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/query/executor.d.ts +4 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +47 -9
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +8 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +29 -1
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +6 -2
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +18 -8
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.js +33 -9
- package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +52 -5
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +5 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +70 -3
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/path-escalation.d.ts +2 -0
- package/dist/runtime/query/path-escalation.d.ts.map +1 -1
- package/dist/runtime/query/path-escalation.js +7 -4
- package/dist/runtime/query/path-escalation.js.map +1 -1
- package/dist/runtime/query/plugin-hooks.js +2 -2
- package/dist/runtime/query/plugin-hooks.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts +2 -0
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +25 -0
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +3 -2
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +32 -13
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/scheduler/local.js +1 -1
- package/dist/scheduler/local.js.map +1 -1
- package/dist/session/retention/archive.js +1 -1
- package/dist/session/retention/archive.js.map +1 -1
- package/dist/session/workspace/driver.d.ts +6 -0
- package/dist/session/workspace/driver.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.d.ts.map +1 -1
- package/dist/session/workspace/git-worktree.js +28 -0
- package/dist/session/workspace/git-worktree.js.map +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/loader.d.ts +7 -0
- package/dist/skills/loader.d.ts.map +1 -1
- package/dist/skills/loader.js +53 -0
- package/dist/skills/loader.js.map +1 -1
- package/dist/skills/registry.d.ts +1 -1
- package/dist/skills/registry.d.ts.map +1 -1
- package/dist/skills/registry.js +2 -1
- package/dist/skills/registry.js.map +1 -1
- package/dist/tools/builtins/bash-cwd.d.ts +52 -0
- package/dist/tools/builtins/bash-cwd.d.ts.map +1 -0
- package/dist/tools/builtins/bash-cwd.js +127 -0
- package/dist/tools/builtins/bash-cwd.js.map +1 -0
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +41 -6
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/bounded-file-read.d.ts +10 -0
- package/dist/tools/builtins/bounded-file-read.d.ts.map +1 -0
- package/dist/tools/builtins/bounded-file-read.js +67 -0
- package/dist/tools/builtins/bounded-file-read.js.map +1 -0
- package/dist/tools/builtins/grep.d.ts +6 -1
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +187 -24
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts +1 -0
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +113 -28
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/read-media.d.ts +68 -0
- package/dist/tools/builtins/read-media.d.ts.map +1 -0
- package/dist/tools/builtins/read-media.js +397 -0
- package/dist/tools/builtins/read-media.js.map +1 -0
- package/dist/tools/builtins/search-tools.js +1 -1
- package/dist/tools/builtins/search-tools.js.map +1 -1
- package/dist/tools/builtins/skill.d.ts +4 -2
- package/dist/tools/builtins/skill.d.ts.map +1 -1
- package/dist/tools/builtins/skill.js +61 -2
- package/dist/tools/builtins/skill.js.map +1 -1
- package/dist/tools/builtins/sleep-guard.d.ts +15 -0
- package/dist/tools/builtins/sleep-guard.d.ts.map +1 -0
- package/dist/tools/builtins/sleep-guard.js +33 -0
- package/dist/tools/builtins/sleep-guard.js.map +1 -0
- package/dist/tools/builtins/view-image.d.ts +23 -1
- package/dist/tools/builtins/view-image.d.ts.map +1 -1
- package/dist/tools/builtins/view-image.js +66 -58
- package/dist/tools/builtins/view-image.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +31 -0
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/toolsets/manager.js +3 -3
- package/dist/toolsets/manager.js.map +1 -1
- package/dist/types/agent/base.d.ts +8 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/query.d.ts +2 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/agent/scheduler.d.ts +8 -0
- package/dist/types/agent/scheduler.d.ts.map +1 -1
- package/dist/types/agent/task.d.ts +6 -2
- package/dist/types/agent/task.d.ts.map +1 -1
- package/dist/types/agent/task.js.map +1 -1
- package/dist/types/connector/mcp.d.ts +46 -0
- package/dist/types/connector/mcp.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +1 -1
- package/dist/types/plugin/index.d.ts +74 -7
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +12 -1
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/session/sub-session.d.ts +6 -2
- package/dist/types/session/sub-session.d.ts.map +1 -1
- package/dist/types/skills/index.d.ts +36 -0
- package/dist/types/skills/index.d.ts.map +1 -1
- package/dist/types/skills/index.js +9 -0
- package/dist/types/skills/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +37 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/workspace/ref.d.ts +2 -0
- package/dist/types/workspace/ref.d.ts.map +1 -1
- package/dist/utils/frontmatter.d.ts +12 -0
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +6 -0
- package/dist/utils/frontmatter.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +3 -0
- package/src/agents/file-definitions.ts +126 -6
- package/src/authorization/protected-paths.ts +30 -2
- package/src/compaction/index.ts +1 -0
- package/src/compaction/manual.ts +68 -30
- package/src/compaction/model-summary.ts +412 -0
- package/src/compaction/verifier.ts +3 -3
- package/src/config/runtime.ts +16 -0
- package/src/connector/mcp/client.ts +87 -1
- package/src/constants/mcp/index.ts +3 -0
- package/src/constants/plugin/index.ts +2 -0
- package/src/manager/agent/lifecycle.ts +24 -5
- package/src/manager/session/turn-recorder.ts +2 -1
- package/src/persona/assembler.ts +2 -2
- package/src/plugin/index.ts +5 -0
- package/src/plugin/lifecycle.ts +16 -2
- package/src/plugin/shell-hook-http.ts +181 -0
- package/src/plugin/shell-hook.ts +386 -89
- package/src/prompt/coding-agent-doctrine.ts +2 -1
- package/src/public-runtime.ts +6 -0
- package/src/public-tools.ts +2 -0
- package/src/public-types.ts +5 -0
- package/src/runtime/query/executor.ts +54 -10
- package/src/runtime/query/index.ts +44 -0
- package/src/runtime/query/iteration/index.ts +11 -3
- package/src/runtime/query/iteration/phases/compaction-breaker.ts +38 -9
- package/src/runtime/query/iteration/phases/compaction.ts +67 -5
- package/src/runtime/query/iteration/phases/context.ts +6 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +74 -3
- package/src/runtime/query/path-escalation.ts +11 -5
- package/src/runtime/query/plugin-hooks.ts +2 -2
- package/src/runtime/query/prepare-turn.ts +33 -0
- package/src/runtime/query/review-policy.ts +45 -16
- package/src/scheduler/local.ts +1 -1
- package/src/session/retention/archive.ts +1 -1
- package/src/session/workspace/driver.ts +6 -0
- package/src/session/workspace/git-worktree.ts +31 -0
- package/src/skills/index.ts +6 -1
- package/src/skills/loader.ts +54 -0
- package/src/skills/registry.ts +7 -6
- package/src/tools/builtins/bash-cwd.ts +156 -0
- package/src/tools/builtins/bash.ts +79 -34
- package/src/tools/builtins/bounded-file-read.ts +80 -0
- package/src/tools/builtins/grep.ts +198 -28
- package/src/tools/builtins/read-file.ts +151 -30
- package/src/tools/builtins/read-media.ts +483 -0
- package/src/tools/builtins/search-tools.ts +1 -1
- package/src/tools/builtins/skill.ts +91 -2
- package/src/tools/builtins/sleep-guard.ts +31 -0
- package/src/tools/builtins/view-image.ts +74 -62
- package/src/tools/file-read-tracker.ts +30 -0
- package/src/toolsets/manager.ts +3 -3
- package/src/types/agent/base.ts +9 -0
- package/src/types/agent/query.ts +2 -0
- package/src/types/agent/scheduler.ts +9 -0
- package/src/types/agent/task.ts +6 -2
- package/src/types/connector/mcp.ts +48 -0
- package/src/types/hitl/index.ts +1 -1
- package/src/types/plugin/index.ts +102 -16
- package/src/types/session/sub-session.ts +6 -2
- package/src/types/skills/index.ts +48 -0
- package/src/types/tool/index.ts +38 -0
- package/src/types/workspace/ref.ts +2 -0
- package/src/utils/frontmatter.ts +21 -0
package/src/persona/assembler.ts
CHANGED
|
@@ -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
|
|
61
|
+
`<description>${skillModelDescription(s.metadata)}</description>`,
|
|
62
62
|
`<location>${s.dirPath}/SKILL.md</location>`,
|
|
63
63
|
]
|
|
64
64
|
if (s.metadata.compatibility) {
|
package/src/plugin/index.ts
CHANGED
|
@@ -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,
|
package/src/plugin/lifecycle.ts
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
+
}
|