@namzu/sdk 52.2.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.
Files changed (126) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  3. package/dist/bridge/a2a/mapper.js +3 -0
  4. package/dist/bridge/a2a/mapper.js.map +1 -1
  5. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  6. package/dist/bridge/sse/mapper.js +18 -0
  7. package/dist/bridge/sse/mapper.js.map +1 -1
  8. package/dist/contracts/session/api.d.ts +1 -1
  9. package/dist/contracts/session/api.d.ts.map +1 -1
  10. package/dist/index.d.ts +4 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +2 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/pals/channels/routing.d.ts +1 -1
  15. package/dist/pals/channels/schema.d.ts +7 -7
  16. package/dist/pals/communication/ingress-schema.d.ts +32 -32
  17. package/dist/public-runtime.d.ts +1 -1
  18. package/dist/public-runtime.d.ts.map +1 -1
  19. package/dist/public-runtime.js +1 -1
  20. package/dist/public-runtime.js.map +1 -1
  21. package/dist/public-tools.d.ts +2 -1
  22. package/dist/public-tools.d.ts.map +1 -1
  23. package/dist/public-tools.js +6 -1
  24. package/dist/public-tools.js.map +1 -1
  25. package/dist/runtime/jobs/awaited-jobs.d.ts +59 -2
  26. package/dist/runtime/jobs/awaited-jobs.d.ts.map +1 -1
  27. package/dist/runtime/jobs/awaited-jobs.js +108 -5
  28. package/dist/runtime/jobs/awaited-jobs.js.map +1 -1
  29. package/dist/runtime/jobs/monitor-stream.d.ts +96 -0
  30. package/dist/runtime/jobs/monitor-stream.d.ts.map +1 -0
  31. package/dist/runtime/jobs/monitor-stream.js +221 -0
  32. package/dist/runtime/jobs/monitor-stream.js.map +1 -0
  33. package/dist/runtime/jobs/registry.d.ts +96 -0
  34. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  35. package/dist/runtime/jobs/registry.js +203 -20
  36. package/dist/runtime/jobs/registry.js.map +1 -1
  37. package/dist/runtime/query/executor.d.ts +9 -0
  38. package/dist/runtime/query/executor.d.ts.map +1 -1
  39. package/dist/runtime/query/executor.js +6 -0
  40. package/dist/runtime/query/executor.js.map +1 -1
  41. package/dist/runtime/query/index.d.ts +16 -0
  42. package/dist/runtime/query/index.d.ts.map +1 -1
  43. package/dist/runtime/query/index.js +93 -3
  44. package/dist/runtime/query/index.js.map +1 -1
  45. package/dist/runtime/query/iteration/outstanding-work.d.ts +14 -0
  46. package/dist/runtime/query/iteration/outstanding-work.d.ts.map +1 -1
  47. package/dist/runtime/query/iteration/outstanding-work.js +64 -3
  48. package/dist/runtime/query/iteration/outstanding-work.js.map +1 -1
  49. package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
  50. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  51. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  52. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  53. package/dist/runtime/query/iteration/phases/tool-review.js +11 -2
  54. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  55. package/dist/runtime/query/release-turn.d.ts.map +1 -1
  56. package/dist/runtime/query/release-turn.js +35 -0
  57. package/dist/runtime/query/release-turn.js.map +1 -1
  58. package/dist/runtime/query/steering.d.ts +35 -0
  59. package/dist/runtime/query/steering.d.ts.map +1 -1
  60. package/dist/runtime/query/steering.js +121 -0
  61. package/dist/runtime/query/steering.js.map +1 -1
  62. package/dist/runtime/query/tooling.d.ts +4 -0
  63. package/dist/runtime/query/tooling.d.ts.map +1 -1
  64. package/dist/runtime/query/tooling.js +4 -0
  65. package/dist/runtime/query/tooling.js.map +1 -1
  66. package/dist/runtime/system-events.d.ts +9 -0
  67. package/dist/runtime/system-events.d.ts.map +1 -1
  68. package/dist/runtime/system-events.js +12 -0
  69. package/dist/runtime/system-events.js.map +1 -1
  70. package/dist/tools/builtins/bash.d.ts +3 -0
  71. package/dist/tools/builtins/bash.d.ts.map +1 -1
  72. package/dist/tools/builtins/bash.js +3 -2
  73. package/dist/tools/builtins/bash.js.map +1 -1
  74. package/dist/tools/builtins/index.d.ts +1 -0
  75. package/dist/tools/builtins/index.d.ts.map +1 -1
  76. package/dist/tools/builtins/index.js +1 -0
  77. package/dist/tools/builtins/index.js.map +1 -1
  78. package/dist/tools/builtins/job.d.ts.map +1 -1
  79. package/dist/tools/builtins/job.js +6 -2
  80. package/dist/tools/builtins/job.js.map +1 -1
  81. package/dist/tools/builtins/monitor.d.ts +24 -0
  82. package/dist/tools/builtins/monitor.d.ts.map +1 -0
  83. package/dist/tools/builtins/monitor.js +167 -0
  84. package/dist/tools/builtins/monitor.js.map +1 -0
  85. package/dist/tools/builtins/wait-for-job.d.ts +1 -1
  86. package/dist/turn/reporter.d.ts.map +1 -1
  87. package/dist/turn/reporter.js +8 -0
  88. package/dist/turn/reporter.js.map +1 -1
  89. package/dist/types/session/events.d.ts +30 -4
  90. package/dist/types/session/events.d.ts.map +1 -1
  91. package/dist/types/session/events.js +6 -1
  92. package/dist/types/session/events.js.map +1 -1
  93. package/dist/types/session/records.d.ts +2 -2
  94. package/dist/types/session/records.d.ts.map +1 -1
  95. package/dist/types/session/records.js +3 -2
  96. package/dist/types/session/records.js.map +1 -1
  97. package/dist/types/tool/index.d.ts +25 -0
  98. package/dist/types/tool/index.d.ts.map +1 -1
  99. package/dist/types/tool/index.js.map +1 -1
  100. package/package.json +1 -1
  101. package/src/bridge/a2a/mapper.ts +3 -0
  102. package/src/bridge/sse/mapper.ts +18 -0
  103. package/src/contracts/session/api.ts +1 -0
  104. package/src/index.ts +23 -0
  105. package/src/public-runtime.ts +3 -0
  106. package/src/public-tools.ts +11 -0
  107. package/src/runtime/jobs/awaited-jobs.ts +127 -5
  108. package/src/runtime/jobs/monitor-stream.ts +269 -0
  109. package/src/runtime/jobs/registry.ts +286 -21
  110. package/src/runtime/query/executor.ts +15 -0
  111. package/src/runtime/query/index.ts +116 -3
  112. package/src/runtime/query/iteration/outstanding-work.ts +64 -3
  113. package/src/runtime/query/iteration/phases/context.ts +9 -0
  114. package/src/runtime/query/iteration/phases/tool-review.ts +21 -9
  115. package/src/runtime/query/release-turn.ts +33 -0
  116. package/src/runtime/query/steering.ts +144 -0
  117. package/src/runtime/query/tooling.ts +8 -0
  118. package/src/runtime/system-events.ts +17 -0
  119. package/src/tools/builtins/bash.ts +3 -2
  120. package/src/tools/builtins/index.ts +6 -0
  121. package/src/tools/builtins/job.ts +11 -2
  122. package/src/tools/builtins/monitor.ts +206 -0
  123. package/src/turn/reporter.ts +8 -0
  124. package/src/types/session/events.ts +31 -4
  125. package/src/types/session/records.ts +3 -2
  126. package/src/types/tool/index.ts +24 -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
