@namzu/sdk 52.2.0 → 52.4.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 +20 -0
- 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/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/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/pals/channels/routing.d.ts +1 -1
- package/dist/pals/channels/schema.d.ts +7 -7
- package/dist/pals/communication/ingress-schema.d.ts +32 -32
- 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 +2 -2
- 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 +6 -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 +175 -1
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +267 -19
- 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/executor.d.ts +9 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +6 -0
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +16 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +93 -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 +134 -0
- package/dist/runtime/query/steering.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +4 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +4 -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/index.d.ts +1 -0
- package/dist/tools/builtins/index.d.ts.map +1 -1
- package/dist/tools/builtins/index.js +1 -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/monitor.d.ts +58 -0
- package/dist/tools/builtins/monitor.d.ts.map +1 -0
- package/dist/tools/builtins/monitor.js +306 -0
- package/dist/tools/builtins/monitor.js.map +1 -0
- 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/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/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 +2 -2
- 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 +60 -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/bridge/a2a/mapper.ts +3 -0
- package/src/bridge/sse/mapper.ts +18 -0
- package/src/connector/web/guarded-fetch.ts +41 -0
- package/src/contracts/session/api.ts +1 -0
- package/src/index.ts +23 -0
- package/src/process/kill-tree.ts +4 -2
- package/src/public-runtime.ts +4 -1
- package/src/public-tools.ts +14 -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 +437 -23
- package/src/runtime/jobs/websocket-source.ts +438 -0
- package/src/runtime/query/executor.ts +15 -0
- package/src/runtime/query/index.ts +116 -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 +170 -0
- package/src/runtime/query/tooling.ts +8 -0
- package/src/runtime/system-events.ts +17 -0
- package/src/tools/builtins/bash.ts +3 -2
- package/src/tools/builtins/index.ts +6 -0
- package/src/tools/builtins/job.ts +11 -2
- package/src/tools/builtins/monitor.ts +376 -0
- package/src/turn/reporter.ts +8 -0
- package/src/types/sandbox/index.ts +10 -0
- package/src/types/session/events.ts +31 -4
- package/src/types/session/records.ts +3 -2
- package/src/types/tool/index.ts +51 -1
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns a command's stdout into a few model-sized events.
|
|
3
|
+
*
|
|
4
|
+
* A watcher (`tail -f`, a poll loop, `gh run watch`) can print a line every
|
|
5
|
+
* millisecond, and every line the model is told about costs a context resend.
|
|
6
|
+
* So a monitor does not forward lines, it forwards BATCHES: lines are split,
|
|
7
|
+
* trimmed and truncated here, held for a short fixed window, and delivered
|
|
8
|
+
* together, and the batches themselves are rationed by a token bucket. A
|
|
9
|
+
* source that outruns the ration is first told so, then stopped, because a
|
|
10
|
+
* model that is flooded stops reading and one that is silenced does not know
|
|
11
|
+
* to look.
|
|
12
|
+
*
|
|
13
|
+
* Pure: no process, no registry, no model. The clock and the timer are
|
|
14
|
+
* injectable (and default to the globals read at call time, so fake timers
|
|
15
|
+
* work without any injection), which is what lets the tests advance a window
|
|
16
|
+
* by calling a function rather than by waiting for one.
|
|
17
|
+
*
|
|
18
|
+
* The constants and the wording come from the reference implementation of this
|
|
19
|
+
* tool; deviations are named where they are made.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** A longer line keeps its first this-many characters. */
|
|
23
|
+
export const MONITOR_LINE_MAX_CHARS = 500
|
|
24
|
+
/** A joined batch longer than this keeps its first this-many characters. */
|
|
25
|
+
export const MONITOR_BATCH_MAX_CHARS = 3000
|
|
26
|
+
/**
|
|
27
|
+
* The first buffered line arms ONE timer for this long; everything that
|
|
28
|
+
* arrives before it fires rides in the same batch. A fixed window, not a
|
|
29
|
+
* debounce: a source that never pauses still gets a batch every window
|
|
30
|
+
* instead of never.
|
|
31
|
+
*/
|
|
32
|
+
export const MONITOR_BATCH_WINDOW_MS = 200
|
|
33
|
+
/** A partial line (no newline yet) keeps its tail past this many bytes. */
|
|
34
|
+
export const MONITOR_PARTIAL_LINE_MAX_BYTES = 1024 * 1024
|
|
35
|
+
/** One token per BATCH, not per line. */
|
|
36
|
+
export const MONITOR_BUCKET_CAPACITY = 10
|
|
37
|
+
export const MONITOR_BUCKET_REFILL_MS = 2000
|
|
38
|
+
/** Suppression older than this, with another batch dropped, stops the monitor. */
|
|
39
|
+
export const MONITOR_SUPPRESSION_LIMIT_MS = 30_000
|
|
40
|
+
/** The effective ceiling on a monitor's life; a host may lower it, never raise it past the schema. */
|
|
41
|
+
export const MONITOR_TIMEOUT_CAP_MS = 1_800_000
|
|
42
|
+
export const MONITOR_TIMEOUT_DEFAULT_MS = 300_000
|
|
43
|
+
export const MONITOR_TIMEOUT_MIN_MS = 1000
|
|
44
|
+
/** The schema ceiling; anything between the cap and this is clamped, not refused. */
|
|
45
|
+
export const MONITOR_TIMEOUT_MAX_MS = 3_600_000
|
|
46
|
+
/** One notification handed to the model is cut to this many characters, in the middle. */
|
|
47
|
+
export const MONITOR_NOTIFICATION_MAX_CHARS = 100_000
|
|
48
|
+
/**
|
|
49
|
+
* One event's content is shrunk to this many characters BEFORE it is framed,
|
|
50
|
+
* so the notification cap never has to cut inside an envelope. A batch is at
|
|
51
|
+
* most 3000, so this only matters to a notice or a caller that outgrew that.
|
|
52
|
+
*/
|
|
53
|
+
export const MONITOR_EVENT_CONTENT_MAX_CHARS = 20_000
|
|
54
|
+
|
|
55
|
+
const LINE_TRUNCATED = '...(truncated)'
|
|
56
|
+
const BATCH_TRUNCATED = '\n...(truncated)'
|
|
57
|
+
|
|
58
|
+
/** `30m`, `5m`, `90s`: whole minutes as minutes, anything else in seconds. */
|
|
59
|
+
export function formatMonitorDuration(ms: number): string {
|
|
60
|
+
const seconds = Math.max(1, Math.round(ms / 1000))
|
|
61
|
+
return seconds % 60 === 0 ? `${seconds / 60}m` : `${seconds}s`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function suppressionNotice(count: number): string {
|
|
65
|
+
return `[${count} events suppressed — output rate too high. Consider stopping this monitor and starting it with a more selective command.]`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function tooMuchOutputNotice(count: number, overMs: number): string {
|
|
69
|
+
return `[Monitor stopped — too much output (${count} events suppressed over ${formatMonitorDuration(overMs)}). Restart it with a more selective command.]`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function expiryNotice(ms: number, delivered: number): string {
|
|
73
|
+
return delivered > 0
|
|
74
|
+
? `[Monitor expired after ${formatMonitorDuration(ms)} with ${delivered} events delivered. Start it again if you still need the watch.]`
|
|
75
|
+
: `[Monitor expired after ${formatMonitorDuration(ms)} with no events delivered. Start it again if you still need the watch — and widen the filter if silence was unexpected.]`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface MonitorStreamStats {
|
|
79
|
+
/** Batches handed to `onBatch`. */
|
|
80
|
+
readonly eventsDelivered: number
|
|
81
|
+
/** Batches the bucket dropped, over the monitor's whole life. */
|
|
82
|
+
readonly suppressed: number
|
|
83
|
+
/** Characters pushed in, before any trimming: zero means the command never wrote to stdout. */
|
|
84
|
+
readonly stdoutChars: number
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface MonitorStreamOptions {
|
|
88
|
+
/** One batch of lines, joined with `\n`. */
|
|
89
|
+
onBatch(text: string): void
|
|
90
|
+
/** Housekeeping text from the stream itself (the suppression notice). Not rationed. */
|
|
91
|
+
onNotice(text: string): void
|
|
92
|
+
/**
|
|
93
|
+
* The source was suppressed for too long and another batch was dropped. The
|
|
94
|
+
* stream is closed before this is called; stopping the process is the
|
|
95
|
+
* caller's.
|
|
96
|
+
*/
|
|
97
|
+
onOverflow(info: { readonly suppressed: number; readonly overMs: number }): void
|
|
98
|
+
now?: () => number
|
|
99
|
+
setTimer?: (callback: () => void, ms: number) => unknown
|
|
100
|
+
clearTimer?: (handle: unknown) => void
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface MonitorStream {
|
|
104
|
+
/** Feed raw stdout text; chunks may split a line anywhere. */
|
|
105
|
+
push(text: string): void
|
|
106
|
+
/** Emit the pending batch now, without the partial line. For expiry. */
|
|
107
|
+
flush(): void
|
|
108
|
+
/** The end of the stream: the partial line counts as a line, then everything pending is emitted. */
|
|
109
|
+
drain(): void
|
|
110
|
+
/** Stop for good. Whatever is pending is discarded. */
|
|
111
|
+
close(): void
|
|
112
|
+
stats(): MonitorStreamStats
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The tail of `text` that fits `maxBytes`, starting on a whole code point. */
|
|
116
|
+
function keepTail(text: string, maxBytes: number): string {
|
|
117
|
+
const bytes = Buffer.from(text)
|
|
118
|
+
if (bytes.length <= maxBytes) return text
|
|
119
|
+
let start = bytes.length - maxBytes
|
|
120
|
+
while (start < bytes.length && ((bytes[start] ?? 0) & 0xc0) === 0x80) start++
|
|
121
|
+
return bytes.subarray(start).toString('utf8')
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createMonitorStream(options: MonitorStreamOptions): MonitorStream {
|
|
125
|
+
const now = options.now ?? (() => Date.now())
|
|
126
|
+
const setTimer =
|
|
127
|
+
options.setTimer ??
|
|
128
|
+
((callback: () => void, ms: number) => {
|
|
129
|
+
const handle = setTimeout(callback, ms)
|
|
130
|
+
// A pending batch must not keep a process alive; the job's own exit
|
|
131
|
+
// drains it.
|
|
132
|
+
handle.unref?.()
|
|
133
|
+
return handle
|
|
134
|
+
})
|
|
135
|
+
const clearTimer =
|
|
136
|
+
options.clearTimer ?? ((handle: unknown) => clearTimeout(handle as NodeJS.Timeout))
|
|
137
|
+
|
|
138
|
+
let closed = false
|
|
139
|
+
let partial = ''
|
|
140
|
+
let lines: string[] = []
|
|
141
|
+
let timer: unknown
|
|
142
|
+
let timerArmed = false
|
|
143
|
+
|
|
144
|
+
let tokens = MONITOR_BUCKET_CAPACITY
|
|
145
|
+
let refilledAt = now()
|
|
146
|
+
|
|
147
|
+
let eventsDelivered = 0
|
|
148
|
+
let suppressedTotal = 0
|
|
149
|
+
let stdoutChars = 0
|
|
150
|
+
|
|
151
|
+
// The suppression tracker. `count` is what the auto-stop reports; `unannounced`
|
|
152
|
+
// is what the next notice reports. They differ after a notice, because the
|
|
153
|
+
// tracker outlives it on purpose: a source that is told and keeps going must
|
|
154
|
+
// still reach the 30 s rule.
|
|
155
|
+
let suppressionStartedAt: number | undefined
|
|
156
|
+
let lastDropAt = 0
|
|
157
|
+
let count = 0
|
|
158
|
+
let unannounced = 0
|
|
159
|
+
let noticeEmitted = false
|
|
160
|
+
|
|
161
|
+
const refill = (at: number): void => {
|
|
162
|
+
const elapsed = at - refilledAt
|
|
163
|
+
if (elapsed <= 0) return
|
|
164
|
+
tokens = Math.min(MONITOR_BUCKET_CAPACITY, tokens + elapsed / MONITOR_BUCKET_REFILL_MS)
|
|
165
|
+
refilledAt = at
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const deliver = (batch: string): void => {
|
|
169
|
+
const at = now()
|
|
170
|
+
// Reset only after a notice went out AND the source has been quiet for
|
|
171
|
+
// three refills. Resetting on quiet alone would let a source that is
|
|
172
|
+
// dropped every other window never reach the stop rule.
|
|
173
|
+
if (noticeEmitted && at - lastDropAt > 3 * MONITOR_BUCKET_REFILL_MS) {
|
|
174
|
+
suppressionStartedAt = undefined
|
|
175
|
+
count = 0
|
|
176
|
+
unannounced = 0
|
|
177
|
+
noticeEmitted = false
|
|
178
|
+
}
|
|
179
|
+
refill(at)
|
|
180
|
+
if (tokens >= 1) {
|
|
181
|
+
tokens -= 1
|
|
182
|
+
if (unannounced > 0) {
|
|
183
|
+
options.onNotice(suppressionNotice(unannounced))
|
|
184
|
+
unannounced = 0
|
|
185
|
+
noticeEmitted = true
|
|
186
|
+
}
|
|
187
|
+
eventsDelivered++
|
|
188
|
+
options.onBatch(batch)
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
// Whether the rule fires is decided by when suppression BEGAN, before
|
|
192
|
+
// this drop is counted, and the count reported includes it.
|
|
193
|
+
const began = suppressionStartedAt
|
|
194
|
+
suppressionStartedAt ??= at
|
|
195
|
+
lastDropAt = at
|
|
196
|
+
suppressedTotal++
|
|
197
|
+
count++
|
|
198
|
+
unannounced++
|
|
199
|
+
if (began !== undefined && at - began > MONITOR_SUPPRESSION_LIMIT_MS) {
|
|
200
|
+
stop()
|
|
201
|
+
options.onOverflow({ suppressed: count, overMs: at - began })
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const emitPending = (): void => {
|
|
206
|
+
if (timerArmed) {
|
|
207
|
+
clearTimer(timer)
|
|
208
|
+
timerArmed = false
|
|
209
|
+
}
|
|
210
|
+
if (lines.length === 0) return
|
|
211
|
+
let batch = lines.join('\n')
|
|
212
|
+
lines = []
|
|
213
|
+
if (batch.length > MONITOR_BATCH_MAX_CHARS) {
|
|
214
|
+
batch = batch.slice(0, MONITOR_BATCH_MAX_CHARS) + BATCH_TRUNCATED
|
|
215
|
+
}
|
|
216
|
+
deliver(batch)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const addLine = (raw: string): void => {
|
|
220
|
+
const line = raw.trim()
|
|
221
|
+
if (line.length === 0) return
|
|
222
|
+
lines.push(
|
|
223
|
+
line.length > MONITOR_LINE_MAX_CHARS
|
|
224
|
+
? line.slice(0, MONITOR_LINE_MAX_CHARS) + LINE_TRUNCATED
|
|
225
|
+
: line,
|
|
226
|
+
)
|
|
227
|
+
if (!timerArmed) {
|
|
228
|
+
timerArmed = true
|
|
229
|
+
timer = setTimer(() => {
|
|
230
|
+
timerArmed = false
|
|
231
|
+
if (!closed) emitPending()
|
|
232
|
+
}, MONITOR_BATCH_WINDOW_MS)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const stop = (): void => {
|
|
237
|
+
closed = true
|
|
238
|
+
if (timerArmed) {
|
|
239
|
+
clearTimer(timer)
|
|
240
|
+
timerArmed = false
|
|
241
|
+
}
|
|
242
|
+
lines = []
|
|
243
|
+
partial = ''
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
push(text) {
|
|
248
|
+
if (closed || text.length === 0) return
|
|
249
|
+
stdoutChars += text.length
|
|
250
|
+
const buffered = partial + text
|
|
251
|
+
const parts = buffered.split('\n')
|
|
252
|
+
partial = keepTail(parts.pop() ?? '', MONITOR_PARTIAL_LINE_MAX_BYTES)
|
|
253
|
+
for (const part of parts) addLine(part)
|
|
254
|
+
},
|
|
255
|
+
flush() {
|
|
256
|
+
if (closed) return
|
|
257
|
+
emitPending()
|
|
258
|
+
},
|
|
259
|
+
drain() {
|
|
260
|
+
if (closed) return
|
|
261
|
+
const rest = partial
|
|
262
|
+
partial = ''
|
|
263
|
+
addLine(rest)
|
|
264
|
+
emitPending()
|
|
265
|
+
},
|
|
266
|
+
close: stop,
|
|
267
|
+
stats: () => ({ eventsDelivered, suppressed: suppressedTotal, stdoutChars }),
|
|
268
|
+
}
|
|
269
|
+
}
|