


























@@ -1,5 +1,6 @@
11import path from "node:path";
22import type { AgentToolResult } from "@mariozechner/pi-agent-core";
3+import { emitDiagnosticEvent } from "../infra/diagnostic-events.js";
34import {
45DEFAULT_EXEC_APPROVAL_TIMEOUT_MS,
56resolveExecApprovalAllowedDecisions,
@@ -165,6 +166,40 @@ export type ExecProcessHandle = {
165166disableUpdates: () => void;
166167};
167168169+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+168203export function renderExecHostLabel(host: ExecHost) {
169204return host === "sandbox" ? "sandbox" : host === "gateway" ? "gateway" : "node";
170205}
@@ -523,6 +558,7 @@ export async function runExecProcess(opts: {
523558const startedAt = Date.now();
524559const sessionId = createSessionSlug();
525560const execCommand = opts.execCommand ?? opts.command;
561+const diagnosticTarget = opts.sandbox ? "sandbox" : "host";
526562const supervisor = getProcessSupervisor();
527563const shellRuntimeEnv: Record<string, string> = {
528564 ...opts.env,
@@ -759,11 +795,33 @@ export async function runExecProcess(opts: {
759795} catch (retryErr) {
760796markExited(session, null, null, "failed");
761797maybeNotifyOnExit(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+});
762809throw retryErr;
763810}
764811} else {
765812markExited(session, null, null, "failed");
766813maybeNotifyOnExit(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+});
767825throw err;
768826}
769827}
@@ -799,17 +857,32 @@ export async function runExecProcess(opts: {
799857token: sandboxFinalizeToken,
800858});
801859}
860+emitExecProcessCompleted({
861+command: opts.command,
862+mode: usingPty ? "pty" : "child",
863+ outcome,
864+sessionKey: opts.sessionKey,
865+target: diagnosticTarget,
866+});
802867return outcome;
803868})
804869.catch((err): ExecProcessOutcome => {
805870updatesDisabled = true;
806871markExited(session, null, null, "failed");
807872maybeNotifyOnExit(session, "failed");
808-return buildExecRuntimeErrorOutcome({
873+const outcome = buildExecRuntimeErrorOutcome({
809874error: err,
810875aggregated: session.aggregated.trim(),
811876durationMs: Date.now() - startedAt,
812877});
878+emitExecProcessCompleted({
879+command: opts.command,
880+mode: usingPty ? "pty" : "child",
881+ outcome,
882+sessionKey: opts.sessionKey,
883+target: diagnosticTarget,
884+});
885+return outcome;
813886});
814887815888return {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。