+ }
@@ -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,
@@ -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: 62 type literals.
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 four. */
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 62 live event type literals. */
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),
@@ -120,6 +120,21 @@ export interface BackgroundJobRegistryRef {
120
120
  id: string
121
121
  status: string
122
122
  }
123
+ /**
124
+ * Start a command whose stdout lines reach the model as events while it
125
+ * runs, and which is stopped at `timeoutMs`. The host clamps the timeout to
126
+ * its own ceiling and the clamped value comes back, so the caller reports
127
+ * the deadline that will actually fire. Optional, like `adopt`: a host that
128
+ * cannot deliver events must omit it, and the `monitor` tool then refuses
129
+ * rather than start a watch nobody will hear from. Throws when the owner is
130
+ * at its job cap.
131
+ */
132
+ startMonitor?(params: {
133
+ command: string
134
+ description: string
135
+ timeoutMs: number
136
+ workingDirectory?: string
137
+ }): { job: { id: string; status: string }; timeoutMs: number }
123
138
  get(id: string): {
124
139
  id: string
125
140
  status: string
@@ -156,7 +171,15 @@ export interface BackgroundJobRegistryRef {
156
171
  error?: string
157
172
  }
158
173
  kill(id: string): Promise<{ id: string; status: string }>
159
- list(): readonly { id: string; command: string; status: string }[]
174
+ list(): readonly {
175
+ id: string
176
+ command: string
177
+ status: string
178
+ /** `monitor` for a job the `monitor` tool started. */
179
+ kind?: 'monitor'
180
+ /** What a monitor watches. */
181
+ description?: string
182
+ }[]
160
183
  /**
161
184
  * Await this job's exit instead of reading it in a loop; resolves at
162
185
  * once for a job that has already stopped. Optional, and added after