Files
software-mansion__argent/packages/tool-server/test/interaction-messages.test.ts
Stanisław Chmiela 7189333ac4 feat(tool-server): add human-readable log message (#582)
## Summary

Adds tool-owned, human-readable interaction messages for every tool
definition.

Each tool defines its own started, completed, and failed wording (as
implemented in https://github.com/software-mansion/argent/pull/367.
Messages can use validated parameters, successful results, and safe
failure codes. They are to avoid sensitive input such as typed text,
cookie values, JavaScript expressions, and URL credentials.

Bridges the gap between current Argent

<img width="954" height="493" alt="Screenshot%202026-07-28%20at%2015 48
37"
src="https://github.com/user-attachments/assets/2c689a93-8ba8-4208-b35e-66eb53c96671"
/>

and current agent-device

<img width="1188" height="476" alt="Zrzut ekranu 2026-07-28 o 20 14 42"
src="https://github.com/user-attachments/assets/6aed0f38-625b-4748-83e5-d384e79266c2"
/>


## Test

Selected fields from the generated JSONL:

```jsonl
{"level":30,"type":"tool.invoked","toolId":"list-devices","toolInvocationId":"decb864f-51c1-41ee-91fc-15bc2494e268","msg":"Listing devices","time":"2026-07-29T10:37:19.896Z"}
{"level":30,"type":"tool.completed","toolId":"list-devices","toolInvocationId":"decb864f-51c1-41ee-91fc-15bc2494e268","durationMs":96.77333299999918,"msg":"Listed 22 devices and 0 AVDs","time":"2026-07-29T10:37:19.993Z"}
{"level":30,"type":"tool.invoked","toolId":"screenshot","toolInvocationId":"8504abb8-83ed-4490-8210-3355d6c638c9","msg":"Capturing screenshot","time":"2026-07-29T10:37:24.503Z"}
{"level":50,"type":"tool.failed","toolId":"screenshot","toolInvocationId":"8504abb8-83ed-4490-8210-3355d6c638c9","failureSignal":{"error_code":"SIMULATOR_SERVER_READY_EXITED","failure_stage":"simulator_server_spawn_ready","failure_area":"tool_server","error_kind":"subprocess","failure_command":"simulator_server","failure_exit_code":1},"durationMs":750.4639999999999,"msg":"Failed to capture screenshot: SIMULATOR_SERVER_READY_EXITED","time":"2026-07-29T10:37:25.254Z"}
{"level":30,"type":"tool.invoked","toolId":"screenshot","toolInvocationId":"63b938fc-3d86-4eb5-9972-a008474ffb01","msg":"Capturing screenshot","time":"2026-07-29T10:38:01.107Z"}
{"level":30,"type":"tool.completed","toolId":"screenshot","toolInvocationId":"63b938fc-3d86-4eb5-9972-a008474ffb01","durationMs":25.509208000003127,"msg":"Captured screenshot argent-screenshot-chromium-cdp-9222-1785321481132-81477.png","time":"2026-07-29T10:38:01.133Z"}
```
2026-07-29 13:07:49 +02:00

106 lines
4.0 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
FAILURE_CODES,
type FailureSignal,
type Registry,
type ToolDefinition,
} from "@argent/registry";
import { createRegistry } from "../src/utils/setup-registry";
import { pasteTool } from "../src/tools/paste";
import { simulatorServerTool } from "../src/tools/simulator/simulator-server";
import { createProposeVariantTool } from "../src/tools/variants/propose-variant";
import { awaitUserSelectionTool } from "../src/tools/variants/await-user-selection";
const failureSignal: FailureSignal = {
error_code: FAILURE_CODES.ARGENT_UNCLASSIFIED_FAILURE,
failure_stage: "interaction_message_test",
failure_area: "tool_server",
error_kind: "unknown",
};
function definitionsById(registry: Registry): Map<string, ToolDefinition<any, any>> {
const definitions = new Map<string, ToolDefinition<any, any>>();
for (const id of registry.getSnapshot().tools) {
definitions.set(id, registry.getTool(id)!);
}
// Lens tools are only registered on macOS; add them explicitly so this test
// covers the same catalog on every CI platform.
definitions.set("propose_variant", createProposeVariantTool(registry));
definitions.set("await_user_selection", awaitUserSelectionTool);
// These definitions intentionally exist outside createRegistry.
definitions.set("paste", pasteTool);
definitions.set("simulator-server", simulatorServerTool);
return definitions;
}
describe("tool interaction messages", () => {
it("defines all three formatters for every tool", () => {
const definitions = definitionsById(createRegistry());
expect(definitions.size).toBe(77);
for (const [id, definition] of definitions) {
expect(definition.interaction?.startedMsg, `${id}.startedMsg`).toBeTypeOf("function");
expect(definition.interaction?.completedMsg, `${id}.completedMsg`).toBeTypeOf("function");
expect(definition.interaction?.failedMsg, `${id}.failedMsg`).toBeTypeOf("function");
}
});
it("formats messages from parameters, results, and failure signals", () => {
const definitions = definitionsById(createRegistry());
expect(
definitions.get("gesture-tap")!.interaction!.completedMsg!({
params: { udid: "device-1", x: 0.5, y: 0.25, clickCount: 2 },
result: {},
})
).toBe("Double-tapped at (50%, 25%)");
expect(
definitions.get("screenshot")!.interaction!.completedMsg!({
params: { udid: "device-1" },
result: { image: { filename: "screenshot.png" } },
})
).toBe("Captured screenshot screenshot.png");
expect(
definitions.get("gesture-tap")!.interaction!.failedMsg!({
params: { udid: "device-1", x: 0.5, y: 0.25 },
error: new Error("raw error"),
failureSignal,
})
).toBe(`Failed to tap at (50%, 25%): ${failureSignal.error_code}`);
});
it("does not expose sensitive inputs", () => {
const definitions = definitionsById(createRegistry());
const secret = "INTERACTION_MESSAGE_SECRET";
const messages = [
definitions.get("keyboard")!.interaction!.startedMsg!({
params: { udid: "device-1", text: secret, key: secret },
}),
definitions.get("open-url")!.interaction!.startedMsg!({
params: {
udid: "device-1",
url: `https://user:${secret}@example.com/path?token=${secret}#fragment`,
},
}),
definitions.get("debugger-evaluate")!.interaction!.startedMsg!({
params: { device_id: "device-1", port: 8081, expression: secret },
}),
definitions.get("chromium-cookies")!.interaction!.completedMsg!({
params: { udid: "chromium-1", action: "set", name: "session", value: secret },
result: { set: true },
}),
definitions.get("flow-add-echo")!.interaction!.completedMsg!({
params: { message: secret },
result: { message: secret, flowFile: "/tmp/flow.yaml", savedTo: "project" },
}),
];
expect(messages.join("\n")).not.toContain(secret);
expect(messages).toContain("Opening example.com");
});
});