|
1 | 1 | import path from "node:path"; |
2 | 2 | import type { AgentToolResult } from "@mariozechner/pi-agent-core"; |
| 3 | +import { emitDiagnosticEvent } from "../infra/diagnostic-events.js"; |
3 | 4 | import { |
4 | 5 | DEFAULT_EXEC_APPROVAL_TIMEOUT_MS, |
5 | 6 | resolveExecApprovalAllowedDecisions, |
@@ -165,6 +166,40 @@ export type ExecProcessHandle = { |
165 | 166 | disableUpdates: () => void; |
166 | 167 | }; |
167 | 168 |
|
| 169 | +function normalizeExecExitSignal(signal: NodeJS.Signals | number | null): string | undefined { |
| 170 | + if (signal === null) { |
| 171 | + return undefined; |
| 172 | + } |
| 173 | + return String(signal); |
| 174 | +} |
| 175 | + |
| 176 | +function emitExecProcessCompleted(params: { |
| 177 | + command: string; |
| 178 | + mode: "child" | "pty"; |
| 179 | + outcome: ExecProcessOutcome; |
| 180 | + sessionKey?: string; |
| 181 | + target: "host" | "sandbox"; |
| 182 | +}): void { |
| 183 | + const exitSignal = normalizeExecExitSignal(params.outcome.exitSignal); |
| 184 | + emitDiagnosticEvent({ |
| 185 | + type: "exec.process.completed", |
| 186 | + target: params.target, |
| 187 | + mode: params.mode, |
| 188 | + outcome: params.outcome.status, |
| 189 | + durationMs: params.outcome.durationMs, |
| 190 | + commandLength: params.command.length, |
| 191 | + ...(params.sessionKey?.trim() ? { sessionKey: params.sessionKey.trim() } : {}), |
| 192 | + ...(typeof params.outcome.exitCode === "number" ? { exitCode: params.outcome.exitCode } : {}), |
| 193 | + ...(exitSignal ? { exitSignal } : {}), |
| 194 | + ...(params.outcome.status === "failed" |
| 195 | + ? { |
| 196 | + timedOut: params.outcome.timedOut, |
| 197 | + failureKind: params.outcome.failureKind, |
| 198 | + } |
| 199 | + : {}), |
| 200 | + }); |
| 201 | +} |
| 202 | + |
168 | 203 | export function renderExecHostLabel(host: ExecHost) { |
169 | 204 | return host === "sandbox" ? "sandbox" : host === "gateway" ? "gateway" : "node"; |
170 | 205 | } |
@@ -523,6 +558,7 @@ export async function runExecProcess(opts: { |
523 | 558 | const startedAt = Date.now(); |
524 | 559 | const sessionId = createSessionSlug(); |
525 | 560 | const execCommand = opts.execCommand ?? opts.command; |
| 561 | + const diagnosticTarget = opts.sandbox ? "sandbox" : "host"; |
526 | 562 | const supervisor = getProcessSupervisor(); |
527 | 563 | const shellRuntimeEnv: Record<string, string> = { |
528 | 564 | ...opts.env, |
@@ -759,11 +795,33 @@ export async function runExecProcess(opts: { |
759 | 795 | } catch (retryErr) { |
760 | 796 | markExited(session, null, null, "failed"); |
761 | 797 | maybeNotifyOnExit(session, "failed"); |
| 798 | + emitExecProcessCompleted({ |
| 799 | + command: opts.command, |
| 800 | + mode: "child", |
| 801 | + outcome: buildExecRuntimeErrorOutcome({ |
| 802 | + error: retryErr, |
| 803 | + aggregated: session.aggregated.trim(), |
| 804 | + durationMs: Date.now() - startedAt, |
| 805 | + }), |
| 806 | + sessionKey: opts.sessionKey, |
| 807 | + target: diagnosticTarget, |
| 808 | + }); |
762 | 809 | throw retryErr; |
763 | 810 | } |
764 | 811 | } else { |
765 | 812 | markExited(session, null, null, "failed"); |
766 | 813 | maybeNotifyOnExit(session, "failed"); |
| 814 | + emitExecProcessCompleted({ |
| 815 | + command: opts.command, |
| 816 | + mode: spawnSpec.mode, |
| 817 | + outcome: buildExecRuntimeErrorOutcome({ |
| 818 | + error: err, |
| 819 | + aggregated: session.aggregated.trim(), |
| 820 | + durationMs: Date.now() - startedAt, |
| 821 | + }), |
| 822 | + sessionKey: opts.sessionKey, |
| 823 | + target: diagnosticTarget, |
| 824 | + }); |
767 | 825 | throw err; |
768 | 826 | } |
769 | 827 | } |
@@ -799,17 +857,32 @@ export async function runExecProcess(opts: { |
799 | 857 | token: sandboxFinalizeToken, |
800 | 858 | }); |
801 | 859 | } |
| 860 | + emitExecProcessCompleted({ |
| 861 | + command: opts.command, |
| 862 | + mode: usingPty ? "pty" : "child", |
| 863 | + outcome, |
| 864 | + sessionKey: opts.sessionKey, |
| 865 | + target: diagnosticTarget, |
| 866 | + }); |
802 | 867 | return outcome; |
803 | 868 | }) |
804 | 869 | .catch((err): ExecProcessOutcome => { |
805 | 870 | updatesDisabled = true; |
806 | 871 | markExited(session, null, null, "failed"); |
807 | 872 | maybeNotifyOnExit(session, "failed"); |
808 | | - return buildExecRuntimeErrorOutcome({ |
| 873 | + const outcome = buildExecRuntimeErrorOutcome({ |
809 | 874 | error: err, |
810 | 875 | aggregated: session.aggregated.trim(), |
811 | 876 | durationMs: Date.now() - startedAt, |
812 | 877 | }); |
| 878 | + emitExecProcessCompleted({ |
| 879 | + command: opts.command, |
| 880 | + mode: usingPty ? "pty" : "child", |
| 881 | + outcome, |
| 882 | + sessionKey: opts.sessionKey, |
| 883 | + target: diagnosticTarget, |
| 884 | + }); |
| 885 | + return outcome; |
813 | 886 | }); |
814 | 887 |
|
815 | 888 | return { |
|
0 commit comments