@cjhyy/code-shell-core 0.8.13 → 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 +8 -4
- 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 +108 -112
- 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 +7 -1
- 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 +4 -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/turn-loop.js +17 -0
- package/dist/engine/types.d.ts +2 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.extension.d.ts +1 -1
- package/dist/index.internal.d.ts +3 -2
- package/dist/index.internal.js +2 -0
- package/dist/index.js +2 -2
- package/dist/llm/client-base.d.ts +2 -1
- package/dist/llm/client-base.js +3 -1
- package/dist/llm/providers/anthropic.js +24 -25
- package/dist/llm/providers/openai.d.ts +4 -1
- package/dist/llm/providers/openai.js +76 -13
- package/dist/panel-apps/index.d.ts +1 -1
- package/dist/panel-apps/index.js +1 -1
- package/dist/panel-apps/installer.d.ts +29 -0
- package/dist/panel-apps/installer.js +124 -15
- package/dist/plugins/pluginAutomationTemplates.d.ts +2 -0
- package/dist/plugins/pluginAutomationTemplates.js +4 -0
- package/dist/plugins/pluginCatalog.d.ts +6 -0
- package/dist/plugins/pluginCatalog.js +7 -2
- package/dist/plugins/pluginContent.d.ts +1 -1
- package/dist/plugins/pluginContent.js +2 -10
- 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 +11 -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 +28 -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 +47 -1
- package/dist/protocol/types.js +4 -0
- package/dist/session/session-manager.d.ts +25 -0
- package/dist/session/session-manager.js +106 -6
- package/dist/session/transcript.d.ts +9 -0
- package/dist/session/transcript.js +81 -0
- package/dist/settings/manager.d.ts +12 -0
- package/dist/settings/manager.js +31 -0
- package/dist/tool-system/builtin/configure-model-connection.d.ts +36 -0
- package/dist/tool-system/builtin/configure-model-connection.js +396 -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-model-catalog.d.ts +4 -6
- package/dist/tool-system/builtin/edit-model-catalog.js +5 -8
- package/dist/tool-system/builtin/edit.js +5 -3
- package/dist/tool-system/builtin/index.js +14 -0
- package/dist/tool-system/builtin/install-capability.js +22 -8
- package/dist/tool-system/builtin/settings-changed.d.ts +9 -0
- package/dist/tool-system/builtin/settings-changed.js +18 -0
- package/dist/tool-system/builtin/write.js +5 -3
- package/dist/tool-system/context.d.ts +12 -3
- 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 +11 -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
|
@@ -34,6 +34,9 @@ export class ChatSessionManager {
|
|
|
34
34
|
closingSessions = new Map();
|
|
35
35
|
closedSessions = new Set();
|
|
36
36
|
sessionGeneration = new Map();
|
|
37
|
+
sessionSlices = new Map();
|
|
38
|
+
migrationClaims = new Map();
|
|
39
|
+
residentMigrations = new Map();
|
|
37
40
|
runtime;
|
|
38
41
|
/** Identity scope this manager serves ("local" unless injected). */
|
|
39
42
|
identity;
|
|
@@ -78,11 +81,133 @@ export class ChatSessionManager {
|
|
|
78
81
|
});
|
|
79
82
|
}
|
|
80
83
|
async getOrCreate(sessionId, slice) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
// A non-resident migration claim is a short ownership handoff to Main.
|
|
85
|
+
// Wait rather than fail the user's run: once Main atomically commits (or
|
|
86
|
+
// aborts) and releases the token, the Engine is created from current disk.
|
|
87
|
+
for (;;) {
|
|
88
|
+
const residentMigration = this.residentMigrations.get(sessionId);
|
|
89
|
+
if (residentMigration) {
|
|
90
|
+
await residentMigration.released;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const claim = this.migrationClaims.get(sessionId);
|
|
94
|
+
if (claim) {
|
|
95
|
+
await claim.released;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const closing = this.closingSessions.get(sessionId);
|
|
99
|
+
if (closing) {
|
|
100
|
+
await closing;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// No await separates the final claim check from getOrCreateNow. On this
|
|
104
|
+
// process's event loop, either this creates the resident owner first or
|
|
105
|
+
// beginSessionMigration installs the fence first; both cannot win.
|
|
106
|
+
return this.getOrCreateNow(sessionId, slice);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Prove whether this worker currently owns a resident Engine, or fence the
|
|
111
|
+
* Session so Main can perform one durable migration without a re-resume race.
|
|
112
|
+
*/
|
|
113
|
+
beginSessionMigration(sessionId, ownershipToken) {
|
|
114
|
+
if (this.residentMigrations.has(sessionId)) {
|
|
115
|
+
return { status: "failed", error: `Session ${sessionId} migration is already in progress` };
|
|
116
|
+
}
|
|
117
|
+
const resident = this.sessions.get(sessionId);
|
|
118
|
+
if (resident)
|
|
119
|
+
return { status: "resident", session: resident };
|
|
120
|
+
if (this.closingSessions.has(sessionId)) {
|
|
121
|
+
return { status: "failed", error: `Session ${sessionId} is closing` };
|
|
122
|
+
}
|
|
123
|
+
if (this.migrationClaims.has(sessionId)) {
|
|
124
|
+
return { status: "failed", error: `Session ${sessionId} migration is already in progress` };
|
|
125
|
+
}
|
|
126
|
+
let release;
|
|
127
|
+
const released = new Promise((resolve) => {
|
|
128
|
+
release = resolve;
|
|
129
|
+
});
|
|
130
|
+
this.migrationClaims.set(sessionId, { ownershipToken, released, release });
|
|
131
|
+
return { status: "not-resident", ownershipToken };
|
|
132
|
+
}
|
|
133
|
+
/** Release only the exact claim minted for this Session. */
|
|
134
|
+
completeSessionMigration(sessionId, ownershipToken) {
|
|
135
|
+
const claim = this.migrationClaims.get(sessionId);
|
|
136
|
+
if (!claim || claim.ownershipToken !== ownershipToken)
|
|
137
|
+
return false;
|
|
138
|
+
this.migrationClaims.delete(sessionId);
|
|
139
|
+
claim.release();
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Rebuild an idle resident Engine against a new authoritative main root.
|
|
144
|
+
*
|
|
145
|
+
* Transaction order is intentional:
|
|
146
|
+
* 1. construct and restore the candidate while durable state still points at
|
|
147
|
+
* the old owner (factory/restore failure is therefore a no-op),
|
|
148
|
+
* 2. atomically commit durable state through the old owning Engine,
|
|
149
|
+
* 3. synchronously swap the Engine inside the existing ChatSession,
|
|
150
|
+
* 4. dispose the unreachable old Engine before reporting success.
|
|
151
|
+
*
|
|
152
|
+
* No await occurs between the idle check, durable commit and owner swap.
|
|
153
|
+
*/
|
|
154
|
+
async migrateResidentSessionMainRoot(sessionId, target) {
|
|
155
|
+
const session = this.sessions.get(sessionId);
|
|
156
|
+
if (!session)
|
|
157
|
+
throw new Error(`Session ${sessionId} is not resident`);
|
|
158
|
+
if (session.isBusy() || session.queueDepth() > 0) {
|
|
159
|
+
throw new Error(`Session ${sessionId} is running or has queued turns`);
|
|
160
|
+
}
|
|
161
|
+
if (this.residentMigrations.has(sessionId) || this.migrationClaims.has(sessionId)) {
|
|
162
|
+
throw new Error(`Session ${sessionId} migration is already in progress`);
|
|
163
|
+
}
|
|
164
|
+
let release;
|
|
165
|
+
const released = new Promise((resolve) => {
|
|
166
|
+
release = resolve;
|
|
167
|
+
});
|
|
168
|
+
this.residentMigrations.set(sessionId, { released, release });
|
|
169
|
+
const previous = session.engine;
|
|
170
|
+
let candidate;
|
|
171
|
+
let committed = false;
|
|
172
|
+
try {
|
|
173
|
+
const previousSlice = this.sessionSlices.get(sessionId) ?? {};
|
|
174
|
+
const permissionMode = previous.getPermissionMode?.() ?? previousSlice.permissionMode;
|
|
175
|
+
const nextSlice = {
|
|
176
|
+
...previousSlice,
|
|
177
|
+
cwd: target.mainRoot,
|
|
178
|
+
workspaceContext: target.workspaceContext,
|
|
179
|
+
projectTrusted: target.projectTrusted,
|
|
180
|
+
...(permissionMode ? { permissionMode } : {}),
|
|
181
|
+
};
|
|
182
|
+
const built = this.factory(nextSlice);
|
|
183
|
+
if (built === previous) {
|
|
184
|
+
throw new Error(`Session ${sessionId} Engine factory reused the resident owner`);
|
|
185
|
+
}
|
|
186
|
+
candidate = built;
|
|
187
|
+
if (permissionMode && candidate.getPermissionMode?.() !== permissionMode) {
|
|
188
|
+
candidate.setPermissionMode?.(permissionMode);
|
|
189
|
+
}
|
|
190
|
+
const candidateGeneration = this.engineSessionManager(candidate)?.registerSessionGeneration(sessionId) ??
|
|
191
|
+
this.sessionGeneration.get(sessionId) ??
|
|
192
|
+
1;
|
|
193
|
+
candidate.restoreSessionModel?.(sessionId);
|
|
194
|
+
const workspace = previous.migrateSessionMainRoot(sessionId, target.project, target.mainRoot);
|
|
195
|
+
session.replaceEngine(previous, candidate);
|
|
196
|
+
this.sessionGeneration.set(sessionId, candidateGeneration);
|
|
197
|
+
this.sessionSlices.set(sessionId, nextSlice);
|
|
198
|
+
committed = true;
|
|
199
|
+
await this.disposeEngine(previous, sessionId);
|
|
200
|
+
return workspace;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (!committed && candidate)
|
|
204
|
+
await this.disposeEngine(candidate, sessionId);
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
this.residentMigrations.delete(sessionId);
|
|
209
|
+
release();
|
|
84
210
|
}
|
|
85
|
-
return this.getOrCreateNow(sessionId, slice);
|
|
86
211
|
}
|
|
87
212
|
/**
|
|
88
213
|
* Cold-resume a persisted session using its own cwd. The first detached
|
|
@@ -129,6 +254,7 @@ export class ChatSessionManager {
|
|
|
129
254
|
const sessionManager = this.engineSessionManager(engine);
|
|
130
255
|
const generation = sessionManager?.registerSessionGeneration(sessionId) ?? 1;
|
|
131
256
|
this.sessionGeneration.set(sessionId, generation);
|
|
257
|
+
this.sessionSlices.set(sessionId, { ...slice });
|
|
132
258
|
this.sessions.set(sessionId, session);
|
|
133
259
|
// A direct user run is an explicit resume/open and may clear the tombstone.
|
|
134
260
|
// Background wakeups must check isUnavailable() before reaching this path.
|
|
@@ -201,6 +327,10 @@ export class ChatSessionManager {
|
|
|
201
327
|
return this.closeSession(sessionId, true);
|
|
202
328
|
}
|
|
203
329
|
closeSession(sessionId, markClosed) {
|
|
330
|
+
const migration = this.residentMigrations.get(sessionId);
|
|
331
|
+
if (migration) {
|
|
332
|
+
return migration.released.then(() => this.closeSession(sessionId, markClosed));
|
|
333
|
+
}
|
|
204
334
|
const alreadyClosing = this.closingSessions.get(sessionId);
|
|
205
335
|
if (alreadyClosing)
|
|
206
336
|
return alreadyClosing;
|
|
@@ -237,6 +367,7 @@ export class ChatSessionManager {
|
|
|
237
367
|
else
|
|
238
368
|
this.closedSessions.delete(sessionId);
|
|
239
369
|
this.sessionGeneration.delete(sessionId);
|
|
370
|
+
this.sessionSlices.delete(sessionId);
|
|
240
371
|
};
|
|
241
372
|
if (!s.isBusy()) {
|
|
242
373
|
finishClose();
|
|
@@ -278,6 +409,9 @@ export class ChatSessionManager {
|
|
|
278
409
|
* immediately afterward (the TUI REPL) doesn't orphan detached dev servers.
|
|
279
410
|
*/
|
|
280
411
|
async closeAllAsync() {
|
|
412
|
+
for (const [sessionId, claim] of [...this.migrationClaims]) {
|
|
413
|
+
this.completeSessionMigration(sessionId, claim.ownershipToken);
|
|
414
|
+
}
|
|
281
415
|
await Promise.all([...this.sessions.keys()].map((id) => this.close(id)));
|
|
282
416
|
// App/worker shutdown — reap every background shell so a detached
|
|
283
417
|
// `npm run dev` doesn't outlive the process as an orphan holding a port
|
|
@@ -335,6 +469,35 @@ export class ChatSessionManager {
|
|
|
335
469
|
});
|
|
336
470
|
});
|
|
337
471
|
}
|
|
472
|
+
async disposeEngine(engine, sessionId) {
|
|
473
|
+
const dispose = engine.dispose;
|
|
474
|
+
if (typeof dispose === "function") {
|
|
475
|
+
try {
|
|
476
|
+
await dispose.call(engine);
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
logger.warn("chat_session.engine_dispose_failed", {
|
|
480
|
+
sessionId,
|
|
481
|
+
identity: this.identity,
|
|
482
|
+
error: error instanceof Error ? error.message : String(error),
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const mcpPool = this.runtime.mcpPool;
|
|
488
|
+
if (typeof mcpPool?.unregisterOwner !== "function")
|
|
489
|
+
return;
|
|
490
|
+
try {
|
|
491
|
+
await mcpPool.unregisterOwner(engine);
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
logger.warn("chat_session.mcp_owner_unregister_failed", {
|
|
495
|
+
sessionId,
|
|
496
|
+
identity: this.identity,
|
|
497
|
+
error: error instanceof Error ? error.message : String(error),
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
338
501
|
engineSessionManager(engine) {
|
|
339
502
|
const candidate = engine;
|
|
340
503
|
const manager = candidate.getSessionManager?.();
|
|
@@ -21,6 +21,7 @@ export interface TurnOpts {
|
|
|
21
21
|
/** Working directory override for this turn. If omitted, Engine uses its
|
|
22
22
|
* configured cwd. */
|
|
23
23
|
cwd?: string;
|
|
24
|
+
workspaceContext?: import("../workspace/workspace-context.js").WorkspaceContext;
|
|
24
25
|
onStream?: (event: StreamEvent) => void;
|
|
25
26
|
/** User-facing text persisted beside the full model-facing task. */
|
|
26
27
|
displayText?: string;
|
|
@@ -37,6 +38,8 @@ export interface TurnOpts {
|
|
|
37
38
|
archiveBeforeCurrentTurn?: {
|
|
38
39
|
fromClientMessageId?: string;
|
|
39
40
|
segmentId?: string;
|
|
41
|
+
/** Host-authored replacement for the archived span; skips the summarizer. */
|
|
42
|
+
summary?: string;
|
|
40
43
|
};
|
|
41
44
|
/** Structured input attachments for this turn. */
|
|
42
45
|
attachments?: InputAttachmentMeta[];
|
|
@@ -68,7 +71,7 @@ export interface TurnOpts {
|
|
|
68
71
|
*/
|
|
69
72
|
export declare class ChatSession {
|
|
70
73
|
readonly id: string;
|
|
71
|
-
|
|
74
|
+
private currentEngine;
|
|
72
75
|
/**
|
|
73
76
|
* Per-session approval callbacks and resolver-free metadata indexed by requestId.
|
|
74
77
|
* `readonly` guards the Map reference (preventing reassignment); the
|
|
@@ -109,6 +112,13 @@ export declare class ChatSession {
|
|
|
109
112
|
private settlePromise;
|
|
110
113
|
private resolveSettled;
|
|
111
114
|
constructor(opts: ChatSessionOptions);
|
|
115
|
+
get engine(): Engine;
|
|
116
|
+
/**
|
|
117
|
+
* Swap the Engine at an idle run boundary without replacing the ChatSession
|
|
118
|
+
* that owns the queue, stream callback, approvals, idle timestamp and id.
|
|
119
|
+
* The expected-owner check makes a stale migration fail closed.
|
|
120
|
+
*/
|
|
121
|
+
replaceEngine(expected: Engine, replacement: Engine): void;
|
|
112
122
|
enqueueTurn(task: string, opts: TurnOpts): Promise<EngineResult>;
|
|
113
123
|
/**
|
|
114
124
|
* Run session maintenance (for example context-package summarization) under
|
|
@@ -7,7 +7,7 @@ import { isSameGoalInstance } from "../goal/lifecycle.js";
|
|
|
7
7
|
*/
|
|
8
8
|
export class ChatSession {
|
|
9
9
|
id;
|
|
10
|
-
|
|
10
|
+
currentEngine;
|
|
11
11
|
/**
|
|
12
12
|
* Per-session approval callbacks and resolver-free metadata indexed by requestId.
|
|
13
13
|
* `readonly` guards the Map reference (preventing reassignment); the
|
|
@@ -49,9 +49,26 @@ export class ChatSession {
|
|
|
49
49
|
resolveSettled = null;
|
|
50
50
|
constructor(opts) {
|
|
51
51
|
this.id = opts.id;
|
|
52
|
-
this.
|
|
52
|
+
this.currentEngine = opts.engine;
|
|
53
53
|
this.defaultOnStream = opts.onStream;
|
|
54
54
|
}
|
|
55
|
+
get engine() {
|
|
56
|
+
return this.currentEngine;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Swap the Engine at an idle run boundary without replacing the ChatSession
|
|
60
|
+
* that owns the queue, stream callback, approvals, idle timestamp and id.
|
|
61
|
+
* The expected-owner check makes a stale migration fail closed.
|
|
62
|
+
*/
|
|
63
|
+
replaceEngine(expected, replacement) {
|
|
64
|
+
if (this.active || this.exclusiveOperation || this.queue.length > 0) {
|
|
65
|
+
throw new Error(`Session ${this.id} is running or has queued turns`);
|
|
66
|
+
}
|
|
67
|
+
if (this.currentEngine !== expected) {
|
|
68
|
+
throw new Error(`Session ${this.id} Engine ownership changed during migration`);
|
|
69
|
+
}
|
|
70
|
+
this.currentEngine = replacement;
|
|
71
|
+
}
|
|
55
72
|
enqueueTurn(task, opts) {
|
|
56
73
|
this.lastActivityAt = Date.now();
|
|
57
74
|
// The user (or a wakeup the guard already let through) is starting a turn —
|
|
@@ -283,6 +300,7 @@ export class ChatSession {
|
|
|
283
300
|
const onStream = next.opts.onStream ?? this.defaultOnStream;
|
|
284
301
|
const result = await this.engine.run(next.task, {
|
|
285
302
|
cwd: next.opts.cwd,
|
|
303
|
+
workspaceContext: next.opts.workspaceContext,
|
|
286
304
|
sessionId: this.id,
|
|
287
305
|
displayText: next.opts.displayText,
|
|
288
306
|
signal: this.controller.signal,
|
|
@@ -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")
|
|
@@ -85,6 +86,13 @@ function runInputError(params) {
|
|
|
85
86
|
return `archiveBeforeCurrentTurn.${field} must be a bounded non-empty string`;
|
|
86
87
|
}
|
|
87
88
|
}
|
|
89
|
+
if (archive.summary !== undefined &&
|
|
90
|
+
(typeof archive.summary !== "string" ||
|
|
91
|
+
archive.summary.trim().length === 0 ||
|
|
92
|
+
archive.summary.length > 4_000 ||
|
|
93
|
+
/[\u0000]/u.test(archive.summary))) {
|
|
94
|
+
return "archiveBeforeCurrentTurn.summary must be a non-empty string up to 4000 characters";
|
|
95
|
+
}
|
|
88
96
|
}
|
|
89
97
|
if (params.behaviorMode !== undefined &&
|
|
90
98
|
(typeof params.behaviorMode !== "string" || params.behaviorMode.length === 0)) {
|
|
@@ -373,6 +381,7 @@ export class AgentServer {
|
|
|
373
381
|
*/
|
|
374
382
|
bgAgentBusUnsubscribe = null;
|
|
375
383
|
wakeupsInFlight = new Set();
|
|
384
|
+
sessionWorkspaceRpc;
|
|
376
385
|
/**
|
|
377
386
|
* Effective session manager for the connection this server serves. Without
|
|
378
387
|
* a resolveIdentity hook this is exactly the host-supplied manager —
|
|
@@ -418,6 +427,14 @@ export class AgentServer {
|
|
|
418
427
|
throw new Error("AgentServer: either chatManager or engine must be supplied");
|
|
419
428
|
}
|
|
420
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
|
+
});
|
|
421
438
|
// Protocol surface from the compiled composition. Each contributing
|
|
422
439
|
// module may attach one observer; the server calls them at every
|
|
423
440
|
// lifecycle hook point and isolates per-observer failures.
|
|
@@ -780,10 +797,16 @@ export class AgentServer {
|
|
|
780
797
|
await this.handleCloseSession(req);
|
|
781
798
|
break;
|
|
782
799
|
case Methods.ReleaseWorkspace:
|
|
783
|
-
this.
|
|
800
|
+
this.sessionWorkspaceRpc.release(req);
|
|
784
801
|
break;
|
|
785
802
|
case Methods.SetWorkspace:
|
|
786
|
-
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);
|
|
787
810
|
break;
|
|
788
811
|
case Methods.GoalExtend:
|
|
789
812
|
this.handleGoalExtend(req);
|
|
@@ -1146,6 +1169,7 @@ export class AgentServer {
|
|
|
1146
1169
|
}
|
|
1147
1170
|
const sessionConfig = {
|
|
1148
1171
|
cwd: params.cwd,
|
|
1172
|
+
workspaceContext: params.workspaceContext,
|
|
1149
1173
|
projectTrusted: params.projectTrusted,
|
|
1150
1174
|
preset: params.preset ??
|
|
1151
1175
|
(params.behaviorMode === ISOLATED_TASK_BEHAVIOR_MODE ? "general" : undefined),
|
|
@@ -1219,6 +1243,7 @@ export class AgentServer {
|
|
|
1219
1243
|
}
|
|
1220
1244
|
const run = session.enqueueTurn(params.task, {
|
|
1221
1245
|
cwd: params.cwd,
|
|
1246
|
+
workspaceContext: params.workspaceContext,
|
|
1222
1247
|
displayText: displayText || undefined,
|
|
1223
1248
|
injected: params.injected === true,
|
|
1224
1249
|
attachments: Array.isArray(params.attachments) ? params.attachments : undefined,
|
|
@@ -1351,6 +1376,7 @@ export class AgentServer {
|
|
|
1351
1376
|
});
|
|
1352
1377
|
const result = await this.legacyEngine.run(params.task, {
|
|
1353
1378
|
cwd: params.cwd,
|
|
1379
|
+
workspaceContext: params.workspaceContext,
|
|
1354
1380
|
sessionId: params.sessionId,
|
|
1355
1381
|
displayText: displayText || undefined,
|
|
1356
1382
|
injected: params.injected === true,
|
|
@@ -1937,55 +1963,6 @@ export class AgentServer {
|
|
|
1937
1963
|
backgroundJobRegistry.dropForSession(params.sessionId);
|
|
1938
1964
|
this.transport.send(createResponse(req.id, { ok: true }));
|
|
1939
1965
|
}
|
|
1940
|
-
// ─── ReleaseWorkspace ──────────────────────────────────────────
|
|
1941
|
-
handleReleaseWorkspace(req) {
|
|
1942
|
-
const params = (req.params ?? {});
|
|
1943
|
-
if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
|
|
1944
|
-
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
1945
|
-
return;
|
|
1946
|
-
}
|
|
1947
|
-
if (this.chatManager) {
|
|
1948
|
-
const session = this.chatManager.get(params.sessionId);
|
|
1949
|
-
if (!session) {
|
|
1950
|
-
this.transport.send(createResponse(req.id, { ok: true, workspace: null }));
|
|
1951
|
-
return;
|
|
1952
|
-
}
|
|
1953
|
-
const engine = session.engine;
|
|
1954
|
-
const workspace = engine.releaseSessionWorkspace?.(params.sessionId) ?? null;
|
|
1955
|
-
this.transport.send(createResponse(req.id, { ok: true, workspace }));
|
|
1956
|
-
return;
|
|
1957
|
-
}
|
|
1958
|
-
const engine = this.legacyEngine;
|
|
1959
|
-
const workspace = engine?.releaseSessionWorkspace?.(params.sessionId) ?? null;
|
|
1960
|
-
this.transport.send(createResponse(req.id, { ok: true, workspace }));
|
|
1961
|
-
}
|
|
1962
|
-
handleSetWorkspace(req) {
|
|
1963
|
-
const params = (req.params ?? {});
|
|
1964
|
-
if (typeof params.sessionId !== "string" || params.sessionId.length === 0) {
|
|
1965
|
-
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "sessionId is required"));
|
|
1966
|
-
return;
|
|
1967
|
-
}
|
|
1968
|
-
if (!params.workspace ||
|
|
1969
|
-
typeof params.workspace !== "object" ||
|
|
1970
|
-
typeof params.workspace.root !== "string" ||
|
|
1971
|
-
params.workspace.root.length === 0 ||
|
|
1972
|
-
(params.workspace.kind !== "main" && params.workspace.kind !== "worktree")) {
|
|
1973
|
-
this.transport.send(createErrorResponse(req.id, ErrorCodes.InvalidParams, "valid workspace is required"));
|
|
1974
|
-
return;
|
|
1975
|
-
}
|
|
1976
|
-
const engine = this.chatManager
|
|
1977
|
-
? this.chatManager.get(params.sessionId)?.engine
|
|
1978
|
-
: this.legacyEngine;
|
|
1979
|
-
if (!engine) {
|
|
1980
|
-
this.transport.send(createResponse(req.id, { ok: true, workspace: null }));
|
|
1981
|
-
return;
|
|
1982
|
-
}
|
|
1983
|
-
const workspace = engine.setSessionWorkspace?.(params.sessionId, params.workspace);
|
|
1984
|
-
this.transport.send(createResponse(req.id, {
|
|
1985
|
-
ok: workspace !== undefined && workspace !== null,
|
|
1986
|
-
workspace: workspace ?? null,
|
|
1987
|
-
}));
|
|
1988
|
-
}
|
|
1989
1966
|
// ─── Configure ──────────────────────────────────────────────────
|
|
1990
1967
|
handleConfigure(req) {
|
|
1991
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 {};
|