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:
2026-10-19 18:46:58 +08:00
parent f5c3cde2c2
commit 6a49d2209e
164 changed files with 12286 additions and 2368 deletions
+17 -8
View File
@@ -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();
});
});