chore: recover hamburg workspace state

This commit is contained in:
Nathan Flurry 2026-03-09 19:59:42 -07:00
parent 5d65013aa5
commit 196541394b
15 changed files with 1082 additions and 60 deletions

View file

@ -25,10 +25,12 @@ export function prepareMockAgentDataHome(dataHome: string): Record<string, strin
runtimeEnv.XDG_DATA_HOME = dataHome;
}
const nodeScript = String.raw`#!/usr/bin/env node
const nodeScript = String.raw`#!/usr/bin/env node
const { createInterface } = require("node:readline");
let nextSession = 0;
let nextPermission = 0;
const pendingPermissions = new Map();
function emit(value) {
process.stdout.write(JSON.stringify(value) + "\n");
@ -65,6 +67,38 @@ rl.on("line", (line) => {
const hasId = Object.prototype.hasOwnProperty.call(msg, "id");
const method = hasMethod ? msg.method : undefined;
if (!hasMethod && hasId) {
const pending = pendingPermissions.get(String(msg.id));
if (pending) {
pendingPermissions.delete(String(msg.id));
const outcome = msg?.result?.outcome;
const optionId = outcome?.outcome === "selected" ? outcome.optionId : "cancelled";
const suffix = optionId === "reject-once" ? "rejected" : "approved";
emit({
jsonrpc: "2.0",
method: "session/update",
params: {
sessionId: pending.sessionId,
update: {
sessionUpdate: "agent_message_chunk",
content: {
type: "text",
text: "mock permission " + suffix + ": " + optionId,
},
},
},
});
emit({
jsonrpc: "2.0",
id: pending.promptId,
result: {
stopReason: "end_turn",
},
});
}
return;
}
if (method === "session/prompt") {
const sessionId = typeof msg?.params?.sessionId === "string" ? msg.params.sessionId : "";
const text = firstText(msg?.params?.prompt);
@ -82,6 +116,51 @@ rl.on("line", (line) => {
},
},
});
if (text.includes("permission")) {
nextPermission += 1;
const permissionId = "permission-" + nextPermission;
pendingPermissions.set(permissionId, {
promptId: msg.id,
sessionId,
});
emit({
jsonrpc: "2.0",
id: permissionId,
method: "session/request_permission",
params: {
sessionId,
toolCall: {
toolCallId: "tool-call-" + nextPermission,
title: "Write mock.txt",
kind: "edit",
status: "pending",
locations: [{ path: "/tmp/mock.txt" }],
rawInput: {
path: "/tmp/mock.txt",
content: "hello",
},
},
options: [
{
kind: "allow_once",
name: "Allow once",
optionId: "allow-once",
},
{
kind: "allow_always",
name: "Always allow",
optionId: "allow-always",
},
{
kind: "reject_once",
name: "Reject",
optionId: "reject-once",
},
],
},
});
}
}
if (!hasMethod || !hasId) {
@ -117,6 +196,10 @@ rl.on("line", (line) => {
}
if (method === "session/prompt") {
const text = firstText(msg?.params?.prompt);
if (text.includes("permission")) {
return;
}
emit({
jsonrpc: "2.0",
id: msg.id,

View file

@ -578,6 +578,42 @@ describe("Integration: TypeScript SDK flat session API", () => {
await sdk.dispose();
});
it("supports permissionMode as a first-class session helper", async () => {
const sdk = await SandboxAgent.connect({
baseUrl,
token,
});
const session = await sdk.createSession({
agent: "mock",
permissionMode: "plan",
});
expect((await session.getModes())?.currentModeId).toBe("plan");
await session.setPermissionMode("normal");
expect((await session.getModes())?.currentModeId).toBe("normal");
await sdk.dispose();
});
it("rejects conflicting mode and permissionMode values", async () => {
const sdk = await SandboxAgent.connect({
baseUrl,
token,
});
await expect(
sdk.createSession({
agent: "mock",
mode: "normal",
permissionMode: "plan",
}),
).rejects.toThrow("conflicting values");
await sdk.dispose();
});
it("setThoughtLevel happy path switches to a valid thought level", async () => {
const sdk = await SandboxAgent.connect({
baseUrl,
@ -625,6 +661,43 @@ describe("Integration: TypeScript SDK flat session API", () => {
await sdk.dispose();
});
it("surfaces ACP permission requests and maps approve/reject replies", async () => {
const sdk = await SandboxAgent.connect({
baseUrl,
token,
});
const session = await sdk.createSession({ agent: "mock" });
const permissionIds: string[] = [];
const permissionTexts: string[] = [];
const offPermissions = session.onPermissionRequest((request) => {
permissionIds.push(request.id);
const reply = permissionIds.length === 1 ? "reject" : "always";
void session.replyPermission(request.id, reply);
});
const offEvents = session.onEvent((event) => {
const text = (event.payload as any)?.params?.update?.content?.text;
if (typeof text === "string" && text.startsWith("mock permission ")) {
permissionTexts.push(text);
}
});
await session.prompt([{ type: "text", text: "trigger permission request one" }]);
await session.prompt([{ type: "text", text: "trigger permission request two" }]);
await waitFor(() => (permissionIds.length === 2 ? permissionIds : undefined));
await waitFor(() => (permissionTexts.length === 2 ? permissionTexts : undefined));
expect(permissionTexts[0]).toContain("rejected");
expect(permissionTexts[1]).toContain("approved");
offEvents();
offPermissions();
await sdk.dispose();
});
it("supports MCP and skills config HTTP helpers", async () => {
const sdk = await SandboxAgent.connect({
baseUrl,