



























@@ -24,14 +24,21 @@ function createTestContext(): {
2424onBlockReplyFlush: ReturnType<typeof vi.fn>;
2525onAgentEvent: ReturnType<typeof vi.fn>;
2626onExecutionPhase: ReturnType<typeof vi.fn>;
27+trace: ReturnType<typeof vi.fn>;
28+isEnabled: ReturnType<typeof vi.fn>;
2729} {
2830const onBlockReplyFlush = vi.fn();
2931const onAgentEvent = vi.fn();
3032const onExecutionPhase = vi.fn();
3133const warn = vi.fn();
34+const trace = vi.fn();
35+const isEnabled = vi.fn(() => false);
3236const ctx: ToolHandlerContext = {
3337params: {
3438runId: "run-test",
39+sessionKey: "agent:unit-session",
40+sessionId: "session-test-id",
41+agentId: "agent-test-id",
3542 onBlockReplyFlush,
3643 onAgentEvent,
3744 onExecutionPhase,
@@ -41,6 +48,8 @@ function createTestContext(): {
4148hookRunner: undefined,
4249log: {
4350debug: vi.fn(),
51+ trace,
52+ isEnabled,
4453info: vi.fn(),
4554 warn,
4655},
@@ -76,7 +85,7 @@ function createTestContext(): {
7685trimMessagingToolSent: vi.fn(),
7786};
788779-return { ctx, warn, onBlockReplyFlush, onAgentEvent, onExecutionPhase };
88+return { ctx, warn, onBlockReplyFlush, onAgentEvent, onExecutionPhase, trace, isEnabled };
8089}
81908291type CapturedAgentEvent = { stream?: string; data?: Record<string, unknown> };
@@ -153,8 +162,57 @@ function requireSingleMessagingTarget(ctx: ToolHandlerContext) {
153162}
154163155164describe("handleToolExecutionStart read path checks", () => {
165+it("emits trace-only tool start diagnostics when trace logging is enabled", async () => {
166+const { ctx, trace, isEnabled, warn } = createTestContext();
167+isEnabled.mockImplementation((level: string) => level === "trace");
168+169+const evt: ToolExecutionStartEvent = {
170+type: "tool_execution_start",
171+toolName: "write",
172+toolCallId: "tool-trace",
173+args: { path: "notes.txt" },
174+};
175+176+await handleToolExecutionStart(ctx, evt);
177+178+expect(warn).not.toHaveBeenCalled();
179+expect(trace).toHaveBeenCalledTimes(1);
180+expect(trace.mock.calls[0]?.[0]).toBe("embedded run tool start");
181+expect(trace.mock.calls[0]?.[1]).toEqual({
182+event: "embedded_tool_execution_start",
183+tags: ["tool_start", "embedded", "trace"],
184+runId: "run-test",
185+toolName: "write",
186+toolCallId: "tool-trace",
187+argsType: "object",
188+argsKeys: ["path"],
189+sessionKey: "agent:unit-session",
190+sessionId: "session-test-id",
191+agentId: "agent-test-id",
192+requiredParamsMissing: ["content"],
193+});
194+});
195+196+it("does not build trace tool start diagnostics unless trace logging is enabled", async () => {
197+const { ctx, trace, isEnabled } = createTestContext();
198+199+const evt: ToolExecutionStartEvent = {
200+type: "tool_execution_start",
201+toolName: "write",
202+toolCallId: "tool-trace-disabled",
203+args: { path: "notes.txt" },
204+};
205+206+await handleToolExecutionStart(ctx, evt);
207+208+expect(isEnabled).toHaveBeenCalledWith("trace");
209+expect(trace).not.toHaveBeenCalled();
210+});
211+156212it("does not warn when read tool uses file_path alias", async () => {
157-const { ctx, warn, onBlockReplyFlush, onExecutionPhase } = createTestContext();
213+const { ctx, warn, trace, isEnabled, onBlockReplyFlush, onExecutionPhase } =
214+createTestContext();
215+isEnabled.mockImplementation((level: string) => level === "trace");
158216159217const evt: ToolExecutionStartEvent = {
160218type: "tool_execution_start",
@@ -173,6 +231,8 @@ describe("handleToolExecutionStart read path checks", () => {
173231source: "pi-embedded",
174232});
175233expect(warn).not.toHaveBeenCalled();
234+expect(trace).toHaveBeenCalledTimes(1);
235+expect(trace.mock.calls[0]?.[1]).not.toHaveProperty("requiredParamsMissing");
176236});
177237178238it("warns when read tool has neither path nor file_path", async () => {
@@ -188,7 +248,42 @@ describe("handleToolExecutionStart read path checks", () => {
188248await handleToolExecutionStart(ctx, evt);
189249190250expect(warn).toHaveBeenCalledTimes(1);
191-expect(String(warn.mock.calls[0]?.[0] ?? "")).toContain("read tool called without path");
251+const warnMessage = String(warn.mock.calls[0]?.[0] ?? "");
252+const warnMeta = warn.mock.calls[0]?.[1] as Record<string, unknown> | undefined;
253+expect(warnMessage).toContain("read tool called without path");
254+expect(warnMeta).toBeTypeOf("object");
255+expect(warnMeta?.event).toBe("embedded_read_tool_start_warning");
256+expect(warnMeta?.tags).toEqual(["tool_start", "read", "embedded", "validation"]);
257+expect(warnMeta?.runId).toBe("run-test");
258+expect(warnMeta?.sessionKey).toBe("agent:unit-session");
259+expect(warnMeta?.sessionId).toBe("session-test-id");
260+expect(warnMeta?.agentId).toBe("agent-test-id");
261+expect(warnMeta?.toolCallId).toBe("tool-2");
262+expect(warnMeta?.argsType).toBe("object");
263+expect(warnMeta?.consoleMessage).toContain("runId=run-test");
264+expect(warnMeta?.consoleMessage).toContain("sessionKey=agent:unit-session");
265+expect(warnMeta?.consoleMessage).toContain("sessionId=session-test-id");
266+expect(warnMeta?.consoleMessage).toContain("agentId=agent-test-id");
267+expect(warnMeta?.consoleMessage).toContain("toolCallId=tool-2");
268+expect(warnMeta?.consoleMessage).toContain("argsType=object");
269+expect(warnMeta?.consoleMessage).toContain("read tool called without path");
270+expect(warnMeta).not.toHaveProperty("argsPreview");
271+});
272+273+it("bounds string args before adding read warning preview", async () => {
274+const { ctx, warn } = createTestContext();
275+276+const evt: ToolExecutionStartEvent = {
277+type: "tool_execution_start",
278+toolName: "read",
279+toolCallId: "tool-string-args",
280+args: "x".repeat(500),
281+};
282+283+await handleToolExecutionStart(ctx, evt);
284+285+const warnMeta = warn.mock.calls[0]?.[1] as Record<string, unknown> | undefined;
286+expect(warnMeta?.argsPreview).toBe(`${"x".repeat(200)}…`);
192287});
193288194289it("awaits onBlockReplyFlush before continuing tool start processing", async () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。