feat(agent): unify MCP tools and adopt Responses delivery
Move project and host queries into native MCP with host-owned OAuth/context, while retaining memory, skills, actions and publication plugins. Add nearby-model queries and explicit chat/task/learning model defaults. Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped session ownership, pending input edits and investigation communication. Disable local-provider discovery: Server /health allowed native vLLM to probe /v1/models on port 8000. A native before/after regression prevents recurrence. Validation: 170 focused Bun tests; service/plugin typechecks; native discovery, provider-discovery and input-inbox smoke checks; no paid model inference.
This commit is contained in:
@@ -1,26 +1,31 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
|
||||
import { mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { config } from "../../src/config.js";
|
||||
import { createConversationWorkspace, resolveConversationWorkspace } from "../../src/runtime/conversationWorkspace.js";
|
||||
import { bindScopedConversationWorkspace, createConversationWorkspace, resolveConversationWorkspace } from "../../src/runtime/conversationWorkspace.js";
|
||||
import { executeSandboxCommand, probeLandlockSandbox } from "../../src/sandbox/landlockSandbox.js";
|
||||
import { canAccessConversationPath } from "../../.opencode/session-sandbox.js";
|
||||
import { canAutomaticallyApprove } from "../../src/routes/chatPermissionPolicy.js";
|
||||
|
||||
describe("conversation OS sandbox", () => {
|
||||
let root: string;
|
||||
let base: string;
|
||||
let workspace: string;
|
||||
let other: string;
|
||||
const previousRoot = config.CONVERSATION_WORKSPACE_ROOT;
|
||||
beforeAll(async () => {
|
||||
root = await mkdtemp(join(tmpdir(), "tjwater-sandbox-test-"));
|
||||
base = await mkdtemp(join(tmpdir(), "tjwater-sandbox-test-"));
|
||||
root = join(base, "data", "session-workspaces");
|
||||
config.CONVERSATION_WORKSPACE_ROOT = root;
|
||||
workspace = await createConversationWorkspace(root);
|
||||
workspace = await bindScopedConversationWorkspace(await createConversationWorkspace(root), root,
|
||||
{ actorKey: "actor-sandbox", projectKey: "project-sandbox", sessionId: "sandbox-session" });
|
||||
await mkdir(join(workspace, "..", "chat"), { mode: 0o700 });
|
||||
await writeFile(join(workspace, "..", "chat", "messages.json"), "PRIVATE_CHAT_RECORD");
|
||||
other = await createConversationWorkspace(root);
|
||||
await writeFile(join(other, "files", "private.txt"), "other-session");
|
||||
});
|
||||
afterAll(async () => { config.CONVERSATION_WORKSPACE_ROOT = previousRoot; await rm(root, { recursive: true, force: true }); });
|
||||
afterAll(async () => { config.CONVERSATION_WORKSPACE_ROOT = previousRoot; await rm(base, { recursive: true, force: true }); });
|
||||
|
||||
it("supports Landlock and runs local file computation with a sanitized environment", async () => {
|
||||
expect((await probeLandlockSandbox()).landlockAbi).toBeGreaterThanOrEqual(4);
|
||||
@@ -38,6 +43,7 @@ describe("conversation OS sandbox", () => {
|
||||
`cat '${other}/files/private.txt'`,
|
||||
`cat '${process.cwd()}/src/config.ts'`,
|
||||
"cat escape",
|
||||
"cat ../../chat/messages.json",
|
||||
"printf malicious > ../opencode.json",
|
||||
"mkdir ../.opencode",
|
||||
"printf denied > /usr/tjwater-sandbox-test",
|
||||
@@ -93,14 +99,17 @@ describe("conversation OS sandbox", () => {
|
||||
await expect(resolveConversationWorkspace(link, root)).rejects.toThrow("symbolic link");
|
||||
});
|
||||
|
||||
it("constrains native file tools and keeps arbitrary execution interactive", () => {
|
||||
it("constrains native file tools while respecting the selected approval mode", () => {
|
||||
expect(canAccessConversationPath(workspace, "files/result.txt", true)).toBeTrue();
|
||||
expect(canAccessConversationPath(workspace, "opencode.json", true)).toBeFalse();
|
||||
expect(canAccessConversationPath(workspace, "files/escape")).toBeFalse();
|
||||
expect(canAccessConversationPath(workspace, "files/broken-escape", true)).toBeFalse();
|
||||
expect(canAccessConversationPath(workspace, "../other")).toBeFalse();
|
||||
expect(canAccessConversationPath(workspace, "files/.env")).toBeFalse();
|
||||
for (const action of ["shell", "edit", "external_directory", "execute"]) expect(canAutomaticallyApprove("always", action)).toBeFalse();
|
||||
expect(canAutomaticallyApprove("always", "web_search")).toBeTrue();
|
||||
for (const action of ["shell", "edit", "external_directory", "execute"]) {
|
||||
expect(canAutomaticallyApprove("request", action)).toBeFalse();
|
||||
expect(canAutomaticallyApprove("always", action)).toBeTrue();
|
||||
}
|
||||
expect(canAutomaticallyApprove("always", "websearch")).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user