@namzu/sdk 52.3.0 → 52.5.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 (176) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/authorization/gate.d.ts +11 -0
  3. package/dist/authorization/gate.d.ts.map +1 -1
  4. package/dist/authorization/gate.js +35 -0
  5. package/dist/authorization/gate.js.map +1 -1
  6. package/dist/authorization/removal.js +11 -6
  7. package/dist/authorization/removal.js.map +1 -1
  8. package/dist/authorization/rules.d.ts.map +1 -1
  9. package/dist/authorization/rules.js +30 -2
  10. package/dist/authorization/rules.js.map +1 -1
  11. package/dist/authorization/shell-paths.d.ts +20 -0
  12. package/dist/authorization/shell-paths.d.ts.map +1 -1
  13. package/dist/authorization/shell-paths.js +254 -52
  14. package/dist/authorization/shell-paths.js.map +1 -1
  15. package/dist/bridge/acp/server.d.ts +8 -0
  16. package/dist/bridge/acp/server.d.ts.map +1 -1
  17. package/dist/bridge/acp/server.js +7 -0
  18. package/dist/bridge/acp/server.js.map +1 -1
  19. package/dist/connector/web/guarded-fetch.d.ts.map +1 -1
  20. package/dist/connector/web/guarded-fetch.js +44 -0
  21. package/dist/connector/web/guarded-fetch.js.map +1 -1
  22. package/dist/manager/resident/outbox.d.ts +4 -4
  23. package/dist/process/kill-tree.d.ts +3 -2
  24. package/dist/process/kill-tree.d.ts.map +1 -1
  25. package/dist/process/kill-tree.js.map +1 -1
  26. package/dist/public-runtime.d.ts +3 -2
  27. package/dist/public-runtime.d.ts.map +1 -1
  28. package/dist/public-runtime.js +4 -1
  29. package/dist/public-runtime.js.map +1 -1
  30. package/dist/public-tools.d.ts +2 -1
  31. package/dist/public-tools.d.ts.map +1 -1
  32. package/dist/public-tools.js +1 -1
  33. package/dist/public-tools.js.map +1 -1
  34. package/dist/public-types.d.ts +1 -0
  35. package/dist/public-types.d.ts.map +1 -1
  36. package/dist/runtime/jobs/registry.d.ts +80 -2
  37. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  38. package/dist/runtime/jobs/registry.js +70 -5
  39. package/dist/runtime/jobs/registry.js.map +1 -1
  40. package/dist/runtime/jobs/websocket-source.d.ts +120 -0
  41. package/dist/runtime/jobs/websocket-source.d.ts.map +1 -0
  42. package/dist/runtime/jobs/websocket-source.js +379 -0
  43. package/dist/runtime/jobs/websocket-source.js.map +1 -0
  44. package/dist/runtime/query/auto-review/classifier.d.ts +130 -0
  45. package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -0
  46. package/dist/runtime/query/auto-review/classifier.js +244 -0
  47. package/dist/runtime/query/auto-review/classifier.js.map +1 -0
  48. package/dist/runtime/query/auto-review/index.d.ts +7 -0
  49. package/dist/runtime/query/auto-review/index.d.ts.map +1 -0
  50. package/dist/runtime/query/auto-review/index.js +4 -0
  51. package/dist/runtime/query/auto-review/index.js.map +1 -0
  52. package/dist/runtime/query/auto-review/prompt.d.ts +20 -0
  53. package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -0
  54. package/dist/runtime/query/auto-review/prompt.js +89 -0
  55. package/dist/runtime/query/auto-review/prompt.js.map +1 -0
  56. package/dist/runtime/query/auto-review/rules.d.ts +49 -0
  57. package/dist/runtime/query/auto-review/rules.d.ts.map +1 -0
  58. package/dist/runtime/query/auto-review/rules.js +81 -0
  59. package/dist/runtime/query/auto-review/rules.js.map +1 -0
  60. package/dist/runtime/query/auto-review/transcript.d.ts +59 -0
  61. package/dist/runtime/query/auto-review/transcript.d.ts.map +1 -0
  62. package/dist/runtime/query/auto-review/transcript.js +116 -0
  63. package/dist/runtime/query/auto-review/transcript.js.map +1 -0
  64. package/dist/runtime/query/auto-review/verdict.d.ts +21 -0
  65. package/dist/runtime/query/auto-review/verdict.d.ts.map +1 -0
  66. package/dist/runtime/query/auto-review/verdict.js +45 -0
  67. package/dist/runtime/query/auto-review/verdict.js.map +1 -0
  68. package/dist/runtime/query/executor.d.ts +7 -0
  69. package/dist/runtime/query/executor.d.ts.map +1 -1
  70. package/dist/runtime/query/executor.js +26 -2
  71. package/dist/runtime/query/executor.js.map +1 -1
  72. package/dist/runtime/query/index.js +2 -2
  73. package/dist/runtime/query/index.js.map +1 -1
  74. package/dist/runtime/query/iteration/phases/context.d.ts +0 -9
  75. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  76. package/dist/runtime/query/iteration/phases/context.js +11 -2
  77. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  78. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  79. package/dist/runtime/query/iteration/phases/tool-review.js +39 -0
  80. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  81. package/dist/runtime/query/path-escalation.d.ts.map +1 -1
  82. package/dist/runtime/query/path-escalation.js +43 -10
  83. package/dist/runtime/query/path-escalation.js.map +1 -1
  84. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  85. package/dist/runtime/query/resume-pending.js +14 -0
  86. package/dist/runtime/query/resume-pending.js.map +1 -1
  87. package/dist/runtime/query/review-policy.d.ts +88 -1
  88. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  89. package/dist/runtime/query/review-policy.js +504 -11
  90. package/dist/runtime/query/review-policy.js.map +1 -1
  91. package/dist/runtime/query/steering.d.ts +2 -2
  92. package/dist/runtime/query/steering.d.ts.map +1 -1
  93. package/dist/runtime/query/steering.js +16 -3
  94. package/dist/runtime/query/steering.js.map +1 -1
  95. package/dist/tools/builtins/bash.d.ts.map +1 -1
  96. package/dist/tools/builtins/bash.js +2 -0
  97. package/dist/tools/builtins/bash.js.map +1 -1
  98. package/dist/tools/builtins/classifier-input.d.ts +9 -0
  99. package/dist/tools/builtins/classifier-input.d.ts.map +1 -0
  100. package/dist/tools/builtins/classifier-input.js +20 -0
  101. package/dist/tools/builtins/classifier-input.js.map +1 -0
  102. package/dist/tools/builtins/code-analysis.d.ts +2 -0
  103. package/dist/tools/builtins/code-analysis.d.ts.map +1 -1
  104. package/dist/tools/builtins/code-analysis.js +196 -119
  105. package/dist/tools/builtins/code-analysis.js.map +1 -1
  106. package/dist/tools/builtins/computer-use.d.ts +10 -10
  107. package/dist/tools/builtins/edit.d.ts.map +1 -1
  108. package/dist/tools/builtins/edit.js +12 -0
  109. package/dist/tools/builtins/edit.js.map +1 -1
  110. package/dist/tools/builtins/monitor.d.ts +38 -4
  111. package/dist/tools/builtins/monitor.d.ts.map +1 -1
  112. package/dist/tools/builtins/monitor.js +239 -97
  113. package/dist/tools/builtins/monitor.js.map +1 -1
  114. package/dist/tools/builtins/web.d.ts.map +1 -1
  115. package/dist/tools/builtins/web.js +3 -0
  116. package/dist/tools/builtins/web.js.map +1 -1
  117. package/dist/tools/builtins/write-file.d.ts.map +1 -1
  118. package/dist/tools/builtins/write-file.js +6 -0
  119. package/dist/tools/builtins/write-file.js.map +1 -1
  120. package/dist/tools/defineTool.d.ts +2 -0
  121. package/dist/tools/defineTool.d.ts.map +1 -1
  122. package/dist/tools/defineTool.js +1 -0
  123. package/dist/tools/defineTool.js.map +1 -1
  124. package/dist/types/acp/index.d.ts +7 -0
  125. package/dist/types/acp/index.d.ts.map +1 -1
  126. package/dist/types/code-analysis/index.d.ts +54 -3
  127. package/dist/types/code-analysis/index.d.ts.map +1 -1
  128. package/dist/types/hitl/index.d.ts +45 -1
  129. package/dist/types/hitl/index.d.ts.map +1 -1
  130. package/dist/types/hitl/index.js.map +1 -1
  131. package/dist/types/sandbox/index.d.ts +10 -0
  132. package/dist/types/sandbox/index.d.ts.map +1 -1
  133. package/dist/types/sandbox/index.js.map +1 -1
  134. package/dist/types/tool/index.d.ts +51 -0
  135. package/dist/types/tool/index.d.ts.map +1 -1
  136. package/dist/types/tool/index.js.map +1 -1
  137. package/package.json +4 -2
  138. package/src/authorization/gate.ts +35 -0
  139. package/src/authorization/removal.ts +12 -6
  140. package/src/authorization/rules.ts +27 -2
  141. package/src/authorization/shell-paths.ts +264 -51
  142. package/src/bridge/acp/server.ts +20 -0
  143. package/src/connector/web/guarded-fetch.ts +41 -0
  144. package/src/process/kill-tree.ts +4 -2
  145. package/src/public-runtime.ts +19 -1
  146. package/src/public-tools.ts +3 -0
  147. package/src/public-types.ts +11 -0
  148. package/src/runtime/jobs/registry.ts +162 -13
  149. package/src/runtime/jobs/websocket-source.ts +438 -0
  150. package/src/runtime/query/auto-review/classifier.ts +404 -0
  151. package/src/runtime/query/auto-review/index.ts +23 -0
  152. package/src/runtime/query/auto-review/prompt.ts +97 -0
  153. package/src/runtime/query/auto-review/rules.ts +106 -0
  154. package/src/runtime/query/auto-review/transcript.ts +159 -0
  155. package/src/runtime/query/auto-review/verdict.ts +50 -0
  156. package/src/runtime/query/executor.ts +29 -2
  157. package/src/runtime/query/index.ts +2 -2
  158. package/src/runtime/query/iteration/phases/context.ts +17 -3
  159. package/src/runtime/query/iteration/phases/tool-review.ts +41 -0
  160. package/src/runtime/query/path-escalation.ts +44 -9
  161. package/src/runtime/query/resume-pending.ts +14 -0
  162. package/src/runtime/query/review-policy.ts +591 -14
  163. package/src/runtime/query/steering.ts +32 -6
  164. package/src/tools/builtins/bash.ts +3 -0
  165. package/src/tools/builtins/classifier-input.ts +19 -0
  166. package/src/tools/builtins/code-analysis.ts +211 -119
  167. package/src/tools/builtins/edit.ts +13 -0
  168. package/src/tools/builtins/monitor.ts +297 -123
  169. package/src/tools/builtins/web.ts +3 -0
  170. package/src/tools/builtins/write-file.ts +6 -0
  171. package/src/tools/defineTool.ts +3 -0
  172. package/src/types/acp/index.ts +7 -0
  173. package/src/types/code-analysis/index.ts +51 -3
  174. package/src/types/hitl/index.ts +49 -1
  175. package/src/types/sandbox/index.ts +10 -0
  176. package/src/types/tool/index.ts +44 -0
