@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/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
- const order = " ORDER BY created_at DESC, rowid DESC";
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
- this.db.prepare(`INSERT INTO wand_milestones (id, name, due_date, workspace_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)`).run(id, input.name, input.dueDate ?? null, workspaceId, now, now);
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
- workspaceId: patch.workspaceId === undefined
1314
- ? current.workspaceId
1315
- : (typeof patch.workspaceId === "string" && patch.workspaceId.trim() ? patch.workspaceId.trim() : null),
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
- /** 删除里程碑只解绑任务(milestone_id 置空),绝不删除任务。 */
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 WHERE milestone_id IS NOT NULL GROUP BY milestone_id`).all();
1333
- return Object.fromEntries(rows.map((row) => [row.milestone_id, Number(row.count) || 0]));
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 && (session.title !== provisional.title || session.description !== provisional.description)) {
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, {
@@ -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;
@@ -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;