feat: add multi-session desktop workspace

This commit is contained in:
2026-08-13 21:34:04 +02:00
parent 12b9b83d51
commit 475f95ba70
56 changed files with 8998 additions and 3805 deletions
+74 -7
View File
@@ -8,6 +8,14 @@ const requestOperations = new Map([
["list_directories", { agent: false, payload: "none" }],
["select_agent", { agent: false, payload: "worktree" }],
["forget_directory", { agent: false, payload: "worktree" }],
["get_workspace", { agent: false, payload: "none" }],
["get_workspace_summary", { agent: false, payload: "none" }],
["create_session_runtime", { agent: false, payload: "worktree" }],
["open_session_runtime", { agent: false, payload: "worktreeSession" }],
["close_session_runtime", { agent: false, payload: "runtime" }],
["list_directory_sessions", { agent: false, payload: "worktree" }],
["get_session_runtime_snapshot", { agent: false, payload: "runtime" }],
["subscribe_workspace", { agent: false, payload: "cursor" }],
["get_state", { agent: true, payload: "none" }],
["get_session_stats", { agent: true, payload: "none" }],
["list_sessions", { agent: true, payload: "none" }],
@@ -25,6 +33,8 @@ const requestOperations = new Map([
["restart", { agent: true, payload: "none" }],
["get_available_models", { agent: true, payload: "none" }],
["get_commands", { agent: true, payload: "none" }],
["set_session_name", { agent: true, payload: "sessionName" }],
["compact", { agent: true, payload: "compact" }],
["set_model", { agent: true, payload: "model" }],
["set_thinking_level", { agent: true, payload: "thinking" }],
]);
@@ -100,10 +110,10 @@ function assertString(value, field, { maxLength = 4096, pattern } = {}) {
function assertOptionalCursor(value) {
if (value === undefined) return undefined;
if (Number.isSafeInteger(value) && value >= 0) return value;
return assertString(value, "payload.cursor", {
maxLength: 256,
pattern: idPattern,
});
throw new ProtocolError(
"invalid_message",
"payload.cursor must be a non-negative integer",
);
}
function validatePayload(kind, value) {
@@ -115,6 +125,7 @@ function validatePayload(kind, value) {
);
return undefined;
}
if (kind === "compact" && value === undefined) return undefined;
const payload = assertRecord(value, "payload");
switch (kind) {
@@ -137,9 +148,7 @@ function validatePayload(kind, value) {
const sessionPath = assertString(
payload.sessionPath,
"payload.sessionPath",
{
maxLength: 4096,
},
{ maxLength: 4096 },
);
if (!path.isAbsolute(sessionPath))
throw new ProtocolError(
@@ -148,6 +157,38 @@ function validatePayload(kind, value) {
);
return { sessionPath };
}
case "worktreeSession": {
assertAllowedKeys(
payload,
new Set(["worktreePath", "sessionPath"]),
"payload",
);
const worktreePath = assertString(
payload.worktreePath,
"payload.worktreePath",
{ maxLength: 4096 },
);
const sessionPath = assertString(
payload.sessionPath,
"payload.sessionPath",
{ maxLength: 4096 },
);
if (!path.isAbsolute(worktreePath) || !path.isAbsolute(sessionPath))
throw new ProtocolError(
"invalid_message",
"payload worktree and session paths must be absolute",
);
return { worktreePath, sessionPath };
}
case "runtime": {
assertAllowedKeys(payload, new Set(["runtimeId"]), "payload");
return {
runtimeId: assertString(payload.runtimeId, "payload.runtimeId", {
maxLength: 128,
pattern: idPattern,
}),
};
}
case "cursor": {
assertAllowedKeys(payload, new Set(["cursor"]), "payload");
return {
@@ -185,6 +226,32 @@ function validatePayload(kind, value) {
response,
};
}
case "sessionName": {
assertAllowedKeys(payload, new Set(["name"]), "payload");
const name = assertString(payload.name, "payload.name", {
maxLength: 4096,
}).trim();
if (!name)
throw new ProtocolError(
"invalid_message",
"payload.name must not be blank",
);
return { name };
}
case "compact": {
assertAllowedKeys(payload, new Set(["customInstructions"]), "payload");
const customInstructions = assertString(
payload.customInstructions,
"payload.customInstructions",
{ maxLength: 32 * 1024 },
).trim();
if (!customInstructions)
throw new ProtocolError(
"invalid_message",
"payload.customInstructions must not be blank",
);
return { customInstructions };
}
case "model": {
assertAllowedKeys(payload, new Set(["provider", "modelId"]), "payload");
return {