* fix(acp): implicit streamToParent for mode=run without thread When spawning ACP sessions with mode=run and no thread binding, automatically route output to parent session instead of Discord. This enables agent-to-agent supervision patterns where the spawning agent wants results returned programmatically, not posted as chat. The change makes sessions_spawn with runtime=acp and thread=false behave like direct acpx invocation - output goes to the spawning session, not to Discord. Fixes the issue where mode=run without thread still posted to Discord because hasDeliveryTarget was true when called from a Discord context. * fix: use resolved spawnMode instead of params.mode Move implicit streamToParent check to after resolveSpawnMode so that both explicit mode="run" and omitted mode (which defaults to "run" when thread is false) correctly trigger parent routing. This fixes the issue where callers that rely on default mode selection would not get the intended parent streaming behavior. * fix: tighten implicit ACP parent relay gating (#42404) (thanks @davidguttman) --------- Co-authored-by: Onur Solmaz <2453968+osolmaz@users.noreply.github.com>
55 lines
2.5 KiB
TypeScript
55 lines
2.5 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
isHeartbeatActionWakeReason,
|
|
isHeartbeatEventDrivenReason,
|
|
normalizeHeartbeatWakeReason,
|
|
resolveHeartbeatReasonKind,
|
|
} from "./heartbeat-reason.js";
|
|
|
|
describe("heartbeat-reason", () => {
|
|
it("normalizes wake reasons with trim + requested fallback", () => {
|
|
expect(normalizeHeartbeatWakeReason(" cron:job-1 ")).toBe("cron:job-1");
|
|
expect(normalizeHeartbeatWakeReason(" ")).toBe("requested");
|
|
expect(normalizeHeartbeatWakeReason(undefined)).toBe("requested");
|
|
});
|
|
|
|
it("classifies known reason kinds", () => {
|
|
expect(resolveHeartbeatReasonKind("retry")).toBe("retry");
|
|
expect(resolveHeartbeatReasonKind("interval")).toBe("interval");
|
|
expect(resolveHeartbeatReasonKind("manual")).toBe("manual");
|
|
expect(resolveHeartbeatReasonKind("exec-event")).toBe("exec-event");
|
|
expect(resolveHeartbeatReasonKind("wake")).toBe("wake");
|
|
expect(resolveHeartbeatReasonKind("acp:spawn:stream")).toBe("wake");
|
|
expect(resolveHeartbeatReasonKind("cron:job-1")).toBe("cron");
|
|
expect(resolveHeartbeatReasonKind("hook:wake")).toBe("hook");
|
|
expect(resolveHeartbeatReasonKind(" hook:wake ")).toBe("hook");
|
|
});
|
|
|
|
it("classifies unknown reasons as other", () => {
|
|
expect(resolveHeartbeatReasonKind("requested")).toBe("other");
|
|
expect(resolveHeartbeatReasonKind("slow")).toBe("other");
|
|
expect(resolveHeartbeatReasonKind("")).toBe("other");
|
|
expect(resolveHeartbeatReasonKind(undefined)).toBe("other");
|
|
});
|
|
|
|
it("matches event-driven behavior used by heartbeat preflight", () => {
|
|
expect(isHeartbeatEventDrivenReason("exec-event")).toBe(true);
|
|
expect(isHeartbeatEventDrivenReason("cron:job-1")).toBe(true);
|
|
expect(isHeartbeatEventDrivenReason("wake")).toBe(true);
|
|
expect(isHeartbeatEventDrivenReason("acp:spawn:stream")).toBe(true);
|
|
expect(isHeartbeatEventDrivenReason("hook:gmail:sync")).toBe(true);
|
|
expect(isHeartbeatEventDrivenReason("interval")).toBe(false);
|
|
expect(isHeartbeatEventDrivenReason("manual")).toBe(false);
|
|
expect(isHeartbeatEventDrivenReason("other")).toBe(false);
|
|
});
|
|
|
|
it("matches action-priority wake behavior", () => {
|
|
expect(isHeartbeatActionWakeReason("manual")).toBe(true);
|
|
expect(isHeartbeatActionWakeReason("exec-event")).toBe(true);
|
|
expect(isHeartbeatActionWakeReason("hook:wake")).toBe(true);
|
|
expect(isHeartbeatActionWakeReason("interval")).toBe(false);
|
|
expect(isHeartbeatActionWakeReason("cron:job-1")).toBe(false);
|
|
expect(isHeartbeatActionWakeReason("retry")).toBe(false);
|
|
});
|
|
});
|