@co0ontty/wand 4.71.2 → 4.72.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/git-quick-commit.d.ts +12 -2
- package/dist/git-quick-commit.js +80 -12
- package/dist/iteration-log.d.ts +133 -0
- package/dist/iteration-log.js +293 -0
- package/dist/milestone-scope.d.ts +19 -0
- package/dist/milestone-scope.js +22 -0
- package/dist/missions.js +14 -2
- package/dist/process-manager.js +7 -2
- package/dist/server-session-routes.js +95 -2
- package/dist/server-task-routes.js +39 -11
- package/dist/server-workspace-routes.js +23 -6
- package/dist/session-topic.d.ts +4 -0
- package/dist/session-topic.js +20 -1
- package/dist/storage.d.ts +43 -2
- package/dist/storage.js +201 -12
- package/dist/structured-session-manager.js +7 -2
- package/dist/task-types.d.ts +47 -1
- package/dist/task-types.js +6 -0
- package/dist/web-ui/content/scripts.js +260 -116
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/package.json +1 -1
package/dist/storage.js
CHANGED
|
@@ -5,7 +5,7 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
5
5
|
import { GLOBAL_WORKSPACE_ID } from "./types.js";
|
|
6
6
|
import { normalizeSessionDirectory } from "./session-directory-tree.js";
|
|
7
7
|
import { inferProviderFromCommand, inferProviderFromRunner, isSessionProvider } from "./session-provider.js";
|
|
8
|
-
import { DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_PRIORITY, isWandTaskAgentKind, normalizeWandTaskAgentMode } from "./task-types.js";
|
|
8
|
+
import { DEFAULT_ITERATION_NAME, DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_PRIORITY, isWandTaskAgentKind, normalizeWandTaskAgentMode } from "./task-types.js";
|
|
9
9
|
import { firstLayoutTabId } from "./layout-tree.js";
|
|
10
10
|
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
11
11
|
import { DEFAULT_PASSWORD_VAULT_ID, DEFAULT_PASSWORD_VAULT_NAME, decryptVaultSecret, encryptVaultSecret, itemMatchesFilter, normalizePasswordItemInput, normalizeVaultName, nowIso, } from "./password-manager.js";
|
|
@@ -52,10 +52,32 @@ function mapWandMilestoneRow(row) {
|
|
|
52
52
|
name,
|
|
53
53
|
dueDate: typeof row.due_date === "string" && row.due_date ? row.due_date : null,
|
|
54
54
|
workspaceId: typeof row.workspace_id === "string" && row.workspace_id ? row.workspace_id : null,
|
|
55
|
+
isDefault: Number(row.is_default) === 1,
|
|
55
56
|
createdAt: String(row.created_at),
|
|
56
57
|
updatedAt: String(row.updated_at),
|
|
57
58
|
};
|
|
58
59
|
}
|
|
60
|
+
/** `wand_iteration_prompts` 行 → 领域对象;标题为空的脏行直接跳过。 */
|
|
61
|
+
function mapIterationPromptRow(row) {
|
|
62
|
+
const title = typeof row.title === "string" ? row.title.trim() : "";
|
|
63
|
+
if (!title)
|
|
64
|
+
return null;
|
|
65
|
+
return {
|
|
66
|
+
id: String(row.id),
|
|
67
|
+
milestoneId: String(row.milestone_id ?? ""),
|
|
68
|
+
workspaceId: typeof row.workspace_id === "string" && row.workspace_id ? row.workspace_id : null,
|
|
69
|
+
sessionId: typeof row.session_id === "string" && row.session_id ? row.session_id : null,
|
|
70
|
+
taskId: typeof row.task_id === "string" && row.task_id ? row.task_id : null,
|
|
71
|
+
repoKey: typeof row.repo_key === "string" && row.repo_key ? row.repo_key : null,
|
|
72
|
+
cwd: typeof row.cwd === "string" ? row.cwd : "",
|
|
73
|
+
title,
|
|
74
|
+
detail: typeof row.detail === "string" ? row.detail : "",
|
|
75
|
+
source: row.source === "dispatch" ? "dispatch" : "session",
|
|
76
|
+
consumedAt: typeof row.consumed_at === "string" && row.consumed_at ? row.consumed_at : null,
|
|
77
|
+
consumedCommit: typeof row.consumed_commit === "string" && row.consumed_commit ? row.consumed_commit : null,
|
|
78
|
+
createdAt: String(row.created_at),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
59
81
|
const SESSION_OPTIONS_SCHEMA_VERSION = 1;
|
|
60
82
|
function isRecord(value) {
|
|
61
83
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -692,11 +714,33 @@ const INIT_SQL = `
|
|
|
692
714
|
name TEXT NOT NULL,
|
|
693
715
|
due_date TEXT,
|
|
694
716
|
workspace_id TEXT,
|
|
717
|
+
is_default INTEGER NOT NULL DEFAULT 0,
|
|
695
718
|
created_at TEXT NOT NULL,
|
|
696
719
|
updated_at TEXT NOT NULL
|
|
697
720
|
);
|
|
698
721
|
CREATE INDEX IF NOT EXISTS idx_wand_milestones_created ON wand_milestones(created_at DESC);
|
|
699
722
|
|
|
723
|
+
-- 迭代提示词记录:用户在本轮迭代里发过的提示词标题,供 commit message / 汇报类生成复用。
|
|
724
|
+
-- 不建外键:删除迭代只把记录改挂到默认迭代,绝不删记录。
|
|
725
|
+
CREATE TABLE IF NOT EXISTS wand_iteration_prompts (
|
|
726
|
+
id TEXT PRIMARY KEY,
|
|
727
|
+
milestone_id TEXT NOT NULL,
|
|
728
|
+
workspace_id TEXT,
|
|
729
|
+
session_id TEXT,
|
|
730
|
+
task_id TEXT,
|
|
731
|
+
repo_key TEXT,
|
|
732
|
+
cwd TEXT NOT NULL,
|
|
733
|
+
title TEXT NOT NULL,
|
|
734
|
+
detail TEXT NOT NULL DEFAULT '',
|
|
735
|
+
source TEXT NOT NULL DEFAULT 'session',
|
|
736
|
+
consumed_at TEXT,
|
|
737
|
+
consumed_commit TEXT,
|
|
738
|
+
created_at TEXT NOT NULL
|
|
739
|
+
);
|
|
740
|
+
CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_iteration ON wand_iteration_prompts(milestone_id, created_at DESC);
|
|
741
|
+
CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_repo ON wand_iteration_prompts(repo_key, created_at DESC);
|
|
742
|
+
CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_session ON wand_iteration_prompts(session_id, created_at DESC);
|
|
743
|
+
|
|
700
744
|
CREATE TABLE IF NOT EXISTS wand_tasks (
|
|
701
745
|
id TEXT PRIMARY KEY,
|
|
702
746
|
identifier TEXT,
|
|
@@ -785,12 +829,45 @@ function ensureWandMilestoneSchema(db) {
|
|
|
785
829
|
if (columns.length > 0 && !names.has("workspace_id")) {
|
|
786
830
|
db.exec("ALTER TABLE wand_milestones ADD COLUMN workspace_id TEXT");
|
|
787
831
|
}
|
|
832
|
+
// 默认迭代标记:只加列,历史行保持 0(读作普通迭代)。
|
|
833
|
+
if (columns.length > 0 && !names.has("is_default")) {
|
|
834
|
+
db.exec("ALTER TABLE wand_milestones ADD COLUMN is_default INTEGER NOT NULL DEFAULT 0");
|
|
835
|
+
}
|
|
788
836
|
if (columns.length > 0) {
|
|
789
837
|
// 索引必须等列存在后再建:老库的 CREATE TABLE IF NOT EXISTS 是空操作,
|
|
790
838
|
// 直接在 INIT_SQL 里建索引会因缺列报错。
|
|
791
839
|
db.exec("CREATE INDEX IF NOT EXISTS idx_wand_milestones_workspace ON wand_milestones(workspace_id, created_at DESC)");
|
|
792
840
|
}
|
|
793
841
|
}
|
|
842
|
+
/**
|
|
843
|
+
* 迭代提示词记录表:新库由 INIT_SQL 建好,老库在这里补建(只加表,不改表)。
|
|
844
|
+
* 空库(没建过任何表)时 PRAGMA 拿不到列,直接返回。
|
|
845
|
+
*/
|
|
846
|
+
function ensureIterationPromptSchema(db) {
|
|
847
|
+
const columns = db.prepare("PRAGMA table_info(wand_iteration_prompts)").all();
|
|
848
|
+
if (columns.length > 0)
|
|
849
|
+
return;
|
|
850
|
+
db.exec(`
|
|
851
|
+
CREATE TABLE IF NOT EXISTS wand_iteration_prompts (
|
|
852
|
+
id TEXT PRIMARY KEY,
|
|
853
|
+
milestone_id TEXT NOT NULL,
|
|
854
|
+
workspace_id TEXT,
|
|
855
|
+
session_id TEXT,
|
|
856
|
+
task_id TEXT,
|
|
857
|
+
repo_key TEXT,
|
|
858
|
+
cwd TEXT NOT NULL,
|
|
859
|
+
title TEXT NOT NULL,
|
|
860
|
+
detail TEXT NOT NULL DEFAULT '',
|
|
861
|
+
source TEXT NOT NULL DEFAULT 'session',
|
|
862
|
+
consumed_at TEXT,
|
|
863
|
+
consumed_commit TEXT,
|
|
864
|
+
created_at TEXT NOT NULL
|
|
865
|
+
)
|
|
866
|
+
`);
|
|
867
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_iteration ON wand_iteration_prompts(milestone_id, created_at DESC)");
|
|
868
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_repo ON wand_iteration_prompts(repo_key, created_at DESC)");
|
|
869
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_session ON wand_iteration_prompts(session_id, created_at DESC)");
|
|
870
|
+
}
|
|
794
871
|
function ensureConnectorSchema(db) {
|
|
795
872
|
// The table is created by INIT_SQL for new databases; this repairs an
|
|
796
873
|
// existing wand.db that predates the connector feature (additive-only).
|
|
@@ -820,6 +897,7 @@ export function ensureDatabaseFile(dbPath) {
|
|
|
820
897
|
ensureWorkspaceSchema(db);
|
|
821
898
|
ensureWandTaskSchema(db);
|
|
822
899
|
ensureWandMilestoneSchema(db);
|
|
900
|
+
ensureIterationPromptSchema(db);
|
|
823
901
|
ensureConnectorSchema(db);
|
|
824
902
|
{
|
|
825
903
|
const missionColumns = db.prepare("PRAGMA table_info(missions)").all();
|
|
@@ -851,6 +929,7 @@ export class WandStorage {
|
|
|
851
929
|
ensureWorkspaceSchema(this.db);
|
|
852
930
|
ensureWandTaskSchema(this.db);
|
|
853
931
|
ensureWandMilestoneSchema(this.db);
|
|
932
|
+
ensureIterationPromptSchema(this.db);
|
|
854
933
|
ensureConnectorSchema(this.db);
|
|
855
934
|
this.ensureDefaultPasswordVault();
|
|
856
935
|
}
|
|
@@ -1285,22 +1364,45 @@ export class WandStorage {
|
|
|
1285
1364
|
*/
|
|
1286
1365
|
listWandMilestones(workspaceId) {
|
|
1287
1366
|
const scoped = typeof workspaceId === "string" ? workspaceId.trim() : "";
|
|
1288
|
-
const select = "SELECT id, name, due_date, workspace_id, created_at, updated_at FROM wand_milestones";
|
|
1289
|
-
|
|
1367
|
+
const select = "SELECT id, name, due_date, workspace_id, is_default, created_at, updated_at FROM wand_milestones";
|
|
1368
|
+
// 默认迭代永远排最前:新建任务的下拉把它放在第一项,不选时也回落到它。
|
|
1369
|
+
const order = " ORDER BY is_default DESC, created_at DESC, rowid DESC";
|
|
1290
1370
|
const rows = (scoped
|
|
1291
1371
|
? this.db.prepare(`${select} WHERE workspace_id = ? OR workspace_id IS NULL${order}`).all(scoped)
|
|
1292
1372
|
: this.db.prepare(`${select}${order}`).all());
|
|
1293
1373
|
return rows.map(mapWandMilestoneRow).filter((item) => item !== null);
|
|
1294
1374
|
}
|
|
1295
1375
|
getWandMilestone(id) {
|
|
1296
|
-
const row = this.db.prepare(`SELECT id, name, due_date, workspace_id, created_at, updated_at FROM wand_milestones WHERE id = ?`).get(id);
|
|
1376
|
+
const row = this.db.prepare(`SELECT id, name, due_date, workspace_id, is_default, created_at, updated_at FROM wand_milestones WHERE id = ?`).get(id);
|
|
1377
|
+
return row ? mapWandMilestoneRow(row) : null;
|
|
1378
|
+
}
|
|
1379
|
+
/** 已有的默认迭代;未创建过时返回 null(读路径用它,不写库)。 */
|
|
1380
|
+
findDefaultWandMilestone() {
|
|
1381
|
+
const row = this.db.prepare(`SELECT id, name, due_date, workspace_id, is_default, created_at, updated_at FROM wand_milestones WHERE is_default = 1 ORDER BY created_at ASC, rowid ASC LIMIT 1`).get();
|
|
1297
1382
|
return row ? mapWandMilestoneRow(row) : null;
|
|
1298
1383
|
}
|
|
1384
|
+
/**
|
|
1385
|
+
* 惰性拿到全局唯一的「默认迭代」:没有就建一个,返回值一定可写进任务 / 记录。
|
|
1386
|
+
* 用户已经自己建过一个同名迭代时直接把它提升为默认,不再造第二个重名行。
|
|
1387
|
+
*/
|
|
1388
|
+
ensureDefaultWandMilestone() {
|
|
1389
|
+
const existing = this.findDefaultWandMilestone();
|
|
1390
|
+
if (existing)
|
|
1391
|
+
return existing;
|
|
1392
|
+
const sameName = this.db.prepare(`SELECT id FROM wand_milestones WHERE name = ? AND workspace_id IS NULL ORDER BY created_at ASC, rowid ASC LIMIT 1`).get(DEFAULT_ITERATION_NAME);
|
|
1393
|
+
if (sameName?.id) {
|
|
1394
|
+
this.db.prepare("UPDATE wand_milestones SET is_default = 1, updated_at = ? WHERE id = ?").run(nowIso(), sameName.id);
|
|
1395
|
+
return this.getWandMilestone(sameName.id);
|
|
1396
|
+
}
|
|
1397
|
+
return this.createWandMilestone({ name: DEFAULT_ITERATION_NAME, isDefault: true });
|
|
1398
|
+
}
|
|
1299
1399
|
createWandMilestone(input) {
|
|
1300
1400
|
const id = crypto.randomUUID();
|
|
1301
1401
|
const now = nowIso();
|
|
1302
1402
|
const workspaceId = typeof input.workspaceId === "string" && input.workspaceId.trim() ? input.workspaceId.trim() : null;
|
|
1303
|
-
|
|
1403
|
+
// 默认迭代是全局兜底:固定 workspace_id = NULL,任何工作区的任务都能挂。
|
|
1404
|
+
const isDefault = input.isDefault === true;
|
|
1405
|
+
this.db.prepare(`INSERT INTO wand_milestones (id, name, due_date, workspace_id, is_default, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`).run(id, input.name, input.dueDate ?? null, isDefault ? null : workspaceId, isDefault ? 1 : 0, now, now);
|
|
1304
1406
|
return this.getWandMilestone(id);
|
|
1305
1407
|
}
|
|
1306
1408
|
updateWandMilestone(id, patch) {
|
|
@@ -1310,27 +1412,114 @@ export class WandStorage {
|
|
|
1310
1412
|
const next = {
|
|
1311
1413
|
name: patch.name ?? current.name,
|
|
1312
1414
|
dueDate: patch.dueDate === undefined ? current.dueDate : patch.dueDate,
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1415
|
+
// 默认迭代永远是全局的:不允许改挂到某个工作区,否则别的项目就看不到兜底迭代了。
|
|
1416
|
+
workspaceId: current.isDefault
|
|
1417
|
+
? null
|
|
1418
|
+
: patch.workspaceId === undefined
|
|
1419
|
+
? current.workspaceId
|
|
1420
|
+
: (typeof patch.workspaceId === "string" && patch.workspaceId.trim() ? patch.workspaceId.trim() : null),
|
|
1316
1421
|
updatedAt: nowIso(),
|
|
1317
1422
|
};
|
|
1318
1423
|
this.db.prepare(`UPDATE wand_milestones SET name = ?, due_date = ?, workspace_id = ?, updated_at = ? WHERE id = ?`).run(next.name, next.dueDate, next.workspaceId, next.updatedAt, id);
|
|
1319
1424
|
return this.getWandMilestone(id);
|
|
1320
1425
|
}
|
|
1321
|
-
/**
|
|
1426
|
+
/**
|
|
1427
|
+
* 删除里程碑只解绑任务(milestone_id 置空)、并把提示词记录改挂到默认迭代,绝不删任务与记录。
|
|
1428
|
+
* 默认迭代不可删除:返回 false,由调用方给出明确提示。
|
|
1429
|
+
*/
|
|
1322
1430
|
deleteWandMilestone(id) {
|
|
1431
|
+
const current = this.getWandMilestone(id);
|
|
1432
|
+
if (!current)
|
|
1433
|
+
return false;
|
|
1434
|
+
if (current.isDefault)
|
|
1435
|
+
return false;
|
|
1323
1436
|
const removed = this.db.prepare("DELETE FROM wand_milestones WHERE id = ?").run(id);
|
|
1324
1437
|
if (Number(removed.changes) === 0)
|
|
1325
1438
|
return false;
|
|
1326
1439
|
this.db.prepare("UPDATE wand_tasks SET milestone_id = NULL WHERE milestone_id = ?").run(id);
|
|
1327
1440
|
this.db.prepare("UPDATE workspace_tasks SET milestone_id = NULL WHERE milestone_id = ?").run(id);
|
|
1441
|
+
// 记录属于「用户的改动历史」,不能因为删迭代就丢:改挂到默认迭代。
|
|
1442
|
+
const fallback = this.findDefaultWandMilestone();
|
|
1443
|
+
if (fallback) {
|
|
1444
|
+
this.db.prepare("UPDATE wand_iteration_prompts SET milestone_id = ? WHERE milestone_id = ?").run(fallback.id, id);
|
|
1445
|
+
}
|
|
1328
1446
|
return true;
|
|
1329
1447
|
}
|
|
1330
|
-
/**
|
|
1448
|
+
/** 各迭代下的任务数(看板卡片),用于下拉里的数量提示;历史 NULL 行算在默认迭代里。 */
|
|
1331
1449
|
countWandTasksByMilestone() {
|
|
1332
|
-
const rows = this.db.prepare(`SELECT milestone_id, COUNT(*) AS count FROM wand_tasks
|
|
1333
|
-
|
|
1450
|
+
const rows = this.db.prepare(`SELECT milestone_id, COUNT(*) AS count FROM wand_tasks GROUP BY milestone_id`).all();
|
|
1451
|
+
const defaultId = this.findDefaultWandMilestone()?.id ?? null;
|
|
1452
|
+
const counts = {};
|
|
1453
|
+
for (const row of rows) {
|
|
1454
|
+
const key = row.milestone_id ? String(row.milestone_id) : defaultId;
|
|
1455
|
+
if (!key)
|
|
1456
|
+
continue;
|
|
1457
|
+
counts[key] = (counts[key] ?? 0) + (Number(row.count) || 0);
|
|
1458
|
+
}
|
|
1459
|
+
return counts;
|
|
1460
|
+
}
|
|
1461
|
+
// ============ 迭代提示词记录 ============
|
|
1462
|
+
/** 同一会话最近一条记录;用来去重(同一提示词重复提交 / 占位与模型结果双写)。 */
|
|
1463
|
+
latestIterationPromptForSession(sessionId) {
|
|
1464
|
+
const row = this.db.prepare(`SELECT * FROM wand_iteration_prompts WHERE session_id = ? ORDER BY created_at DESC, rowid DESC LIMIT 1`).get(sessionId);
|
|
1465
|
+
return row ? mapIterationPromptRow(row) : null;
|
|
1466
|
+
}
|
|
1467
|
+
createIterationPrompt(input) {
|
|
1468
|
+
const id = crypto.randomUUID();
|
|
1469
|
+
const now = nowIso();
|
|
1470
|
+
this.db.prepare(`INSERT INTO wand_iteration_prompts (id, milestone_id, workspace_id, session_id, task_id, repo_key, cwd, title, detail, source, consumed_at, consumed_commit, created_at)
|
|
1471
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?)`).run(id, input.milestoneId, input.workspaceId ?? null, input.sessionId ?? null, input.taskId ?? null, input.repoKey ?? null, input.cwd ?? "", input.title, input.detail ?? "", input.source ?? "session", now);
|
|
1472
|
+
return mapIterationPromptRow(this.db.prepare(`SELECT * FROM wand_iteration_prompts WHERE id = ?`).get(id));
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* 迭代窗口里的提示词记录,按时间正序(模型读起来就是「先做什么、后做什么」)。
|
|
1476
|
+
* `repoKey` 给值时按仓库隔离;`includeConsumed=false` 只看还没提交的部分。
|
|
1477
|
+
*/
|
|
1478
|
+
listIterationPrompts(filter = {}) {
|
|
1479
|
+
const conditions = [];
|
|
1480
|
+
const values = [];
|
|
1481
|
+
if (filter.milestoneId) {
|
|
1482
|
+
conditions.push("milestone_id = ?");
|
|
1483
|
+
values.push(filter.milestoneId);
|
|
1484
|
+
}
|
|
1485
|
+
if (filter.repoKey) {
|
|
1486
|
+
conditions.push("repo_key = ?");
|
|
1487
|
+
values.push(filter.repoKey);
|
|
1488
|
+
}
|
|
1489
|
+
if (filter.sessionId) {
|
|
1490
|
+
conditions.push("session_id = ?");
|
|
1491
|
+
values.push(filter.sessionId);
|
|
1492
|
+
}
|
|
1493
|
+
if (!filter.includeConsumed)
|
|
1494
|
+
conditions.push("consumed_at IS NULL");
|
|
1495
|
+
const limit = Math.max(1, Math.min(500, Math.floor(filter.limit ?? 200)));
|
|
1496
|
+
const where = conditions.length ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
1497
|
+
const rows = this.db.prepare(`SELECT * FROM wand_iteration_prompts${where} ORDER BY created_at ASC, rowid ASC LIMIT ?`).all(...values, limit);
|
|
1498
|
+
return rows.map(mapIterationPromptRow).filter((item) => item !== null);
|
|
1499
|
+
}
|
|
1500
|
+
/** 按 id 批量取记录(用户只勾选了一部分历史变更时用)。 */
|
|
1501
|
+
listIterationPromptsByIds(ids) {
|
|
1502
|
+
const unique = [...new Set(ids.filter(Boolean))].slice(0, 500);
|
|
1503
|
+
if (unique.length === 0)
|
|
1504
|
+
return [];
|
|
1505
|
+
const placeholders = unique.map(() => "?").join(", ");
|
|
1506
|
+
const rows = this.db.prepare(`SELECT * FROM wand_iteration_prompts WHERE id IN (${placeholders}) ORDER BY created_at ASC, rowid ASC`).all(...unique);
|
|
1507
|
+
return rows.map(mapIterationPromptRow).filter((item) => item !== null);
|
|
1508
|
+
}
|
|
1509
|
+
/** 标记这些记录已被某次提交用掉,下一轮「上次提交以来」从这里开始。 */
|
|
1510
|
+
markIterationPromptsConsumed(ids, commit) {
|
|
1511
|
+
const unique = [...new Set(ids.filter(Boolean))];
|
|
1512
|
+
if (unique.length === 0)
|
|
1513
|
+
return 0;
|
|
1514
|
+
const placeholders = unique.map(() => "?").join(", ");
|
|
1515
|
+
const result = this.db.prepare(`UPDATE wand_iteration_prompts SET consumed_at = ?, consumed_commit = ? WHERE id IN (${placeholders})`).run(nowIso(), commit, ...unique);
|
|
1516
|
+
return Number(result.changes) || 0;
|
|
1517
|
+
}
|
|
1518
|
+
// ============ 会话 → 任务 / 迭代 ============
|
|
1519
|
+
/** 会话绑定的看板任务 id;没绑或已删除时返回 null。 */
|
|
1520
|
+
getWandTaskIdForSession(sessionId) {
|
|
1521
|
+
const row = this.db.prepare("SELECT task_id FROM wand_task_sessions WHERE session_id = ? ORDER BY rowid DESC LIMIT 1").get(sessionId);
|
|
1522
|
+
return row?.task_id || null;
|
|
1334
1523
|
}
|
|
1335
1524
|
listWandTaskSessionIds(taskId) {
|
|
1336
1525
|
const rows = this.db.prepare("SELECT session_id FROM wand_task_sessions WHERE task_id = ? ORDER BY rowid ASC, session_id ASC").all(taskId);
|
|
@@ -4,9 +4,10 @@ import { prepareSessionWorktree } from "./git-worktree.js";
|
|
|
4
4
|
import { truncateMessagesForTransport } from "./message-truncator.js";
|
|
5
5
|
import { buildChildEnv } from "./env-utils.js";
|
|
6
6
|
import { getErrorMessage } from "./error-utils.js";
|
|
7
|
+
import { recordIterationPrompt } from "./iteration-log.js";
|
|
7
8
|
import { signalNameFromNumber } from "./signal-utils.js";
|
|
8
9
|
import { resolveSdkClaudeBinary } from "./claude-sdk-runner.js";
|
|
9
|
-
import { provisionalSessionTopic, SessionTopicCoordinator, sessionTopicBlocklistForSnapshot, shouldAcceptGeneratedSessionTitle, } from "./session-topic.js";
|
|
10
|
+
import { provisionalSessionTopic, SessionTopicCoordinator, sessionTopicBlocklistForSnapshot, shouldAcceptGeneratedSessionTitle, shouldGenerateSessionTopicFromInput, } from "./session-topic.js";
|
|
10
11
|
import { resolveSessionCwd } from "./session-cwd.js";
|
|
11
12
|
import { isSessionProvider } from "./session-provider.js";
|
|
12
13
|
import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
@@ -982,9 +983,13 @@ export class StructuredSessionManager {
|
|
|
982
983
|
const session = this.sessions.get(id);
|
|
983
984
|
if (this.disposed || !session || !input.trim())
|
|
984
985
|
return;
|
|
986
|
+
// 迭代提示词记录:和会话标题共用同一套「有没有信息量」判断,用户不用多做一步。
|
|
987
|
+
recordIterationPrompt(this.storage, session, input, "session");
|
|
985
988
|
const blockedTitles = sessionTopicBlocklistForSnapshot(session, this.storage);
|
|
986
989
|
const provisional = provisionalSessionTopic(input, blockedTitles);
|
|
987
|
-
if (provisional
|
|
990
|
+
if (provisional
|
|
991
|
+
&& shouldGenerateSessionTopicFromInput(input)
|
|
992
|
+
&& (session.title !== provisional.title || session.description !== provisional.description)) {
|
|
988
993
|
this.setSessionTopic(id, provisional.title, provisional.description);
|
|
989
994
|
}
|
|
990
995
|
this.topicCoordinator.request(id, {
|
package/dist/task-types.d.ts
CHANGED
|
@@ -54,10 +54,12 @@ export interface WandTaskAgent {
|
|
|
54
54
|
export type WandTaskTitleSource = "user" | "auto";
|
|
55
55
|
/** 里程碑名上限;所有任务面板的新增入口共用同一约束。 */
|
|
56
56
|
export declare const WAND_MILESTONE_NAME_MAX_LENGTH = 60;
|
|
57
|
+
/** 兜底迭代的名字;用户没选迭代时任务/会话都归到它下面。 */
|
|
58
|
+
export declare const DEFAULT_ITERATION_NAME = "\u9ED8\u8BA4\u8FED\u4EE3";
|
|
57
59
|
/**
|
|
58
60
|
* 里程碑(迭代):归属某个工作区,新建任务时按所选工作区过滤展示,也可以不选。
|
|
59
61
|
* workspaceId 为 null 表示全局里程碑(历史数据,或未指定工作区时创建),任何工作区都能选。
|
|
60
|
-
* 任务只保存 milestoneId
|
|
62
|
+
* 任务只保存 milestoneId,删除里程碑时只解绑任务(并改挂默认迭代)、不删除任务。
|
|
61
63
|
*/
|
|
62
64
|
export interface WandTaskMilestone {
|
|
63
65
|
id: string;
|
|
@@ -66,9 +68,53 @@ export interface WandTaskMilestone {
|
|
|
66
68
|
dueDate: string | null;
|
|
67
69
|
/** 所属工作区;null = 全局里程碑。 */
|
|
68
70
|
workspaceId: string | null;
|
|
71
|
+
/**
|
|
72
|
+
* 是否为兜底「默认迭代」:全局唯一、不可删除,用户没选迭代时一切新建都落到它这里。
|
|
73
|
+
* 同一个工作区可见的迭代里最多只有一个 isDefault,服务端保证惰性创建。
|
|
74
|
+
*/
|
|
75
|
+
isDefault: boolean;
|
|
69
76
|
createdAt: string;
|
|
70
77
|
updatedAt: string;
|
|
71
78
|
}
|
|
79
|
+
/** 迭代提示词记录的来源:会话里用户输入的 / 任务派发的。 */
|
|
80
|
+
export type WandIterationPromptSource = "session" | "dispatch";
|
|
81
|
+
/** 记录标题上限(列表里显示的一行)。 */
|
|
82
|
+
export declare const WAND_ITERATION_PROMPT_TITLE_MAX_LENGTH = 60;
|
|
83
|
+
/** 记录详情上限:够模型总结,又不至于把整段提示词全量灌进 commit 生成。 */
|
|
84
|
+
export declare const WAND_ITERATION_PROMPT_DETAIL_MAX_LENGTH = 600;
|
|
85
|
+
/**
|
|
86
|
+
* 迭代提示词记录(「两次迭代之间我到底改了什么」的输入源):
|
|
87
|
+
* 用户每发一条有信息量的提示词就追加一行,commit / 汇报类生成直接读它,
|
|
88
|
+
* 不用每次都让模型去读 diff。
|
|
89
|
+
*/
|
|
90
|
+
export interface WandIterationPrompt {
|
|
91
|
+
id: string;
|
|
92
|
+
/** 归属迭代;默认迭代也有自己真实的行 id,不会留下 null。 */
|
|
93
|
+
milestoneId: string;
|
|
94
|
+
/** 记录时所属项目;null = 未指定项目的会话 / 任务。 */
|
|
95
|
+
workspaceId: string | null;
|
|
96
|
+
sessionId: string | null;
|
|
97
|
+
/** 关联的看板任务(wand_tasks.id)。 */
|
|
98
|
+
taskId: string | null;
|
|
99
|
+
/**
|
|
100
|
+
* 仓库身份:`git rev-parse --git-common-dir` 的绝对路径,同一仓库的 worktree 共享同一个值。
|
|
101
|
+
* 用来把提示词按仓库隔离,避免把别的项目的历史混进这次 commit message。
|
|
102
|
+
* 拿不到仓库信息时为 null。
|
|
103
|
+
*/
|
|
104
|
+
repoKey: string | null;
|
|
105
|
+
/** 记录时的会话工作目录。 */
|
|
106
|
+
cwd: string;
|
|
107
|
+
/** 提示词的本地标题(不调模型,取第一行有效内容)。 */
|
|
108
|
+
title: string;
|
|
109
|
+
/** 提示词摘要,已经过清洗与截断。 */
|
|
110
|
+
detail: string;
|
|
111
|
+
source: WandIterationPromptSource;
|
|
112
|
+
/** 已被某次 commit message 用掉的时间;null = 还没提交。 */
|
|
113
|
+
consumedAt: string | null;
|
|
114
|
+
/** 用掉它的 commit hash;提交失败或只生成不提交时为 null。 */
|
|
115
|
+
consumedCommit: string | null;
|
|
116
|
+
createdAt: string;
|
|
117
|
+
}
|
|
72
118
|
export interface WandTask {
|
|
73
119
|
id: string;
|
|
74
120
|
workspaceId: string | null;
|
package/dist/task-types.js
CHANGED
|
@@ -39,3 +39,9 @@ export function isWandTaskAgentKind(value) {
|
|
|
39
39
|
}
|
|
40
40
|
/** 里程碑名上限;所有任务面板的新增入口共用同一约束。 */
|
|
41
41
|
export const WAND_MILESTONE_NAME_MAX_LENGTH = 60;
|
|
42
|
+
/** 兜底迭代的名字;用户没选迭代时任务/会话都归到它下面。 */
|
|
43
|
+
export const DEFAULT_ITERATION_NAME = "默认迭代";
|
|
44
|
+
/** 记录标题上限(列表里显示的一行)。 */
|
|
45
|
+
export const WAND_ITERATION_PROMPT_TITLE_MAX_LENGTH = 60;
|
|
46
|
+
/** 记录详情上限:够模型总结,又不至于把整段提示词全量灌进 commit 生成。 */
|
|
47
|
+
export const WAND_ITERATION_PROMPT_DETAIL_MAX_LENGTH = 600;
|