@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
|
@@ -10,7 +10,15 @@ import type {
|
|
|
10
10
|
BackgroundJobOutputWaitResult,
|
|
11
11
|
} from '../../types/job/index.js'
|
|
12
12
|
import { subscribeToAbort } from '../../utils/abort.js'
|
|
13
|
+
import { oneMetadataLine } from '../system-events.js'
|
|
13
14
|
import { createBackgroundJobId } from './job-id.js'
|
|
15
|
+
import {
|
|
16
|
+
MONITOR_TIMEOUT_CAP_MS,
|
|
17
|
+
type MonitorStreamStats,
|
|
18
|
+
createMonitorStream,
|
|
19
|
+
expiryNotice,
|
|
20
|
+
tooMuchOutputNotice,
|
|
21
|
+
} from './monitor-stream.js'
|
|
14
22
|
|
|
15
23
|
/**
|
|
16
24
|
* Work that outlives a tool call, owned by whoever started it.
|
|
@@ -57,6 +65,38 @@ export interface BackgroundJob {
|
|
|
57
65
|
readonly recoveryRequired?: boolean
|
|
58
66
|
/** Safe registry diagnostic for an unconfirmed stop; excludes transport secrets. */
|
|
59
67
|
readonly stopError?: string
|
|
68
|
+
/**
|
|
69
|
+
* `monitor` for a job started by the `monitor` tool: its stdout is
|
|
70
|
+
* delivered line by line as events, and it expires. Absent for every
|
|
71
|
+
* other job.
|
|
72
|
+
*/
|
|
73
|
+
readonly kind?: 'monitor'
|
|
74
|
+
/** What the monitor watches, in the model's words; shown with every event. */
|
|
75
|
+
readonly description?: string
|
|
76
|
+
/** When the monitor will be stopped if the command has not ended first (epoch ms). */
|
|
77
|
+
readonly expiresAt?: number
|
|
78
|
+
/**
|
|
79
|
+
* Why a monitor was stopped, when the registry stopped it: `expired` at
|
|
80
|
+
* its deadline, `too_much_output` when the source outran the event
|
|
81
|
+
* ration, `stopped` for a `kill()` asked for from outside. Absent while it
|
|
82
|
+
* runs and when the command ended on its own.
|
|
83
|
+
*/
|
|
84
|
+
readonly stopReason?: 'expired' | 'too_much_output' | 'stopped'
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* One thing a monitor has to say: a batch of its stdout lines, or a notice
|
|
89
|
+
* from the monitor itself (events suppressed, stopped, expired).
|
|
90
|
+
*/
|
|
91
|
+
export interface MonitorEvent {
|
|
92
|
+
/** `${jobId}:${seq}`, unique per registry and stable, so a host can acknowledge it. */
|
|
93
|
+
readonly id: string
|
|
94
|
+
readonly jobId: string
|
|
95
|
+
readonly owner: string
|
|
96
|
+
readonly description: string
|
|
97
|
+
readonly kind: 'output' | 'notice'
|
|
98
|
+
readonly text: string
|
|
99
|
+
readonly at: number
|
|
60
100
|
}
|
|
61
101
|
|
|
62
102
|
export interface BackgroundJobOutput {
|
|
@@ -114,6 +154,13 @@ export interface StartJobParams {
|
|
|
114
154
|
* decoded the way a foreground command's are, not as a Linux guest's.
|
|
115
155
|
*/
|
|
116
156
|
readonly hostOutput?: boolean
|
|
157
|
+
/**
|
|
158
|
+
* Run the command as a monitor: stdout is split into lines and delivered
|
|
159
|
+
* to `onMonitorEvent` listeners in batches, and the job is stopped at
|
|
160
|
+
* `timeoutMs`. Both streams stay in the retained buffer, so `read` still
|
|
161
|
+
* shows everything, stderr included.
|
|
162
|
+
*/
|
|
163
|
+
readonly monitor?: { readonly description: string; readonly timeoutMs: number }
|
|
117
164
|
}
|
|
118
165
|
|
|
119
166
|
export interface BackgroundJobRegistryConfig {
|
|
@@ -212,6 +259,16 @@ interface JobEntry {
|
|
|
212
259
|
exit: Promise<void>
|
|
213
260
|
/** Settles once the process exists or failed to; `error` says which. */
|
|
214
261
|
started: Promise<{ readonly error?: string }>
|
|
262
|
+
/** Present for a monitor. */
|
|
263
|
+
monitor?: MonitorState
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
interface MonitorState {
|
|
267
|
+
readonly stream: ReturnType<typeof createMonitorStream>
|
|
268
|
+
readonly description: string
|
|
269
|
+
readonly timeoutMs: number
|
|
270
|
+
seq: number
|
|
271
|
+
expiryTimer?: ReturnType<typeof setTimeout>
|
|
215
272
|
}
|
|
216
273
|
|
|
217
274
|
/** Whether any process of the group led by `pid` is still alive. */
|
|
@@ -230,6 +287,7 @@ const GROUP_POLL_MS = 250
|
|
|
230
287
|
export class BackgroundJobRegistry {
|
|
231
288
|
private readonly jobs = new Map<string, JobEntry>()
|
|
232
289
|
private readonly exitListeners = new Set<(job: BackgroundJob) => void>()
|
|
290
|
+
private readonly monitorListeners = new Set<(event: MonitorEvent) => void>()
|
|
233
291
|
private outputWaiters = 0
|
|
234
292
|
private readonly outputWaitersByOwner = new Map<string, number>()
|
|
235
293
|
|
|
@@ -256,6 +314,52 @@ export class BackgroundJobRegistry {
|
|
|
256
314
|
}
|
|
257
315
|
}
|
|
258
316
|
|
|
317
|
+
/**
|
|
318
|
+
* Be told what a monitor has to say, whoever owns it: a batch of its
|
|
319
|
+
* stdout lines, or a notice from the monitor itself. Returns the
|
|
320
|
+
* unsubscribe.
|
|
321
|
+
*
|
|
322
|
+
* Every listener hears every monitor's events, whatever their `owner`: a
|
|
323
|
+
* consumer serving one turn or session MUST filter on `event.owner`, as the
|
|
324
|
+
* turn does. A listener that throws is its owner's problem, as in
|
|
325
|
+
* {@link onExit}.
|
|
326
|
+
*/
|
|
327
|
+
onMonitorEvent(listener: (event: MonitorEvent) => void): () => void {
|
|
328
|
+
this.monitorListeners.add(listener)
|
|
329
|
+
return () => {
|
|
330
|
+
this.monitorListeners.delete(listener)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private announceMonitorEvent(entry: JobEntry, kind: MonitorEvent['kind'], text: string): void {
|
|
335
|
+
const monitor = entry.monitor
|
|
336
|
+
if (!monitor) return
|
|
337
|
+
monitor.seq += 1
|
|
338
|
+
const event: MonitorEvent = {
|
|
339
|
+
id: `${entry.record.id}:${monitor.seq}`,
|
|
340
|
+
jobId: entry.record.id,
|
|
341
|
+
owner: entry.record.owner,
|
|
342
|
+
description: monitor.description,
|
|
343
|
+
kind,
|
|
344
|
+
text,
|
|
345
|
+
at: Date.now(),
|
|
346
|
+
}
|
|
347
|
+
for (const listener of this.monitorListeners) {
|
|
348
|
+
try {
|
|
349
|
+
listener(event)
|
|
350
|
+
} catch {
|
|
351
|
+
// A listener that throws is its owner's problem, not the job's.
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** What a monitor's stream has done so far, for the summary of how it ended. */
|
|
357
|
+
monitorStats(id: string): MonitorStreamStats {
|
|
358
|
+
const entry = this.jobs.get(id)
|
|
359
|
+
if (!entry) throw new UnknownBackgroundJobError({ id })
|
|
360
|
+
return entry.monitor?.stream.stats() ?? { eventsDelivered: 0, suppressed: 0, stdoutChars: 0 }
|
|
361
|
+
}
|
|
362
|
+
|
|
259
363
|
/** A failed remote stop never releases ownership or announces a terminal job. */
|
|
260
364
|
private confirmTermination(entry: JobEntry): Promise<void> {
|
|
261
365
|
if (entry.terminationAttempt) return entry.terminationAttempt
|
|
@@ -364,6 +468,32 @@ export class BackgroundJobRegistry {
|
|
|
364
468
|
// it may be remote, and need not emit `spawn` at all.
|
|
365
469
|
if (params.spawn || child.pid !== undefined) settleStarted()
|
|
366
470
|
|
|
471
|
+
// A monitor's stdout goes through a stream that batches and rations it;
|
|
472
|
+
// everything it says reaches listeners as `MonitorEvent`s. The callbacks
|
|
473
|
+
// close over `entry`, which exists by the time any of them can run.
|
|
474
|
+
const monitor: MonitorState | undefined = params.monitor
|
|
475
|
+
? {
|
|
476
|
+
description: params.monitor.description,
|
|
477
|
+
timeoutMs: params.monitor.timeoutMs,
|
|
478
|
+
seq: 0,
|
|
479
|
+
stream: createMonitorStream({
|
|
480
|
+
onBatch: (text) => this.announceMonitorEvent(entry, 'output', text),
|
|
481
|
+
onNotice: (text) => this.announceMonitorEvent(entry, 'notice', text),
|
|
482
|
+
onOverflow: ({ suppressed, overMs }) => {
|
|
483
|
+
// The reason is recorded before the kill, so the exit that
|
|
484
|
+
// follows is described as what it was and not as a stop
|
|
485
|
+
// somebody asked for.
|
|
486
|
+
entry.record = { ...entry.record, stopReason: 'too_much_output' }
|
|
487
|
+
this.announceMonitorEvent(entry, 'notice', tooMuchOutputNotice(suppressed, overMs))
|
|
488
|
+
void this.kill(id).catch(() => {
|
|
489
|
+
// The retained record carries the failure (recoveryRequired).
|
|
490
|
+
})
|
|
491
|
+
},
|
|
492
|
+
}),
|
|
493
|
+
}
|
|
494
|
+
: undefined
|
|
495
|
+
const startedAt = Date.now()
|
|
496
|
+
|
|
367
497
|
let finalizeEntry!: (code: number | null, signal: NodeJS.Signals | null) => void
|
|
368
498
|
const entry: JobEntry = {
|
|
369
499
|
record: {
|
|
@@ -371,8 +501,16 @@ export class BackgroundJobRegistry {
|
|
|
371
501
|
owner: params.owner,
|
|
372
502
|
command: params.command,
|
|
373
503
|
status: 'running',
|
|
374
|
-
startedAt
|
|
504
|
+
startedAt,
|
|
505
|
+
...(monitor
|
|
506
|
+
? {
|
|
507
|
+
kind: 'monitor' as const,
|
|
508
|
+
description: monitor.description,
|
|
509
|
+
expiresAt: startedAt + monitor.timeoutMs,
|
|
510
|
+
}
|
|
511
|
+
: {}),
|
|
375
512
|
},
|
|
513
|
+
...(monitor ? { monitor } : {}),
|
|
376
514
|
child,
|
|
377
515
|
...(started.kill ? { killProcess: started.kill.bind(started) } : {}),
|
|
378
516
|
...(started.terminate ? { terminateProcess: started.terminate.bind(started) } : {}),
|
|
@@ -400,6 +538,15 @@ export class BackgroundJobRegistry {
|
|
|
400
538
|
}
|
|
401
539
|
resolve()
|
|
402
540
|
for (const observer of entry.outputObservers) observer()
|
|
541
|
+
if (entry.monitor) {
|
|
542
|
+
// The last batch is a monitor's last words and must precede
|
|
543
|
+
// the exit: a listener that learns the job ended and THEN
|
|
544
|
+
// gets output would be telling the model about a finished
|
|
545
|
+
// watch after it had been told the watch was over.
|
|
546
|
+
clearTimeout(entry.monitor.expiryTimer)
|
|
547
|
+
entry.monitor.stream.drain()
|
|
548
|
+
entry.monitor.stream.close()
|
|
549
|
+
}
|
|
403
550
|
this.announceExit(entry.record)
|
|
404
551
|
}
|
|
405
552
|
finalizeEntry = finalize
|
|
@@ -467,6 +614,10 @@ export class BackgroundJobRegistry {
|
|
|
467
614
|
entry.buffer += text
|
|
468
615
|
entry.buffer = outputTail(entry.buffer, this.maxBytes).text
|
|
469
616
|
entry.outputChunks.push(chunk)
|
|
617
|
+
// Only stdout is an event. stderr stays readable with `read`, which is
|
|
618
|
+
// where a watcher's diagnostics belong: a model told about every
|
|
619
|
+
// warning line would stop reading the events that matter.
|
|
620
|
+
if (stream === 'stdout') entry.monitor?.stream.push(text)
|
|
470
621
|
const retainedStart = entry.produced - Buffer.byteLength(entry.buffer)
|
|
471
622
|
while (
|
|
472
623
|
entry.outputChunks.length > MAX_CHANNEL_CHUNKS ||
|
|
@@ -497,9 +648,37 @@ export class BackgroundJobRegistry {
|
|
|
497
648
|
child.stderr?.on('data', onData('stderr'))
|
|
498
649
|
|
|
499
650
|
this.jobs.set(id, entry)
|
|
651
|
+
if (monitor) {
|
|
652
|
+
// A monitor has a life. Without a deadline a watch the model forgot
|
|
653
|
+
// would run, and bill its events, until the session ended.
|
|
654
|
+
monitor.expiryTimer = setTimeout(() => this.expireMonitor(entry), monitor.timeoutMs)
|
|
655
|
+
monitor.expiryTimer.unref?.()
|
|
656
|
+
}
|
|
500
657
|
return entry.record
|
|
501
658
|
}
|
|
502
659
|
|
|
660
|
+
/** The deadline: the last batch, then the notice, then the same kill every stop uses. */
|
|
661
|
+
private expireMonitor(entry: JobEntry): void {
|
|
662
|
+
const monitor = entry.monitor
|
|
663
|
+
if (!monitor || entry.record.status !== 'running') return
|
|
664
|
+
monitor.stream.flush()
|
|
665
|
+
// The flush can itself have stopped the monitor for too much output.
|
|
666
|
+
if (entry.record.stopReason !== undefined || entry.record.status !== 'running') return
|
|
667
|
+
entry.record = { ...entry.record, stopReason: 'expired' }
|
|
668
|
+
this.announceMonitorEvent(
|
|
669
|
+
entry,
|
|
670
|
+
'notice',
|
|
671
|
+
expiryNotice(monitor.timeoutMs, monitor.stream.stats().eventsDelivered),
|
|
672
|
+
)
|
|
673
|
+
// Nothing follows a terminal notice. A command that ignores SIGTERM keeps
|
|
674
|
+
// printing until the SIGKILL, and those lines would reach the model after
|
|
675
|
+
// it was told the watch was over.
|
|
676
|
+
monitor.stream.close()
|
|
677
|
+
void this.kill(entry.record.id).catch(() => {
|
|
678
|
+
// The retained record carries the failure (recoveryRequired).
|
|
679
|
+
})
|
|
680
|
+
}
|
|
681
|
+
|
|
503
682
|
/**
|
|
504
683
|
* Resolves once the job's process exists, or once it could not be
|
|
505
684
|
* created (`error` then says why). Lets a caller report a start that
|
|
@@ -762,9 +941,33 @@ export class BackgroundJobRegistry {
|
|
|
762
941
|
const entry = this.jobs.get(id)
|
|
763
942
|
if (!entry) throw new UnknownBackgroundJobError({ id })
|
|
764
943
|
if (entry.record.status !== 'running') return entry.record
|
|
944
|
+
// A stop nobody else explained is the model's (or the operator's). The
|
|
945
|
+
// expiry and output rules record their own reason first.
|
|
946
|
+
const stoppedBy = entry.monitor !== undefined && entry.record.stopReason === undefined
|
|
947
|
+
if (stoppedBy) entry.record = { ...entry.record, stopReason: 'stopped' }
|
|
948
|
+
// An accepted stop ends the event stream: what was already complete goes
|
|
949
|
+
// out, and nothing the dying process prints afterwards does. (The expiry
|
|
950
|
+
// and output rules close it themselves, after their own notice.)
|
|
951
|
+
const closeStream = (): void => {
|
|
952
|
+
if (!stoppedBy) return
|
|
953
|
+
entry.monitor?.stream.flush()
|
|
954
|
+
entry.monitor?.stream.close()
|
|
955
|
+
}
|
|
765
956
|
if (entry.terminateProcess) {
|
|
766
957
|
entry.killRequested = true
|
|
767
|
-
|
|
958
|
+
try {
|
|
959
|
+
await this.confirmTermination(entry)
|
|
960
|
+
} catch (error) {
|
|
961
|
+
// Still running: do not leave it saying a stop happened.
|
|
962
|
+
if (stoppedBy) {
|
|
963
|
+
const { stopReason: _reason, ...rest } = entry.record
|
|
964
|
+
entry.record = rest
|
|
965
|
+
}
|
|
966
|
+
throw error
|
|
967
|
+
}
|
|
968
|
+
// Only once the stop is confirmed: a remote stop that failed leaves a
|
|
969
|
+
// running monitor, and a closed stream could not be reopened.
|
|
970
|
+
closeStream()
|
|
768
971
|
await entry.exit
|
|
769
972
|
return entry.record
|
|
770
973
|
}
|
|
@@ -773,6 +976,7 @@ export class BackgroundJobRegistry {
|
|
|
773
976
|
// tell a kill from an ordinary exit. Set it after and the race decides
|
|
774
977
|
// which of two different answers a reader gets.
|
|
775
978
|
entry.record = { ...entry.record, status: 'killed' }
|
|
979
|
+
closeStream()
|
|
776
980
|
for (const observer of entry.outputObservers) observer()
|
|
777
981
|
const signal = (sig: NodeJS.Signals) =>
|
|
778
982
|
entry.killProcess ? entry.killProcess(sig) : killTree(entry.child, sig)
|
|
@@ -847,6 +1051,19 @@ export function bindOwner(
|
|
|
847
1051
|
* than present and silently doing nothing.
|
|
848
1052
|
*/
|
|
849
1053
|
readonly onAwaited?: (id: string) => void
|
|
1054
|
+
/**
|
|
1055
|
+
* The longest a monitor started through this binding may live. A host
|
|
1056
|
+
* may lower it (a single-shot run has no tomorrow to watch for); the
|
|
1057
|
+
* default is {@link MONITOR_TIMEOUT_CAP_MS}.
|
|
1058
|
+
*/
|
|
1059
|
+
readonly monitorTimeoutCapMs?: number
|
|
1060
|
+
/**
|
|
1061
|
+
* Be told that this binding started a monitor. The turn passes its own
|
|
1062
|
+
* recorder here when the monitor belongs to the turn itself, which is
|
|
1063
|
+
* what makes the turn stay open for it; a session-owned monitor is not
|
|
1064
|
+
* reported, because the host wakes on its events.
|
|
1065
|
+
*/
|
|
1066
|
+
readonly onMonitorStarted?: (id: string) => void
|
|
850
1067
|
} = {},
|
|
851
1068
|
) {
|
|
852
1069
|
const mine = (id: string): BackgroundJob => {
|
|
@@ -854,26 +1071,48 @@ export function bindOwner(
|
|
|
854
1071
|
if (job.owner !== owner) throw new UnknownBackgroundJobError({ id })
|
|
855
1072
|
return job
|
|
856
1073
|
}
|
|
1074
|
+
const launch = (
|
|
1075
|
+
params: { command: string; workingDirectory?: string },
|
|
1076
|
+
monitor?: StartJobParams['monitor'],
|
|
1077
|
+
): BackgroundJob => {
|
|
1078
|
+
const workingDirectory = params.workingDirectory ?? defaults.workingDirectory ?? process.cwd()
|
|
1079
|
+
const spawnHost = defaults.spawn
|
|
1080
|
+
return registry.start({
|
|
1081
|
+
owner,
|
|
1082
|
+
command: params.command,
|
|
1083
|
+
workingDirectory,
|
|
1084
|
+
...(defaults.env ? { env: defaults.env } : {}),
|
|
1085
|
+
...(spawnHost
|
|
1086
|
+
? {
|
|
1087
|
+
spawn: () =>
|
|
1088
|
+
spawnHost({
|
|
1089
|
+
command: params.command,
|
|
1090
|
+
workingDirectory,
|
|
1091
|
+
...(defaults.env ? { env: defaults.env } : {}),
|
|
1092
|
+
}),
|
|
1093
|
+
}
|
|
1094
|
+
: {}),
|
|
1095
|
+
...(monitor ? { monitor } : {}),
|
|
1096
|
+
})
|
|
1097
|
+
}
|
|
857
1098
|
return {
|
|
858
|
-
start: (params: { command: string; workingDirectory?: string }) =>
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
: {}),
|
|
876
|
-
})
|
|
1099
|
+
start: (params: { command: string; workingDirectory?: string }) => launch(params),
|
|
1100
|
+
// The same spawn, owner and cap as `start`, plus a deadline. The timeout
|
|
1101
|
+
// is clamped to the host's ceiling and the clamped value is returned, so
|
|
1102
|
+
// the number the model is told is the number that will fire.
|
|
1103
|
+
startMonitor: (params: {
|
|
1104
|
+
command: string
|
|
1105
|
+
description: string
|
|
1106
|
+
timeoutMs: number
|
|
1107
|
+
workingDirectory?: string
|
|
1108
|
+
}) => {
|
|
1109
|
+
const timeoutMs = Math.min(
|
|
1110
|
+
params.timeoutMs,
|
|
1111
|
+
defaults.monitorTimeoutCapMs ?? MONITOR_TIMEOUT_CAP_MS,
|
|
1112
|
+
)
|
|
1113
|
+
const job = launch(params, { description: params.description, timeoutMs })
|
|
1114
|
+
defaults.onMonitorStarted?.(job.id)
|
|
1115
|
+
return { job, timeoutMs }
|
|
877
1116
|
},
|
|
878
1117
|
// A foreground command that ran out of time, kept instead of killed.
|
|
879
1118
|
// The same owner and cap as `start`; the process is the one already
|
|
@@ -924,3 +1163,29 @@ export function bindOwner(
|
|
|
924
1163
|
list: () => registry.list(owner),
|
|
925
1164
|
}
|
|
926
1165
|
}
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* How a monitor ended, in the words the model, the transcript and a
|
|
1169
|
+
* single-shot run all use, so the three cannot disagree about it.
|
|
1170
|
+
*
|
|
1171
|
+
* `completed` is the command ending by itself: with output it is the stream
|
|
1172
|
+
* ending, and without any it is a watch that never saw anything, which a
|
|
1173
|
+
* reader must not mistake for a quiet success. A non-zero exit is `failed`.
|
|
1174
|
+
* `killed` covers every stop the registry or the model asked for; the expiry
|
|
1175
|
+
* and the too-much-output stop already told the model why in a notice of their
|
|
1176
|
+
* own, so the summary does not repeat it.
|
|
1177
|
+
*/
|
|
1178
|
+
export function describeMonitorEnd(
|
|
1179
|
+
job: Pick<BackgroundJob, 'description' | 'status' | 'exitCode' | 'signal' | 'error'>,
|
|
1180
|
+
stats: Pick<MonitorStreamStats, 'stdoutChars'>,
|
|
1181
|
+
): { status: 'completed' | 'failed' | 'killed'; summary: string } {
|
|
1182
|
+
const label = `Monitor "${oneMetadataLine(job.description ?? 'unnamed')}"`
|
|
1183
|
+
if (job.status === 'killed') return { status: 'killed', summary: `${label} stopped` }
|
|
1184
|
+
if (job.exitCode === 0 && job.error === undefined) {
|
|
1185
|
+
return stats.stdoutChars > 0
|
|
1186
|
+
? { status: 'completed', summary: `${label} stream ended` }
|
|
1187
|
+
: { status: 'completed', summary: `${label} ended without producing output (exit 0)` }
|
|
1188
|
+
}
|
|
1189
|
+
const how = job.exitCode !== undefined ? `exit ${job.exitCode}` : (job.signal ?? 'exit unknown')
|
|
1190
|
+
return { status: 'failed', summary: `${label} script failed (${how})` }
|
|
1191
|
+
}
|
|
@@ -336,6 +336,7 @@ export const DEFAULT_TOOL_RETRY_BACKOFF: BackoffPolicy = {
|
|
|
336
336
|
|
|
337
337
|
export interface ToolExecutorConfig {
|
|
338
338
|
fileReadTracker?: FileReadTracker
|
|
339
|
+
codeAnalysis?: import('../../types/code-analysis/index.js').CodeAnalysisProvider
|
|
339
340
|
tools: ToolManager
|
|
340
341
|
/** Exact runtime output-tool owner allowed to retain post-screen, post-hook JSON before preview capping. */
|
|
341
342
|
durableStructuredOutputTool?: ToolDefinition
|
|
@@ -394,6 +395,15 @@ export interface ToolExecutorConfig {
|
|
|
394
395
|
* recorder gets no hold rather than a marking call that goes nowhere.
|
|
395
396
|
*/
|
|
396
397
|
onJobAwaited?: (id: string) => void
|
|
398
|
+
/** The longest a monitor may live in this turn; see `QueryParams.monitorTimeoutCapMs`. */
|
|
399
|
+
monitorTimeoutCapMs?: number
|
|
400
|
+
/**
|
|
401
|
+
* Where a monitor the turn owns itself is recorded, so the turn stays open
|
|
402
|
+
* for it. Passed only when the monitor's owner is the turn: a session-owned
|
|
403
|
+
* monitor is heard by the host, and holding the turn for it would delay the
|
|
404
|
+
* end of every turn for as long as the watch lives.
|
|
405
|
+
*/
|
|
406
|
+
onMonitorStarted?: (id: string) => void
|
|
397
407
|
|
|
398
408
|
/**
|
|
399
409
|
* Where the `skill` tool reads from.
|
|
@@ -1905,6 +1915,7 @@ export class ToolExecutor {
|
|
|
1905
1915
|
this.dispatchNested(name, input, context, recordObservation, undefined, options),
|
|
1906
1916
|
sandbox: this.config.sandbox,
|
|
1907
1917
|
fileReadTracker: this.fileReadTracker,
|
|
1918
|
+
...(this.config.codeAnalysis ? { codeAnalysis: this.config.codeAnalysis } : {}),
|
|
1908
1919
|
// Bound to this turn, once. Binding here rather than passing the
|
|
1909
1920
|
// owner from the tool is what makes the scoping structural: there
|
|
1910
1921
|
// is no argument a tool could pass to reach another turn's jobs.
|
|
@@ -1946,6 +1957,12 @@ export class ToolExecutor {
|
|
|
1946
1957
|
}
|
|
1947
1958
|
: {}),
|
|
1948
1959
|
...(this.config.onJobAwaited ? { onAwaited: this.config.onJobAwaited } : {}),
|
|
1960
|
+
...(this.config.monitorTimeoutCapMs !== undefined
|
|
1961
|
+
? { monitorTimeoutCapMs: this.config.monitorTimeoutCapMs }
|
|
1962
|
+
: {}),
|
|
1963
|
+
...(this.config.onMonitorStarted
|
|
1964
|
+
? { onMonitorStarted: this.config.onMonitorStarted }
|
|
1965
|
+
: {}),
|
|
1949
1966
|
},
|
|
1950
1967
|
),
|
|
1951
1968
|
}
|
|
@@ -145,7 +145,13 @@ import {
|
|
|
145
145
|
supersededByRecovery,
|
|
146
146
|
} from './resume-pending.js'
|
|
147
147
|
import { acquireSandbox } from './sandbox-lifecycle.js'
|
|
148
|
-
import {
|
|
148
|
+
import {
|
|
149
|
+
SteeringBinding,
|
|
150
|
+
type SteeringChannel,
|
|
151
|
+
formatMonitorEnd,
|
|
152
|
+
formatMonitorEvent,
|
|
153
|
+
isOperatorUserMessage,
|
|
154
|
+
} from './steering.js'
|
|
149
155
|
import { ToolGrantSet } from './tool-grants.js'
|
|
150
156
|
import { createToolPause } from './tool-pause.js'
|
|
151
157
|
import { ToolingBootstrap } from './tooling.js'
|
|
@@ -153,6 +159,8 @@ import { ToolingBootstrap } from './tooling.js'
|
|
|
153
159
|
export interface QueryParams {
|
|
154
160
|
/** Share observations across turns of one conversation and filesystem; otherwise run-local. */
|
|
155
161
|
fileReadTracker?: import('../../types/tool/index.js').FileReadTracker
|
|
162
|
+
/** The `code_analysis` tool's index; see `ToolContext.codeAnalysis`. Absent: the tool is not offered. */
|
|
163
|
+
codeAnalysis?: import('../../types/code-analysis/index.js').CodeAnalysisProvider
|
|
156
164
|
/** One ledger shared with the task scheduler and descendant child sessions, keyed by (rootSessionId, rootTurnId). */
|
|
157
165
|
budget?: SessionTokenBudget
|
|
158
166
|
/**
|
|
@@ -278,6 +286,22 @@ export interface QueryParams {
|
|
|
278
286
|
* mean the model saw it. A host can carry unacknowledged exits to its next turn.
|
|
279
287
|
*/
|
|
280
288
|
onJobNoticeDelivered?: (jobIds: readonly string[]) => void
|
|
289
|
+
/**
|
|
290
|
+
* Called with monitor event ids only when their text enters model context:
|
|
291
|
+
* a tool result or a recorded runtime message. A `monitor_event` alone does
|
|
292
|
+
* not mean the model saw it, so a host that wakes an idle session for
|
|
293
|
+
* events must not treat one as delivered until this says so. The end of a
|
|
294
|
+
* monitor is acknowledged through {@link onJobNoticeDelivered}, with its
|
|
295
|
+
* job id, as any job's exit is.
|
|
296
|
+
*/
|
|
297
|
+
onMonitorEventsDelivered?: (eventIds: readonly string[]) => void
|
|
298
|
+
/**
|
|
299
|
+
* The longest a monitor started in this turn may live, in milliseconds.
|
|
300
|
+
* Absent, the SDK ceiling (30 minutes). A host lowers it where nobody will
|
|
301
|
+
* be around for the rest, such as a single-shot run. Larger requests are
|
|
302
|
+
* clamped to it without an error.
|
|
303
|
+
*/
|
|
304
|
+
monitorTimeoutCapMs?: number
|
|
281
305
|
|
|
282
306
|
/**
|
|
283
307
|
* What else goes in this turn's prompt.
|
|
@@ -1371,6 +1395,27 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1371
1395
|
// subscription that fills it below, because the wait-intent recorder needs
|
|
1372
1396
|
// to ask whether its text has been read yet.
|
|
1373
1397
|
const jobNotices = params.backgroundJobs ? new SteeringBinding() : undefined
|
|
1398
|
+
// What this turn's monitors said, in the system-event envelope. Its own
|
|
1399
|
+
// channel because the exit notices above are plain text with a plain ack,
|
|
1400
|
+
// and a monitor's text is untrusted process output with an ack of its own.
|
|
1401
|
+
const monitorNotices = params.backgroundJobs ? new SteeringBinding() : undefined
|
|
1402
|
+
const pendingMonitorEventIds = new Set<string>()
|
|
1403
|
+
const pendingMonitorEndIds = new Set<string>()
|
|
1404
|
+
const acknowledgeMonitorNotices = () => {
|
|
1405
|
+
const eventIds = [...pendingMonitorEventIds]
|
|
1406
|
+
const endIds = [...pendingMonitorEndIds]
|
|
1407
|
+
pendingMonitorEventIds.clear()
|
|
1408
|
+
pendingMonitorEndIds.clear()
|
|
1409
|
+
try {
|
|
1410
|
+
if (eventIds.length > 0) params.onMonitorEventsDelivered?.(eventIds)
|
|
1411
|
+
if (endIds.length > 0) params.onJobNoticeDelivered?.(endIds)
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
// A host observer cannot retract model-visible context or fail the turn.
|
|
1414
|
+
ctx.log.warn('A monitor notice observer failed', {
|
|
1415
|
+
'exception.message': error instanceof Error ? error.message : String(error),
|
|
1416
|
+
})
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1374
1419
|
const pendingJobNoticeIds = new Set<string>()
|
|
1375
1420
|
const acknowledgeJobNotices = () => {
|
|
1376
1421
|
if (pendingJobNoticeIds.size === 0) return
|
|
@@ -1389,8 +1434,17 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1389
1434
|
// thing that can hold this turn open for a job. Built only where there is a
|
|
1390
1435
|
// registry, so a host with no background mode carries no recorder and the
|
|
1391
1436
|
// bound ref has no `markAwaited` to offer.
|
|
1437
|
+
// A turn that owns its jobs itself has no host listening for a monitor, so
|
|
1438
|
+
// it holds open for the monitors it started; a session-owned monitor is the
|
|
1439
|
+
// host's to hear (`AwaitedJobs`' `MonitorHold`).
|
|
1440
|
+
const turnOwnsJobs = params.backgroundJobOwner === undefined
|
|
1392
1441
|
const awaitedJobs = params.backgroundJobs
|
|
1393
|
-
? new AwaitedJobs(
|
|
1442
|
+
? new AwaitedJobs(
|
|
1443
|
+
params.backgroundJobs,
|
|
1444
|
+
jobOwner,
|
|
1445
|
+
() => jobNotices?.pending ?? false,
|
|
1446
|
+
turnOwnsJobs ? { unreadNotice: () => monitorNotices?.pending ?? false } : undefined,
|
|
1447
|
+
)
|
|
1394
1448
|
: undefined
|
|
1395
1449
|
awaitedJobs?.attach()
|
|
1396
1450
|
|
|
@@ -1422,12 +1476,17 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1422
1476
|
...(params.backgroundJobs ? { backgroundJobs: params.backgroundJobs } : {}),
|
|
1423
1477
|
...(params.backgroundJobOwner ? { backgroundJobOwner: params.backgroundJobOwner } : {}),
|
|
1424
1478
|
...(awaitedJobs ? { onJobAwaited: (id: string) => awaitedJobs.expect(id) } : {}),
|
|
1479
|
+
...(awaitedJobs ? { onMonitorStarted: (id: string) => awaitedJobs.expectMonitor(id) } : {}),
|
|
1480
|
+
...(params.monitorTimeoutCapMs !== undefined
|
|
1481
|
+
? { monitorTimeoutCapMs: params.monitorTimeoutCapMs }
|
|
1482
|
+
: {}),
|
|
1425
1483
|
// The `skill` tool's registry. Threaded from the turn rather than
|
|
1426
1484
|
// held by the tool, because a tool that reached for a module-level
|
|
1427
1485
|
// registry would answer about whatever the last turn configured.
|
|
1428
1486
|
...(params.skillRegistry ? { skills: params.skillRegistry } : {}),
|
|
1429
1487
|
...(params.web ? { web: params.web } : {}),
|
|
1430
1488
|
...(params.fileReadTracker ? { fileReadTracker: params.fileReadTracker } : {}),
|
|
1489
|
+
...(params.codeAnalysis ? { codeAnalysis: params.codeAnalysis } : {}),
|
|
1431
1490
|
...(params.toolTimeoutMs !== undefined ? { toolTimeoutMs: params.toolTimeoutMs } : {}),
|
|
1432
1491
|
...(params.toolRetryBackoff !== undefined
|
|
1433
1492
|
? { toolRetryBackoff: params.toolRetryBackoff }
|
|
@@ -1490,8 +1549,31 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1490
1549
|
// result, and the host as an event — without either polling. Subscribed
|
|
1491
1550
|
// for the owner the turn's jobs are bound to, so a session-owned job that
|
|
1492
1551
|
// ends during this turn is reported here too.
|
|
1493
|
-
const
|
|
1552
|
+
const unsubscribeExits = params.backgroundJobs?.onExit((job) => {
|
|
1494
1553
|
if (job.owner !== jobOwner) return
|
|
1554
|
+
if (job.kind === 'monitor' && params.backgroundJobs) {
|
|
1555
|
+
// A monitor's end is told in the monitor's own words (the same
|
|
1556
|
+
// `describeMonitorEnd` the transcript and a single-shot run use) and
|
|
1557
|
+
// in the same envelope as its events, rather than as a generic exit.
|
|
1558
|
+
monitorNotices?.steer(formatMonitorEnd(job, params.backgroundJobs.monitorStats(job.id)))
|
|
1559
|
+
pendingMonitorEndIds.add(job.id)
|
|
1560
|
+
void eventTranslator
|
|
1561
|
+
.emitEvent({
|
|
1562
|
+
type: 'background_job_exited',
|
|
1563
|
+
turnId: ctx.turnId,
|
|
1564
|
+
jobId: job.id,
|
|
1565
|
+
command: job.command,
|
|
1566
|
+
status: job.status === 'killed' ? 'killed' : 'exited',
|
|
1567
|
+
...(job.exitCode !== undefined ? { exitCode: job.exitCode } : {}),
|
|
1568
|
+
...(job.signal ? { signal: job.signal } : {}),
|
|
1569
|
+
monitor: {
|
|
1570
|
+
description: job.description ?? '',
|
|
1571
|
+
...(job.stopReason ? { stopReason: job.stopReason } : {}),
|
|
1572
|
+
},
|
|
1573
|
+
})
|
|
1574
|
+
.catch(() => {})
|
|
1575
|
+
return
|
|
1576
|
+
}
|
|
1495
1577
|
const outcome =
|
|
1496
1578
|
job.status === 'killed'
|
|
1497
1579
|
? 'was stopped'
|
|
@@ -1512,6 +1594,38 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1512
1594
|
})
|
|
1513
1595
|
.catch(() => {})
|
|
1514
1596
|
})
|
|
1597
|
+
// What a monitor prints reaches the model as an event on its next tool
|
|
1598
|
+
// result, and the host as `monitor_event`. Subscribed for the owner the
|
|
1599
|
+
// turn's jobs are bound to, like the exits above.
|
|
1600
|
+
const unsubscribeMonitorEvents = params.backgroundJobs?.onMonitorEvent((event) => {
|
|
1601
|
+
if (event.owner !== jobOwner) return
|
|
1602
|
+
let command = ''
|
|
1603
|
+
try {
|
|
1604
|
+
command = params.backgroundJobs?.get(event.jobId).command ?? ''
|
|
1605
|
+
} catch {
|
|
1606
|
+
// The record is gone; the event still stands without its command.
|
|
1607
|
+
}
|
|
1608
|
+
monitorNotices?.steer(formatMonitorEvent(event, command))
|
|
1609
|
+
pendingMonitorEventIds.add(event.id)
|
|
1610
|
+
void eventTranslator
|
|
1611
|
+
.emitEvent({
|
|
1612
|
+
type: 'monitor_event',
|
|
1613
|
+
turnId: ctx.turnId,
|
|
1614
|
+
jobId: event.jobId,
|
|
1615
|
+
eventId: event.id,
|
|
1616
|
+
description: event.description,
|
|
1617
|
+
kind: event.kind,
|
|
1618
|
+
text: event.text,
|
|
1619
|
+
})
|
|
1620
|
+
.catch(() => {})
|
|
1621
|
+
})
|
|
1622
|
+
const unsubscribeJobExits =
|
|
1623
|
+
unsubscribeExits || unsubscribeMonitorEvents
|
|
1624
|
+
? () => {
|
|
1625
|
+
unsubscribeExits?.()
|
|
1626
|
+
unsubscribeMonitorEvents?.()
|
|
1627
|
+
}
|
|
1628
|
+
: undefined
|
|
1515
1629
|
let workingStateManager: WorkingStateManager | undefined
|
|
1516
1630
|
// Normalised once, defaults applied. A host that passes a partial
|
|
1517
1631
|
// object — a strategy and a window, nothing else — used to reach the
|
|
@@ -1661,6 +1775,8 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1661
1775
|
...(params.steering ? { steering: params.steering } : {}),
|
|
1662
1776
|
...(jobNotices ? { jobNotices } : {}),
|
|
1663
1777
|
...(jobNotices ? { onJobNoticeDelivered: acknowledgeJobNotices } : {}),
|
|
1778
|
+
...(monitorNotices ? { monitorNotices } : {}),
|
|
1779
|
+
...(monitorNotices ? { onMonitorNoticeDelivered: acknowledgeMonitorNotices } : {}),
|
|
1664
1780
|
...(awaitedJobs ? { awaitedJobs } : {}),
|
|
1665
1781
|
checkpointMgr,
|
|
1666
1782
|
planManager: ctx.planManager,
|