feat(agent): add controlled task execution
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
import { afterEach, describe, expect, it } from "bun:test";
|
||||
|
||||
let bridgeServer: ReturnType<typeof Bun.serve> | undefined;
|
||||
|
||||
afterEach(() => {
|
||||
bridgeServer?.stop(true);
|
||||
bridgeServer = undefined;
|
||||
});
|
||||
|
||||
describe("OpenCode internal bridge tools", () => {
|
||||
it("uses the documented AGENT_INTERNAL_TOKEN from an external process", async () => {
|
||||
let receivedToken: string | null = null;
|
||||
let receivedSessionId: string | null = null;
|
||||
bridgeServer = Bun.serve({
|
||||
hostname: "127.0.0.1",
|
||||
port: 0,
|
||||
fetch(request) {
|
||||
const url = new URL(request.url);
|
||||
receivedToken = request.headers.get("x-agent-internal-token");
|
||||
receivedSessionId = url.searchParams.get("session_id");
|
||||
return Response.json({ task: { task_id: "task-1" } });
|
||||
},
|
||||
});
|
||||
|
||||
const child = Bun.spawn([
|
||||
processExecPath(),
|
||||
"-e",
|
||||
[
|
||||
'import tool from "./.opencode/tools/get_operational_task_context.ts";',
|
||||
'const result = await tool.execute({}, { sessionID: "runtime-session-1" });',
|
||||
"process.stdout.write(result);",
|
||||
].join("\n"),
|
||||
], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
AGENT_INTERNAL_TOKEN: "external-shared-token",
|
||||
TJWATER_AGENT_INTERNAL_BASE_URL: `http://127.0.0.1:${bridgeServer.port}`,
|
||||
TJWATER_AGENT_INTERNAL_TOKEN: "",
|
||||
},
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
const [exitCode, stdout, stderr] = await Promise.all([
|
||||
child.exited,
|
||||
new Response(child.stdout).text(),
|
||||
new Response(child.stderr).text(),
|
||||
]);
|
||||
|
||||
expect({ exitCode, stderr }).toEqual({ exitCode: 0, stderr: "" });
|
||||
expect(JSON.parse(stdout)).toEqual({ task: { task_id: "task-1" } });
|
||||
expect(String(receivedToken)).toBe("external-shared-token");
|
||||
expect(String(receivedSessionId)).toBe("runtime-session-1");
|
||||
});
|
||||
|
||||
it("fails explicitly before making a bridge request when no shared token exists", async () => {
|
||||
const child = Bun.spawn([
|
||||
processExecPath(),
|
||||
"-e",
|
||||
[
|
||||
'import tool from "./.opencode/tools/get_operational_task_context.ts";',
|
||||
'await tool.execute({}, { sessionID: "runtime-session-1" });',
|
||||
].join("\n"),
|
||||
], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
AGENT_INTERNAL_TOKEN: "",
|
||||
TJWATER_AGENT_INTERNAL_TOKEN: "",
|
||||
},
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
const [exitCode, stderr] = await Promise.all([
|
||||
child.exited,
|
||||
new Response(child.stderr).text(),
|
||||
]);
|
||||
|
||||
expect(exitCode).not.toBe(0);
|
||||
expect(stderr).toContain("internal Agent tool bridge is not configured");
|
||||
});
|
||||
|
||||
it("publishes execution plans through the authenticated task artifact bridge", async () => {
|
||||
let receivedBody: Record<string, unknown> | undefined;
|
||||
bridgeServer = Bun.serve({
|
||||
hostname: "127.0.0.1",
|
||||
port: 0,
|
||||
async fetch(request) {
|
||||
receivedBody = await request.json() as Record<string, unknown>;
|
||||
return Response.json({ plan_revision: 1, report_revision: null });
|
||||
},
|
||||
});
|
||||
|
||||
const child = Bun.spawn([
|
||||
processExecPath(),
|
||||
"-e",
|
||||
[
|
||||
'import tool from "./.opencode/tools/publish_execution_plan.ts";',
|
||||
'const result = await tool.execute({ request_id: "60000000-0000-4000-8000-000000000001", title: "调查", reason: "开始", nodes: [{ id: "read", title: "读取", status: "completed", owner: "agent", detail: "完成", evidence: ["evidence-1"], action_refs: [] }], edges: [] }, { sessionID: "runtime-session-1" });',
|
||||
"process.stdout.write(result);",
|
||||
].join("\n"),
|
||||
], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
AGENT_INTERNAL_TOKEN: "external-shared-token",
|
||||
TJWATER_AGENT_INTERNAL_BASE_URL: `http://127.0.0.1:${bridgeServer.port}`,
|
||||
TJWATER_AGENT_INTERNAL_TOKEN: "",
|
||||
},
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
const [exitCode, stdout, stderr] = await Promise.all([
|
||||
child.exited,
|
||||
new Response(child.stdout).text(),
|
||||
new Response(child.stderr).text(),
|
||||
]);
|
||||
|
||||
expect({ exitCode, stderr }).toEqual({ exitCode: 0, stderr: "" });
|
||||
expect(JSON.parse(stdout)).toEqual({ plan_revision: 1, report_revision: null });
|
||||
expect(receivedBody).toMatchObject({
|
||||
session_id: "runtime-session-1",
|
||||
request_id: "60000000-0000-4000-8000-000000000001",
|
||||
execution_plan: { title: "调查", reason: "开始" },
|
||||
});
|
||||
});
|
||||
|
||||
it("fails the execution plan tool when the task artifact bridge rejects it", async () => {
|
||||
let requestCount = 0;
|
||||
bridgeServer = Bun.serve({
|
||||
hostname: "127.0.0.1",
|
||||
port: 0,
|
||||
fetch() {
|
||||
requestCount += 1;
|
||||
return Response.json({ message: "A running or completed node has incomplete predecessors" }, { status: 409 });
|
||||
},
|
||||
});
|
||||
|
||||
const child = Bun.spawn([
|
||||
processExecPath(),
|
||||
"-e",
|
||||
[
|
||||
'import tool from "./.opencode/tools/publish_execution_plan.ts";',
|
||||
'await tool.execute({ request_id: "60000000-0000-4000-8000-000000000001", title: "调查", reason: "开始", nodes: [{ id: "read", title: "读取", status: "completed", owner: "agent", detail: "完成", evidence: ["evidence-1"], action_refs: [] }], edges: [] }, { sessionID: "runtime-session-1" });',
|
||||
].join("\n"),
|
||||
], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
AGENT_INTERNAL_TOKEN: "external-shared-token",
|
||||
TJWATER_AGENT_INTERNAL_BASE_URL: `http://127.0.0.1:${bridgeServer.port}`,
|
||||
TJWATER_AGENT_INTERNAL_TOKEN: "",
|
||||
},
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
const [exitCode, stdout, stderr] = await Promise.all([
|
||||
child.exited,
|
||||
new Response(child.stdout).text(),
|
||||
new Response(child.stderr).text(),
|
||||
]);
|
||||
|
||||
expect(exitCode).not.toBe(0);
|
||||
expect(stdout).toBe("");
|
||||
expect(stderr).toContain("task plan publish failed (409)");
|
||||
expect(requestCount).toBe(1);
|
||||
});
|
||||
|
||||
it("rejects an invalid work-order action payload before a bridge request", async () => {
|
||||
let requestCount = 0;
|
||||
bridgeServer = Bun.serve({
|
||||
hostname: "127.0.0.1",
|
||||
port: 0,
|
||||
fetch() {
|
||||
requestCount += 1;
|
||||
return Response.json({ action_id: "70000000-0000-4000-8000-000000000001" });
|
||||
},
|
||||
});
|
||||
|
||||
const child = Bun.spawn([
|
||||
processExecPath(),
|
||||
"-e",
|
||||
[
|
||||
'import tool from "./.opencode/tools/propose_task_action.ts";',
|
||||
'await tool.execute({ request_id: "60000000-0000-4000-8000-000000000001", kind: "create_work_order", reason: "现场采样", payload: { title: "采样", summary: "错误字段", priority: "high" }, evidence_refs: [] }, { sessionID: "runtime-session-1" });',
|
||||
].join("\n"),
|
||||
], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
AGENT_INTERNAL_TOKEN: "external-shared-token",
|
||||
TJWATER_AGENT_INTERNAL_BASE_URL: `http://127.0.0.1:${bridgeServer.port}`,
|
||||
TJWATER_AGENT_INTERNAL_TOKEN: "",
|
||||
},
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
const [exitCode, stdout] = await Promise.all([
|
||||
child.exited,
|
||||
new Response(child.stdout).text(),
|
||||
new Response(child.stderr).text(),
|
||||
]);
|
||||
|
||||
expect(exitCode).not.toBe(0);
|
||||
expect(stdout).toBe("");
|
||||
expect(requestCount).toBe(0);
|
||||
});
|
||||
|
||||
it("sends a valid work-order proposal using the exact Server payload fields", async () => {
|
||||
let receivedBody: Record<string, unknown> | undefined;
|
||||
bridgeServer = Bun.serve({
|
||||
hostname: "127.0.0.1",
|
||||
port: 0,
|
||||
async fetch(request) {
|
||||
receivedBody = await request.json() as Record<string, unknown>;
|
||||
return Response.json({
|
||||
action_id: "70000000-0000-4000-8000-000000000001",
|
||||
approval_status: "pending",
|
||||
execution_status: "not_submitted",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const child = Bun.spawn([
|
||||
processExecPath(),
|
||||
"-e",
|
||||
[
|
||||
'import tool from "./.opencode/tools/propose_task_action.ts";',
|
||||
'const result = await tool.execute({ request_id: "60000000-0000-4000-8000-000000000001", kind: "create_work_order", reason: "现场采样", payload: { title: "采样", description: "采集二次供水水样", work_order_type: "water_quality_sampling", priority: "urgent", location: "联调演示小区" }, evidence_refs: ["evidence-1"], node_id: "field_sampling" }, { sessionID: "runtime-session-1" });',
|
||||
"process.stdout.write(result);",
|
||||
].join("\n"),
|
||||
], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
AGENT_INTERNAL_TOKEN: "external-shared-token",
|
||||
TJWATER_AGENT_INTERNAL_BASE_URL: `http://127.0.0.1:${bridgeServer.port}`,
|
||||
TJWATER_AGENT_INTERNAL_TOKEN: "",
|
||||
},
|
||||
stderr: "pipe",
|
||||
stdout: "pipe",
|
||||
});
|
||||
const [exitCode, stdout, stderr] = await Promise.all([
|
||||
child.exited,
|
||||
new Response(child.stdout).text(),
|
||||
new Response(child.stderr).text(),
|
||||
]);
|
||||
|
||||
expect({ exitCode, stderr }).toEqual({ exitCode: 0, stderr: "" });
|
||||
expect(JSON.parse(stdout)).toMatchObject({ approval_status: "pending" });
|
||||
expect(receivedBody).toMatchObject({
|
||||
session_id: "runtime-session-1",
|
||||
action: {
|
||||
kind: "create_work_order",
|
||||
payload: {
|
||||
title: "采样",
|
||||
description: "采集二次供水水样",
|
||||
work_order_type: "water_quality_sampling",
|
||||
priority: "urgent",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const processExecPath = () => process.execPath;
|
||||
Reference in New Issue
Block a user