@co0ontty/wand 4.42.1 → 4.43.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/build-info.json +3 -3
- package/dist/cli.js +0 -3
- package/dist/distribution-manager.d.ts +8 -0
- package/dist/distribution-manager.js +41 -2
- package/dist/missions.d.ts +4 -7
- package/dist/missions.js +12 -61
- package/dist/process-manager.js +15 -7
- package/dist/provider-history-scanner.js +5 -0
- package/dist/server-mission-routes.js +2 -4
- package/dist/server-session-routes.d.ts +4 -11
- package/dist/server-session-routes.js +41 -66
- package/dist/server-update-routes.d.ts +1 -0
- package/dist/server-update-routes.js +2 -0
- package/dist/server-workspace-routes.js +17 -7
- package/dist/server.js +7 -4
- package/dist/storage.d.ts +8 -0
- package/dist/storage.js +24 -1
- package/dist/terminal-daemon-client.d.ts +20 -0
- package/dist/terminal-daemon-client.js +112 -7
- package/dist/types.d.ts +2 -0
- package/dist/web-ui/content/scripts.js +72 -89
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/vendor/xterm/xterm.bundle.js +10 -10
- package/dist/web-ui/embedded-assets.d.ts +2 -2
- package/dist/web-ui/embedded-assets.js +5 -5
- package/dist/workspace-binding.d.ts +29 -0
- package/dist/workspace-binding.js +111 -0
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ interface ResolvedUpdateAsset {
|
|
|
16
16
|
size: number;
|
|
17
17
|
source: "local" | "github";
|
|
18
18
|
releaseNotes?: string;
|
|
19
|
+
sha256?: string;
|
|
19
20
|
}
|
|
20
21
|
export interface PublicUpdateRoutesDependencies {
|
|
21
22
|
resolveLatestApk(channel: "stable" | "beta"): Promise<ResolvedUpdateAsset | null>;
|
|
@@ -31,6 +31,8 @@ export function registerPublicUpdateRoutes(app, deps) {
|
|
|
31
31
|
source: latest.source,
|
|
32
32
|
channel,
|
|
33
33
|
releaseNotes: updateAvailable ? (latest.releaseNotes ?? null) : null,
|
|
34
|
+
// 本地分发时为 hex SHA-256,Android 客户端下载后校验;GitHub 来源为 null。
|
|
35
|
+
sha256: updateAvailable ? (latest.sha256 ?? null) : null,
|
|
34
36
|
});
|
|
35
37
|
}));
|
|
36
38
|
app.get("/android/download", asyncRoute(async (req, res) => {
|
|
@@ -5,6 +5,8 @@ import { asyncRoute } from "./express-async.js";
|
|
|
5
5
|
import { getErrorMessage } from "./error-utils.js";
|
|
6
6
|
import { expandHomePath } from "./middleware/path-safety.js";
|
|
7
7
|
import { checkSessionWorktreeMergeabilityAsync, cleanupWorktreeSync, prepareSessionWorktree, resolveWorktreeTargetBranchAsync, } from "./git-worktree.js";
|
|
8
|
+
import { resolveSessionDisplayTitle } from "./session-transport.js";
|
|
9
|
+
import { attachUnboundSessionsToWorkspace, backfillSessionWorkspaces, } from "./workspace-binding.js";
|
|
8
10
|
const PROVIDERS = new Set(["claude", "codex", "opencode", "grok", "qoder", "pi"]);
|
|
9
11
|
function parseDefaultProvider(value) {
|
|
10
12
|
return typeof value === "string" && PROVIDERS.has(value) ? value : undefined;
|
|
@@ -29,11 +31,13 @@ function deleteSessions(storage, sessions, sessionIds) {
|
|
|
29
31
|
storage.deleteSession(sessionId);
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
|
-
function
|
|
34
|
+
function workspaceWithCounts(storage, workspace, sessionCounts) {
|
|
33
35
|
const worktreeCount = storage.listWorkspaceTasks(workspace.id)
|
|
34
36
|
.filter((task) => task.worktree !== null)
|
|
35
37
|
.length;
|
|
36
|
-
|
|
38
|
+
const sessionCount = sessionCounts?.get(workspace.id)
|
|
39
|
+
?? storage.listSessionsByWorkspace(workspace.id).length;
|
|
40
|
+
return { ...workspace, worktreeCount, sessionCount };
|
|
37
41
|
}
|
|
38
42
|
// ── Layout validation / sanitization(前端 PUT 与测试共用)──
|
|
39
43
|
function sanitizePaneTab(value) {
|
|
@@ -148,7 +152,9 @@ export function sanitizeTaskLayout(value) {
|
|
|
148
152
|
export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
149
153
|
// 列出所有项目(按最近打开排序)
|
|
150
154
|
app.get("/api/workspaces", (_req, res) => {
|
|
151
|
-
|
|
155
|
+
backfillSessionWorkspaces(storage);
|
|
156
|
+
const sessionCounts = storage.countSessionsByWorkspace();
|
|
157
|
+
res.json(storage.listWorkspaces().map((workspace) => workspaceWithCounts(storage, workspace, sessionCounts)));
|
|
152
158
|
});
|
|
153
159
|
// 新建项目:名称 + 目录 + 默认 IDE,不启动会话
|
|
154
160
|
app.post("/api/workspaces", asyncRoute(async (req, res) => {
|
|
@@ -168,7 +174,8 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
168
174
|
}
|
|
169
175
|
const defaultProvider = parseDefaultProvider(body.defaultProvider);
|
|
170
176
|
const workspace = storage.createWorkspace({ name, cwd, defaultProvider });
|
|
171
|
-
|
|
177
|
+
const attached = attachUnboundSessionsToWorkspace(storage, workspace);
|
|
178
|
+
res.status(201).json({ ...workspace, worktreeCount: 0, sessionCount: attached });
|
|
172
179
|
}));
|
|
173
180
|
// 项目详情:meta + 会话 + 布局;访问即更新 lastOpenedAt
|
|
174
181
|
app.get("/api/workspaces/:id", (req, res) => {
|
|
@@ -179,8 +186,11 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
179
186
|
}
|
|
180
187
|
storage.touchWorkspace(workspace.id);
|
|
181
188
|
res.json({
|
|
182
|
-
...
|
|
183
|
-
sessions: storage.listSessionsByWorkspace(workspace.id)
|
|
189
|
+
...workspaceWithCounts(storage, workspace),
|
|
190
|
+
sessions: storage.listSessionsByWorkspace(workspace.id).map((session) => ({
|
|
191
|
+
...session,
|
|
192
|
+
title: resolveSessionDisplayTitle(session),
|
|
193
|
+
})),
|
|
184
194
|
});
|
|
185
195
|
});
|
|
186
196
|
// 改名 / 目录 / 默认 IDE
|
|
@@ -213,7 +223,7 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
213
223
|
}
|
|
214
224
|
storage.updateWorkspace(existing.id, patch);
|
|
215
225
|
const updated = storage.getWorkspace(existing.id);
|
|
216
|
-
res.json(updated ?
|
|
226
|
+
res.json(updated ? workspaceWithCounts(storage, updated) : null);
|
|
217
227
|
});
|
|
218
228
|
// 删除项目;cascade=true 连带删会话,否则仅解绑
|
|
219
229
|
app.delete("/api/workspaces/:id", (req, res) => {
|
package/dist/server.js
CHANGED
|
@@ -28,6 +28,7 @@ import { refreshProviderCliUpdateState, registerAdminUpdateRoutes, registerPubli
|
|
|
28
28
|
import { parseSessionCreationOrigin, registerClaudeHistoryRoutes, registerSessionRoutes } from "./server-session-routes.js";
|
|
29
29
|
import { registerWorkspaceRoutes } from "./server-workspace-routes.js";
|
|
30
30
|
import { resolveSessionCwd } from "./session-cwd.js";
|
|
31
|
+
import { resolveWorkspaceIdForNewSession } from "./workspace-binding.js";
|
|
31
32
|
import { getErrorMessage } from "./error-utils.js";
|
|
32
33
|
import { asyncRoute, jsonErrorHandler } from "./express-async.js";
|
|
33
34
|
import { checkPackageUpdateAsync, installPackageGloballyAsync, normalizeUpdateChannel, resolveGlobalWandCli, } from "./npm-update-utils.js";
|
|
@@ -1020,23 +1021,25 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1020
1021
|
: undefined;
|
|
1021
1022
|
const reqCols = typeof body.cols === "number" && Number.isFinite(body.cols) ? body.cols : undefined;
|
|
1022
1023
|
const reqRows = typeof body.rows === "number" && Number.isFinite(body.rows) ? body.rows : undefined;
|
|
1024
|
+
const sessionCwd = resolveSessionCwd(body.cwd, config.defaultCwd);
|
|
1025
|
+
const workspaceId = resolveWorkspaceIdForNewSession(storage, sessionCwd, body.workspaceId);
|
|
1023
1026
|
const snapshot = await (interactiveShell
|
|
1024
|
-
? processes.startShell(
|
|
1027
|
+
? processes.startShell(sessionCwd, body.mode ?? "default", {
|
|
1025
1028
|
worktreeEnabled: body.worktreeEnabled === true,
|
|
1026
1029
|
cols: reqCols,
|
|
1027
1030
|
rows: reqRows,
|
|
1028
|
-
workspaceId
|
|
1031
|
+
workspaceId,
|
|
1029
1032
|
workspaceTaskId: body.workspaceTaskId,
|
|
1030
1033
|
...origin,
|
|
1031
1034
|
})
|
|
1032
|
-
: processes.start(command,
|
|
1035
|
+
: processes.start(command, sessionCwd, body.mode ?? config.defaultMode, initialInput || undefined, {
|
|
1033
1036
|
worktreeEnabled: body.worktreeEnabled === true,
|
|
1034
1037
|
provider,
|
|
1035
1038
|
model: effectiveModel,
|
|
1036
1039
|
cols: reqCols,
|
|
1037
1040
|
rows: reqRows,
|
|
1038
1041
|
thinkingEffort: body.thinkingEffort ?? config.defaultThinkingEffort,
|
|
1039
|
-
workspaceId
|
|
1042
|
+
workspaceId,
|
|
1040
1043
|
workspaceTaskId: body.workspaceTaskId,
|
|
1041
1044
|
...origin,
|
|
1042
1045
|
}));
|
package/dist/storage.d.ts
CHANGED
|
@@ -60,6 +60,14 @@ export declare class WandStorage {
|
|
|
60
60
|
listSessionsByWorkspace(workspaceId: string): SessionSnapshot[];
|
|
61
61
|
/** 显式更新某会话的工作空间归属(用于创建时绑定)。 */
|
|
62
62
|
setSessionWorkspaceId(sessionId: string, workspaceId: string | null): void;
|
|
63
|
+
/** Lightweight count of persisted sessions grouped by workspace. */
|
|
64
|
+
countSessionsByWorkspace(): Map<string, number>;
|
|
65
|
+
/** Sessions not yet attached to a project. Omits messages/output. */
|
|
66
|
+
listUnboundSessionBindings(): Array<{
|
|
67
|
+
id: string;
|
|
68
|
+
cwd: string;
|
|
69
|
+
worktree: SessionSnapshot["worktree"];
|
|
70
|
+
}>;
|
|
63
71
|
listWorkspaceTasks(workspaceId: string): WorkspaceTask[];
|
|
64
72
|
getWorkspaceTask(id: string): WorkspaceTask | null;
|
|
65
73
|
createWorkspaceTask(input: {
|
package/dist/storage.js
CHANGED
|
@@ -861,6 +861,29 @@ export class WandStorage {
|
|
|
861
861
|
setSessionWorkspaceId(sessionId, workspaceId) {
|
|
862
862
|
this.db.prepare("UPDATE command_sessions SET workspace_id = ? WHERE id = ?").run(workspaceId, sessionId);
|
|
863
863
|
}
|
|
864
|
+
/** Lightweight count of persisted sessions grouped by workspace. */
|
|
865
|
+
countSessionsByWorkspace() {
|
|
866
|
+
const rows = this.db
|
|
867
|
+
.prepare(`SELECT workspace_id AS id, COUNT(*) AS n
|
|
868
|
+
FROM command_sessions
|
|
869
|
+
WHERE workspace_id IS NOT NULL AND workspace_id != ''
|
|
870
|
+
GROUP BY workspace_id`)
|
|
871
|
+
.all();
|
|
872
|
+
return new Map(rows.map((row) => [row.id, Number(row.n) || 0]));
|
|
873
|
+
}
|
|
874
|
+
/** Sessions not yet attached to a project. Omits messages/output. */
|
|
875
|
+
listUnboundSessionBindings() {
|
|
876
|
+
const rows = this.db
|
|
877
|
+
.prepare(`SELECT id, cwd, worktree_info
|
|
878
|
+
FROM command_sessions
|
|
879
|
+
WHERE workspace_id IS NULL OR workspace_id = ''`)
|
|
880
|
+
.all();
|
|
881
|
+
return rows.map((row) => ({
|
|
882
|
+
id: row.id,
|
|
883
|
+
cwd: row.cwd,
|
|
884
|
+
worktree: parseWorktreeInfo(row.worktree_info) ?? null,
|
|
885
|
+
}));
|
|
886
|
+
}
|
|
864
887
|
// ── Workspace tasks(任务 = 命名 + 独立 worktree + 一组标签)──
|
|
865
888
|
listWorkspaceTasks(workspaceId) {
|
|
866
889
|
const rows = this.db
|
|
@@ -1111,7 +1134,7 @@ export class WandStorage {
|
|
|
1111
1134
|
deleteExpiredAuthSessions(now) {
|
|
1112
1135
|
this.db.prepare("DELETE FROM auth_sessions WHERE expires_at < ?").run(now);
|
|
1113
1136
|
}
|
|
1114
|
-
// ============ Missions
|
|
1137
|
+
// ============ Missions ============
|
|
1115
1138
|
saveMission(mission) {
|
|
1116
1139
|
this.db.prepare(`INSERT INTO missions (
|
|
1117
1140
|
id, title, prompt, cwd, status, base_ref, shared_directories, copy_paths, created_at, updated_at
|
|
@@ -11,12 +11,32 @@ export declare class TerminalDaemonClient implements TerminalHost {
|
|
|
11
11
|
private readonly inventory;
|
|
12
12
|
private readonly handles;
|
|
13
13
|
private readonly pendingEvents;
|
|
14
|
+
private disposed;
|
|
15
|
+
private reconnectTimer;
|
|
16
|
+
private reconnectDelayMs;
|
|
17
|
+
private reconnectFailureLogged;
|
|
14
18
|
constructor(socketPath: string, token: string);
|
|
15
19
|
connect(): Promise<void>;
|
|
16
20
|
attach(sessionId: string, afterSeq?: number): TerminalAttachResult | null;
|
|
17
21
|
createOrAttach(request: TerminalSpawnRequest, afterSeq?: number): Promise<TerminalAttachResult>;
|
|
18
22
|
forget(sessionId: string): void;
|
|
19
23
|
disconnect(): void;
|
|
24
|
+
/**
|
|
25
|
+
* Socket teardown path. Without a reconnect, a daemon restart would leave
|
|
26
|
+
* every RemoteTerminalProcess silently dead while ProcessManager keeps the
|
|
27
|
+
* session status at "running" forever.
|
|
28
|
+
*/
|
|
29
|
+
private handleDisconnect;
|
|
30
|
+
private scheduleReconnect;
|
|
31
|
+
private tryReconnect;
|
|
32
|
+
/**
|
|
33
|
+
* Diff the pre-disconnect inventory against the daemon's fresh `list`.
|
|
34
|
+
* Sessions that vanished (daemon restart/forget) or exited while we were
|
|
35
|
+
* disconnected get a synthetic exit delivered to their existing handle so
|
|
36
|
+
* ProcessManager finalizes them; live sessions replay chunks missed during
|
|
37
|
+
* the gap.
|
|
38
|
+
*/
|
|
39
|
+
private reconcileAfterReconnect;
|
|
20
40
|
request(method: TerminalDaemonRequest["method"], params?: Record<string, unknown>): Promise<unknown>;
|
|
21
41
|
reportOperationError(sessionId: string, error: unknown): void;
|
|
22
42
|
private resultFromState;
|
|
@@ -5,6 +5,8 @@ import process from "node:process";
|
|
|
5
5
|
import { TERMINAL_DAEMON_PROTOCOL_VERSION, terminalDaemonPaths, } from "./terminal-daemon-protocol.js";
|
|
6
6
|
import { appendTerminalChunkWindow, InProcessTerminalHost, } from "./terminal-host.js";
|
|
7
7
|
import { appendWindow, PTY_OUTPUT_MAX_SIZE } from "./pty-text-utils.js";
|
|
8
|
+
const TERMINAL_DAEMON_RECONNECT_INITIAL_MS = 500;
|
|
9
|
+
const TERMINAL_DAEMON_RECONNECT_MAX_MS = 10_000;
|
|
8
10
|
class RemoteTerminalProcess {
|
|
9
11
|
sessionId;
|
|
10
12
|
incarnationId;
|
|
@@ -106,11 +108,17 @@ export class TerminalDaemonClient {
|
|
|
106
108
|
inventory = new Map();
|
|
107
109
|
handles = new Map();
|
|
108
110
|
pendingEvents = new Map();
|
|
111
|
+
disposed = false;
|
|
112
|
+
reconnectTimer = null;
|
|
113
|
+
reconnectDelayMs = TERMINAL_DAEMON_RECONNECT_INITIAL_MS;
|
|
114
|
+
reconnectFailureLogged = false;
|
|
109
115
|
constructor(socketPath, token) {
|
|
110
116
|
this.socketPath = socketPath;
|
|
111
117
|
this.token = token;
|
|
112
118
|
}
|
|
113
119
|
async connect() {
|
|
120
|
+
if (this.disposed)
|
|
121
|
+
throw new Error("Terminal daemon client disposed");
|
|
114
122
|
if (this.socket && !this.socket.destroyed)
|
|
115
123
|
return;
|
|
116
124
|
const socket = net.createConnection(this.socketPath);
|
|
@@ -127,13 +135,33 @@ export class TerminalDaemonClient {
|
|
|
127
135
|
socket.once("error", onError);
|
|
128
136
|
});
|
|
129
137
|
socket.on("data", (data) => this.consume(data.toString("utf8")));
|
|
130
|
-
socket.on("close", () => this.
|
|
131
|
-
socket.on("error", (error) =>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.
|
|
138
|
+
socket.on("close", () => this.handleDisconnect());
|
|
139
|
+
socket.on("error", (error) => {
|
|
140
|
+
this.rejectPending(error instanceof Error ? error : new Error(String(error)));
|
|
141
|
+
this.handleDisconnect();
|
|
142
|
+
});
|
|
143
|
+
try {
|
|
144
|
+
await this.request("hello");
|
|
145
|
+
const previous = new Map(this.inventory);
|
|
146
|
+
const sessions = await this.request("list");
|
|
147
|
+
this.inventory.clear();
|
|
148
|
+
for (const session of sessions)
|
|
149
|
+
this.inventory.set(session.sessionId, session);
|
|
150
|
+
if (previous.size > 0) {
|
|
151
|
+
this.reconcileAfterReconnect(previous);
|
|
152
|
+
process.stderr.write("[wand] Reconnected to terminal daemon; reconciled PTY inventory.\n");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (!this.disposed) {
|
|
157
|
+
try {
|
|
158
|
+
socket.destroy();
|
|
159
|
+
}
|
|
160
|
+
catch { /* best-effort cleanup */ }
|
|
161
|
+
this.handleDisconnect();
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
137
165
|
}
|
|
138
166
|
attach(sessionId, afterSeq = 0) {
|
|
139
167
|
const state = this.inventory.get(sessionId);
|
|
@@ -156,6 +184,11 @@ export class TerminalDaemonClient {
|
|
|
156
184
|
void this.request("forget", { sessionId }).catch((error) => this.reportOperationError(sessionId, error));
|
|
157
185
|
}
|
|
158
186
|
disconnect() {
|
|
187
|
+
this.disposed = true;
|
|
188
|
+
if (this.reconnectTimer) {
|
|
189
|
+
clearTimeout(this.reconnectTimer);
|
|
190
|
+
this.reconnectTimer = null;
|
|
191
|
+
}
|
|
159
192
|
const socket = this.socket;
|
|
160
193
|
this.socket = null;
|
|
161
194
|
if (socket && !socket.destroyed)
|
|
@@ -164,6 +197,78 @@ export class TerminalDaemonClient {
|
|
|
164
197
|
this.handles.clear();
|
|
165
198
|
this.pendingEvents.clear();
|
|
166
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Socket teardown path. Without a reconnect, a daemon restart would leave
|
|
202
|
+
* every RemoteTerminalProcess silently dead while ProcessManager keeps the
|
|
203
|
+
* session status at "running" forever.
|
|
204
|
+
*/
|
|
205
|
+
handleDisconnect() {
|
|
206
|
+
if (this.socket?.destroyed)
|
|
207
|
+
this.socket = null;
|
|
208
|
+
this.rejectPending(new Error("Terminal daemon disconnected"));
|
|
209
|
+
if (this.disposed)
|
|
210
|
+
return;
|
|
211
|
+
this.scheduleReconnect();
|
|
212
|
+
}
|
|
213
|
+
scheduleReconnect() {
|
|
214
|
+
if (this.disposed || this.reconnectTimer)
|
|
215
|
+
return;
|
|
216
|
+
this.reconnectTimer = setTimeout(() => {
|
|
217
|
+
this.reconnectTimer = null;
|
|
218
|
+
void this.tryReconnect();
|
|
219
|
+
}, this.reconnectDelayMs);
|
|
220
|
+
this.reconnectTimer.unref?.();
|
|
221
|
+
this.reconnectDelayMs = Math.min(this.reconnectDelayMs * 2, TERMINAL_DAEMON_RECONNECT_MAX_MS);
|
|
222
|
+
}
|
|
223
|
+
async tryReconnect() {
|
|
224
|
+
if (this.disposed)
|
|
225
|
+
return;
|
|
226
|
+
try {
|
|
227
|
+
await this.connect();
|
|
228
|
+
this.reconnectDelayMs = TERMINAL_DAEMON_RECONNECT_INITIAL_MS;
|
|
229
|
+
this.reconnectFailureLogged = false;
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
if (this.disposed)
|
|
233
|
+
return;
|
|
234
|
+
if (!this.reconnectFailureLogged) {
|
|
235
|
+
this.reconnectFailureLogged = true;
|
|
236
|
+
process.stderr.write(`[wand] Terminal daemon reconnect failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
237
|
+
}
|
|
238
|
+
this.scheduleReconnect();
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Diff the pre-disconnect inventory against the daemon's fresh `list`.
|
|
243
|
+
* Sessions that vanished (daemon restart/forget) or exited while we were
|
|
244
|
+
* disconnected get a synthetic exit delivered to their existing handle so
|
|
245
|
+
* ProcessManager finalizes them; live sessions replay chunks missed during
|
|
246
|
+
* the gap.
|
|
247
|
+
*/
|
|
248
|
+
reconcileAfterReconnect(previous) {
|
|
249
|
+
for (const [sessionId, oldState] of previous) {
|
|
250
|
+
const current = this.inventory.get(sessionId);
|
|
251
|
+
const handle = this.handles.get(sessionId);
|
|
252
|
+
const handleMatches = !!handle && handle.incarnationId === oldState.incarnationId;
|
|
253
|
+
const stillRunning = !!current
|
|
254
|
+
&& current.incarnationId === oldState.incarnationId
|
|
255
|
+
&& current.status === "running";
|
|
256
|
+
if (stillRunning && handleMatches) {
|
|
257
|
+
for (const chunk of current.chunks) {
|
|
258
|
+
if (chunk.seq > oldState.seq)
|
|
259
|
+
handle.acceptData(chunk);
|
|
260
|
+
}
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
const exitedOnDaemon = !!current
|
|
264
|
+
&& current.incarnationId === oldState.incarnationId
|
|
265
|
+
&& current.status === "exited";
|
|
266
|
+
const exitCode = exitedOnDaemon ? current.exitCode ?? -1 : -1;
|
|
267
|
+
this.handles.delete(sessionId);
|
|
268
|
+
if (handleMatches)
|
|
269
|
+
handle.acceptExit({ exitCode });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
167
272
|
async request(method, params) {
|
|
168
273
|
const socket = this.socket;
|
|
169
274
|
if (!socket || socket.destroyed || !socket.writable)
|
package/dist/types.d.ts
CHANGED
|
@@ -344,6 +344,8 @@ export interface InputRequest {
|
|
|
344
344
|
* and continue receiving progress through the existing event stream.
|
|
345
345
|
*/
|
|
346
346
|
respondImmediately?: boolean;
|
|
347
|
+
/** PTY input can request a small acknowledgement instead of a full session detail payload. */
|
|
348
|
+
responseMode?: "snapshot" | "accepted";
|
|
347
349
|
/** Current UI view: "chat" or "terminal". Chat view uses PTY-derived structured messages. */
|
|
348
350
|
view?: "chat" | "terminal";
|
|
349
351
|
autonomyPolicy?: AutonomyPolicy;
|