## Summary Full implementation of the DeepSeek Harness (dsh) connector plan (tasks T1–T8): - **T1**: 5-value `AgentSource` expansion across Rust, Swift, and schemas with SQLite schema v7 migration - **T2**: `parse_dsh` event normalization (15 mappings, 6 turn/end kinds, reasoning `block-start` thinking boundary, fail-open unknown-kind handling) - **T3**: dsh connector management lifecycle (check, repair, refresh, uninstall targeting the web profile's `cordis.patch.yml`) - **T4**: Official dsh Cordis plugin template with single-flight queue, broadcast-only subscriptions, child session suppression, and background fence - **T5**: App-side `RuntimeReleaseManifest.swift` expansion - **T6**: End-to-end integration tests in `integration_d` covering all 9 contract fixtures - **T7**: Durable contract and architecture documentation in `docs/integrations/agent-connectors.md` and `docs/architecture/runtime-and-ipc.md` - **T8**: Product scope expansion to five Agents across `AGENTS.md`, `README.md`, `README.zh-CN.md`, and architecture documents 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: gd-ops <gd-ops@xmfunny.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1461 lines
55 KiB
JavaScript
1461 lines
55 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { EventEmitter } from "node:events";
|
|
import { readFile } from "node:fs/promises";
|
|
import { createRequire, syncBuiltinESMExports } from "node:module";
|
|
import test from "node:test";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const childProcess = require("node:child_process");
|
|
|
|
async function importTemplate(relativePath, cacheKey = "default", testExports = []) {
|
|
let source = (await readFile(new URL(relativePath, import.meta.url), "utf8"))
|
|
.replace("__APC_CLI_JSON__", JSON.stringify("/unused/petcore-cli"));
|
|
if (testExports.length > 0) {
|
|
source += `\nexport { ${testExports.join(", ")} };\n`;
|
|
}
|
|
const encoded = Buffer.from(source).toString("base64");
|
|
return import(`data:text/javascript;base64,${encoded}#${cacheKey}`);
|
|
}
|
|
|
|
test("Pi, OpenCode, and dsh expose bounded local activity without leaking host-private context", async () => {
|
|
const captured = [];
|
|
const originalSpawn = childProcess.spawn;
|
|
const originalDiagnostic = process.env.APC_CONNECTOR_DIAGNOSTIC;
|
|
const originalProbe = process.env.APC_CONNECTOR_PROBE;
|
|
const originalProbeID = process.env.APC_CONNECTOR_PROBE_ID;
|
|
let spawnCloseDelayMs = 0;
|
|
|
|
childProcess.spawn = (_command, args, options = {}) => {
|
|
const child = new EventEmitter();
|
|
const stdin = new EventEmitter();
|
|
let closeTimer;
|
|
stdin.end = (data) => {
|
|
const sourceIndex = args.indexOf("--source");
|
|
captured.push({
|
|
source: sourceIndex >= 0 ? args[sourceIndex + 1] : undefined,
|
|
payload: JSON.parse(String(data)),
|
|
});
|
|
if (spawnCloseDelayMs > 0) {
|
|
closeTimer = setTimeout(() => child.emit("close", 0), spawnCloseDelayMs);
|
|
} else {
|
|
queueMicrotask(() => child.emit("close", 0));
|
|
}
|
|
};
|
|
options.signal?.addEventListener("abort", () => {
|
|
clearTimeout(closeTimer);
|
|
queueMicrotask(() => child.emit("error", new Error("aborted")));
|
|
setTimeout(() => child.emit("close", 1), 5);
|
|
}, { once: true });
|
|
child.stdin = stdin;
|
|
return child;
|
|
};
|
|
syncBuiltinESMExports();
|
|
process.env.APC_CONNECTOR_DIAGNOSTIC = "1";
|
|
delete process.env.APC_CONNECTOR_PROBE;
|
|
|
|
try {
|
|
const pi = await importTemplate("./pi/agent-pet-companion.ts.tpl");
|
|
assert.equal(pi.APC_PI_CONTRACT_VERSION, "pi-extension-0.80.10-events-v15");
|
|
assert.equal(pi.APC_PI_EVENT_INVENTORY.length, 33);
|
|
const piActivity = await importTemplate(
|
|
"./pi/agent-pet-companion.ts.tpl",
|
|
"semantic-activity",
|
|
["activityText"],
|
|
);
|
|
const encodedAndDumpActivityCases = [
|
|
{
|
|
name: "encoded safe object",
|
|
value: JSON.stringify({ path: "README.md", headers: { Authorization: "Bearer private" } }),
|
|
expected: "README.md",
|
|
},
|
|
{
|
|
name: "encoded safe array",
|
|
value: JSON.stringify([{ file_path: "Sources/App.swift" }]),
|
|
expected: "Sources/App.swift",
|
|
},
|
|
{
|
|
name: "double encoded safe object",
|
|
value: JSON.stringify(JSON.stringify({ path: "README.md" })),
|
|
expected: "README.md",
|
|
},
|
|
{
|
|
name: "encoded credential object",
|
|
value: JSON.stringify({ headers: { message: "secret-header" } }),
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "encoded credential array",
|
|
value: JSON.stringify([{ credentials: { output: "secret-credential" } }]),
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "double encoded credentials",
|
|
value: JSON.stringify(JSON.stringify({ client_secret: { content: "secret-client" } })),
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "triple encoded credentials",
|
|
value: JSON.stringify(JSON.stringify(JSON.stringify({
|
|
client_secret: { content: "secret-client-triple" },
|
|
}))),
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "oversized encoded object",
|
|
value: JSON.stringify({ headers: { message: "x".repeat(32 * 1024) } }),
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "oversized double encoded object",
|
|
value: JSON.stringify(JSON.stringify({
|
|
credentials: { output: "x".repeat(32 * 1024) },
|
|
})),
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "authorization header dump",
|
|
value: "Authorization: Bearer private",
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "environment dump",
|
|
value: "API_KEY=private\nPATH=/usr/bin",
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "truncated object",
|
|
value: " {\"path\":\"README.md\"",
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "truncated array",
|
|
value: " \n [{\"path\":\"README.md\"}",
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "malformed quoted encoded object",
|
|
value: " \"{\\\"headers\\\":{\\\"message\\\":\\\"secret\\\"}",
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "double encoded truncated object",
|
|
value: JSON.stringify("{\"headers\":{\"message\":\"secret\"}"),
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "ordinary prose with braces",
|
|
value: "Planning includes {draft} markers",
|
|
expected: "Planning includes {draft} markers",
|
|
},
|
|
{
|
|
name: "valid quoted prose",
|
|
value: JSON.stringify("Planning includes {draft} markers"),
|
|
expected: "Planning includes {draft} markers",
|
|
},
|
|
{
|
|
name: "same-line environment dump",
|
|
value: "PATH=/usr/bin API_KEY=private",
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "same-line header dump",
|
|
value: "Content-Type: text/plain Authorization: Bearer private",
|
|
expected: undefined,
|
|
},
|
|
{
|
|
name: "control-separated environment dump",
|
|
value: "PATH=/usr/bin\u0000API_KEY=private",
|
|
expected: undefined,
|
|
},
|
|
];
|
|
for (const { name, value, expected } of encodedAndDumpActivityCases) {
|
|
assert.equal(
|
|
piActivity.activityText({ type: "tool_execution_end", result: value }),
|
|
expected,
|
|
`Pi ${name}`,
|
|
);
|
|
}
|
|
assert.equal(
|
|
piActivity.activityText({
|
|
type: "tool_call",
|
|
input: { command: "TOKEN=secret-command" },
|
|
}),
|
|
"TOKEN=secret-command",
|
|
"Pi explicit command context must retain environment assignments",
|
|
);
|
|
assert.equal(
|
|
piActivity.activityText({
|
|
type: "tool_call",
|
|
input: { command: "PATH=/usr/bin API_KEY=secret-command" },
|
|
}),
|
|
"PATH=/usr/bin API_KEY=secret-command",
|
|
"Pi explicit command context must retain same-line environment assignments",
|
|
);
|
|
assert.equal(
|
|
piActivity.activityText({
|
|
type: "tool_execution_start",
|
|
args: { path: "Sources/App.swift" },
|
|
}),
|
|
"Sources/App.swift",
|
|
"Pi tool_execution_start must read the host's args field",
|
|
);
|
|
assert.equal(
|
|
piActivity.activityText({
|
|
type: "message_end",
|
|
message: {
|
|
role: "assistant",
|
|
content: [{ type: "thinking", thinking: "Inspecting the Swift view" }],
|
|
},
|
|
}),
|
|
"Inspecting the Swift view",
|
|
"Pi finalized thinking blocks must read ThinkingContent.thinking",
|
|
);
|
|
const sensitiveIdentifierTokens = [
|
|
"env", "environment", "header", "headers", "auth", "oauth", "authentication",
|
|
"authorization", "secret", "password", "passphrase", "credential", "credentials",
|
|
"cookie", "cookies", "token", "tokens", "keychain", "keystore", "pem",
|
|
];
|
|
const capitalizedIdentifier = (value) => value[0].toUpperCase() + value.slice(1);
|
|
const sensitiveIdentifierMatrix = sensitiveIdentifierTokens.flatMap((sensitive) => [
|
|
...["_", "-", ".", " "].flatMap((separator) => [
|
|
`${sensitive}${separator}value`,
|
|
`value${separator}${sensitive}`,
|
|
]),
|
|
`${sensitive}Value`,
|
|
`value${capitalizedIdentifier(sensitive)}`,
|
|
]);
|
|
const sensitiveKeyMatrix = [
|
|
"private", "api", "ssh", "signing", "encryption", "access", "client",
|
|
].flatMap((qualifier) => [
|
|
`${qualifier}_key`,
|
|
`key-${qualifier}`,
|
|
`${qualifier}Key`,
|
|
`key${capitalizedIdentifier(qualifier)}`,
|
|
]);
|
|
const credentialContainerVariants = [
|
|
{ headers: { content: "secret-header-content" } },
|
|
{ environment: { message: "secret-environment-message" } },
|
|
{ tokens: { output: "secret-token-output" } },
|
|
{ client_secret: { message: "secret-client-message" } },
|
|
{ sessionToken: { output: "secret-session-output" } },
|
|
{ privateKey: { content: "secret-private-key" } },
|
|
{ Authorization: { message: "secret-authorization" } },
|
|
{ customBearerToken: { result: "secret-bearer-token" } },
|
|
{ processEnv: { message: "secret-process-environment" } },
|
|
{ requestHeader: { content: "secret-request-header" } },
|
|
{ requestHeaders: { content: "secret-request-headers" } },
|
|
{ clientAuth: { output: "secret-client-auth" } },
|
|
{ oauth: { result: "secret-oauth" } },
|
|
{ processEnvironment: { message: "secret-process-environment-long" } },
|
|
{ clientAuthentication: { content: "secret-client-authentication" } },
|
|
{ authConfig: { output: "secret-auth-config" } },
|
|
{ token_value: { message: "secret-token-underscore" } },
|
|
{ "token-value": { output: "secret-token-hyphen" } },
|
|
{ tokenValue: { content: "secret-token-camel-case" } },
|
|
...sensitiveIdentifierMatrix.map((key) => ({
|
|
[key]: { message: `secret identifier ${key}` },
|
|
})),
|
|
...sensitiveKeyMatrix.map((key) => ({
|
|
[key]: { output: `secret key identifier ${key}` },
|
|
})),
|
|
];
|
|
for (const input of credentialContainerVariants) {
|
|
assert.equal(
|
|
piActivity.activityText({ type: "tool_call", input }),
|
|
undefined,
|
|
"Pi activity must not recurse through credential containers",
|
|
);
|
|
}
|
|
assert.equal(
|
|
piActivity.activityText({ type: "tool_call", input: { file_path: "Sources/App.swift" } }),
|
|
"Sources/App.swift",
|
|
);
|
|
assert.equal(
|
|
piActivity.activityText({
|
|
type: "tool_call",
|
|
input: { token_count: { message: "42 tokens processed" } },
|
|
}),
|
|
"42 tokens processed",
|
|
"Pi token statistics must not be mistaken for credential containers",
|
|
);
|
|
assert.equal(
|
|
piActivity.activityText({
|
|
type: "tool_call",
|
|
input: { token_counts: { message: "42 input, 17 output" } },
|
|
}),
|
|
"42 input, 17 output",
|
|
"Pi token statistics plural must remain allowlisted",
|
|
);
|
|
assert.equal(
|
|
piActivity.activityText({
|
|
type: "tool_call",
|
|
input: { authorMetadata: { message: "Ada Lovelace" } },
|
|
}),
|
|
"Ada Lovelace",
|
|
"Pi author metadata must not be mistaken for auth credentials",
|
|
);
|
|
assert.equal(
|
|
piActivity.activityText({
|
|
type: "tool_call",
|
|
input: { envelope: { message: "ordinary transport envelope" } },
|
|
}),
|
|
"ordinary transport envelope",
|
|
"Pi envelope metadata must not be mistaken for environment credentials",
|
|
);
|
|
|
|
const piHandlers = new Map();
|
|
pi.default({ on: (name, handler) => piHandlers.set(name, handler) });
|
|
assert.deepEqual(
|
|
new Set(piHandlers.keys()),
|
|
new Set(pi.APC_PI_EVENT_INVENTORY),
|
|
"every Pi 0.80.10 ExtensionAPI event must be registered",
|
|
);
|
|
assert.deepEqual(
|
|
await piHandlers.get("project_trust")(
|
|
{ type: "project_trust", cwd: "/secret/project" },
|
|
{ mode: "rpc" },
|
|
),
|
|
{ trusted: "undecided" },
|
|
);
|
|
|
|
const piContext = {
|
|
sessionManager: {
|
|
getSessionId: () => "pi-session",
|
|
getSessionName: () => "Visible Pi session",
|
|
},
|
|
model: { provider: "fixture", id: "fixture-model" },
|
|
cwd: "/secret/project",
|
|
};
|
|
await piHandlers.get("context")(
|
|
{ type: "context", messages: [{ content: "secret-context" }] },
|
|
piContext,
|
|
);
|
|
await piHandlers.get("before_provider_headers")(
|
|
{ type: "before_provider_headers", headers: { authorization: "secret-header" } },
|
|
piContext,
|
|
);
|
|
await piHandlers.get("session_info_changed")(
|
|
{
|
|
type: "session_info_changed",
|
|
name: "Generated Pi title",
|
|
privateMetadata: "secret-session-metadata",
|
|
},
|
|
piContext,
|
|
);
|
|
await piHandlers.get("message_update")(
|
|
{ type: "message_update", assistantMessageEvent: { delta: "secret-reasoning" } },
|
|
piContext,
|
|
);
|
|
await piHandlers.get("tool_execution_update")(
|
|
{ type: "tool_execution_update", partialResult: "secret-partial-output" },
|
|
piContext,
|
|
);
|
|
await piHandlers.get("input")(
|
|
{ type: "input", text: "Visible Pi prompt" },
|
|
piContext,
|
|
);
|
|
await piHandlers.get("before_agent_start")(
|
|
{
|
|
type: "before_agent_start",
|
|
prompt: "Visible Pi prompt",
|
|
systemPrompt: "secret-system-prompt",
|
|
},
|
|
piContext,
|
|
);
|
|
await piHandlers.get("tool_call")(
|
|
{
|
|
type: "tool_call",
|
|
toolName: "bash",
|
|
toolCallId: "opaque-pi-call",
|
|
input: { command: "TOKEN=secret-command" },
|
|
},
|
|
piContext,
|
|
);
|
|
await piHandlers.get("tool_execution_start")(
|
|
{
|
|
type: "tool_execution_start",
|
|
toolName: "read",
|
|
toolCallId: "opaque-pi-read",
|
|
args: { path: "Sources/App.swift" },
|
|
},
|
|
piContext,
|
|
);
|
|
await piHandlers.get("tool_execution_start")(
|
|
{
|
|
type: "tool_execution_start",
|
|
toolName: "read",
|
|
toolCallId: "opaque-pi-malformed",
|
|
},
|
|
piContext,
|
|
);
|
|
await piHandlers.get("message_end")(
|
|
{
|
|
type: "message_end",
|
|
message: {
|
|
role: "assistant",
|
|
content: [{ type: "thinking", thinking: "Inspecting the Swift view" }],
|
|
},
|
|
},
|
|
piContext,
|
|
);
|
|
await piHandlers.get("tool_execution_end")(
|
|
{
|
|
type: "tool_execution_end",
|
|
toolName: "bash",
|
|
toolCallId: "opaque-pi-call",
|
|
result: "secret-tool-output",
|
|
isError: true,
|
|
},
|
|
piContext,
|
|
);
|
|
const beforeAgentEnd = captured.length;
|
|
await piHandlers.get("agent_end")(
|
|
{
|
|
type: "agent_end",
|
|
messages: [
|
|
{ role: "toolResult", content: "secret-agent-tool-result" },
|
|
{ role: "assistant", content: [{ type: "text", text: "Visible Pi answer" }] },
|
|
],
|
|
},
|
|
piContext,
|
|
);
|
|
assert.equal(captured.length, beforeAgentEnd, "Pi agent_end must not be terminal or forwarded");
|
|
await piHandlers.get("agent_settled")({ type: "agent_settled" }, piContext);
|
|
|
|
const piPayloads = captured.filter((item) => item.source === "pi").map((item) => item.payload);
|
|
const serializedPi = JSON.stringify(piPayloads);
|
|
for (const secret of [
|
|
"secret-context",
|
|
"secret-header",
|
|
"secret-session-metadata",
|
|
"secret-reasoning",
|
|
"secret-partial-output",
|
|
"secret-system-prompt",
|
|
"secret-agent-tool-result",
|
|
"/secret/project",
|
|
]) {
|
|
assert.equal(serializedPi.includes(secret), false, `Pi leaked ${secret}`);
|
|
}
|
|
assert.ok(piPayloads.every((payload) => payload.diagnostic === true));
|
|
assert.equal(piPayloads.at(-1).type, "agent_settled");
|
|
assert.equal(piPayloads.at(-1).message_content, "Visible Pi answer");
|
|
assert.ok(piPayloads.some((payload) => (
|
|
payload.type === "tool_call"
|
|
&& payload.activity_content === "TOKEN=secret-command"
|
|
)));
|
|
assert.ok(piPayloads.some((payload) => (
|
|
payload.type === "tool_execution_end"
|
|
&& payload.activity_content === "secret-tool-output"
|
|
)));
|
|
assert.ok(piPayloads.some((payload) => (
|
|
payload.type === "tool_execution_start"
|
|
&& payload.activity_content === "Sources/App.swift"
|
|
&& payload.parse_warnings === undefined
|
|
)));
|
|
assert.ok(piPayloads.some((payload) => (
|
|
payload.type === "tool_execution_start"
|
|
&& payload.tool_call_id === "opaque-pi-malformed"
|
|
&& payload.parse_warnings?.some((warning) => (
|
|
warning.field === "tool_arguments"
|
|
&& warning.failure === "missing_required"
|
|
))
|
|
)));
|
|
assert.ok(piPayloads.some((payload) => (
|
|
payload.type === "message_end"
|
|
&& payload.activity_kind === "thinking"
|
|
&& payload.activity_content === "Inspecting the Swift view"
|
|
)));
|
|
assert.ok(piPayloads.some((payload) => (
|
|
payload.type === "session_info_changed"
|
|
&& payload.session_title === "Generated Pi title"
|
|
&& payload.message_content === undefined
|
|
)));
|
|
|
|
const piNoModelStart = captured.length;
|
|
const piNoModelContext = {
|
|
sessionManager: {
|
|
getSessionId: () => "pi-no-model-session",
|
|
getSessionName: () => "No model session",
|
|
},
|
|
model: undefined,
|
|
};
|
|
await piHandlers.get("input")(
|
|
{ type: "input", text: "Prompt without a selected model" },
|
|
piNoModelContext,
|
|
);
|
|
const piNoModelPayloads = captured
|
|
.slice(piNoModelStart)
|
|
.filter((item) => item.source === "pi")
|
|
.map((item) => item.payload);
|
|
assert.deepEqual(piNoModelPayloads.map((payload) => payload.type), ["input"]);
|
|
assert.equal(piNoModelPayloads[0].agent_error, true);
|
|
assert.equal(piNoModelPayloads[0].reason, "model_unavailable");
|
|
|
|
const piChildStart = captured.length;
|
|
const piChildContext = {
|
|
sessionManager: {
|
|
getSessionId: () => "pi-child-session",
|
|
getSessionName: () => "must-not-cross-child-title",
|
|
getHeader: () => ({ parentSession: "/private/root-session.jsonl" }),
|
|
},
|
|
};
|
|
await piHandlers.get("input")(
|
|
{ type: "input", text: "must-not-cross-child-prompt" },
|
|
piChildContext,
|
|
);
|
|
await piHandlers.get("tool_call")(
|
|
{ type: "tool_call", toolName: "bash", input: { command: "must-not-cross-child-tool" } },
|
|
piChildContext,
|
|
);
|
|
const piChildPayloads = captured
|
|
.slice(piChildStart)
|
|
.filter((item) => item.source === "pi")
|
|
.map((item) => item.payload);
|
|
assert.equal(piChildPayloads.length, 1);
|
|
assert.equal(piChildPayloads[0].type, "session.child");
|
|
assert.equal(piChildPayloads[0].session_id, "pi-child-session");
|
|
assert.equal(JSON.stringify(piChildPayloads).includes("/private/root-session.jsonl"), false);
|
|
assert.equal(JSON.stringify(piChildPayloads).includes("must-not-cross"), false);
|
|
|
|
const piReservedChildStart = captured.length;
|
|
const piReservedChildContext = {
|
|
sessionManager: {
|
|
getSessionId: () => "pi-reserved-child-session",
|
|
getSessionName: () => "subagent-reviewer-security",
|
|
getHeader: () => ({}),
|
|
},
|
|
model: { provider: "fixture", id: "fixture-model" },
|
|
};
|
|
await piHandlers.get("input")(
|
|
{ type: "input", text: "must-not-cross-reserved-child-prompt" },
|
|
piReservedChildContext,
|
|
);
|
|
await piHandlers.get("tool_call")(
|
|
{ type: "tool_call", toolName: "bash", input: { command: "must-not-cross-reserved-child-tool" } },
|
|
piReservedChildContext,
|
|
);
|
|
const piReservedChildPayloads = captured
|
|
.slice(piReservedChildStart)
|
|
.filter((item) => item.source === "pi")
|
|
.map((item) => item.payload);
|
|
assert.equal(piReservedChildPayloads.length, 1);
|
|
assert.equal(piReservedChildPayloads[0].type, "session.child");
|
|
assert.equal(piReservedChildPayloads[0].session_id, "pi-reserved-child-session");
|
|
assert.equal(JSON.stringify(piReservedChildPayloads).includes("subagent-reviewer"), false);
|
|
assert.equal(JSON.stringify(piReservedChildPayloads).includes("must-not-cross"), false);
|
|
|
|
const productionOpenCode = await importTemplate(
|
|
"./opencode/agent-pet-companion.js.tpl",
|
|
"production-exports",
|
|
);
|
|
assert.deepEqual(
|
|
Object.keys(productionOpenCode),
|
|
["AgentPetCompanion"],
|
|
"OpenCode treats every named export as a Plugin factory",
|
|
);
|
|
|
|
const opencode = await importTemplate(
|
|
"./opencode/agent-pet-companion.js.tpl",
|
|
"test-inventory-exports",
|
|
[
|
|
"APC_OPENCODE_CONTRACT_VERSION",
|
|
"APC_OPENCODE_PLUGIN_HOOK_INVENTORY",
|
|
"APC_OPENCODE_EVENT_INVENTORY",
|
|
"boundedActivity",
|
|
],
|
|
);
|
|
assert.equal(opencode.APC_OPENCODE_CONTRACT_VERSION, "opencode-v1.18.4-events-v16");
|
|
assert.equal(opencode.APC_OPENCODE_PLUGIN_HOOK_INVENTORY.length, 21);
|
|
assert.ok(opencode.APC_OPENCODE_PLUGIN_HOOK_INVENTORY.includes("tool.definition"));
|
|
assert.ok(opencode.APC_OPENCODE_PLUGIN_HOOK_INVENTORY.includes("dispose"));
|
|
assert.ok(opencode.APC_OPENCODE_PLUGIN_HOOK_INVENTORY.includes("provider"));
|
|
assert.equal(opencode.APC_OPENCODE_EVENT_INVENTORY.length, 91);
|
|
for (const eventName of [
|
|
"permission.asked",
|
|
"permission.v2.asked",
|
|
"question.v2.replied",
|
|
"catalog.updated",
|
|
"session.compacted",
|
|
"session.next.prompt.admitted",
|
|
"session.next.reasoning.delta",
|
|
"session.next.tool.input.delta",
|
|
"session.next.tool.failed",
|
|
"session.next.compaction.ended",
|
|
]) {
|
|
assert.ok(opencode.APC_OPENCODE_EVENT_INVENTORY.includes(eventName));
|
|
}
|
|
assert.equal(opencode.APC_OPENCODE_EVENT_INVENTORY.includes("catalog.model.updated"), false);
|
|
for (const { name, value, expected } of encodedAndDumpActivityCases) {
|
|
assert.equal(opencode.boundedActivity(value), expected, `OpenCode ${name}`);
|
|
}
|
|
assert.equal(
|
|
opencode.boundedActivity({ command: "TOKEN=secret-command" }),
|
|
"TOKEN=secret-command",
|
|
"OpenCode explicit command context must retain environment assignments",
|
|
);
|
|
assert.equal(
|
|
opencode.boundedActivity({ command: "PATH=/usr/bin API_KEY=secret-command" }),
|
|
"PATH=/usr/bin API_KEY=secret-command",
|
|
"OpenCode explicit command context must retain same-line environment assignments",
|
|
);
|
|
for (const value of credentialContainerVariants) {
|
|
assert.equal(
|
|
opencode.boundedActivity(value),
|
|
undefined,
|
|
"OpenCode activity must not recurse through credential containers",
|
|
);
|
|
}
|
|
assert.equal(opencode.boundedActivity({ path: "README.md" }), "README.md");
|
|
assert.equal(
|
|
opencode.boundedActivity({ token_count: { message: "42 tokens processed" } }),
|
|
"42 tokens processed",
|
|
"OpenCode token statistics must not be mistaken for credential containers",
|
|
);
|
|
assert.equal(
|
|
opencode.boundedActivity({ token_counts: { message: "42 input, 17 output" } }),
|
|
"42 input, 17 output",
|
|
"OpenCode token statistics plural must remain allowlisted",
|
|
);
|
|
assert.equal(
|
|
opencode.boundedActivity({ authorMetadata: { message: "Ada Lovelace" } }),
|
|
"Ada Lovelace",
|
|
"OpenCode author metadata must not be mistaken for auth credentials",
|
|
);
|
|
assert.equal(
|
|
opencode.boundedActivity({ envelope: { message: "ordinary transport envelope" } }),
|
|
"ordinary transport envelope",
|
|
"OpenCode envelope metadata must not be mistaken for environment credentials",
|
|
);
|
|
|
|
let releaseExistingSessionList;
|
|
let existingSessionListRequested = false;
|
|
const hooksPromise = opencode.AgentPetCompanion({
|
|
directory: "/secret/project",
|
|
worktree: "/secret/worktree",
|
|
client: {
|
|
session: {
|
|
list: () => new Promise((resolve) => {
|
|
existingSessionListRequested = true;
|
|
releaseExistingSessionList = () => resolve({
|
|
data: [{
|
|
id: "opencode-existing-child",
|
|
parentID: "opencode-root",
|
|
title: "must-not-cross-existing-child-title",
|
|
}],
|
|
});
|
|
}),
|
|
},
|
|
},
|
|
});
|
|
let hooksResolvedBeforeLineage = false;
|
|
void hooksPromise.then(() => { hooksResolvedBeforeLineage = true; });
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.equal(hooksResolvedBeforeLineage, true);
|
|
const hooks = await hooksPromise;
|
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
assert.equal(existingSessionListRequested, true);
|
|
let eventResolvedBeforeLineage = false;
|
|
const gatedExistingChildEvent = hooks.event({
|
|
event: {
|
|
type: "session.status",
|
|
properties: {
|
|
sessionID: "opencode-existing-child",
|
|
status: { type: "busy" },
|
|
},
|
|
},
|
|
});
|
|
void gatedExistingChildEvent.then(() => { eventResolvedBeforeLineage = true; });
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.equal(eventResolvedBeforeLineage, false);
|
|
releaseExistingSessionList();
|
|
await gatedExistingChildEvent;
|
|
const expectedOpenCodeHooks = [
|
|
"event",
|
|
"dispose",
|
|
"chat.message",
|
|
"permission.ask",
|
|
"command.execute.before",
|
|
"tool.execute.before",
|
|
"tool.execute.after",
|
|
"experimental.session.compacting",
|
|
"experimental.text.complete",
|
|
];
|
|
for (const hookName of expectedOpenCodeHooks) {
|
|
assert.equal(typeof hooks[hookName], "function", `OpenCode ${hookName} hook missing`);
|
|
}
|
|
assert.deepEqual(new Set(Object.keys(hooks)), new Set(expectedOpenCodeHooks));
|
|
assert.equal(hooks["tool.definition"], undefined, "tool.definition must remain unregistered");
|
|
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.updated",
|
|
properties: {
|
|
info: {
|
|
id: "opencode-session",
|
|
title: "Generated OpenCode title",
|
|
privateMetadata: "secret-opencode-session-metadata",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "permission.asked",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
patterns: ["secret-permission-pattern"],
|
|
metadata: { token: "secret-permission-metadata" },
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
id: "secret-v2-permission-event-id",
|
|
type: "permission.v2.asked",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
permission: "secret-permission-name",
|
|
resources: ["secret-permission-resource"],
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
id: "secret-v2-permission-reply-event-id",
|
|
type: "permission.v2.replied",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
reply: "once",
|
|
resources: ["secret-replied-resource"],
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
id: "secret-v2-question-event-id",
|
|
type: "question.v2.asked",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
questions: [{ question: "secret-question", options: ["secret-option"] }],
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "question.v2.replied",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
answers: [["secret-answer"]],
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.next.prompt.admitted",
|
|
id: "secret-prompt-admitted-event-id",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
messageID: "secret-prompt-message-id",
|
|
prompt: "secret-admitted-prompt",
|
|
delivery: { metadata: "secret-delivery" },
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.error",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
error: { message: "secret-provider-error", responseBody: "secret-response-body" },
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "todo.updated",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
todos: [{ content: "secret-todo-content" }],
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.next.reasoning.delta",
|
|
properties: { sessionID: "opencode-reasoning-session", delta: "secret-hidden-reasoning" },
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.next.reasoning.ended",
|
|
properties: {
|
|
sessionID: "opencode-reasoning-session",
|
|
summary: "Stable reasoning checkpoint",
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "message.part.updated",
|
|
properties: {
|
|
part: {
|
|
id: "reasoning-growing",
|
|
messageID: "reasoning-message",
|
|
sessionID: "opencode-reasoning-session",
|
|
type: "reasoning",
|
|
text: "Transient growing reasoning",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "message.part.updated",
|
|
properties: {
|
|
part: {
|
|
id: "reasoning-finished",
|
|
messageID: "reasoning-message",
|
|
sessionID: "opencode-reasoning-session",
|
|
type: "reasoning",
|
|
text: "Stable v1 reasoning checkpoint",
|
|
time: { end: 1 },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "todo.updated",
|
|
properties: {
|
|
sessionID: "opencode-plan-session",
|
|
todos: [{ content: "Stable plan checkpoint" }],
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.next.tool.input.delta",
|
|
properties: { sessionID: "opencode-session", callID: "opaque-open-call", delta: "secret-tool-input" },
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.next.tool.called",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
callID: "opaque-open-call",
|
|
tool: "bash",
|
|
input: { command: "TOKEN=secret-v2-command" },
|
|
provider: { metadata: { token: "secret-provider-metadata" } },
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.next.tool.failed",
|
|
properties: {
|
|
sessionID: "opencode-session",
|
|
callID: "opaque-open-call",
|
|
error: { message: "secret-v2-tool-error" },
|
|
content: [{ type: "text", text: "secret-v2-tool-output" }],
|
|
},
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.next.compaction.delta",
|
|
properties: { sessionID: "opencode-session", text: "secret-compaction-text" },
|
|
},
|
|
});
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.next.text.ended",
|
|
properties: { sessionID: "opencode-session", text: "Visible OpenCode answer" },
|
|
},
|
|
});
|
|
await hooks["permission.ask"](
|
|
{
|
|
sessionID: "opencode-session",
|
|
patterns: ["secret-direct-permission"],
|
|
metadata: { token: "secret-direct-metadata" },
|
|
},
|
|
{ status: "ask" },
|
|
);
|
|
await hooks["chat.message"](
|
|
{ sessionID: "opencode-session", messageID: "secret-chat-message-id" },
|
|
{ parts: [{ type: "text", text: "Visible OpenCode prompt" }] },
|
|
);
|
|
await hooks["command.execute.before"](
|
|
{ sessionID: "opencode-session", command: "secret-command-name", arguments: "secret-command-arguments" },
|
|
{ parts: [{ type: "text", text: "secret-command-parts" }] },
|
|
);
|
|
await hooks["tool.execute.before"](
|
|
{ sessionID: "opencode-session", callID: "opaque-direct-call", tool: "bash" },
|
|
{ args: { command: "TOKEN=secret-direct-command" } },
|
|
);
|
|
await hooks["tool.execute.after"](
|
|
{ sessionID: "opencode-session", callID: "opaque-direct-call", tool: "bash" },
|
|
{ output: "secret-direct-output", metadata: { token: "secret-tool-metadata" } },
|
|
);
|
|
await hooks["experimental.session.compacting"](
|
|
{ sessionID: "opencode-session" },
|
|
{ context: ["secret-compact-context"], prompt: "secret-compact-prompt" },
|
|
);
|
|
await hooks["experimental.text.complete"](
|
|
{ sessionID: "opencode-session", messageID: "message", partID: "part" },
|
|
{ text: "Visible direct OpenCode answer" },
|
|
);
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.created",
|
|
properties: {
|
|
info: {
|
|
id: "opencode-live-child",
|
|
parentID: "opencode-root",
|
|
title: "must-not-cross-live-child-title",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await hooks["chat.message"](
|
|
{ sessionID: "opencode-live-child", messageID: "child-message" },
|
|
{ parts: [{ type: "text", text: "must-not-cross-live-child-prompt" }] },
|
|
);
|
|
await hooks.event({
|
|
event: {
|
|
type: "session.status",
|
|
properties: { sessionID: "opencode-live-child", status: { type: "busy" } },
|
|
},
|
|
});
|
|
await hooks.dispose();
|
|
|
|
const opencodePayloads = captured
|
|
.filter((item) => item.source === "opencode")
|
|
.map((item) => item.payload);
|
|
const serializedOpenCode = JSON.stringify(opencodePayloads);
|
|
for (const secret of [
|
|
"secret-permission-pattern",
|
|
"secret-permission-metadata",
|
|
"secret-permission-name",
|
|
"secret-permission-resource",
|
|
"secret-replied-resource",
|
|
"secret-question",
|
|
"secret-option",
|
|
"secret-answer",
|
|
"secret-admitted-prompt",
|
|
"secret-delivery",
|
|
"secret-v2-permission-event-id",
|
|
"secret-v2-permission-reply-event-id",
|
|
"secret-v2-question-event-id",
|
|
"secret-prompt-admitted-event-id",
|
|
"secret-prompt-message-id",
|
|
"secret-opencode-session-metadata",
|
|
"secret-chat-message-id",
|
|
"secret-response-body",
|
|
"secret-hidden-reasoning",
|
|
"secret-tool-input",
|
|
"secret-provider-metadata",
|
|
"secret-v2-tool-error",
|
|
"opaque-open-call",
|
|
"secret-compaction-text",
|
|
"secret-direct-permission",
|
|
"secret-direct-metadata",
|
|
"secret-command-arguments",
|
|
"secret-command-parts",
|
|
"opaque-direct-call",
|
|
"secret-tool-metadata",
|
|
"secret-compact-context",
|
|
"secret-compact-prompt",
|
|
"must-not-cross-existing-child-title",
|
|
"must-not-cross-live-child-title",
|
|
"must-not-cross-live-child-prompt",
|
|
"/secret/project",
|
|
"/secret/worktree",
|
|
]) {
|
|
assert.equal(serializedOpenCode.includes(secret), false, `OpenCode leaked ${secret}`);
|
|
}
|
|
assert.ok(opencodePayloads.every((payload) => (
|
|
payload.diagnostic === true || payload.properties?.diagnostic === true
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "tool.execute.after" && payload.is_error === true
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "session.error"
|
|
&& payload.properties?.activity_content === "secret-provider-error"
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "tool.execute.before"
|
|
&& payload.activity_content?.includes("TOKEN=secret-direct-command")
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "tool.execute.after"
|
|
&& payload.activity_content?.includes("secret-direct-output")
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "command.execute.before"
|
|
&& payload.activity_content?.includes("secret-command-name")
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "session.compaction.started"
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "session.next.prompt.admitted"
|
|
&& /^[0-9a-f]{64}$/.test(payload.turn_id)
|
|
&& /^[0-9a-f]{64}$/.test(payload.eventID)
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "session.next.reasoning.ended"
|
|
&& payload.properties?.activity_content === "Stable reasoning checkpoint"
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "session.next.reasoning.ended"
|
|
&& payload.properties?.activity_content === "Stable v1 reasoning checkpoint"
|
|
)));
|
|
assert.equal(opencodePayloads.some((payload) => (
|
|
payload.properties?.activity_content === "Transient growing reasoning"
|
|
)), false);
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "session.plan.updated"
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "permission.v2.replied"
|
|
&& payload.properties?.response === "once"
|
|
&& /^[0-9a-f]{64}$/.test(payload.eventID)
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "message.user"
|
|
&& /^[0-9a-f]{64}$/.test(payload.turn_id)
|
|
)));
|
|
assert.ok(opencodePayloads.some((payload) => (
|
|
payload.type === "session.updated"
|
|
&& payload.properties?.session_title === "Generated OpenCode title"
|
|
&& payload.properties?.privateMetadata === undefined
|
|
)));
|
|
assert.ok(opencodePayloads.filter((payload) => payload.input?.callID).every((payload) => (
|
|
/^[0-9a-f]{64}$/.test(payload.input.callID)
|
|
)));
|
|
for (const childID of ["opencode-existing-child", "opencode-live-child"]) {
|
|
const childPayloads = opencodePayloads.filter((payload) => (
|
|
payload.properties?.sessionID === childID
|
|
));
|
|
assert.equal(childPayloads.length, 1);
|
|
assert.equal(childPayloads[0].type, "session.child");
|
|
}
|
|
|
|
const adversarial = await importTemplate(
|
|
"./opencode/agent-pet-companion.js.tpl",
|
|
"adversarial-runtime",
|
|
);
|
|
const adversarialHooks = await adversarial.AgentPetCompanion({});
|
|
const adversarialStart = captured.length;
|
|
await adversarialHooks["chat.message"](
|
|
{ sessionID: "opencode-error-order", messageID: "error-user" },
|
|
{ parts: [{ type: "text", text: "Trigger failure" }] },
|
|
);
|
|
await adversarialHooks.event({ event: {
|
|
type: "message.updated",
|
|
properties: { info: { id: "pending-error-answer", sessionID: "opencode-error-order", parentID: "error-user", role: "assistant" } },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "message.part.updated",
|
|
properties: { part: { id: "pending-error-part", messageID: "pending-error-answer", sessionID: "opencode-error-order", type: "text", text: "must-not-flush-after-error" } },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.error",
|
|
properties: { sessionID: "opencode-error-order", error: { message: "raw-error-must-not-cross" } },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.status",
|
|
properties: { sessionID: "opencode-error-order", status: { type: "idle" } },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.idle",
|
|
properties: { sessionID: "opencode-error-order" },
|
|
} });
|
|
await adversarialHooks["tool.execute.after"]({
|
|
sessionID: "opencode-error-order", tool: "bash", callID: "error-tail-call",
|
|
});
|
|
await adversarialHooks["experimental.text.complete"](
|
|
{ sessionID: "opencode-error-order", messageID: "error-tail-message" },
|
|
{ text: "must-not-forward-direct-tail" },
|
|
);
|
|
|
|
const cancelledFinal = adversarialHooks.event({ event: {
|
|
type: "session.next.step.ended",
|
|
properties: { sessionID: "opencode-steered", assistantMessageID: "step-one", finish: "stop" },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.started",
|
|
properties: { sessionID: "opencode-steered", assistantMessageID: "step-two" },
|
|
} });
|
|
await cancelledFinal;
|
|
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.started",
|
|
properties: { sessionID: "opencode-late-failure", assistantMessageID: "old-step" },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.started",
|
|
properties: { sessionID: "opencode-late-failure", assistantMessageID: "current-step" },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.failed",
|
|
properties: { sessionID: "opencode-late-failure", assistantMessageID: "old-step" },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.ended",
|
|
properties: { sessionID: "opencode-late-failure", assistantMessageID: "current-step", finish: "stop" },
|
|
} });
|
|
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.started",
|
|
properties: { sessionID: "opencode-local-step", assistantMessageID: "local-step" },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.tool.called",
|
|
properties: {
|
|
sessionID: "opencode-local-step",
|
|
assistantMessageID: "local-step",
|
|
callID: "local-call",
|
|
tool: "bash",
|
|
providerExecuted: false,
|
|
},
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.ended",
|
|
properties: { sessionID: "opencode-local-step", assistantMessageID: "local-step", finish: "stop" },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.started",
|
|
properties: { sessionID: "opencode-hosted-step", assistantMessageID: "hosted-step" },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.tool.called",
|
|
properties: {
|
|
sessionID: "opencode-hosted-step",
|
|
assistantMessageID: "hosted-step",
|
|
callID: "hosted-call",
|
|
tool: "hosted",
|
|
provider: { executed: true },
|
|
},
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.ended",
|
|
properties: { sessionID: "opencode-hosted-step", assistantMessageID: "hosted-step", finish: "tool-calls" },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.ended",
|
|
properties: { sessionID: "opencode-final-step", assistantMessageID: "final-step", finish: "stop" },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.failed",
|
|
properties: { sessionID: "opencode-failed-step", error: { message: "raw-step-error" } },
|
|
} });
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.step.failed",
|
|
properties: { error: { message: "unattributed-step-error" } },
|
|
} });
|
|
await adversarialHooks["experimental.text.complete"](
|
|
{ sessionID: "opencode-assistant-dedup", messageID: "assistant-dedup" },
|
|
{ text: "One visible answer" },
|
|
);
|
|
await adversarialHooks.event({ event: {
|
|
type: "session.next.text.ended",
|
|
properties: {
|
|
sessionID: "opencode-assistant-dedup",
|
|
assistantMessageID: "assistant-dedup",
|
|
text: "One visible answer",
|
|
},
|
|
} });
|
|
await adversarialHooks.dispose();
|
|
|
|
const adversarialPayloads = captured.slice(adversarialStart).map((item) => item.payload);
|
|
const errorPayloads = adversarialPayloads.filter((payload) => (
|
|
payload.properties?.sessionID === "opencode-error-order"
|
|
|| payload.input?.sessionID === "opencode-error-order"
|
|
));
|
|
assert.deepEqual(errorPayloads.map((payload) => payload.type), ["message.user", "session.error"]);
|
|
assert.equal(JSON.stringify(errorPayloads).includes("raw-error-must-not-cross"), true);
|
|
assert.equal(JSON.stringify(errorPayloads).includes("must-not-flush-after-error"), false);
|
|
assert.equal(JSON.stringify(errorPayloads).includes("must-not-forward-direct-tail"), false);
|
|
assert.equal(adversarialPayloads.some((payload) => (
|
|
payload.type === "session.next.step.ended"
|
|
&& payload.properties?.sessionID === "opencode-steered"
|
|
)), false);
|
|
assert.equal(adversarialPayloads.some((payload) => (
|
|
payload.type === "session.next.step.failed"
|
|
&& payload.properties?.sessionID === "opencode-late-failure"
|
|
)), false);
|
|
assert.equal(adversarialPayloads.filter((payload) => (
|
|
payload.type === "session.next.step.ended"
|
|
&& payload.properties?.sessionID === "opencode-late-failure"
|
|
&& payload.outcome === "completed"
|
|
)).length, 1);
|
|
assert.equal(adversarialPayloads.filter((payload) => (
|
|
payload.type === "session.next.step.ended"
|
|
&& payload.properties?.sessionID === "opencode-local-step"
|
|
&& payload.outcome === "continued"
|
|
)).length, 1);
|
|
assert.equal(adversarialPayloads.filter((payload) => (
|
|
payload.type === "session.next.step.ended"
|
|
&& payload.properties?.sessionID === "opencode-hosted-step"
|
|
&& payload.outcome === "completed"
|
|
)).length, 1);
|
|
assert.equal(adversarialPayloads.filter((payload) => (
|
|
payload.type === "session.next.step.ended"
|
|
&& payload.properties?.sessionID === "opencode-final-step"
|
|
&& payload.outcome === "completed"
|
|
)).length, 1);
|
|
assert.equal(adversarialPayloads.filter((payload) => (
|
|
payload.type === "session.next.step.failed"
|
|
)).length, 1);
|
|
assert.equal(JSON.stringify(adversarialPayloads).includes("raw-step-error"), true);
|
|
assert.equal(adversarialPayloads.filter((payload) => (
|
|
payload.type === "message.assistant"
|
|
&& payload.properties?.sessionID === "opencode-assistant-dedup"
|
|
)).length, 1);
|
|
|
|
const epochQueueModule = await importTemplate(
|
|
"./opencode/agent-pet-companion.js.tpl",
|
|
"queued-terminal-cancellation",
|
|
);
|
|
const epochQueueHooks = await epochQueueModule.AgentPetCompanion({});
|
|
spawnCloseDelayMs = 200;
|
|
const epochQueueStart = captured.length;
|
|
const queuedCalls = [epochQueueHooks.event({ event: {
|
|
type: "session.status",
|
|
properties: { sessionID: "queue-blocker", status: { type: "busy" } },
|
|
} })];
|
|
queuedCalls.push(epochQueueHooks.event({ event: {
|
|
type: "session.error",
|
|
properties: { sessionID: "queued-error-retry" },
|
|
} }));
|
|
queuedCalls.push(epochQueueHooks.event({ event: {
|
|
type: "session.status",
|
|
properties: { sessionID: "queued-error-retry", status: { type: "retry" } },
|
|
} }));
|
|
queuedCalls.push(epochQueueHooks.event({ event: {
|
|
type: "session.idle",
|
|
properties: { sessionID: "queued-idle-tool" },
|
|
} }));
|
|
queuedCalls.push(epochQueueHooks["tool.execute.before"]({
|
|
sessionID: "queued-idle-tool", tool: "bash", callID: "queued-tool-call",
|
|
}));
|
|
await epochQueueHooks.dispose();
|
|
await Promise.all(queuedCalls);
|
|
spawnCloseDelayMs = 0;
|
|
const epochQueuePayloads = captured.slice(epochQueueStart).map((item) => item.payload);
|
|
assert.equal(epochQueuePayloads.some((payload) => (
|
|
payload.type === "session.error"
|
|
&& payload.properties?.sessionID === "queued-error-retry"
|
|
)), false);
|
|
assert.equal(epochQueuePayloads.some((payload) => (
|
|
payload.type === "session.status"
|
|
&& payload.properties?.sessionID === "queued-error-retry"
|
|
&& payload.properties?.status === "retry"
|
|
)), true);
|
|
assert.equal(epochQueuePayloads.some((payload) => (
|
|
payload.type === "session.idle"
|
|
&& payload.properties?.sessionID === "queued-idle-tool"
|
|
)), false);
|
|
assert.equal(epochQueuePayloads.some((payload) => (
|
|
payload.type === "tool.execute.before"
|
|
&& payload.input?.sessionID === "queued-idle-tool"
|
|
)), true);
|
|
|
|
const stormModule = await importTemplate(
|
|
"./opencode/agent-pet-companion.js.tpl",
|
|
"bounded-offline-storm",
|
|
);
|
|
const stormHooks = await stormModule.AgentPetCompanion({});
|
|
spawnCloseDelayMs = 10_000;
|
|
const stormStart = captured.length;
|
|
const stormCalls = [];
|
|
stormCalls.push(stormHooks["chat.message"](
|
|
{ sessionID: "opencode-storm", messageID: "storm-user" },
|
|
{ parts: [{ type: "text", text: "Storm prompt" }] },
|
|
));
|
|
stormCalls.push(stormHooks["chat.message"](
|
|
{ sessionID: "opencode-full-assistant", messageID: "full-user" },
|
|
{ parts: [{ type: "text", text: "Full queue prompt" }] },
|
|
));
|
|
stormCalls.push(stormHooks.event({ event: {
|
|
type: "message.updated",
|
|
properties: { info: { id: "full-answer", sessionID: "opencode-full-assistant", role: "assistant" } },
|
|
} }));
|
|
stormCalls.push(stormHooks.event({ event: {
|
|
type: "message.part.updated",
|
|
properties: { part: { id: "full-part", messageID: "full-answer", sessionID: "opencode-full-assistant", type: "text", text: "Latest answer survives capacity" } },
|
|
} }));
|
|
for (let index = 0; index < 300; index += 1) {
|
|
stormCalls.push(stormHooks.event({ event: {
|
|
type: "session.status",
|
|
properties: { sessionID: `other-${index}`, status: { type: "busy" } },
|
|
} }));
|
|
}
|
|
stormCalls.push(stormHooks.event({ event: {
|
|
type: "session.idle",
|
|
properties: { sessionID: "opencode-full-assistant" },
|
|
} }));
|
|
stormCalls.push(stormHooks.event({ event: {
|
|
type: "session.error",
|
|
properties: { sessionID: "opencode-storm", error: { message: "offline-secret" } },
|
|
} }));
|
|
stormCalls.push(stormHooks.event({ event: {
|
|
type: "session.error",
|
|
properties: { sessionID: "opencode-storm-two", error: { message: "offline-secret-two" } },
|
|
} }));
|
|
const disposeStarted = Date.now();
|
|
await stormHooks.dispose();
|
|
await Promise.all(stormCalls);
|
|
const disposeElapsed = Date.now() - disposeStarted;
|
|
spawnCloseDelayMs = 0;
|
|
const stormPayloads = captured.slice(stormStart).map((item) => item.payload);
|
|
const stormSessionPayloads = stormPayloads.filter((payload) => (
|
|
payload.properties?.sessionID === "opencode-storm"
|
|
|| payload.input?.sessionID === "opencode-storm"
|
|
));
|
|
assert.deepEqual(stormSessionPayloads.map((payload) => payload.type), ["message.user", "session.error"]);
|
|
assert.equal(stormPayloads.some((payload) => (
|
|
payload.type === "session.error"
|
|
&& payload.properties?.sessionID === "opencode-storm-two"
|
|
)), true);
|
|
const capacitySessionTypes = stormPayloads.filter((payload) => (
|
|
payload.properties?.sessionID === "opencode-full-assistant"
|
|
|| payload.input?.sessionID === "opencode-full-assistant"
|
|
)).map((payload) => payload.type);
|
|
assert.deepEqual(capacitySessionTypes, ["message.user", "message.assistant", "session.idle"]);
|
|
assert.ok(stormPayloads.length <= 100, `bounded queue spawned ${stormPayloads.length} deliveries`);
|
|
assert.ok(disposeElapsed < 4200, `dispose took ${disposeElapsed}ms`);
|
|
|
|
process.env.APC_CONNECTOR_PROBE = "1";
|
|
process.env.APC_CONNECTOR_PROBE_ID = "apc-probe-018f47d2-6f9d-7b1a-8d31-12f447f59f01";
|
|
const piProbe = await importTemplate("./pi/agent-pet-companion.ts.tpl", "probe");
|
|
const piProbeHandlers = new Map();
|
|
piProbe.default({ on: (name, handler) => piProbeHandlers.set(name, handler) });
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.ok(captured.some((item) => (
|
|
item.source === "pi"
|
|
&& item.payload.type === "connector.probe"
|
|
&& item.payload.diagnostic === true
|
|
&& item.payload.session_id === process.env.APC_CONNECTOR_PROBE_ID
|
|
)), "Pi must emit its load canary before model validation can end the host");
|
|
await piProbeHandlers.get("session_start")(
|
|
{ type: "session_start" },
|
|
{ sessionManager: { getSessionId: () => "pi-probe-session" } },
|
|
);
|
|
await piProbeHandlers.get("session_shutdown")(
|
|
{ type: "session_shutdown" },
|
|
{ sessionManager: { getSessionId: () => "pi-probe-session" } },
|
|
);
|
|
const opencodeProbe = await importTemplate("./opencode/agent-pet-companion.js.tpl", "probe");
|
|
await opencodeProbe.AgentPetCompanion({});
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.ok(captured.some((item) => (
|
|
item.source === "pi"
|
|
&& item.payload.type === "connector.probe"
|
|
&& item.payload.diagnostic === true
|
|
&& item.payload.session_id === process.env.APC_CONNECTOR_PROBE_ID
|
|
)));
|
|
assert.equal(captured.filter((item) => (
|
|
item.source === "pi" && item.payload.type === "connector.probe"
|
|
)).length, 1);
|
|
assert.ok(captured.some((item) => (
|
|
item.source === "opencode"
|
|
&& item.payload.type === "connector.probe"
|
|
&& item.payload.properties?.diagnostic === true
|
|
&& item.payload.properties?.sessionID === process.env.APC_CONNECTOR_PROBE_ID
|
|
)));
|
|
|
|
// --- DeepSeek Harness (dsh) template privacy and filtering tests ---
|
|
const dsh = await importTemplate("./dsh/agent-pet-companion.js.tpl", "dsh-privacy");
|
|
assert.equal(dsh.APC_DSH_CONTRACT_VERSION, "dsh-v0.1.0-rc.6-events-v1");
|
|
assert.equal(dsh.APC_DSH_AUDITED_EVENTS.length, 5);
|
|
|
|
const dshListeners = new Map();
|
|
const fakeCtx = {
|
|
on: (event, handler) => {
|
|
dshListeners.set(event, handler);
|
|
},
|
|
};
|
|
dsh.apply(fakeCtx);
|
|
assert.ok(dshListeners.has("session/event"), "dsh must listen to session/event");
|
|
assert.ok(dshListeners.has("subagent/start"), "dsh must listen to subagent/start");
|
|
assert.ok(dshListeners.has("subagent/end"), "dsh must listen to subagent/end");
|
|
assert.ok(dshListeners.has("session/disposed"), "dsh must listen to session/disposed");
|
|
|
|
// Test 1: session.child emission on child session first sight
|
|
const childSession = {
|
|
id: "child-sess-1",
|
|
header: { origin: "subagent", parentSession: "parent-sess-1", delegationDepth: 1 },
|
|
};
|
|
dshListeners.get("session/event")(childSession, { type: "turn/start", data: { turn: 1 } });
|
|
await new Promise((r) => setTimeout(r, 20));
|
|
assert.ok(captured.some((item) => (
|
|
item.source === "dsh"
|
|
&& item.payload.type === "session.child"
|
|
&& item.payload.session_id === "child-sess-1"
|
|
)), "dsh must emit session.child on child session detection");
|
|
|
|
// Subsequent events from child are dropped locally
|
|
const countBefore = captured.length;
|
|
dshListeners.get("session/event")(childSession, {
|
|
type: "assistant/chunk",
|
|
data: { turn: 1, step: 1, chunk: { type: "block-start", blockType: "reasoning" } },
|
|
});
|
|
await new Promise((r) => setTimeout(r, 20));
|
|
assert.equal(captured.length, countBefore, "child events after first sight must be dropped");
|
|
|
|
// Test 2: parent turn/end with active background fence emits background_active
|
|
const parentSession = {
|
|
id: "parent-sess-1",
|
|
header: { origin: undefined, parentSession: undefined },
|
|
};
|
|
// subagent/start puts run in parent's fence
|
|
dshListeners.get("subagent/start")({ runId: "run-1", id: "child-sess-1", local: true });
|
|
// parent completes turn while fence has active run
|
|
dshListeners.get("session/event")(parentSession, {
|
|
type: "turn/end",
|
|
data: { turn: 1, reason: { kind: "completed" } },
|
|
});
|
|
await new Promise((r) => setTimeout(r, 30));
|
|
assert.ok(captured.some((item) => (
|
|
item.source === "dsh"
|
|
&& item.payload.type === "turn/end"
|
|
&& item.payload.session_id === "parent-sess-1"
|
|
&& item.payload.background_active === true
|
|
)), "parent turn/end with active fence must emit background_active");
|
|
|
|
// subagent/end drains fence -> emits deferred completed turn/end
|
|
dshListeners.get("subagent/end")({ runId: "run-1" });
|
|
await new Promise((r) => setTimeout(r, 30));
|
|
assert.ok(captured.some((item) => (
|
|
item.source === "dsh"
|
|
&& item.payload.type === "turn/end"
|
|
&& item.payload.session_id === "parent-sess-1"
|
|
&& item.payload.background_active === undefined
|
|
&& item.payload.reason?.kind === "completed"
|
|
)), "subagent/end fence drain must emit deferred completed turn/end");
|
|
|
|
// Test 3: delta chunks dropped (zero churn)
|
|
const countBeforeDelta = captured.length;
|
|
dshListeners.get("session/event")(parentSession, {
|
|
type: "assistant/chunk",
|
|
data: { turn: 2, step: 1, chunk: { type: "text-delta", text: "churn" } },
|
|
});
|
|
await new Promise((r) => setTimeout(r, 20));
|
|
assert.equal(captured.length, countBeforeDelta, "text-delta chunks must be dropped");
|
|
|
|
} finally {
|
|
childProcess.spawn = originalSpawn;
|
|
syncBuiltinESMExports();
|
|
if (originalDiagnostic === undefined) delete process.env.APC_CONNECTOR_DIAGNOSTIC;
|
|
else process.env.APC_CONNECTOR_DIAGNOSTIC = originalDiagnostic;
|
|
if (originalProbe === undefined) delete process.env.APC_CONNECTOR_PROBE;
|
|
else process.env.APC_CONNECTOR_PROBE = originalProbe;
|
|
if (originalProbeID === undefined) delete process.env.APC_CONNECTOR_PROBE_ID;
|
|
else process.env.APC_CONNECTOR_PROBE_ID = originalProbeID;
|
|
}
|
|
});
|