@cjhyy/code-shell-core 0.8.20 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/automation/desktop-authority-client.d.ts +9 -0
- package/dist/automation/desktop-authority-client.js +47 -0
- package/dist/automation/scheduler.d.ts +8 -0
- package/dist/automation/scheduler.js +37 -1
- package/dist/automation/store.js +14 -1
- package/dist/capabilities/index.d.ts +1 -0
- package/dist/cli/agent-server-stdio.js +4 -1
- package/dist/engine/engine-workspace-authority.d.ts +35 -0
- package/dist/engine/engine-workspace-authority.js +136 -0
- package/dist/engine/engine.d.ts +5 -10
- package/dist/engine/engine.js +84 -101
- package/dist/engine/input-attachments.d.ts +1 -0
- package/dist/engine/input-attachments.js +6 -2
- package/dist/engine/run-environment.d.ts +8 -3
- package/dist/engine/run-environment.js +33 -8
- package/dist/engine/run-image-input.d.ts +1 -0
- package/dist/engine/run-image-input.js +1 -0
- package/dist/engine/run-session-open.d.ts +2 -1
- package/dist/engine/run-session-open.js +6 -0
- package/dist/engine/run-setup.d.ts +1 -0
- package/dist/engine/run-setup.js +2 -1
- package/dist/engine/run-tooling.d.ts +2 -0
- package/dist/engine/run-tooling.js +3 -0
- package/dist/engine/run-types.d.ts +2 -0
- package/dist/engine/run-workspace.d.ts +6 -1
- package/dist/engine/run-workspace.js +47 -0
- package/dist/engine/subagent-spawner.d.ts +1 -0
- package/dist/engine/subagent-spawner.js +1 -0
- package/dist/engine/types.d.ts +2 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.extension.d.ts +1 -1
- package/dist/index.internal.d.ts +2 -2
- package/dist/index.internal.js +1 -0
- package/dist/index.js +1 -1
- package/dist/plugins/pluginAutomationTemplates.d.ts +2 -0
- package/dist/plugins/pluginAutomationTemplates.js +4 -0
- package/dist/prompt/composer.d.ts +4 -1
- package/dist/prompt/composer.js +11 -3
- package/dist/protocol/chat-session-manager.d.ts +42 -1
- package/dist/protocol/chat-session-manager.js +167 -4
- package/dist/protocol/chat-session.d.ts +9 -1
- package/dist/protocol/chat-session.js +20 -2
- package/dist/protocol/mobile-remote-types.d.ts +15 -0
- package/dist/protocol/server.d.ts +1 -2
- package/dist/protocol/server.js +21 -51
- package/dist/protocol/session-workspace-rpc.d.ts +23 -0
- package/dist/protocol/session-workspace-rpc.js +171 -0
- package/dist/protocol/types.d.ts +45 -1
- package/dist/protocol/types.js +4 -0
- package/dist/session/session-manager.d.ts +18 -0
- package/dist/session/session-manager.js +87 -0
- package/dist/tool-system/builtin/cron.d.ts +11 -0
- package/dist/tool-system/builtin/cron.js +27 -2
- package/dist/tool-system/builtin/edit.js +5 -3
- package/dist/tool-system/builtin/write.js +5 -3
- package/dist/tool-system/context.d.ts +4 -1
- package/dist/tool-system/executor.js +1 -1
- package/dist/tool-system/path-policy.d.ts +11 -3
- package/dist/tool-system/path-policy.js +56 -36
- package/dist/types.d.ts +6 -0
- package/dist/workspace/canonical-key.d.ts +7 -0
- package/dist/workspace/canonical-key.js +39 -0
- package/dist/workspace/workspace-context.d.ts +26 -0
- package/dist/workspace/workspace-context.js +112 -0
- package/package.json +1 -1
|
@@ -66,10 +66,18 @@ export interface MobilePermissionModeSnapshotEntry {
|
|
|
66
66
|
mode: PermissionMode;
|
|
67
67
|
}
|
|
68
68
|
export interface MobileProjectMeta {
|
|
69
|
+
id?: string;
|
|
69
70
|
path: string;
|
|
70
71
|
name: string;
|
|
71
72
|
addedAt?: number;
|
|
72
73
|
pinned?: boolean;
|
|
74
|
+
roots?: Array<{
|
|
75
|
+
id: string;
|
|
76
|
+
path: string;
|
|
77
|
+
name: string;
|
|
78
|
+
role: "primary" | "secondary";
|
|
79
|
+
}>;
|
|
80
|
+
primaryRootId?: string;
|
|
73
81
|
}
|
|
74
82
|
export type MobileImageMime = "image/png" | "image/jpeg" | "image/webp" | "image/gif";
|
|
75
83
|
export interface MobileImageBase {
|
|
@@ -112,6 +120,11 @@ export type MobileClientEvent = {
|
|
|
112
120
|
sessionId: string;
|
|
113
121
|
} | {
|
|
114
122
|
type: "session.create";
|
|
123
|
+
/** Stable V2 project identity. null explicitly selects the no-repo workspace. */
|
|
124
|
+
projectId?: string | null;
|
|
125
|
+
/** Stable V2 root identity. Omit to use the project's current primary root. */
|
|
126
|
+
rootId?: string;
|
|
127
|
+
/** Legacy client compatibility only; Main validates it against mounted roots. */
|
|
115
128
|
cwd?: string | null;
|
|
116
129
|
name?: string;
|
|
117
130
|
} | {
|
|
@@ -230,6 +243,8 @@ export type MobileServerEvent = {
|
|
|
230
243
|
type: "chat.accepted";
|
|
231
244
|
sessionId?: string;
|
|
232
245
|
cwd?: string | null;
|
|
246
|
+
projectId?: string | null;
|
|
247
|
+
rootId?: string;
|
|
233
248
|
clientMessageId?: string;
|
|
234
249
|
attachments?: MobileAttachmentSummary[];
|
|
235
250
|
} | {
|
|
@@ -198,6 +198,7 @@ export declare class AgentServer {
|
|
|
198
198
|
*/
|
|
199
199
|
private bgAgentBusUnsubscribe;
|
|
200
200
|
private readonly wakeupsInFlight;
|
|
201
|
+
private readonly sessionWorkspaceRpc;
|
|
201
202
|
/**
|
|
202
203
|
* Effective session manager for the connection this server serves. Without
|
|
203
204
|
* a resolveIdentity hook this is exactly the host-supplied manager —
|
|
@@ -310,8 +311,6 @@ export declare class AgentServer {
|
|
|
310
311
|
*/
|
|
311
312
|
private handleBackgroundWork;
|
|
312
313
|
private handleCloseSession;
|
|
313
|
-
private handleReleaseWorkspace;
|
|
314
|
-
private handleSetWorkspace;
|
|
315
314
|
private handleConfigure;
|
|
316
315
|
/**
|
|
317
316
|
* Resolve the Engine that owns a session-scoped query (compact, archive_range,
|
package/dist/protocol/server.js
CHANGED
|
@@ -30,6 +30,7 @@ import { redactSecrets } from "../logging/sanitize-messages.js";
|
|
|
30
30
|
import { compileComposition } from "../composition/compiler.js";
|
|
31
31
|
import { attachProtocolContributions } from "../composition/protocol-attach.js";
|
|
32
32
|
import { computeEffectiveDisabledLists } from "../capability-control/disabled-lists.js";
|
|
33
|
+
import { SessionWorkspaceRpcHandlers } from "./session-workspace-rpc.js";
|
|
33
34
|
import { describePluginCommands, expandPluginCommandBody, scanPluginCommands, MAX_PLUGIN_COMMAND_ARGUMENT_CHARS, } from "../plugins/pluginCommandsLoader.js";
|
|
34
35
|
function isValidRunAttachment(value) {
|
|
35
36
|
if (!value || typeof value !== "object")
|
|
@@ -380,6 +381,7 @@ export class AgentServer {
|
|
|
380
381
|
*/
|
|
381
382
|
bgAgentBusUnsubscribe = null;
|
|
382
383
|
wakeupsInFlight = new Set();
|
|
384
|
+
sessionWorkspaceRpc;
|
|
383
385
|
/**
|
|
384
386
|
* Effective session manager for the connection this server serves. Without
|
|
385
387
|
* a resolveIdentity hook this is exactly the host-supplied manager —
|
|
@@ -425,6 +427,14 @@ export class AgentServer {
|
|
|
425
427
|
throw new Error("AgentServer: either chatManager or engine must be supplied");
|
|
426
428
|
}
|
|
427
429
|
this.transport = options.transport;
|
|
430
|
+
this.sessionWorkspaceRpc = new SessionWorkspaceRpcHandlers({
|
|
431
|
+
transport: this.transport,
|
|
432
|
+
getChatManager: () => this.chatManager,
|
|
433
|
+
getLegacyEngine: () => this.legacyEngine,
|
|
434
|
+
getLastSlice: (sessionId) => this.lastSliceBySession.get(sessionId),
|
|
435
|
+
rememberSessionSlice: (sessionId, slice) => this.rememberSessionSlice(sessionId, slice),
|
|
436
|
+
wireInteractiveSession: (session, sessionId) => this.wireInteractiveSession(session, sessionId),
|
|
437
|
+
});
|
|
428
438
|
// Protocol surface from the compiled composition. Each contributing
|
|
429
439
|
// module may attach one observer; the server calls them at every
|
|
430
440
|
// lifecycle hook point and isolates per-observer failures.
|
|
@@ -787,10 +797,16 @@ export class AgentServer {
|
|
|
787
797
|
await this.handleCloseSession(req);
|
|
788
798
|
break;
|
|
789
799
|
case Methods.ReleaseWorkspace:
|
|
790
|
-
this.
|
|
800
|
+
this.sessionWorkspaceRpc.release(req);
|
|
791
801
|
break;
|
|
792
802
|
case Methods.SetWorkspace:
|
|
793
|
-
this.
|
|
803
|
+
this.sessionWorkspaceRpc.set(req);
|
|
804
|
+
break;
|
|
805
|
+
case Methods.MigrateSessionMainRoot:
|
|
806
|
+
await this.sessionWorkspaceRpc.migrateMainRoot(req);
|
|
807
|
+
break;
|
|
808
|
+
case Methods.CompleteSessionMainRootMigration:
|
|
809
|
+
this.sessionWorkspaceRpc.completeMainRootMigration(req);
|
|
794
810
|
break;
|
|
795
811
|
case Methods.GoalExtend:
|
|
796
812
|
this.handleGoalExtend(req);
|
|
@@ -1153,6 +1169,7 @@ export class AgentServer {
|
|
|
1153
1169
|
}
|
|
1154
1170
|
const sessionConfig = {
|
|
1155
1171
|
cwd: params.cwd,
|
|
1172
|
+
workspaceContext: params.workspaceContext,
|
|
1156
1173
|
projectTrusted: params.projectTrusted,
|
|
1157
1174
|
preset: params.preset ??
|
|
1158
1175
|
(params.behaviorMode === ISOLATED_TASK_BEHAVIOR_MODE ? "general" : undefined),
|
|
@@ -1226,6 +1243,7 @@ export class AgentServer {
|
|
|
1226
1243
|
}
|
|
1227
1244
|
const run = session.enqueueTurn(params.task, {
|
|
1228
1245
|
cwd: params.cwd,
|
|
1246
|
+
workspaceContext: params.workspaceContext,
|
|
1229
1247
|
displayText: displayText || undefined,
|
|
1230
1248
|
injected: params.injected === true,
|
|
1231
1249
|
attachments: Array.isArray(params.attachments) ? params.attachments : undefined,
|
|
@@ -1358,6 +1376,7 @@ export class AgentServer {
|
|
|
1358
1376
|
});
|
|
1359
1377
|
const result = await this.legacyEngine.run(params.task, {
|
|
1360
1378
|
cwd: params.cwd,
|
|
1379
|
+
workspaceContext: params.workspaceContext,
|
|
1361
1380
|
sessionId: params.sessionId,
|
|
1362
1381
|
displayText: displayText || undefined,
|
|
1363
1382
|
injected: params.injected === true,
|
|
@@ -1944,55 +1963,6 @@ export class AgentServer {
|
|
|
1944
1963
|
backgroundJobRegistry.dropForSession(params.sessionId);
|
|
1945
1964
|
this.transport.send(createResponse(req.id, { ok: true }));
|
|
1946
1965
|
}
|
|
1947
|
-
// ─── ReleaseWorkspace ──────────────────────────────────────────
|
|
1948
|
-
handleReleaseWorkspace(req) {
|
|
1949
|
-
const params = (req.params ?? {});
|
|
1950
|
-
if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
|
|
1951
|
-
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
1952
|
-
return;
|
|
1953
|
-
}
|
|
1954
|
-
if (this.chatManager) {
|
|
1955
|
-
const session = this.chatManager.get(params.sessionId);
|
|
1956
|
-
if (!session) {
|
|
1957
|
-
this.transport.send(createResponse(req.id, { ok: true, workspace: null }));
|
|
1958
|
-
return;
|
|
1959
|
-
}
|
|
1960
|
-
const engine = session.engine;
|
|
1961
|
-
const workspace = engine.releaseSessionWorkspace?.(params.sessionId) ?? null;
|
|
1962
|
-
this.transport.send(createResponse(req.id, { ok: true, workspace }));
|
|
1963
|
-
return;
|
|
1964
|
-
}
|
|
1965
|
-
const engine = this.legacyEngine;
|
|
1966
|
-
const workspace = engine?.releaseSessionWorkspace?.(params.sessionId) ?? null;
|
|
1967
|
-
this.transport.send(createResponse(req.id, { ok: true, workspace }));
|
|
1968
|
-
}
|
|
1969
|
-
handleSetWorkspace(req) {
|
|
1970
|
-
const params = (req.params ?? {});
|
|
1971
|
-
if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
|
|
1972
|
-
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
1973
|
-
return;
|
|
1974
|
-
}
|
|
1975
|
-
if (!params.workspace ||
|
|
1976
|
-
typeof params.workspace !== "object" ||
|
|
1977
|
-
typeof params.workspace.root !== "string" ||
|
|
1978
|
-
params.workspace.root.length === 0 ||
|
|
1979
|
-
(params.workspace.kind !== "main" && params.workspace.kind !== "worktree")) {
|
|
1980
|
-
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "valid workspace is required"));
|
|
1981
|
-
return;
|
|
1982
|
-
}
|
|
1983
|
-
const engine = this.chatManager
|
|
1984
|
-
? this.chatManager.get(params.sessionId)?.engine
|
|
1985
|
-
: this.legacyEngine;
|
|
1986
|
-
if (!engine) {
|
|
1987
|
-
this.transport.send(createResponse(req.id, { ok: true, workspace: null }));
|
|
1988
|
-
return;
|
|
1989
|
-
}
|
|
1990
|
-
const workspace = engine.setSessionWorkspace?.(params.sessionId, params.workspace);
|
|
1991
|
-
this.transport.send(createResponse(req.id, {
|
|
1992
|
-
ok: workspace !== undefined && workspace !== null,
|
|
1993
|
-
workspace: workspace ?? null,
|
|
1994
|
-
}));
|
|
1995
|
-
}
|
|
1996
1966
|
// ─── Configure ──────────────────────────────────────────────────
|
|
1997
1967
|
handleConfigure(req) {
|
|
1998
1968
|
const params = (req.params ?? {});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Engine } from "../engine/engine.js";
|
|
2
|
+
import type { ChatSession } from "./chat-session.js";
|
|
3
|
+
import type { ChatSessionManager, EngineConfigSlice } from "./chat-session-manager.js";
|
|
4
|
+
import type { Transport } from "./transport.js";
|
|
5
|
+
import { type RpcRequest } from "./types.js";
|
|
6
|
+
interface SessionWorkspaceRpcDependencies {
|
|
7
|
+
transport: Transport;
|
|
8
|
+
getChatManager(): ChatSessionManager | null;
|
|
9
|
+
getLegacyEngine(): Engine | null;
|
|
10
|
+
getLastSlice(sessionId: string): EngineConfigSlice | undefined;
|
|
11
|
+
rememberSessionSlice(sessionId: string, slice: EngineConfigSlice): void;
|
|
12
|
+
wireInteractiveSession(session: ChatSession, sessionId: string): void;
|
|
13
|
+
}
|
|
14
|
+
/** Protocol boundary for Session workspace ownership and root migration. */
|
|
15
|
+
export declare class SessionWorkspaceRpcHandlers {
|
|
16
|
+
private readonly deps;
|
|
17
|
+
constructor(deps: SessionWorkspaceRpcDependencies);
|
|
18
|
+
release(req: RpcRequest): void;
|
|
19
|
+
set(req: RpcRequest): void;
|
|
20
|
+
migrateMainRoot(req: RpcRequest): Promise<void>;
|
|
21
|
+
completeMainRootMigration(req: RpcRequest): void;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { canonicalKey } from "../workspace/canonical-key.js";
|
|
2
|
+
import { validateWorkspaceContext, workspacePrimaryRoot, } from "../workspace/workspace-context.js";
|
|
3
|
+
import { createErrorResponse, createResponse, ErrorCodes, } from "./types.js";
|
|
4
|
+
/** Protocol boundary for Session workspace ownership and root migration. */
|
|
5
|
+
export class SessionWorkspaceRpcHandlers {
|
|
6
|
+
deps;
|
|
7
|
+
constructor(deps) {
|
|
8
|
+
this.deps = deps;
|
|
9
|
+
}
|
|
10
|
+
release(req) {
|
|
11
|
+
const params = (req.params ?? {});
|
|
12
|
+
if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
|
|
13
|
+
this.deps.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const manager = this.deps.getChatManager();
|
|
17
|
+
if (manager) {
|
|
18
|
+
const session = manager.get(params.sessionId);
|
|
19
|
+
if (!session) {
|
|
20
|
+
this.deps.transport.send(createResponse(req.id, { ok: true, workspace: null }));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const workspace = session.engine.releaseSessionWorkspace?.(params.sessionId) ?? null;
|
|
24
|
+
this.deps.transport.send(createResponse(req.id, { ok: true, workspace }));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const workspace = this.deps.getLegacyEngine()?.releaseSessionWorkspace?.(params.sessionId) ?? null;
|
|
28
|
+
this.deps.transport.send(createResponse(req.id, { ok: true, workspace }));
|
|
29
|
+
}
|
|
30
|
+
set(req) {
|
|
31
|
+
const params = (req.params ?? {});
|
|
32
|
+
if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
|
|
33
|
+
this.deps.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!params.workspace ||
|
|
37
|
+
typeof params.workspace !== "object" ||
|
|
38
|
+
typeof params.workspace.root !== "string" ||
|
|
39
|
+
params.workspace.root.length === 0 ||
|
|
40
|
+
(params.workspace.kind !== "main" && params.workspace.kind !== "worktree")) {
|
|
41
|
+
this.deps.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "valid workspace is required"));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const manager = this.deps.getChatManager();
|
|
45
|
+
const engine = manager ? manager.get(params.sessionId)?.engine : this.deps.getLegacyEngine();
|
|
46
|
+
if (!engine) {
|
|
47
|
+
this.deps.transport.send(createResponse(req.id, { ok: true, workspace: null }));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const workspace = engine.setSessionWorkspace?.(params.sessionId, params.workspace);
|
|
51
|
+
this.deps.transport.send(createResponse(req.id, {
|
|
52
|
+
ok: workspace !== undefined && workspace !== null,
|
|
53
|
+
workspace: workspace ?? null,
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
async migrateMainRoot(req) {
|
|
57
|
+
const params = (req.params ?? {});
|
|
58
|
+
if (!validMigrationParams(params)) {
|
|
59
|
+
this.deps.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "valid Session root migration is required"));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let workspaceContext;
|
|
63
|
+
try {
|
|
64
|
+
workspaceContext = validateWorkspaceContext(params.workspaceContext);
|
|
65
|
+
const primary = workspacePrimaryRoot(workspaceContext);
|
|
66
|
+
if (workspaceContext.projectId !== params.project.projectId ||
|
|
67
|
+
workspaceContext.sessionMainRootId !== params.project.mainRootId ||
|
|
68
|
+
canonicalKey(primary.path) !== canonicalKey(params.mainRoot)) {
|
|
69
|
+
throw new Error("migration target authority does not match the requested root");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
this.deps.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, error instanceof Error ? error.message : "valid target authority is required"));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const manager = this.deps.getChatManager();
|
|
77
|
+
let engine;
|
|
78
|
+
let residentSession;
|
|
79
|
+
if (manager) {
|
|
80
|
+
const ownership = manager.beginSessionMigration(params.sessionId, params.ownershipToken);
|
|
81
|
+
if (ownership.status === "not-resident") {
|
|
82
|
+
this.deps.transport.send(createResponse(req.id, {
|
|
83
|
+
status: "not-resident",
|
|
84
|
+
ownershipToken: ownership.ownershipToken,
|
|
85
|
+
}));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (ownership.status === "failed") {
|
|
89
|
+
this.deps.transport.send(createResponse(req.id, {
|
|
90
|
+
status: "failed",
|
|
91
|
+
error: ownership.error,
|
|
92
|
+
}));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
residentSession = ownership.session;
|
|
96
|
+
engine = residentSession.engine;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
engine = this.deps.getLegacyEngine();
|
|
100
|
+
}
|
|
101
|
+
if (!engine) {
|
|
102
|
+
this.deps.transport.send(createResponse(req.id, {
|
|
103
|
+
status: "failed",
|
|
104
|
+
error: "Session migration owner is unavailable",
|
|
105
|
+
}));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const workspace = residentSession
|
|
110
|
+
? await manager.migrateResidentSessionMainRoot(params.sessionId, {
|
|
111
|
+
project: params.project,
|
|
112
|
+
mainRoot: params.mainRoot,
|
|
113
|
+
workspaceContext,
|
|
114
|
+
projectTrusted: params.projectTrusted,
|
|
115
|
+
})
|
|
116
|
+
: engine.migrateSessionMainRoot?.(params.sessionId, params.project, params.mainRoot);
|
|
117
|
+
if (!workspace)
|
|
118
|
+
throw new Error(`Session ${params.sessionId} migration was not committed`);
|
|
119
|
+
if (residentSession) {
|
|
120
|
+
this.deps.rememberSessionSlice(params.sessionId, {
|
|
121
|
+
...(this.deps.getLastSlice(params.sessionId) ?? {}),
|
|
122
|
+
cwd: params.mainRoot,
|
|
123
|
+
workspaceContext,
|
|
124
|
+
projectTrusted: params.projectTrusted,
|
|
125
|
+
});
|
|
126
|
+
this.deps.wireInteractiveSession(residentSession, params.sessionId);
|
|
127
|
+
}
|
|
128
|
+
this.deps.transport.send(createResponse(req.id, {
|
|
129
|
+
status: "migrated",
|
|
130
|
+
workspace,
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
this.deps.transport.send(createResponse(req.id, {
|
|
135
|
+
status: "failed",
|
|
136
|
+
error: error instanceof Error ? error.message : String(error),
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
completeMainRootMigration(req) {
|
|
141
|
+
const params = (req.params ?? {});
|
|
142
|
+
if (typeof params.sessionId !== "string" ||
|
|
143
|
+
params.sessionId.length === 0 ||
|
|
144
|
+
typeof params.ownershipToken !== "string" ||
|
|
145
|
+
params.ownershipToken.length === 0 ||
|
|
146
|
+
params.ownershipToken.length > 128) {
|
|
147
|
+
this.deps.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "valid migration claim is required"));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!this.deps.getChatManager()?.completeSessionMigration(params.sessionId, params.ownershipToken)) {
|
|
151
|
+
this.deps.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "migration claim is not active"));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.deps.transport.send(createResponse(req.id, { released: true }));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function validMigrationParams(params) {
|
|
158
|
+
return (typeof params.sessionId === "string" &&
|
|
159
|
+
params.sessionId.length > 0 &&
|
|
160
|
+
Boolean(params.project) &&
|
|
161
|
+
typeof params.project?.projectId === "string" &&
|
|
162
|
+
params.project.projectId.length > 0 &&
|
|
163
|
+
typeof params.project.mainRootId === "string" &&
|
|
164
|
+
params.project.mainRootId.length > 0 &&
|
|
165
|
+
typeof params.mainRoot === "string" &&
|
|
166
|
+
params.mainRoot.length > 0 &&
|
|
167
|
+
typeof params.projectTrusted === "boolean" &&
|
|
168
|
+
typeof params.ownershipToken === "string" &&
|
|
169
|
+
params.ownershipToken.length > 0 &&
|
|
170
|
+
params.ownershipToken.length <= 128);
|
|
171
|
+
}
|
package/dist/protocol/types.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Server → Client: notifications (stream events, approval requests)
|
|
8
8
|
* Server → Client: responses (results of requests)
|
|
9
9
|
*/
|
|
10
|
-
import type { InputAttachmentMeta, LegacyPetWorkspaceOption, LegacyPetWorkDelegation, StreamEvent, TokenUsage, TerminalReason, ApprovalRequest, ApprovalResult, PermissionMode, SessionForkLineage, SessionKind, SessionWorkspace } from "../types.js";
|
|
10
|
+
import type { InputAttachmentMeta, LegacyPetWorkspaceOption, LegacyPetWorkDelegation, StreamEvent, TokenUsage, TerminalReason, ApprovalRequest, ApprovalResult, PermissionMode, SessionForkLineage, SessionKind, SessionProjectBinding, SessionWorkspace } from "../types.js";
|
|
11
11
|
import type { RunBehaviorMode } from "../engine/run-types.js";
|
|
12
12
|
import type { SessionMessageTarget } from "../session/session-message.js";
|
|
13
13
|
export type PendingApprovalKind = "tool_approval" | "ask_user" | "internal";
|
|
@@ -69,6 +69,12 @@ export type { InputAttachmentKind, InputAttachmentOrigin, InputAttachmentMeta }
|
|
|
69
69
|
export interface RunParams {
|
|
70
70
|
sessionId: string;
|
|
71
71
|
task: string;
|
|
72
|
+
/** Renderer/mobile project hint. Desktop Main resolves it to authoritative roots. */
|
|
73
|
+
projectId?: string;
|
|
74
|
+
/** Renderer/mobile root hint. Desktop Main resolves it within projectId. */
|
|
75
|
+
rootId?: string;
|
|
76
|
+
/** Trusted host-injected roots. Desktop Main strips any renderer/mobile supplied value. */
|
|
77
|
+
workspaceContext?: import("../workspace/workspace-context.js").WorkspaceContext;
|
|
72
78
|
/**
|
|
73
79
|
* Optional user-facing representation of `task`. The model still receives
|
|
74
80
|
* the full task, while session feeds and transcript replay show this text.
|
|
@@ -273,6 +279,40 @@ export interface SetWorkspaceParams {
|
|
|
273
279
|
sessionId: string;
|
|
274
280
|
workspace: SessionWorkspace;
|
|
275
281
|
}
|
|
282
|
+
/** Atomically rebind every main-root field through the live Session owner. */
|
|
283
|
+
export interface MigrateSessionMainRootParams {
|
|
284
|
+
sessionId: string;
|
|
285
|
+
project: SessionProjectBinding;
|
|
286
|
+
mainRoot: string;
|
|
287
|
+
/** Main-resolved target authority used to construct the replacement Engine. */
|
|
288
|
+
workspaceContext: import("../workspace/workspace-context.js").WorkspaceContext;
|
|
289
|
+
/** Trust decision for the target main root, resolved by the host trust store. */
|
|
290
|
+
projectTrusted: boolean;
|
|
291
|
+
/** Main-minted nonce used to fence and later release a non-resident handoff. */
|
|
292
|
+
ownershipToken: string;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Proven ownership result for a Session main-root migration.
|
|
296
|
+
*
|
|
297
|
+
* `not-resident` is the only result that authorizes a durable host fallback.
|
|
298
|
+
* The ownership token fences a concurrent getOrCreate until the host reports
|
|
299
|
+
* that its atomic disk commit (or failed attempt) has finished.
|
|
300
|
+
*/
|
|
301
|
+
export type MigrateSessionMainRootResult = {
|
|
302
|
+
status: "migrated";
|
|
303
|
+
workspace: SessionWorkspace;
|
|
304
|
+
} | {
|
|
305
|
+
status: "not-resident";
|
|
306
|
+
ownershipToken: string;
|
|
307
|
+
} | {
|
|
308
|
+
status: "failed";
|
|
309
|
+
error: string;
|
|
310
|
+
};
|
|
311
|
+
/** Release the non-resident ownership fence after Main's durable attempt. */
|
|
312
|
+
export interface CompleteSessionMainRootMigrationParams {
|
|
313
|
+
sessionId: string;
|
|
314
|
+
ownershipToken: string;
|
|
315
|
+
}
|
|
276
316
|
/** Inject context into a session transcript. */
|
|
277
317
|
export interface InjectParams {
|
|
278
318
|
sessionId: string;
|
|
@@ -500,6 +540,10 @@ export declare const Methods: {
|
|
|
500
540
|
readonly ReleaseWorkspace: "agent/releaseWorkspace";
|
|
501
541
|
/** Persist a workspace pointer through the live session owner. */
|
|
502
542
|
readonly SetWorkspace: "agent/setWorkspace";
|
|
543
|
+
/** Atomically migrate a Session's main-root authority through its live owner. */
|
|
544
|
+
readonly MigrateSessionMainRoot: "agent/migrateSessionMainRoot";
|
|
545
|
+
/** Release a non-resident migration fence after Main's durable attempt. */
|
|
546
|
+
readonly CompleteSessionMainRootMigration: "agent/completeSessionMainRootMigration";
|
|
503
547
|
/** Extend a running goal's turn/budget ceilings mid-run (TODO 3.1). */
|
|
504
548
|
readonly GoalExtend: "agent/goalExtend";
|
|
505
549
|
/** Edit or pause/resume a session's persisted goal. */
|
package/dist/protocol/types.js
CHANGED
|
@@ -46,6 +46,10 @@ export const Methods = {
|
|
|
46
46
|
ReleaseWorkspace: "agent/releaseWorkspace",
|
|
47
47
|
/** Persist a workspace pointer through the live session owner. */
|
|
48
48
|
SetWorkspace: "agent/setWorkspace",
|
|
49
|
+
/** Atomically migrate a Session's main-root authority through its live owner. */
|
|
50
|
+
MigrateSessionMainRoot: "agent/migrateSessionMainRoot",
|
|
51
|
+
/** Release a non-resident migration fence after Main's durable attempt. */
|
|
52
|
+
CompleteSessionMainRootMigration: "agent/completeSessionMainRootMigration",
|
|
49
53
|
/** Extend a running goal's turn/budget ceilings mid-run (TODO 3.1). */
|
|
50
54
|
GoalExtend: "agent/goalExtend",
|
|
51
55
|
/** Edit or pause/resume a session's persisted goal. */
|
|
@@ -118,6 +118,8 @@ export declare class SessionManager {
|
|
|
118
118
|
* fallback when a worktree is unavailable or is being released.
|
|
119
119
|
*/
|
|
120
120
|
readSessionMainRoot(sessionId: string): string | undefined;
|
|
121
|
+
/** Cheap durable project-binding read; absent for legacy/no-repo Sessions. */
|
|
122
|
+
readSessionProjectBinding(sessionId: string): import("../types.js").SessionProjectBinding | undefined;
|
|
121
123
|
/** Cheap durable classification read. Legacy sessions are ordinary work sessions. */
|
|
122
124
|
readSessionKind(sessionId: string): SessionKind | undefined;
|
|
123
125
|
/** Cheap durable digital-human binding read. */
|
|
@@ -154,6 +156,22 @@ export declare class SessionManager {
|
|
|
154
156
|
* is a safety pointer and resume breadcrumb.
|
|
155
157
|
*/
|
|
156
158
|
setSessionWorkspace(sessionId: string, workspace: SessionWorkspace): number;
|
|
159
|
+
/**
|
|
160
|
+
* Commit a Session main-root migration as one state.json replacement.
|
|
161
|
+
*
|
|
162
|
+
* `cwd`, the durable project binding, and the execution workspace are one
|
|
163
|
+
* consistency unit: exposing any subset would let a resumed run combine the
|
|
164
|
+
* old authority with a new path. Derived availability such as dir_missing is
|
|
165
|
+
* deliberately absent from this patch and is recomputed by the host.
|
|
166
|
+
*/
|
|
167
|
+
migrateSessionMainRoot(sessionId: string, project: import("../types.js").SessionProjectBinding, mainRoot: string): number;
|
|
168
|
+
/**
|
|
169
|
+
* Commit a host-owned offline migration only if the durable snapshot Main
|
|
170
|
+
* just revalidated is still current. Unlike the ordinary field-level writer,
|
|
171
|
+
* this must not retry onto a newer revision: a new writer means ownership is
|
|
172
|
+
* no longer provably unchanged, so the migration fails closed.
|
|
173
|
+
*/
|
|
174
|
+
migrateSessionMainRootIfRevision(sessionId: string, project: import("../types.js").SessionProjectBinding, mainRoot: string, expectedStateRevision: number | undefined): number;
|
|
157
175
|
/** Read the durable archival timestamp; undefined = not archived / unprovable. */
|
|
158
176
|
readSessionArchivedAt(sessionId: string): number | undefined;
|
|
159
177
|
/** Set (number) or clear (undefined) the durable archival marker. */
|
|
@@ -505,6 +505,29 @@ export class SessionManager {
|
|
|
505
505
|
return undefined;
|
|
506
506
|
}
|
|
507
507
|
}
|
|
508
|
+
/** Cheap durable project-binding read; absent for legacy/no-repo Sessions. */
|
|
509
|
+
readSessionProjectBinding(sessionId) {
|
|
510
|
+
try {
|
|
511
|
+
assertSafeSessionId(sessionId);
|
|
512
|
+
const processLocal = this.processLocalBundle(sessionId);
|
|
513
|
+
const state = processLocal
|
|
514
|
+
? processLocal.state
|
|
515
|
+
: sessionId.startsWith("qchat-")
|
|
516
|
+
? undefined
|
|
517
|
+
: JSON.parse(readFileSync(join(this.sessionsDir, sessionId, "state.json"), "utf-8"));
|
|
518
|
+
const project = state?.project;
|
|
519
|
+
return project &&
|
|
520
|
+
typeof project.projectId === "string" &&
|
|
521
|
+
project.projectId.length > 0 &&
|
|
522
|
+
typeof project.mainRootId === "string" &&
|
|
523
|
+
project.mainRootId.length > 0
|
|
524
|
+
? { projectId: project.projectId, mainRootId: project.mainRootId }
|
|
525
|
+
: undefined;
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
return undefined;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
508
531
|
/** Cheap durable classification read. Legacy sessions are ordinary work sessions. */
|
|
509
532
|
readSessionKind(sessionId) {
|
|
510
533
|
try {
|
|
@@ -686,6 +709,70 @@ export class SessionManager {
|
|
|
686
709
|
}
|
|
687
710
|
return this.updateSessionState(sessionId, { workspace });
|
|
688
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* Commit a Session main-root migration as one state.json replacement.
|
|
714
|
+
*
|
|
715
|
+
* `cwd`, the durable project binding, and the execution workspace are one
|
|
716
|
+
* consistency unit: exposing any subset would let a resumed run combine the
|
|
717
|
+
* old authority with a new path. Derived availability such as dir_missing is
|
|
718
|
+
* deliberately absent from this patch and is recomputed by the host.
|
|
719
|
+
*/
|
|
720
|
+
migrateSessionMainRoot(sessionId, project, mainRoot) {
|
|
721
|
+
if (!project ||
|
|
722
|
+
typeof project.projectId !== "string" ||
|
|
723
|
+
project.projectId.length === 0 ||
|
|
724
|
+
typeof project.mainRootId !== "string" ||
|
|
725
|
+
project.mainRootId.length === 0 ||
|
|
726
|
+
typeof mainRoot !== "string" ||
|
|
727
|
+
mainRoot.length === 0) {
|
|
728
|
+
throw new SessionError(`invalid main-root migration for ${sessionId}`);
|
|
729
|
+
}
|
|
730
|
+
return this.updateSessionState(sessionId, {
|
|
731
|
+
project: { ...project },
|
|
732
|
+
cwd: mainRoot,
|
|
733
|
+
workspace: { root: mainRoot, kind: "main" },
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Commit a host-owned offline migration only if the durable snapshot Main
|
|
738
|
+
* just revalidated is still current. Unlike the ordinary field-level writer,
|
|
739
|
+
* this must not retry onto a newer revision: a new writer means ownership is
|
|
740
|
+
* no longer provably unchanged, so the migration fails closed.
|
|
741
|
+
*/
|
|
742
|
+
migrateSessionMainRootIfRevision(sessionId, project, mainRoot, expectedStateRevision) {
|
|
743
|
+
assertSafeSessionId(sessionId);
|
|
744
|
+
if (!project ||
|
|
745
|
+
typeof project.projectId !== "string" ||
|
|
746
|
+
project.projectId.length === 0 ||
|
|
747
|
+
typeof project.mainRootId !== "string" ||
|
|
748
|
+
project.mainRootId.length === 0 ||
|
|
749
|
+
typeof mainRoot !== "string" ||
|
|
750
|
+
mainRoot.length === 0) {
|
|
751
|
+
throw new SessionError(`invalid main-root migration for ${sessionId}`);
|
|
752
|
+
}
|
|
753
|
+
const state = this.readPersistedState(sessionId);
|
|
754
|
+
if (state.stateRevision !== expectedStateRevision) {
|
|
755
|
+
throw new SessionError(`Session state revision conflict for ${sessionId}`);
|
|
756
|
+
}
|
|
757
|
+
Object.assign(state, {
|
|
758
|
+
project: { ...project },
|
|
759
|
+
cwd: mainRoot,
|
|
760
|
+
workspace: { root: mainRoot, kind: "main" },
|
|
761
|
+
});
|
|
762
|
+
const result = this.saveStateAttempt(state);
|
|
763
|
+
if (result.ok)
|
|
764
|
+
return state.stateRevision;
|
|
765
|
+
if (result.reason === "generation_conflict") {
|
|
766
|
+
throw new SessionError(`Session generation conflict for ${sessionId}`);
|
|
767
|
+
}
|
|
768
|
+
if (result.reason === "lock_conflict") {
|
|
769
|
+
throw new SessionError(`Session state lock contention for ${sessionId}`);
|
|
770
|
+
}
|
|
771
|
+
if (result.reason === "kind_conflict") {
|
|
772
|
+
throw new SessionError(`Session kind is immutable for ${sessionId}`);
|
|
773
|
+
}
|
|
774
|
+
throw new SessionError(`Session state revision conflict for ${sessionId}`);
|
|
775
|
+
}
|
|
689
776
|
/** Read the durable archival timestamp; undefined = not archived / unprovable. */
|
|
690
777
|
readSessionArchivedAt(sessionId) {
|
|
691
778
|
try {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { ToolDefinition } from "../../types.js";
|
|
5
5
|
import type { ToolContext } from "../context.js";
|
|
6
|
+
import type { CronJob, CreateJobOptions } from "../../automation/scheduler.js";
|
|
6
7
|
export { cronListToolDef } from "./cron-list.definition.js";
|
|
7
8
|
/** Sink notified after a cron job is created/deleted, so the host (Electron
|
|
8
9
|
* main) can reload+arm the scheduler that actually executes jobs. The worker
|
|
@@ -10,6 +11,16 @@ export { cronListToolDef } from "./cron-list.definition.js";
|
|
|
10
11
|
* host never learns about an AI-created job until the user opens the UI. */
|
|
11
12
|
type CronChangedSink = () => void;
|
|
12
13
|
export declare function setCronChangedSink(sink: CronChangedSink | null): void;
|
|
14
|
+
export interface CronCreateAuthorityInput extends CreateJobOptions {
|
|
15
|
+
name: string;
|
|
16
|
+
schedule: string;
|
|
17
|
+
prompt: string;
|
|
18
|
+
/** Current Engine Session, used by Desktop Main as authority even for standalone jobs. */
|
|
19
|
+
authoritySessionId?: string;
|
|
20
|
+
}
|
|
21
|
+
export type CronCreateAuthority = (input: CronCreateAuthorityInput) => Promise<CronJob>;
|
|
22
|
+
/** Desktop installs a Main-process delegate; other hosts keep the local scheduler path. */
|
|
23
|
+
export declare function setCronCreateAuthority(authority: CronCreateAuthority | null): void;
|
|
13
24
|
export declare const cronCreateToolDef: ToolDefinition;
|
|
14
25
|
export declare function cronCreateTool(args: Record<string, unknown>, context?: ToolContext): Promise<string>;
|
|
15
26
|
export declare const cronDeleteToolDef: ToolDefinition;
|