@agimon-ai/po-runner 0.0.0-alpha.60 → 0.0.0-alpha.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{bashRunService-DOabKvab.mjs → bashRunService-CqIzJ5F6.mjs} +6 -3
- package/dist/bashRunService-CqIzJ5F6.mjs.map +1 -0
- package/dist/{bashRunService-CmdDhaTn.d.mts → bashRunService-D95EJ5P5.d.mts} +5 -1
- package/dist/bashRunService-D95EJ5P5.d.mts.map +1 -0
- package/dist/{bashRunService-Dy92bThU.cjs → bashRunService-pdXCVU1H.cjs} +6 -3
- package/dist/bashRunService-pdXCVU1H.cjs.map +1 -0
- package/dist/bashRunService.cjs +1 -1
- package/dist/bashRunService.d.mts +4 -4
- package/dist/bashRunService.d.mts.map +1 -1
- package/dist/bashRunService.mjs +1 -1
- package/dist/bashTool.d.mts +2 -2
- package/dist/bin/cli.cjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/bin/runnerHost.cjs +28 -15
- package/dist/bin/runnerHost.cjs.map +1 -1
- package/dist/bin/runnerHost.d.mts.map +1 -1
- package/dist/bin/runnerHost.mjs +28 -15
- package/dist/bin/runnerHost.mjs.map +1 -1
- package/dist/cliApp.d.mts +3 -3
- package/dist/compaction.d.mts +1 -1
- package/dist/extensions/mcp.cjs +1 -0
- package/dist/extensions/mcp.cjs.map +1 -1
- package/dist/extensions/mcp.mjs +1 -0
- package/dist/extensions/mcp.mjs.map +1 -1
- package/dist/extensions/server.cjs +40 -17
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.mjs +40 -17
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/{index-L4iX9nzL.d.mts → index-BKor0Jn_.d.mts} +4 -4
- package/dist/{index-L4iX9nzL.d.mts.map → index-BKor0Jn_.d.mts.map} +1 -1
- package/dist/{index-CoBOAigd.d.mts → index-Bhh0LCcN.d.mts} +2 -2
- package/dist/{index-CoBOAigd.d.mts.map → index-Bhh0LCcN.d.mts.map} +1 -1
- package/dist/{index-EFeluIMR.d.mts → index-CH-z0BX3.d.mts} +2 -2
- package/dist/{index-EFeluIMR.d.mts.map → index-CH-z0BX3.d.mts.map} +1 -1
- package/dist/{index-DoeHCVHQ.d.mts → index-DFORykA2.d.mts} +2 -2
- package/dist/{index-DoeHCVHQ.d.mts.map → index-DFORykA2.d.mts.map} +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +2 -2
- package/dist/launch.cjs +4 -0
- package/dist/launch.d.mts +44 -0
- package/dist/launch.d.mts.map +1 -0
- package/dist/launch.mjs +1 -0
- package/dist/{launcher-CUtcSwXc.d.mts → launcher-CDDBTvZq.d.mts} +2 -1
- package/dist/launcher-CDDBTvZq.d.mts.map +1 -0
- package/dist/{launcher-Dw4pXVq9.mjs → launcher-D3Cl0fGC.mjs} +2 -1
- package/dist/launcher-D3Cl0fGC.mjs.map +1 -0
- package/dist/{launcher-7Ho1Rp7S.cjs → launcher-rqlG32NT.cjs} +2 -1
- package/dist/launcher-rqlG32NT.cjs.map +1 -0
- package/dist/launcher.cjs +1 -1
- package/dist/launcher.d.mts +1 -1
- package/dist/launcher.d.mts.map +1 -1
- package/dist/launcher.mjs +1 -1
- package/dist/{ptyHost-ChkewKLr.d.mts → ptyHost-C_6x2xuJ.d.mts} +2 -2
- package/dist/{ptyHost-ChkewKLr.d.mts.map → ptyHost-C_6x2xuJ.d.mts.map} +1 -1
- package/dist/ptyHost.d.mts +1 -1
- package/dist/reconcile.d.mts +3 -3
- package/dist/responseEnvelope.d.mts +2 -2
- package/dist/{rmuxBackend-DazdRaxC.mjs → rmuxBackend-B-7aY8Kc.mjs} +31 -13
- package/dist/rmuxBackend-B-7aY8Kc.mjs.map +1 -0
- package/dist/{rmuxBackend-D4_-qOQz.cjs → rmuxBackend-CPVm0YUN.cjs} +31 -13
- package/dist/rmuxBackend-CPVm0YUN.cjs.map +1 -0
- package/dist/{rmuxBackend-BPBpI7en.d.mts → rmuxBackend-xFzlANM9.d.mts} +3 -3
- package/dist/{rmuxBackend-BPBpI7en.d.mts.map → rmuxBackend-xFzlANM9.d.mts.map} +1 -1
- package/dist/rmuxBackend.cjs +1 -1
- package/dist/rmuxBackend.d.mts +2 -2
- package/dist/rmuxBackend.mjs +1 -1
- package/dist/{runnerDependencies-CVRUC9Gw.mjs → runnerDependencies-C1TO2nk_.mjs} +42 -23
- package/dist/runnerDependencies-C1TO2nk_.mjs.map +1 -0
- package/dist/{runnerDependencies-BBjsrtTH.cjs → runnerDependencies-CqxPZasw.cjs} +42 -23
- package/dist/runnerDependencies-CqxPZasw.cjs.map +1 -0
- package/dist/runnerDependencies.cjs +1 -1
- package/dist/runnerDependencies.d.mts +1 -1
- package/dist/runnerDependencies.mjs +1 -1
- package/dist/runnerLaunch-C2Cp6EaL.cjs +138 -0
- package/dist/runnerLaunch-C2Cp6EaL.cjs.map +1 -0
- package/dist/runnerLaunch-DN5H79t8.mjs +121 -0
- package/dist/runnerLaunch-DN5H79t8.mjs.map +1 -0
- package/dist/runnerNamer.d.mts +1 -1
- package/dist/{runnerPaths-bzlBTHKU.mjs → runnerPaths-DtT9RJxQ.mjs} +10 -12
- package/dist/runnerPaths-DtT9RJxQ.mjs.map +1 -0
- package/dist/{runnerPaths-NGHLeATQ.cjs → runnerPaths-sdEEgdM8.cjs} +9 -11
- package/dist/runnerPaths-sdEEgdM8.cjs.map +1 -0
- package/dist/runnerPaths.cjs +1 -1
- package/dist/runnerPaths.d.mts.map +1 -1
- package/dist/runnerPaths.mjs +1 -1
- package/dist/{runnerRegistry-V-Ocaf4N.d.mts → runnerRegistry-BlZhS9yL.d.mts} +2 -2
- package/dist/{runnerRegistry-V-Ocaf4N.d.mts.map → runnerRegistry-BlZhS9yL.d.mts.map} +1 -1
- package/dist/runnerRegistry.d.mts +2 -2
- package/dist/{type-CRhqDus6.d.mts → type-BZAFiQjC.d.mts} +7 -7
- package/dist/{type-CRhqDus6.d.mts.map → type-BZAFiQjC.d.mts.map} +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +22 -17
- package/dist/bashRunService-CmdDhaTn.d.mts.map +0 -1
- package/dist/bashRunService-DOabKvab.mjs.map +0 -1
- package/dist/bashRunService-Dy92bThU.cjs.map +0 -1
- package/dist/launcher-7Ho1Rp7S.cjs.map +0 -1
- package/dist/launcher-CUtcSwXc.d.mts.map +0 -1
- package/dist/launcher-Dw4pXVq9.mjs.map +0 -1
- package/dist/rmuxBackend-D4_-qOQz.cjs.map +0 -1
- package/dist/rmuxBackend-DazdRaxC.mjs.map +0 -1
- package/dist/runnerDependencies-BBjsrtTH.cjs.map +0 -1
- package/dist/runnerDependencies-CVRUC9Gw.mjs.map +0 -1
- package/dist/runnerPaths-NGHLeATQ.cjs.map +0 -1
- package/dist/runnerPaths-bzlBTHKU.mjs.map +0 -1
|
@@ -108,6 +108,8 @@ var BashRunService = class {
|
|
|
108
108
|
this.executionBudget = executionBudget;
|
|
109
109
|
}
|
|
110
110
|
async run(request) {
|
|
111
|
+
if (request.env && request.backend !== "native") throw new Error("Environment overrides require the native launcher.");
|
|
112
|
+
if (request.backend === "native" && (request.interactive || request.preferInteractive)) throw new Error("Native package launches are noninteractive.");
|
|
111
113
|
const id = runnerId();
|
|
112
114
|
const name = await this.namer.allocate(request.command, request.sessionId, request.name);
|
|
113
115
|
const cwd = request.cwd ?? this.cwd;
|
|
@@ -124,7 +126,7 @@ var BashRunService = class {
|
|
|
124
126
|
try {
|
|
125
127
|
if (this.executionBudget && isHeavyCommand(request.command)) releaseSlot = await this.executionBudget.acquire(request.signal);
|
|
126
128
|
request.signal?.throwIfAborted();
|
|
127
|
-
const rmuxHandle = await this.rmuxBackend.launch({
|
|
129
|
+
const rmuxHandle = request.backend === "native" ? void 0 : await this.rmuxBackend.launch({
|
|
128
130
|
id,
|
|
129
131
|
name,
|
|
130
132
|
command: request.command,
|
|
@@ -147,7 +149,8 @@ var BashRunService = class {
|
|
|
147
149
|
name,
|
|
148
150
|
command: request.command,
|
|
149
151
|
cwd,
|
|
150
|
-
sessionId: request.sessionId
|
|
152
|
+
sessionId: request.sessionId,
|
|
153
|
+
...request.env ? { env: request.env } : {}
|
|
151
154
|
});
|
|
152
155
|
} catch (error) {
|
|
153
156
|
releaseSlot?.();
|
|
@@ -446,4 +449,4 @@ var BashRunService = class {
|
|
|
446
449
|
//#endregion
|
|
447
450
|
export { BashRunService as t };
|
|
448
451
|
|
|
449
|
-
//# sourceMappingURL=bashRunService-
|
|
452
|
+
//# sourceMappingURL=bashRunService-CqIzJ5F6.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bashRunService-CqIzJ5F6.mjs","names":[],"sources":["../src/constants/bashRunService.ts","../src/services/bashRunService/resources.ts","../src/services/bashRunService/index.ts"],"sourcesContent":["export const RUNNER_ID_RANDOM_BYTES = 3;\nexport const OUTPUT_UPDATE_POLL_MS = 100;\nexport const COMPLETED = 'completed';\nexport const FAILED = 'failed';\nexport const LAUNCHER_ERROR = 'launcher_error';\nexport const SIGNALED = 'signaled';\n/** An interactive launch found neither multiplexer. */\nexport const TERMINAL_UNAVAILABLE = 'A terminal needs rmux or tmux, and neither is available on this machine.';\n","/** Split shell words without mistaking quoted examples for executed commands. */\nfunction commandInvocations(command: string): string[][] {\n const invocations: string[][] = [];\n let words: string[] = [];\n let word = '';\n let quote = '';\n let escaped = false;\n const flushWord = (): void => {\n if (word) words.push(word);\n word = '';\n };\n for (const character of command) {\n if (escaped) {\n word += character;\n escaped = false;\n } else if (character === '\\\\' && quote !== \"'\") {\n escaped = true;\n } else if (quote) {\n if (character === quote) quote = '';\n else word += character;\n } else if (character === '\"' || character === \"'\") {\n quote = character;\n } else if (';&|\\n'.includes(character)) {\n flushWord();\n if (words.length) invocations.push(words);\n words = [];\n } else if (/\\s/.test(character)) {\n flushWord();\n } else {\n word += character;\n }\n }\n flushWord();\n if (words.length) invocations.push(words);\n return invocations;\n}\n\nfunction executableName(word: string): string {\n return word.replace(/\\\\/g, '/').split('/').pop() ?? word;\n}\n\n/** Admission policy, not a shell security boundary. Opaque scripts still need explicit ownership. */\nexport function isHeavyCommand(command: string): boolean {\n const heavyTarget =\n /(?:^|[\\s:,=])(?:build(?:-[\\w-]+)?|test(?:-[\\w-]+)?|lint|fixcode|typecheck|check|bundle)(?:[\\s:,=]|$)/;\n const expensiveBinary =\n /^(?:vitest|jest|tsc|tsgo|tsdown|webpack|rollup|esbuild|oxlint|oxfmt|pytest|maestro|xcodebuild|gradle|gradlew)$/;\n return commandInvocations(command).some((words) => {\n while (words.length) {\n const first = words[0]!;\n if (/^[A-Za-z_][A-Za-z_0-9]*=/.test(first) || /^(?:env|command|exec|time)$/.test(executableName(first))) {\n words.shift();\n } else break;\n }\n let executable = executableName(words.shift() ?? '');\n if (words.includes('--help') || words.includes('--version')) return false;\n if (/^(?:pnpm|npm|npx|yarn|bun)$/.test(executable)) {\n while (words[0]?.startsWith('-')) {\n const option = words.shift()!;\n if (/^(?:--filter|--dir|-C|--cwd|--prefix)$/.test(option)) words.shift();\n }\n if (words[0] === 'exec' || words[0] === 'run') words.shift();\n const invocation = words.shift() ?? '';\n if (/^(?:build|test|lint|fixcode|typecheck|check)(?::[\\w-]+)?$/.test(invocation)) return true;\n executable = executableName(invocation);\n }\n if (expensiveBinary.test(executable)) return true;\n if (executable === 'nx') {\n if (/^(?:show|graph|daemon|reset|report|list)$/.test(words[0] ?? '')) return false;\n return heavyTarget.test(words.join(' '));\n }\n if (/^(?:playwright|vite|cargo|go)$/.test(executable)) return heavyTarget.test(words.join(' '));\n if (/^(?:python|python3)$/.test(executable)) return words[0] === '-m' && words[1] === 'pytest';\n if (/^(?:node|bun|tsx)$/.test(executable)) {\n executable = executableName(words.find((word) => !word.startsWith('-')) ?? '');\n }\n const script = executable.replace(/\\.(?:[cm]?[jt]s)$/, '');\n return (\n script !== executable &&\n (expensiveBinary.test(script) || /^(?:build|test|lint|fixcode|typecheck|dev)(?:[-:]|$)/.test(script))\n );\n });\n}\n","import { randomBytes } from 'node:crypto';\n\nimport type { PoHostExecutionBudget, PoHostExecutionPermit } from '@agimon-ai/po-core/packageApi';\n\nimport {\n COMPLETED,\n FAILED,\n LAUNCHER_ERROR,\n OUTPUT_UPDATE_POLL_MS,\n RUNNER_ID_RANDOM_BYTES,\n SIGNALED,\n TERMINAL_UNAVAILABLE,\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';\nimport { isHeavyCommand } from './resources';\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 private readonly cwd: string = process.cwd(),\n private readonly env: Readonly<Record<string, string | undefined>> = process.env,\n private readonly executionBudget?: PoHostExecutionBudget,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n if (request.env && request.backend !== 'native')\n throw new Error('Environment overrides require the native launcher.');\n if (request.backend === 'native' && (request.interactive || request.preferInteractive))\n throw new Error('Native package launches are noninteractive.');\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? this.cwd;\n // Reassigned once the launch shows whether a pane was actually available.\n let interactive = request.interactive === true;\n const wantsPane = interactive || request.preferInteractive === true;\n if (request.signal?.aborted) return { kind: FAILED, id, name, error: 'Operation aborted' };\n\n let handle: RunHandle;\n let releaseSlot: PoHostExecutionPermit | undefined;\n try {\n if (this.executionBudget && isHeavyCommand(request.command)) {\n releaseSlot = await this.executionBudget.acquire(request.signal);\n }\n request.signal?.throwIfAborted();\n const rmuxHandle =\n request.backend === 'native'\n ? undefined\n : await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive: wantsPane,\n });\n if (interactive && !rmuxHandle) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: TERMINAL_UNAVAILABLE };\n }\n interactive = wantsPane && rmuxHandle !== undefined;\n handle =\n rmuxHandle ??\n (await this.launcher.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n ...(request.env ? { env: request.env } : {}),\n }));\n } catch (error) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n // A background promotion is not process completion. Keep the slot until actual exit.\n if (releaseSlot) void handle.completion().then(releaseSlot, releaseSlot);\n // Register first so a process that survives PID attachment failure remains discoverable.\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 await request.onStarted?.({ id, name, logPath: handle.logPath });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (releaseSlot) {\n try {\n await releaseSlot.attachProcess?.(handle.pid);\n } catch (error) {\n if (!(await handle.stop())) {\n return this.promote(handle, 'requested', undefined);\n }\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n }\n if (request.signal?.aborted) {\n if (!(await handle.stop())) return this.promote(handle, 'requested', undefined);\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return { kind: FAILED, id, name, error: 'Operation aborted' };\n }\n\n // A sequence owns the wait and hands control back as soon as its interactive stage starts.\n if (request.waitForExit !== true && (request.background === true || interactive))\n return this.promote(handle, request.background === true ? 'requested' : 'interactive', request.signal);\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, request.waitForExit !== true);\n await outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold', request.signal);\n }\n if (outcome === ABORTED) {\n await this.registry.requestTermination(id, { reason: 'stopped', terminationReason: 'request_cancelled' });\n const stopped = await handle.stop();\n if (!stopped && request.waitForExit) return this.promote(handle, 'requested', undefined);\n await 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: await 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 this.registry.requestTermination(id, { reason: 'timed_out', terminationReason: 'timeout' });\n const stopped = await handle.stop();\n if (!stopped && request.waitForExit) return this.promote(handle, 'requested', undefined);\n await 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: await 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.reason ?? (outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED),\n code: outcome.code,\n signal: outcome.signal,\n ...(outcome.terminationReason ? { terminationReason: outcome.terminationReason } : {}),\n ...(outcome.stopReason ? { stopReason: outcome.stopReason } : {}),\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: await 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(): Promise<void>; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = async (): Promise<void> => {\n if (!active) return;\n const output = await handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = async (): Promise<void> => {\n if (!active) return;\n await 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 promoteAtThreshold = true,\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 if (promoteAtThreshold)\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(this.env), () => 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 = this.registry\n .requestTermination(handle.id, { reason: 'stopped', terminationReason: 'request_cancelled' })\n .then(() => handle.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 // The signal belongs to the tool call, which ends when this returns, and\n // Pi aborts it at that point. A promoted runner outlives the call, so only\n // an abort that landed before the handoff stops it.\n if (signal?.aborted) abort();\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.reason ?? (outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED),\n code: outcome.code,\n signal: outcome.signal,\n ...(outcome.terminationReason ? { terminationReason: outcome.terminationReason } : {}),\n ...(outcome.stopReason ? { stopReason: outcome.stopReason } : {}),\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\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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"}
|
|
@@ -3,6 +3,10 @@ import { l as RtkProcessedOutput } from "./rtkProcessor-CVZvNhkJ.mjs";
|
|
|
3
3
|
interface BashRunRequest {
|
|
4
4
|
command: string;
|
|
5
5
|
cwd?: string;
|
|
6
|
+
/** Internal package launch path. Native bypasses all terminal multiplexers. */
|
|
7
|
+
backend?: 'native';
|
|
8
|
+
/** Per-run environment overrides, only accepted on the native path. */
|
|
9
|
+
env?: Record<string, string>;
|
|
6
10
|
/**
|
|
7
11
|
* Stops the command outright once it has run this long. Only meaningful
|
|
8
12
|
* below the background threshold: past that the command is promoted instead.
|
|
@@ -81,4 +85,4 @@ interface IBashRunService {
|
|
|
81
85
|
}
|
|
82
86
|
//#endregion
|
|
83
87
|
export { IBashRunService as a, FailedRun as i, BashRunResult as n, PromotedRun as o, CompletedRun as r, BashRunRequest as t };
|
|
84
|
-
//# sourceMappingURL=bashRunService-
|
|
88
|
+
//# sourceMappingURL=bashRunService-D95EJ5P5.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bashRunService-D95EJ5P5.d.mts","names":[],"sources":["../src/types/bashRunService.ts"],"mappings":";;UAEiB;EACf;EACA;;EAEA;;EAEA,MAAM;;;;;EAKN;;EAEA;;EAEA;;;;;EAKA;;EAEA;;EAEA,YAAY;;EAEZ,SAAS;;EAET;;EAEA,aAAa;IAAO;IAAY;IAAc;eAA6B;;EAE3E;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA,QAAQ,OAAO;EACf;EACA;;EAEA,YAAY;;EAEZ;;EAEA;;EAEA;;;UAIe;EACf;EACA;EACA;EACA;EACA;EACA;;;;;EAKA;;;UAIe;EACf;EACA;EACA;EACA;;KAGU,gBAAgB,eAAe,cAAc;;UAGxC;EACf,IAAI,SAAS,iBAAiB,QAAQ"}
|
|
@@ -108,6 +108,8 @@ var BashRunService = class {
|
|
|
108
108
|
this.executionBudget = executionBudget;
|
|
109
109
|
}
|
|
110
110
|
async run(request) {
|
|
111
|
+
if (request.env && request.backend !== "native") throw new Error("Environment overrides require the native launcher.");
|
|
112
|
+
if (request.backend === "native" && (request.interactive || request.preferInteractive)) throw new Error("Native package launches are noninteractive.");
|
|
111
113
|
const id = runnerId();
|
|
112
114
|
const name = await this.namer.allocate(request.command, request.sessionId, request.name);
|
|
113
115
|
const cwd = request.cwd ?? this.cwd;
|
|
@@ -124,7 +126,7 @@ var BashRunService = class {
|
|
|
124
126
|
try {
|
|
125
127
|
if (this.executionBudget && isHeavyCommand(request.command)) releaseSlot = await this.executionBudget.acquire(request.signal);
|
|
126
128
|
request.signal?.throwIfAborted();
|
|
127
|
-
const rmuxHandle = await this.rmuxBackend.launch({
|
|
129
|
+
const rmuxHandle = request.backend === "native" ? void 0 : await this.rmuxBackend.launch({
|
|
128
130
|
id,
|
|
129
131
|
name,
|
|
130
132
|
command: request.command,
|
|
@@ -147,7 +149,8 @@ var BashRunService = class {
|
|
|
147
149
|
name,
|
|
148
150
|
command: request.command,
|
|
149
151
|
cwd,
|
|
150
|
-
sessionId: request.sessionId
|
|
152
|
+
sessionId: request.sessionId,
|
|
153
|
+
...request.env ? { env: request.env } : {}
|
|
151
154
|
});
|
|
152
155
|
} catch (error) {
|
|
153
156
|
releaseSlot?.();
|
|
@@ -451,4 +454,4 @@ Object.defineProperty(exports, "BashRunService", {
|
|
|
451
454
|
}
|
|
452
455
|
});
|
|
453
456
|
|
|
454
|
-
//# sourceMappingURL=bashRunService-
|
|
457
|
+
//# sourceMappingURL=bashRunService-pdXCVU1H.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bashRunService-pdXCVU1H.cjs","names":["randomBytes","RTK_FAILED_WARNING","getBackgroundThresholdMs"],"sources":["../src/constants/bashRunService.ts","../src/services/bashRunService/resources.ts","../src/services/bashRunService/index.ts"],"sourcesContent":["export const RUNNER_ID_RANDOM_BYTES = 3;\nexport const OUTPUT_UPDATE_POLL_MS = 100;\nexport const COMPLETED = 'completed';\nexport const FAILED = 'failed';\nexport const LAUNCHER_ERROR = 'launcher_error';\nexport const SIGNALED = 'signaled';\n/** An interactive launch found neither multiplexer. */\nexport const TERMINAL_UNAVAILABLE = 'A terminal needs rmux or tmux, and neither is available on this machine.';\n","/** Split shell words without mistaking quoted examples for executed commands. */\nfunction commandInvocations(command: string): string[][] {\n const invocations: string[][] = [];\n let words: string[] = [];\n let word = '';\n let quote = '';\n let escaped = false;\n const flushWord = (): void => {\n if (word) words.push(word);\n word = '';\n };\n for (const character of command) {\n if (escaped) {\n word += character;\n escaped = false;\n } else if (character === '\\\\' && quote !== \"'\") {\n escaped = true;\n } else if (quote) {\n if (character === quote) quote = '';\n else word += character;\n } else if (character === '\"' || character === \"'\") {\n quote = character;\n } else if (';&|\\n'.includes(character)) {\n flushWord();\n if (words.length) invocations.push(words);\n words = [];\n } else if (/\\s/.test(character)) {\n flushWord();\n } else {\n word += character;\n }\n }\n flushWord();\n if (words.length) invocations.push(words);\n return invocations;\n}\n\nfunction executableName(word: string): string {\n return word.replace(/\\\\/g, '/').split('/').pop() ?? word;\n}\n\n/** Admission policy, not a shell security boundary. Opaque scripts still need explicit ownership. */\nexport function isHeavyCommand(command: string): boolean {\n const heavyTarget =\n /(?:^|[\\s:,=])(?:build(?:-[\\w-]+)?|test(?:-[\\w-]+)?|lint|fixcode|typecheck|check|bundle)(?:[\\s:,=]|$)/;\n const expensiveBinary =\n /^(?:vitest|jest|tsc|tsgo|tsdown|webpack|rollup|esbuild|oxlint|oxfmt|pytest|maestro|xcodebuild|gradle|gradlew)$/;\n return commandInvocations(command).some((words) => {\n while (words.length) {\n const first = words[0]!;\n if (/^[A-Za-z_][A-Za-z_0-9]*=/.test(first) || /^(?:env|command|exec|time)$/.test(executableName(first))) {\n words.shift();\n } else break;\n }\n let executable = executableName(words.shift() ?? '');\n if (words.includes('--help') || words.includes('--version')) return false;\n if (/^(?:pnpm|npm|npx|yarn|bun)$/.test(executable)) {\n while (words[0]?.startsWith('-')) {\n const option = words.shift()!;\n if (/^(?:--filter|--dir|-C|--cwd|--prefix)$/.test(option)) words.shift();\n }\n if (words[0] === 'exec' || words[0] === 'run') words.shift();\n const invocation = words.shift() ?? '';\n if (/^(?:build|test|lint|fixcode|typecheck|check)(?::[\\w-]+)?$/.test(invocation)) return true;\n executable = executableName(invocation);\n }\n if (expensiveBinary.test(executable)) return true;\n if (executable === 'nx') {\n if (/^(?:show|graph|daemon|reset|report|list)$/.test(words[0] ?? '')) return false;\n return heavyTarget.test(words.join(' '));\n }\n if (/^(?:playwright|vite|cargo|go)$/.test(executable)) return heavyTarget.test(words.join(' '));\n if (/^(?:python|python3)$/.test(executable)) return words[0] === '-m' && words[1] === 'pytest';\n if (/^(?:node|bun|tsx)$/.test(executable)) {\n executable = executableName(words.find((word) => !word.startsWith('-')) ?? '');\n }\n const script = executable.replace(/\\.(?:[cm]?[jt]s)$/, '');\n return (\n script !== executable &&\n (expensiveBinary.test(script) || /^(?:build|test|lint|fixcode|typecheck|dev)(?:[-:]|$)/.test(script))\n );\n });\n}\n","import { randomBytes } from 'node:crypto';\n\nimport type { PoHostExecutionBudget, PoHostExecutionPermit } from '@agimon-ai/po-core/packageApi';\n\nimport {\n COMPLETED,\n FAILED,\n LAUNCHER_ERROR,\n OUTPUT_UPDATE_POLL_MS,\n RUNNER_ID_RANDOM_BYTES,\n SIGNALED,\n TERMINAL_UNAVAILABLE,\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';\nimport { isHeavyCommand } from './resources';\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 private readonly cwd: string = process.cwd(),\n private readonly env: Readonly<Record<string, string | undefined>> = process.env,\n private readonly executionBudget?: PoHostExecutionBudget,\n ) {}\n\n async run(request: BashRunRequest): Promise<BashRunResult> {\n if (request.env && request.backend !== 'native')\n throw new Error('Environment overrides require the native launcher.');\n if (request.backend === 'native' && (request.interactive || request.preferInteractive))\n throw new Error('Native package launches are noninteractive.');\n const id = runnerId();\n const name = await this.namer.allocate(request.command, request.sessionId, request.name);\n const cwd = request.cwd ?? this.cwd;\n // Reassigned once the launch shows whether a pane was actually available.\n let interactive = request.interactive === true;\n const wantsPane = interactive || request.preferInteractive === true;\n if (request.signal?.aborted) return { kind: FAILED, id, name, error: 'Operation aborted' };\n\n let handle: RunHandle;\n let releaseSlot: PoHostExecutionPermit | undefined;\n try {\n if (this.executionBudget && isHeavyCommand(request.command)) {\n releaseSlot = await this.executionBudget.acquire(request.signal);\n }\n request.signal?.throwIfAborted();\n const rmuxHandle =\n request.backend === 'native'\n ? undefined\n : await this.rmuxBackend.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n interactive: wantsPane,\n });\n if (interactive && !rmuxHandle) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: TERMINAL_UNAVAILABLE };\n }\n interactive = wantsPane && rmuxHandle !== undefined;\n handle =\n rmuxHandle ??\n (await this.launcher.launch({\n id,\n name,\n command: request.command,\n cwd,\n sessionId: request.sessionId,\n ...(request.env ? { env: request.env } : {}),\n }));\n } catch (error) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (handle.pid === undefined) {\n releaseSlot?.();\n return { kind: FAILED, id, name, error: 'The command did not start, so it cannot be supervised' };\n }\n // A background promotion is not process completion. Keep the slot until actual exit.\n if (releaseSlot) void handle.completion().then(releaseSlot, releaseSlot);\n // Register first so a process that survives PID attachment failure remains discoverable.\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 await request.onStarted?.({ id, name, logPath: handle.logPath });\n } catch (error) {\n await handle.stop();\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n\n if (releaseSlot) {\n try {\n await releaseSlot.attachProcess?.(handle.pid);\n } catch (error) {\n if (!(await handle.stop())) {\n return this.promote(handle, 'requested', undefined);\n }\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return { kind: FAILED, id, name, error: error instanceof Error ? error.message : String(error) };\n }\n }\n if (request.signal?.aborted) {\n if (!(await handle.stop())) return this.promote(handle, 'requested', undefined);\n await this.registry.complete(id, { reason: 'stopped', code: null, signal: 'SIGTERM' });\n return { kind: FAILED, id, name, error: 'Operation aborted' };\n }\n\n // A sequence owns the wait and hands control back as soon as its interactive stage starts.\n if (request.waitForExit !== true && (request.background === true || interactive))\n return this.promote(handle, request.background === true ? 'requested' : 'interactive', request.signal);\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, request.waitForExit !== true);\n await outputUpdates?.flush();\n if (outcome === TIMED_OUT) {\n return this.promote(handle, 'threshold', request.signal);\n }\n if (outcome === ABORTED) {\n await this.registry.requestTermination(id, { reason: 'stopped', terminationReason: 'request_cancelled' });\n const stopped = await handle.stop();\n if (!stopped && request.waitForExit) return this.promote(handle, 'requested', undefined);\n await 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: await 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 this.registry.requestTermination(id, { reason: 'timed_out', terminationReason: 'timeout' });\n const stopped = await handle.stop();\n if (!stopped && request.waitForExit) return this.promote(handle, 'requested', undefined);\n await 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: await 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.reason ?? (outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED),\n code: outcome.code,\n signal: outcome.signal,\n ...(outcome.terminationReason ? { terminationReason: outcome.terminationReason } : {}),\n ...(outcome.stopReason ? { stopReason: outcome.stopReason } : {}),\n });\n\n return {\n kind: COMPLETED,\n id,\n name,\n output: await 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(): Promise<void>; dispose(): void } {\n let active = true;\n let previous = '';\n let cancelPoll: (() => void) | undefined;\n const flush = async (): Promise<void> => {\n if (!active) return;\n const output = await handle.output();\n if (output === previous) return;\n previous = output;\n onOutput(output);\n };\n const poll = async (): Promise<void> => {\n if (!active) return;\n await 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 promoteAtThreshold = true,\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 if (promoteAtThreshold)\n racers.push(\n new Promise((resolve) => {\n cancels.push(this.clock.after(getBackgroundThresholdMs(this.env), () => 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 = this.registry\n .requestTermination(handle.id, { reason: 'stopped', terminationReason: 'request_cancelled' })\n .then(() => handle.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 // The signal belongs to the tool call, which ends when this returns, and\n // Pi aborts it at that point. A promoted runner outlives the call, so only\n // an abort that landed before the handoff stops it.\n if (signal?.aborted) abort();\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.reason ?? (outcome.signal ? SIGNALED : outcome.code === 0 ? COMPLETED : FAILED),\n code: outcome.code,\n signal: outcome.signal,\n ...(outcome.terminationReason ? { terminationReason: outcome.terminationReason } : {}),\n ...(outcome.stopReason ? { stopReason: outcome.stopReason } : {}),\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\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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"}
|
package/dist/bashRunService.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_bashRunService = require("./bashRunService-
|
|
2
|
+
const require_bashRunService = require("./bashRunService-pdXCVU1H.cjs");
|
|
3
3
|
exports.BashRunService = require_bashRunService.BashRunService;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as IRtkProcessor } from "./rtkProcessor-CVZvNhkJ.mjs";
|
|
2
|
-
import { a as IBashRunService, i as FailedRun, n as BashRunResult, o as PromotedRun, r as CompletedRun, t as BashRunRequest } from "./bashRunService-
|
|
2
|
+
import { a as IBashRunService, i as FailedRun, n as BashRunResult, o as PromotedRun, r as CompletedRun, t as BashRunRequest } from "./bashRunService-D95EJ5P5.mjs";
|
|
3
3
|
import { t as IClock } from "./clock-euJkweLq.mjs";
|
|
4
|
-
import { t as ILauncher } from "./launcher-
|
|
5
|
-
import { t as IRmuxBackend } from "./rmuxBackend-
|
|
6
|
-
import { n as IRunnerRegistry } from "./runnerRegistry-
|
|
4
|
+
import { t as ILauncher } from "./launcher-CDDBTvZq.mjs";
|
|
5
|
+
import { t as IRmuxBackend } from "./rmuxBackend-xFzlANM9.mjs";
|
|
6
|
+
import { n as IRunnerRegistry } from "./runnerRegistry-BlZhS9yL.mjs";
|
|
7
7
|
import { t as IRunnerNamer } from "./type-vWQNTahw.mjs";
|
|
8
8
|
import { PoHostExecutionBudget } from "@agimon-ai/po-core/packageApi";
|
|
9
9
|
//#region src/services/bashRunService/index.d.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bashRunService.d.mts","names":[],"sources":["../src/services/bashRunService/index.ts"],"mappings":";;;;;;;;;qBA0Ca,0BAA0B;mBAElB;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;EATnB,YACmB,UAAU,WACV,aAAa,cACb,OAAO,cACP,UAAU,iBACV,OAAO,QACP,cAAc,eACd,cACA,MAAK,SAAS,qCACd,kBAAkB;EAG/B,IAAI,SAAS,iBAAiB,QAAQ;
|
|
1
|
+
{"version":3,"file":"bashRunService.d.mts","names":[],"sources":["../src/services/bashRunService/index.ts"],"mappings":";;;;;;;;;qBA0Ca,0BAA0B;mBAElB;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;EATnB,YACmB,UAAU,WACV,aAAa,cACb,OAAO,cACP,UAAU,iBACV,OAAO,QACP,cAAc,eACd,cACA,MAAK,SAAS,qCACd,kBAAkB;EAG/B,IAAI,SAAS,iBAAiB,QAAQ;UA4K9B;UAWN;;;;;;;;UAqCM;UAkDA"}
|
package/dist/bashRunService.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { t as BashRunService } from "./bashRunService-
|
|
1
|
+
export { t as BashRunService } from "./bashRunService-CqIzJ5F6.mjs";
|
package/dist/bashTool.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as BashRunResult } from "./bashRunService-
|
|
1
|
+
import { n as BashRunResult } from "./bashRunService-D95EJ5P5.mjs";
|
|
2
2
|
import { a as ResultBudget, s as ToolResult } from "./bashResult-CfOPpOHN.mjs";
|
|
3
|
-
import { t as createHeadlessBashTool } from "./index-
|
|
3
|
+
import { t as createHeadlessBashTool } from "./index-DFORykA2.mjs";
|
|
4
4
|
//#region src/services/runnerResult/index.d.ts
|
|
5
5
|
declare function formatRunnerResult(result: BashRunResult, budget?: ResultBudget): Promise<ToolResult>;
|
|
6
6
|
//#endregion
|
package/dist/bin/cli.cjs
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const require_runnerSequenceStore = require("../runnerSequenceStore-Ck6M-8r_.cjs");
|
|
4
4
|
const require_cliApp = require("../cliApp-GKcqD6CT.cjs");
|
|
5
5
|
const require_session = require("../session-D_nH4XhL.cjs");
|
|
6
|
-
const require_runnerDependencies = require("../runnerDependencies-
|
|
6
|
+
const require_runnerDependencies = require("../runnerDependencies-CqxPZasw.cjs");
|
|
7
7
|
let node_fs_promises = require("node:fs/promises");
|
|
8
8
|
let node_url = require("node:url");
|
|
9
9
|
let _agimon_ai_po_core_telemetry = require("@agimon-ai/po-core/telemetry");
|
package/dist/bin/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { t as createRunnerSequenceStore } from "../runnerSequenceStore-CaY7S_Sd.mjs";
|
|
3
3
|
import { n as runnerCliHelp, t as runCli } from "../cliApp-g7APOwcU.mjs";
|
|
4
4
|
import { t as inheritedSessionId } from "../session-3oSU3QjI.mjs";
|
|
5
|
-
import { t as createRunnerDependencies } from "../runnerDependencies-
|
|
5
|
+
import { t as createRunnerDependencies } from "../runnerDependencies-C1TO2nk_.mjs";
|
|
6
6
|
import { realpath } from "node:fs/promises";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { createPoTelemetry } from "@agimon-ai/po-core/telemetry";
|
package/dist/bin/runnerHost.cjs
CHANGED
|
@@ -13,6 +13,11 @@ node_os = require_rolldown_runtime.__toESM(node_os, 1);
|
|
|
13
13
|
const POLL_MS = 10;
|
|
14
14
|
const FORWARDED_SIGNAL_GRACE_MS = 2e3;
|
|
15
15
|
const INVALID_COMMAND_SPEC_ERROR = "invalid runner command spec";
|
|
16
|
+
const OWNER_LOST_EXIT = {
|
|
17
|
+
reason: "stopped",
|
|
18
|
+
terminationReason: "owner_lost",
|
|
19
|
+
stopReason: "owner session ended"
|
|
20
|
+
};
|
|
16
21
|
async function main(argv = process.argv.slice(2)) {
|
|
17
22
|
if (argv[0] === "--observe-sessions") {
|
|
18
23
|
require_services_lifeline_client.observeSessions(cleanupSessions);
|
|
@@ -21,11 +26,24 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
21
26
|
const [specPath, gatePath, exitPath] = argv;
|
|
22
27
|
if (!specPath || !gatePath || !exitPath) throw new Error("runnerHost requires spec, gate, and exit paths");
|
|
23
28
|
const spec = await readSpec(specPath);
|
|
24
|
-
|
|
29
|
+
let ownerLost = false;
|
|
30
|
+
let onOwnerLost = () => void 0;
|
|
31
|
+
const detachLifeline = require_services_lifeline_client.watchOwner(() => {
|
|
32
|
+
ownerLost = true;
|
|
33
|
+
onOwnerLost();
|
|
34
|
+
}, spec.env[require_services_lifeline_client.LIFELINE_ENV]);
|
|
35
|
+
if (!await waitForGate(gatePath, () => ownerLost)) {
|
|
36
|
+
detachLifeline();
|
|
37
|
+
await writeAtomic(exitPath, {
|
|
38
|
+
code: null,
|
|
39
|
+
signal: null,
|
|
40
|
+
...OWNER_LOST_EXIT
|
|
41
|
+
});
|
|
42
|
+
return 1;
|
|
43
|
+
}
|
|
25
44
|
let child;
|
|
26
45
|
let forwardedSignal = null;
|
|
27
46
|
let escalationTimer;
|
|
28
|
-
let ownerLost = false;
|
|
29
47
|
const signalChild = (signal) => {
|
|
30
48
|
if (!child?.pid) return;
|
|
31
49
|
try {
|
|
@@ -52,10 +70,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
52
70
|
detached: true
|
|
53
71
|
});
|
|
54
72
|
if (forwardedSignal) forward(forwardedSignal);
|
|
55
|
-
|
|
56
|
-
ownerLost = true;
|
|
57
|
-
forward("SIGTERM");
|
|
58
|
-
}, spec.env[require_services_lifeline_client.LIFELINE_ENV]);
|
|
73
|
+
onOwnerLost = () => forward("SIGTERM");
|
|
59
74
|
try {
|
|
60
75
|
const result = await new Promise((resolve, reject) => {
|
|
61
76
|
child?.once("error", reject);
|
|
@@ -66,11 +81,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
66
81
|
});
|
|
67
82
|
await writeAtomic(exitPath, {
|
|
68
83
|
...result,
|
|
69
|
-
...ownerLost ? {
|
|
70
|
-
reason: "stopped",
|
|
71
|
-
terminationReason: "owner_lost",
|
|
72
|
-
stopReason: "owner session ended"
|
|
73
|
-
} : {}
|
|
84
|
+
...ownerLost ? OWNER_LOST_EXIT : {}
|
|
74
85
|
});
|
|
75
86
|
return result.code !== null ? result.code : result.signal ? 128 + node_os.default.constants.signals[result.signal] : 1;
|
|
76
87
|
} finally {
|
|
@@ -89,16 +100,18 @@ async function readSpec(target) {
|
|
|
89
100
|
env: spec.env
|
|
90
101
|
};
|
|
91
102
|
}
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
/** False once the owner is gone, since only the owner opens the gate. */
|
|
104
|
+
async function waitForGate(target, abandoned) {
|
|
105
|
+
while (!abandoned()) {
|
|
94
106
|
try {
|
|
95
107
|
await node_fs_promises.default.access(target);
|
|
96
|
-
return;
|
|
108
|
+
return true;
|
|
97
109
|
} catch (error) {
|
|
98
110
|
if (error.code !== "ENOENT") throw error;
|
|
99
111
|
}
|
|
100
112
|
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
101
113
|
}
|
|
114
|
+
return false;
|
|
102
115
|
}
|
|
103
116
|
async function writeAtomic(target, value) {
|
|
104
117
|
const temporary = `${target}.${process.pid}.tmp`;
|
|
@@ -149,7 +162,7 @@ async function readMarker(marker) {
|
|
|
149
162
|
async function loadCleaner() {
|
|
150
163
|
const [{ cleanupRunnerSession }, { RunnerPaths }, { createDefaultProcessRegistry }] = await Promise.all([
|
|
151
164
|
Promise.resolve().then(() => require("../runnerHost-BXk4sPBP.cjs")).then((n) => n.runnerHost_exports),
|
|
152
|
-
Promise.resolve().then(() => require("../runnerPaths-
|
|
165
|
+
Promise.resolve().then(() => require("../runnerPaths-sdEEgdM8.cjs")).then((n) => n.runnerPaths_exports),
|
|
153
166
|
Promise.resolve().then(() => require("../runnerRegistry-BucrRJsG.cjs")).then((n) => n.runnerRegistry_exports)
|
|
154
167
|
]);
|
|
155
168
|
const registries = /* @__PURE__ */ new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runnerHost.cjs","names":["spawn","watchOwner","LIFELINE_ENV","os","fs","pathToFileURL","REGISTRY_PATH_ENV"],"sources":["../../src/bin/runnerHost.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport { pathToFileURL } from 'node:url';\n\nimport type { PoTelemetry } from '@agimon-ai/po-core/telemetry';\n\nimport { OBSERVE_SESSIONS_FLAG } from '../constants/lifeline';\nimport { REGISTRY_PATH_ENV } from '../constants/runnerRegistry';\nimport { LIFELINE_ENV, observeSessions, watchOwner } from '../services/lifeline/client';\nimport type { ProcessRegistryPort } from '../services/runnerRegistry';\nimport type { LifelineWatch } from '../types/lifeline';\n\n// Everything heavier loads on first use: the observer idles for its owner's whole life and\n// these imports alone would more than double its memory. A command's host loads none of it:\n// the server records the runner when it completes the record from this host's exit file.\n\ninterface CommandSpec {\n command: string;\n cwd: string;\n env: Record<string, string>;\n}\n\nconst POLL_MS = 10;\nconst FORWARDED_SIGNAL_GRACE_MS = 2_000;\nconst INVALID_COMMAND_SPEC_ERROR = 'invalid runner command spec';\n\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n if (argv[0] === OBSERVE_SESSIONS_FLAG) {\n observeSessions(cleanupSessions);\n return 0;\n }\n const [specPath, gatePath, exitPath] = argv;\n if (!specPath || !gatePath || !exitPath) throw new Error('runnerHost requires spec, gate, and exit paths');\n const spec = await readSpec(specPath);\n await waitForGate(gatePath);\n let child: ChildProcess | undefined;\n let forwardedSignal: NodeJS.Signals | null = null;\n let escalationTimer: NodeJS.Timeout | undefined;\n let ownerLost = false;\n const signalChild = (signal: NodeJS.Signals): void => {\n if (!child?.pid) return;\n try {\n process.kill(-child.pid, signal);\n } catch {\n child.kill(signal);\n }\n };\n const forward = (signal: NodeJS.Signals): void => {\n forwardedSignal = signal;\n signalChild(signal);\n escalationTimer ??= setTimeout(() => signalChild('SIGKILL'), FORWARDED_SIGNAL_GRACE_MS);\n };\n const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGHUP'];\n for (const signal of signals) process.once(signal, () => forward(signal));\n\n child = spawn(spec.command, {\n cwd: spec.cwd,\n env: spec.env,\n shell: '/bin/bash',\n stdio: 'inherit',\n detached: true,\n });\n if (forwardedSignal) forward(forwardedSignal);\n\n // This process supervises the command for its whole life, so it is also the\n // right place to notice an owner that died without stopping it.\n //\n // The lifeline comes from the spec rather than this process's environment: an\n // rmux server outlives the session that started it, so its own environment can\n // still name a previous session's socket.\n const detachLifeline = watchOwner(() => {\n ownerLost = true;\n forward('SIGTERM');\n }, spec.env[LIFELINE_ENV]);\n\n try {\n const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => {\n child?.once('error', reject);\n child?.once('exit', (code, signal) => resolve({ code, signal: signal ?? forwardedSignal }));\n });\n await writeAtomic(exitPath, {\n ...result,\n ...(ownerLost ? { reason: 'stopped', terminationReason: 'owner_lost', stopReason: 'owner session ended' } : {}),\n });\n return result.code !== null ? result.code : result.signal ? 128 + os.constants.signals[result.signal] : 1;\n } finally {\n detachLifeline();\n if (escalationTimer) clearTimeout(escalationTimer);\n }\n}\n\nasync function readSpec(target: string): Promise<CommandSpec> {\n const value: unknown = JSON.parse(await fs.readFile(target, 'utf8'));\n if (typeof value !== 'object' || value === null) throw new Error(INVALID_COMMAND_SPEC_ERROR);\n const spec = value as Partial<CommandSpec>;\n if (typeof spec.command !== 'string' || typeof spec.cwd !== 'string' || !spec.env) {\n throw new Error(INVALID_COMMAND_SPEC_ERROR);\n }\n return { command: spec.command, cwd: spec.cwd, env: spec.env };\n}\n\nasync function waitForGate(target: string): Promise<void> {\n for (;;) {\n try {\n await fs.access(target);\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, POLL_MS));\n }\n}\n\nasync function writeAtomic(target: string, value: unknown): Promise<void> {\n const temporary = `${target}.${process.pid}.tmp`;\n await fs.writeFile(temporary, `${JSON.stringify(value)}\\n`, { mode: 0o600 });\n await fs.rename(temporary, target);\n}\n\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n\n/** Loaded on the first session an owner leaves behind, then shared by every later one. */\nlet cleaner: Promise<(watch: LifelineWatch) => Promise<void>> | undefined;\nlet telemetry: Promise<PoTelemetry> | undefined;\n\n/**\n * A separate process survives SIGKILL of the owner, even if that session has no active runners.\n * The owner cleans up each session it ends with its own shared services and removes its marker,\n * so only a session still marked with its generation is left here; the rest load nothing.\n */\nasync function cleanupSessions(watches: readonly LifelineWatch[]): Promise<void> {\n const failures: { watch: LifelineWatch; error: unknown }[] = [];\n // One session failing to clean up must not stop the others.\n for (const watch of watches) {\n try {\n // The same fence cleanupRunnerSession applies, checked before anything loads for it.\n if ((await readMarker(watch.marker)) !== watch.generation) continue;\n const clean = await (cleaner ??= loadCleaner());\n await clean(watch);\n } catch (error) {\n failures.push({ watch, error });\n }\n }\n const [first] = failures;\n if (!first) return;\n const log = await (telemetry ??= loadTelemetry(first.watch.environment));\n for (const { watch, error } of failures) {\n await log.recordError('po_runner.observer_cleanup_failed', error, { 'session.id': watch.sessionId });\n }\n await log.flush();\n throw new AggregateError(\n failures.map(({ error }) => error),\n `Could not clean up runner sessions ${failures.map(({ watch }) => watch.sessionId).join(', ')}`,\n );\n}\n\nasync function readMarker(marker: string): Promise<string | undefined> {\n try {\n return await fs.readFile(marker, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n}\n\n/** One registry per registry path, held until the observer runs out of work. */\nasync function loadCleaner(): Promise<(watch: LifelineWatch) => Promise<void>> {\n const [{ cleanupRunnerSession }, { RunnerPaths }, { createDefaultProcessRegistry }] = await Promise.all([\n import('../services/runnerHost'),\n import('../services/runnerPaths'),\n import('../services/runnerRegistry'),\n ]);\n const registries = new Map<string | undefined, ProcessRegistryPort>();\n process.once('beforeExit', () => {\n for (const registry of registries.values()) registry.close();\n });\n return (watch) => {\n const registryPath = watch.environment[REGISTRY_PATH_ENV];\n let registry = registries.get(registryPath);\n if (!registry) {\n registry = createDefaultProcessRegistry(watch.environment);\n registries.set(registryPath, registry);\n }\n return cleanupRunnerSession({\n sessionId: watch.sessionId,\n paths: new RunnerPaths(watch.repository, watch.environment),\n processRegistry: registry,\n ownerPid: watch.ownerPid,\n generation: watch.generation,\n });\n };\n}\n\nasync function loadTelemetry(environment: LifelineWatch['environment']): Promise<PoTelemetry> {\n const { createPoTelemetry } = await import('@agimon-ai/po-core/telemetry');\n const shared = createPoTelemetry({\n serviceName: 'po-runner-host',\n packageName: '@agimon-ai/po-runner',\n env: environment,\n enableLogs: true,\n });\n process.once('beforeExit', () => void shared.shutdown());\n return shared;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runnerHost.cjs","names":["watchOwner","LIFELINE_ENV","spawn","os","fs","pathToFileURL","REGISTRY_PATH_ENV"],"sources":["../../src/bin/runnerHost.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport { pathToFileURL } from 'node:url';\n\nimport type { PoTelemetry } from '@agimon-ai/po-core/telemetry';\n\nimport { OBSERVE_SESSIONS_FLAG } from '../constants/lifeline';\nimport { REGISTRY_PATH_ENV } from '../constants/runnerRegistry';\nimport { LIFELINE_ENV, observeSessions, watchOwner } from '../services/lifeline/client';\nimport type { ProcessRegistryPort } from '../services/runnerRegistry';\nimport type { LifelineWatch } from '../types/lifeline';\n\n// Everything heavier loads on first use: the observer idles for its owner's whole life and\n// these imports alone would more than double its memory. A command's host loads none of it:\n// the server records the runner when it completes the record from this host's exit file.\n\ninterface CommandSpec {\n command: string;\n cwd: string;\n env: Record<string, string>;\n}\n\nconst POLL_MS = 10;\nconst FORWARDED_SIGNAL_GRACE_MS = 2_000;\nconst INVALID_COMMAND_SPEC_ERROR = 'invalid runner command spec';\nconst OWNER_LOST_EXIT = { reason: 'stopped', terminationReason: 'owner_lost', stopReason: 'owner session ended' };\n\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n if (argv[0] === OBSERVE_SESSIONS_FLAG) {\n observeSessions(cleanupSessions);\n return 0;\n }\n const [specPath, gatePath, exitPath] = argv;\n if (!specPath || !gatePath || !exitPath) throw new Error('runnerHost requires spec, gate, and exit paths');\n const spec = await readSpec(specPath);\n let ownerLost = false;\n let onOwnerLost = (): void => undefined;\n // Watched from before the gate: an owner that dies before opening it never will, and nothing\n // else would end that wait.\n //\n // The lifeline comes from the spec rather than this process's environment: an\n // rmux server outlives the session that started it, so its own environment can\n // still name a previous session's socket.\n const detachLifeline = watchOwner(() => {\n ownerLost = true;\n onOwnerLost();\n }, spec.env[LIFELINE_ENV]);\n if (!(await waitForGate(gatePath, () => ownerLost))) {\n detachLifeline();\n await writeAtomic(exitPath, { code: null, signal: null, ...OWNER_LOST_EXIT });\n return 1;\n }\n let child: ChildProcess | undefined;\n let forwardedSignal: NodeJS.Signals | null = null;\n let escalationTimer: NodeJS.Timeout | undefined;\n const signalChild = (signal: NodeJS.Signals): void => {\n if (!child?.pid) return;\n try {\n process.kill(-child.pid, signal);\n } catch {\n child.kill(signal);\n }\n };\n const forward = (signal: NodeJS.Signals): void => {\n forwardedSignal = signal;\n signalChild(signal);\n escalationTimer ??= setTimeout(() => signalChild('SIGKILL'), FORWARDED_SIGNAL_GRACE_MS);\n };\n const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGHUP'];\n for (const signal of signals) process.once(signal, () => forward(signal));\n\n child = spawn(spec.command, {\n cwd: spec.cwd,\n env: spec.env,\n shell: '/bin/bash',\n stdio: 'inherit',\n detached: true,\n });\n if (forwardedSignal) forward(forwardedSignal);\n\n // This process supervises the command for its whole life, so it is also the\n // right place to notice an owner that died without stopping it.\n onOwnerLost = () => forward('SIGTERM');\n\n try {\n const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => {\n child?.once('error', reject);\n child?.once('exit', (code, signal) => resolve({ code, signal: signal ?? forwardedSignal }));\n });\n await writeAtomic(exitPath, { ...result, ...(ownerLost ? OWNER_LOST_EXIT : {}) });\n return result.code !== null ? result.code : result.signal ? 128 + os.constants.signals[result.signal] : 1;\n } finally {\n detachLifeline();\n if (escalationTimer) clearTimeout(escalationTimer);\n }\n}\n\nasync function readSpec(target: string): Promise<CommandSpec> {\n const value: unknown = JSON.parse(await fs.readFile(target, 'utf8'));\n if (typeof value !== 'object' || value === null) throw new Error(INVALID_COMMAND_SPEC_ERROR);\n const spec = value as Partial<CommandSpec>;\n if (typeof spec.command !== 'string' || typeof spec.cwd !== 'string' || !spec.env) {\n throw new Error(INVALID_COMMAND_SPEC_ERROR);\n }\n return { command: spec.command, cwd: spec.cwd, env: spec.env };\n}\n\n/** False once the owner is gone, since only the owner opens the gate. */\nasync function waitForGate(target: string, abandoned: () => boolean): Promise<boolean> {\n while (!abandoned()) {\n try {\n await fs.access(target);\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, POLL_MS));\n }\n return false;\n}\n\nasync function writeAtomic(target: string, value: unknown): Promise<void> {\n const temporary = `${target}.${process.pid}.tmp`;\n await fs.writeFile(temporary, `${JSON.stringify(value)}\\n`, { mode: 0o600 });\n await fs.rename(temporary, target);\n}\n\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n\n/** Loaded on the first session an owner leaves behind, then shared by every later one. */\nlet cleaner: Promise<(watch: LifelineWatch) => Promise<void>> | undefined;\nlet telemetry: Promise<PoTelemetry> | undefined;\n\n/**\n * A separate process survives SIGKILL of the owner, even if that session has no active runners.\n * The owner cleans up each session it ends with its own shared services and removes its marker,\n * so only a session still marked with its generation is left here; the rest load nothing.\n */\nasync function cleanupSessions(watches: readonly LifelineWatch[]): Promise<void> {\n const failures: { watch: LifelineWatch; error: unknown }[] = [];\n // One session failing to clean up must not stop the others.\n for (const watch of watches) {\n try {\n // The same fence cleanupRunnerSession applies, checked before anything loads for it.\n if ((await readMarker(watch.marker)) !== watch.generation) continue;\n const clean = await (cleaner ??= loadCleaner());\n await clean(watch);\n } catch (error) {\n failures.push({ watch, error });\n }\n }\n const [first] = failures;\n if (!first) return;\n const log = await (telemetry ??= loadTelemetry(first.watch.environment));\n for (const { watch, error } of failures) {\n await log.recordError('po_runner.observer_cleanup_failed', error, { 'session.id': watch.sessionId });\n }\n await log.flush();\n throw new AggregateError(\n failures.map(({ error }) => error),\n `Could not clean up runner sessions ${failures.map(({ watch }) => watch.sessionId).join(', ')}`,\n );\n}\n\nasync function readMarker(marker: string): Promise<string | undefined> {\n try {\n return await fs.readFile(marker, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n}\n\n/** One registry per registry path, held until the observer runs out of work. */\nasync function loadCleaner(): Promise<(watch: LifelineWatch) => Promise<void>> {\n const [{ cleanupRunnerSession }, { RunnerPaths }, { createDefaultProcessRegistry }] = await Promise.all([\n import('../services/runnerHost'),\n import('../services/runnerPaths'),\n import('../services/runnerRegistry'),\n ]);\n const registries = new Map<string | undefined, ProcessRegistryPort>();\n process.once('beforeExit', () => {\n for (const registry of registries.values()) registry.close();\n });\n return (watch) => {\n const registryPath = watch.environment[REGISTRY_PATH_ENV];\n let registry = registries.get(registryPath);\n if (!registry) {\n registry = createDefaultProcessRegistry(watch.environment);\n registries.set(registryPath, registry);\n }\n return cleanupRunnerSession({\n sessionId: watch.sessionId,\n paths: new RunnerPaths(watch.repository, watch.environment),\n processRegistry: registry,\n ownerPid: watch.ownerPid,\n generation: watch.generation,\n });\n };\n}\n\nasync function loadTelemetry(environment: LifelineWatch['environment']): Promise<PoTelemetry> {\n const { createPoTelemetry } = await import('@agimon-ai/po-core/telemetry');\n const shared = createPoTelemetry({\n serviceName: 'po-runner-host',\n packageName: '@agimon-ai/po-runner',\n env: environment,\n enableLogs: true,\n });\n process.once('beforeExit', () => void shared.shutdown());\n return shared;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runnerHost.d.mts","names":[],"sources":["../../src/bin/runnerHost.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"runnerHost.d.mts","names":[],"sources":["../../src/bin/runnerHost.ts"],"mappings":";wBA4BsB,KAAK,2BAAkD"}
|
package/dist/bin/runnerHost.mjs
CHANGED
|
@@ -9,6 +9,11 @@ import os from "node:os";
|
|
|
9
9
|
const POLL_MS = 10;
|
|
10
10
|
const FORWARDED_SIGNAL_GRACE_MS = 2e3;
|
|
11
11
|
const INVALID_COMMAND_SPEC_ERROR = "invalid runner command spec";
|
|
12
|
+
const OWNER_LOST_EXIT = {
|
|
13
|
+
reason: "stopped",
|
|
14
|
+
terminationReason: "owner_lost",
|
|
15
|
+
stopReason: "owner session ended"
|
|
16
|
+
};
|
|
12
17
|
async function main(argv = process.argv.slice(2)) {
|
|
13
18
|
if (argv[0] === "--observe-sessions") {
|
|
14
19
|
observeSessions(cleanupSessions);
|
|
@@ -17,11 +22,24 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
17
22
|
const [specPath, gatePath, exitPath] = argv;
|
|
18
23
|
if (!specPath || !gatePath || !exitPath) throw new Error("runnerHost requires spec, gate, and exit paths");
|
|
19
24
|
const spec = await readSpec(specPath);
|
|
20
|
-
|
|
25
|
+
let ownerLost = false;
|
|
26
|
+
let onOwnerLost = () => void 0;
|
|
27
|
+
const detachLifeline = watchOwner(() => {
|
|
28
|
+
ownerLost = true;
|
|
29
|
+
onOwnerLost();
|
|
30
|
+
}, spec.env[LIFELINE_ENV]);
|
|
31
|
+
if (!await waitForGate(gatePath, () => ownerLost)) {
|
|
32
|
+
detachLifeline();
|
|
33
|
+
await writeAtomic(exitPath, {
|
|
34
|
+
code: null,
|
|
35
|
+
signal: null,
|
|
36
|
+
...OWNER_LOST_EXIT
|
|
37
|
+
});
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
21
40
|
let child;
|
|
22
41
|
let forwardedSignal = null;
|
|
23
42
|
let escalationTimer;
|
|
24
|
-
let ownerLost = false;
|
|
25
43
|
const signalChild = (signal) => {
|
|
26
44
|
if (!child?.pid) return;
|
|
27
45
|
try {
|
|
@@ -48,10 +66,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
48
66
|
detached: true
|
|
49
67
|
});
|
|
50
68
|
if (forwardedSignal) forward(forwardedSignal);
|
|
51
|
-
|
|
52
|
-
ownerLost = true;
|
|
53
|
-
forward("SIGTERM");
|
|
54
|
-
}, spec.env[LIFELINE_ENV]);
|
|
69
|
+
onOwnerLost = () => forward("SIGTERM");
|
|
55
70
|
try {
|
|
56
71
|
const result = await new Promise((resolve, reject) => {
|
|
57
72
|
child?.once("error", reject);
|
|
@@ -62,11 +77,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
62
77
|
});
|
|
63
78
|
await writeAtomic(exitPath, {
|
|
64
79
|
...result,
|
|
65
|
-
...ownerLost ? {
|
|
66
|
-
reason: "stopped",
|
|
67
|
-
terminationReason: "owner_lost",
|
|
68
|
-
stopReason: "owner session ended"
|
|
69
|
-
} : {}
|
|
80
|
+
...ownerLost ? OWNER_LOST_EXIT : {}
|
|
70
81
|
});
|
|
71
82
|
return result.code !== null ? result.code : result.signal ? 128 + os.constants.signals[result.signal] : 1;
|
|
72
83
|
} finally {
|
|
@@ -85,16 +96,18 @@ async function readSpec(target) {
|
|
|
85
96
|
env: spec.env
|
|
86
97
|
};
|
|
87
98
|
}
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
/** False once the owner is gone, since only the owner opens the gate. */
|
|
100
|
+
async function waitForGate(target, abandoned) {
|
|
101
|
+
while (!abandoned()) {
|
|
90
102
|
try {
|
|
91
103
|
await fs.access(target);
|
|
92
|
-
return;
|
|
104
|
+
return true;
|
|
93
105
|
} catch (error) {
|
|
94
106
|
if (error.code !== "ENOENT") throw error;
|
|
95
107
|
}
|
|
96
108
|
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
97
109
|
}
|
|
110
|
+
return false;
|
|
98
111
|
}
|
|
99
112
|
async function writeAtomic(target, value) {
|
|
100
113
|
const temporary = `${target}.${process.pid}.tmp`;
|
|
@@ -145,7 +158,7 @@ async function readMarker(marker) {
|
|
|
145
158
|
async function loadCleaner() {
|
|
146
159
|
const [{ cleanupRunnerSession }, { RunnerPaths }, { createDefaultProcessRegistry }] = await Promise.all([
|
|
147
160
|
import("../runnerHost-B2Jiba1U.mjs").then((n) => n.r),
|
|
148
|
-
import("../runnerPaths-
|
|
161
|
+
import("../runnerPaths-DtT9RJxQ.mjs").then((n) => n.r),
|
|
149
162
|
import("../runnerRegistry-_B6LXMhS.mjs").then((n) => n.r)
|
|
150
163
|
]);
|
|
151
164
|
const registries = /* @__PURE__ */ new Map();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runnerHost.mjs","names":[],"sources":["../../src/bin/runnerHost.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport { pathToFileURL } from 'node:url';\n\nimport type { PoTelemetry } from '@agimon-ai/po-core/telemetry';\n\nimport { OBSERVE_SESSIONS_FLAG } from '../constants/lifeline';\nimport { REGISTRY_PATH_ENV } from '../constants/runnerRegistry';\nimport { LIFELINE_ENV, observeSessions, watchOwner } from '../services/lifeline/client';\nimport type { ProcessRegistryPort } from '../services/runnerRegistry';\nimport type { LifelineWatch } from '../types/lifeline';\n\n// Everything heavier loads on first use: the observer idles for its owner's whole life and\n// these imports alone would more than double its memory. A command's host loads none of it:\n// the server records the runner when it completes the record from this host's exit file.\n\ninterface CommandSpec {\n command: string;\n cwd: string;\n env: Record<string, string>;\n}\n\nconst POLL_MS = 10;\nconst FORWARDED_SIGNAL_GRACE_MS = 2_000;\nconst INVALID_COMMAND_SPEC_ERROR = 'invalid runner command spec';\n\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n if (argv[0] === OBSERVE_SESSIONS_FLAG) {\n observeSessions(cleanupSessions);\n return 0;\n }\n const [specPath, gatePath, exitPath] = argv;\n if (!specPath || !gatePath || !exitPath) throw new Error('runnerHost requires spec, gate, and exit paths');\n const spec = await readSpec(specPath);\n await waitForGate(gatePath);\n let child: ChildProcess | undefined;\n let forwardedSignal: NodeJS.Signals | null = null;\n let escalationTimer: NodeJS.Timeout | undefined;\n let ownerLost = false;\n const signalChild = (signal: NodeJS.Signals): void => {\n if (!child?.pid) return;\n try {\n process.kill(-child.pid, signal);\n } catch {\n child.kill(signal);\n }\n };\n const forward = (signal: NodeJS.Signals): void => {\n forwardedSignal = signal;\n signalChild(signal);\n escalationTimer ??= setTimeout(() => signalChild('SIGKILL'), FORWARDED_SIGNAL_GRACE_MS);\n };\n const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGHUP'];\n for (const signal of signals) process.once(signal, () => forward(signal));\n\n child = spawn(spec.command, {\n cwd: spec.cwd,\n env: spec.env,\n shell: '/bin/bash',\n stdio: 'inherit',\n detached: true,\n });\n if (forwardedSignal) forward(forwardedSignal);\n\n // This process supervises the command for its whole life, so it is also the\n // right place to notice an owner that died without stopping it.\n //\n // The lifeline comes from the spec rather than this process's environment: an\n // rmux server outlives the session that started it, so its own environment can\n // still name a previous session's socket.\n const detachLifeline = watchOwner(() => {\n ownerLost = true;\n forward('SIGTERM');\n }, spec.env[LIFELINE_ENV]);\n\n try {\n const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => {\n child?.once('error', reject);\n child?.once('exit', (code, signal) => resolve({ code, signal: signal ?? forwardedSignal }));\n });\n await writeAtomic(exitPath, {\n ...result,\n ...(ownerLost ? { reason: 'stopped', terminationReason: 'owner_lost', stopReason: 'owner session ended' } : {}),\n });\n return result.code !== null ? result.code : result.signal ? 128 + os.constants.signals[result.signal] : 1;\n } finally {\n detachLifeline();\n if (escalationTimer) clearTimeout(escalationTimer);\n }\n}\n\nasync function readSpec(target: string): Promise<CommandSpec> {\n const value: unknown = JSON.parse(await fs.readFile(target, 'utf8'));\n if (typeof value !== 'object' || value === null) throw new Error(INVALID_COMMAND_SPEC_ERROR);\n const spec = value as Partial<CommandSpec>;\n if (typeof spec.command !== 'string' || typeof spec.cwd !== 'string' || !spec.env) {\n throw new Error(INVALID_COMMAND_SPEC_ERROR);\n }\n return { command: spec.command, cwd: spec.cwd, env: spec.env };\n}\n\nasync function waitForGate(target: string): Promise<void> {\n for (;;) {\n try {\n await fs.access(target);\n return;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, POLL_MS));\n }\n}\n\nasync function writeAtomic(target: string, value: unknown): Promise<void> {\n const temporary = `${target}.${process.pid}.tmp`;\n await fs.writeFile(temporary, `${JSON.stringify(value)}\\n`, { mode: 0o600 });\n await fs.rename(temporary, target);\n}\n\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n\n/** Loaded on the first session an owner leaves behind, then shared by every later one. */\nlet cleaner: Promise<(watch: LifelineWatch) => Promise<void>> | undefined;\nlet telemetry: Promise<PoTelemetry> | undefined;\n\n/**\n * A separate process survives SIGKILL of the owner, even if that session has no active runners.\n * The owner cleans up each session it ends with its own shared services and removes its marker,\n * so only a session still marked with its generation is left here; the rest load nothing.\n */\nasync function cleanupSessions(watches: readonly LifelineWatch[]): Promise<void> {\n const failures: { watch: LifelineWatch; error: unknown }[] = [];\n // One session failing to clean up must not stop the others.\n for (const watch of watches) {\n try {\n // The same fence cleanupRunnerSession applies, checked before anything loads for it.\n if ((await readMarker(watch.marker)) !== watch.generation) continue;\n const clean = await (cleaner ??= loadCleaner());\n await clean(watch);\n } catch (error) {\n failures.push({ watch, error });\n }\n }\n const [first] = failures;\n if (!first) return;\n const log = await (telemetry ??= loadTelemetry(first.watch.environment));\n for (const { watch, error } of failures) {\n await log.recordError('po_runner.observer_cleanup_failed', error, { 'session.id': watch.sessionId });\n }\n await log.flush();\n throw new AggregateError(\n failures.map(({ error }) => error),\n `Could not clean up runner sessions ${failures.map(({ watch }) => watch.sessionId).join(', ')}`,\n );\n}\n\nasync function readMarker(marker: string): Promise<string | undefined> {\n try {\n return await fs.readFile(marker, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n}\n\n/** One registry per registry path, held until the observer runs out of work. */\nasync function loadCleaner(): Promise<(watch: LifelineWatch) => Promise<void>> {\n const [{ cleanupRunnerSession }, { RunnerPaths }, { createDefaultProcessRegistry }] = await Promise.all([\n import('../services/runnerHost'),\n import('../services/runnerPaths'),\n import('../services/runnerRegistry'),\n ]);\n const registries = new Map<string | undefined, ProcessRegistryPort>();\n process.once('beforeExit', () => {\n for (const registry of registries.values()) registry.close();\n });\n return (watch) => {\n const registryPath = watch.environment[REGISTRY_PATH_ENV];\n let registry = registries.get(registryPath);\n if (!registry) {\n registry = createDefaultProcessRegistry(watch.environment);\n registries.set(registryPath, registry);\n }\n return cleanupRunnerSession({\n sessionId: watch.sessionId,\n paths: new RunnerPaths(watch.repository, watch.environment),\n processRegistry: registry,\n ownerPid: watch.ownerPid,\n generation: watch.generation,\n });\n };\n}\n\nasync function loadTelemetry(environment: LifelineWatch['environment']): Promise<PoTelemetry> {\n const { createPoTelemetry } = await import('@agimon-ai/po-core/telemetry');\n const shared = createPoTelemetry({\n serviceName: 'po-runner-host',\n packageName: '@agimon-ai/po-runner',\n env: environment,\n enableLogs: true,\n });\n process.once('beforeExit', () => void shared.shutdown());\n return shared;\n}\n"],"mappings":";;;;;;;;AAuBA,MAAM,UAAU;AAChB,MAAM,4BAA4B;AAClC,MAAM,6BAA6B;AAEnC,eAAsB,KAAK,OAA0B,QAAQ,KAAK,MAAM,CAAC,GAAoB;CAC3F,IAAI,KAAK,OAAA,sBAA8B;EACrC,gBAAgB,eAAe;EAC/B,OAAO;CACT;CACA,MAAM,CAAC,UAAU,UAAU,YAAY;CACvC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,UAAU,MAAM,IAAI,MAAM,gDAAgD;CACzG,MAAM,OAAO,MAAM,SAAS,QAAQ;CACpC,MAAM,YAAY,QAAQ;CAC1B,IAAI;CACJ,IAAI,kBAAyC;CAC7C,IAAI;CACJ,IAAI,YAAY;CAChB,MAAM,eAAe,WAAiC;EACpD,IAAI,CAAC,OAAO,KAAK;EACjB,IAAI;GACF,QAAQ,KAAK,CAAC,MAAM,KAAK,MAAM;EACjC,QAAQ;GACN,MAAM,KAAK,MAAM;EACnB;CACF;CACA,MAAM,WAAW,WAAiC;EAChD,kBAAkB;EAClB,YAAY,MAAM;EAClB,oBAAoB,iBAAiB,YAAY,SAAS,GAAG,yBAAyB;CACxF;CAEA,KAAK,MAAM,UAAU;EADc;EAAW;EAAU;CAC7B,GAAG,QAAQ,KAAK,cAAc,QAAQ,MAAM,CAAC;CAExE,QAAQ,MAAM,KAAK,SAAS;EAC1B,KAAK,KAAK;EACV,KAAK,KAAK;EACV,OAAO;EACP,OAAO;EACP,UAAU;CACZ,CAAC;CACD,IAAI,iBAAiB,QAAQ,eAAe;CAQ5C,MAAM,iBAAiB,iBAAiB;EACtC,YAAY;EACZ,QAAQ,SAAS;CACnB,GAAG,KAAK,IAAI,aAAa;CAEzB,IAAI;EACF,MAAM,SAAS,MAAM,IAAI,SAAiE,SAAS,WAAW;GAC5G,OAAO,KAAK,SAAS,MAAM;GAC3B,OAAO,KAAK,SAAS,MAAM,WAAW,QAAQ;IAAE;IAAM,QAAQ,UAAU;GAAgB,CAAC,CAAC;EAC5F,CAAC;EACD,MAAM,YAAY,UAAU;GAC1B,GAAG;GACH,GAAI,YAAY;IAAE,QAAQ;IAAW,mBAAmB;IAAc,YAAY;GAAsB,IAAI,CAAC;EAC/G,CAAC;EACD,OAAO,OAAO,SAAS,OAAO,OAAO,OAAO,OAAO,SAAS,MAAM,GAAG,UAAU,QAAQ,OAAO,UAAU;CAC1G,UAAU;EACR,eAAe;EACf,IAAI,iBAAiB,aAAa,eAAe;CACnD;AACF;AAEA,eAAe,SAAS,QAAsC;CAC5D,MAAM,QAAiB,KAAK,MAAM,MAAM,GAAG,SAAS,QAAQ,MAAM,CAAC;CACnE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,MAAM,IAAI,MAAM,0BAA0B;CAC3F,MAAM,OAAO;CACb,IAAI,OAAO,KAAK,YAAY,YAAY,OAAO,KAAK,QAAQ,YAAY,CAAC,KAAK,KAC5E,MAAM,IAAI,MAAM,0BAA0B;CAE5C,OAAO;EAAE,SAAS,KAAK;EAAS,KAAK,KAAK;EAAK,KAAK,KAAK;CAAI;AAC/D;AAEA,eAAe,YAAY,QAA+B;CACxD,SAAS;EACP,IAAI;GACF,MAAM,GAAG,OAAO,MAAM;GACtB;EACF,SAAS,OAAO;GACd,IAAK,MAAgC,SAAS,UAAU,MAAM;EAChE;EACA,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,OAAO,CAAC;CAC7D;AACF;AAEA,eAAe,YAAY,QAAgB,OAA+B;CACxE,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,IAAI;CAC3C,MAAM,GAAG,UAAU,WAAW,GAAG,KAAK,UAAU,KAAK,EAAE,KAAK,EAAE,MAAM,IAAM,CAAC;CAC3E,MAAM,GAAG,OAAO,WAAW,MAAM;AACnC;AAEA,IAAI,QAAQ,KAAK,MAAM,YAAY,QAAQ,cAAc,QAAQ,KAAK,EAAE,CAAC,CAAC,MACxE,KAAK,CAAC,CAAC,MACJ,SAAS;CACR,QAAQ,WAAW;AACrB,IACC,UAAmB;CAClB,QAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,GAAG;CAClF,QAAQ,WAAW;AACrB,CACF;;AAIF,IAAI;AACJ,IAAI;;;;;;AAOJ,eAAe,gBAAgB,SAAkD;CAC/E,MAAM,WAAuD,CAAC;CAE9D,KAAK,MAAM,SAAS,SAClB,IAAI;EAEF,IAAK,MAAM,WAAW,MAAM,MAAM,MAAO,MAAM,YAAY;EAE3D,OAAM,OADe,YAAY,YAAY,GAAA,CACjC,KAAK;CACnB,SAAS,OAAO;EACd,SAAS,KAAK;GAAE;GAAO;EAAM,CAAC;CAChC;CAEF,MAAM,CAAC,SAAS;CAChB,IAAI,CAAC,OAAO;CACZ,MAAM,MAAM,OAAO,cAAc,cAAc,MAAM,MAAM,WAAW;CACtE,KAAK,MAAM,EAAE,OAAO,WAAW,UAC7B,MAAM,IAAI,YAAY,qCAAqC,OAAO,EAAE,cAAc,MAAM,UAAU,CAAC;CAErG,MAAM,IAAI,MAAM;CAChB,MAAM,IAAI,eACR,SAAS,KAAK,EAAE,YAAY,KAAK,GACjC,sCAAsC,SAAS,KAAK,EAAE,YAAY,MAAM,SAAS,CAAC,CAAC,KAAK,IAAI,GAC9F;AACF;AAEA,eAAe,WAAW,QAA6C;CACrE,IAAI;EACF,OAAO,MAAM,GAAG,SAAS,QAAQ,MAAM;CACzC,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO,KAAA;EAC/D,MAAM;CACR;AACF;;AAGA,eAAe,cAAgE;CAC7E,MAAM,CAAC,EAAE,wBAAwB,EAAE,eAAe,EAAE,kCAAkC,MAAM,QAAQ,IAAI;EACtG,OAAO,6BAAyB,CAAA,MAAA,MAAA,EAAA,CAAA;EAChC,OAAO,8BAA0B,CAAA,MAAA,MAAA,EAAA,CAAA;EACjC,OAAO,iCAA6B,CAAA,MAAA,MAAA,EAAA,CAAA;CACtC,CAAC;CACD,MAAM,6BAAa,IAAI,IAA6C;CACpE,QAAQ,KAAK,oBAAoB;EAC/B,KAAK,MAAM,YAAY,WAAW,OAAO,GAAG,SAAS,MAAM;CAC7D,CAAC;CACD,QAAQ,UAAU;EAChB,MAAM,eAAe,MAAM,YAAY;EACvC,IAAI,WAAW,WAAW,IAAI,YAAY;EAC1C,IAAI,CAAC,UAAU;GACb,WAAW,6BAA6B,MAAM,WAAW;GACzD,WAAW,IAAI,cAAc,QAAQ;EACvC;EACA,OAAO,qBAAqB;GAC1B,WAAW,MAAM;GACjB,OAAO,IAAI,YAAY,MAAM,YAAY,MAAM,WAAW;GAC1D,iBAAiB;GACjB,UAAU,MAAM;GAChB,YAAY,MAAM;EACpB,CAAC;CACH;AACF;AAEA,eAAe,cAAc,aAAiE;CAC5F,MAAM,EAAE,sBAAsB,MAAM,OAAO;CAC3C,MAAM,SAAS,kBAAkB;EAC/B,aAAa;EACb,aAAa;EACb,KAAK;EACL,YAAY;CACd,CAAC;CACD,QAAQ,KAAK,oBAAoB,KAAK,OAAO,SAAS,CAAC;CACvD,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"runnerHost.mjs","names":[],"sources":["../../src/bin/runnerHost.ts"],"sourcesContent":["import { type ChildProcess, spawn } from 'node:child_process';\nimport fs from 'node:fs/promises';\nimport os from 'node:os';\nimport { pathToFileURL } from 'node:url';\n\nimport type { PoTelemetry } from '@agimon-ai/po-core/telemetry';\n\nimport { OBSERVE_SESSIONS_FLAG } from '../constants/lifeline';\nimport { REGISTRY_PATH_ENV } from '../constants/runnerRegistry';\nimport { LIFELINE_ENV, observeSessions, watchOwner } from '../services/lifeline/client';\nimport type { ProcessRegistryPort } from '../services/runnerRegistry';\nimport type { LifelineWatch } from '../types/lifeline';\n\n// Everything heavier loads on first use: the observer idles for its owner's whole life and\n// these imports alone would more than double its memory. A command's host loads none of it:\n// the server records the runner when it completes the record from this host's exit file.\n\ninterface CommandSpec {\n command: string;\n cwd: string;\n env: Record<string, string>;\n}\n\nconst POLL_MS = 10;\nconst FORWARDED_SIGNAL_GRACE_MS = 2_000;\nconst INVALID_COMMAND_SPEC_ERROR = 'invalid runner command spec';\nconst OWNER_LOST_EXIT = { reason: 'stopped', terminationReason: 'owner_lost', stopReason: 'owner session ended' };\n\nexport async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {\n if (argv[0] === OBSERVE_SESSIONS_FLAG) {\n observeSessions(cleanupSessions);\n return 0;\n }\n const [specPath, gatePath, exitPath] = argv;\n if (!specPath || !gatePath || !exitPath) throw new Error('runnerHost requires spec, gate, and exit paths');\n const spec = await readSpec(specPath);\n let ownerLost = false;\n let onOwnerLost = (): void => undefined;\n // Watched from before the gate: an owner that dies before opening it never will, and nothing\n // else would end that wait.\n //\n // The lifeline comes from the spec rather than this process's environment: an\n // rmux server outlives the session that started it, so its own environment can\n // still name a previous session's socket.\n const detachLifeline = watchOwner(() => {\n ownerLost = true;\n onOwnerLost();\n }, spec.env[LIFELINE_ENV]);\n if (!(await waitForGate(gatePath, () => ownerLost))) {\n detachLifeline();\n await writeAtomic(exitPath, { code: null, signal: null, ...OWNER_LOST_EXIT });\n return 1;\n }\n let child: ChildProcess | undefined;\n let forwardedSignal: NodeJS.Signals | null = null;\n let escalationTimer: NodeJS.Timeout | undefined;\n const signalChild = (signal: NodeJS.Signals): void => {\n if (!child?.pid) return;\n try {\n process.kill(-child.pid, signal);\n } catch {\n child.kill(signal);\n }\n };\n const forward = (signal: NodeJS.Signals): void => {\n forwardedSignal = signal;\n signalChild(signal);\n escalationTimer ??= setTimeout(() => signalChild('SIGKILL'), FORWARDED_SIGNAL_GRACE_MS);\n };\n const signals: NodeJS.Signals[] = ['SIGTERM', 'SIGINT', 'SIGHUP'];\n for (const signal of signals) process.once(signal, () => forward(signal));\n\n child = spawn(spec.command, {\n cwd: spec.cwd,\n env: spec.env,\n shell: '/bin/bash',\n stdio: 'inherit',\n detached: true,\n });\n if (forwardedSignal) forward(forwardedSignal);\n\n // This process supervises the command for its whole life, so it is also the\n // right place to notice an owner that died without stopping it.\n onOwnerLost = () => forward('SIGTERM');\n\n try {\n const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => {\n child?.once('error', reject);\n child?.once('exit', (code, signal) => resolve({ code, signal: signal ?? forwardedSignal }));\n });\n await writeAtomic(exitPath, { ...result, ...(ownerLost ? OWNER_LOST_EXIT : {}) });\n return result.code !== null ? result.code : result.signal ? 128 + os.constants.signals[result.signal] : 1;\n } finally {\n detachLifeline();\n if (escalationTimer) clearTimeout(escalationTimer);\n }\n}\n\nasync function readSpec(target: string): Promise<CommandSpec> {\n const value: unknown = JSON.parse(await fs.readFile(target, 'utf8'));\n if (typeof value !== 'object' || value === null) throw new Error(INVALID_COMMAND_SPEC_ERROR);\n const spec = value as Partial<CommandSpec>;\n if (typeof spec.command !== 'string' || typeof spec.cwd !== 'string' || !spec.env) {\n throw new Error(INVALID_COMMAND_SPEC_ERROR);\n }\n return { command: spec.command, cwd: spec.cwd, env: spec.env };\n}\n\n/** False once the owner is gone, since only the owner opens the gate. */\nasync function waitForGate(target: string, abandoned: () => boolean): Promise<boolean> {\n while (!abandoned()) {\n try {\n await fs.access(target);\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, POLL_MS));\n }\n return false;\n}\n\nasync function writeAtomic(target: string, value: unknown): Promise<void> {\n const temporary = `${target}.${process.pid}.tmp`;\n await fs.writeFile(temporary, `${JSON.stringify(value)}\\n`, { mode: 0o600 });\n await fs.rename(temporary, target);\n}\n\nif (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {\n main().then(\n (code) => {\n process.exitCode = code;\n },\n (error: unknown) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n },\n );\n}\n\n/** Loaded on the first session an owner leaves behind, then shared by every later one. */\nlet cleaner: Promise<(watch: LifelineWatch) => Promise<void>> | undefined;\nlet telemetry: Promise<PoTelemetry> | undefined;\n\n/**\n * A separate process survives SIGKILL of the owner, even if that session has no active runners.\n * The owner cleans up each session it ends with its own shared services and removes its marker,\n * so only a session still marked with its generation is left here; the rest load nothing.\n */\nasync function cleanupSessions(watches: readonly LifelineWatch[]): Promise<void> {\n const failures: { watch: LifelineWatch; error: unknown }[] = [];\n // One session failing to clean up must not stop the others.\n for (const watch of watches) {\n try {\n // The same fence cleanupRunnerSession applies, checked before anything loads for it.\n if ((await readMarker(watch.marker)) !== watch.generation) continue;\n const clean = await (cleaner ??= loadCleaner());\n await clean(watch);\n } catch (error) {\n failures.push({ watch, error });\n }\n }\n const [first] = failures;\n if (!first) return;\n const log = await (telemetry ??= loadTelemetry(first.watch.environment));\n for (const { watch, error } of failures) {\n await log.recordError('po_runner.observer_cleanup_failed', error, { 'session.id': watch.sessionId });\n }\n await log.flush();\n throw new AggregateError(\n failures.map(({ error }) => error),\n `Could not clean up runner sessions ${failures.map(({ watch }) => watch.sessionId).join(', ')}`,\n );\n}\n\nasync function readMarker(marker: string): Promise<string | undefined> {\n try {\n return await fs.readFile(marker, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw error;\n }\n}\n\n/** One registry per registry path, held until the observer runs out of work. */\nasync function loadCleaner(): Promise<(watch: LifelineWatch) => Promise<void>> {\n const [{ cleanupRunnerSession }, { RunnerPaths }, { createDefaultProcessRegistry }] = await Promise.all([\n import('../services/runnerHost'),\n import('../services/runnerPaths'),\n import('../services/runnerRegistry'),\n ]);\n const registries = new Map<string | undefined, ProcessRegistryPort>();\n process.once('beforeExit', () => {\n for (const registry of registries.values()) registry.close();\n });\n return (watch) => {\n const registryPath = watch.environment[REGISTRY_PATH_ENV];\n let registry = registries.get(registryPath);\n if (!registry) {\n registry = createDefaultProcessRegistry(watch.environment);\n registries.set(registryPath, registry);\n }\n return cleanupRunnerSession({\n sessionId: watch.sessionId,\n paths: new RunnerPaths(watch.repository, watch.environment),\n processRegistry: registry,\n ownerPid: watch.ownerPid,\n generation: watch.generation,\n });\n };\n}\n\nasync function loadTelemetry(environment: LifelineWatch['environment']): Promise<PoTelemetry> {\n const { createPoTelemetry } = await import('@agimon-ai/po-core/telemetry');\n const shared = createPoTelemetry({\n serviceName: 'po-runner-host',\n packageName: '@agimon-ai/po-runner',\n env: environment,\n enableLogs: true,\n });\n process.once('beforeExit', () => void shared.shutdown());\n return shared;\n}\n"],"mappings":"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"}
|