@agimon-ai/doompi-runner 0.0.1-alpha.73 → 0.0.1-alpha.76

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.
@@ -7,6 +7,8 @@ let node_crypto = require("node:crypto");
7
7
  const TIMED_OUT = Symbol("threshold-reached");
8
8
  /** The command outlived the caller's explicit timeout and should be stopped. */
9
9
  const DEADLINE = Symbol("deadline-reached");
10
+ /** The caller cancelled the command and its owned runner should be stopped. */
11
+ const ABORTED = Symbol("aborted");
10
12
  function runnerId() {
11
13
  return `${Date.now().toString(36)}-${(0, node_crypto.randomBytes)(3).toString("base64url")}`;
12
14
  }
@@ -30,6 +32,12 @@ var BashRunService = class {
30
32
  const name = await this.namer.allocate(request.command, request.sessionId, request.name);
31
33
  const cwd = request.cwd ?? process.cwd();
32
34
  const interactive = request.interactive === true;
35
+ if (request.signal?.aborted) return {
36
+ kind: require_bashRunService.FAILED,
37
+ id,
38
+ name,
39
+ error: "Operation aborted"
40
+ };
33
41
  let handle;
34
42
  try {
35
43
  const rmuxHandle = await this.rmuxBackend.launch({
@@ -67,6 +75,15 @@ var BashRunService = class {
67
75
  name,
68
76
  error: "The command did not start, so it cannot be supervised"
69
77
  };
78
+ if (request.signal?.aborted) {
79
+ await handle.stop();
80
+ return {
81
+ kind: require_bashRunService.FAILED,
82
+ id,
83
+ name,
84
+ error: "Operation aborted"
85
+ };
86
+ }
70
87
  try {
71
88
  await this.registry.register({
72
89
  id,
@@ -89,12 +106,32 @@ var BashRunService = class {
89
106
  error: error instanceof Error ? error.message : String(error)
90
107
  };
91
108
  }
92
- if (request.background === true || interactive) return this.promote(handle, request.background === true ? "requested" : "interactive");
109
+ if (request.background === true || interactive) return this.promote(handle, request.background === true ? "requested" : "interactive", request.signal);
93
110
  const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : void 0;
94
111
  try {
95
- const outcome = await this.race(handle, request.timeoutMs);
112
+ const outcome = await this.race(handle, request.timeoutMs, request.signal);
96
113
  outputUpdates?.flush();
97
- if (outcome === TIMED_OUT) return this.promote(handle, "threshold");
114
+ if (outcome === TIMED_OUT) return this.promote(handle, "threshold", request.signal);
115
+ if (outcome === ABORTED) {
116
+ await handle.stop();
117
+ outputUpdates?.flush();
118
+ await this.registry.complete(id, {
119
+ reason: "stopped",
120
+ code: null,
121
+ signal: "SIGTERM"
122
+ });
123
+ return {
124
+ kind: require_bashRunService.COMPLETED,
125
+ id,
126
+ name,
127
+ output: handle.output(),
128
+ exitCode: null,
129
+ signal: "SIGTERM",
130
+ logPath: handle.logPath,
131
+ backend: handle.backend,
132
+ aborted: true
133
+ };
134
+ }
98
135
  if (outcome === DEADLINE) {
99
136
  await handle.stop();
100
137
  outputUpdates?.flush();
@@ -194,7 +231,7 @@ var BashRunService = class {
194
231
  * A rejected completion is returned rather than thrown so the caller can
195
232
  * report a start failure as a tool result instead of an exception.
196
233
  */
197
- async race(handle, timeoutMs) {
234
+ async race(handle, timeoutMs, signal) {
198
235
  const cancels = [];
199
236
  const racers = [handle.completion().catch((error) => error)];
200
237
  racers.push(new Promise((resolve) => {
@@ -203,13 +240,22 @@ var BashRunService = class {
203
240
  if (timeoutMs !== void 0 && timeoutMs > 0) racers.push(new Promise((resolve) => {
204
241
  cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));
205
242
  }));
243
+ if (signal) racers.push(new Promise((resolve) => {
244
+ if (signal.aborted) {
245
+ resolve(ABORTED);
246
+ return;
247
+ }
248
+ const abort = () => resolve(ABORTED);
249
+ signal.addEventListener("abort", abort, { once: true });
250
+ cancels.push(() => signal.removeEventListener("abort", abort));
251
+ }));
206
252
  try {
207
253
  return await Promise.race(racers);
208
254
  } finally {
209
255
  for (const cancel of cancels) cancel();
210
256
  }
211
257
  }
212
- async promote(handle, reason) {
258
+ async promote(handle, reason, signal) {
213
259
  if (handle.pid === void 0) return {
214
260
  kind: require_bashRunService.FAILED,
215
261
  id: handle.id,
@@ -218,18 +264,43 @@ var BashRunService = class {
218
264
  };
219
265
  await this.registry.markPromoted(handle.id);
220
266
  handle.detach();
221
- handle.completion().then((outcome) => this.registry.complete(handle.id, {
222
- reason: outcome.signal ? require_bashRunService.SIGNALED : outcome.code === 0 ? require_bashRunService.COMPLETED : require_bashRunService.FAILED,
223
- code: outcome.code,
224
- signal: outcome.signal
225
- }), () => this.registry.complete(handle.id, {
226
- reason: require_bashRunService.LAUNCHER_ERROR,
227
- code: null,
228
- signal: null
229
- })).catch((error) => {
267
+ let abortStop;
268
+ const abort = () => {
269
+ abortStop = handle.stop().then(async () => {
270
+ await this.registry.complete(handle.id, {
271
+ reason: "stopped",
272
+ code: null,
273
+ signal: "SIGTERM"
274
+ });
275
+ return true;
276
+ }).catch((error) => {
277
+ const message = error instanceof Error ? error.message : String(error);
278
+ process.emitWarning(`Failed to stop aborted runner ${handle.id}: ${message}`);
279
+ return false;
280
+ });
281
+ };
282
+ if (signal) {
283
+ if (signal.aborted) abort();
284
+ else signal.addEventListener("abort", abort, { once: true });
285
+ }
286
+ handle.completion().then(async (outcome) => {
287
+ if (await abortStop) return;
288
+ await this.registry.complete(handle.id, {
289
+ reason: outcome.signal ? require_bashRunService.SIGNALED : outcome.code === 0 ? require_bashRunService.COMPLETED : require_bashRunService.FAILED,
290
+ code: outcome.code,
291
+ signal: outcome.signal
292
+ });
293
+ }, async () => {
294
+ if (await abortStop) return;
295
+ await this.registry.complete(handle.id, {
296
+ reason: require_bashRunService.LAUNCHER_ERROR,
297
+ code: null,
298
+ signal: null
299
+ });
300
+ }).catch((error) => {
230
301
  const message = error instanceof Error ? error.message : String(error);
231
302
  process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);
232
- });
303
+ }).finally(() => signal?.removeEventListener("abort", abort));
233
304
  return {
234
305
  kind: "promoted",
235
306
  id: handle.id,
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["randomBytes","FAILED","COMPLETED","LAUNCHER_ERROR","SIGNALED","RTK_FAILED_WARNING","getBackgroundThresholdMs"],"sources":["../../../../src/services/bashRunService/index.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\n\nimport {\n COMPLETED,\n FAILED,\n LAUNCHER_ERROR,\n OUTPUT_UPDATE_POLL_MS,\n RUNNER_ID_RANDOM_BYTES,\n SIGNALED,\n} from '../../constants/bashRunService';\nimport type {\n BashRunRequest,\n BashRunResult,\n CompletedRun,\n IBashRunService,\n PromotedRun,\n} from '../../types/bashRunService';\nimport type { IClock } from '../../types/clock';\nimport type { ILauncher, RunHandle } from '../../types/launcher';\nimport type { IRmuxBackend } from '../../types/rmuxBackend';\nimport { type IRtkProcessor, RTK_FAILED_WARNING } from '../../types/rtkProcessor';\nimport type { IRunnerRegistry } from '../../types/runnerRegistry';\nimport { getBackgroundThresholdMs } from '../runnerConfig';\nimport type { IRunnerNamer } from '../runnerNamer/type';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\nexport class BashRunService implements IBashRunService {\n constructor(\n private readonly launcher: ILauncher,\n private readonly rmuxBackend: IRmuxBackend,\n private readonly namer: IRunnerNamer,\n private readonly registry: IRunnerRegistry,\n private readonly clock: IClock,\n private readonly rtkProcessor: IRtkProcessor,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? process.cwd();\n const interactive = request.interactive === true;\n\n let handle: RunHandle;\n try {\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n if (interactive && !rmuxHandle) {\n return { kind: FAILED, id, name, error: 'RMUX is required for interactive commands but is unavailable' };\n }\n handle =\n rmuxHandle ?? this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId });\n } catch (error) {\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive');\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold');\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n ...(await this.processLog(request.command, handle.logPath)),\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private async processLog(command: string, logPath: string): Promise<Pick<CompletedRun, 'rtkOutput' | 'rtkWarning'>> {\n try {\n const result = await this.rtkProcessor.process({ command, logPath });\n if (result.kind === 'processed') return { rtkOutput: result.result };\n if (result.kind === 'fallback') return { rtkWarning: result.warning };\n return {};\n } catch {\n return { rtkWarning: RTK_FAILED_WARNING };\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n ): Promise<Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE> {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(handle: RunHandle, reason: PromotedRun['reason']): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n void handle\n .completion()\n .then(\n (outcome) =>\n this.registry.complete(handle.id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n }),\n () => this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null }),\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n });\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["randomBytes","FAILED","COMPLETED","LAUNCHER_ERROR","SIGNALED","RTK_FAILED_WARNING","getBackgroundThresholdMs"],"sources":["../../../../src/services/bashRunService/index.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\n\nimport {\n COMPLETED,\n FAILED,\n LAUNCHER_ERROR,\n OUTPUT_UPDATE_POLL_MS,\n RUNNER_ID_RANDOM_BYTES,\n SIGNALED,\n} from '../../constants/bashRunService';\nimport type {\n BashRunRequest,\n BashRunResult,\n CompletedRun,\n IBashRunService,\n PromotedRun,\n} from '../../types/bashRunService';\nimport type { IClock } from '../../types/clock';\nimport type { ILauncher, RunHandle } from '../../types/launcher';\nimport type { IRmuxBackend } from '../../types/rmuxBackend';\nimport { type IRtkProcessor, RTK_FAILED_WARNING } from '../../types/rtkProcessor';\nimport type { IRunnerRegistry } from '../../types/runnerRegistry';\nimport { getBackgroundThresholdMs } from '../runnerConfig';\nimport type { IRunnerNamer } from '../runnerNamer/type';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\n/** The caller cancelled the command and its owned runner should be stopped. */\nconst ABORTED = Symbol('aborted');\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\nexport class BashRunService implements IBashRunService {\n constructor(\n private readonly launcher: ILauncher,\n private readonly rmuxBackend: IRmuxBackend,\n private readonly namer: IRunnerNamer,\n private readonly registry: IRunnerRegistry,\n private readonly clock: IClock,\n private readonly rtkProcessor: IRtkProcessor,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? process.cwd();\n const interactive = request.interactive === true;\n if (request.signal?.aborted) return { kind: FAILED, id, name, error: 'Operation aborted' };\n\n let handle: RunHandle;\n try {\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n if (interactive && !rmuxHandle) {\n return { kind: FAILED, id, name, error: 'RMUX is required for interactive commands but is unavailable' };\n }\n handle =\n rmuxHandle ?? this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId });\n } catch (error) {\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n if (request.signal?.aborted) {\n await handle.stop();\n return { kind: FAILED, id, name, error: 'Operation aborted' };\n }\n\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive', request.signal);\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs, request.signal);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold', request.signal);\n }\n if (outcome === ABORTED) {\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n aborted: true,\n };\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n ...(await this.processLog(request.command, handle.logPath)),\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private async processLog(command: string, logPath: string): Promise<Pick<CompletedRun, 'rtkOutput' | 'rtkWarning'>> {\n try {\n const result = await this.rtkProcessor.process({ command, logPath });\n if (result.kind === 'processed') return { rtkOutput: result.result };\n if (result.kind === 'fallback') return { rtkWarning: result.warning };\n return {};\n } catch {\n return { rtkWarning: RTK_FAILED_WARNING };\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n signal: AbortSignal | undefined,\n ): Promise<\n Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE | typeof ABORTED\n > {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n if (signal) {\n racers.push(\n new Promise((resolve) => {\n if (signal.aborted) {\n resolve(ABORTED);\n return;\n }\n const abort = (): void => resolve(ABORTED);\n signal.addEventListener('abort', abort, { once: true });\n cancels.push(() => signal.removeEventListener('abort', abort));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as\n | Awaited<ReturnType<RunHandle['completion']>>\n | Error\n | typeof TIMED_OUT\n | typeof DEADLINE\n | typeof ABORTED;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(\n handle: RunHandle,\n reason: PromotedRun['reason'],\n signal: AbortSignal | undefined,\n ): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n let abortStop: Promise<boolean> | undefined;\n const abort = (): void => {\n abortStop = handle\n .stop()\n .then(async () => {\n await this.registry.complete(handle.id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return true;\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to stop aborted runner ${handle.id}: ${message}`);\n return false;\n });\n };\n if (signal) {\n if (signal.aborted) abort();\n else signal.addEventListener('abort', abort, { once: true });\n }\n void handle\n .completion()\n .then(\n async (outcome) => {\n if (await abortStop) return;\n await this.registry.complete(handle.id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n },\n async () => {\n if (await abortStop) return;\n await this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n },\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n })\n .finally(() => signal?.removeEventListener('abort', abort));\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/bashRunService/index.ts"],"mappings":";;;;;;;;qBAoCa,0BAA0B;mBAElB;mBACA;mBACA;mBACA;mBACA;mBACA;EANnB,YACmB,UAAU,WACV,aAAa,cACb,OAAO,cACP,UAAU,iBACV,OAAO,QACP,cAAc;EAG3B,IAAI,SAAS,iBAAiB,QAAQ;UAyG9B;UAWN;;;;;;;;UAqCM;UA2BA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/bashRunService/index.ts"],"mappings":";;;;;;;;qBAsCa,0BAA0B;mBAElB;mBACA;mBACA;mBACA;mBACA;mBACA;EANnB,YACmB,UAAU,WACV,aAAa,cACb,OAAO,cACP,UAAU,iBACV,OAAO,QACP,cAAc;EAG3B,IAAI,SAAS,iBAAiB,QAAQ;UA8H9B;UAWN;;;;;;;;UAqCM;UAgDA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/bashRunService/index.ts"],"mappings":";;;;;;;;qBAoCa,0BAA0B;mBAElB;mBACA;mBACA;mBACA;mBACA;mBACA;EANnB,YACmB,UAAU,WACV,aAAa,cACb,OAAO,cACP,UAAU,iBACV,OAAO,QACP,cAAc;EAG3B,IAAI,SAAS,iBAAiB,QAAQ;UAyG9B;UAWN;;;;;;;;UAqCM;UA2BA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/bashRunService/index.ts"],"mappings":";;;;;;;;qBAsCa,0BAA0B;mBAElB;mBACA;mBACA;mBACA;mBACA;mBACA;EANnB,YACmB,UAAU,WACV,aAAa,cACb,OAAO,cACP,UAAU,iBACV,OAAO,QACP,cAAc;EAG3B,IAAI,SAAS,iBAAiB,QAAQ;UA8H9B;UAWN;;;;;;;;UAqCM;UAgDA"}
@@ -7,6 +7,8 @@ import { randomBytes } from "node:crypto";
7
7
  const TIMED_OUT = Symbol("threshold-reached");
8
8
  /** The command outlived the caller's explicit timeout and should be stopped. */
9
9
  const DEADLINE = Symbol("deadline-reached");
10
+ /** The caller cancelled the command and its owned runner should be stopped. */
11
+ const ABORTED = Symbol("aborted");
10
12
  function runnerId() {
11
13
  return `${Date.now().toString(36)}-${randomBytes(3).toString("base64url")}`;
12
14
  }
@@ -30,6 +32,12 @@ var BashRunService = class {
30
32
  const name = await this.namer.allocate(request.command, request.sessionId, request.name);
31
33
  const cwd = request.cwd ?? process.cwd();
32
34
  const interactive = request.interactive === true;
35
+ if (request.signal?.aborted) return {
36
+ kind: FAILED,
37
+ id,
38
+ name,
39
+ error: "Operation aborted"
40
+ };
33
41
  let handle;
34
42
  try {
35
43
  const rmuxHandle = await this.rmuxBackend.launch({
@@ -67,6 +75,15 @@ var BashRunService = class {
67
75
  name,
68
76
  error: "The command did not start, so it cannot be supervised"
69
77
  };
78
+ if (request.signal?.aborted) {
79
+ await handle.stop();
80
+ return {
81
+ kind: FAILED,
82
+ id,
83
+ name,
84
+ error: "Operation aborted"
85
+ };
86
+ }
70
87
  try {
71
88
  await this.registry.register({
72
89
  id,
@@ -89,12 +106,32 @@ var BashRunService = class {
89
106
  error: error instanceof Error ? error.message : String(error)
90
107
  };
91
108
  }
92
- if (request.background === true || interactive) return this.promote(handle, request.background === true ? "requested" : "interactive");
109
+ if (request.background === true || interactive) return this.promote(handle, request.background === true ? "requested" : "interactive", request.signal);
93
110
  const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : void 0;
94
111
  try {
95
- const outcome = await this.race(handle, request.timeoutMs);
112
+ const outcome = await this.race(handle, request.timeoutMs, request.signal);
96
113
  outputUpdates?.flush();
97
- if (outcome === TIMED_OUT) return this.promote(handle, "threshold");
114
+ if (outcome === TIMED_OUT) return this.promote(handle, "threshold", request.signal);
115
+ if (outcome === ABORTED) {
116
+ await handle.stop();
117
+ outputUpdates?.flush();
118
+ await this.registry.complete(id, {
119
+ reason: "stopped",
120
+ code: null,
121
+ signal: "SIGTERM"
122
+ });
123
+ return {
124
+ kind: COMPLETED,
125
+ id,
126
+ name,
127
+ output: handle.output(),
128
+ exitCode: null,
129
+ signal: "SIGTERM",
130
+ logPath: handle.logPath,
131
+ backend: handle.backend,
132
+ aborted: true
133
+ };
134
+ }
98
135
  if (outcome === DEADLINE) {
99
136
  await handle.stop();
100
137
  outputUpdates?.flush();
@@ -194,7 +231,7 @@ var BashRunService = class {
194
231
  * A rejected completion is returned rather than thrown so the caller can
195
232
  * report a start failure as a tool result instead of an exception.
196
233
  */
197
- async race(handle, timeoutMs) {
234
+ async race(handle, timeoutMs, signal) {
198
235
  const cancels = [];
199
236
  const racers = [handle.completion().catch((error) => error)];
200
237
  racers.push(new Promise((resolve) => {
@@ -203,13 +240,22 @@ var BashRunService = class {
203
240
  if (timeoutMs !== void 0 && timeoutMs > 0) racers.push(new Promise((resolve) => {
204
241
  cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));
205
242
  }));
243
+ if (signal) racers.push(new Promise((resolve) => {
244
+ if (signal.aborted) {
245
+ resolve(ABORTED);
246
+ return;
247
+ }
248
+ const abort = () => resolve(ABORTED);
249
+ signal.addEventListener("abort", abort, { once: true });
250
+ cancels.push(() => signal.removeEventListener("abort", abort));
251
+ }));
206
252
  try {
207
253
  return await Promise.race(racers);
208
254
  } finally {
209
255
  for (const cancel of cancels) cancel();
210
256
  }
211
257
  }
212
- async promote(handle, reason) {
258
+ async promote(handle, reason, signal) {
213
259
  if (handle.pid === void 0) return {
214
260
  kind: FAILED,
215
261
  id: handle.id,
@@ -218,18 +264,43 @@ var BashRunService = class {
218
264
  };
219
265
  await this.registry.markPromoted(handle.id);
220
266
  handle.detach();
221
- handle.completion().then((outcome) => this.registry.complete(handle.id, {
222
- reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,
223
- code: outcome.code,
224
- signal: outcome.signal
225
- }), () => this.registry.complete(handle.id, {
226
- reason: LAUNCHER_ERROR,
227
- code: null,
228
- signal: null
229
- })).catch((error) => {
267
+ let abortStop;
268
+ const abort = () => {
269
+ abortStop = handle.stop().then(async () => {
270
+ await this.registry.complete(handle.id, {
271
+ reason: "stopped",
272
+ code: null,
273
+ signal: "SIGTERM"
274
+ });
275
+ return true;
276
+ }).catch((error) => {
277
+ const message = error instanceof Error ? error.message : String(error);
278
+ process.emitWarning(`Failed to stop aborted runner ${handle.id}: ${message}`);
279
+ return false;
280
+ });
281
+ };
282
+ if (signal) {
283
+ if (signal.aborted) abort();
284
+ else signal.addEventListener("abort", abort, { once: true });
285
+ }
286
+ handle.completion().then(async (outcome) => {
287
+ if (await abortStop) return;
288
+ await this.registry.complete(handle.id, {
289
+ reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,
290
+ code: outcome.code,
291
+ signal: outcome.signal
292
+ });
293
+ }, async () => {
294
+ if (await abortStop) return;
295
+ await this.registry.complete(handle.id, {
296
+ reason: LAUNCHER_ERROR,
297
+ code: null,
298
+ signal: null
299
+ });
300
+ }).catch((error) => {
230
301
  const message = error instanceof Error ? error.message : String(error);
231
302
  process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);
232
- });
303
+ }).finally(() => signal?.removeEventListener("abort", abort));
233
304
  return {
234
305
  kind: "promoted",
235
306
  id: handle.id,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/bashRunService/index.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\n\nimport {\n COMPLETED,\n FAILED,\n LAUNCHER_ERROR,\n OUTPUT_UPDATE_POLL_MS,\n RUNNER_ID_RANDOM_BYTES,\n SIGNALED,\n} from '../../constants/bashRunService';\nimport type {\n BashRunRequest,\n BashRunResult,\n CompletedRun,\n IBashRunService,\n PromotedRun,\n} from '../../types/bashRunService';\nimport type { IClock } from '../../types/clock';\nimport type { ILauncher, RunHandle } from '../../types/launcher';\nimport type { IRmuxBackend } from '../../types/rmuxBackend';\nimport { type IRtkProcessor, RTK_FAILED_WARNING } from '../../types/rtkProcessor';\nimport type { IRunnerRegistry } from '../../types/runnerRegistry';\nimport { getBackgroundThresholdMs } from '../runnerConfig';\nimport type { IRunnerNamer } from '../runnerNamer/type';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\nexport class BashRunService implements IBashRunService {\n constructor(\n private readonly launcher: ILauncher,\n private readonly rmuxBackend: IRmuxBackend,\n private readonly namer: IRunnerNamer,\n private readonly registry: IRunnerRegistry,\n private readonly clock: IClock,\n private readonly rtkProcessor: IRtkProcessor,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? process.cwd();\n const interactive = request.interactive === true;\n\n let handle: RunHandle;\n try {\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n if (interactive && !rmuxHandle) {\n return { kind: FAILED, id, name, error: 'RMUX is required for interactive commands but is unavailable' };\n }\n handle =\n rmuxHandle ?? this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId });\n } catch (error) {\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive');\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold');\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n ...(await this.processLog(request.command, handle.logPath)),\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private async processLog(command: string, logPath: string): Promise<Pick<CompletedRun, 'rtkOutput' | 'rtkWarning'>> {\n try {\n const result = await this.rtkProcessor.process({ command, logPath });\n if (result.kind === 'processed') return { rtkOutput: result.result };\n if (result.kind === 'fallback') return { rtkWarning: result.warning };\n return {};\n } catch {\n return { rtkWarning: RTK_FAILED_WARNING };\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n ): Promise<Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE> {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(handle: RunHandle, reason: PromotedRun['reason']): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n void handle\n .completion()\n .then(\n (outcome) =>\n this.registry.complete(handle.id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n }),\n () => this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null }),\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n });\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/bashRunService/index.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\n\nimport {\n COMPLETED,\n FAILED,\n LAUNCHER_ERROR,\n OUTPUT_UPDATE_POLL_MS,\n RUNNER_ID_RANDOM_BYTES,\n SIGNALED,\n} from '../../constants/bashRunService';\nimport type {\n BashRunRequest,\n BashRunResult,\n CompletedRun,\n IBashRunService,\n PromotedRun,\n} from '../../types/bashRunService';\nimport type { IClock } from '../../types/clock';\nimport type { ILauncher, RunHandle } from '../../types/launcher';\nimport type { IRmuxBackend } from '../../types/rmuxBackend';\nimport { type IRtkProcessor, RTK_FAILED_WARNING } from '../../types/rtkProcessor';\nimport type { IRunnerRegistry } from '../../types/runnerRegistry';\nimport { getBackgroundThresholdMs } from '../runnerConfig';\nimport type { IRunnerNamer } from '../runnerNamer/type';\n\n/** The command outlived the background threshold and should be promoted. */\nconst TIMED_OUT = Symbol('threshold-reached');\n/** The command outlived the caller's explicit timeout and should be stopped. */\nconst DEADLINE = Symbol('deadline-reached');\n/** The caller cancelled the command and its owned runner should be stopped. */\nconst ABORTED = Symbol('aborted');\n\nfunction runnerId(): string {\n const timestamp = Date.now().toString(36);\n const random = randomBytes(RUNNER_ID_RANDOM_BYTES).toString('base64url');\n return `${timestamp}-${random}`;\n}\n\nexport class BashRunService implements IBashRunService {\n constructor(\n private readonly launcher: ILauncher,\n private readonly rmuxBackend: IRmuxBackend,\n private readonly namer: IRunnerNamer,\n private readonly registry: IRunnerRegistry,\n private readonly clock: IClock,\n private readonly rtkProcessor: IRtkProcessor,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? process.cwd();\n const interactive = request.interactive === true;\n if (request.signal?.aborted) return { kind: FAILED, id, name, error: 'Operation aborted' };\n\n let handle: RunHandle;\n try {\n const rmuxHandle = await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive,\n });\n if (interactive && !rmuxHandle) {\n return { kind: FAILED, id, name, error: 'RMUX is required for interactive commands but is unavailable' };\n }\n handle =\n rmuxHandle ?? this.launcher.launch({ id, name, command: request.command, cwd, sessionId: request.sessionId });\n } catch (error) {\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n if (request.signal?.aborted) {\n await handle.stop();\n return { kind: FAILED, id, name, error: 'Operation aborted' };\n }\n\n try {\n await this.registry.register({\n id,\n name,\n pid: handle.pid,\n command: request.command,\n cwd,\n logPath: handle.logPath,\n interactive,\n sessionId: request.sessionId,\n backend: handle.backend,\n ...(handle.backendTarget ? { backendTarget: handle.backendTarget } : {}),\n });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n // An interactive run has nothing to wait for: it is going to prompt, so\n // the model needs the runner name before it can answer.\n if (request.background === true || interactive) {\n return this.promote(handle, request.background === true ? 'requested' : 'interactive', request.signal);\n }\n\n const outputUpdates = request.onOutput ? this.followOutput(handle, request.onOutput) : undefined;\n try {\n const outcome = await this.race(handle, request.timeoutMs, request.signal);\n outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold', request.signal);\n }\n if (outcome === ABORTED) {\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n aborted: true,\n };\n }\n if (outcome === DEADLINE) {\n // An explicit timeout means the caller wants the command dead, not\n // supervised, so it is stopped rather than promoted.\n await handle.stop();\n outputUpdates?.flush();\n await this.registry.complete(id, { reason: 'timed_out', code: null, signal: 'SIGTERM' });\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: null,\n signal: 'SIGTERM',\n logPath: handle.logPath,\n backend: handle.backend,\n timedOut: true,\n };\n }\n if (outcome instanceof Error) {\n await this.registry.complete(id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n return { kind: FAILED, id, name, error: outcome.message };\n }\n\n await this.registry.complete(id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: handle.output(),\n exitCode: outcome.code,\n signal: outcome.signal,\n logPath: handle.logPath,\n backend: handle.backend,\n ...(await this.processLog(request.command, handle.logPath)),\n };\n } finally {\n outputUpdates?.dispose();\n }\n }\n\n private async processLog(command: string, logPath: string): Promise<Pick<CompletedRun, 'rtkOutput' | 'rtkWarning'>> {\n try {\n const result = await this.rtkProcessor.process({ command, logPath });\n if (result.kind === 'processed') return { rtkOutput: result.result };\n if (result.kind === 'fallback') return { rtkWarning: result.warning };\n return {};\n } catch {\n return { rtkWarning: RTK_FAILED_WARNING };\n }\n }\n\n private followOutput(\n handle: RunHandle,\n onOutput: NonNullable<BashRunRequest['onOutput']>,\n ): { flush(): void; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = (): void => {\n if (!active) return;\n const output = handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = (): void => {\n if (!active) return;\n flush();\n if (active) cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n };\n cancelPoll = this.clock.after(OUTPUT_UPDATE_POLL_MS, poll);\n return {\n flush,\n dispose: () => {\n active = false;\n cancelPoll?.();\n cancelPoll = undefined;\n },\n };\n }\n\n /**\n * Waits for the command, giving up at the background threshold or at the\n * caller's timeout, whichever comes first.\n *\n * A rejected completion is returned rather than thrown so the caller can\n * report a start failure as a tool result instead of an exception.\n */\n private async race(\n handle: RunHandle,\n timeoutMs: number | undefined,\n signal: AbortSignal | undefined,\n ): Promise<\n Awaited<ReturnType<RunHandle['completion']>> | Error | typeof TIMED_OUT | typeof DEADLINE | typeof ABORTED\n > {\n const cancels: Array<() => void> = [];\n const racers: Array<Promise<unknown>> = [handle.completion().catch((error: Error) => error)];\n\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(), () => resolve(TIMED_OUT)));\n }),\n );\n if (timeoutMs !== undefined && timeoutMs > 0) {\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(timeoutMs, () => resolve(DEADLINE)));\n }),\n );\n }\n if (signal) {\n racers.push(\n new Promise((resolve) => {\n if (signal.aborted) {\n resolve(ABORTED);\n return;\n }\n const abort = (): void => resolve(ABORTED);\n signal.addEventListener('abort', abort, { once: true });\n cancels.push(() => signal.removeEventListener('abort', abort));\n }),\n );\n }\n\n try {\n return (await Promise.race(racers)) as\n | Awaited<ReturnType<RunHandle['completion']>>\n | Error\n | typeof TIMED_OUT\n | typeof DEADLINE\n | typeof ABORTED;\n } finally {\n for (const cancel of cancels) cancel();\n }\n }\n\n private async promote(\n handle: RunHandle,\n reason: PromotedRun['reason'],\n signal: AbortSignal | undefined,\n ): Promise<BashRunResult> {\n if (handle.pid === undefined)\n return { kind: FAILED, id: handle.id, name: handle.name, error: 'The command did not start' };\n await this.registry.markPromoted(handle.id);\n // Registration first: an unregistered runner is invisible, and the handle\n // stops buffering the moment it is detached.\n handle.detach();\n let abortStop: Promise<boolean> | undefined;\n const abort = (): void => {\n abortStop = handle\n .stop()\n .then(async () => {\n await this.registry.complete(handle.id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return true;\n })\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to stop aborted runner ${handle.id}: ${message}`);\n return false;\n });\n };\n if (signal) {\n if (signal.aborted) abort();\n else signal.addEventListener('abort', abort, { once: true });\n }\n void handle\n .completion()\n .then(\n async (outcome) => {\n if (await abortStop) return;\n await this.registry.complete(handle.id, {\n reason: outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED,\n code: outcome.code,\n signal: outcome.signal,\n });\n },\n async () => {\n if (await abortStop) return;\n await this.registry.complete(handle.id, { reason: LAUNCHER_ERROR, code: null, signal: null });\n },\n )\n .catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.emitWarning(`Failed to complete runner ${handle.id}: ${message}`);\n })\n .finally(() => signal?.removeEventListener('abort', abort));\n\n return {\n kind: 'promoted',\n id: handle.id,\n name: handle.name,\n pid: handle.pid,\n logPath: handle.logPath,\n backend: handle.backend,\n reason,\n };\n }\n}\n"],"mappings":"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"}
@@ -31,7 +31,7 @@ function createBashTool(dependencies, renderers = {}) {
31
31
  promptGuidelines: bashPromptGuidelines(),
32
32
  parameters: require_bashTool.BashParamsSchema,
33
33
  renderShell: "self",
34
- async execute(_toolCallId, params, _signal, onUpdate, _ctx) {
34
+ async execute(_toolCallId, params, signal, onUpdate, _ctx) {
35
35
  const { command, timeout, background, interactive, name } = params;
36
36
  let onOutput;
37
37
  if (onUpdate) onUpdate(require_index$1.textResult(`Starting ${interactive === true ? "interactive runner" : background === true ? "background runner" : "command"}...`));
@@ -45,8 +45,10 @@ function createBashTool(dependencies, renderers = {}) {
45
45
  interactive,
46
46
  name,
47
47
  ...onOutput ? { onOutput } : {},
48
+ ...signal ? { signal } : {},
48
49
  sessionId: await dependencies.getSessionId()
49
50
  });
51
+ if (signal?.aborted) throw new Error("Operation aborted");
50
52
  } catch (error) {
51
53
  const message = error instanceof Error ? error.message : String(error);
52
54
  throw new Error([`Could not execute command: ${message}`, "Next: verify the command, runtime, and working directory. Retry only after correcting the cause."].join("\n"), { cause: error });
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["getBackgroundThresholdMs","MS_PER_SECOND","definePiTool","BASH_TOOL_NAME","BASH_TOOL_LABEL","BashParamsSchema","textResult","formatRunResult","parseResultPragma"],"sources":["../../../../src/services/bashTool/index.ts"],"sourcesContent":["import { definePiTool, type PiToolDeclaration } from '@agimon-ai/doompi-core/pi-extension';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME, MS_PER_SECOND } from '../../constants/bashTool';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport { type ToolResult } from '../../types/bashResult';\nimport type { BashRunResult } from '../../types/bashRunService';\nimport type { BashToolDependencies, BashToolRenderers } from '../../types/bashTool';\n\nexport const BASH_PROMPT_SNIPPET =\n 'Execute shell commands with bounded foreground output and supervised background runners';\n\n/** Written at registration time so the stated threshold matches the configured one. */\nexport function bashPromptGuidelines(thresholdMs = getBackgroundThresholdMs()): string[] {\n return [\n `A command still running after ${Math.round(thresholdMs / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path. You are messaged automatically when it exits, so never sleep, poll, or pgrep to wait for one.`,\n 'Pass background: true only for commands you know will remain active, such as dev servers, watchers, and tails.',\n 'Pass interactive: true only when the command will prompt for input. Use Runner Space for terminal input; avoid interactive mode otherwise because its logs are noisier.',\n 'Results are already bounded and the full log is saved to disk. Do not pipe to head or tail: it adds nothing and leaves the live log empty while the command runs.',\n 'On failure, use the returned output first. Inspect the saved log only when the result says output was truncated or no useful output was returned. Never retry an unchanged command merely to recover output.',\n 'Stop background runners when they are no longer needed. Every runner is stopped automatically when the session ends.',\n ];\n}\n\n/**\n * Registers a tool named `bash`, replacing pi's built-in.\n *\n * The name is deliberate: hooks, guardrails and doom-pi's dispatcher all key on\n * `bash`, and they keep working only while the replacement keeps the name.\n */\nexport function createBashTool(\n dependencies: BashToolDependencies,\n renderers: BashToolRenderers = {},\n): PiToolDeclaration {\n return definePiTool({\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path. Do not rerun a command merely to recover output; inspect the saved log only when the result identifies missing context.',\n promptSnippet: BASH_PROMPT_SNIPPET,\n promptGuidelines: bashPromptGuidelines(),\n parameters: BashParamsSchema,\n // Bash output already carries its own status glyphs and ANSI colours. Owning\n // the shell keeps Pi from filling every successful command with the global\n // toolSuccessBg, which overwhelms long logs and diffs.\n renderShell: 'self',\n\n async execute(_toolCallId, params, _signal, onUpdate, _ctx): Promise<ToolResult> {\n const { command, timeout, background, interactive, name } = params as BashParams;\n let onOutput: ((output: string) => void) | undefined;\n if (onUpdate) {\n const mode =\n interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command';\n onUpdate(textResult(`Starting ${mode}...`));\n }\n if (background !== true && interactive !== true && onUpdate) {\n onOutput = (output) => onUpdate(textResult(output));\n }\n let result: BashRunResult;\n try {\n result = await dependencies.bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n sessionId: await dependencies.getSessionId(),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n [\n `Could not execute command: ${message}`,\n 'Next: verify the command, runtime, and working directory. Retry only after correcting the cause.',\n ].join('\\n'),\n { cause: error },\n );\n }\n\n if (result.kind === 'promoted') dependencies.onRunnerStarted(result.id);\n return formatRunResult(result, parseResultPragma(params.command), dependencies.summarizeLog);\n },\n\n ...renderers,\n });\n}\n"],"mappings":";;;;;;AAUA,MAAa,sBACX;;AAGF,SAAgB,qBAAqB,cAAcA,cAAAA,yBAAyB,GAAa;CACvF,OAAO;EACL,iCAAiC,KAAK,MAAM,cAAcC,mBAAAA,aAAa,EAAE;EACzE;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;AAQA,SAAgB,eACd,cACA,YAA+B,CAAC,GACb;CACnB,QAAA,GAAOC,oCAAAA,aAAAA,CAAa;EAClB,MAAMC,mBAAAA;EACN,OAAOC,mBAAAA;EACP,aACE;EACF,eAAe;EACf,kBAAkB,qBAAqB;EACvC,YAAYC,iBAAAA;EAIZ,aAAa;EAEb,MAAM,QAAQ,aAAa,QAAQ,SAAS,UAAU,MAA2B;GAC/E,MAAM,EAAE,SAAS,SAAS,YAAY,aAAa,SAAS;GAC5D,IAAI;GACJ,IAAI,UAGF,SAASC,gBAAAA,WAAW,YADlB,gBAAgB,OAAO,uBAAuB,eAAe,OAAO,sBAAsB,UACvD,IAAI,CAAC;GAE5C,IAAI,eAAe,QAAQ,gBAAgB,QAAQ,UACjD,YAAY,WAAW,SAASA,gBAAAA,WAAW,MAAM,CAAC;GAEpD,IAAI;GACJ,IAAI;IACF,SAAS,MAAM,aAAa,eAAe,IAAI;KAC7C;KACA,WAAW,YAAY,KAAA,IAAY,KAAA,IAAY,UAAUL,mBAAAA;KACzD;KACA;KACA;KACA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;KAC/B,WAAW,MAAM,aAAa,aAAa;IAC7C,CAAC;GACH,SAAS,OAAO;IACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IACrE,MAAM,IAAI,MACR,CACE,8BAA8B,WAC9B,kGACF,CAAC,CAAC,KAAK,IAAI,GACX,EAAE,OAAO,MAAM,CACjB;GACF;GAEA,IAAI,OAAO,SAAS,YAAY,aAAa,gBAAgB,OAAO,EAAE;GACtE,OAAOM,gBAAAA,gBAAgB,QAAQC,gBAAAA,kBAAkB,OAAO,OAAO,GAAG,aAAa,YAAY;EAC7F;EAEA,GAAG;CACL,CAAC;AACH"}
1
+ {"version":3,"file":"index.cjs","names":["getBackgroundThresholdMs","MS_PER_SECOND","definePiTool","BASH_TOOL_NAME","BASH_TOOL_LABEL","BashParamsSchema","textResult","formatRunResult","parseResultPragma"],"sources":["../../../../src/services/bashTool/index.ts"],"sourcesContent":["import { definePiTool, type PiToolDeclaration } from '@agimon-ai/doompi-core/pi-extension';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME, MS_PER_SECOND } from '../../constants/bashTool';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport { type ToolResult } from '../../types/bashResult';\nimport type { BashRunResult } from '../../types/bashRunService';\nimport type { BashToolDependencies, BashToolRenderers } from '../../types/bashTool';\n\nexport const BASH_PROMPT_SNIPPET =\n 'Execute shell commands with bounded foreground output and supervised background runners';\n\n/** Written at registration time so the stated threshold matches the configured one. */\nexport function bashPromptGuidelines(thresholdMs = getBackgroundThresholdMs()): string[] {\n return [\n `A command still running after ${Math.round(thresholdMs / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path. You are messaged automatically when it exits, so never sleep, poll, or pgrep to wait for one.`,\n 'Pass background: true only for commands you know will remain active, such as dev servers, watchers, and tails.',\n 'Pass interactive: true only when the command will prompt for input. Use Runner Space for terminal input; avoid interactive mode otherwise because its logs are noisier.',\n 'Results are already bounded and the full log is saved to disk. Do not pipe to head or tail: it adds nothing and leaves the live log empty while the command runs.',\n 'On failure, use the returned output first. Inspect the saved log only when the result says output was truncated or no useful output was returned. Never retry an unchanged command merely to recover output.',\n 'Stop background runners when they are no longer needed. Every runner is stopped automatically when the session ends.',\n ];\n}\n\n/**\n * Registers a tool named `bash`, replacing pi's built-in.\n *\n * The name is deliberate: hooks, guardrails and doom-pi's dispatcher all key on\n * `bash`, and they keep working only while the replacement keeps the name.\n */\nexport function createBashTool(\n dependencies: BashToolDependencies,\n renderers: BashToolRenderers = {},\n): PiToolDeclaration {\n return definePiTool({\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path. Do not rerun a command merely to recover output; inspect the saved log only when the result identifies missing context.',\n promptSnippet: BASH_PROMPT_SNIPPET,\n promptGuidelines: bashPromptGuidelines(),\n parameters: BashParamsSchema,\n // Bash output already carries its own status glyphs and ANSI colours. Owning\n // the shell keeps Pi from filling every successful command with the global\n // toolSuccessBg, which overwhelms long logs and diffs.\n renderShell: 'self',\n\n async execute(_toolCallId, params, signal, onUpdate, _ctx): Promise<ToolResult> {\n const { command, timeout, background, interactive, name } = params as BashParams;\n let onOutput: ((output: string) => void) | undefined;\n if (onUpdate) {\n const mode =\n interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command';\n onUpdate(textResult(`Starting ${mode}...`));\n }\n if (background !== true && interactive !== true && onUpdate) {\n onOutput = (output) => onUpdate(textResult(output));\n }\n let result: BashRunResult;\n try {\n result = await dependencies.bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n ...(signal ? { signal } : {}),\n sessionId: await dependencies.getSessionId(),\n });\n if (signal?.aborted) throw new Error('Operation aborted');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n [\n `Could not execute command: ${message}`,\n 'Next: verify the command, runtime, and working directory. Retry only after correcting the cause.',\n ].join('\\n'),\n { cause: error },\n );\n }\n\n if (result.kind === 'promoted') dependencies.onRunnerStarted(result.id);\n return formatRunResult(result, parseResultPragma(params.command), dependencies.summarizeLog);\n },\n\n ...renderers,\n });\n}\n"],"mappings":";;;;;;AAUA,MAAa,sBACX;;AAGF,SAAgB,qBAAqB,cAAcA,cAAAA,yBAAyB,GAAa;CACvF,OAAO;EACL,iCAAiC,KAAK,MAAM,cAAcC,mBAAAA,aAAa,EAAE;EACzE;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;AAQA,SAAgB,eACd,cACA,YAA+B,CAAC,GACb;CACnB,QAAA,GAAOC,oCAAAA,aAAAA,CAAa;EAClB,MAAMC,mBAAAA;EACN,OAAOC,mBAAAA;EACP,aACE;EACF,eAAe;EACf,kBAAkB,qBAAqB;EACvC,YAAYC,iBAAAA;EAIZ,aAAa;EAEb,MAAM,QAAQ,aAAa,QAAQ,QAAQ,UAAU,MAA2B;GAC9E,MAAM,EAAE,SAAS,SAAS,YAAY,aAAa,SAAS;GAC5D,IAAI;GACJ,IAAI,UAGF,SAASC,gBAAAA,WAAW,YADlB,gBAAgB,OAAO,uBAAuB,eAAe,OAAO,sBAAsB,UACvD,IAAI,CAAC;GAE5C,IAAI,eAAe,QAAQ,gBAAgB,QAAQ,UACjD,YAAY,WAAW,SAASA,gBAAAA,WAAW,MAAM,CAAC;GAEpD,IAAI;GACJ,IAAI;IACF,SAAS,MAAM,aAAa,eAAe,IAAI;KAC7C;KACA,WAAW,YAAY,KAAA,IAAY,KAAA,IAAY,UAAUL,mBAAAA;KACzD;KACA;KACA;KACA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;KAC/B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;KAC3B,WAAW,MAAM,aAAa,aAAa;IAC7C,CAAC;IACD,IAAI,QAAQ,SAAS,MAAM,IAAI,MAAM,mBAAmB;GAC1D,SAAS,OAAO;IACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IACrE,MAAM,IAAI,MACR,CACE,8BAA8B,WAC9B,kGACF,CAAC,CAAC,KAAK,IAAI,GACX,EAAE,OAAO,MAAM,CACjB;GACF;GAEA,IAAI,OAAO,SAAS,YAAY,aAAa,gBAAgB,OAAO,EAAE;GACtE,OAAOM,gBAAAA,gBAAgB,QAAQC,gBAAAA,kBAAkB,OAAO,OAAO,GAAG,aAAa,YAAY;EAC7F;EAEA,GAAG;CACL,CAAC;AACH"}
@@ -31,7 +31,7 @@ function createBashTool(dependencies, renderers = {}) {
31
31
  promptGuidelines: bashPromptGuidelines(),
32
32
  parameters: BashParamsSchema,
33
33
  renderShell: "self",
34
- async execute(_toolCallId, params, _signal, onUpdate, _ctx) {
34
+ async execute(_toolCallId, params, signal, onUpdate, _ctx) {
35
35
  const { command, timeout, background, interactive, name } = params;
36
36
  let onOutput;
37
37
  if (onUpdate) onUpdate(textResult(`Starting ${interactive === true ? "interactive runner" : background === true ? "background runner" : "command"}...`));
@@ -45,8 +45,10 @@ function createBashTool(dependencies, renderers = {}) {
45
45
  interactive,
46
46
  name,
47
47
  ...onOutput ? { onOutput } : {},
48
+ ...signal ? { signal } : {},
48
49
  sessionId: await dependencies.getSessionId()
49
50
  });
51
+ if (signal?.aborted) throw new Error("Operation aborted");
50
52
  } catch (error) {
51
53
  const message = error instanceof Error ? error.message : String(error);
52
54
  throw new Error([`Could not execute command: ${message}`, "Next: verify the command, runtime, and working directory. Retry only after correcting the cause."].join("\n"), { cause: error });
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/bashTool/index.ts"],"sourcesContent":["import { definePiTool, type PiToolDeclaration } from '@agimon-ai/doompi-core/pi-extension';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME, MS_PER_SECOND } from '../../constants/bashTool';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport { type ToolResult } from '../../types/bashResult';\nimport type { BashRunResult } from '../../types/bashRunService';\nimport type { BashToolDependencies, BashToolRenderers } from '../../types/bashTool';\n\nexport const BASH_PROMPT_SNIPPET =\n 'Execute shell commands with bounded foreground output and supervised background runners';\n\n/** Written at registration time so the stated threshold matches the configured one. */\nexport function bashPromptGuidelines(thresholdMs = getBackgroundThresholdMs()): string[] {\n return [\n `A command still running after ${Math.round(thresholdMs / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path. You are messaged automatically when it exits, so never sleep, poll, or pgrep to wait for one.`,\n 'Pass background: true only for commands you know will remain active, such as dev servers, watchers, and tails.',\n 'Pass interactive: true only when the command will prompt for input. Use Runner Space for terminal input; avoid interactive mode otherwise because its logs are noisier.',\n 'Results are already bounded and the full log is saved to disk. Do not pipe to head or tail: it adds nothing and leaves the live log empty while the command runs.',\n 'On failure, use the returned output first. Inspect the saved log only when the result says output was truncated or no useful output was returned. Never retry an unchanged command merely to recover output.',\n 'Stop background runners when they are no longer needed. Every runner is stopped automatically when the session ends.',\n ];\n}\n\n/**\n * Registers a tool named `bash`, replacing pi's built-in.\n *\n * The name is deliberate: hooks, guardrails and doom-pi's dispatcher all key on\n * `bash`, and they keep working only while the replacement keeps the name.\n */\nexport function createBashTool(\n dependencies: BashToolDependencies,\n renderers: BashToolRenderers = {},\n): PiToolDeclaration {\n return definePiTool({\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path. Do not rerun a command merely to recover output; inspect the saved log only when the result identifies missing context.',\n promptSnippet: BASH_PROMPT_SNIPPET,\n promptGuidelines: bashPromptGuidelines(),\n parameters: BashParamsSchema,\n // Bash output already carries its own status glyphs and ANSI colours. Owning\n // the shell keeps Pi from filling every successful command with the global\n // toolSuccessBg, which overwhelms long logs and diffs.\n renderShell: 'self',\n\n async execute(_toolCallId, params, _signal, onUpdate, _ctx): Promise<ToolResult> {\n const { command, timeout, background, interactive, name } = params as BashParams;\n let onOutput: ((output: string) => void) | undefined;\n if (onUpdate) {\n const mode =\n interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command';\n onUpdate(textResult(`Starting ${mode}...`));\n }\n if (background !== true && interactive !== true && onUpdate) {\n onOutput = (output) => onUpdate(textResult(output));\n }\n let result: BashRunResult;\n try {\n result = await dependencies.bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n sessionId: await dependencies.getSessionId(),\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n [\n `Could not execute command: ${message}`,\n 'Next: verify the command, runtime, and working directory. Retry only after correcting the cause.',\n ].join('\\n'),\n { cause: error },\n );\n }\n\n if (result.kind === 'promoted') dependencies.onRunnerStarted(result.id);\n return formatRunResult(result, parseResultPragma(params.command), dependencies.summarizeLog);\n },\n\n ...renderers,\n });\n}\n"],"mappings":";;;;;;AAUA,MAAa,sBACX;;AAGF,SAAgB,qBAAqB,cAAc,yBAAyB,GAAa;CACvF,OAAO;EACL,iCAAiC,KAAK,MAAM,cAAc,aAAa,EAAE;EACzE;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;AAQA,SAAgB,eACd,cACA,YAA+B,CAAC,GACb;CACnB,OAAO,aAAa;EAClB,MAAM;EACN,OAAO;EACP,aACE;EACF,eAAe;EACf,kBAAkB,qBAAqB;EACvC,YAAY;EAIZ,aAAa;EAEb,MAAM,QAAQ,aAAa,QAAQ,SAAS,UAAU,MAA2B;GAC/E,MAAM,EAAE,SAAS,SAAS,YAAY,aAAa,SAAS;GAC5D,IAAI;GACJ,IAAI,UAGF,SAAS,WAAW,YADlB,gBAAgB,OAAO,uBAAuB,eAAe,OAAO,sBAAsB,UACvD,IAAI,CAAC;GAE5C,IAAI,eAAe,QAAQ,gBAAgB,QAAQ,UACjD,YAAY,WAAW,SAAS,WAAW,MAAM,CAAC;GAEpD,IAAI;GACJ,IAAI;IACF,SAAS,MAAM,aAAa,eAAe,IAAI;KAC7C;KACA,WAAW,YAAY,KAAA,IAAY,KAAA,IAAY,UAAU;KACzD;KACA;KACA;KACA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;KAC/B,WAAW,MAAM,aAAa,aAAa;IAC7C,CAAC;GACH,SAAS,OAAO;IACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IACrE,MAAM,IAAI,MACR,CACE,8BAA8B,WAC9B,kGACF,CAAC,CAAC,KAAK,IAAI,GACX,EAAE,OAAO,MAAM,CACjB;GACF;GAEA,IAAI,OAAO,SAAS,YAAY,aAAa,gBAAgB,OAAO,EAAE;GACtE,OAAO,gBAAgB,QAAQ,kBAAkB,OAAO,OAAO,GAAG,aAAa,YAAY;EAC7F;EAEA,GAAG;CACL,CAAC;AACH"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/bashTool/index.ts"],"sourcesContent":["import { definePiTool, type PiToolDeclaration } from '@agimon-ai/doompi-core/pi-extension';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME, MS_PER_SECOND } from '../../constants/bashTool';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport { type ToolResult } from '../../types/bashResult';\nimport type { BashRunResult } from '../../types/bashRunService';\nimport type { BashToolDependencies, BashToolRenderers } from '../../types/bashTool';\n\nexport const BASH_PROMPT_SNIPPET =\n 'Execute shell commands with bounded foreground output and supervised background runners';\n\n/** Written at registration time so the stated threshold matches the configured one. */\nexport function bashPromptGuidelines(thresholdMs = getBackgroundThresholdMs()): string[] {\n return [\n `A command still running after ${Math.round(thresholdMs / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path. You are messaged automatically when it exits, so never sleep, poll, or pgrep to wait for one.`,\n 'Pass background: true only for commands you know will remain active, such as dev servers, watchers, and tails.',\n 'Pass interactive: true only when the command will prompt for input. Use Runner Space for terminal input; avoid interactive mode otherwise because its logs are noisier.',\n 'Results are already bounded and the full log is saved to disk. Do not pipe to head or tail: it adds nothing and leaves the live log empty while the command runs.',\n 'On failure, use the returned output first. Inspect the saved log only when the result says output was truncated or no useful output was returned. Never retry an unchanged command merely to recover output.',\n 'Stop background runners when they are no longer needed. Every runner is stopped automatically when the session ends.',\n ];\n}\n\n/**\n * Registers a tool named `bash`, replacing pi's built-in.\n *\n * The name is deliberate: hooks, guardrails and doom-pi's dispatcher all key on\n * `bash`, and they keep working only while the replacement keeps the name.\n */\nexport function createBashTool(\n dependencies: BashToolDependencies,\n renderers: BashToolRenderers = {},\n): PiToolDeclaration {\n return definePiTool({\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path. Do not rerun a command merely to recover output; inspect the saved log only when the result identifies missing context.',\n promptSnippet: BASH_PROMPT_SNIPPET,\n promptGuidelines: bashPromptGuidelines(),\n parameters: BashParamsSchema,\n // Bash output already carries its own status glyphs and ANSI colours. Owning\n // the shell keeps Pi from filling every successful command with the global\n // toolSuccessBg, which overwhelms long logs and diffs.\n renderShell: 'self',\n\n async execute(_toolCallId, params, signal, onUpdate, _ctx): Promise<ToolResult> {\n const { command, timeout, background, interactive, name } = params as BashParams;\n let onOutput: ((output: string) => void) | undefined;\n if (onUpdate) {\n const mode =\n interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command';\n onUpdate(textResult(`Starting ${mode}...`));\n }\n if (background !== true && interactive !== true && onUpdate) {\n onOutput = (output) => onUpdate(textResult(output));\n }\n let result: BashRunResult;\n try {\n result = await dependencies.bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n ...(signal ? { signal } : {}),\n sessionId: await dependencies.getSessionId(),\n });\n if (signal?.aborted) throw new Error('Operation aborted');\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n [\n `Could not execute command: ${message}`,\n 'Next: verify the command, runtime, and working directory. Retry only after correcting the cause.',\n ].join('\\n'),\n { cause: error },\n );\n }\n\n if (result.kind === 'promoted') dependencies.onRunnerStarted(result.id);\n return formatRunResult(result, parseResultPragma(params.command), dependencies.summarizeLog);\n },\n\n ...renderers,\n });\n}\n"],"mappings":";;;;;;AAUA,MAAa,sBACX;;AAGF,SAAgB,qBAAqB,cAAc,yBAAyB,GAAa;CACvF,OAAO;EACL,iCAAiC,KAAK,MAAM,cAAc,aAAa,EAAE;EACzE;EACA;EACA;EACA;EACA;CACF;AACF;;;;;;;AAQA,SAAgB,eACd,cACA,YAA+B,CAAC,GACb;CACnB,OAAO,aAAa;EAClB,MAAM;EACN,OAAO;EACP,aACE;EACF,eAAe;EACf,kBAAkB,qBAAqB;EACvC,YAAY;EAIZ,aAAa;EAEb,MAAM,QAAQ,aAAa,QAAQ,QAAQ,UAAU,MAA2B;GAC9E,MAAM,EAAE,SAAS,SAAS,YAAY,aAAa,SAAS;GAC5D,IAAI;GACJ,IAAI,UAGF,SAAS,WAAW,YADlB,gBAAgB,OAAO,uBAAuB,eAAe,OAAO,sBAAsB,UACvD,IAAI,CAAC;GAE5C,IAAI,eAAe,QAAQ,gBAAgB,QAAQ,UACjD,YAAY,WAAW,SAAS,WAAW,MAAM,CAAC;GAEpD,IAAI;GACJ,IAAI;IACF,SAAS,MAAM,aAAa,eAAe,IAAI;KAC7C;KACA,WAAW,YAAY,KAAA,IAAY,KAAA,IAAY,UAAU;KACzD;KACA;KACA;KACA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;KAC/B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;KAC3B,WAAW,MAAM,aAAa,aAAa;IAC7C,CAAC;IACD,IAAI,QAAQ,SAAS,MAAM,IAAI,MAAM,mBAAmB;GAC1D,SAAS,OAAO;IACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IACrE,MAAM,IAAI,MACR,CACE,8BAA8B,WAC9B,kGACF,CAAC,CAAC,KAAK,IAAI,GACX,EAAE,OAAO,MAAM,CACjB;GACF;GAEA,IAAI,OAAO,SAAS,YAAY,aAAa,gBAAgB,OAAO,EAAE;GACtE,OAAO,gBAAgB,QAAQ,kBAAkB,OAAO,OAAO,GAAG,aAAa,YAAY;EAC7F;EAEA,GAAG;CACL,CAAC;AACH"}
@@ -33,9 +33,11 @@ function createHeadlessBashTool(bashRunService, summarizeLog) {
33
33
  interactive,
34
34
  name,
35
35
  ...onOutput ? { onOutput } : {},
36
+ ...signal ? { signal } : {},
36
37
  cwd: context.cwd,
37
38
  sessionId: context.sessionId
38
39
  });
40
+ if (signal?.aborted) throw new Error("Operation aborted");
39
41
  return require_index$1.formatRunResult(result, require_index$1.parseResultPragma(command), summarizeLog);
40
42
  }
41
43
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["BASH_TOOL_NAME","BASH_TOOL_LABEL","getBackgroundThresholdMs","MS_PER_SECOND","BashParamsSchema","textResult","formatRunResult","parseResultPragma","COMMAND_NAME","SERVER_COMMAND_DESCRIPTION","parseRunnersCommand","stopRunnerProcess"],"sources":["../../../../src/services/headless/index.ts"],"sourcesContent":["import type {\n DoomHeadlessCommand,\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME } from '../../constants/bashTool';\nimport { MS_PER_SECOND } from '../../constants/headless';\nimport { COMMAND_NAME, SERVER_COMMAND_DESCRIPTION } from '../../constants/runners';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { stopRunnerProcess } from '../../services/reconcile';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport type { RunnerDependencies } from '../../services/runnerDependencies/type';\nimport { parseRunnersCommand } from '../../services/runnersCommand';\nimport { type LogSummarizer } from '../../types/bashResult';\nimport type { IBashRunService } from '../../types/bashRunService';\n\nexport function createHeadlessBashTool(\n bashRunService: IBashRunService,\n summarizeLog?: LogSummarizer,\n): DoomHeadlessTool<typeof BashParamsSchema> {\n return {\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path.',\n promptSnippet: 'Execute shell commands with bounded foreground output and supervised background runners',\n promptGuidelines: [\n `A command still running after ${Math.round(getBackgroundThresholdMs() / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path.`,\n 'Pass background: true for commands that should remain active, such as dev servers, watchers, and tails.',\n 'Stop background runners when they are no longer needed.',\n ],\n parameters: BashParamsSchema,\n executionMode: 'serial',\n execute: async (\n _toolCallId: string,\n params: BashParams,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const { command, timeout, background, interactive, name } = params;\n if (signal?.aborted) throw new Error('Operation aborted');\n if (onUpdate)\n onUpdate(\n textResult(\n `Starting ${interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command'}...`,\n ),\n );\n let onOutput: ((output: string) => void) | undefined;\n if (background !== true && interactive !== true && onUpdate) onOutput = (output) => onUpdate(textResult(output));\n const result = await bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n cwd: context.cwd,\n sessionId: context.sessionId,\n });\n return formatRunResult(result, parseResultPragma(command), summarizeLog);\n },\n };\n}\n\nexport function createHeadlessRunnersCommand(dependencies: RunnerDependencies): DoomHeadlessCommand {\n return {\n name: COMMAND_NAME,\n description: SERVER_COMMAND_DESCRIPTION,\n async execute(args: string, context: DoomHeadlessExecutionContext) {\n const request = parseRunnersCommand(args);\n if (request.kind === 'space') {\n const active = await dependencies.runnerRegistry.listBySession(context.sessionId);\n await context.client.notify({\n body: `${active.length} active runner${active.length === 1 ? '' : 's'}.`,\n level: 'info',\n });\n return;\n }\n if (request.kind === 'start') {\n if (!request.command) throw new Error('Usage: /runners start [name=x] [cwd=y] [interactive=true] -- <command>');\n const result = await dependencies.bashRunService.run({\n command: request.command,\n background: true,\n sessionId: context.sessionId,\n cwd: request.cwd === undefined ? context.cwd : request.cwd,\n ...(request.name ? { name: request.name } : {}),\n ...(request.interactive ? { interactive: true } : {}),\n });\n await context.client.notify({\n body: formatRunResult(result).content[0]?.text ?? 'Runner started.',\n level: 'info',\n });\n return;\n }\n if (!request.id) throw new Error('Usage: /runners stop <runner-id> [reason]');\n const record = await dependencies.runnerRegistry.get(request.id, context.sessionId);\n if (!record) {\n await context.client.notify({ body: `No active runner ${request.id} in this session.`, level: 'warning' });\n return;\n }\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend);\n if (stopped) {\n await dependencies.runnerRegistry.complete(\n request.id,\n {\n reason: 'stopped',\n code: null,\n signal: null,\n ...(request.reason ? { stopReason: request.reason } : {}),\n },\n context.sessionId,\n );\n }\n await context.client.notify({\n body: stopped ? `Stopped runner ${request.id}.` : `Could not stop runner ${request.id}.`,\n level: stopped ? 'info' : 'error',\n });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["BASH_TOOL_NAME","BASH_TOOL_LABEL","getBackgroundThresholdMs","MS_PER_SECOND","BashParamsSchema","textResult","formatRunResult","parseResultPragma","COMMAND_NAME","SERVER_COMMAND_DESCRIPTION","parseRunnersCommand","stopRunnerProcess"],"sources":["../../../../src/services/headless/index.ts"],"sourcesContent":["import type {\n DoomHeadlessCommand,\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME } from '../../constants/bashTool';\nimport { MS_PER_SECOND } from '../../constants/headless';\nimport { COMMAND_NAME, SERVER_COMMAND_DESCRIPTION } from '../../constants/runners';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { stopRunnerProcess } from '../../services/reconcile';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport type { RunnerDependencies } from '../../services/runnerDependencies/type';\nimport { parseRunnersCommand } from '../../services/runnersCommand';\nimport { type LogSummarizer } from '../../types/bashResult';\nimport type { IBashRunService } from '../../types/bashRunService';\n\nexport function createHeadlessBashTool(\n bashRunService: IBashRunService,\n summarizeLog?: LogSummarizer,\n): DoomHeadlessTool<typeof BashParamsSchema> {\n return {\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path.',\n promptSnippet: 'Execute shell commands with bounded foreground output and supervised background runners',\n promptGuidelines: [\n `A command still running after ${Math.round(getBackgroundThresholdMs() / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path.`,\n 'Pass background: true for commands that should remain active, such as dev servers, watchers, and tails.',\n 'Stop background runners when they are no longer needed.',\n ],\n parameters: BashParamsSchema,\n executionMode: 'serial',\n execute: async (\n _toolCallId: string,\n params: BashParams,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const { command, timeout, background, interactive, name } = params;\n if (signal?.aborted) throw new Error('Operation aborted');\n if (onUpdate)\n onUpdate(\n textResult(\n `Starting ${interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command'}...`,\n ),\n );\n let onOutput: ((output: string) => void) | undefined;\n if (background !== true && interactive !== true && onUpdate) onOutput = (output) => onUpdate(textResult(output));\n const result = await bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n ...(signal ? { signal } : {}),\n cwd: context.cwd,\n sessionId: context.sessionId,\n });\n if (signal?.aborted) throw new Error('Operation aborted');\n return formatRunResult(result, parseResultPragma(command), summarizeLog);\n },\n };\n}\n\nexport function createHeadlessRunnersCommand(dependencies: RunnerDependencies): DoomHeadlessCommand {\n return {\n name: COMMAND_NAME,\n description: SERVER_COMMAND_DESCRIPTION,\n async execute(args: string, context: DoomHeadlessExecutionContext) {\n const request = parseRunnersCommand(args);\n if (request.kind === 'space') {\n const active = await dependencies.runnerRegistry.listBySession(context.sessionId);\n await context.client.notify({\n body: `${active.length} active runner${active.length === 1 ? '' : 's'}.`,\n level: 'info',\n });\n return;\n }\n if (request.kind === 'start') {\n if (!request.command) throw new Error('Usage: /runners start [name=x] [cwd=y] [interactive=true] -- <command>');\n const result = await dependencies.bashRunService.run({\n command: request.command,\n background: true,\n sessionId: context.sessionId,\n cwd: request.cwd === undefined ? context.cwd : request.cwd,\n ...(request.name ? { name: request.name } : {}),\n ...(request.interactive ? { interactive: true } : {}),\n });\n await context.client.notify({\n body: formatRunResult(result).content[0]?.text ?? 'Runner started.',\n level: 'info',\n });\n return;\n }\n if (!request.id) throw new Error('Usage: /runners stop <runner-id> [reason]');\n const record = await dependencies.runnerRegistry.get(request.id, context.sessionId);\n if (!record) {\n await context.client.notify({ body: `No active runner ${request.id} in this session.`, level: 'warning' });\n return;\n }\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend);\n if (stopped) {\n await dependencies.runnerRegistry.complete(\n request.id,\n {\n reason: 'stopped',\n code: null,\n signal: null,\n ...(request.reason ? { stopReason: request.reason } : {}),\n },\n context.sessionId,\n );\n }\n await context.client.notify({\n body: stopped ? `Stopped runner ${request.id}.` : `Could not stop runner ${request.id}.`,\n level: stopped ? 'info' : 'error',\n });\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/headless/index.ts"],"mappings":";;;;;;wBAmBgB,uBACd,gBAAgB,iBAChB,eAAe,gBACd,wBAAwB;wBA8CX,6BAA6B,cAAc,qBAAqB"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/headless/index.ts"],"mappings":";;;;;;wBAmBgB,uBACd,gBAAgB,iBAChB,eAAe,gBACd,wBAAwB;wBAgDX,6BAA6B,cAAc,qBAAqB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/headless/index.ts"],"mappings":";;;;;;wBAmBgB,uBACd,gBAAgB,iBAChB,eAAe,gBACd,wBAAwB;wBA8CX,6BAA6B,cAAc,qBAAqB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/headless/index.ts"],"mappings":";;;;;;wBAmBgB,uBACd,gBAAgB,iBAChB,eAAe,gBACd,wBAAwB;wBAgDX,6BAA6B,cAAc,qBAAqB"}
@@ -33,9 +33,11 @@ function createHeadlessBashTool(bashRunService, summarizeLog) {
33
33
  interactive,
34
34
  name,
35
35
  ...onOutput ? { onOutput } : {},
36
+ ...signal ? { signal } : {},
36
37
  cwd: context.cwd,
37
38
  sessionId: context.sessionId
38
39
  });
40
+ if (signal?.aborted) throw new Error("Operation aborted");
39
41
  return formatRunResult(result, parseResultPragma(command), summarizeLog);
40
42
  }
41
43
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/headless/index.ts"],"sourcesContent":["import type {\n DoomHeadlessCommand,\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME } from '../../constants/bashTool';\nimport { MS_PER_SECOND } from '../../constants/headless';\nimport { COMMAND_NAME, SERVER_COMMAND_DESCRIPTION } from '../../constants/runners';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { stopRunnerProcess } from '../../services/reconcile';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport type { RunnerDependencies } from '../../services/runnerDependencies/type';\nimport { parseRunnersCommand } from '../../services/runnersCommand';\nimport { type LogSummarizer } from '../../types/bashResult';\nimport type { IBashRunService } from '../../types/bashRunService';\n\nexport function createHeadlessBashTool(\n bashRunService: IBashRunService,\n summarizeLog?: LogSummarizer,\n): DoomHeadlessTool<typeof BashParamsSchema> {\n return {\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path.',\n promptSnippet: 'Execute shell commands with bounded foreground output and supervised background runners',\n promptGuidelines: [\n `A command still running after ${Math.round(getBackgroundThresholdMs() / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path.`,\n 'Pass background: true for commands that should remain active, such as dev servers, watchers, and tails.',\n 'Stop background runners when they are no longer needed.',\n ],\n parameters: BashParamsSchema,\n executionMode: 'serial',\n execute: async (\n _toolCallId: string,\n params: BashParams,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const { command, timeout, background, interactive, name } = params;\n if (signal?.aborted) throw new Error('Operation aborted');\n if (onUpdate)\n onUpdate(\n textResult(\n `Starting ${interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command'}...`,\n ),\n );\n let onOutput: ((output: string) => void) | undefined;\n if (background !== true && interactive !== true && onUpdate) onOutput = (output) => onUpdate(textResult(output));\n const result = await bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n cwd: context.cwd,\n sessionId: context.sessionId,\n });\n return formatRunResult(result, parseResultPragma(command), summarizeLog);\n },\n };\n}\n\nexport function createHeadlessRunnersCommand(dependencies: RunnerDependencies): DoomHeadlessCommand {\n return {\n name: COMMAND_NAME,\n description: SERVER_COMMAND_DESCRIPTION,\n async execute(args: string, context: DoomHeadlessExecutionContext) {\n const request = parseRunnersCommand(args);\n if (request.kind === 'space') {\n const active = await dependencies.runnerRegistry.listBySession(context.sessionId);\n await context.client.notify({\n body: `${active.length} active runner${active.length === 1 ? '' : 's'}.`,\n level: 'info',\n });\n return;\n }\n if (request.kind === 'start') {\n if (!request.command) throw new Error('Usage: /runners start [name=x] [cwd=y] [interactive=true] -- <command>');\n const result = await dependencies.bashRunService.run({\n command: request.command,\n background: true,\n sessionId: context.sessionId,\n cwd: request.cwd === undefined ? context.cwd : request.cwd,\n ...(request.name ? { name: request.name } : {}),\n ...(request.interactive ? { interactive: true } : {}),\n });\n await context.client.notify({\n body: formatRunResult(result).content[0]?.text ?? 'Runner started.',\n level: 'info',\n });\n return;\n }\n if (!request.id) throw new Error('Usage: /runners stop <runner-id> [reason]');\n const record = await dependencies.runnerRegistry.get(request.id, context.sessionId);\n if (!record) {\n await context.client.notify({ body: `No active runner ${request.id} in this session.`, level: 'warning' });\n return;\n }\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend);\n if (stopped) {\n await dependencies.runnerRegistry.complete(\n request.id,\n {\n reason: 'stopped',\n code: null,\n signal: null,\n ...(request.reason ? { stopReason: request.reason } : {}),\n },\n context.sessionId,\n );\n }\n await context.client.notify({\n body: stopped ? `Stopped runner ${request.id}.` : `Could not stop runner ${request.id}.`,\n level: stopped ? 'info' : 'error',\n });\n },\n };\n}\n"],"mappings":";;;;;;;;;AAmBA,SAAgB,uBACd,gBACA,cAC2C;CAC3C,OAAO;EACL,MAAM;EACN,OAAO;EACP,aACE;EACF,eAAe;EACf,kBAAkB;GAChB,iCAAiC,KAAK,MAAM,yBAAyB,IAAI,aAAa,EAAE;GACxF;GACA;EACF;EACA,YAAY;EACZ,eAAe;EACf,SAAS,OACP,aACA,QACA,QACA,UACA,YACG;GACH,MAAM,EAAE,SAAS,SAAS,YAAY,aAAa,SAAS;GAC5D,IAAI,QAAQ,SAAS,MAAM,IAAI,MAAM,mBAAmB;GACxD,IAAI,UACF,SACE,WACE,YAAY,gBAAgB,OAAO,uBAAuB,eAAe,OAAO,sBAAsB,UAAU,IAClH,CACF;GACF,IAAI;GACJ,IAAI,eAAe,QAAQ,gBAAgB,QAAQ,UAAU,YAAY,WAAW,SAAS,WAAW,MAAM,CAAC;GAC/G,MAAM,SAAS,MAAM,eAAe,IAAI;IACtC;IACA,WAAW,YAAY,KAAA,IAAY,KAAA,IAAY,UAAU;IACzD;IACA;IACA;IACA,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;IAC/B,KAAK,QAAQ;IACb,WAAW,QAAQ;GACrB,CAAC;GACD,OAAO,gBAAgB,QAAQ,kBAAkB,OAAO,GAAG,YAAY;EACzE;CACF;AACF;AAEA,SAAgB,6BAA6B,cAAuD;CAClG,OAAO;EACL,MAAM;EACN,aAAa;EACb,MAAM,QAAQ,MAAc,SAAuC;GACjE,MAAM,UAAU,oBAAoB,IAAI;GACxC,IAAI,QAAQ,SAAS,SAAS;IAC5B,MAAM,SAAS,MAAM,aAAa,eAAe,cAAc,QAAQ,SAAS;IAChF,MAAM,QAAQ,OAAO,OAAO;KAC1B,MAAM,GAAG,OAAO,OAAO,gBAAgB,OAAO,WAAW,IAAI,KAAK,IAAI;KACtE,OAAO;IACT,CAAC;IACD;GACF;GACA,IAAI,QAAQ,SAAS,SAAS;IAC5B,IAAI,CAAC,QAAQ,SAAS,MAAM,IAAI,MAAM,wEAAwE;IAC9G,MAAM,SAAS,MAAM,aAAa,eAAe,IAAI;KACnD,SAAS,QAAQ;KACjB,YAAY;KACZ,WAAW,QAAQ;KACnB,KAAK,QAAQ,QAAQ,KAAA,IAAY,QAAQ,MAAM,QAAQ;KACvD,GAAI,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;KAC7C,GAAI,QAAQ,cAAc,EAAE,aAAa,KAAK,IAAI,CAAC;IACrD,CAAC;IACD,MAAM,QAAQ,OAAO,OAAO;KAC1B,MAAM,gBAAgB,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,QAAQ;KAClD,OAAO;IACT,CAAC;IACD;GACF;GACA,IAAI,CAAC,QAAQ,IAAI,MAAM,IAAI,MAAM,2CAA2C;GAC5E,MAAM,SAAS,MAAM,aAAa,eAAe,IAAI,QAAQ,IAAI,QAAQ,SAAS;GAClF,IAAI,CAAC,QAAQ;IACX,MAAM,QAAQ,OAAO,OAAO;KAAE,MAAM,oBAAoB,QAAQ,GAAG;KAAoB,OAAO;IAAU,CAAC;IACzG;GACF;GACA,MAAM,UAAU,MAAM,kBAAkB,QAAQ,aAAa,UAAU,aAAa,WAAW;GAC/F,IAAI,SACF,MAAM,aAAa,eAAe,SAChC,QAAQ,IACR;IACE,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,GAAI,QAAQ,SAAS,EAAE,YAAY,QAAQ,OAAO,IAAI,CAAC;GACzD,GACA,QAAQ,SACV;GAEF,MAAM,QAAQ,OAAO,OAAO;IAC1B,MAAM,UAAU,kBAAkB,QAAQ,GAAG,KAAK,yBAAyB,QAAQ,GAAG;IACtF,OAAO,UAAU,SAAS;GAC5B,CAAC;EACH;CACF;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/headless/index.ts"],"sourcesContent":["import type {\n DoomHeadlessCommand,\n DoomHeadlessExecutionContext,\n DoomHeadlessTool,\n DoomHeadlessToolResult,\n} from '@agimon-ai/doompi-core/headless';\n\nimport { BASH_TOOL_LABEL, BASH_TOOL_NAME } from '../../constants/bashTool';\nimport { MS_PER_SECOND } from '../../constants/headless';\nimport { COMMAND_NAME, SERVER_COMMAND_DESCRIPTION } from '../../constants/runners';\nimport { type BashParams, BashParamsSchema } from '../../schemas/bashTool';\nimport { formatRunResult, parseResultPragma, textResult } from '../../services/bashResult';\nimport { stopRunnerProcess } from '../../services/reconcile';\nimport { getBackgroundThresholdMs } from '../../services/runnerConfig';\nimport type { RunnerDependencies } from '../../services/runnerDependencies/type';\nimport { parseRunnersCommand } from '../../services/runnersCommand';\nimport { type LogSummarizer } from '../../types/bashResult';\nimport type { IBashRunService } from '../../types/bashRunService';\n\nexport function createHeadlessBashTool(\n bashRunService: IBashRunService,\n summarizeLog?: LogSummarizer,\n): DoomHeadlessTool<typeof BashParamsSchema> {\n return {\n name: BASH_TOOL_NAME,\n label: BASH_TOOL_LABEL,\n description:\n 'Execute one Bash command in the current working directory. Foreground commands return bounded output; commands that outlive the threshold return a supervised runner id and streaming log path.',\n promptSnippet: 'Execute shell commands with bounded foreground output and supervised background runners',\n promptGuidelines: [\n `A command still running after ${Math.round(getBackgroundThresholdMs() / MS_PER_SECOND)} seconds remains active as a background runner with an id and streaming log path.`,\n 'Pass background: true for commands that should remain active, such as dev servers, watchers, and tails.',\n 'Stop background runners when they are no longer needed.',\n ],\n parameters: BashParamsSchema,\n executionMode: 'serial',\n execute: async (\n _toolCallId: string,\n params: BashParams,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ) => {\n const { command, timeout, background, interactive, name } = params;\n if (signal?.aborted) throw new Error('Operation aborted');\n if (onUpdate)\n onUpdate(\n textResult(\n `Starting ${interactive === true ? 'interactive runner' : background === true ? 'background runner' : 'command'}...`,\n ),\n );\n let onOutput: ((output: string) => void) | undefined;\n if (background !== true && interactive !== true && onUpdate) onOutput = (output) => onUpdate(textResult(output));\n const result = await bashRunService.run({\n command,\n timeoutMs: timeout === undefined ? undefined : timeout * MS_PER_SECOND,\n background,\n interactive,\n name,\n ...(onOutput ? { onOutput } : {}),\n ...(signal ? { signal } : {}),\n cwd: context.cwd,\n sessionId: context.sessionId,\n });\n if (signal?.aborted) throw new Error('Operation aborted');\n return formatRunResult(result, parseResultPragma(command), summarizeLog);\n },\n };\n}\n\nexport function createHeadlessRunnersCommand(dependencies: RunnerDependencies): DoomHeadlessCommand {\n return {\n name: COMMAND_NAME,\n description: SERVER_COMMAND_DESCRIPTION,\n async execute(args: string, context: DoomHeadlessExecutionContext) {\n const request = parseRunnersCommand(args);\n if (request.kind === 'space') {\n const active = await dependencies.runnerRegistry.listBySession(context.sessionId);\n await context.client.notify({\n body: `${active.length} active runner${active.length === 1 ? '' : 's'}.`,\n level: 'info',\n });\n return;\n }\n if (request.kind === 'start') {\n if (!request.command) throw new Error('Usage: /runners start [name=x] [cwd=y] [interactive=true] -- <command>');\n const result = await dependencies.bashRunService.run({\n command: request.command,\n background: true,\n sessionId: context.sessionId,\n cwd: request.cwd === undefined ? context.cwd : request.cwd,\n ...(request.name ? { name: request.name } : {}),\n ...(request.interactive ? { interactive: true } : {}),\n });\n await context.client.notify({\n body: formatRunResult(result).content[0]?.text ?? 'Runner started.',\n level: 'info',\n });\n return;\n }\n if (!request.id) throw new Error('Usage: /runners stop <runner-id> [reason]');\n const record = await dependencies.runnerRegistry.get(request.id, context.sessionId);\n if (!record) {\n await context.client.notify({ body: `No active runner ${request.id} in this session.`, level: 'warning' });\n return;\n }\n const stopped = await stopRunnerProcess(record, dependencies.launcher, dependencies.rmuxBackend);\n if (stopped) {\n await dependencies.runnerRegistry.complete(\n request.id,\n {\n reason: 'stopped',\n code: null,\n signal: null,\n ...(request.reason ? { stopReason: request.reason } : {}),\n },\n context.sessionId,\n );\n }\n await context.client.notify({\n body: stopped ? `Stopped runner ${request.id}.` : `Could not stop runner ${request.id}.`,\n level: stopped ? 'info' : 'error',\n });\n },\n };\n}\n"],"mappings":"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"}
@@ -16,6 +16,8 @@ export interface BashRunRequest {
16
16
  name?: string;
17
17
  /** Receives bounded output snapshots while the command remains in the foreground. */
18
18
  onOutput?: (output: string) => void;
19
+ /** Stops this run, including after promotion to a supervised background runner. */
20
+ signal?: AbortSignal;
19
21
  /** Session that owns the runner, so shutdown can stop what it started. */
20
22
  sessionId: string;
21
23
  }
@@ -35,6 +37,8 @@ export interface CompletedRun {
35
37
  rtkWarning?: string;
36
38
  /** True when the run was stopped by the caller's timeout rather than finishing. */
37
39
  timedOut?: boolean;
40
+ /** True when the run was stopped by the caller's abort signal. */
41
+ aborted?: boolean;
38
42
  }
39
43
  /** The command is still running and is now a supervised background runner. */
40
44
  export interface PromotedRun {
@@ -1 +1 @@
1
- {"version":3,"file":"bashRunService.d.cts","names":[],"sources":["../../../src/types/bashRunService.ts"],"mappings":";;iBAEiB;EACf;EACA;;;;;EAKA;;EAEA;;EAEA;;EAEA;;EAEA,YAAY;;EAEZ;;;iBAIe;EACf;EACA;EACA;EACA;EACA;EACA,QAAQ,OAAO;EACf;EACA;;EAEA,YAAY;;EAEZ;;EAEA;;;iBAIe;EACf;EACA;EACA;EACA;EACA;EACA;;;;;EAKA;;;iBAIe;EACf;EACA;EACA;EACA;;YAGU,gBAAgB,eAAe,cAAc;;iBAGxC;EACf,IAAI,SAAS,iBAAiB,QAAQ"}
1
+ {"version":3,"file":"bashRunService.d.cts","names":[],"sources":["../../../src/types/bashRunService.ts"],"mappings":";;iBAEiB;EACf;EACA;;;;;EAKA;;EAEA;;EAEA;;EAEA;;EAEA,YAAY;;EAEZ,SAAS;;EAET;;;iBAIe;EACf;EACA;EACA;EACA;EACA;EACA,QAAQ,OAAO;EACf;EACA;;EAEA,YAAY;;EAEZ;;EAEA;;EAEA;;;iBAIe;EACf;EACA;EACA;EACA;EACA;EACA;;;;;EAKA;;;iBAIe;EACf;EACA;EACA;EACA;;YAGU,gBAAgB,eAAe,cAAc;;iBAGxC;EACf,IAAI,SAAS,iBAAiB,QAAQ"}
@@ -16,6 +16,8 @@ export interface BashRunRequest {
16
16
  name?: string;
17
17
  /** Receives bounded output snapshots while the command remains in the foreground. */
18
18
  onOutput?: (output: string) => void;
19
+ /** Stops this run, including after promotion to a supervised background runner. */
20
+ signal?: AbortSignal;
19
21
  /** Session that owns the runner, so shutdown can stop what it started. */
20
22
  sessionId: string;
21
23
  }
@@ -35,6 +37,8 @@ export interface CompletedRun {
35
37
  rtkWarning?: string;
36
38
  /** True when the run was stopped by the caller's timeout rather than finishing. */
37
39
  timedOut?: boolean;
40
+ /** True when the run was stopped by the caller's abort signal. */
41
+ aborted?: boolean;
38
42
  }
39
43
  /** The command is still running and is now a supervised background runner. */
40
44
  export interface PromotedRun {
@@ -1 +1 @@
1
- {"version":3,"file":"bashRunService.d.mts","names":[],"sources":["../../../src/types/bashRunService.ts"],"mappings":";;iBAEiB;EACf;EACA;;;;;EAKA;;EAEA;;EAEA;;EAEA;;EAEA,YAAY;;EAEZ;;;iBAIe;EACf;EACA;EACA;EACA;EACA;EACA,QAAQ,OAAO;EACf;EACA;;EAEA,YAAY;;EAEZ;;EAEA;;;iBAIe;EACf;EACA;EACA;EACA;EACA;EACA;;;;;EAKA;;;iBAIe;EACf;EACA;EACA;EACA;;YAGU,gBAAgB,eAAe,cAAc;;iBAGxC;EACf,IAAI,SAAS,iBAAiB,QAAQ"}
1
+ {"version":3,"file":"bashRunService.d.mts","names":[],"sources":["../../../src/types/bashRunService.ts"],"mappings":";;iBAEiB;EACf;EACA;;;;;EAKA;;EAEA;;EAEA;;EAEA;;EAEA,YAAY;;EAEZ,SAAS;;EAET;;;iBAIe;EACf;EACA;EACA;EACA;EACA;EACA,QAAQ,OAAO;EACf;EACA;;EAEA,YAAY;;EAEZ;;EAEA;;EAEA;;;iBAIe;EACf;EACA;EACA;EACA;EACA;EACA;;;;;EAKA;;;iBAIe;EACf;EACA;EACA;EACA;;YAGU,gBAAgB,eAAe,cAAc;;iBAGxC;EACf,IAAI,SAAS,iBAAiB,QAAQ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-runner",
3
- "version": "0.0.1-alpha.73",
3
+ "version": "0.0.1-alpha.76",
4
4
  "description": "Supervised shell execution, background process control, and run logs for Pi coding agents.",
5
5
  "keywords": [
6
6
  "ai",
@@ -233,11 +233,11 @@
233
233
  "registry": "https://registry.npmjs.org/"
234
234
  },
235
235
  "dependencies": {
236
- "@agimon-ai/doompi-core": "0.0.1-alpha.73",
237
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.70",
238
- "@agimon-ai/doompi-ui": "0.0.1-alpha.73",
239
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.31",
240
- "@agimon-ai/doompi-web-security": "0.0.1-alpha.33",
236
+ "@agimon-ai/doompi-core": "0.0.1-alpha.76",
237
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.73",
238
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.76",
239
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.34",
240
+ "@agimon-ai/doompi-web-security": "0.0.1-alpha.36",
241
241
  "@agimon-ai/foundation-process-registry": "0.29.25",
242
242
  "@deepseek-ai/cordis": "4.0.2",
243
243
  "@rmux/sdk": "0.6.5",
@@ -246,7 +246,7 @@
246
246
  "typebox": "1.3.30"
247
247
  },
248
248
  "devDependencies": {
249
- "@agimon-ai/doompi-build": "0.0.1-alpha.2",
249
+ "@agimon-ai/doompi-build": "0.0.1-alpha.5",
250
250
  "@earendil-works/pi-coding-agent": "0.85.1",
251
251
  "@earendil-works/pi-tui": "0.85.1",
252
252
  "@tanstack/react-store": "0.11.1",
@@ -273,14 +273,14 @@
273
273
  }
274
274
  },
275
275
  "optionalDependencies": {
276
- "@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.68",
277
- "@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.68",
278
- "@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.68",
279
- "@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.68",
280
- "@agimon-ai/doompi-runner-rtk-darwin-arm64": "0.0.1-alpha.68",
281
- "@agimon-ai/doompi-runner-rtk-darwin-x64": "0.0.1-alpha.68",
282
- "@agimon-ai/doompi-runner-rtk-linux-arm64": "0.0.1-alpha.68",
283
- "@agimon-ai/doompi-runner-rtk-linux-x64": "0.0.1-alpha.68"
276
+ "@agimon-ai/doompi-runner-rmux-darwin-arm64": "0.0.1-alpha.71",
277
+ "@agimon-ai/doompi-runner-rmux-darwin-x64": "0.0.1-alpha.71",
278
+ "@agimon-ai/doompi-runner-rmux-linux-arm64": "0.0.1-alpha.71",
279
+ "@agimon-ai/doompi-runner-rmux-linux-x64": "0.0.1-alpha.71",
280
+ "@agimon-ai/doompi-runner-rtk-darwin-arm64": "0.0.1-alpha.71",
281
+ "@agimon-ai/doompi-runner-rtk-darwin-x64": "0.0.1-alpha.71",
282
+ "@agimon-ai/doompi-runner-rtk-linux-arm64": "0.0.1-alpha.71",
283
+ "@agimon-ai/doompi-runner-rtk-linux-x64": "0.0.1-alpha.71"
284
284
  },
285
285
  "engines": {
286
286
  "node": ">=22.19.0"