feat: upgrade investigation and presentation runtime
This commit is contained in:
@@ -40,12 +40,12 @@ describe("OpenCode 2 business tool registration", () => {
|
||||
rpc: { register: async (_definition: unknown, handlers: { list: () => Promise<unknown>; read_todos: (input: { session_id: string }) => Promise<unknown> }) => { rpcList = handlers.list; readTodos = handlers.read_todos; } },
|
||||
storage: { set: async (key: string, value: unknown) => storage.set(key, value), get: async (key: string) => storage.get(key) },
|
||||
} as never);
|
||||
expect(tools.get("render_scada_analysis").options).toEqual({ codemode: false, permission: "render_scada_analysis" });
|
||||
expect(tools.get("present_view").options).toEqual({ codemode: false, permission: "present_view" });
|
||||
for (const name of ["publish_task_progress", "publish_task_presentation"]) {
|
||||
expect(tools.get(name).options).toEqual({ codemode: false, permission: name });
|
||||
}
|
||||
const render = tools.get("render_scada_analysis");
|
||||
await expect(render.execute({ reason: "test", items: [{ device_id: "x", level: "wrong" }] }, { sessionID: "s", id: "call-native" })).rejects.toThrow();
|
||||
const render = tools.get("present_view");
|
||||
await expect(render.execute({ reason: "test", command: { action: "render_scada_analysis", items: [{ device_id: "x", level: "wrong" }] } }, { sessionID: "s", id: "call-native" })).rejects.toThrow();
|
||||
const todo = tools.get("todowrite");
|
||||
const todos = [{ content: "check", status: "pending" }];
|
||||
await todo.execute({ todos }, { sessionID: "s" });
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import presentView from "../../.opencode/tools/present_view.js";
|
||||
import { taskPublicationCommandSchema } from "../../.opencode/tools/task-publication-schema.js";
|
||||
import { viewCommandSchema } from "../../src/frontendAction/command.js";
|
||||
import { taskPresentationCommandSchema } from "../../src/product/task-presentation.js";
|
||||
import { frontendActionResultSchema } from "../../src/frontendAction/coordinator.js";
|
||||
|
||||
const data = { type: "FeatureCollection", features: [{ type: "Feature", geometry: { type: "Point", coordinates: [121, 31] }, properties: { value: 0.4 } }] };
|
||||
const chart = { title: "压力", chart_type: "line", x_data: ["10:00"], series: [{ name: "MP01", data: [0.4] }] };
|
||||
const commands = [
|
||||
{ action: "locate_features", layer: "pipes", feature_ids: ["P1"] },
|
||||
{ action: "zoom_to_map", center: [121, 31], zoom: 14 },
|
||||
{ action: "highlight_features", layer: "pipes", feature_ids: ["P1"], effect: "pulse" },
|
||||
{ action: "clear_highlights" },
|
||||
{ action: "open_chart", chart_id: "pressure", chart, evidence_refs: ["E1"] },
|
||||
{ action: "close_chart", chart_id: "pressure" },
|
||||
{ action: "set_overlay", overlay_id: "points", title: "证据", mode: "geometry", evidence_id: "E1", data },
|
||||
{ action: "clear_overlay", overlay_id: "points" },
|
||||
{ action: "render_scada_analysis", items: [{ device_id: "MP01", level: "high" }] },
|
||||
{ action: "clear_scada_analysis" },
|
||||
];
|
||||
describe("unified view commands", () => {
|
||||
it("uses identical native and host command definitions for every action", () => {
|
||||
for (const command of commands) {
|
||||
expect(presentView.args.command.parse(command)).toEqual(viewCommandSchema.parse(command));
|
||||
expect(taskPublicationCommandSchema.parse(command)).toEqual(taskPresentationCommandSchema.parse(command));
|
||||
}
|
||||
});
|
||||
it("requires resolved chat overlays and evidence-backed task charts", () => {
|
||||
const { data: _data, ...unresolved } = commands[6];
|
||||
expect(viewCommandSchema.safeParse(unresolved).success).toBeFalse();
|
||||
expect(taskPresentationCommandSchema.safeParse(unresolved).success).toBeTrue();
|
||||
expect(viewCommandSchema.safeParse({ ...commands[4], evidence_refs: [] }).success).toBeTrue();
|
||||
expect(taskPresentationCommandSchema.safeParse({ ...commands[4], evidence_refs: [] }).success).toBeFalse();
|
||||
});
|
||||
it("rejects old envelopes, unknown fields, scripts, nonfinite numbers and old receipts", () => {
|
||||
for (const command of [
|
||||
{ name: "locate_features", params: { layer: "pipes", feature_ids: ["P1"] } },
|
||||
{ action: "show_chart", ...chart },
|
||||
{ ...commands[1], zoom: Infinity },
|
||||
{ ...commands[6], script: "alert(1)" },
|
||||
]) expect(viewCommandSchema.safeParse(command).success).toBeFalse();
|
||||
expect(frontendActionResultSchema.safeParse({ version: "frontend-action-result@1", actionId: "a", status: "succeeded", completedAt: Date.now() }).success).toBeFalse();
|
||||
});
|
||||
});
|
||||
@@ -24,8 +24,7 @@ const start = (
|
||||
const pending = coordinator.request({
|
||||
sessionId: "session-1",
|
||||
toolCallId: "call-1",
|
||||
name,
|
||||
params,
|
||||
command: { action: name, ...(params as object) },
|
||||
});
|
||||
if (!emitted) throw new Error("request was not emitted");
|
||||
return { emitted, pending };
|
||||
@@ -33,7 +32,7 @@ const start = (
|
||||
|
||||
describe("FrontendActionCoordinator", () => {
|
||||
it("registers the finite view actions with 15 second expiry", () => {
|
||||
expect(frontendActionRegistry.map((action) => action.id).sort()).toEqual(["clear_scada_analysis", "locate_features", "render_scada_analysis", "show_chart", "view_history", "zoom_to_map"]);
|
||||
expect(frontendActionRegistry.map((action) => action.id).sort()).toEqual(["clear_highlights", "clear_overlay", "clear_scada_analysis", "close_chart", "highlight_features", "locate_features", "open_chart", "render_scada_analysis", "set_overlay", "zoom_to_map"]);
|
||||
expect(frontendActionRegistry.every((action) => action.risk === "view" && action.timeoutMs === 15_000)).toBeTrue();
|
||||
});
|
||||
|
||||
@@ -41,14 +40,13 @@ describe("FrontendActionCoordinator", () => {
|
||||
const coordinator = new FrontendActionCoordinator();
|
||||
const { emitted, pending } = start(coordinator);
|
||||
expect(emitted).toMatchObject({
|
||||
name: "render_scada_analysis",
|
||||
command: { action: "render_scada_analysis", items },
|
||||
risk: "view",
|
||||
replayPolicy: "never",
|
||||
params: { items },
|
||||
});
|
||||
expect(emitted.expiresAt - emitted.issuedAt).toBe(15_000);
|
||||
const result = {
|
||||
version: "frontend-action-result@1" as const,
|
||||
version: "frontend-action-result@2" as const,
|
||||
actionId: emitted.actionId,
|
||||
status: "succeeded" as const,
|
||||
output: {
|
||||
@@ -71,14 +69,13 @@ describe("FrontendActionCoordinator", () => {
|
||||
const pending = coordinator.request({
|
||||
sessionId: "session-1",
|
||||
toolCallId: "call-no-replay",
|
||||
name: "clear_scada_analysis",
|
||||
params: {},
|
||||
command: { action: "clear_scada_analysis" },
|
||||
});
|
||||
unregister();
|
||||
coordinator.registerSink("session-1", (request) => emitted.push(request));
|
||||
expect(emitted).toHaveLength(1);
|
||||
coordinator.submitResult("session-1", {
|
||||
version: "frontend-action-result@1",
|
||||
version: "frontend-action-result@2",
|
||||
actionId: emitted[0].actionId,
|
||||
status: "succeeded",
|
||||
output: { cleared: true },
|
||||
@@ -91,14 +88,14 @@ describe("FrontendActionCoordinator", () => {
|
||||
const coordinator = new FrontendActionCoordinator();
|
||||
const { emitted, pending } = start(coordinator, "clear_scada_analysis", {});
|
||||
expect(() => coordinator.submitResult("session-1", {
|
||||
version: "frontend-action-result@1",
|
||||
version: "frontend-action-result@2",
|
||||
actionId: emitted.actionId,
|
||||
status: "succeeded",
|
||||
output: { cleared: false },
|
||||
completedAt: Date.now(),
|
||||
})).toThrow("invalid frontend action output");
|
||||
coordinator.submitResult("session-1", {
|
||||
version: "frontend-action-result@1",
|
||||
version: "frontend-action-result@2",
|
||||
actionId: emitted.actionId,
|
||||
status: "succeeded",
|
||||
output: { cleared: true },
|
||||
@@ -113,21 +110,20 @@ describe("FrontendActionCoordinator", () => {
|
||||
const request = (value: unknown) => coordinator.request({
|
||||
sessionId: "s",
|
||||
toolCallId: "call-validation",
|
||||
name: "render_scada_analysis",
|
||||
params: value,
|
||||
command: { action: "render_scada_analysis", ...(value as object) },
|
||||
});
|
||||
expect(() => request({ items: [] })).toThrow(FrontendActionError);
|
||||
expect(() => request({ items: Array.from({ length: 101 }, (_, index) => ({ device_id: `MP${index}`, level: "low" })) })).toThrow("invalid frontend action params");
|
||||
expect(() => request({ items: [{ device_id: "MP01", level: "high" }, { device_id: "MP01", level: "low" }] })).toThrow("invalid frontend action params");
|
||||
expect(() => request({ items: [{ device_id: "x".repeat(129), level: "low" }] })).toThrow("invalid frontend action params");
|
||||
expect(() => request({ items: [{ device_id: "MP01", level: "critical" }] })).toThrow("invalid frontend action params");
|
||||
expect(() => request({ items: Array.from({ length: 101 }, (_, index) => ({ device_id: `MP${index}`, level: "low" })) })).toThrow("invalid view command");
|
||||
expect(() => request({ items: [{ device_id: "MP01", level: "high" }, { device_id: "MP01", level: "low" }] })).toThrow("invalid view command");
|
||||
expect(() => request({ items: [{ device_id: "x".repeat(129), level: "low" }] })).toThrow("invalid view command");
|
||||
expect(() => request({ items: [{ device_id: "MP01", level: "critical" }] })).toThrow("invalid view command");
|
||||
});
|
||||
|
||||
it("keeps terminal results idempotent and rejects conflicts", async () => {
|
||||
const coordinator = new FrontendActionCoordinator();
|
||||
const { emitted, pending } = start(coordinator, "clear_scada_analysis", {});
|
||||
const result = {
|
||||
version: "frontend-action-result@1" as const,
|
||||
version: "frontend-action-result@2" as const,
|
||||
actionId: emitted.actionId,
|
||||
status: "succeeded" as const,
|
||||
output: { cleared: true as const },
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
|
||||
import { tool } from "../../.opencode/tool.js";
|
||||
import presentView from "../../.opencode/tools/present_view.js";
|
||||
import { unwrapFrontendActionResult } from "../../.opencode/tools/frontend_action.js";
|
||||
|
||||
describe("frontend action tool bridge", () => {
|
||||
it("accepts a presentation without a reason and trims optional explanations", () => {
|
||||
const schema = tool.schema.object(presentView.args);
|
||||
const command = { action: "clear_highlights" };
|
||||
expect(schema.parse({ command })).toEqual({ command });
|
||||
expect(schema.parse({ command, reason: " 清理上轮定位 " }).reason).toBe("清理上轮定位");
|
||||
expect(schema.safeParse({ command, reason: " " }).success).toBeFalse();
|
||||
});
|
||||
|
||||
it("returns only validated browser output for successful actions", () => {
|
||||
expect(unwrapFrontendActionResult(JSON.stringify({
|
||||
version: "frontend-action-result@1",
|
||||
version: "frontend-action-result@2",
|
||||
actionId: "action-1",
|
||||
status: "succeeded",
|
||||
output: { cleared: true },
|
||||
@@ -15,14 +25,14 @@ describe("frontend action tool bridge", () => {
|
||||
|
||||
it("throws the browser error for failed and expired actions", () => {
|
||||
expect(() => unwrapFrontendActionResult(JSON.stringify({
|
||||
version: "frontend-action-result@1",
|
||||
version: "frontend-action-result@2",
|
||||
actionId: "action-1",
|
||||
status: "failed",
|
||||
error: { code: "WFS_UNAVAILABLE", message: "SCADA geometry query failed" },
|
||||
completedAt: Date.now(),
|
||||
}))).toThrow("WFS_UNAVAILABLE: SCADA geometry query failed");
|
||||
expect(() => unwrapFrontendActionResult(JSON.stringify({
|
||||
version: "frontend-action-result@1",
|
||||
version: "frontend-action-result@2",
|
||||
actionId: "action-2",
|
||||
status: "expired",
|
||||
error: { code: "ACTION_TIMEOUT", message: "browser action timed out" },
|
||||
|
||||
Reference in New Issue
Block a user