@namzu/sdk 52.1.0 → 52.3.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 +16 -0
- package/README.md +1 -1
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +3 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +18 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/connector/tools/adapter.d.ts +2 -2
- package/dist/contracts/a2a.d.ts +92 -92
- package/dist/contracts/session/api.d.ts +1 -1
- package/dist/contracts/session/api.d.ts.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/manager/resident/learning.d.ts +6 -6
- package/dist/manager/resident/store.d.ts +4 -4
- package/dist/pals/activity/subscription-schema.d.ts +48 -48
- package/dist/pals/channels/routing.d.ts +1 -1
- package/dist/pals/channels/schema.d.ts +7 -7
- package/dist/pals/communication/ingress-schema.d.ts +224 -224
- package/dist/pals/communication/schema.d.ts +18 -18
- package/dist/peers/protocol.d.ts +36 -36
- package/dist/public-runtime.d.ts +2 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +3 -1
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +7 -1
- package/dist/public-tools.js.map +1 -1
- package/dist/runtime/jobs/awaited-jobs.d.ts +59 -2
- package/dist/runtime/jobs/awaited-jobs.d.ts.map +1 -1
- package/dist/runtime/jobs/awaited-jobs.js +108 -5
- package/dist/runtime/jobs/awaited-jobs.js.map +1 -1
- package/dist/runtime/jobs/monitor-stream.d.ts +96 -0
- package/dist/runtime/jobs/monitor-stream.d.ts.map +1 -0
- package/dist/runtime/jobs/monitor-stream.js +221 -0
- package/dist/runtime/jobs/monitor-stream.js.map +1 -0
- package/dist/runtime/jobs/registry.d.ts +96 -0
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +203 -20
- package/dist/runtime/jobs/registry.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +10 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +7 -0
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +18 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +94 -3
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/outstanding-work.d.ts +14 -0
- package/dist/runtime/query/iteration/outstanding-work.d.ts.map +1 -1
- package/dist/runtime/query/iteration/outstanding-work.js +64 -3
- package/dist/runtime/query/iteration/outstanding-work.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +9 -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 +11 -2
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/release-turn.d.ts.map +1 -1
- package/dist/runtime/query/release-turn.js +35 -0
- package/dist/runtime/query/release-turn.js.map +1 -1
- package/dist/runtime/query/steering.d.ts +35 -0
- package/dist/runtime/query/steering.d.ts.map +1 -1
- package/dist/runtime/query/steering.js +121 -0
- package/dist/runtime/query/steering.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +5 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +5 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/runtime/system-events.d.ts +9 -0
- package/dist/runtime/system-events.d.ts.map +1 -1
- package/dist/runtime/system-events.js +12 -0
- package/dist/runtime/system-events.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts +3 -0
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +3 -2
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/browser.d.ts +4 -4
- package/dist/tools/builtins/code-analysis.d.ts +49 -0
- package/dist/tools/builtins/code-analysis.d.ts.map +1 -0
- package/dist/tools/builtins/code-analysis.js +409 -0
- package/dist/tools/builtins/code-analysis.js.map +1 -0
- package/dist/tools/builtins/computer-use.d.ts +24 -24
- package/dist/tools/builtins/index.d.ts +2 -0
- package/dist/tools/builtins/index.d.ts.map +1 -1
- package/dist/tools/builtins/index.js +2 -0
- package/dist/tools/builtins/index.js.map +1 -1
- package/dist/tools/builtins/job.d.ts.map +1 -1
- package/dist/tools/builtins/job.js +6 -2
- package/dist/tools/builtins/job.js.map +1 -1
- package/dist/tools/builtins/lsp.d.ts +1 -1
- package/dist/tools/builtins/monitor.d.ts +24 -0
- package/dist/tools/builtins/monitor.d.ts.map +1 -0
- package/dist/tools/builtins/monitor.js +167 -0
- package/dist/tools/builtins/monitor.js.map +1 -0
- package/dist/tools/builtins/skill.d.ts +2 -2
- package/dist/tools/builtins/wait-for-job.d.ts +1 -1
- package/dist/turn/reporter.d.ts.map +1 -1
- package/dist/turn/reporter.js +8 -0
- package/dist/turn/reporter.js.map +1 -1
- package/dist/types/code-analysis/index.d.ts +299 -0
- package/dist/types/code-analysis/index.d.ts.map +1 -0
- package/dist/types/code-analysis/index.js +30 -0
- package/dist/types/code-analysis/index.js.map +1 -0
- package/dist/types/plugin/index.d.ts +6 -6
- package/dist/types/session/events.d.ts +30 -4
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js +6 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/session/records.d.ts +129 -129
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +3 -2
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/tool/index.d.ts +34 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +2 -2
- package/src/bridge/a2a/mapper.ts +3 -0
- package/src/bridge/sse/mapper.ts +18 -0
- package/src/contracts/session/api.ts +1 -0
- package/src/index.ts +23 -0
- package/src/public-runtime.ts +28 -0
- package/src/public-tools.ts +16 -0
- package/src/runtime/jobs/awaited-jobs.ts +127 -5
- package/src/runtime/jobs/monitor-stream.ts +269 -0
- package/src/runtime/jobs/registry.ts +286 -21
- package/src/runtime/query/executor.ts +17 -0
- package/src/runtime/query/index.ts +119 -3
- package/src/runtime/query/iteration/outstanding-work.ts +64 -3
- package/src/runtime/query/iteration/phases/context.ts +9 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +21 -9
- package/src/runtime/query/release-turn.ts +33 -0
- package/src/runtime/query/steering.ts +144 -0
- package/src/runtime/query/tooling.ts +10 -0
- package/src/runtime/system-events.ts +17 -0
- package/src/tools/builtins/bash.ts +3 -2
- package/src/tools/builtins/code-analysis.ts +486 -0
- package/src/tools/builtins/index.ts +11 -0
- package/src/tools/builtins/job.ts +11 -2
- package/src/tools/builtins/monitor.ts +206 -0
- package/src/turn/reporter.ts +8 -0
- package/src/types/code-analysis/index.ts +325 -0
- package/src/types/session/events.ts +31 -4
- package/src/types/session/records.ts +3 -2
- package/src/types/tool/index.ts +33 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
MONITOR_TIMEOUT_DEFAULT_MS,
|
|
5
|
+
MONITOR_TIMEOUT_MAX_MS,
|
|
6
|
+
MONITOR_TIMEOUT_MIN_MS,
|
|
7
|
+
formatMonitorDuration,
|
|
8
|
+
} from '../../runtime/jobs/monitor-stream.js'
|
|
9
|
+
import {
|
|
10
|
+
bashToolDialect,
|
|
11
|
+
hostCommandShell,
|
|
12
|
+
isWindowsBash,
|
|
13
|
+
rewriteWindowsNullRedirect,
|
|
14
|
+
} from '../command-shell.js'
|
|
15
|
+
import { defineTool } from '../defineTool.js'
|
|
16
|
+
import {
|
|
17
|
+
DISABLE_BACKGROUND_TASKS_ENV,
|
|
18
|
+
backgroundTasksDisabled,
|
|
19
|
+
isDangerousCommand,
|
|
20
|
+
} from './bash.js'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Watch a command's output while the model keeps working.
|
|
24
|
+
*
|
|
25
|
+
* `bash run_in_background` plus `wait_for_job` answers "tell me when this
|
|
26
|
+
* finishes", and nothing answered "tell me when something happens in it": a
|
|
27
|
+
* log line, a failed check, a changed status. The only alternative was a
|
|
28
|
+
* polling loop, which spends a full context resend per look and is silent
|
|
29
|
+
* between looks. This tool starts the command as a job the registry owns and
|
|
30
|
+
* turns each line it prints on stdout into an event the model reads on its
|
|
31
|
+
* next tool result (or wakes it, when it has stopped) — see
|
|
32
|
+
* `runtime/jobs/monitor-stream.ts` for how lines become batches and why a
|
|
33
|
+
* source that prints too much is stopped.
|
|
34
|
+
*
|
|
35
|
+
* It is a different tool from `bash`, and not a flag on it, for the same
|
|
36
|
+
* reason `wait_for_job` is: the model chooses by intent. The permission story
|
|
37
|
+
* is bash's, line for line — a monitor runs a shell command — so the rules
|
|
38
|
+
* read the command the way they read `bash`'s, and it is never read-only. It
|
|
39
|
+
* has no way out of the sandbox: a watch that outlives one approved call must
|
|
40
|
+
* not run outside the boundary that call was approved under.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
const inputSchema = z.object({
|
|
44
|
+
description: z
|
|
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
|
|
52
|
+
.string()
|
|
53
|
+
.min(1)
|
|
54
|
+
// Same rewrite as `bash`, for the same reason: the rules read the line a
|
|
55
|
+
// shell will run.
|
|
56
|
+
.transform((command) =>
|
|
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
|
+
)
|
|
72
|
+
.describe(
|
|
73
|
+
`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.`,
|
|
74
|
+
),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
type MonitorInput = z.infer<typeof inputSchema>
|
|
78
|
+
|
|
79
|
+
export const MONITOR_TOOL_NAME = 'monitor'
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* What a sandbox without `spawnDetached` is told. The wording of `bash`'s own
|
|
83
|
+
* refusal names `run_in_background` and `timeout`, neither of which is this
|
|
84
|
+
* tool's, so it is restated here; the substance is the same and so is the
|
|
85
|
+
* rule: the watch is never run on the host to get around the sandbox.
|
|
86
|
+
*/
|
|
87
|
+
export const MONITOR_CANNOT_DETACH =
|
|
88
|
+
'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
|
+
|
|
90
|
+
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
|
+
export const MonitorTool = defineTool({
|
|
94
|
+
name: MONITOR_TOOL_NAME,
|
|
95
|
+
description: MONITOR_DESCRIPTION,
|
|
96
|
+
inputSchema,
|
|
97
|
+
category: 'shell',
|
|
98
|
+
// Bash's, because it IS a shell command: the gate reads the line, splits
|
|
99
|
+
// chains, and applies the operator's rules to each part.
|
|
100
|
+
commandArgument: 'command',
|
|
101
|
+
commandDialect: bashToolDialect,
|
|
102
|
+
permissions: ['shell_execute'],
|
|
103
|
+
readOnly: false,
|
|
104
|
+
destructive: (input: MonitorInput) => isDangerousCommand(input.command),
|
|
105
|
+
// Starting a monitor registers a job against the owner's cap and its id
|
|
106
|
+
// is then named in later calls; two at once could race the cap, as they
|
|
107
|
+
// can for a background `bash`.
|
|
108
|
+
concurrencySafe: false,
|
|
109
|
+
|
|
110
|
+
presentCall(input) {
|
|
111
|
+
if (typeof input.command !== 'string') return undefined
|
|
112
|
+
return {
|
|
113
|
+
kind: 'generic',
|
|
114
|
+
label: `Monitor · ${typeof input.description === 'string' ? input.description : input.command}`,
|
|
115
|
+
facts: { kind: 'command', command: input.command, background: true },
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
async execute(input, context) {
|
|
120
|
+
if (isDangerousCommand(input.command)) {
|
|
121
|
+
return {
|
|
122
|
+
success: false,
|
|
123
|
+
output: '',
|
|
124
|
+
error: `Dangerous command blocked: "${input.command}"`,
|
|
125
|
+
facts: { kind: 'failure', reason: 'blocked', subject: MONITOR_TOOL_NAME },
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (backgroundTasksDisabled()) {
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
output: '',
|
|
132
|
+
error: `Background jobs are switched off here (${DISABLE_BACKGROUND_TASKS_ENV}), and a monitor is one. Check in the foreground with bash instead.`,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Same rule as background bash: the executor withholds the registry from
|
|
136
|
+
// a sandbox that cannot start a detached process, and this check is for
|
|
137
|
+
// the direct caller who hands both.
|
|
138
|
+
if (context.sandbox && context.sandbox.spawnDetached === undefined) {
|
|
139
|
+
return { success: false, output: '', error: MONITOR_CANNOT_DETACH }
|
|
140
|
+
}
|
|
141
|
+
const jobs = context.backgroundJobs
|
|
142
|
+
if (!jobs) {
|
|
143
|
+
return {
|
|
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.',
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!jobs.startMonitor) {
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
output: '',
|
|
154
|
+
error:
|
|
155
|
+
'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.',
|
|
156
|
+
}
|
|
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) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
output: '',
|
|
174
|
+
error: `${startup.error} (monitor ${job.id} did not run).`,
|
|
175
|
+
data: { jobId: job.id, monitor: true, startFailed: true },
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
success: true,
|
|
180
|
+
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.`,
|
|
181
|
+
data: { jobId: job.id, monitor: true, timeoutMs },
|
|
182
|
+
facts: { kind: 'command', command: input.command, background: true },
|
|
183
|
+
}
|
|
184
|
+
} catch (err) {
|
|
185
|
+
// The per-owner cap, most likely; naming the limit is actionable.
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
output: '',
|
|
189
|
+
error: err instanceof Error ? err.message : String(err),
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The tool, for a host that opts in.
|
|
197
|
+
*
|
|
198
|
+
* Not in `getBuiltinTools()`: a watch needs a host that delivers events and
|
|
199
|
+
* wakes on them, and offering a tool that starts a job nobody will hear from
|
|
200
|
+
* is worse than not offering it. A function rather than a constant so a host
|
|
201
|
+
* can decide per turn, and so it reads like `getCodeAnalysisTools`, beside
|
|
202
|
+
* which a host mounts it deferred.
|
|
203
|
+
*/
|
|
204
|
+
export function getMonitorTools(): (typeof MonitorTool)[] {
|
|
205
|
+
return [MonitorTool]
|
|
206
|
+
}
|
package/src/turn/reporter.ts
CHANGED
|
@@ -309,6 +309,14 @@ export function createTurnReporter(parentLogger?: Logger): TurnReporter {
|
|
|
309
309
|
...(event.exitCode !== undefined ? { 'namzu.jobs.exit_code': event.exitCode } : {}),
|
|
310
310
|
})
|
|
311
311
|
break
|
|
312
|
+
case 'monitor_event':
|
|
313
|
+
log.debug('Monitor event', {
|
|
314
|
+
'namzu.jobs.id': event.jobId,
|
|
315
|
+
'namzu.monitor.event_id': event.eventId,
|
|
316
|
+
'namzu.monitor.kind': event.kind,
|
|
317
|
+
'namzu.monitor.chars': event.text.length,
|
|
318
|
+
})
|
|
319
|
+
break
|
|
312
320
|
case 'memory_consolidated':
|
|
313
321
|
log.info('Turn learnings consolidated into the memory store', {
|
|
314
322
|
'namzu.memory.id': event.memoryId,
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam a semantic code index arrives on.
|
|
3
|
+
*
|
|
4
|
+
* Declared in the SDK and implemented in `@namzu/analysis`, the same split
|
|
5
|
+
* `CodeNavigationProvider` uses with `@namzu/lsp`: the kernel owns the shape a
|
|
6
|
+
* tool programs against, the optional package owns the parser and the store
|
|
7
|
+
* that answer. A host constructs one and puts it on the tool context — there
|
|
8
|
+
* is no discovery step.
|
|
9
|
+
*
|
|
10
|
+
* Kept here rather than imported from `@namzu/analysis` because the
|
|
11
|
+
* dependency direction forbids it: `sdk ← analysis`, never the other way.
|
|
12
|
+
*
|
|
13
|
+
* ## What this answers that `grep` and a language server do not
|
|
14
|
+
*
|
|
15
|
+
* `grep` finds a string and says nothing of the function around it. A
|
|
16
|
+
* language server resolves a symbol the project's own types can see, and has
|
|
17
|
+
* nothing to say about a 40 MB bundle with no types and a hundred
|
|
18
|
+
* single-letter names. This index is for the second case: *which function
|
|
19
|
+
* contains this literal, what is its exact source, and who calls it by name*,
|
|
20
|
+
* over code nobody wrote for you to read.
|
|
21
|
+
*
|
|
22
|
+
* ## Positions
|
|
23
|
+
*
|
|
24
|
+
* Lines and columns are ONE-based, and columns count UTF-16 code units, which
|
|
25
|
+
* is what a JavaScript string index is. A character outside the Basic
|
|
26
|
+
* Multilingual Plane therefore takes two columns. `start` / `end` are the
|
|
27
|
+
* same UTF-16 offsets into the file's text, half-open. A line ends at `\n`.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* How much of the question the answer covers.
|
|
32
|
+
*
|
|
33
|
+
* Two cases, and the second carries its reasons. `partial` is not a failure —
|
|
34
|
+
* the answer is true as far as it goes — but a caller deciding "nothing
|
|
35
|
+
* calls this" from a partial answer is guessing, and the reasons say what is
|
|
36
|
+
* missing (a file that did not parse, a file that changed since it was
|
|
37
|
+
* indexed, a page cut off by `limit`).
|
|
38
|
+
*/
|
|
39
|
+
export type AnalysisCoverage =
|
|
40
|
+
| { readonly status: 'complete' }
|
|
41
|
+
| { readonly status: 'partial'; readonly reasons: readonly string[] }
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The evidence behind an answer, in three parts.
|
|
45
|
+
*
|
|
46
|
+
* - `observed`: what the index read out of the files, stated as fact.
|
|
47
|
+
* - `inferred`: what was concluded rather than read. A callee matched by
|
|
48
|
+
* its TEXT is an inference — the index does no scope analysis, so two
|
|
49
|
+
* unrelated functions called `run` are the same callee to it.
|
|
50
|
+
* - `coverage`: whether the answer is the whole answer.
|
|
51
|
+
*
|
|
52
|
+
* Every successful result carries one. An answer without it reads as
|
|
53
|
+
* certain, and the cases this package exists for are the ones where it is
|
|
54
|
+
* not.
|
|
55
|
+
*/
|
|
56
|
+
export interface AnalysisEvidence {
|
|
57
|
+
readonly observed: readonly string[]
|
|
58
|
+
readonly inferred: readonly string[]
|
|
59
|
+
readonly coverage: AnalysisCoverage
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* What a query answers. Three cases, closed, as with `CodeNavigationResult`:
|
|
64
|
+
* `unsupported` is "this index does not do that", `failed` is "something
|
|
65
|
+
* broke", and neither is an empty page. An empty page means "I looked and
|
|
66
|
+
* there are none", the answer a deletion depends on.
|
|
67
|
+
*/
|
|
68
|
+
export type CodeAnalysisResult<T> =
|
|
69
|
+
| { readonly kind: 'ok'; readonly value: T; readonly evidence: AnalysisEvidence }
|
|
70
|
+
| { readonly kind: 'unsupported'; readonly reason: string }
|
|
71
|
+
| { readonly kind: 'failed'; readonly error: string }
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* One page of a larger answer. `total` is the count across ALL pages, so a
|
|
75
|
+
* caller can tell a complete list from the first slice of a long one;
|
|
76
|
+
* `nextCursor` is absent on the last page and is opaque — pass it back
|
|
77
|
+
* unchanged.
|
|
78
|
+
*/
|
|
79
|
+
export interface AnalysisPage<T> {
|
|
80
|
+
readonly items: readonly T[]
|
|
81
|
+
readonly total: number
|
|
82
|
+
readonly nextCursor?: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* What every request may carry beyond its own fields.
|
|
87
|
+
*
|
|
88
|
+
* `signal` stops the call: an index run in progress is abandoned (what it had
|
|
89
|
+
* already committed stays, so the next call continues from there), and a call
|
|
90
|
+
* still queued behind another never starts. Without it nothing a user does,
|
|
91
|
+
* Esc included, can stop a multi-second index of a large tree.
|
|
92
|
+
*/
|
|
93
|
+
export interface AnalysisCallOptions {
|
|
94
|
+
readonly signal?: AbortSignal
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The paging arguments every list-returning request takes. */
|
|
98
|
+
export interface AnalysisPaging extends AnalysisCallOptions {
|
|
99
|
+
/** Items per page. The provider clamps it to its own maximum. */
|
|
100
|
+
readonly limit?: number
|
|
101
|
+
/** The previous page's `nextCursor`. */
|
|
102
|
+
readonly cursor?: string
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export type AnalysisFunctionKind =
|
|
106
|
+
| 'declaration'
|
|
107
|
+
| 'expression'
|
|
108
|
+
| 'arrow'
|
|
109
|
+
| 'method'
|
|
110
|
+
| 'getter'
|
|
111
|
+
| 'setter'
|
|
112
|
+
| 'constructor'
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A function, located. `id` is valid until its file changes and is then
|
|
116
|
+
* re-issued: do not keep one across an `index` that re-read the file.
|
|
117
|
+
*/
|
|
118
|
+
export interface AnalysisFunction {
|
|
119
|
+
readonly id: number
|
|
120
|
+
/** `null` for an anonymous function with nothing to infer a name from. */
|
|
121
|
+
readonly name: string | null
|
|
122
|
+
readonly kind: AnalysisFunctionKind
|
|
123
|
+
/** Absolute path. */
|
|
124
|
+
readonly path: string
|
|
125
|
+
readonly line: number
|
|
126
|
+
readonly column: number
|
|
127
|
+
readonly endLine: number
|
|
128
|
+
readonly start: number
|
|
129
|
+
readonly end: number
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** A piece of a file's source, with the honesty about a cut. */
|
|
133
|
+
export interface AnalysisSourceSlice {
|
|
134
|
+
readonly text: string
|
|
135
|
+
/** The full length in UTF-16 units, whether or not all of it is in `text`. */
|
|
136
|
+
readonly chars: number
|
|
137
|
+
/** `true` when `text` is shorter than `chars` because of the size cap. */
|
|
138
|
+
readonly truncated: boolean
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface IndexRequest extends AnalysisCallOptions {
|
|
142
|
+
/** Absolute directory the index belongs to. */
|
|
143
|
+
readonly root: string
|
|
144
|
+
/**
|
|
145
|
+
* Index only this file or directory under `root`, instead of all of it.
|
|
146
|
+
*
|
|
147
|
+
* The way to make progress on a root too big to index whole (a home
|
|
148
|
+
* directory): a call that stops at its limits says so, and the next step is
|
|
149
|
+
* a narrower `scope`. Work from earlier calls is kept.
|
|
150
|
+
*/
|
|
151
|
+
readonly scope?: string
|
|
152
|
+
/** Re-read every file, ignoring size / mtime / hash. */
|
|
153
|
+
readonly force?: boolean
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface IndexSummary {
|
|
157
|
+
readonly root: string
|
|
158
|
+
/** Whether the index lives on disk; `false` for an in-memory fallback. */
|
|
159
|
+
readonly persisted: boolean
|
|
160
|
+
readonly files: {
|
|
161
|
+
readonly indexed: number
|
|
162
|
+
/** Parsed this call (new or changed). */
|
|
163
|
+
readonly parsed: number
|
|
164
|
+
/** Seen and left alone: same size and mtime, or same content hash. */
|
|
165
|
+
readonly unchanged: number
|
|
166
|
+
readonly removed: number
|
|
167
|
+
readonly skipped: number
|
|
168
|
+
readonly withParseErrors: number
|
|
169
|
+
}
|
|
170
|
+
/** Why files were skipped, as reason → count. */
|
|
171
|
+
readonly skippedReasons: Readonly<Record<string, number>>
|
|
172
|
+
readonly functions: number
|
|
173
|
+
readonly strings: number
|
|
174
|
+
readonly calls: number
|
|
175
|
+
readonly durationMs: number
|
|
176
|
+
/**
|
|
177
|
+
* Why this call stopped before covering `scope`, when it did: a time or
|
|
178
|
+
* directory limit. Present means the index is partial; the text names the
|
|
179
|
+
* next step.
|
|
180
|
+
*/
|
|
181
|
+
readonly truncated?: string
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface FindLiteralRequest extends AnalysisPaging {
|
|
185
|
+
readonly root: string
|
|
186
|
+
/** The substring to look for inside string and template literals. */
|
|
187
|
+
readonly text: string
|
|
188
|
+
/** Absolute file or directory to restrict the search to. */
|
|
189
|
+
readonly path?: string
|
|
190
|
+
/** Default `true`. */
|
|
191
|
+
readonly caseSensitive?: boolean
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface AnalysisLiteralHit {
|
|
195
|
+
readonly path: string
|
|
196
|
+
readonly line: number
|
|
197
|
+
readonly column: number
|
|
198
|
+
readonly start: number
|
|
199
|
+
readonly end: number
|
|
200
|
+
readonly kind: 'string' | 'template' | 'key'
|
|
201
|
+
/** The literal's value, cut to a preview. */
|
|
202
|
+
readonly value: string
|
|
203
|
+
readonly valueTruncated: boolean
|
|
204
|
+
/** The innermost function around it; absent at the top level of a file. */
|
|
205
|
+
readonly function?: AnalysisFunction
|
|
206
|
+
/**
|
|
207
|
+
* Present when the file this came from no longer matches the index: the
|
|
208
|
+
* position may be off (`changed`) or the file may be gone (`deleted`).
|
|
209
|
+
*/
|
|
210
|
+
readonly stale?: 'changed' | 'deleted'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface EnclosingFunctionRequest extends AnalysisCallOptions {
|
|
214
|
+
readonly root: string
|
|
215
|
+
readonly path: string
|
|
216
|
+
readonly line: number
|
|
217
|
+
readonly column?: number
|
|
218
|
+
/** Cap on the source slice, in UTF-16 units. */
|
|
219
|
+
readonly maxChars?: number
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface AnalysisEnclosingFunction {
|
|
223
|
+
/** `null` when the position is not inside any function — a real answer. */
|
|
224
|
+
readonly function: AnalysisFunction | null
|
|
225
|
+
/** The function's parents, innermost first. */
|
|
226
|
+
readonly parents: readonly AnalysisFunction[]
|
|
227
|
+
readonly source?: AnalysisSourceSlice
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface FunctionSourceRequest extends AnalysisPaging {
|
|
231
|
+
readonly root: string
|
|
232
|
+
/** A function id from an earlier answer. */
|
|
233
|
+
readonly id?: number
|
|
234
|
+
/** Alternatively, a file and a name; several functions can share one. */
|
|
235
|
+
readonly path?: string
|
|
236
|
+
readonly name?: string
|
|
237
|
+
readonly maxChars?: number
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface AnalysisFunctionSource {
|
|
241
|
+
readonly function: AnalysisFunction
|
|
242
|
+
readonly parents: readonly AnalysisFunction[]
|
|
243
|
+
readonly source: AnalysisSourceSlice
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface CallersOfRequest extends AnalysisPaging {
|
|
247
|
+
readonly root: string
|
|
248
|
+
/** The callee text: `foo`, `obj.foo`, `this.foo`. */
|
|
249
|
+
readonly name: string
|
|
250
|
+
/**
|
|
251
|
+
* `file` looks in `path` only and is the default, because a callee matched
|
|
252
|
+
* by text is far more likely to be the one you mean inside one file than
|
|
253
|
+
* across a bundle of unrelated modules that reuse short names.
|
|
254
|
+
*/
|
|
255
|
+
readonly scope?: 'file' | 'workspace'
|
|
256
|
+
readonly path?: string
|
|
257
|
+
/**
|
|
258
|
+
* `exact` matches the callee text as written. `member` also matches any
|
|
259
|
+
* `<expression>.name`, which finds method calls whose receiver is unknown.
|
|
260
|
+
*/
|
|
261
|
+
readonly match?: 'exact' | 'member'
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface AnalysisCallSite {
|
|
265
|
+
readonly path: string
|
|
266
|
+
readonly line: number
|
|
267
|
+
readonly column: number
|
|
268
|
+
readonly callee: string
|
|
269
|
+
readonly kind: 'call' | 'new'
|
|
270
|
+
readonly argumentCount: number
|
|
271
|
+
/** The function the call sits in; absent at the top level of a file. */
|
|
272
|
+
readonly caller?: AnalysisFunction
|
|
273
|
+
/** See {@link AnalysisLiteralHit.stale}. */
|
|
274
|
+
readonly stale?: 'changed' | 'deleted'
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface ImportsRequest extends AnalysisPaging {
|
|
278
|
+
readonly root: string
|
|
279
|
+
readonly path: string
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export type AnalysisModuleEdge =
|
|
283
|
+
| {
|
|
284
|
+
readonly direction: 'import'
|
|
285
|
+
readonly kind: 'static' | 'type' | 'require' | 'dynamic'
|
|
286
|
+
readonly source: string
|
|
287
|
+
readonly names: readonly string[]
|
|
288
|
+
readonly line: number
|
|
289
|
+
readonly column: number
|
|
290
|
+
}
|
|
291
|
+
| {
|
|
292
|
+
readonly direction: 'export'
|
|
293
|
+
readonly kind: 'named' | 'default' | 'reexport' | 'star'
|
|
294
|
+
readonly name: string
|
|
295
|
+
readonly source?: string
|
|
296
|
+
readonly line: number
|
|
297
|
+
readonly column: number
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface CodeAnalysisProvider {
|
|
301
|
+
/**
|
|
302
|
+
* Build or refresh the index for a directory. Incremental: a file whose
|
|
303
|
+
* size and mtime are unchanged is not read, and one whose content hash is
|
|
304
|
+
* unchanged is not parsed.
|
|
305
|
+
*/
|
|
306
|
+
index(request: IndexRequest): Promise<CodeAnalysisResult<IndexSummary>>
|
|
307
|
+
/** Where a string appears, and the function around each hit. */
|
|
308
|
+
findLiteral(
|
|
309
|
+
request: FindLiteralRequest,
|
|
310
|
+
): Promise<CodeAnalysisResult<AnalysisPage<AnalysisLiteralHit>>>
|
|
311
|
+
/** The function containing a position, with its source and parent chain. */
|
|
312
|
+
enclosingFunction(
|
|
313
|
+
request: EnclosingFunctionRequest,
|
|
314
|
+
): Promise<CodeAnalysisResult<AnalysisEnclosingFunction>>
|
|
315
|
+
/** A function's exact source, by id or by file and name. */
|
|
316
|
+
functionSource(
|
|
317
|
+
request: FunctionSourceRequest,
|
|
318
|
+
): Promise<CodeAnalysisResult<AnalysisPage<AnalysisFunctionSource>>>
|
|
319
|
+
/** Call sites of a name. Matched by text, not by scope; see the evidence. */
|
|
320
|
+
callersOf(request: CallersOfRequest): Promise<CodeAnalysisResult<AnalysisPage<AnalysisCallSite>>>
|
|
321
|
+
/** One file's imports and exports. */
|
|
322
|
+
imports(request: ImportsRequest): Promise<CodeAnalysisResult<AnalysisPage<AnalysisModuleEdge>>>
|
|
323
|
+
/** Close whatever backs it. Not optional: an open database is a held file. */
|
|
324
|
+
dispose(): Promise<void>
|
|
325
|
+
}
|
|
@@ -612,6 +612,28 @@ type CoreSessionEvent =
|
|
|
612
612
|
status: 'exited' | 'killed'
|
|
613
613
|
exitCode?: number
|
|
614
614
|
signal?: string
|
|
615
|
+
/** Present when the job was a monitor: what it watched and, if the registry stopped it, why. */
|
|
616
|
+
monitor?: {
|
|
617
|
+
description: string
|
|
618
|
+
stopReason?: 'expired' | 'too_much_output' | 'stopped'
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* A monitor this session (or the owner it runs under) started has
|
|
623
|
+
* something to say: a batch of its stdout lines, or a notice from the
|
|
624
|
+
* monitor itself (events suppressed, stopped, expired). The model reads it
|
|
625
|
+
* on its next tool result; the host learns it from this, whether or not a
|
|
626
|
+
* turn is running. `eventId` is what a host acknowledges.
|
|
627
|
+
*/
|
|
628
|
+
| {
|
|
629
|
+
type: 'monitor_event'
|
|
630
|
+
sessionId: SessionId
|
|
631
|
+
turnId?: TurnId
|
|
632
|
+
jobId: string
|
|
633
|
+
eventId: string
|
|
634
|
+
description: string
|
|
635
|
+
kind: 'output' | 'notice'
|
|
636
|
+
text: string
|
|
615
637
|
}
|
|
616
638
|
/**
|
|
617
639
|
* What the turn learned was written to the host's memory store: its
|
|
@@ -1229,7 +1251,7 @@ export type ChildSessionLifecycleEvent =
|
|
|
1229
1251
|
| ChildSessionIdledEvent
|
|
1230
1252
|
|
|
1231
1253
|
/**
|
|
1232
|
-
* Discriminated union of every live session event:
|
|
1254
|
+
* Discriminated union of every live session event: 63 type literals.
|
|
1233
1255
|
*
|
|
1234
1256
|
* `type` is the sole discriminator for exhaustive switches; envelope fields
|
|
1235
1257
|
* are additive and never take part in discrimination.
|
|
@@ -1247,21 +1269,26 @@ export type SessionEventListener = (event: SessionEvent) => void | Promise<void>
|
|
|
1247
1269
|
/**
|
|
1248
1270
|
* Event types whose volume makes durable persistence wasteful. They reach the
|
|
1249
1271
|
* live stream and never the session log: the completed message, reasoning
|
|
1250
|
-
* block and tool input carry the same content once.
|
|
1272
|
+
* block and tool input carry the same content once. A `monitor_event` is the
|
|
1273
|
+
* same case: the text the model read is already in the transcript as the
|
|
1274
|
+
* runtime-context message or tool result that carried it, and a noisy watch
|
|
1275
|
+
* would write tens of megabytes an hour of raw process output a second time.
|
|
1276
|
+
* `background_job_exited.monitor` is the durable record of how a watch ended.
|
|
1251
1277
|
*/
|
|
1252
1278
|
export const EPHEMERAL_EVENT_TYPES: ReadonlySet<SessionEventType> = new Set<SessionEventType>([
|
|
1253
1279
|
'text_delta',
|
|
1254
1280
|
'tool_input_delta',
|
|
1255
1281
|
'reasoning_delta',
|
|
1256
1282
|
'tool_progress',
|
|
1283
|
+
'monitor_event',
|
|
1257
1284
|
])
|
|
1258
1285
|
|
|
1259
1286
|
export function isEphemeralEvent(event: { readonly type: string }): boolean {
|
|
1260
1287
|
return EPHEMERAL_EVENT_TYPES.has(event.type as SessionEventType)
|
|
1261
1288
|
}
|
|
1262
1289
|
|
|
1263
|
-
/** The event types a session log records: every literal except the ephemeral
|
|
1290
|
+
/** The event types a session log records: every literal except the ephemeral five. */
|
|
1264
1291
|
export type PersistedSessionEventType = Exclude<
|
|
1265
1292
|
SessionEventType,
|
|
1266
|
-
'text_delta' | 'tool_input_delta' | 'reasoning_delta' | 'tool_progress'
|
|
1293
|
+
'text_delta' | 'tool_input_delta' | 'reasoning_delta' | 'tool_progress' | 'monitor_event'
|
|
1267
1294
|
>
|
|
@@ -648,6 +648,7 @@ const SESSION_EVENT_TYPE_TABLE: Record<SessionEventType, true> = {
|
|
|
648
648
|
turn_resuming: true,
|
|
649
649
|
guardrail_triggered: true,
|
|
650
650
|
background_job_exited: true,
|
|
651
|
+
monitor_event: true,
|
|
651
652
|
memory_consolidated: true,
|
|
652
653
|
turn_completed: true,
|
|
653
654
|
turn_failed: true,
|
|
@@ -687,12 +688,12 @@ const SESSION_EVENT_TYPE_TABLE: Record<SessionEventType, true> = {
|
|
|
687
688
|
child_session_idled: true,
|
|
688
689
|
}
|
|
689
690
|
|
|
690
|
-
/** All
|
|
691
|
+
/** All 63 live event type literals. */
|
|
691
692
|
export const SESSION_EVENT_TYPES: readonly SessionEventType[] = Object.freeze(
|
|
692
693
|
Object.keys(SESSION_EVENT_TYPE_TABLE) as SessionEventType[],
|
|
693
694
|
)
|
|
694
695
|
|
|
695
|
-
/** The 58 event types a session log records. */
|
|
696
|
+
/** The 58 event types a session log records: all 63 but the five ephemeral ones. */
|
|
696
697
|
export const PERSISTED_SESSION_EVENT_TYPES: readonly PersistedSessionEventType[] = Object.freeze(
|
|
697
698
|
SESSION_EVENT_TYPES.filter(
|
|
698
699
|
(type) => !EPHEMERAL_EVENT_TYPES.has(type),
|