@co0ontty/wand 4.47.0 → 4.48.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/config.d.ts +1 -1
- package/dist/config.js +15 -0
- package/dist/mission-types.d.ts +4 -0
- package/dist/missions.js +15 -7
- package/dist/server-file-routes.js +4 -4
- package/dist/server-session-routes.js +2 -1
- package/dist/server-workspace-routes.js +110 -13
- package/dist/server.js +2 -0
- package/dist/storage.js +11 -3
- package/dist/structured-session-manager.d.ts +2 -2
- package/dist/structured-session-manager.js +5 -2
- package/dist/types.d.ts +2 -0
- package/dist/web-ui/content/scripts.js +143 -70
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/dist/web-ui/index.js +2 -2
- package/dist/web-ui/provider-identity.d.ts +1 -0
- package/dist/web-ui/provider-identity.js +7 -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.
|
|
2
|
+
"commit": "2c450db11f34504be1a3e73354679a0231a91c43",
|
|
3
|
+
"builtAt": "2026-08-23T13:37:45.889Z",
|
|
4
|
+
"version": "4.48.0",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { WandStorage } from "./storage.js";
|
|
|
7
7
|
* 升级路径:老 JSON 里仍存有这些字段时,首次启动会被搬到 DB(见 migrateLegacyPreferencesToDb),
|
|
8
8
|
* 然后下一次 saveConfig 写回 JSON 时它们会被剥离(见 stripPreferenceFields)。
|
|
9
9
|
*/
|
|
10
|
-
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
10
|
+
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
11
11
|
export type PreferenceKey = (typeof PREFERENCE_KEYS)[number];
|
|
12
12
|
export declare function isPreferenceKey(key: string): key is PreferenceKey;
|
|
13
13
|
export declare const defaultConfig: () => WandConfig;
|
package/dist/config.js
CHANGED
|
@@ -24,6 +24,7 @@ const DEFAULT_CONFIG_FILE = "config.json";
|
|
|
24
24
|
export const PREFERENCE_KEYS = [
|
|
25
25
|
"defaultProvider",
|
|
26
26
|
"defaultSessionKind",
|
|
27
|
+
"defaultTaskWorktree",
|
|
27
28
|
"defaultMode",
|
|
28
29
|
"defaultCwd",
|
|
29
30
|
"defaultModel",
|
|
@@ -56,6 +57,7 @@ export const defaultConfig = () => ({
|
|
|
56
57
|
password: "change-me",
|
|
57
58
|
defaultProvider: "claude",
|
|
58
59
|
defaultSessionKind: "structured",
|
|
60
|
+
defaultTaskWorktree: true,
|
|
59
61
|
// 非 root 启动时才有资格用 Claude 的 permission-bypass(root 会被 Claude CLI 拒绝),
|
|
60
62
|
// 所以这种环境下把默认执行模式抬到「托管」——开箱即得自动确认权限的全自主体验。
|
|
61
63
|
// root 启动则保守回落到「default」(托管在 root 下也只能降级成 acceptEdits)。
|
|
@@ -296,6 +298,11 @@ export function applyStoragePreferences(config, storage) {
|
|
|
296
298
|
if (v === "pty" || v === "structured")
|
|
297
299
|
config.defaultSessionKind = v;
|
|
298
300
|
}
|
|
301
|
+
if (storage.hasPreference(preferenceStorageKey("defaultTaskWorktree"))) {
|
|
302
|
+
const v = storage.getPreference(preferenceStorageKey("defaultTaskWorktree"), defaults.defaultTaskWorktree ?? true);
|
|
303
|
+
if (typeof v === "boolean")
|
|
304
|
+
config.defaultTaskWorktree = v;
|
|
305
|
+
}
|
|
299
306
|
if (storage.hasPreference(preferenceStorageKey("defaultMode"))) {
|
|
300
307
|
const v = storage.getPreference(preferenceStorageKey("defaultMode"), defaults.defaultMode);
|
|
301
308
|
if (isExecutionMode(v))
|
|
@@ -399,6 +406,13 @@ export function writePreferenceToStorage(config, storage, key, value, options =
|
|
|
399
406
|
config.defaultSessionKind = value;
|
|
400
407
|
break;
|
|
401
408
|
}
|
|
409
|
+
case "defaultTaskWorktree": {
|
|
410
|
+
if (typeof value !== "boolean")
|
|
411
|
+
throw new Error("defaultTaskWorktree 必须是布尔值。");
|
|
412
|
+
storage.setPreference(dbKey, value);
|
|
413
|
+
config.defaultTaskWorktree = value;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
402
416
|
case "defaultMode": {
|
|
403
417
|
if (!isExecutionMode(value))
|
|
404
418
|
throw new Error(`无效执行模式: ${value}`);
|
|
@@ -691,6 +705,7 @@ function mergeWithDefaults(input) {
|
|
|
691
705
|
cardDefaults: normalizeCardDefaults(input.cardDefaults),
|
|
692
706
|
defaultProvider: input.defaultProvider === "codex" || input.defaultProvider === "opencode" || input.defaultProvider === "grok" || input.defaultProvider === "qoder" || input.defaultProvider === "pi" ? input.defaultProvider : "claude",
|
|
693
707
|
defaultSessionKind: input.defaultSessionKind === "pty" ? "pty" : "structured",
|
|
708
|
+
defaultTaskWorktree: typeof input.defaultTaskWorktree === "boolean" ? input.defaultTaskWorktree : defaults.defaultTaskWorktree,
|
|
694
709
|
defaultModel: typeof input.defaultModel === "string" ? input.defaultModel.trim() : defaults.defaultModel,
|
|
695
710
|
defaultCodexModel: typeof input.defaultCodexModel === "string" ? input.defaultCodexModel.trim() : defaults.defaultCodexModel,
|
|
696
711
|
defaultOpenCodeModel: typeof input.defaultOpenCodeModel === "string" ? input.defaultOpenCodeModel.trim() : defaults.defaultOpenCodeModel,
|
package/dist/mission-types.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface Mission {
|
|
|
14
14
|
prompt: string;
|
|
15
15
|
cwd: string;
|
|
16
16
|
status: MissionStatus;
|
|
17
|
+
/** 可选关联的任务(workspace task):派发的 attempt 会话绑定该任务。 */
|
|
18
|
+
taskId?: string | null;
|
|
17
19
|
worktree: MissionWorktreeOptions;
|
|
18
20
|
createdAt: string;
|
|
19
21
|
updatedAt: string;
|
|
@@ -78,6 +80,8 @@ export interface CreateMissionInput {
|
|
|
78
80
|
title?: string;
|
|
79
81
|
cwd: string;
|
|
80
82
|
providers: SessionProvider[];
|
|
83
|
+
/** 关联到指定 workspace task;派发会话将绑定 workspaceTaskId。 */
|
|
84
|
+
taskId?: string;
|
|
81
85
|
baseRef?: string;
|
|
82
86
|
sharedDirectories?: string[];
|
|
83
87
|
copyPaths?: string[];
|
package/dist/missions.js
CHANGED
|
@@ -133,6 +133,7 @@ export class Missions {
|
|
|
133
133
|
prompt,
|
|
134
134
|
cwd,
|
|
135
135
|
status: "dispatching",
|
|
136
|
+
taskId: input.taskId?.trim() || null,
|
|
136
137
|
worktree: {
|
|
137
138
|
baseRef: input.baseRef?.trim() || undefined,
|
|
138
139
|
sharedDirectories: normalizeStringList(input.sharedDirectories, "sharedDirectories"),
|
|
@@ -253,13 +254,20 @@ export class Missions {
|
|
|
253
254
|
cwd: mission.cwd,
|
|
254
255
|
mode: "agent",
|
|
255
256
|
provider,
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
257
|
+
// 关联任务的派发直接落在任务目录(不再叠加一层隔离),
|
|
258
|
+
// 会话绑定 workspaceTaskId,出现在该任务下。
|
|
259
|
+
worktreeEnabled: mission.taskId ? false : true,
|
|
260
|
+
...(mission.taskId ? { workspaceTaskId: mission.taskId } : {}),
|
|
261
|
+
...(!mission.taskId
|
|
262
|
+
? {
|
|
263
|
+
worktreeSpec: {
|
|
264
|
+
baseRef: mission.worktree.baseRef,
|
|
265
|
+
taskName: `${mission.title}-${provider}`,
|
|
266
|
+
sharedDirectories: mission.worktree.sharedDirectories,
|
|
267
|
+
copyPaths: mission.worktree.copyPaths,
|
|
268
|
+
},
|
|
269
|
+
}
|
|
270
|
+
: {}),
|
|
263
271
|
sessionSource: "automation",
|
|
264
272
|
automationId: mission.id,
|
|
265
273
|
});
|
|
@@ -428,10 +428,10 @@ export function registerFileRoutes(app, deps) {
|
|
|
428
428
|
app.get("/api/quick-paths", asyncRoute(async (_req, res) => {
|
|
429
429
|
const home = process.env.HOME || process.env.USERPROFILE || "/home";
|
|
430
430
|
res.json([
|
|
431
|
-
{ path: "/tmp", name: "临时目录", icon: "
|
|
432
|
-
{ path: home, name: "主目录", icon: "
|
|
433
|
-
{ path: process.cwd(), name: "当前目录", icon: "
|
|
434
|
-
{ path: "/", name: "根目录", icon: "
|
|
431
|
+
{ path: "/tmp", name: "临时目录", icon: "folder" },
|
|
432
|
+
{ path: home, name: "主目录", icon: "home" },
|
|
433
|
+
{ path: process.cwd(), name: "当前目录", icon: "folder" },
|
|
434
|
+
{ path: "/", name: "根目录", icon: "folder" },
|
|
435
435
|
]);
|
|
436
436
|
}));
|
|
437
437
|
app.get("/api/recent-paths", (_req, res) => {
|
|
@@ -667,8 +667,9 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
667
667
|
res.status(400).json({ error: "下标无效。" });
|
|
668
668
|
return;
|
|
669
669
|
}
|
|
670
|
+
const expectedText = typeof req.body?.expectedText === "string" ? req.body.expectedText : undefined;
|
|
670
671
|
try {
|
|
671
|
-
const snapshot = structured.deleteQueuedMessage(req.params.id, index);
|
|
672
|
+
const snapshot = structured.deleteQueuedMessage(req.params.id, index, expectedText);
|
|
672
673
|
res.json(sessionResponseDTO(snapshot));
|
|
673
674
|
}
|
|
674
675
|
catch (error) {
|
|
@@ -267,6 +267,99 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
267
267
|
res.json({ ok: true, layout });
|
|
268
268
|
});
|
|
269
269
|
// ── 任务(Task = 命名 + 独立 worktree + 一组标签)──
|
|
270
|
+
// 目录组为一级容器的任务聚合列表,供侧栏「任务」视图一次拉全。
|
|
271
|
+
app.get("/api/tasks", (req, res) => {
|
|
272
|
+
// 查询参数:workspaceId 过滤单目录;limit 截断每目录任务数;
|
|
273
|
+
// maxSessions 截断每任务内嵌会话数(大数据量时控制响应体积)。
|
|
274
|
+
const workspaceFilter = typeof req.query.workspaceId === "string" ? req.query.workspaceId : "";
|
|
275
|
+
const parseBoundedCount = (raw) => {
|
|
276
|
+
const value = Number(raw);
|
|
277
|
+
return Number.isFinite(value) && value > 0 ? Math.min(Math.floor(value), 500) : null;
|
|
278
|
+
};
|
|
279
|
+
const taskLimit = parseBoundedCount(req.query.limit);
|
|
280
|
+
const sessionLimit = parseBoundedCount(req.query.maxSessions);
|
|
281
|
+
backfillSessionWorkspaces(storage);
|
|
282
|
+
const workspaces = storage.listWorkspaces();
|
|
283
|
+
const summarize = (session) => ({
|
|
284
|
+
id: session.id,
|
|
285
|
+
provider: session.provider,
|
|
286
|
+
sessionKind: session.sessionKind,
|
|
287
|
+
runner: session.runner,
|
|
288
|
+
title: resolveSessionDisplayTitle(session),
|
|
289
|
+
status: session.status,
|
|
290
|
+
cwd: session.cwd,
|
|
291
|
+
startedAt: session.startedAt,
|
|
292
|
+
});
|
|
293
|
+
const visibleWorkspaces = workspaceFilter
|
|
294
|
+
? workspaces.filter((workspace) => workspace.id === workspaceFilter)
|
|
295
|
+
: workspaces;
|
|
296
|
+
const groups = new Map();
|
|
297
|
+
for (const workspace of visibleWorkspaces) {
|
|
298
|
+
groups.set(workspace.id, {
|
|
299
|
+
workspaceId: workspace.id,
|
|
300
|
+
workspaceName: workspace.name,
|
|
301
|
+
workspaceCwd: workspace.cwd,
|
|
302
|
+
tasks: storage.listWorkspaceTasks(workspace.id)
|
|
303
|
+
.slice(0, taskLimit ?? undefined)
|
|
304
|
+
.map((task) => {
|
|
305
|
+
const allSessions = storage.listSessionsByWorkspaceTask(task.id);
|
|
306
|
+
const sessions = allSessions
|
|
307
|
+
.slice(0, sessionLimit ?? undefined)
|
|
308
|
+
.map((session) => ({
|
|
309
|
+
...summarize(session),
|
|
310
|
+
workspaceTaskId: task.id,
|
|
311
|
+
}));
|
|
312
|
+
return {
|
|
313
|
+
...task,
|
|
314
|
+
cwd: task.worktree?.path ?? workspace.cwd,
|
|
315
|
+
isolated: task.worktree !== null,
|
|
316
|
+
sessions,
|
|
317
|
+
totalSessions: allSessions.length,
|
|
318
|
+
};
|
|
319
|
+
}),
|
|
320
|
+
standaloneSessions: [],
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
const taskBoundSessionIds = new Set();
|
|
324
|
+
for (const workspace of workspaces) {
|
|
325
|
+
for (const task of storage.listWorkspaceTasks(workspace.id)) {
|
|
326
|
+
for (const session of storage.listSessionsByWorkspaceTask(task.id))
|
|
327
|
+
taskBoundSessionIds.add(session.id);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
for (const session of storage.loadSessions()) {
|
|
331
|
+
if (taskBoundSessionIds.has(session.id))
|
|
332
|
+
continue;
|
|
333
|
+
const direct = session.workspaceId ? groups.get(session.workspaceId) : undefined;
|
|
334
|
+
let group = direct && !direct.synthetic ? direct : undefined;
|
|
335
|
+
const resolved = session.cwd ? path.resolve(session.cwd) : "";
|
|
336
|
+
if (!group && resolved) {
|
|
337
|
+
group = [...groups.values()].find((candidate) => !candidate.synthetic && candidate.workspaceCwd === resolved);
|
|
338
|
+
}
|
|
339
|
+
// 过滤模式下不创建合成组:不属于目标目录的会话直接排除。
|
|
340
|
+
if (!group && resolved && !workspaceFilter) {
|
|
341
|
+
const id = `cwd:${resolved}`;
|
|
342
|
+
let synthetic = groups.get(id);
|
|
343
|
+
if (!synthetic) {
|
|
344
|
+
synthetic = {
|
|
345
|
+
workspaceId: id,
|
|
346
|
+
workspaceName: resolved.split("/").filter(Boolean).at(-1) || resolved,
|
|
347
|
+
workspaceCwd: resolved,
|
|
348
|
+
synthetic: true,
|
|
349
|
+
tasks: [],
|
|
350
|
+
standaloneSessions: [],
|
|
351
|
+
};
|
|
352
|
+
groups.set(id, synthetic);
|
|
353
|
+
}
|
|
354
|
+
group = synthetic;
|
|
355
|
+
}
|
|
356
|
+
// workspaceId 过滤时,不属于目标目录组的会话直接排除(含合成组)。
|
|
357
|
+
if (workspaceFilter && group?.workspaceId !== workspaceFilter)
|
|
358
|
+
continue;
|
|
359
|
+
group?.standaloneSessions.push(summarize(session));
|
|
360
|
+
}
|
|
361
|
+
res.json([...groups.values()]);
|
|
362
|
+
});
|
|
270
363
|
// 列出某工作空间下的任务
|
|
271
364
|
app.get("/api/workspaces/:id/tasks", (req, res) => {
|
|
272
365
|
const workspace = storage.getWorkspace(req.params.id);
|
|
@@ -352,7 +445,8 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
352
445
|
worktrees,
|
|
353
446
|
});
|
|
354
447
|
}));
|
|
355
|
-
// 新建任务:命名 +
|
|
448
|
+
// 新建任务:命名 + 可选独立 worktree(默认尝试创建,非 git 仓库时退化为直接用项目目录;
|
|
449
|
+
// 显式传 worktree:false 时跳过隔离,会话直接跑在项目目录)。
|
|
356
450
|
app.post("/api/workspaces/:id/tasks", asyncRoute(async (req, res) => {
|
|
357
451
|
const workspace = storage.getWorkspace(req.params.id);
|
|
358
452
|
if (!workspace) {
|
|
@@ -366,20 +460,23 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
366
460
|
return;
|
|
367
461
|
}
|
|
368
462
|
const baseRef = typeof body.baseRef === "string" && body.baseRef.trim() ? body.baseRef.trim() : undefined;
|
|
463
|
+
const wantWorktree = body.worktree !== false;
|
|
369
464
|
let worktree = null;
|
|
370
465
|
let worktreeError;
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
466
|
+
if (wantWorktree) {
|
|
467
|
+
try {
|
|
468
|
+
const setup = prepareSessionWorktree({
|
|
469
|
+
cwd: workspace.cwd,
|
|
470
|
+
// 用随机短 id 作分支后缀,避免同名任务撞分支。
|
|
471
|
+
sessionId: crypto.randomUUID(),
|
|
472
|
+
spec: { taskName: name, baseRef },
|
|
473
|
+
});
|
|
474
|
+
worktree = setup.worktree;
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
// 非 git 仓库 / 基线不存在:任务照常创建,但无 worktree 隔离。
|
|
478
|
+
worktreeError = getErrorMessage(error, "无法创建 worktree,将在项目目录直接运行。");
|
|
479
|
+
}
|
|
383
480
|
}
|
|
384
481
|
const task = storage.createWorkspaceTask({ workspaceId: workspace.id, name, worktree });
|
|
385
482
|
res.status(201).json({
|
package/dist/server.js
CHANGED
|
@@ -213,6 +213,7 @@ const CONNECTED_APP_PREFERENCE_KEYS = new Set([
|
|
|
213
213
|
"defaultThinkingEffort",
|
|
214
214
|
"defaultProvider",
|
|
215
215
|
"defaultSessionKind",
|
|
216
|
+
"defaultTaskWorktree",
|
|
216
217
|
]);
|
|
217
218
|
function requireAdminOrSessionPreferences(req, res, next) {
|
|
218
219
|
const principal = requestPrincipals.get(req);
|
|
@@ -763,6 +764,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
763
764
|
port: config.port,
|
|
764
765
|
defaultProvider: config.defaultProvider ?? "claude",
|
|
765
766
|
defaultSessionKind: config.defaultSessionKind ?? "structured",
|
|
767
|
+
defaultTaskWorktree: config.defaultTaskWorktree !== false,
|
|
766
768
|
defaultMode: config.defaultMode,
|
|
767
769
|
defaultCwd: config.defaultCwd,
|
|
768
770
|
defaultModel: defaultModels.claude,
|
package/dist/storage.js
CHANGED
|
@@ -568,6 +568,7 @@ const INIT_SQL = `
|
|
|
568
568
|
prompt TEXT NOT NULL,
|
|
569
569
|
cwd TEXT NOT NULL,
|
|
570
570
|
status TEXT NOT NULL,
|
|
571
|
+
task_id TEXT,
|
|
571
572
|
base_ref TEXT,
|
|
572
573
|
shared_directories TEXT NOT NULL DEFAULT '[]',
|
|
573
574
|
copy_paths TEXT NOT NULL DEFAULT '[]',
|
|
@@ -663,6 +664,12 @@ export function ensureDatabaseFile(dbPath) {
|
|
|
663
664
|
db.exec(INIT_SQL);
|
|
664
665
|
ensureAuthSessionSchema(db);
|
|
665
666
|
ensureCommandSessionSchema(db);
|
|
667
|
+
{
|
|
668
|
+
const missionColumns = db.prepare("PRAGMA table_info(missions)").all();
|
|
669
|
+
if (missionColumns.length > 0 && !missionColumns.some((column) => column.name === "task_id")) {
|
|
670
|
+
db.exec("ALTER TABLE missions ADD COLUMN task_id TEXT");
|
|
671
|
+
}
|
|
672
|
+
}
|
|
666
673
|
db.close();
|
|
667
674
|
chmodSync(dbPath, 0o600);
|
|
668
675
|
return created;
|
|
@@ -1147,13 +1154,13 @@ export class WandStorage {
|
|
|
1147
1154
|
// ============ Missions ============
|
|
1148
1155
|
saveMission(mission) {
|
|
1149
1156
|
this.db.prepare(`INSERT INTO missions (
|
|
1150
|
-
id, title, prompt, cwd, status, base_ref, shared_directories, copy_paths, created_at, updated_at
|
|
1151
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1157
|
+
id, title, prompt, cwd, status, base_ref, shared_directories, copy_paths, created_at, updated_at, task_id
|
|
1158
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1152
1159
|
ON CONFLICT(id) DO UPDATE SET
|
|
1153
1160
|
title = excluded.title, prompt = excluded.prompt, cwd = excluded.cwd,
|
|
1154
1161
|
status = excluded.status, base_ref = excluded.base_ref,
|
|
1155
1162
|
shared_directories = excluded.shared_directories, copy_paths = excluded.copy_paths,
|
|
1156
|
-
updated_at = excluded.updated_at`).run(mission.id, mission.title, mission.prompt, mission.cwd, mission.status, mission.worktree.baseRef ?? null, JSON.stringify(mission.worktree.sharedDirectories ?? []), JSON.stringify(mission.worktree.copyPaths ?? []), mission.createdAt, mission.updatedAt);
|
|
1163
|
+
updated_at = excluded.updated_at, task_id = excluded.task_id`).run(mission.id, mission.title, mission.prompt, mission.cwd, mission.status, mission.worktree.baseRef ?? null, JSON.stringify(mission.worktree.sharedDirectories ?? []), JSON.stringify(mission.worktree.copyPaths ?? []), mission.createdAt, mission.updatedAt, mission.taskId ?? null);
|
|
1157
1164
|
}
|
|
1158
1165
|
getMission(id) {
|
|
1159
1166
|
const row = this.db.prepare("SELECT * FROM missions WHERE id = ?").get(id);
|
|
@@ -1336,6 +1343,7 @@ function mapMissionRow(row) {
|
|
|
1336
1343
|
sharedDirectories: safeJsonParse(typeof row.shared_directories === "string" ? row.shared_directories : null) ?? [],
|
|
1337
1344
|
copyPaths: safeJsonParse(typeof row.copy_paths === "string" ? row.copy_paths : null) ?? [],
|
|
1338
1345
|
},
|
|
1346
|
+
taskId: typeof row.task_id === "string" ? row.task_id : null,
|
|
1339
1347
|
createdAt: String(row.created_at),
|
|
1340
1348
|
updatedAt: String(row.updated_at),
|
|
1341
1349
|
};
|
|
@@ -145,8 +145,8 @@ export declare class StructuredSessionManager {
|
|
|
145
145
|
* 本身,flushNext 在另一段时序里读 sessions.get(...) 当前快照,已经天然安全。
|
|
146
146
|
*/
|
|
147
147
|
reorderQueuedMessages(sessionId: string, order: number[]): SessionSnapshot;
|
|
148
|
-
/** Remove a single queued message
|
|
149
|
-
deleteQueuedMessage(sessionId: string, index: number): SessionSnapshot;
|
|
148
|
+
/** Remove a single queued message only while index and text still identify the same item. */
|
|
149
|
+
deleteQueuedMessage(sessionId: string, index: number, expectedText?: string): SessionSnapshot;
|
|
150
150
|
/**
|
|
151
151
|
* Remove one queued message by index before sending it. Keeping this operation
|
|
152
152
|
* on the server prevents clients from re-sending the text while the original
|
|
@@ -1255,13 +1255,16 @@ export class StructuredSessionManager {
|
|
|
1255
1255
|
this.emitStructuredSnapshot(updated);
|
|
1256
1256
|
return updated;
|
|
1257
1257
|
}
|
|
1258
|
-
/** Remove a single queued message
|
|
1259
|
-
deleteQueuedMessage(sessionId, index) {
|
|
1258
|
+
/** Remove a single queued message only while index and text still identify the same item. */
|
|
1259
|
+
deleteQueuedMessage(sessionId, index, expectedText) {
|
|
1260
1260
|
const session = this.requireSession(sessionId);
|
|
1261
1261
|
const queue = session.queuedMessages ?? [];
|
|
1262
1262
|
if (!Number.isInteger(index) || index < 0 || index >= queue.length) {
|
|
1263
1263
|
throw new Error("队列中没有该条消息(可能已被处理)。");
|
|
1264
1264
|
}
|
|
1265
|
+
if (expectedText !== undefined && queue[index] !== expectedText) {
|
|
1266
|
+
throw new Error("排队消息已变化,请按最新顺序重试。");
|
|
1267
|
+
}
|
|
1265
1268
|
const next = queue.slice(0, index).concat(queue.slice(index + 1));
|
|
1266
1269
|
const skills = session.queuedMessageSkills ?? [];
|
|
1267
1270
|
const updated = {
|
package/dist/types.d.ts
CHANGED
|
@@ -94,6 +94,8 @@ export interface WandConfig {
|
|
|
94
94
|
defaultProvider?: SessionProvider;
|
|
95
95
|
/** 新建会话时默认使用的承载类型。 */
|
|
96
96
|
defaultSessionKind?: SessionKind;
|
|
97
|
+
/** 新建任务时是否默认开启独立 worktree。 */
|
|
98
|
+
defaultTaskWorktree?: boolean;
|
|
97
99
|
defaultMode: ExecutionMode;
|
|
98
100
|
shell: string;
|
|
99
101
|
defaultCwd: string;
|