@co0ontty/wand 4.42.1 → 4.43.0-beta.ga1bd9c8
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 +4 -4
- package/dist/cli.js +2 -2
- package/dist/distribution-manager.d.ts +8 -0
- package/dist/distribution-manager.js +41 -2
- package/dist/missions.d.ts +4 -5
- package/dist/missions.js +28 -59
- package/dist/password-manager.d.ts +4 -0
- package/dist/password-manager.js +37 -0
- package/dist/process-manager.js +15 -7
- package/dist/provider-history-scanner.js +5 -0
- package/dist/server-file-routes.js +12 -6
- package/dist/server-mission-routes.js +2 -2
- package/dist/server-session-routes.d.ts +4 -11
- package/dist/server-session-routes.js +102 -77
- package/dist/server-update-routes.d.ts +1 -0
- package/dist/server-update-routes.js +3 -1
- package/dist/server-workspace-routes.js +17 -7
- package/dist/server.js +44 -13
- package/dist/session-transport.d.ts +2 -0
- package/dist/session-transport.js +5 -0
- package/dist/storage.d.ts +10 -0
- package/dist/storage.js +41 -6
- package/dist/structured-session-manager.js +3 -1
- package/dist/terminal-daemon-client.d.ts +20 -0
- package/dist/terminal-daemon-client.js +112 -7
- package/dist/types.d.ts +3 -1
- 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/dist/ws-broadcast.d.ts +3 -1
- package/dist/ws-broadcast.js +5 -2
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-08-
|
|
4
|
-
"version": "4.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "a1bd9c8e9124a3e7fe47bd5c1e270115e4828481",
|
|
3
|
+
"builtAt": "2026-08-22T02:42:33.618Z",
|
|
4
|
+
"version": "4.43.0-beta.ga1bd9c8",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
package/dist/cli.js
CHANGED
|
@@ -225,10 +225,10 @@ Commands:
|
|
|
225
225
|
|
|
226
226
|
Agent runtime:
|
|
227
227
|
wand session:list List sessions as JSON
|
|
228
|
+
wand inbox:list List mission inbox items as JSON
|
|
228
229
|
wand session:read <id> Read a complete session snapshot
|
|
229
230
|
wand session:send <id> <text>
|
|
230
231
|
wand session:wait <id> [--timeout 900]
|
|
231
|
-
wand inbox:list List global Agent Inbox items
|
|
232
232
|
wand mission:list List parallel tasks
|
|
233
233
|
wand mission:create --prompt <text> --cwd <path> --providers claude,codex
|
|
234
234
|
wand mission:diff <mission-id> <attempt-id>
|
|
@@ -262,6 +262,7 @@ async function runAgentCliCommand(command, args, configPath) {
|
|
|
262
262
|
return value;
|
|
263
263
|
};
|
|
264
264
|
switch (command) {
|
|
265
|
+
case "inbox:list": return output(await api.get("/api/inbox"));
|
|
265
266
|
case "session:list": return output(await api.get("/api/sessions"));
|
|
266
267
|
case "session:read": {
|
|
267
268
|
const id = required(args[1], "wand session:read <id>");
|
|
@@ -286,7 +287,6 @@ async function runAgentCliCommand(command, args, configPath) {
|
|
|
286
287
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
287
288
|
}
|
|
288
289
|
}
|
|
289
|
-
case "inbox:list": return output(await api.get("/api/inbox"));
|
|
290
290
|
case "mission:list": return output(await api.get("/api/missions"));
|
|
291
291
|
case "mission:create": {
|
|
292
292
|
const prompt = required(readFlagValue(args, "--prompt"), "wand mission:create --prompt <text> --cwd <path> --providers claude,codex");
|
|
@@ -16,6 +16,8 @@ export interface ResolvedDistributionAsset {
|
|
|
16
16
|
size: number;
|
|
17
17
|
source: "local" | "github";
|
|
18
18
|
releaseNotes?: string;
|
|
19
|
+
/** 本地分发文件的 SHA-256(hex);GitHub 来源不提供,客户端据此跳过校验。 */
|
|
20
|
+
sha256?: string;
|
|
19
21
|
}
|
|
20
22
|
export interface DistributionSettings {
|
|
21
23
|
androidApk: Record<string, unknown>;
|
|
@@ -45,6 +47,12 @@ export declare class DistributionManager {
|
|
|
45
47
|
resolveLatestDmg(): Promise<ResolvedDistributionAsset | null>;
|
|
46
48
|
resolveMacosDownload(): Promise<LocalDistributionAsset | null>;
|
|
47
49
|
getSettings(): Promise<DistributionSettings>;
|
|
50
|
+
private readonly sha256Cache;
|
|
51
|
+
/**
|
|
52
|
+
* 计算本地分发文件的 SHA-256(hex)。按 path + size + mtime 缓存:更新检查
|
|
53
|
+
* 可能被多个客户端频繁触发,避免每次都全量读盘。
|
|
54
|
+
*/
|
|
55
|
+
private computeLocalFileSha256;
|
|
48
56
|
private refreshConfig;
|
|
49
57
|
private resolveLocalAsset;
|
|
50
58
|
private readLocalAsset;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
1
3
|
import { mkdir, readdir, readFile, stat } from "node:fs/promises";
|
|
2
4
|
import path from "node:path";
|
|
3
5
|
import { compareApkInstallOrder, compareSemver, extractSemver } from "./version-utils.js";
|
|
@@ -48,10 +50,27 @@ export class DistributionManager {
|
|
|
48
50
|
source: "local",
|
|
49
51
|
} : null;
|
|
50
52
|
const github = githubApk ? { ...githubApk, source: "github" } : null;
|
|
53
|
+
let winner;
|
|
51
54
|
if (local && github) {
|
|
52
|
-
|
|
55
|
+
winner = compareApkInstallOrder(github.version, local.version) > 0 ? github : local;
|
|
53
56
|
}
|
|
54
|
-
|
|
57
|
+
else {
|
|
58
|
+
winner = local ?? github;
|
|
59
|
+
}
|
|
60
|
+
// 本地分发的 APK 附带 SHA-256,客户端下载后校验(GitHub 来源走正规 TLS,不提供)。
|
|
61
|
+
if (winner?.source === "local" && localApk) {
|
|
62
|
+
try {
|
|
63
|
+
const fileStat = await stat(localApk.filePath);
|
|
64
|
+
winner = {
|
|
65
|
+
...winner,
|
|
66
|
+
sha256: await this.computeLocalFileSha256(localApk.filePath, fileStat.size, fileStat.mtimeMs),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// 哈希计算失败不阻断更新检查;客户端拿到空 sha256 会跳过校验。
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return winner;
|
|
55
74
|
}
|
|
56
75
|
async resolveAndroidDownload(channel = "beta") {
|
|
57
76
|
await this.refreshConfig();
|
|
@@ -109,6 +128,26 @@ export class DistributionManager {
|
|
|
109
128
|
macosDmg: this.buildSettings("dmg", dmgDir, this.options.config.macos?.enabled === true, localDmg, githubDmg),
|
|
110
129
|
};
|
|
111
130
|
}
|
|
131
|
+
sha256Cache = new Map();
|
|
132
|
+
/**
|
|
133
|
+
* 计算本地分发文件的 SHA-256(hex)。按 path + size + mtime 缓存:更新检查
|
|
134
|
+
* 可能被多个客户端频繁触发,避免每次都全量读盘。
|
|
135
|
+
*/
|
|
136
|
+
async computeLocalFileSha256(filePath, size, mtimeMs) {
|
|
137
|
+
const key = `${size}:${mtimeMs}`;
|
|
138
|
+
const cached = this.sha256Cache.get(filePath);
|
|
139
|
+
if (cached && cached.key === key)
|
|
140
|
+
return cached.digest;
|
|
141
|
+
const digest = await new Promise((resolve, reject) => {
|
|
142
|
+
const hash = createHash("sha256");
|
|
143
|
+
const stream = createReadStream(filePath);
|
|
144
|
+
stream.on("error", reject);
|
|
145
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
146
|
+
stream.on("end", () => resolve(hash.digest("hex")));
|
|
147
|
+
});
|
|
148
|
+
this.sha256Cache.set(filePath, { key, digest });
|
|
149
|
+
return digest;
|
|
150
|
+
}
|
|
112
151
|
async refreshConfig() {
|
|
113
152
|
let raw;
|
|
114
153
|
try {
|
package/dist/missions.d.ts
CHANGED
|
@@ -4,8 +4,8 @@ import type { StructuredSessionManager } from "./structured-session-manager.js";
|
|
|
4
4
|
import type { WandStorage } from "./storage.js";
|
|
5
5
|
import type { ProcessEvent } from "./types.js";
|
|
6
6
|
/**
|
|
7
|
-
* Deep task-orchestration module. Callers create missions
|
|
8
|
-
*
|
|
7
|
+
* Deep task-orchestration module. Callers create missions and review diffs
|
|
8
|
+
* without coordinating session/worktree/storage details.
|
|
9
9
|
*/
|
|
10
10
|
export declare class Missions {
|
|
11
11
|
private readonly storage;
|
|
@@ -14,17 +14,16 @@ export declare class Missions {
|
|
|
14
14
|
constructor(storage: WandStorage, structured: StructuredSessionManager, sessions: SessionRegistry);
|
|
15
15
|
list(): MissionDetails[];
|
|
16
16
|
get(id: string): MissionDetails | null;
|
|
17
|
+
create(input: CreateMissionInput): MissionDetails;
|
|
18
|
+
ingest(event: ProcessEvent): void;
|
|
17
19
|
inbox(): AgentActivityItem[];
|
|
18
20
|
markInboxRead(sessionId?: string): void;
|
|
19
|
-
create(input: CreateMissionInput): MissionDetails;
|
|
20
|
-
ingest(event: ProcessEvent): AgentActivityItem | null;
|
|
21
21
|
diff(missionId: string, attemptId: string): MissionDiff;
|
|
22
22
|
addReviewComment(missionId: string, attemptId: string, input: CreateReviewCommentInput): MissionReviewComment;
|
|
23
23
|
sendReview(missionId: string, attemptId: string, commentIds?: string[]): MissionReviewComment[];
|
|
24
24
|
resolveReview(missionId: string, attemptId: string, commentIds: string[]): MissionReviewComment[];
|
|
25
25
|
archive(id: string): MissionDetails;
|
|
26
26
|
private dispatchAttempt;
|
|
27
|
-
private seedInbox;
|
|
28
27
|
private details;
|
|
29
28
|
private getIncludingArchived;
|
|
30
29
|
private requireAttempt;
|
package/dist/missions.js
CHANGED
|
@@ -94,8 +94,8 @@ function reviewPrompt(comments) {
|
|
|
94
94
|
].join("\n");
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
|
-
* Deep task-orchestration module. Callers create missions
|
|
98
|
-
*
|
|
97
|
+
* Deep task-orchestration module. Callers create missions and review diffs
|
|
98
|
+
* without coordinating session/worktree/storage details.
|
|
99
99
|
*/
|
|
100
100
|
export class Missions {
|
|
101
101
|
storage;
|
|
@@ -113,13 +113,6 @@ export class Missions {
|
|
|
113
113
|
const mission = this.storage.getMission(id);
|
|
114
114
|
return mission ? this.details(mission) : null;
|
|
115
115
|
}
|
|
116
|
-
inbox() {
|
|
117
|
-
this.seedInbox();
|
|
118
|
-
return this.storage.listAgentActivity();
|
|
119
|
-
}
|
|
120
|
-
markInboxRead(sessionId) {
|
|
121
|
-
this.storage.markAgentActivityRead(sessionId);
|
|
122
|
-
}
|
|
123
116
|
create(input) {
|
|
124
117
|
const prompt = input.prompt?.trim();
|
|
125
118
|
if (!prompt)
|
|
@@ -156,38 +149,41 @@ export class Missions {
|
|
|
156
149
|
}
|
|
157
150
|
ingest(event) {
|
|
158
151
|
if (!event.sessionId || event.sessionId === "__system__")
|
|
159
|
-
return
|
|
152
|
+
return;
|
|
160
153
|
const snapshot = this.sessions.getLatest(event.sessionId);
|
|
161
154
|
if (!snapshot)
|
|
162
|
-
return
|
|
155
|
+
return;
|
|
163
156
|
const attempt = this.storage.getMissionAttemptBySession(event.sessionId);
|
|
164
|
-
|
|
157
|
+
if (!attempt)
|
|
158
|
+
return;
|
|
165
159
|
const state = activityState(snapshot, event);
|
|
166
160
|
const updatedAt = nowIso();
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
161
|
+
this.storage.saveMissionAttempt({
|
|
162
|
+
...attempt,
|
|
163
|
+
state: state,
|
|
164
|
+
summary: sessionSummary(snapshot),
|
|
165
|
+
error: state === "failed" ? snapshot.structuredState?.lastError ?? "任务执行失败" : null,
|
|
166
|
+
updatedAt,
|
|
167
|
+
});
|
|
168
|
+
this.storage.upsertAgentActivity({
|
|
169
|
+
sessionId: event.sessionId,
|
|
170
|
+
missionId: attempt.missionId,
|
|
171
|
+
attemptId: attempt.id,
|
|
171
172
|
state,
|
|
172
|
-
title:
|
|
173
|
+
title: snapshot.title?.trim() || snapshot.summary?.trim() || firstPromptLine(this.storage.getMission(attempt.missionId)?.prompt ?? ""),
|
|
173
174
|
summary: sessionSummary(snapshot),
|
|
174
|
-
provider: snapshot.provider ??
|
|
175
|
-
cwd: snapshot.cwd
|
|
175
|
+
provider: snapshot.provider ?? attempt.provider,
|
|
176
|
+
cwd: snapshot.cwd,
|
|
176
177
|
updatedAt,
|
|
177
178
|
readAt: null,
|
|
178
|
-
};
|
|
179
|
-
this.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
updatedAt,
|
|
187
|
-
});
|
|
188
|
-
this.refreshMissionStatus(attempt.missionId);
|
|
189
|
-
}
|
|
190
|
-
return item;
|
|
179
|
+
});
|
|
180
|
+
this.refreshMissionStatus(attempt.missionId);
|
|
181
|
+
}
|
|
182
|
+
inbox() {
|
|
183
|
+
return this.storage.listAgentActivity();
|
|
184
|
+
}
|
|
185
|
+
markInboxRead(sessionId) {
|
|
186
|
+
this.storage.markAgentActivityRead(sessionId);
|
|
191
187
|
}
|
|
192
188
|
diff(missionId, attemptId) {
|
|
193
189
|
const attempt = this.requireAttempt(missionId, attemptId);
|
|
@@ -277,11 +273,6 @@ export class Missions {
|
|
|
277
273
|
updatedAt: nowIso(),
|
|
278
274
|
};
|
|
279
275
|
this.storage.saveMissionAttempt(attempt);
|
|
280
|
-
this.storage.upsertAgentActivity({
|
|
281
|
-
sessionId: session.id, missionId: mission.id, attemptId, state: "working",
|
|
282
|
-
title: mission.title, summary: `已分派给 ${provider}`, provider, cwd: session.cwd,
|
|
283
|
-
updatedAt: attempt.updatedAt, readAt: null,
|
|
284
|
-
});
|
|
285
276
|
const completion = this.structured.sendMessage(session.id, mission.prompt);
|
|
286
277
|
completion.catch((error) => {
|
|
287
278
|
console.error(`[Missions] Attempt ${attemptId} failed after dispatch:`, error);
|
|
@@ -296,28 +287,6 @@ export class Missions {
|
|
|
296
287
|
});
|
|
297
288
|
}
|
|
298
289
|
}
|
|
299
|
-
seedInbox() {
|
|
300
|
-
const existing = new Set(this.storage.listAgentActivity().map((item) => item.sessionId));
|
|
301
|
-
for (const snapshot of this.sessions.listSlim()) {
|
|
302
|
-
if (existing.has(snapshot.id))
|
|
303
|
-
continue;
|
|
304
|
-
const attempt = this.storage.getMissionAttemptBySession(snapshot.id);
|
|
305
|
-
const mission = attempt ? this.storage.getMission(attempt.missionId) : null;
|
|
306
|
-
const at = nowIso();
|
|
307
|
-
this.storage.upsertAgentActivity({
|
|
308
|
-
sessionId: snapshot.id,
|
|
309
|
-
missionId: attempt?.missionId ?? null,
|
|
310
|
-
attemptId: attempt?.id ?? null,
|
|
311
|
-
state: activityState(snapshot),
|
|
312
|
-
title: mission?.title || snapshot.title || `${snapshot.provider ?? "agent"} 会话`,
|
|
313
|
-
summary: sessionSummary(snapshot),
|
|
314
|
-
provider: snapshot.provider ?? snapshot.structuredState?.provider ?? null,
|
|
315
|
-
cwd: snapshot.cwd || null,
|
|
316
|
-
updatedAt: snapshot.endedAt || snapshot.startedAt || at,
|
|
317
|
-
readAt: at,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
290
|
details(mission) {
|
|
322
291
|
return {
|
|
323
292
|
...mission,
|
|
@@ -77,6 +77,10 @@ export interface PasswordGeneratorOptions {
|
|
|
77
77
|
digits?: boolean;
|
|
78
78
|
symbols?: boolean;
|
|
79
79
|
}
|
|
80
|
+
/** AES-256-GCM at-rest wrapper. Legacy plaintext values pass through on read. */
|
|
81
|
+
export declare function encryptVaultSecret(plaintext: string, secret: string): string;
|
|
82
|
+
export declare function decryptVaultSecret(value: string | undefined, secret: string | null): string | undefined;
|
|
83
|
+
export declare function isEncryptedVaultSecret(value: string | undefined): boolean;
|
|
80
84
|
export declare function generatePassword(options?: PasswordGeneratorOptions): string;
|
|
81
85
|
export declare function generateTotpCode(secret: string, timeMs?: number, digits?: number, period?: number): string;
|
|
82
86
|
export declare function decodeTotpSecret(secret: string): Buffer;
|
package/dist/password-manager.js
CHANGED
|
@@ -240,6 +240,43 @@ export function buildPasswordSecurityReport(items, now = Date.now()) {
|
|
|
240
240
|
issues: issues.sort((a, b) => issueRank(b.severity) - issueRank(a.severity)),
|
|
241
241
|
};
|
|
242
242
|
}
|
|
243
|
+
const VAULT_CIPHER_PREFIX = "enc:v1:";
|
|
244
|
+
function deriveVaultKey(secret) {
|
|
245
|
+
return crypto.createHash("sha256").update(secret, "utf8").digest();
|
|
246
|
+
}
|
|
247
|
+
/** AES-256-GCM at-rest wrapper. Legacy plaintext values pass through on read. */
|
|
248
|
+
export function encryptVaultSecret(plaintext, secret) {
|
|
249
|
+
if (!plaintext || !secret)
|
|
250
|
+
return plaintext;
|
|
251
|
+
if (plaintext.startsWith(VAULT_CIPHER_PREFIX))
|
|
252
|
+
return plaintext;
|
|
253
|
+
const iv = crypto.randomBytes(12);
|
|
254
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", deriveVaultKey(secret), iv);
|
|
255
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
256
|
+
return `${VAULT_CIPHER_PREFIX}${iv.toString("base64url")}.${cipher.getAuthTag().toString("base64url")}.${encrypted.toString("base64url")}`;
|
|
257
|
+
}
|
|
258
|
+
export function decryptVaultSecret(value, secret) {
|
|
259
|
+
if (!value)
|
|
260
|
+
return undefined;
|
|
261
|
+
if (!value.startsWith(VAULT_CIPHER_PREFIX))
|
|
262
|
+
return value;
|
|
263
|
+
if (!secret)
|
|
264
|
+
return undefined;
|
|
265
|
+
try {
|
|
266
|
+
const [ivB64, tagB64, dataB64] = value.slice(VAULT_CIPHER_PREFIX.length).split(".");
|
|
267
|
+
if (!ivB64 || !tagB64 || !dataB64)
|
|
268
|
+
return undefined;
|
|
269
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", deriveVaultKey(secret), Buffer.from(ivB64, "base64url"));
|
|
270
|
+
decipher.setAuthTag(Buffer.from(tagB64, "base64url"));
|
|
271
|
+
return Buffer.concat([decipher.update(Buffer.from(dataB64, "base64url")), decipher.final()]).toString("utf8");
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
export function isEncryptedVaultSecret(value) {
|
|
278
|
+
return typeof value === "string" && value.startsWith(VAULT_CIPHER_PREFIX);
|
|
279
|
+
}
|
|
243
280
|
export function generatePassword(options = {}) {
|
|
244
281
|
const length = clampInteger(options.length ?? 20, 8, 80);
|
|
245
282
|
const lower = "abcdefghijkmnopqrstuvwxyz";
|
package/dist/process-manager.js
CHANGED
|
@@ -369,7 +369,11 @@ function listOpenCodeSessionCandidates() {
|
|
|
369
369
|
let db = null;
|
|
370
370
|
try {
|
|
371
371
|
db = new DatabaseSync(dbPath, { readOnly: true });
|
|
372
|
-
const
|
|
372
|
+
const sessionColumns = db.prepare("PRAGMA table_info(session)").all();
|
|
373
|
+
const rootSessionFilter = sessionColumns.some((column) => column.name === "parent_id")
|
|
374
|
+
? "WHERE parent_id IS NULL"
|
|
375
|
+
: "";
|
|
376
|
+
const rows = db.prepare(`SELECT id, directory, time_created, time_updated FROM session ${rootSessionFilter} ORDER BY time_updated DESC LIMIT 500`).all();
|
|
373
377
|
return rows.flatMap((row) => {
|
|
374
378
|
if (typeof row.id !== "string" || typeof row.directory !== "string")
|
|
375
379
|
return [];
|
|
@@ -968,12 +972,16 @@ export class ProcessManager extends EventEmitter {
|
|
|
968
972
|
let priorMessages = [];
|
|
969
973
|
let inheritedSessionSource;
|
|
970
974
|
let inheritedAutomationId;
|
|
975
|
+
let inheritedWorkspaceId;
|
|
976
|
+
let inheritedWorkspaceTaskId;
|
|
971
977
|
if (opts?.reuseId) {
|
|
972
978
|
const oldRecord = this.sessions.get(id);
|
|
973
979
|
if (oldRecord) {
|
|
974
980
|
priorMessages = oldRecord.ptyBridge?.getMessages() ?? oldRecord.messages ?? [];
|
|
975
981
|
inheritedSessionSource = oldRecord.sessionSource;
|
|
976
982
|
inheritedAutomationId = oldRecord.automationId;
|
|
983
|
+
inheritedWorkspaceId = oldRecord.workspaceId;
|
|
984
|
+
inheritedWorkspaceTaskId = oldRecord.workspaceTaskId;
|
|
977
985
|
this.cleanupRecord(oldRecord);
|
|
978
986
|
this.sessions.delete(id);
|
|
979
987
|
}
|
|
@@ -982,6 +990,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
982
990
|
priorMessages = stored?.messages ?? [];
|
|
983
991
|
inheritedSessionSource = stored?.sessionSource;
|
|
984
992
|
inheritedAutomationId = stored?.automationId;
|
|
993
|
+
inheritedWorkspaceId = stored?.workspaceId;
|
|
994
|
+
inheritedWorkspaceTaskId = stored?.workspaceTaskId;
|
|
985
995
|
}
|
|
986
996
|
this.terminalHost.forget(id);
|
|
987
997
|
}
|
|
@@ -1032,8 +1042,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
1032
1042
|
id,
|
|
1033
1043
|
sessionSource: opts?.sessionSource ?? inheritedSessionSource ?? "interactive",
|
|
1034
1044
|
automationId: opts?.automationId ?? inheritedAutomationId,
|
|
1035
|
-
workspaceId: opts?.workspaceId,
|
|
1036
|
-
workspaceTaskId: opts?.workspaceTaskId,
|
|
1045
|
+
workspaceId: opts?.workspaceId ?? inheritedWorkspaceId,
|
|
1046
|
+
workspaceTaskId: opts?.workspaceTaskId ?? inheritedWorkspaceTaskId,
|
|
1037
1047
|
provider,
|
|
1038
1048
|
command,
|
|
1039
1049
|
cwd: resolvedCwd,
|
|
@@ -2352,10 +2362,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
2352
2362
|
const escapedModel = trimmedModel.replace(/'/g, "'\\''");
|
|
2353
2363
|
result += ` --model '${escapedModel}'`;
|
|
2354
2364
|
}
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
result += ` --variant '${variant.replace(/'/g, "'\\''")}'`;
|
|
2358
|
-
}
|
|
2365
|
+
// thinkingEffort → --variant 仅适用于 `opencode run`(结构化 runner);
|
|
2366
|
+
// 交互 TUI(裸 `opencode`)没有该选项,注入会导致 CLI 直接报错退出。
|
|
2359
2367
|
if ((mode === "managed" || mode === "full-access" || mode === "auto-edit") && !/--auto(?:\s|$)/.test(result)) {
|
|
2360
2368
|
result += " --auto";
|
|
2361
2369
|
}
|
|
@@ -434,6 +434,10 @@ export class ProviderHistoryScanner {
|
|
|
434
434
|
let database = null;
|
|
435
435
|
try {
|
|
436
436
|
database = new DatabaseSync(this.openCodeDatabasePath, { readOnly: true });
|
|
437
|
+
const sessionColumns = database.prepare("PRAGMA table_info(session)").all();
|
|
438
|
+
const rootSessionFilter = sessionColumns.some((column) => column.name === "parent_id")
|
|
439
|
+
? "WHERE session.parent_id IS NULL"
|
|
440
|
+
: "";
|
|
437
441
|
const rows = database.prepare(`
|
|
438
442
|
SELECT
|
|
439
443
|
session.id AS id,
|
|
@@ -445,6 +449,7 @@ export class ProviderHistoryScanner {
|
|
|
445
449
|
SUM(CASE WHEN json_extract(message.data, '$.role') = 'assistant' THEN 1 ELSE 0 END) AS assistant_count
|
|
446
450
|
FROM session
|
|
447
451
|
LEFT JOIN message ON message.session_id = session.id
|
|
452
|
+
${rootSessionFilter}
|
|
448
453
|
GROUP BY session.id
|
|
449
454
|
ORDER BY session.time_updated DESC
|
|
450
455
|
LIMIT 1000
|
|
@@ -7,7 +7,7 @@ import process from "node:process";
|
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
8
|
import { getErrorMessage } from "./error-utils.js";
|
|
9
9
|
import { asyncRoute } from "./express-async.js";
|
|
10
|
-
import { isBlockedFolderPath,
|
|
10
|
+
import { isBlockedFolderPath, normalizeFolderPath } from "./middleware/path-safety.js";
|
|
11
11
|
import { parseBoundedInteger } from "./request-limits.js";
|
|
12
12
|
const execAsync = promisify(exec);
|
|
13
13
|
const DIRECTORY_MAX_ITEMS = 200;
|
|
@@ -493,15 +493,21 @@ export function registerFileRoutes(app, deps) {
|
|
|
493
493
|
}));
|
|
494
494
|
app.get("/api/file-search", asyncRoute(async (req, res) => {
|
|
495
495
|
const query = typeof req.query.q === "string" ? req.query.q.trim().slice(0, 256) : "";
|
|
496
|
-
const cwd = typeof req.query.cwd === "string" ? req.query.cwd :
|
|
496
|
+
const cwd = typeof req.query.cwd === "string" ? req.query.cwd : defaultCwd;
|
|
497
497
|
const maxDepth = parseBoundedInteger(req.query.depth, 5, 0, 8);
|
|
498
498
|
const maxResults = parseBoundedInteger(req.query.limit, 50, 1, 200);
|
|
499
499
|
const ignoredDirectories = new Set([".git", "node_modules", ".next", "dist", "build", "coverage", ".wand-uploads"]);
|
|
500
500
|
const maxVisitedEntries = 20_000;
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
501
|
+
let resolvedCwd;
|
|
502
|
+
try {
|
|
503
|
+
resolvedCwd = normalizeFolderPath(cwd);
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
res.status(400).json({ error: "无效的搜索目录。" });
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (isBlockedFolderPath(resolvedCwd)) {
|
|
510
|
+
res.status(403).json({ error: "访问被拒绝:不能搜索系统目录。" });
|
|
505
511
|
return;
|
|
506
512
|
}
|
|
507
513
|
if (!query) {
|
|
@@ -9,8 +9,8 @@ export function registerMissionRoutes(app, missions) {
|
|
|
9
9
|
res.json({ items: missions.inbox() });
|
|
10
10
|
});
|
|
11
11
|
app.post("/api/inbox/read", (req, res) => {
|
|
12
|
-
const sessionId = typeof req.body?.sessionId === "string" ? req.body.sessionId :
|
|
13
|
-
missions.markInboxRead(sessionId);
|
|
12
|
+
const sessionId = typeof req.body?.sessionId === "string" ? req.body.sessionId.trim() : "";
|
|
13
|
+
missions.markInboxRead(sessionId || undefined);
|
|
14
14
|
res.json({ ok: true });
|
|
15
15
|
});
|
|
16
16
|
app.get("/api/missions", (_req, res) => {
|
|
@@ -37,13 +37,6 @@ export type SessionListPageEntry = {
|
|
|
37
37
|
key: string;
|
|
38
38
|
sortTimestamp: number;
|
|
39
39
|
session: ReturnType<typeof toSessionListItemDTO>;
|
|
40
|
-
} | {
|
|
41
|
-
type: "recoverable";
|
|
42
|
-
key: string;
|
|
43
|
-
sortTimestamp: number;
|
|
44
|
-
history: ProviderHistorySession & {
|
|
45
|
-
provider: "claude" | "codex" | "opencode" | "qoder";
|
|
46
|
-
};
|
|
47
40
|
};
|
|
48
41
|
export interface SessionListPage {
|
|
49
42
|
entries: SessionListPageEntry[];
|
|
@@ -60,9 +53,9 @@ export interface SessionDirectoryTreeResponse {
|
|
|
60
53
|
/** Full directory-tree revision, including custom workspace names. */
|
|
61
54
|
treeRevision: string;
|
|
62
55
|
}
|
|
63
|
-
export declare function buildSessionListEntries(sessions: SessionSnapshot[]
|
|
64
|
-
export declare function buildSessionListPage(sessions: SessionSnapshot[],
|
|
65
|
-
export declare function buildSessionDirectoryTree(sessions: SessionSnapshot[],
|
|
56
|
+
export declare function buildSessionListEntries(sessions: SessionSnapshot[]): SessionListPageEntry[];
|
|
57
|
+
export declare function buildSessionListPage(sessions: SessionSnapshot[], offset: number, limit: number): SessionListPage;
|
|
58
|
+
export declare function buildSessionDirectoryTree(sessions: SessionSnapshot[], customNames?: ReadonlyMap<string, string>): SessionDirectoryTreeResponse;
|
|
66
59
|
/**
|
|
67
60
|
* Provider history is scanned by ProcessManager, but structured sessions live
|
|
68
61
|
* in StructuredSessionManager. Annotate against the combined session list so a
|
|
@@ -71,5 +64,5 @@ export declare function buildSessionDirectoryTree(sessions: SessionSnapshot[], c
|
|
|
71
64
|
*/
|
|
72
65
|
export declare function markManagedProviderHistory<T extends ProviderHistorySession>(history: T[], sessions: SessionSnapshot[], provider: "claude" | "codex" | "opencode" | "qoder"): T[];
|
|
73
66
|
export declare function registerSessionRoutes(app: Express, processes: ProcessManager, structured: StructuredSessionManager, storage: WandStorage, defaultMode: ExecutionMode, config: WandConfig, sessions: SessionRegistry, onSessionCreated?: (cwd: string | undefined | null) => void): void;
|
|
74
|
-
export declare function registerClaudeHistoryRoutes(app: Express, processes: ProcessManager, _structured: StructuredSessionManager, storage: WandStorage,
|
|
67
|
+
export declare function registerClaudeHistoryRoutes(app: Express, processes: ProcessManager, _structured: StructuredSessionManager, storage: WandStorage, _sessionRegistry: SessionRegistry): void;
|
|
75
68
|
export {};
|