@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.
- package/CHANGELOG.md +41 -0
- package/dist/authorization/gate.d.ts +11 -0
- package/dist/authorization/gate.d.ts.map +1 -1
- package/dist/authorization/gate.js +35 -0
- package/dist/authorization/gate.js.map +1 -1
- package/dist/authorization/removal.js +11 -6
- package/dist/authorization/removal.js.map +1 -1
- package/dist/authorization/rules.d.ts.map +1 -1
- package/dist/authorization/rules.js +30 -2
- package/dist/authorization/rules.js.map +1 -1
- package/dist/authorization/shell-paths.d.ts +20 -0
- package/dist/authorization/shell-paths.d.ts.map +1 -1
- package/dist/authorization/shell-paths.js +254 -52
- package/dist/authorization/shell-paths.js.map +1 -1
- package/dist/bridge/acp/server.d.ts +8 -0
- package/dist/bridge/acp/server.d.ts.map +1 -1
- package/dist/bridge/acp/server.js +7 -0
- package/dist/bridge/acp/server.js.map +1 -1
- package/dist/connector/web/guarded-fetch.d.ts.map +1 -1
- package/dist/connector/web/guarded-fetch.js +44 -0
- package/dist/connector/web/guarded-fetch.js.map +1 -1
- package/dist/manager/resident/outbox.d.ts +4 -4
- package/dist/process/kill-tree.d.ts +3 -2
- package/dist/process/kill-tree.d.ts.map +1 -1
- package/dist/process/kill-tree.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 +4 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +2 -1
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +1 -1
- package/dist/public-tools.js.map +1 -1
- package/dist/public-types.d.ts +1 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.d.ts +80 -2
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +70 -5
- package/dist/runtime/jobs/registry.js.map +1 -1
- package/dist/runtime/jobs/websocket-source.d.ts +120 -0
- package/dist/runtime/jobs/websocket-source.d.ts.map +1 -0
- package/dist/runtime/jobs/websocket-source.js +379 -0
- package/dist/runtime/jobs/websocket-source.js.map +1 -0
- package/dist/runtime/query/auto-review/classifier.d.ts +130 -0
- package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/classifier.js +244 -0
- package/dist/runtime/query/auto-review/classifier.js.map +1 -0
- package/dist/runtime/query/auto-review/index.d.ts +7 -0
- package/dist/runtime/query/auto-review/index.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/index.js +4 -0
- package/dist/runtime/query/auto-review/index.js.map +1 -0
- package/dist/runtime/query/auto-review/prompt.d.ts +20 -0
- package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/prompt.js +89 -0
- package/dist/runtime/query/auto-review/prompt.js.map +1 -0
- package/dist/runtime/query/auto-review/rules.d.ts +49 -0
- package/dist/runtime/query/auto-review/rules.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/rules.js +81 -0
- package/dist/runtime/query/auto-review/rules.js.map +1 -0
- package/dist/runtime/query/auto-review/transcript.d.ts +59 -0
- package/dist/runtime/query/auto-review/transcript.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/transcript.js +116 -0
- package/dist/runtime/query/auto-review/transcript.js.map +1 -0
- package/dist/runtime/query/auto-review/verdict.d.ts +21 -0
- package/dist/runtime/query/auto-review/verdict.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/verdict.js +45 -0
- package/dist/runtime/query/auto-review/verdict.js.map +1 -0
- package/dist/runtime/query/executor.d.ts +7 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +26 -2
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.js +2 -2
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +0 -9
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +11 -2
- 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 +39 -0
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/path-escalation.d.ts.map +1 -1
- package/dist/runtime/query/path-escalation.js +43 -10
- package/dist/runtime/query/path-escalation.js.map +1 -1
- package/dist/runtime/query/resume-pending.d.ts.map +1 -1
- package/dist/runtime/query/resume-pending.js +14 -0
- package/dist/runtime/query/resume-pending.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +88 -1
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +504 -11
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/steering.d.ts +2 -2
- package/dist/runtime/query/steering.d.ts.map +1 -1
- package/dist/runtime/query/steering.js +16 -3
- package/dist/runtime/query/steering.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +2 -0
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/classifier-input.d.ts +9 -0
- package/dist/tools/builtins/classifier-input.d.ts.map +1 -0
- package/dist/tools/builtins/classifier-input.js +20 -0
- package/dist/tools/builtins/classifier-input.js.map +1 -0
- package/dist/tools/builtins/code-analysis.d.ts +2 -0
- package/dist/tools/builtins/code-analysis.d.ts.map +1 -1
- package/dist/tools/builtins/code-analysis.js +196 -119
- package/dist/tools/builtins/code-analysis.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts +10 -10
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +12 -0
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/monitor.d.ts +38 -4
- package/dist/tools/builtins/monitor.d.ts.map +1 -1
- package/dist/tools/builtins/monitor.js +239 -97
- package/dist/tools/builtins/monitor.js.map +1 -1
- package/dist/tools/builtins/web.d.ts.map +1 -1
- package/dist/tools/builtins/web.js +3 -0
- package/dist/tools/builtins/web.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +6 -0
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/defineTool.d.ts +2 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +1 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/types/acp/index.d.ts +7 -0
- package/dist/types/acp/index.d.ts.map +1 -1
- package/dist/types/code-analysis/index.d.ts +54 -3
- package/dist/types/code-analysis/index.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +45 -1
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/sandbox/index.d.ts +10 -0
- package/dist/types/sandbox/index.d.ts.map +1 -1
- package/dist/types/sandbox/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +51 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +4 -2
- package/src/authorization/gate.ts +35 -0
- package/src/authorization/removal.ts +12 -6
- package/src/authorization/rules.ts +27 -2
- package/src/authorization/shell-paths.ts +264 -51
- package/src/bridge/acp/server.ts +20 -0
- package/src/connector/web/guarded-fetch.ts +41 -0
- package/src/process/kill-tree.ts +4 -2
- package/src/public-runtime.ts +19 -1
- package/src/public-tools.ts +3 -0
- package/src/public-types.ts +11 -0
- package/src/runtime/jobs/registry.ts +162 -13
- package/src/runtime/jobs/websocket-source.ts +438 -0
- package/src/runtime/query/auto-review/classifier.ts +404 -0
- package/src/runtime/query/auto-review/index.ts +23 -0
- package/src/runtime/query/auto-review/prompt.ts +97 -0
- package/src/runtime/query/auto-review/rules.ts +106 -0
- package/src/runtime/query/auto-review/transcript.ts +159 -0
- package/src/runtime/query/auto-review/verdict.ts +50 -0
- package/src/runtime/query/executor.ts +29 -2
- package/src/runtime/query/index.ts +2 -2
- package/src/runtime/query/iteration/phases/context.ts +17 -3
- package/src/runtime/query/iteration/phases/tool-review.ts +41 -0
- package/src/runtime/query/path-escalation.ts +44 -9
- package/src/runtime/query/resume-pending.ts +14 -0
- package/src/runtime/query/review-policy.ts +591 -14
- package/src/runtime/query/steering.ts +32 -6
- package/src/tools/builtins/bash.ts +3 -0
- package/src/tools/builtins/classifier-input.ts +19 -0
- package/src/tools/builtins/code-analysis.ts +211 -119
- package/src/tools/builtins/edit.ts +13 -0
- package/src/tools/builtins/monitor.ts +297 -123
- package/src/tools/builtins/web.ts +3 -0
- package/src/tools/builtins/write-file.ts +6 -0
- package/src/tools/defineTool.ts +3 -0
- package/src/types/acp/index.ts +7 -0
- package/src/types/code-analysis/index.ts +51 -3
- package/src/types/hitl/index.ts +49 -1
- package/src/types/sandbox/index.ts +10 -0
- package/src/types/tool/index.ts +44 -0
|
@@ -6,6 +6,17 @@ import {
|
|
|
6
6
|
MONITOR_TIMEOUT_MIN_MS,
|
|
7
7
|
formatMonitorDuration,
|
|
8
8
|
} from '../../runtime/jobs/monitor-stream.js'
|
|
9
|
+
import {
|
|
10
|
+
WEBSOCKET_PROTOCOLS_MAX,
|
|
11
|
+
WebSocketRefusedError,
|
|
12
|
+
type WebSocketVetOptions,
|
|
13
|
+
vetWebSocketTarget,
|
|
14
|
+
webSocketHostname,
|
|
15
|
+
webSocketProtocolsProblem,
|
|
16
|
+
webSocketSandboxRefusal,
|
|
17
|
+
webSocketUrlProblem,
|
|
18
|
+
} from '../../runtime/jobs/websocket-source.js'
|
|
19
|
+
import type { MonitorWebSocketTarget } from '../../types/tool/index.js'
|
|
9
20
|
import {
|
|
10
21
|
bashToolDialect,
|
|
11
22
|
hostCommandShell,
|
|
@@ -38,42 +49,82 @@ import {
|
|
|
38
49
|
* read the command the way they read `bash`'s, and it is never read-only. It
|
|
39
50
|
* has no way out of the sandbox: a watch that outlives one approved call must
|
|
40
51
|
* not run outside the boundary that call was approved under.
|
|
52
|
+
*
|
|
53
|
+
* A source can also be a WebSocket (`ws`), for services that push their events
|
|
54
|
+
* and offer no command that prints them. That one is a different thing to
|
|
55
|
+
* approve: it is not a shell line, it opens a connection FROM THE HOST to an
|
|
56
|
+
* address the model chose, and it does so outside any sandbox. So it is read
|
|
57
|
+
* by no command rule, it is always reviewed, a sandbox that blocks the network
|
|
58
|
+
* refuses it, and the address is checked and pinned (see
|
|
59
|
+
* `runtime/jobs/websocket-source.ts`). Untrusted frames reach the model the way
|
|
60
|
+
* a command's lines do: through the monitor event path, in its untrusted frame.
|
|
41
61
|
*/
|
|
42
62
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
.string()
|
|
46
|
-
.min(1)
|
|
47
|
-
.max(200)
|
|
48
|
-
.describe(
|
|
49
|
-
'What you are watching, in a few specific words. It is shown with every event ("Monitor event: <description>"), so make it identify this watch among several: "errors in deploy log", not "monitor".',
|
|
50
|
-
),
|
|
51
|
-
command: z
|
|
63
|
+
const webSocketInput = z.object({
|
|
64
|
+
url: z
|
|
52
65
|
.string()
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
isWindowsBash(hostCommandShell()) ? rewriteWindowsNullRedirect(command) : command,
|
|
58
|
-
)
|
|
59
|
-
.describe(
|
|
60
|
-
'The shell command to watch. Each line it prints to stdout becomes an event. It runs until it ends or the timeout; it must not need stdin.',
|
|
61
|
-
),
|
|
62
|
-
timeout_ms: z
|
|
63
|
-
.preprocess(
|
|
64
|
-
(v) => (typeof v === 'string' ? Number(v) : v),
|
|
65
|
-
z
|
|
66
|
-
.number()
|
|
67
|
-
.int()
|
|
68
|
-
.min(MONITOR_TIMEOUT_MIN_MS)
|
|
69
|
-
.max(MONITOR_TIMEOUT_MAX_MS)
|
|
70
|
-
.default(MONITOR_TIMEOUT_DEFAULT_MS),
|
|
71
|
-
)
|
|
66
|
+
.superRefine((value, ctx) => {
|
|
67
|
+
const problem = webSocketUrlProblem(value)
|
|
68
|
+
if (problem !== undefined) ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem })
|
|
69
|
+
})
|
|
72
70
|
.describe(
|
|
73
|
-
|
|
71
|
+
'The ws:// or wss:// URL to connect to. A query string is kept; credentials in it are refused.',
|
|
74
72
|
),
|
|
73
|
+
protocols: z
|
|
74
|
+
.array(z.string())
|
|
75
|
+
.max(WEBSOCKET_PROTOCOLS_MAX)
|
|
76
|
+
.superRefine((value, ctx) => {
|
|
77
|
+
const problem = webSocketProtocolsProblem(value)
|
|
78
|
+
if (problem !== undefined) ctx.addIssue({ code: z.ZodIssueCode.custom, message: problem })
|
|
79
|
+
})
|
|
80
|
+
.optional()
|
|
81
|
+
.describe('Subprotocol names to offer, when the service asks for one.'),
|
|
75
82
|
})
|
|
76
83
|
|
|
84
|
+
const inputSchema = z
|
|
85
|
+
.object({
|
|
86
|
+
description: z
|
|
87
|
+
.string()
|
|
88
|
+
.min(1)
|
|
89
|
+
.max(200)
|
|
90
|
+
.describe(
|
|
91
|
+
'What you are watching, in a few specific words. It is shown with every event ("Monitor event: <description>"), so make it identify this watch among several: "errors in deploy log", not "monitor".',
|
|
92
|
+
),
|
|
93
|
+
command: z
|
|
94
|
+
.string()
|
|
95
|
+
.min(1)
|
|
96
|
+
// Same rewrite as `bash`, for the same reason: the rules read the line a
|
|
97
|
+
// shell will run.
|
|
98
|
+
.transform((command) =>
|
|
99
|
+
isWindowsBash(hostCommandShell()) ? rewriteWindowsNullRedirect(command) : command,
|
|
100
|
+
)
|
|
101
|
+
.optional()
|
|
102
|
+
.describe(
|
|
103
|
+
'The shell command to watch. Each line it prints to stdout becomes an event. It runs until it ends or the timeout; it must not need stdin. Give this or ws, not both.',
|
|
104
|
+
),
|
|
105
|
+
ws: webSocketInput
|
|
106
|
+
.optional()
|
|
107
|
+
.describe(
|
|
108
|
+
'Watch a WebSocket instead of a command: each line of each text frame the server sends becomes an event. Give this or command, not both.',
|
|
109
|
+
),
|
|
110
|
+
timeout_ms: z
|
|
111
|
+
.preprocess(
|
|
112
|
+
(v) => (typeof v === 'string' ? Number(v) : v),
|
|
113
|
+
z
|
|
114
|
+
.number()
|
|
115
|
+
.int()
|
|
116
|
+
.min(MONITOR_TIMEOUT_MIN_MS)
|
|
117
|
+
.max(MONITOR_TIMEOUT_MAX_MS)
|
|
118
|
+
.default(MONITOR_TIMEOUT_DEFAULT_MS),
|
|
119
|
+
)
|
|
120
|
+
.describe(
|
|
121
|
+
`How long to watch, in milliseconds. Default ${MONITOR_TIMEOUT_DEFAULT_MS}. The host caps it (30 minutes by default; shorter in a single-shot run), and a larger value is lowered to that cap without an error; the result states the time actually granted.`,
|
|
122
|
+
),
|
|
123
|
+
})
|
|
124
|
+
.refine((input) => (input.command === undefined) !== (input.ws === undefined), {
|
|
125
|
+
message: 'Give exactly one of command and ws.',
|
|
126
|
+
})
|
|
127
|
+
|
|
77
128
|
type MonitorInput = z.infer<typeof inputSchema>
|
|
78
129
|
|
|
79
130
|
export const MONITOR_TOOL_NAME = 'monitor'
|
|
@@ -88,109 +139,231 @@ export const MONITOR_CANNOT_DETACH =
|
|
|
88
139
|
'monitor is unavailable: this sandbox cannot start a detached process, and the watch will not be run on the host to get around it. Run the command in the foreground with bash, with a timeout.'
|
|
89
140
|
|
|
90
141
|
const MONITOR_DESCRIPTION =
|
|
91
|
-
'Starts a command and tells you about each line it prints to stdout, as it prints it, while you keep working. Lines that arrive within 200 ms come together as one event. Use it to watch a log, a test or deploy run, a status endpoint — anything where you must react to something happening INSIDE a long-running command. For ONE notification when something finishes, do not use this: run bash with run_in_background and wait_for_job. Every stage of a pipe must flush per line or events arrive late or never (grep --line-buffered, awk with fflush(), sed -u; `head` cannot). Make the filter match failure as well as success (crash, Traceback, Error, FAILED, Killed…): silence from a monitor is not success. Poll a remote API no faster than every 30 seconds. stderr is not an event; read it with job. A monitor that prints too much is stopped, so filter at the source. It expires after the timeout (default 5 minutes, at most 30) and you get one notice; start it again if you still need it. Stop one early with job {"action":"kill","id":"<id>"}.'
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
142
|
+
'Starts a command and tells you about each line it prints to stdout, as it prints it, while you keep working. Lines that arrive within 200 ms come together as one event. Use it to watch a log, a test or deploy run, a status endpoint — anything where you must react to something happening INSIDE a long-running command. For ONE notification when something finishes, do not use this: run bash with run_in_background and wait_for_job. Every stage of a pipe must flush per line or events arrive late or never (grep --line-buffered, awk with fflush(), sed -u; `head` cannot). Make the filter match failure as well as success (crash, Traceback, Error, FAILED, Killed…): silence from a monitor is not success. Poll a remote API no faster than every 30 seconds. stderr is not an event; read it with job. A monitor that prints too much is stopped, so filter at the source. It expires after the timeout (default 5 minutes, at most 30) and you get one notice; start it again if you still need it. Stop one early with job {"action":"kill","id":"<id>"}. ' +
|
|
143
|
+
'To watch a service that PUSHES events (a CI or log stream, a change feed) give ws instead of command: { "url": "wss://…", "protocols": [...] }. Each line of each text frame becomes an event, binary frames are only counted, and the watch ends when the server closes the connection. Use it for a push stream, not for request and response. It connects from this machine, so private and local addresses are refused, and you are asked before it connects.'
|
|
144
|
+
|
|
145
|
+
/** What a host can tune about the WebSocket source; for tests only, see {@link WebSocketVetOptions}. */
|
|
146
|
+
export interface MonitorToolOptions {
|
|
147
|
+
readonly webSocket?: WebSocketVetOptions
|
|
148
|
+
/**
|
|
149
|
+
* How to turn web access on, in the host's own words, appended to the
|
|
150
|
+
* refusal a WebSocket call gets while it is off. The SDK cannot name a
|
|
151
|
+
* switch it does not own (a CLI config key, a desktop setting), and a
|
|
152
|
+
* refusal that only says "off" leaves the operator guessing. One short
|
|
153
|
+
* sentence; it is shown to the model and the operator as written.
|
|
154
|
+
*/
|
|
155
|
+
readonly webAccessHint?: string
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The text that says a WebSocket watch was refused because the host has web access off. */
|
|
159
|
+
export const MONITOR_WEBSOCKET_WEB_OFF =
|
|
160
|
+
"WebSocket monitors follow the host's web access setting, and web access is switched off here, so no connection was opened. Watch the service with a command (for example curl or websocat through monitor) if the operator allows it."
|
|
161
|
+
|
|
162
|
+
const MONITOR_WEBSOCKET_NOT_SUPPORTED =
|
|
163
|
+
'This host cannot start a WebSocket monitor, so no connection was opened. Watch the service with a command through monitor if the operator allows it.'
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The tool, built once with defaults for a host and again by a test that needs
|
|
167
|
+
* to reach a server on this machine. The options belong to whoever builds the
|
|
168
|
+
* tool; nothing a model sends can set them.
|
|
169
|
+
*/
|
|
170
|
+
export function createMonitorTool(options: MonitorToolOptions = {}) {
|
|
171
|
+
return defineTool({
|
|
172
|
+
name: MONITOR_TOOL_NAME,
|
|
173
|
+
description: MONITOR_DESCRIPTION,
|
|
174
|
+
inputSchema,
|
|
175
|
+
category: 'shell',
|
|
176
|
+
// Bash's, because it IS a shell command: the gate reads the line, splits
|
|
177
|
+
// chains, and applies the operator's rules to each part. A WebSocket call
|
|
178
|
+
// has no `command`, so a rule written for commands has nothing to match
|
|
179
|
+
// and cannot allow it; `requiresApproval` below is what asks.
|
|
180
|
+
commandArgument: 'command',
|
|
181
|
+
classifierInput: (input: MonitorInput) =>
|
|
182
|
+
input.command !== undefined
|
|
183
|
+
? `[long-running watch] ${input.command}`
|
|
184
|
+
: `[long-running WebSocket watch] ${JSON.stringify(input.ws)}`,
|
|
185
|
+
commandDialect: bashToolDialect,
|
|
186
|
+
// `network_access` too: a WebSocket call reaches the network. The gate
|
|
187
|
+
// reads a tool's `category`, not this list, to route a call, so declaring
|
|
188
|
+
// it changes no decision for a command; it says what the tool may do.
|
|
189
|
+
permissions: ['shell_execute', 'network_access'],
|
|
190
|
+
readOnly: false,
|
|
191
|
+
destructive: (input: MonitorInput) =>
|
|
192
|
+
input.command !== undefined && isDangerousCommand(input.command),
|
|
193
|
+
// A WebSocket call is the one input-aware seam the gate offers that no
|
|
194
|
+
// allow rule or mode outranks: it connects from the host, outside any
|
|
195
|
+
// sandbox, to an address the model chose. `category: 'shell'` is what the
|
|
196
|
+
// sandboxed-shell preset auto-approves, and moving the whole tool to
|
|
197
|
+
// `network` would start asking about commands that were never a question;
|
|
198
|
+
// this asks only about the connection. It is stronger than "review where
|
|
199
|
+
// the mode reviews non-read-only calls": `auto` mode and an unattended
|
|
200
|
+
// turn are asked too (or refuse when nobody can be asked), which is the
|
|
201
|
+
// point for a connection that leaves the boundary those modes rely on.
|
|
202
|
+
requiresApproval: (input: MonitorInput) => input.ws !== undefined,
|
|
203
|
+
// Starting a monitor registers a job against the owner's cap and its id
|
|
204
|
+
// is then named in later calls; two at once could race the cap, as they
|
|
205
|
+
// can for a background `bash`.
|
|
206
|
+
concurrencySafe: false,
|
|
207
|
+
|
|
208
|
+
presentCall(input) {
|
|
209
|
+
const label = typeof input.description === 'string' ? input.description : undefined
|
|
210
|
+
if (input.ws !== undefined && typeof input.ws === 'object') {
|
|
211
|
+
const ws = input.ws as { url?: unknown; protocols?: unknown }
|
|
212
|
+
if (typeof ws.url !== 'string') return undefined
|
|
213
|
+
const protocols =
|
|
214
|
+
Array.isArray(ws.protocols) && ws.protocols.length > 0
|
|
215
|
+
? ` (subprotocols: ${ws.protocols.join(', ')})`
|
|
216
|
+
: ''
|
|
217
|
+
// What will happen, said plainly, because this is what a person
|
|
218
|
+
// approves: no command line stands in for it.
|
|
219
|
+
return {
|
|
220
|
+
kind: 'generic',
|
|
221
|
+
label: `Monitor${label ? ` · ${label}` : ''} · WebSocket ${ws.url}${protocols}`,
|
|
222
|
+
}
|
|
126
223
|
}
|
|
127
|
-
|
|
128
|
-
if (backgroundTasksDisabled()) {
|
|
224
|
+
if (typeof input.command !== 'string') return undefined
|
|
129
225
|
return {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
226
|
+
kind: 'generic',
|
|
227
|
+
label: `Monitor · ${label ?? input.command}`,
|
|
228
|
+
facts: { kind: 'command', command: input.command, background: true },
|
|
133
229
|
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
success: false,
|
|
145
|
-
output: '',
|
|
146
|
-
error:
|
|
147
|
-
'monitor was requested, but this host provides no background job registry. Run the command in the foreground with bash, with a timeout.',
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
async execute(input, context) {
|
|
233
|
+
if (input.command !== undefined && isDangerousCommand(input.command)) {
|
|
234
|
+
return {
|
|
235
|
+
success: false,
|
|
236
|
+
output: '',
|
|
237
|
+
error: `Dangerous command blocked: "${input.command}"`,
|
|
238
|
+
facts: { kind: 'failure', reason: 'blocked', subject: MONITOR_TOOL_NAME },
|
|
239
|
+
}
|
|
148
240
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
241
|
+
if (backgroundTasksDisabled()) {
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
output: '',
|
|
245
|
+
error: `Background jobs are switched off here (${DISABLE_BACKGROUND_TASKS_ENV}), and a monitor is one. Check in the foreground with bash instead.`,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// What is checked about a connection is checked before anything that
|
|
249
|
+
// speaks of commands, so a refusal names its own cause.
|
|
250
|
+
let webSocket: MonitorWebSocketTarget | undefined
|
|
251
|
+
if (input.ws !== undefined) {
|
|
252
|
+
// The operator's switch for reaching the web at all. A host that has
|
|
253
|
+
// not given this turn a web capability has not decided to let the
|
|
254
|
+
// model open connections, and a WebSocket is one.
|
|
255
|
+
if (context.web?.fetch === undefined) {
|
|
256
|
+
return {
|
|
257
|
+
success: false,
|
|
258
|
+
output: '',
|
|
259
|
+
error: options.webAccessHint
|
|
260
|
+
? `${MONITOR_WEBSOCKET_WEB_OFF} ${options.webAccessHint}`
|
|
261
|
+
: MONITOR_WEBSOCKET_WEB_OFF,
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const hostname = webSocketHostname(input.ws.url)
|
|
265
|
+
const sandboxRefusal = webSocketSandboxRefusal(context.sandbox, hostname)
|
|
266
|
+
if (sandboxRefusal !== undefined) {
|
|
267
|
+
return { success: false, output: '', error: sandboxRefusal }
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const addresses = await vetWebSocketTarget(input.ws.url, options.webSocket)
|
|
271
|
+
webSocket = {
|
|
272
|
+
url: input.ws.url,
|
|
273
|
+
...(input.ws.protocols ? { protocols: input.ws.protocols } : {}),
|
|
274
|
+
addresses,
|
|
275
|
+
}
|
|
276
|
+
} catch (err) {
|
|
277
|
+
if (err instanceof WebSocketRefusedError) {
|
|
278
|
+
return { success: false, output: '', error: err.message }
|
|
279
|
+
}
|
|
280
|
+
throw err
|
|
281
|
+
}
|
|
282
|
+
} else if (context.sandbox && context.sandbox.spawnDetached === undefined) {
|
|
283
|
+
// Same rule as background bash: the executor withholds the registry
|
|
284
|
+
// from a sandbox that cannot start a detached process, and this check
|
|
285
|
+
// is for the direct caller who hands both.
|
|
286
|
+
return { success: false, output: '', error: MONITOR_CANNOT_DETACH }
|
|
156
287
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const { job, timeoutMs } = jobs.startMonitor({
|
|
161
|
-
command: input.command,
|
|
162
|
-
description: input.description,
|
|
163
|
-
timeoutMs: input.timeout_ms,
|
|
164
|
-
workingDirectory: context.workingDirectory,
|
|
165
|
-
})
|
|
166
|
-
// `startMonitor` returns before the process exists, and a spawn that
|
|
167
|
-
// fails reports a tick later; wait for that one event so "started" is
|
|
168
|
-
// never said of a watch that could not start.
|
|
169
|
-
const startup = await jobs.awaitStarted?.(job.id)
|
|
170
|
-
if (startup?.error !== undefined) {
|
|
288
|
+
const jobs = context.backgroundJobs
|
|
289
|
+
if (!jobs) {
|
|
171
290
|
return {
|
|
172
291
|
success: false,
|
|
173
292
|
output: '',
|
|
174
|
-
error:
|
|
175
|
-
|
|
293
|
+
error:
|
|
294
|
+
'monitor was requested, but this host provides no background job registry. Run the command in the foreground with bash, with a timeout.',
|
|
176
295
|
}
|
|
177
296
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
297
|
+
if (webSocket !== undefined && !jobs.startWebSocketMonitor) {
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
output: '',
|
|
301
|
+
error: MONITOR_WEBSOCKET_NOT_SUPPORTED,
|
|
302
|
+
}
|
|
183
303
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
304
|
+
if (webSocket === undefined && !jobs.startMonitor) {
|
|
305
|
+
return {
|
|
306
|
+
success: false,
|
|
307
|
+
output: '',
|
|
308
|
+
error:
|
|
309
|
+
'This host cannot deliver monitor events, so no watch was started. Use bash with run_in_background and wait_for_job to be told when a command finishes.',
|
|
310
|
+
}
|
|
190
311
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const common = {
|
|
315
|
+
description: input.description,
|
|
316
|
+
timeoutMs: input.timeout_ms,
|
|
317
|
+
workingDirectory: context.workingDirectory,
|
|
318
|
+
}
|
|
319
|
+
const { job, timeoutMs } =
|
|
320
|
+
webSocket !== undefined
|
|
321
|
+
? (jobs.startWebSocketMonitor as NonNullable<typeof jobs.startWebSocketMonitor>)({
|
|
322
|
+
...common,
|
|
323
|
+
webSocket,
|
|
324
|
+
})
|
|
325
|
+
: (jobs.startMonitor as NonNullable<typeof jobs.startMonitor>)({
|
|
326
|
+
...common,
|
|
327
|
+
command: input.command as string,
|
|
328
|
+
})
|
|
329
|
+
// `startMonitor` returns before the process exists, and a spawn that
|
|
330
|
+
// fails reports a tick later; wait for that one event so "started" is
|
|
331
|
+
// never said of a watch that could not start.
|
|
332
|
+
const startup = await jobs.awaitStarted?.(job.id)
|
|
333
|
+
if (startup?.error !== undefined) {
|
|
334
|
+
return {
|
|
335
|
+
success: false,
|
|
336
|
+
output: '',
|
|
337
|
+
error: `${startup.error} (monitor ${job.id} did not run).`,
|
|
338
|
+
data: { jobId: job.id, monitor: true, startFailed: true },
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
if (webSocket !== undefined) {
|
|
342
|
+
return {
|
|
343
|
+
success: true,
|
|
344
|
+
output: `WebSocket monitor started (job ${job.id}, expires in ${formatMonitorDuration(timeoutMs)} unless the server closes the connection first; you get one notice at expiry — start it again if you still need the watch). Each line of each text frame the server sends reaches you as an event while you keep working; binary frames are only counted. The connection opens in the background: if it fails you are told in an event. Do not poll or sleep. Stop it with job {"action":"kill","id":"${job.id}"}. An event may arrive while you are waiting for the user — it is not their reply.`,
|
|
345
|
+
data: { jobId: job.id, monitor: true, timeoutMs, transport: 'websocket' },
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
success: true,
|
|
350
|
+
output: `Monitor started (job ${job.id}, expires in ${formatMonitorDuration(timeoutMs)} unless the command ends first; you get one notice at expiry — start it again if you still need the watch). Each line the command prints to stdout reaches you as an event while you keep working; do not poll or sleep. Stop it with job {"action":"kill","id":"${job.id}"}. An event may arrive while you are waiting for the user — it is not their reply.`,
|
|
351
|
+
data: { jobId: job.id, monitor: true, timeoutMs },
|
|
352
|
+
facts: { kind: 'command', command: input.command as string, background: true },
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
// The per-owner cap, most likely; naming the limit is actionable.
|
|
356
|
+
return {
|
|
357
|
+
success: false,
|
|
358
|
+
output: '',
|
|
359
|
+
error: err instanceof Error ? err.message : String(err),
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export const MonitorTool = createMonitorTool()
|
|
194
367
|
|
|
195
368
|
/**
|
|
196
369
|
* The tool, for a host that opts in.
|
|
@@ -199,8 +372,9 @@ export const MonitorTool = defineTool({
|
|
|
199
372
|
* wakes on them, and offering a tool that starts a job nobody will hear from
|
|
200
373
|
* is worse than not offering it. A function rather than a constant so a host
|
|
201
374
|
* can decide per turn, and so it reads like `getCodeAnalysisTools`, beside
|
|
202
|
-
* which a host mounts it deferred.
|
|
375
|
+
* which a host mounts it deferred. Called with no options it returns the
|
|
376
|
+
* shared {@link MonitorTool}.
|
|
203
377
|
*/
|
|
204
|
-
export function getMonitorTools(): (typeof MonitorTool)[] {
|
|
205
|
-
return [MonitorTool]
|
|
378
|
+
export function getMonitorTools(options?: MonitorToolOptions): (typeof MonitorTool)[] {
|
|
379
|
+
return [options === undefined ? MonitorTool : createMonitorTool(options)]
|
|
206
380
|
}
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { WebFetchRefusedError } from '../../connector/web/types.js'
|
|
12
12
|
import type { ToolContext } from '../../types/tool/index.js'
|
|
13
13
|
import { defineTool } from '../defineTool.js'
|
|
14
|
+
import { clip } from './classifier-input.js'
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* The two web tools, and the reason they are one file.
|
|
@@ -151,6 +152,8 @@ export const WebFetchTool = defineTool({
|
|
|
151
152
|
// preset — handing outbound reach to runs whose sandbox was never asked
|
|
152
153
|
// to confine the network.
|
|
153
154
|
category: 'network',
|
|
155
|
+
// The classifier judges the address and what is asked of it.
|
|
156
|
+
classifierInput: (input) => `${clip(input.url, 1000)}: ${clip(input.prompt, 300)}`,
|
|
154
157
|
permissions: ['network_access'],
|
|
155
158
|
// Reads a page and changes nothing HERE. That is not the same as
|
|
156
159
|
// harmless — a GET can still act on a remote system — which is why the
|
|
@@ -6,6 +6,7 @@ import type { ToolFacts } from '../../types/tool/presentation.js'
|
|
|
6
6
|
import { defineTool } from '../defineTool.js'
|
|
7
7
|
import { resolveWithinAnyReal, toolRoots } from '../paths.js'
|
|
8
8
|
import { atomicWriteFile } from './atomic-write-file.js'
|
|
9
|
+
import { clip } from './classifier-input.js'
|
|
9
10
|
import { fingerprintContent, staleFileError } from './content-fingerprint.js'
|
|
10
11
|
import { withFileMutationLock } from './file-mutation-lock.js'
|
|
11
12
|
import { fileWriteResult } from './file-write-result.js'
|
|
@@ -93,6 +94,11 @@ export const WriteFileTool = defineTool({
|
|
|
93
94
|
// Declared so a path outside the turn's roots can be reviewed before the
|
|
94
95
|
// call runs, rather than refused after. See `ToolDefinition.pathArgument`.
|
|
95
96
|
pathArgument: 'path',
|
|
97
|
+
// The classifier judges where a file is written and how much, not the body.
|
|
98
|
+
classifierInput: (input: WriteInput) => {
|
|
99
|
+
const body = input.content ?? input.newStr ?? ''
|
|
100
|
+
return `${input.path}: writes ${body.length} characters, starting ${JSON.stringify(clip(body, 300))}`
|
|
101
|
+
},
|
|
96
102
|
permissions: ['file_write'],
|
|
97
103
|
readOnly: false,
|
|
98
104
|
destructive: true,
|
package/src/tools/defineTool.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface DefineToolOptions<S extends z.ZodType> {
|
|
|
33
33
|
capturesScreen?: boolean | ((input: z.infer<S>) => boolean)
|
|
34
34
|
/** Whether this exact call always needs a person's approval; see {@link ToolDefinition.requiresApproval}. */
|
|
35
35
|
requiresApproval?: boolean | ((input: z.infer<S>) => boolean)
|
|
36
|
+
/** What the auto-mode classifier is shown of this call; see {@link ToolDefinition.classifierInput}. */
|
|
37
|
+
classifierInput?: (input: z.infer<S>) => string
|
|
36
38
|
/** Free-form filtering data, never sent to the model; see {@link ToolDefinition.metadata}. */
|
|
37
39
|
metadata?: Readonly<Record<string, unknown>>
|
|
38
40
|
/** Batch ordering boundary; see {@link ToolDefinition.executionBarrier}. */
|
|
@@ -186,6 +188,7 @@ export function defineTool<S extends z.ZodType>(
|
|
|
186
188
|
: () => options.requiresApproval as boolean,
|
|
187
189
|
}
|
|
188
190
|
: {}),
|
|
191
|
+
...(options.classifierInput !== undefined ? { classifierInput: options.classifierInput } : {}),
|
|
189
192
|
...(options.metadata !== undefined ? { metadata: options.metadata } : {}),
|
|
190
193
|
|
|
191
194
|
async execute(input: TInput, context: ToolContext): Promise<ToolResult> {
|
package/src/types/acp/index.ts
CHANGED
|
@@ -43,6 +43,13 @@ export interface AcpInitializeResult {
|
|
|
43
43
|
readonly promptAttachments?: boolean
|
|
44
44
|
/** True only when this host applies captured effort and review settings per prompt. */
|
|
45
45
|
readonly promptOptions?: boolean
|
|
46
|
+
/**
|
|
47
|
+
* The review modes beyond the original five (`prompt`, `plan`,
|
|
48
|
+
* `accept-edits`, `auto`, `strict`) that this host applies per prompt:
|
|
49
|
+
* `bypass` and `classify`, when it supports them. Absent: it accepts only
|
|
50
|
+
* the original five, and refuses the others before a turn starts.
|
|
51
|
+
*/
|
|
52
|
+
readonly promptPermissionModes?: readonly ('bypass' | 'classify')[]
|
|
46
53
|
/** Optional, explicitly installed Namzu host methods; core ACP peers can ignore them. */
|
|
47
54
|
readonly extensions?: readonly string[]
|
|
48
55
|
readonly protocolVersion: number
|
|
@@ -119,6 +119,14 @@ export interface AnalysisFunction {
|
|
|
119
119
|
readonly id: number
|
|
120
120
|
/** `null` for an anonymous function with nothing to infer a name from. */
|
|
121
121
|
readonly name: string | null
|
|
122
|
+
/**
|
|
123
|
+
* `name` qualified by what holds it, when it is a member: `Svc.init` for a
|
|
124
|
+
* method of the class `Svc` (a class expression assigned to a variable takes
|
|
125
|
+
* the variable's name), `obj.run` for a method of an object literal assigned
|
|
126
|
+
* to `obj`. Absent for a function that is not a member. `functionSource`
|
|
127
|
+
* accepts either spelling.
|
|
128
|
+
*/
|
|
129
|
+
readonly qualifiedName?: string
|
|
122
130
|
readonly kind: AnalysisFunctionKind
|
|
123
131
|
/** Absolute path. */
|
|
124
132
|
readonly path: string
|
|
@@ -136,6 +144,17 @@ export interface AnalysisSourceSlice {
|
|
|
136
144
|
readonly chars: number
|
|
137
145
|
/** `true` when `text` is shorter than `chars` because of the size cap. */
|
|
138
146
|
readonly truncated: boolean
|
|
147
|
+
/**
|
|
148
|
+
* Present when the cut was made around a position rather than at the end:
|
|
149
|
+
* `text` is the function's first `headerChars` characters, a marker for
|
|
150
|
+
* what was left out, then the file's own characters `from` to `to`, which
|
|
151
|
+
* contain the position asked about. `from` and `to` are offsets into the file.
|
|
152
|
+
*/
|
|
153
|
+
readonly window?: {
|
|
154
|
+
readonly headerChars: number
|
|
155
|
+
readonly from: number
|
|
156
|
+
readonly to: number
|
|
157
|
+
}
|
|
139
158
|
}
|
|
140
159
|
|
|
141
160
|
export interface IndexRequest extends AnalysisCallOptions {
|
|
@@ -222,6 +241,11 @@ export interface EnclosingFunctionRequest extends AnalysisCallOptions {
|
|
|
222
241
|
export interface AnalysisEnclosingFunction {
|
|
223
242
|
/** `null` when the position is not inside any function — a real answer. */
|
|
224
243
|
readonly function: AnalysisFunction | null
|
|
244
|
+
/**
|
|
245
|
+
* When `function` is `null` because the position is in a class body outside
|
|
246
|
+
* every method (a field initializer, a `static {}` block): that class.
|
|
247
|
+
*/
|
|
248
|
+
readonly inClass?: string
|
|
225
249
|
/** The function's parents, innermost first. */
|
|
226
250
|
readonly parents: readonly AnalysisFunction[]
|
|
227
251
|
readonly source?: AnalysisSourceSlice
|
|
@@ -235,6 +259,11 @@ export interface FunctionSourceRequest extends AnalysisPaging {
|
|
|
235
259
|
readonly path?: string
|
|
236
260
|
readonly name?: string
|
|
237
261
|
readonly maxChars?: number
|
|
262
|
+
/**
|
|
263
|
+
* A position in the function. When the source is longer than `maxChars`, the
|
|
264
|
+
* slice is cut around it instead of at the end, and always contains it.
|
|
265
|
+
*/
|
|
266
|
+
readonly around?: { readonly line: number; readonly column?: number }
|
|
238
267
|
}
|
|
239
268
|
|
|
240
269
|
export interface AnalysisFunctionSource {
|
|
@@ -245,7 +274,11 @@ export interface AnalysisFunctionSource {
|
|
|
245
274
|
|
|
246
275
|
export interface CallersOfRequest extends AnalysisPaging {
|
|
247
276
|
readonly root: string
|
|
248
|
-
/**
|
|
277
|
+
/**
|
|
278
|
+
* The callee text: `foo`, `obj.foo`, `this.foo`. A method's qualified name
|
|
279
|
+
* (`Svc.init`, as `functionSource` shows it) finds `this.init()` inside
|
|
280
|
+
* `Svc` as a resolved call, and every other `<expr>.init()` as `text-only`.
|
|
281
|
+
*/
|
|
249
282
|
readonly name: string
|
|
250
283
|
/**
|
|
251
284
|
* `file` looks in `path` only and is the default, because a callee matched
|
|
@@ -266,8 +299,19 @@ export interface AnalysisCallSite {
|
|
|
266
299
|
readonly line: number
|
|
267
300
|
readonly column: number
|
|
268
301
|
readonly callee: string
|
|
269
|
-
|
|
302
|
+
/**
|
|
303
|
+
* `call-via` is `f.call(…)`, `f.apply(…)` or `f.bind(…)` of the callee (a
|
|
304
|
+
* use, not necessarily a call); `tagged` is a tagged template.
|
|
305
|
+
*/
|
|
306
|
+
readonly kind: 'call' | 'new' | 'call-via' | 'tagged'
|
|
270
307
|
readonly argumentCount: number
|
|
308
|
+
/**
|
|
309
|
+
* Only when the request named a method by its qualified name: `resolved`
|
|
310
|
+
* for `this.m()` inside the class that declares it and for the qualified
|
|
311
|
+
* text itself, `text-only` for any other receiver, which may be an
|
|
312
|
+
* unrelated method of the same name.
|
|
313
|
+
*/
|
|
314
|
+
readonly resolution?: 'resolved' | 'text-only'
|
|
271
315
|
/** The function the call sits in; absent at the top level of a file. */
|
|
272
316
|
readonly caller?: AnalysisFunction
|
|
273
317
|
/** See {@link AnalysisLiteralHit.stale}. */
|
|
@@ -290,7 +334,11 @@ export type AnalysisModuleEdge =
|
|
|
290
334
|
}
|
|
291
335
|
| {
|
|
292
336
|
readonly direction: 'export'
|
|
293
|
-
|
|
337
|
+
/**
|
|
338
|
+
* `type` is a type-only export, `commonjs` is `module.exports` or
|
|
339
|
+
* `exports.x`, `assign` is `export =`.
|
|
340
|
+
*/
|
|
341
|
+
readonly kind: 'named' | 'default' | 'reexport' | 'star' | 'type' | 'commonjs' | 'assign'
|
|
294
342
|
readonly name: string
|
|
295
343
|
readonly source?: string
|
|
296
344
|
readonly line: number
|