Files
t8y2--dbx/packages/app-tests/aiAgentStepPresentation.test.ts
2026-07-13 13:09:14 +08:00

202 lines
6.8 KiB
TypeScript
Vendored

import { strict as assert } from "node:assert";
import { test } from "vitest";
import { buildAiAgentStepItems, toolCallStepKey, upsertAgentStep, type AiAgentStepItem } from "../../apps/desktop/src/lib/ai/aiAgentStepPresentation.ts";
import type { AiAgentPlan } from "../../apps/desktop/src/lib/ai/aiAgentPlan.ts";
test("presents auto-execute agent plans as completed generation, safety, and execution steps", () => {
const plan: AiAgentPlan = {
executableSql: "SELECT count(*) FROM users",
handoffSql: "SELECT count(*) FROM users",
steps: [
{ kind: "generate_sql", status: "done", sql: "SELECT count(*) FROM users" },
{
kind: "risk_check",
status: "done",
action: "auto_execute",
environment: "non_production",
category: "read",
reasons: [],
},
{ kind: "execute_sql", status: "pending", sql: "SELECT count(*) FROM users" },
],
};
assert.deepEqual(buildAiAgentStepItems(plan), [
{ key: "generated", labelKey: "ai.agentSteps.generated", tone: "success" },
{
key: "safe",
labelKey: "ai.agentSteps.safe",
titleKey: "ai.agentStepTitles.riskCheck",
titleParams: { action: "auto_execute", category: "read", environment: "non_production", reasons: "-" },
tone: "success",
},
{ key: "autoExecute", labelKey: "ai.agentSteps.autoExecute", tone: "active" },
]);
});
test("presents blocked and confirmation plans with warning tones", () => {
const blocked: AiAgentPlan = {
handoffSql: "DROP TABLE users",
steps: [
{ kind: "generate_sql", status: "done", sql: "DROP TABLE users" },
{
kind: "risk_check",
status: "done",
action: "block",
environment: "non_production",
category: "dangerous",
reasons: [],
},
{ kind: "execute_sql", status: "skipped", reason: "blocked_by_policy" },
],
};
const confirm: AiAgentPlan = {
handoffSql: "INSERT INTO users(name) VALUES ('a')",
steps: [
{ kind: "generate_sql", status: "done", sql: "INSERT INTO users(name) VALUES ('a')" },
{
kind: "risk_check",
status: "done",
action: "confirm",
environment: "production",
category: "low_risk_write",
reasons: [],
},
{ kind: "execute_sql", status: "skipped", reason: "requires_confirmation" },
],
};
assert.deepEqual(
buildAiAgentStepItems(blocked).map((item) => [item.labelKey, item.tone, item.titleKey, item.titleParams]),
[
["ai.agentSteps.generated", "success", undefined, undefined],
["ai.agentSteps.blocked", "danger", "ai.agentStepTitles.riskCheck", { action: "block", category: "dangerous", environment: "non_production", reasons: "-" }],
["ai.agentSteps.skipped", "muted", "ai.agentStepTitles.blocked", undefined],
],
);
assert.deepEqual(
buildAiAgentStepItems(confirm).map((item) => [item.labelKey, item.tone, item.titleKey, item.titleParams]),
[
["ai.agentSteps.generated", "success", undefined, undefined],
["ai.agentSteps.needsConfirm", "warning", "ai.agentStepTitles.riskCheck", { action: "confirm", category: "low_risk_write", environment: "production", reasons: "-" }],
["ai.agentSteps.skipped", "muted", "ai.agentStepTitles.requiresConfirmation", undefined],
],
);
});
test("includes risk reasons in risk check titles", () => {
const plan: AiAgentPlan = {
handoffSql: "INSERT INTO users(name) VALUES ('a'); UPDATE users SET name='b' WHERE id=1",
steps: [
{
kind: "risk_check",
status: "done",
action: "confirm",
environment: "non_production",
category: "write",
reasons: ["multi_statement"],
},
],
};
assert.deepEqual(buildAiAgentStepItems(plan)[0], {
key: "needsConfirm",
labelKey: "ai.agentSteps.needsConfirm",
titleKey: "ai.agentStepTitles.riskCheck",
titleParams: {
action: "confirm",
category: "write",
environment: "non_production",
reasons: "multi_statement",
},
tone: "warning",
});
});
test("presents no-sql and no-intent plans as muted skipped states", () => {
const noSql: AiAgentPlan = {
steps: [
{ kind: "generate_sql", status: "skipped", reason: "no_sql" },
{ kind: "execute_sql", status: "skipped", reason: "no_sql" },
],
};
const noIntent: AiAgentPlan = {
steps: [
{ kind: "generate_sql", status: "done", sql: "SELECT count(*) FROM users" },
{ kind: "execute_sql", status: "skipped", reason: "no_execution_intent" },
],
};
assert.deepEqual(
buildAiAgentStepItems(noSql).map((item) => item.labelKey),
["ai.agentSteps.noSql", "ai.agentSteps.skipped"],
);
assert.deepEqual(
buildAiAgentStepItems(noIntent).map((item) => item.labelKey),
["ai.agentSteps.generated", "ai.agentSteps.notRequested"],
);
});
test("uses stable tool step keys only for trustworthy tool call ids", () => {
assert.equal(toolCallStepKey("call-1", 1, "tool_call_start"), "tool-call-1");
assert.equal(toolCallStepKey("call-1", 2, "tool_call_end"), "tool-call-1");
assert.equal(toolCallStepKey("", 1, "tool_call_start"), "tool-tool_call_start-1");
assert.equal(toolCallStepKey("cli-tool-call", 2, "tool_call_end"), "tool-tool_call_end-2");
});
test("upserts tool call steps and preserves start args when final result arrives", () => {
const steps: AiAgentStepItem[] = [
{ key: "context", labelKey: "ai.agentSteps.contextCompacted", tone: "active" },
{
key: "tool-call-1",
labelKey: "ai.agentSteps.callingTool",
tone: "active",
toolName: "list_tables",
toolArgs: { schema: "public" },
},
];
upsertAgentStep(steps, {
key: "tool-call-1",
labelKey: "ai.agentSteps.toolDone",
tone: "success",
toolName: "list_tables",
toolResult: "users\norders",
isError: false,
});
assert.deepEqual(steps, [
{ key: "context", labelKey: "ai.agentSteps.contextCompacted", tone: "active" },
{
key: "tool-call-1",
labelKey: "ai.agentSteps.toolDone",
tone: "success",
toolName: "list_tables",
toolArgs: { schema: "public" },
toolResult: "users\norders",
isError: false,
},
]);
});
test("upserts with splice so array identity and ordering are preserved", () => {
const steps: AiAgentStepItem[] = [
{ key: "before", labelKey: "before", tone: "muted" },
{ key: "tool-call-1", labelKey: "ai.agentSteps.callingTool", tone: "active" },
{ key: "after", labelKey: "after", tone: "muted" },
];
const sameArray = steps;
upsertAgentStep(steps, { key: "tool-call-1", labelKey: "ai.agentSteps.toolError", tone: "danger", isError: true });
assert.equal(steps, sameArray);
assert.deepEqual(
steps.map((step) => [step.key, step.labelKey, step.tone]),
[
["before", "before", "muted"],
["tool-call-1", "ai.agentSteps.toolError", "danger"],
["after", "after", "muted"],
],
);
});