


















@@ -0,0 +1,123 @@
1+import { describe, expect, it } from "vitest";
2+import {
3+assertNoGatewayLogSentinels,
4+formatGatewayLogSentinelSummary,
5+scanDirectReplyTranscriptSentinels,
6+scanGatewayLogSentinels,
7+} from "./gateway-log-sentinel.js";
8+9+describe("gateway log sentinels", () => {
10+it("classifies May 13 beta.5 operational failure signatures", () => {
11+const findings = scanGatewayLogSentinels(
12+[
13+"2026-05-13T00:00:01Z plugin before_prompt_build hook failed: TypeError: boom",
14+"2026-05-13T00:00:02Z plugin before_tool_call crashed while evaluating policy",
15+"2026-05-13T00:00:03Z plugin manifest invalid: missing contracts.tools registration",
16+"2026-05-13T00:00:04Z codex app-server attempt timed out after 180000ms",
17+"2026-05-13T00:00:05Z codex_app_server progress stalled for run abc123",
18+"2026-05-13T00:00:06Z cron payload model openai/gpt-5.4 is not in model allowlist",
19+"2026-05-13T00:00:07Z OpenAI quota exceeded for live-frontier request",
20+].join("\n"),
21+);
22+23+expect(findings.map((finding) => finding.kind)).toEqual([
24+"plugin-hook-failure",
25+"plugin-hook-failure",
26+"plugin-contract-error",
27+"codex-app-server-timeout",
28+"stalled-agent-run",
29+"cron-model-allowlist",
30+"live-quota-or-subscription",
31+]);
32+expect(findings.find((finding) => finding.kind === "plugin-hook-failure")).toMatchObject({
33+verdict: "qa-harness-bug",
34+owner: "plugin",
35+productImpact: "P1",
36+});
37+expect(findings.find((finding) => finding.kind === "live-quota-or-subscription")).toMatchObject(
38+{
39+verdict: "environment-blocked",
40+owner: "environment",
41+productImpact: "P4",
42+},
43+);
44+});
45+46+it("honors log cursors while preserving absolute line numbers", () => {
47+const prefix = "safe line\n";
48+const findings = scanGatewayLogSentinels(`${prefix}codex app-server attempt timed out`, {
49+since: prefix.length,
50+});
51+52+expect(findings).toHaveLength(1);
53+expect(findings[0]).toMatchObject({
54+kind: "codex-app-server-timeout",
55+line: 2,
56+});
57+});
58+59+it("throws actionable summaries unless only environment blockers are allowed", () => {
60+expect(() => assertNoGatewayLogSentinels("codex_app_server progress stalled")).toThrow(
61+"stalled-agent-run",
62+);
63+expect(() =>
64+assertNoGatewayLogSentinels("OpenAI quota exceeded", { allowEnvironmentBlocked: true }),
65+).not.toThrow();
66+expect(formatGatewayLogSentinelSummary(scanGatewayLogSentinels("OpenAI quota exceeded"))).toBe(
67+"live-quota-or-subscription@1 environment-blocked owner=environment: OpenAI quota exceeded",
68+);
69+});
70+71+it("detects direct reply self-message transcripts separately from gateway logs", () => {
72+const findings = scanDirectReplyTranscriptSentinels(
73+[
74+JSON.stringify({
75+message: {
76+role: "assistant",
77+content: [
78+{
79+type: "tool_use",
80+name: "message",
81+input: { action: "send", conversationId: "qa-operator", text: "hello" },
82+},
83+],
84+},
85+}),
86+JSON.stringify({ message: { role: "assistant", content: "Sent." } }),
87+].join("\n"),
88+);
89+90+expect(findings).toHaveLength(1);
91+expect(findings[0]).toMatchObject({
92+kind: "direct-reply-self-message",
93+verdict: "product-bug",
94+owner: "openclaw-routing",
95+});
96+});
97+98+it("detects OpenAI function_call-shaped direct reply transcripts", () => {
99+const findings = scanDirectReplyTranscriptSentinels(
100+[
101+JSON.stringify({
102+message: {
103+role: "assistant",
104+content: [
105+{
106+type: "function_call",
107+name: "message",
108+arguments: JSON.stringify({
109+action: "send",
110+target: "current",
111+text: "hello",
112+}),
113+},
114+],
115+},
116+}),
117+JSON.stringify({ message: { role: "assistant", content: "Sent." } }),
118+].join("\n"),
119+);
120+121+expect(findings.map((finding) => finding.kind)).toEqual(["direct-reply-self-message"]);
122+});
123+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。