@@ -0,0 +1,120 @@
1
+ import type { Sandbox } from '../../types/sandbox/index.js';
2
+ import type { MonitorWebSocketTarget } from '../../types/tool/index.js';
3
+ import type { JobHost, JobSource } from './registry.js';
4
+ /**
5
+ * A WebSocket as a monitor's source.
6
+ *
7
+ * A command's stdout is not the only thing worth watching: some services push
8
+ * their events (a CI run's log stream, a server's change feed) and offer no
9
+ * command that prints them. This module turns such a connection into the same
10
+ * thing a command is to the registry: a process-shaped object that writes
11
+ * lines on stdout, can be asked to stop, and ends. Everything after that (the
12
+ * 200 ms batches, the rationing, the expiry, the stop, the untrusted frame the
13
+ * model reads it in) is the registry's and is shared with commands unchanged.
14
+ *
15
+ * What is different is where it runs and what it can reach. A command runs
16
+ * where the registry's spawn puts it, inside a sandbox if there is one; this
17
+ * connection is opened by the HOST process, outside any sandbox. So the
18
+ * address is checked before anything is sent, a sandbox that isolates the
19
+ * network refuses it unless it has said the host is allowed, and the tool
20
+ * asks a person first (see `tools/builtins/monitor.ts`).
21
+ */
22
+ /** The largest frame accepted; a bigger one ends the watch. Same as the reference. */
23
+ export declare const WEBSOCKET_MAX_PAYLOAD_BYTES = 1048576;
24
+ export declare const WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 15000;
25
+ /** After a polite close, how long a peer gets to answer before the socket is destroyed. */
26
+ export declare const WEBSOCKET_CLOSE_GRACE_MS = 1000;
27
+ export declare const WEBSOCKET_URL_MAX_CHARS = 2048;
28
+ export declare const WEBSOCKET_PROTOCOLS_MAX = 16;
29
+ /** Why a URL cannot be a monitor's source, or undefined when it can. */
30
+ export declare function webSocketUrlProblem(raw: string): string | undefined;
31
+ /** Why a subprotocol list is not valid, or undefined when it is. */
32
+ export declare function webSocketProtocolsProblem(protocols: readonly string[]): string | undefined;
33
+ /** What a record and an approval call this source: the URL as given, which can carry no credentials. */
34
+ export declare function webSocketJobCommand(url: string): string;
35
+ /** The address check refused, in words for the model. */
36
+ export declare class WebSocketRefusedError extends Error {
37
+ constructor(message: string);
38
+ }
39
+ export interface WebSocketVetOptions {
40
+ /**
41
+ * How a name becomes addresses. Defaults to `node:dns`. For tests only, and
42
+ * never reachable from a tool argument: the hook belongs to whoever builds
43
+ * the tool (`createMonitorTool`).
44
+ */
45
+ readonly resolve?: (hostname: string) => Promise<readonly string[]>;
46
+ /**
47
+ * Allow addresses inside the host's own network. For tests only: a local
48
+ * server on 127.0.0.1 is the one honest reason to, and it is a decision the
49
+ * code that builds the tool makes, never the model.
50
+ */
51
+ readonly allowPrivateAddresses?: boolean;
52
+ }
53
+ /** The host name of a WebSocket URL as a resolver or an address check wants it. */
54
+ export declare function webSocketHostname(raw: string): string;
55
+ /**
56
+ * Resolve the host and refuse unless EVERY address is outside the host's own
57
+ * network; return the addresses that were checked.
58
+ *
59
+ * Refusal covers a name that does not resolve, one that resolves to nothing,
60
+ * and one with any private address among its answers: a name that answers both
61
+ * a public and a private address is a name an attacker controls, and a
62
+ * connection may pick either. Failing to resolve is a refusal and never a
63
+ * pass: an empty list satisfies "no private address found" and the connection
64
+ * that followed would resolve the name again against whatever answered.
65
+ */
66
+ export declare function vetWebSocketTarget(raw: string, options?: WebSocketVetOptions): Promise<readonly {
67
+ readonly address: string;
68
+ readonly family: 4 | 6;
69
+ }[]>;
70
+ /**
71
+ * A `lookup` for the socket that answers with the addresses that were checked
72
+ * and nothing else.
73
+ *
74
+ * This is what closes the gap `web_fetch` documents: there the address is
75
+ * checked, then the platform `fetch` resolves the name AGAIN, so a name that
76
+ * answers publicly the first time and privately the second (DNS rebinding)
77
+ * gets through. Here the connection never asks DNS: it is handed the checked
78
+ * addresses, so there is no second answer to differ. The URL still names the
79
+ * host, so TLS keeps its SNI and its certificate check on the hostname and not
80
+ * on an address.
81
+ */
82
+ export declare function pinnedLookup(addresses: readonly {
83
+ readonly address: string;
84
+ readonly family: 4 | 6;
85
+ }[]): (_hostname: string, options: {
86
+ all?: boolean;
87
+ family?: number | string;
88
+ } | undefined, callback: (error: NodeJS.ErrnoException | null, address: string | {
89
+ address: string;
90
+ family: number;
91
+ }[], family?: number) => void) => void;
92
+ /** `api.example.com` is that host; `.example.com` is the domain and its subdomains. */
93
+ export declare function hostMatchesPolicy(hostname: string, allowedHosts: readonly string[]): boolean;
94
+ /**
95
+ * Why a sandbox forbids this connection, or undefined when it does not.
96
+ *
97
+ * The connection is opened by the host process, outside the boundary, so a
98
+ * sandbox's network rules do not bind it. If a turn runs under a sandbox that
99
+ * isolates the network, letting the host connect would be a way out of that
100
+ * isolation, so it is refused unless the sandbox's live policy names the host.
101
+ * A sandbox that does not isolate the network says nothing about it, and the
102
+ * connection is allowed, subject to the address check and to review.
103
+ */
104
+ export declare function webSocketSandboxRefusal(sandbox: Sandbox | undefined, hostname: string): string | undefined;
105
+ export declare const webSocketRejectedNotice: (status: number) => string;
106
+ export declare const webSocketErrorNotice: (message: string) => string;
107
+ export declare const webSocketClosedNotice: (code: number, reason: string) => string;
108
+ export declare const webSocketDroppedNotice: () => string;
109
+ export interface WebSocketSourceOptions {
110
+ readonly target: MonitorWebSocketTarget;
111
+ readonly host: JobHost;
112
+ }
113
+ /**
114
+ * Open the connection and return it as a job's process.
115
+ *
116
+ * The socket library is loaded when the first monitor of this kind starts, so
117
+ * a session that never watches a WebSocket never pays for it.
118
+ */
119
+ export declare function connectWebSocketSource(options: WebSocketSourceOptions): JobSource;
120
+ //# sourceMappingURL=websocket-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-source.d.ts","sourceRoot":"","sources":["../../../src/runtime/jobs/websocket-source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,379 @@
1
+ import { lookup as dnsLookup } from 'node:dns/promises';
2
+ import { EventEmitter } from 'node:events';
3
+ import { isIP } from 'node:net';
4
+ import { isPrivateAddress } from '../../connector/web/guarded-fetch.js';
5
+ import { isolationOf } from '../../sandbox/isolation.js';
6
+ /**
7
+ * A WebSocket as a monitor's source.
8
+ *
9
+ * A command's stdout is not the only thing worth watching: some services push
10
+ * their events (a CI run's log stream, a server's change feed) and offer no
11
+ * command that prints them. This module turns such a connection into the same
12
+ * thing a command is to the registry: a process-shaped object that writes
13
+ * lines on stdout, can be asked to stop, and ends. Everything after that (the
14
+ * 200 ms batches, the rationing, the expiry, the stop, the untrusted frame the
15
+ * model reads it in) is the registry's and is shared with commands unchanged.
16
+ *
17
+ * What is different is where it runs and what it can reach. A command runs
18
+ * where the registry's spawn puts it, inside a sandbox if there is one; this
19
+ * connection is opened by the HOST process, outside any sandbox. So the
20
+ * address is checked before anything is sent, a sandbox that isolates the
21
+ * network refuses it unless it has said the host is allowed, and the tool
22
+ * asks a person first (see `tools/builtins/monitor.ts`).
23
+ */
24
+ /** The largest frame accepted; a bigger one ends the watch. Same as the reference. */
25
+ export const WEBSOCKET_MAX_PAYLOAD_BYTES = 1_048_576;
26
+ export const WEBSOCKET_HANDSHAKE_TIMEOUT_MS = 15_000;
27
+ /** After a polite close, how long a peer gets to answer before the socket is destroyed. */
28
+ export const WEBSOCKET_CLOSE_GRACE_MS = 1000;
29
+ export const WEBSOCKET_URL_MAX_CHARS = 2048;
30
+ export const WEBSOCKET_PROTOCOLS_MAX = 16;
31
+ /** Longest close reason relayed; the protocol allows 123 bytes. */
32
+ const CLOSE_REASON_MAX_CHARS = 123;
33
+ // ---------------------------------------------------------------------------
34
+ // Input
35
+ // ---------------------------------------------------------------------------
36
+ /** RFC 6455 subprotocol names are RFC 2616 tokens: no separators, no controls. */
37
+ const TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
38
+ /** Why a URL cannot be a monitor's source, or undefined when it can. */
39
+ export function webSocketUrlProblem(raw) {
40
+ if (raw.length > WEBSOCKET_URL_MAX_CHARS) {
41
+ return `The URL is longer than ${WEBSOCKET_URL_MAX_CHARS} characters.`;
42
+ }
43
+ // ASCII without whitespace or controls: a model that wants a non-ASCII host
44
+ // writes it in punycode, and nothing here is left to the parser's leniency
45
+ // (it strips tabs and newlines silently).
46
+ if (!/^[\x21-\x7e]+$/.test(raw)) {
47
+ return 'The URL must be ASCII, with no spaces or control characters.';
48
+ }
49
+ let url;
50
+ try {
51
+ url = new URL(raw);
52
+ }
53
+ catch {
54
+ return 'That is not a URL.';
55
+ }
56
+ if (url.protocol !== 'ws:' && url.protocol !== 'wss:') {
57
+ return 'The URL must start with ws:// or wss://.';
58
+ }
59
+ if (url.username !== '' || url.password !== '') {
60
+ return 'A URL with a user name or password is not opened.';
61
+ }
62
+ if (url.hash !== '')
63
+ return 'A WebSocket URL cannot have a fragment (#...).';
64
+ if (url.hostname === '')
65
+ return 'The URL has no host.';
66
+ return undefined;
67
+ }
68
+ /** Why a subprotocol list is not valid, or undefined when it is. */
69
+ export function webSocketProtocolsProblem(protocols) {
70
+ if (protocols.length > WEBSOCKET_PROTOCOLS_MAX) {
71
+ return `At most ${WEBSOCKET_PROTOCOLS_MAX} subprotocols.`;
72
+ }
73
+ for (const protocol of protocols) {
74
+ if (!TOKEN.test(protocol))
75
+ return `"${protocol}" is not a valid subprotocol name.`;
76
+ }
77
+ if (new Set(protocols).size !== protocols.length)
78
+ return 'Subprotocol names must be unique.';
79
+ return undefined;
80
+ }
81
+ /** What a record and an approval call this source: the URL as given, which can carry no credentials. */
82
+ export function webSocketJobCommand(url) {
83
+ return `websocket ${url}`;
84
+ }
85
+ // ---------------------------------------------------------------------------
86
+ // Where it may connect
87
+ // ---------------------------------------------------------------------------
88
+ /** The address check refused, in words for the model. */
89
+ export class WebSocketRefusedError extends Error {
90
+ constructor(message) {
91
+ super(message);
92
+ this.name = 'WebSocketRefusedError';
93
+ }
94
+ }
95
+ /** The host name of a WebSocket URL as a resolver or an address check wants it. */
96
+ export function webSocketHostname(raw) {
97
+ return new URL(raw).hostname.toLowerCase().replace(/^\[|\]$/g, '');
98
+ }
99
+ /**
100
+ * Resolve the host and refuse unless EVERY address is outside the host's own
101
+ * network; return the addresses that were checked.
102
+ *
103
+ * Refusal covers a name that does not resolve, one that resolves to nothing,
104
+ * and one with any private address among its answers: a name that answers both
105
+ * a public and a private address is a name an attacker controls, and a
106
+ * connection may pick either. Failing to resolve is a refusal and never a
107
+ * pass: an empty list satisfies "no private address found" and the connection
108
+ * that followed would resolve the name again against whatever answered.
109
+ */
110
+ export async function vetWebSocketTarget(raw, options = {}) {
111
+ const hostname = webSocketHostname(raw);
112
+ let addresses;
113
+ const literal = isIP(hostname);
114
+ if (literal !== 0) {
115
+ addresses = [hostname];
116
+ }
117
+ else {
118
+ const resolve = options.resolve ??
119
+ (async (name) => (await dnsLookup(name, { all: true })).map((r) => r.address));
120
+ try {
121
+ addresses = await resolve(hostname);
122
+ }
123
+ catch (err) {
124
+ throw new WebSocketRefusedError(`Refused "${hostname}": it could not be resolved (${err instanceof Error ? err.message : String(err)}), so nothing here can say where it points.`);
125
+ }
126
+ if (addresses.length === 0) {
127
+ throw new WebSocketRefusedError(`Refused "${hostname}": it resolves to no addresses.`);
128
+ }
129
+ }
130
+ if (options.allowPrivateAddresses !== true) {
131
+ for (const address of addresses) {
132
+ if (isPrivateAddress(address)) {
133
+ throw new WebSocketRefusedError(`Refused "${hostname}": it resolves to ${address}, inside this host's own network. A WebSocket monitor only connects to public addresses.`);
134
+ }
135
+ }
136
+ }
137
+ return addresses.map((address) => ({ address, family: isIP(address) === 6 ? 6 : 4 }));
138
+ }
139
+ /**
140
+ * A `lookup` for the socket that answers with the addresses that were checked
141
+ * and nothing else.
142
+ *
143
+ * This is what closes the gap `web_fetch` documents: there the address is
144
+ * checked, then the platform `fetch` resolves the name AGAIN, so a name that
145
+ * answers publicly the first time and privately the second (DNS rebinding)
146
+ * gets through. Here the connection never asks DNS: it is handed the checked
147
+ * addresses, so there is no second answer to differ. The URL still names the
148
+ * host, so TLS keeps its SNI and its certificate check on the hostname and not
149
+ * on an address.
150
+ */
151
+ export function pinnedLookup(addresses) {
152
+ return (_hostname, options, callback) => {
153
+ // A caller that wants one family gets that family's addresses; asking for
154
+ // one we hold none of is an error rather than a fall back to DNS.
155
+ const wanted = options?.family === 4 || options?.family === 6 ? options.family : undefined;
156
+ const usable = wanted === undefined ? addresses : addresses.filter((a) => a.family === wanted);
157
+ const first = usable[0];
158
+ if (first === undefined) {
159
+ const error = new Error('no vetted address of that family');
160
+ error.code = 'ENOTFOUND';
161
+ callback(error, '', 0);
162
+ return;
163
+ }
164
+ if (options?.all === true) {
165
+ callback(null, usable.map((a) => ({ address: a.address, family: a.family })));
166
+ return;
167
+ }
168
+ callback(null, first.address, first.family);
169
+ };
170
+ }
171
+ /** `api.example.com` is that host; `.example.com` is the domain and its subdomains. */
172
+ export function hostMatchesPolicy(hostname, allowedHosts) {
173
+ const host = hostname.toLowerCase().replace(/\.$/, '');
174
+ return allowedHosts.some((entry) => {
175
+ const allowed = entry.toLowerCase().replace(/\.$/, '');
176
+ if (allowed.startsWith('.'))
177
+ return host === allowed.slice(1) || host.endsWith(allowed);
178
+ return host === allowed;
179
+ });
180
+ }
181
+ /**
182
+ * Why a sandbox forbids this connection, or undefined when it does not.
183
+ *
184
+ * The connection is opened by the host process, outside the boundary, so a
185
+ * sandbox's network rules do not bind it. If a turn runs under a sandbox that
186
+ * isolates the network, letting the host connect would be a way out of that
187
+ * isolation, so it is refused unless the sandbox's live policy names the host.
188
+ * A sandbox that does not isolate the network says nothing about it, and the
189
+ * connection is allowed, subject to the address check and to review.
190
+ */
191
+ export function webSocketSandboxRefusal(sandbox, hostname) {
192
+ if (sandbox === undefined)
193
+ return undefined;
194
+ if (!isolationOf(sandbox.environment).network)
195
+ return undefined;
196
+ const allowed = sandbox.networkPolicy?.allowedHosts;
197
+ if (allowed !== undefined && hostMatchesPolicy(hostname, allowed))
198
+ return undefined;
199
+ return `A WebSocket monitor connects from the host, outside this turn's sandbox, which blocks the network. "${hostname}" is not on the sandbox's list of allowed hosts, so the connection was not opened. Allow the host in the sandbox's network policy, or watch the service from inside the sandbox with a command.`;
200
+ }
201
+ // ---------------------------------------------------------------------------
202
+ // Notices
203
+ // ---------------------------------------------------------------------------
204
+ /** Server-written text, made one short line before it goes into a notice. */
205
+ function oneLine(text) {
206
+ const line = Array.from(text, (character) => {
207
+ const code = character.charCodeAt(0);
208
+ return code < 0x20 || (code >= 0x7f && code <= 0x9f) ? ' ' : character;
209
+ })
210
+ .join('')
211
+ .replace(/\s+/g, ' ')
212
+ .trim();
213
+ return line.length <= CLOSE_REASON_MAX_CHARS
214
+ ? line
215
+ : `${line.slice(0, CLOSE_REASON_MAX_CHARS)}...`;
216
+ }
217
+ export const webSocketRejectedNotice = (status) => `[WebSocket upgrade rejected: HTTP ${status}]`;
218
+ export const webSocketErrorNotice = (message) => `[WebSocket error: ${oneLine(message)}]`;
219
+ export const webSocketClosedNotice = (code, reason) => {
220
+ const why = oneLine(reason);
221
+ return why === '' ? `[WebSocket closed: ${code}]` : `[WebSocket closed: ${code} ${why}]`;
222
+ };
223
+ export const webSocketDroppedNotice = () => `[Dropped a frame larger than ${WEBSOCKET_MAX_PAYLOAD_BYTES} bytes; closing]`;
224
+ function frameBytes(data) {
225
+ if (Array.isArray(data))
226
+ return data.reduce((sum, part) => sum + part.length, 0);
227
+ return data instanceof ArrayBuffer ? data.byteLength : data.length;
228
+ }
229
+ /**
230
+ * Open the connection and return it as a job's process.
231
+ *
232
+ * The socket library is loaded when the first monitor of this kind starts, so
233
+ * a session that never watches a WebSocket never pays for it.
234
+ */
235
+ export function connectWebSocketSource(options) {
236
+ const { target, host } = options;
237
+ const stand = Object.assign(new EventEmitter(), {
238
+ pid: undefined,
239
+ stdout: new EventEmitter(),
240
+ stderr: new EventEmitter(),
241
+ });
242
+ let socket;
243
+ let graceTimer;
244
+ // Set once somebody asked the connection to stop (a kill, the expiry, the
245
+ // output rule). From then on the connection says nothing: the registry has
246
+ // already told the model why the watch ended, and nothing follows that.
247
+ let stopping = false;
248
+ let opened = false;
249
+ let rejected = false;
250
+ let oversize = false;
251
+ // The connection failed before it opened: the error notice was the last word.
252
+ let failedToOpen = false;
253
+ let ended = false;
254
+ const write = (text) => {
255
+ if (stopping || ended)
256
+ return;
257
+ stand.stdout.emit('data', text);
258
+ };
259
+ const end = (exitCode) => {
260
+ if (ended)
261
+ return;
262
+ ended = true;
263
+ clearTimeout(graceTimer);
264
+ stand.emit('close', exitCode, null);
265
+ };
266
+ void import('ws')
267
+ .then(({ WebSocket }) => {
268
+ if (stopping) {
269
+ end(0);
270
+ return;
271
+ }
272
+ const client = new WebSocket(target.url, target.protocols && target.protocols.length > 0 ? [...target.protocols] : undefined, {
273
+ maxPayload: WEBSOCKET_MAX_PAYLOAD_BYTES,
274
+ // A redirect could lead to an address nobody checked.
275
+ followRedirects: false,
276
+ // A compressed frame inflates past the size cap before it is
277
+ // measured; refusing the extension leaves every frame as sent.
278
+ perMessageDeflate: false,
279
+ handshakeTimeout: WEBSOCKET_HANDSHAKE_TIMEOUT_MS,
280
+ // Only the vetted addresses; see `pinnedLookup`.
281
+ lookup: pinnedLookup(target.addresses),
282
+ });
283
+ socket = client;
284
+ client.on('open', () => {
285
+ opened = true;
286
+ });
287
+ client.on('unexpected-response', (_request, response) => {
288
+ if (stopping)
289
+ return;
290
+ rejected = true;
291
+ response.resume();
292
+ host.notice(webSocketRejectedNotice(response.statusCode ?? 0), { final: true });
293
+ // With a listener for this event the library leaves the half-open
294
+ // request to us.
295
+ client.terminate();
296
+ });
297
+ client.on('error', (error) => {
298
+ if (stopping || rejected || oversize)
299
+ return;
300
+ if (error.code === 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH') {
301
+ // The library knows a frame was too big and not how big: it stops
302
+ // reading at the length field.
303
+ oversize = true;
304
+ host.notice(webSocketDroppedNotice(), { final: true });
305
+ client.terminate();
306
+ return;
307
+ }
308
+ if (!opened) {
309
+ // It never opened, so there is no close to report after this:
310
+ // one notice says it failed, and it is the last.
311
+ failedToOpen = true;
312
+ host.notice(webSocketErrorNotice(error.message), { final: true });
313
+ return;
314
+ }
315
+ host.notice(webSocketErrorNotice(error.message));
316
+ });
317
+ client.on('message', (data, isBinary) => {
318
+ if (isBinary) {
319
+ write(`[binary frame, ${frameBytes(data)} bytes]\n`);
320
+ return;
321
+ }
322
+ const text = Array.isArray(data)
323
+ ? Buffer.concat(data).toString('utf8')
324
+ : Buffer.from(data).toString('utf8');
325
+ // Frames are not lines. A newline closes this one, so the next frame
326
+ // starts a fresh line and a frame without one is not glued to it.
327
+ write(text.endsWith('\n') ? text : `${text}\n`);
328
+ });
329
+ client.on('close', (code, reason) => {
330
+ const why = reason.toString('utf8');
331
+ if (!stopping && !rejected && !oversize && !failedToOpen) {
332
+ host.notice(webSocketClosedNotice(code, why), { final: true });
333
+ if (opened)
334
+ host.connectionClosed(code, oneLine(why));
335
+ }
336
+ // A normal or going-away close is the stream ending; anything else,
337
+ // and a connection that never opened, is a failure.
338
+ end(opened && (code === 1000 || code === 1001 || code === 1005) ? 0 : 1);
339
+ });
340
+ })
341
+ .catch((error) => {
342
+ host.notice(webSocketErrorNotice(error instanceof Error ? error.message : String(error)), {
343
+ final: true,
344
+ });
345
+ end(1);
346
+ });
347
+ return {
348
+ child: stand,
349
+ // Not a process, so the registry's group kill has nothing to signal; this
350
+ // is how it is stopped. SIGTERM asks politely, SIGKILL (and the grace
351
+ // running out) destroys the socket.
352
+ kill(signal) {
353
+ if (ended)
354
+ return;
355
+ if (signal === 'SIGKILL') {
356
+ stopping = true;
357
+ if (socket)
358
+ socket.terminate();
359
+ else
360
+ end(0);
361
+ return;
362
+ }
363
+ if (stopping)
364
+ return;
365
+ stopping = true;
366
+ if (!socket)
367
+ return; // the import resolves, sees `stopping`, and ends
368
+ if (socket.readyState === socket.OPEN) {
369
+ socket.close(1000);
370
+ graceTimer = setTimeout(() => socket?.terminate(), WEBSOCKET_CLOSE_GRACE_MS);
371
+ graceTimer.unref?.();
372
+ }
373
+ else {
374
+ socket.terminate();
375
+ }
376
+ },
377
+ };
378
+ }
379
+ //# sourceMappingURL=websocket-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-source.js","sourceRoot":"","sources":["../../../src/runtime/jobs/websocket-source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The auto-review classifier: a model that reads one proposed action in the
3
+ * light of the conversation and says whether it may run.
4
+ *
5
+ * It backs the `classify` review mode. The mode decides WHICH calls are put to
6
+ * it (see `review-policy.ts`); this decides what happens to one. Four things
7
+ * are non-negotiable, and each has a test:
8
+ *
9
+ * - **Two passes.** A short pass (at most 64 output tokens) answers block or
10
+ * allow. Allow ends it. Block goes to a longer pass (at most 8192) that may
11
+ * reason about the person's authority, and its verdict stands.
12
+ * - **Fail closed.** A provider error, an unreadable answer, or running out of
13
+ * retries blocks the call. There is no path from "could not check" to "run".
14
+ * - **No tool results.** The transcript it reads holds what the person and the
15
+ * agent said and what the agent asked tools to do, never what they returned.
16
+ * - **Counting.** Blocks are counted so a host can stop a session that keeps
17
+ * hitting them: after a run of consecutive blocks, or a total, the decision
18
+ * carries `limitReached` and the host chooses what to do. The classifier
19
+ * never changes a mode.
20
+ */
21
+ import type { ToolManager } from '../../../toolsets/manager.js';
22
+ import type { TokenUsage } from '../../../types/common/index.js';
23
+ import type { ToolCallSummary } from '../../../types/hitl/index.js';
24
+ import { type Message } from '../../../types/message/index.js';
25
+ import type { LLMProvider } from '../../../types/provider/interface.js';
26
+ import { type BackoffPolicy } from '../../../utils/backoff.js';
27
+ import { type Logger } from '../../../utils/logger.js';
28
+ import { type AutoReviewRules } from './rules.js';
29
+ /** Which pass produced a decision. `0` means neither ran. */
30
+ export type AutoReviewStage = 0 | 1 | 2;
31
+ /** What one review came to. */
32
+ export type AutoReviewDecision = {
33
+ readonly verdict: 'allow';
34
+ readonly stage: 1 | 2;
35
+ readonly latencyMs: number;
36
+ } | {
37
+ readonly verdict: 'block';
38
+ /**
39
+ * `rule`: the classifier judged it unsafe. `unavailable`: the check could
40
+ * not be completed. `unreadable`: the answer was not a verdict.
41
+ * `aborted`: the turn was cancelled while it ran.
42
+ */
43
+ readonly cause: 'rule' | 'unavailable' | 'unreadable' | 'aborted';
44
+ readonly stage: AutoReviewStage;
45
+ /** One plain sentence, fit to show the person and to tell the model. */
46
+ readonly reason: string;
47
+ /** The rule the classifier named, when it named one. */
48
+ readonly rule?: string;
49
+ readonly latencyMs: number;
50
+ /**
51
+ * Set on the block that brought the counters to a limit. They are reset
52
+ * as this is returned; what to do about it is the host's decision.
53
+ */
54
+ readonly limitReached?: 'consecutive' | 'total';
55
+ } | {
56
+ /**
57
+ * The conversation is too long for the check to read. Nothing was
58
+ * judged; the host should compact and try again.
59
+ */
60
+ readonly verdict: 'too_long';
61
+ };
62
+ /** One action to be reviewed. */
63
+ export interface AutoReviewRequest {
64
+ /** The call under review. Its id is left out of the transcript so it is not shown twice. */
65
+ readonly call: Pick<ToolCallSummary, 'id' | 'name' | 'input'>;
66
+ /** The conversation so far. Tool results in it are ignored. */
67
+ readonly transcript: readonly Message[];
68
+ /** Where tool definitions are read for `classifierInput`. */
69
+ readonly tools?: Pick<ToolManager, 'get'>;
70
+ readonly signal?: AbortSignal;
71
+ }
72
+ /** How many blocks the classifier tolerates before it tells the host to step in. */
73
+ export interface AutoReviewLimits {
74
+ /** Blocks in a row. Default 3. */
75
+ readonly consecutive?: number;
76
+ /** Blocks over the whole session, until a limit is reported. Default 20. */
77
+ readonly total?: number;
78
+ }
79
+ export interface AutoReviewClassifierOptions {
80
+ readonly provider: LLMProvider;
81
+ readonly model: string;
82
+ /** Additions to, or replacements of, the built-in rule lists. */
83
+ readonly rules?: AutoReviewRules;
84
+ readonly log?: Logger;
85
+ /** Told what each pass cost, so a host can charge it to the session. */
86
+ readonly onUsage?: (usage: TokenUsage, stage: 1 | 2) => void;
87
+ readonly limits?: AutoReviewLimits;
88
+ /**
89
+ * The longest rendered transcript, in characters, that is sent. Longer is
90
+ * reported as `too_long`. Default 400 000 (roughly 100 000 tokens).
91
+ */
92
+ readonly maxTranscriptChars?: number;
93
+ /** The most characters one transcript entry keeps. Default 2000. */
94
+ readonly maxEntryChars?: number;
95
+ /** Retries after a failed provider call. Default 2. */
96
+ readonly maxRetries?: number;
97
+ /** The wait before a retry. Default 2 s doubling to 10 s, with some jitter. */
98
+ readonly retryDelay?: BackoffPolicy;
99
+ /** How long the first pass may take. Default 60 s. */
100
+ readonly quickDeadlineMs?: number;
101
+ /** How long the second pass may take. Default 120 s. */
102
+ readonly reasonedDeadlineMs?: number;
103
+ /** Sampling temperature. Default 0. */
104
+ readonly temperature?: number;
105
+ /** The wait between retries; replaced by tests so they never wait for real. */
106
+ readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
107
+ /** Jitter source for the retry delay. */
108
+ readonly random?: () => number;
109
+ /** Clock for latency. */
110
+ readonly now?: () => number;
111
+ }
112
+ export interface AutoReviewClassifier {
113
+ /** Reviews one action. Never throws for a provider failure: that is a block. */
114
+ review(request: AutoReviewRequest): Promise<AutoReviewDecision>;
115
+ /** The block counters right now. */
116
+ readonly denials: {
117
+ readonly consecutive: number;
118
+ readonly total: number;
119
+ };
120
+ /** Zeroes the block counters. */
121
+ reset(): void;
122
+ }
123
+ export declare const DEFAULT_AUTO_REVIEW_LIMITS: {
124
+ readonly consecutive: 3;
125
+ readonly total: 20;
126
+ };
127
+ /** The words the model is told when a call is blocked. */
128
+ export declare function autoReviewBlockedFeedback(reason: string): string;
129
+ export declare function createAutoReviewClassifier(options: AutoReviewClassifierOptions): AutoReviewClassifier;
130
+ //# sourceMappingURL=classifier.d.ts.map