@co0ontty/wand 4.82.0 → 4.83.1

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.
Files changed (91) hide show
  1. package/README.md +47 -9
  2. package/dist/agent-dispatch.d.ts +3 -2
  3. package/dist/agent-dispatch.js +19 -3
  4. package/dist/ai-team-delivery-types.d.ts +40 -0
  5. package/dist/ai-team-delivery-types.js +1 -0
  6. package/dist/ai-team-delivery.d.ts +13 -0
  7. package/dist/ai-team-delivery.js +89 -0
  8. package/dist/ai-team-employee-binding.d.ts +16 -0
  9. package/dist/ai-team-employee-binding.js +77 -0
  10. package/dist/ai-team-prompts.js +5 -0
  11. package/dist/ai-team-runner.d.ts +9 -2
  12. package/dist/ai-team-runner.js +52 -7
  13. package/dist/ai-team-types.d.ts +17 -3
  14. package/dist/ai-team-types.js +43 -3
  15. package/dist/build-info.json +3 -3
  16. package/dist/cli.js +36 -0
  17. package/dist/config.d.ts +18 -10
  18. package/dist/config.js +63 -105
  19. package/dist/decision-client.d.ts +4 -0
  20. package/dist/decision-client.js +91 -0
  21. package/dist/decision-runner.d.ts +9 -0
  22. package/dist/decision-runner.js +36 -0
  23. package/dist/decision-service.d.ts +46 -0
  24. package/dist/decision-service.js +230 -0
  25. package/dist/decision-skill.d.ts +6 -0
  26. package/dist/decision-skill.js +70 -0
  27. package/dist/decision-tool.d.ts +21 -0
  28. package/dist/decision-tool.js +346 -0
  29. package/dist/decision-types.d.ts +36 -0
  30. package/dist/decision-types.js +123 -0
  31. package/dist/decision-worker.py +86 -0
  32. package/dist/default-employee.d.ts +4 -1
  33. package/dist/default-employee.js +11 -1
  34. package/dist/env-utils.js +3 -0
  35. package/dist/git-quick-commit.js +7 -1
  36. package/dist/message-truncator.d.ts +1 -1
  37. package/dist/message-truncator.js +29 -22
  38. package/dist/models.d.ts +1 -0
  39. package/dist/models.js +31 -4
  40. package/dist/process-manager.d.ts +39 -1
  41. package/dist/process-manager.js +112 -8
  42. package/dist/provider-catalog.d.ts +47 -0
  43. package/dist/provider-catalog.js +138 -0
  44. package/dist/provider-cli-updater.d.ts +1 -1
  45. package/dist/pty-foreground.d.ts +37 -0
  46. package/dist/pty-foreground.js +56 -0
  47. package/dist/pty-turn-activity.d.ts +6 -5
  48. package/dist/pty-turn-activity.js +6 -5
  49. package/dist/retention.d.ts +29 -0
  50. package/dist/retention.js +142 -0
  51. package/dist/server-ai-team-routes.d.ts +2 -1
  52. package/dist/server-ai-team-routes.js +27 -9
  53. package/dist/server-decision-routes.d.ts +10 -0
  54. package/dist/server-decision-routes.js +52 -0
  55. package/dist/server-employee-routes.js +9 -1
  56. package/dist/server-session-routes.d.ts +2 -2
  57. package/dist/server-session-routes.js +97 -1
  58. package/dist/server-settings-routes.js +10 -36
  59. package/dist/server-update-routes.js +2 -1
  60. package/dist/server-workspace-routes.js +8 -0
  61. package/dist/server.js +37 -5
  62. package/dist/session-completion-state.d.ts +26 -0
  63. package/dist/session-completion-state.js +30 -0
  64. package/dist/session-completion.d.ts +12 -0
  65. package/dist/session-completion.js +62 -0
  66. package/dist/session-provider.d.ts +1 -15
  67. package/dist/session-provider.js +4 -54
  68. package/dist/session-registry.d.ts +5 -0
  69. package/dist/session-registry.js +19 -1
  70. package/dist/session-transport.js +2 -0
  71. package/dist/storage.d.ts +12 -0
  72. package/dist/storage.js +119 -16
  73. package/dist/structured-client-protocol.js +36 -1
  74. package/dist/structured-provider-common.js +3 -3
  75. package/dist/structured-session-manager.d.ts +13 -1
  76. package/dist/structured-session-manager.js +196 -27
  77. package/dist/system-employee.js +1 -0
  78. package/dist/task-types.d.ts +4 -2
  79. package/dist/tool-preview.d.ts +8 -0
  80. package/dist/tool-preview.js +158 -0
  81. package/dist/types.d.ts +35 -2
  82. package/dist/web-ui/content/ai-teams.js +13 -13
  83. package/dist/web-ui/content/scripts.js +66 -64
  84. package/dist/web-ui/content/styles.css +1 -1
  85. package/dist/web-ui/content/tailwind.css +3 -0
  86. package/dist/web-ui/embedded-assets.js +3 -3
  87. package/dist/web-ui/provider-identity.d.ts +7 -13
  88. package/dist/web-ui/provider-identity.js +8 -71
  89. package/package.json +3 -2
  90. package/skills/wand-decision/SKILL.md +61 -0
  91. package/skills/wand-decision/scripts/decide.mjs +16 -0
@@ -1,16 +1,9 @@
1
1
  import { constants, accessSync, statSync } from "node:fs";
2
2
  import path from "node:path";
3
- /** 全部受支持的会话 provider;顺序与 UI / 派发选择器一致。 */
4
- export const SESSION_PROVIDERS = ["claude", "codex", "opencode", "grok", "qoder", "pi", "gemini"];
5
- const SESSION_PROVIDER_SET = new Set(SESSION_PROVIDERS);
6
- /** 运行时守卫:把配置、数据库、请求体里的任意值收敛成 SessionProvider。 */
7
- export function isSessionProvider(value) {
8
- return typeof value === "string" && SESSION_PROVIDER_SET.has(value);
9
- }
10
- /** provider 对应的 CLI 可执行文件名(Qoder 的 CLI 叫 qodercli)。 */
11
- export function providerCliCommand(provider) {
12
- return provider === "qoder" ? "qodercli" : provider;
13
- }
3
+ import { providerCliCommand } from "./provider-catalog.js";
4
+ // Provider 列表、别名、展示名与推断规则全部收敛在 src/provider-catalog.ts(浏览器层同用)。
5
+ // 这里只保留服务端特有的、需要 node:fs 的能力。
6
+ export { SESSION_PROVIDERS, isSessionProvider, providerCliCommand, providerDisplayName, inferProviderFromRunner, inferProviderFromCommand, normalizeProviderId, } from "./provider-catalog.js";
14
7
  /**
15
8
  * 不启动 shell 也能判断某个 provider 的 CLI 是否在 PATH 上(与子进程 PATH 一致)。
16
9
  * 用于多候选降级时跳过没安装的工具,避免白等一次 spawn 失败。
@@ -30,46 +23,3 @@ export function providerCliInstalled(provider, pathValue = process.env.PATH ?? "
30
23
  }
31
24
  return false;
32
25
  }
33
- /** 从 runner 名推断 provider;`pty` 等与 provider 无关的 runner 返回 undefined。 */
34
- export function inferProviderFromRunner(runner) {
35
- if (runner === "claude-cli" || runner === "claude-cli-print" || runner === "claude-sdk")
36
- return "claude";
37
- if (runner === "codex-cli-exec")
38
- return "codex";
39
- if (runner === "opencode-cli-run")
40
- return "opencode";
41
- if (runner === "grok-cli-headless")
42
- return "grok";
43
- if (runner === "qoder-cli-print")
44
- return "qoder";
45
- if (runner === "pi-cli-json")
46
- return "pi";
47
- if (runner === "gemini-cli-json")
48
- return "gemini";
49
- return undefined;
50
- }
51
- /**
52
- * 从启动命令推断 provider;无法识别时返回 undefined,由调用方决定默认值。
53
- *
54
- * 识别的是命令行的第一个 token:`claude`、`npx claude`、任意路径下的 `claude`
55
- * 都算 Claude,其余 provider 要求命令以其 CLI 名开头。
56
- */
57
- export function inferProviderFromCommand(command) {
58
- const value = command.trim();
59
- if (/^(?:claude|npx\s+claude|[^\s]+\/claude)(?:\s|$)/i.test(value))
60
- return "claude";
61
- if (/^codex\b/i.test(value))
62
- return "codex";
63
- if (/^opencode\b/i.test(value))
64
- return "opencode";
65
- if (/^grok\b/i.test(value))
66
- return "grok";
67
- if (/^qodercli\b/i.test(value))
68
- return "qoder";
69
- if (/^pi\b/i.test(value))
70
- return "pi";
71
- // Gemini CLI 的可执行文件就叫 gemini;-p / --prompt 等参数不影响首 token 识别。
72
- if (/^gemini\b/i.test(value))
73
- return "gemini";
74
- return undefined;
75
- }
@@ -31,6 +31,11 @@ export declare class SessionRegistry {
31
31
  setSessionThinkingEffort(id: string, effort: SessionSnapshot["thinkingEffort"]): SessionSnapshot | null;
32
32
  setSessionMode(id: string, mode: ExecutionMode): SessionSnapshot | null;
33
33
  setSessionTopic(id: string, title: string, description: string): SessionSnapshot | null;
34
+ /**
35
+ * 归档 / 取消归档一个会话:只写标记,不杀进程、不删历史;真正的清理留给保留期扫描。
36
+ * 会话可能只存在于 storage(重启后未加载),因此 storage-only 也要能改。
37
+ */
38
+ setArchived(id: string, archived: boolean): SessionSnapshot | null;
34
39
  updateWorktreeState(id: string, status: SessionSnapshot["worktreeMergeStatus"], info: SessionSnapshot["worktreeMergeInfo"]): SessionSnapshot | null;
35
40
  delete(id: string): SessionSnapshot | null;
36
41
  deleteWithProviderHistory(id: string): SessionSnapshot | null;
@@ -67,7 +67,7 @@ export class SessionRegistry {
67
67
  get(id) {
68
68
  const snapshot = this.structured.get(id) ?? this.processes.getOwned(id) ?? this.storage.getSession(id);
69
69
  const membership = this.storage.getSessionWorkspace(id);
70
- return snapshot && membership ? { ...snapshot, ...membership } : snapshot;
70
+ return snapshot ? { ...snapshot, ...membership, ...this.storage.getSessionCompletion(id) } : null;
71
71
  }
72
72
  getLatest(id) {
73
73
  return this.get(id);
@@ -84,6 +84,8 @@ export class SessionRegistry {
84
84
  const live = byId.get(snapshot.id);
85
85
  byId.set(snapshot.id, live ? {
86
86
  ...live, workspaceId: snapshot.workspaceId, workspaceTaskId: snapshot.workspaceTaskId,
87
+ completionRevision: snapshot.completionRevision,
88
+ viewedCompletionRevision: snapshot.viewedCompletionRevision,
87
89
  } : snapshot);
88
90
  }
89
91
  return Array.from(byId.values()).sort((a, b) => b.startedAt.localeCompare(a.startedAt));
@@ -137,6 +139,22 @@ export class SessionRegistry {
137
139
  return this.processes.setSessionTopic(id, title, description);
138
140
  return this.updateStored(id, (snapshot) => ({ ...snapshot, title, description, summary: description }));
139
141
  }
142
+ /**
143
+ * 归档 / 取消归档一个会话:只写标记,不杀进程、不删历史;真正的清理留给保留期扫描。
144
+ * 会话可能只存在于 storage(重启后未加载),因此 storage-only 也要能改。
145
+ */
146
+ setArchived(id, archived) {
147
+ const owner = this.ownerOf(id);
148
+ if (owner === "structured")
149
+ return this.structured.setSessionArchived(id, archived);
150
+ if (owner === "pty")
151
+ return this.processes.setSessionArchived(id, archived);
152
+ return this.updateStored(id, (snapshot) => ({
153
+ ...snapshot,
154
+ archived,
155
+ archivedAt: archived ? new Date().toISOString() : null,
156
+ }));
157
+ }
140
158
  updateWorktreeState(id, status, info) {
141
159
  const owner = this.ownerOf(id);
142
160
  if (owner === "structured")
@@ -50,6 +50,8 @@ function sessionBase(snapshot) {
50
50
  exitCode: snapshot.exitCode,
51
51
  startedAt: snapshot.startedAt,
52
52
  endedAt: snapshot.endedAt,
53
+ completionRevision: snapshot.completionRevision ?? 0,
54
+ viewedCompletionRevision: snapshot.viewedCompletionRevision ?? 0,
53
55
  archived: snapshot.archived,
54
56
  archivedAt: snapshot.archivedAt,
55
57
  permissionBlocked: snapshot.permissionBlocked,
package/dist/storage.d.ts CHANGED
@@ -345,6 +345,10 @@ export declare class WandStorage {
345
345
  issueEmployeeKnowledgeAccess(sessionId: string, employeeId: string, now?: number): string;
346
346
  resolveEmployeeKnowledgeAccess(token: string, now?: number): string | null;
347
347
  revokeEmployeeKnowledgeAccess(token: string): void;
348
+ /** Inference-only capability; never accepted by general HTTP authentication. */
349
+ issueDecisionAccess(sessionId: string, now?: number): string;
350
+ resolveDecisionAccess(token: string, now?: number): string | null;
351
+ revokeDecisionAccess(token: string): void;
348
352
  /** No IO on input hot paths; queued observations still CAS against the database before writing. */
349
353
  userMemoryCaptureState(): Readonly<{
350
354
  enabled: boolean;
@@ -412,6 +416,14 @@ export declare class WandStorage {
412
416
  upsertAgentActivity(item: AgentActivityItem): void;
413
417
  listAgentActivity(): AgentActivityItem[];
414
418
  markAgentActivityRead(sessionId?: string): void;
419
+ /** Completion/read state is independent of runner checkpoints and survives restarts. */
420
+ getSessionCompletion(id: string): {
421
+ completionRevision: number;
422
+ viewedCompletionRevision: number;
423
+ } | null;
424
+ recordSessionCompletion(id: string): ReturnType<WandStorage["getSessionCompletion"]>;
425
+ /** A delayed view of generation N must never consume a newer completion N+1. */
426
+ markSessionCompletionViewed(id: string, revision: number): boolean;
415
427
  saveSession(snapshot: SessionSnapshot): void;
416
428
  /** Update runtime/scalar fields without serializing or rewriting messages/output. */
417
429
  updateSessionRuntimeMetadata(snapshot: SessionSnapshot): void;
package/dist/storage.js CHANGED
@@ -8,12 +8,13 @@ import { inferProviderFromCommand, inferProviderFromRunner, isSessionProvider, S
8
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 { isUnnamedWorkspaceTaskName } from "./wand-task-sync.js";
11
- import { AI_TEAM_DEFAULT_MAX_STEPS, AI_TEAM_TERMINAL_RUN_STATUSES, aiTeamChatTitle, isTeamMemberRole, memberAgents } from "./ai-team-types.js";
11
+ import { AI_TEAM_DEFAULT_MAX_STEPS, AI_TEAM_TERMINAL_RUN_STATUSES, aiTeamChatTitle, isBuiltinSiliconEmployee, isTeamMemberRole, memberAgents, parseSiliconEmployeeTags, siliconEmployeeTags } from "./ai-team-types.js";
12
+ import { freezeTeamEmployees, projectTeamDefinition, restoreTeamMemberEmployee, serializeExecutionTeam } from "./ai-team-employee-binding.js";
12
13
  import { isTaskExecutionSubject } from "./task-types.js";
13
14
  import { SYSTEM_EMPLOYEE_KEY, systemEmployeeDefinition, systemEmployeeSeedAgents, } from "./system-employee.js";
14
15
  import { isThinkingEffort } from "./structured-provider-common.js";
15
16
  import { DEFAULT_EMPLOYEE_KEY } from "./ai-team-types.js";
16
- import { defaultEmployeeDefinition } from "./default-employee.js";
17
+ import { defaultEmployeeDefinition, legacyPtyRoleIdentity } from "./default-employee.js";
17
18
  import { normalizeEmployeeKnowledge } from "./employee-knowledge-content.js";
18
19
  import { EMPLOYEE_KNOWLEDGE_MAX_ENTRIES } from "./employee-knowledge-types.js";
19
20
  import { USER_MEMORY_MAX_EVENTS, USER_MEMORY_RETENTION_MS, } from "./user-memory-types.js";
@@ -302,7 +303,7 @@ function mapWorktreeMergeFields(row) {
302
303
  }
303
304
  function sessionSelectFields(slim = false) {
304
305
  return `id, session_source, automation_id, provider, session_kind, runner, command, cwd, mode, status, exit_code, started_at, ended_at, ${slim ? "'' AS output" : "output"}, pty_output_seq, archived, archived_at, claude_session_id, ${slim ? "NULL AS messages" : "messages"}, queued_messages, queued_message_skills, structured_state
305
- , resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description, session_options, workspace_id, workspace_task_id`;
306
+ , resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description, session_options, workspace_id, workspace_task_id, completion_revision, viewed_completion_revision`;
306
307
  }
307
308
  function mapWorkspaceKind(raw) {
308
309
  return raw === "global" ? "global" : "project";
@@ -497,6 +498,8 @@ function mapSessionCore(row) {
497
498
  exitCode: row.exit_code,
498
499
  startedAt: row.started_at,
499
500
  endedAt: row.ended_at,
501
+ completionRevision: row.completion_revision ?? 0,
502
+ viewedCompletionRevision: row.viewed_completion_revision ?? 0,
500
503
  output: row.output,
501
504
  ptyOutputSeq: row.pty_output_seq,
502
505
  archived: Boolean(row.archived),
@@ -515,6 +518,7 @@ function mapSessionCore(row) {
515
518
  workspaceTaskId: row.workspace_task_id ?? undefined,
516
519
  ...mapWorktreeMergeFields(row),
517
520
  ...sessionOptions,
521
+ ...legacyPtyRoleIdentity({ ...sessionOptions, sessionKind: row.session_kind ?? "pty", provider }),
518
522
  ...(Object.prototype.hasOwnProperty.call(sessionOptions, "pendingEscalation")
519
523
  ? { permissionBlocked: Boolean(sessionOptions.pendingEscalation) }
520
524
  : {}),
@@ -781,6 +785,7 @@ const INIT_SQL = `
781
785
  parent_task_id TEXT,
782
786
  title_source TEXT,
783
787
  auto_title_signature TEXT,
788
+ archived_at TEXT,
784
789
  created_at TEXT NOT NULL,
785
790
  updated_at TEXT NOT NULL,
786
791
  FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE SET NULL
@@ -839,6 +844,13 @@ const INIT_SQL = `
839
844
  );
840
845
  CREATE INDEX IF NOT EXISTS idx_employee_knowledge_access_expiry ON employee_knowledge_access(expires_at);
841
846
 
847
+ CREATE TABLE IF NOT EXISTS decision_access (
848
+ token_hash TEXT PRIMARY KEY,
849
+ session_id TEXT NOT NULL REFERENCES command_sessions(id) ON DELETE CASCADE,
850
+ expires_at INTEGER NOT NULL
851
+ );
852
+ CREATE INDEX IF NOT EXISTS idx_decision_access_expiry ON decision_access(expires_at);
853
+
842
854
  CREATE TABLE IF NOT EXISTS user_memory_events (
843
855
  id INTEGER PRIMARY KEY AUTOINCREMENT,
844
856
  feature TEXT NOT NULL,
@@ -935,6 +947,9 @@ function ensureAiTeamSchema(db) {
935
947
  if (employeeColumns.size > 0 && !employeeColumns.has("system_key")) {
936
948
  db.exec("ALTER TABLE silicon_employees ADD COLUMN system_key TEXT");
937
949
  }
950
+ if (employeeColumns.size > 0 && !employeeColumns.has("tags_json")) {
951
+ db.exec("ALTER TABLE silicon_employees ADD COLUMN tags_json TEXT NOT NULL DEFAULT '[]'");
952
+ }
938
953
  }
939
954
  function ensureWandTaskSchema(db) {
940
955
  const columns = db.prepare("PRAGMA table_info(wand_tasks)").all();
@@ -962,6 +977,9 @@ function ensureWandTaskSchema(db) {
962
977
  // 里程碑:只加列,历史行保持 NULL;里程碑本体在 wand_milestones(INIT_SQL 建表)。
963
978
  if (columns.length > 0 && !names.has("milestone_id"))
964
979
  db.exec("ALTER TABLE wand_tasks ADD COLUMN milestone_id TEXT");
980
+ // 归档时间(自动/手动归档都写它):保留期清理按它起算 7 天,历史归档行保持 NULL。
981
+ if (columns.length > 0 && !names.has("archived_at"))
982
+ db.exec("ALTER TABLE wand_tasks ADD COLUMN archived_at TEXT");
965
983
  if (columns.length > 0) {
966
984
  // Index creation must wait until the column exists on legacy databases.
967
985
  // INIT_SQL cannot create it: CREATE TABLE IF NOT EXISTS is a no-op on old
@@ -1086,7 +1104,7 @@ export function ensureDatabaseFile(dbPath) {
1086
1104
  }
1087
1105
  const WAND_TASK_FIELDS = `id, identifier, workspace_id, workspace_task_id, parent_task_id,
1088
1106
  title, title_source, auto_title_signature, description, status, priority, labels_json,
1089
- due_date, milestone_id, sort_order, agent_json, execution_subject_json, created_at, updated_at`;
1107
+ due_date, milestone_id, sort_order, agent_json, execution_subject_json, archived_at, created_at, updated_at`;
1090
1108
  // Shared metadata comes from the latest explicitly linked card, including meaningful NULLs.
1091
1109
  // The old container columns remain readable for legacy rows until the startup migration.
1092
1110
  const WORKSPACE_TASK_PROJECTION = `SELECT wt.id,
@@ -1559,9 +1577,12 @@ export class WandStorage {
1559
1577
  }
1560
1578
  tasksAggregateFingerprint() {
1561
1579
  const sessions = this.db.prepare(`SELECT COUNT(*) AS count,
1580
+ COALESCE(SUM(archived), 0) AS archivedCount,
1562
1581
  COALESCE(MAX(started_at), '') AS started,
1563
1582
  COALESCE(MAX(ended_at), '') AS ended,
1564
- COALESCE(MAX(title), '') AS title
1583
+ COALESCE(MAX(title), '') AS title,
1584
+ COALESCE(SUM(completion_revision), 0) AS completionRevision,
1585
+ COALESCE(SUM(viewed_completion_revision), 0) AS viewedCompletionRevision
1565
1586
  FROM command_sessions`).get();
1566
1587
  // Per-task metadata catches edits even when another task owns the maximum
1567
1588
  // timestamp/revision. Layout payloads are represented by their own revision.
@@ -1630,6 +1651,7 @@ export class WandStorage {
1630
1651
  })(),
1631
1652
  createdAt: String(row.created_at),
1632
1653
  updatedAt: String(row.updated_at),
1654
+ archivedAt: typeof row.archived_at === "string" && row.archived_at ? row.archived_at : null,
1633
1655
  };
1634
1656
  }
1635
1657
  getWandTask(id) {
@@ -1646,13 +1668,15 @@ export class WandStorage {
1646
1668
  insertWandTask(input, createdAt = nowIso()) {
1647
1669
  const id = crypto.randomUUID();
1648
1670
  const status = input.status ?? "todo";
1671
+ const createdAtValue = createdAt;
1672
+ const archivedAt = status === "archived" ? createdAtValue : null;
1649
1673
  const max = this.db.prepare(`SELECT COALESCE(MAX(sort_order), -1) AS value FROM wand_tasks
1650
1674
  WHERE workspace_id IS ? AND status = ?`).get(input.workspaceId ?? null, status);
1651
1675
  const number = this.db.prepare(`SELECT COALESCE(MAX(CAST(substr(identifier, 6) AS INTEGER)), 0) AS value
1652
1676
  FROM wand_tasks WHERE identifier GLOB 'TASK-[0-9]*'`).get();
1653
1677
  this.db.prepare(`INSERT INTO wand_tasks (${WAND_TASK_FIELDS})
1654
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1655
- .run(id, `TASK-${number.value + 1}`, input.workspaceId ?? null, input.workspaceTaskId ?? null, input.parentTaskId ?? null, input.title, input.titleSource ?? "user", null, input.description ?? "", status, input.priority ?? DEFAULT_WAND_TASK_PRIORITY, JSON.stringify(input.labels ?? []), input.dueDate ?? null, input.milestoneId ?? null, max.value + 1, input.agent ? JSON.stringify(input.agent) : null, input.executionSubject ? JSON.stringify(input.executionSubject) : null, createdAt, createdAt);
1678
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1679
+ .run(id, `TASK-${number.value + 1}`, input.workspaceId ?? null, input.workspaceTaskId ?? null, input.parentTaskId ?? null, input.title, input.titleSource ?? "user", null, input.description ?? "", status, input.priority ?? DEFAULT_WAND_TASK_PRIORITY, JSON.stringify(input.labels ?? []), input.dueDate ?? null, input.milestoneId ?? null, max.value + 1, input.agent ? JSON.stringify(input.agent) : null, input.executionSubject ? JSON.stringify(input.executionSubject) : null, archivedAt, createdAtValue, createdAtValue);
1656
1680
  return this.getWandTask(id);
1657
1681
  }
1658
1682
  createWandTask(input) {
@@ -1685,6 +1709,14 @@ export class WandStorage {
1685
1709
  /** Private canonical writer; callers own the transaction, never a reverse writer. */
1686
1710
  writeWandTask(current, patch) {
1687
1711
  const next = { ...current, ...patch, updatedAt: nowIso() };
1712
+ // 归档时间只在进入归档时写一次;取消归档/重新打开时清空,恢复“未动多久”重新计时。
1713
+ if (next.status === "archived") {
1714
+ if (!next.archivedAt)
1715
+ next.archivedAt = next.updatedAt;
1716
+ }
1717
+ else {
1718
+ next.archivedAt = null;
1719
+ }
1688
1720
  if (patch.milestoneId !== undefined || patch.workspaceId !== undefined) {
1689
1721
  next.milestoneId = this.taskMilestoneForWrite(next.milestoneId, next.workspaceId);
1690
1722
  }
@@ -1699,9 +1731,10 @@ export class WandStorage {
1699
1731
  }
1700
1732
  this.db.prepare(`UPDATE wand_tasks SET workspace_id = ?, workspace_task_id = ?, parent_task_id = ?,
1701
1733
  title = ?, title_source = ?, auto_title_signature = ?, description = ?, status = ?, priority = ?,
1702
- labels_json = ?, due_date = ?, milestone_id = ?, sort_order = ?, agent_json = ?, execution_subject_json = ?, updated_at = ?
1734
+ labels_json = ?, due_date = ?, milestone_id = ?, sort_order = ?, agent_json = ?, execution_subject_json = ?,
1735
+ archived_at = ?, updated_at = ?
1703
1736
  WHERE id = ?`)
1704
- .run(next.workspaceId, next.workspaceTaskId, next.parentTaskId, next.title, next.titleSource, next.autoTitleSignature ?? null, next.description, next.status, next.priority, JSON.stringify(next.labels), next.dueDate, next.milestoneId ?? null, next.sortOrder, next.agent ? JSON.stringify(next.agent) : null, next.executionSubject ? JSON.stringify(next.executionSubject) : null, next.updatedAt, next.id);
1737
+ .run(next.workspaceId, next.workspaceTaskId, next.parentTaskId, next.title, next.titleSource, next.autoTitleSignature ?? null, next.description, next.status, next.priority, JSON.stringify(next.labels), next.dueDate, next.milestoneId ?? null, next.sortOrder, next.agent ? JSON.stringify(next.agent) : null, next.executionSubject ? JSON.stringify(next.executionSubject) : null, next.archivedAt, next.updatedAt, next.id);
1705
1738
  if (patch.workspaceId !== undefined && next.workspaceTaskId) {
1706
1739
  const workspaceId = next.workspaceId ?? this.ensureGlobalWorkspace().id;
1707
1740
  // The container FK/index and standalone session workspace bucket follow the canonical project.
@@ -2416,18 +2449,22 @@ export class WandStorage {
2416
2449
  return existing;
2417
2450
  }
2418
2451
  saveSiliconEmployee(employee) {
2452
+ const tags = isBuiltinSiliconEmployee(employee)
2453
+ ? siliconEmployeeTags(employee)
2454
+ : parseSiliconEmployeeTags(employee.tags ?? []);
2419
2455
  this.db.prepare(`INSERT INTO silicon_employees (
2420
- id, name, duty, prompt, avatar, agents_json, system_key, archived_at, created_at, updated_at
2421
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2456
+ id, name, duty, prompt, avatar, agents_json, system_key, archived_at, created_at, updated_at, tags_json
2457
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2422
2458
  ON CONFLICT(id) DO UPDATE SET
2423
2459
  name = excluded.name,
2424
2460
  duty = excluded.duty,
2425
2461
  prompt = excluded.prompt,
2426
2462
  avatar = excluded.avatar,
2427
2463
  agents_json = excluded.agents_json,
2464
+ tags_json = excluded.tags_json,
2428
2465
  system_key = excluded.system_key,
2429
2466
  archived_at = excluded.archived_at,
2430
- updated_at = excluded.updated_at`).run(employee.id, employee.name, employee.duty, employee.prompt, employee.avatar, JSON.stringify(employee.agents), employee.systemKey ?? null, employee.archivedAt ?? null, employee.createdAt, employee.updatedAt);
2467
+ updated_at = excluded.updated_at`).run(employee.id, employee.name, employee.duty, employee.prompt, employee.avatar, JSON.stringify(employee.agents), employee.systemKey ?? null, employee.archivedAt ?? null, employee.createdAt, employee.updatedAt, JSON.stringify(tags));
2431
2468
  }
2432
2469
  archiveSiliconEmployee(id, archivedAt = new Date().toISOString()) {
2433
2470
  this.db.prepare("UPDATE silicon_employees SET archived_at = ?, updated_at = ? WHERE id = ?").run(archivedAt, new Date().toISOString(), id);
@@ -2530,6 +2567,27 @@ export class WandStorage {
2530
2567
  this.db.prepare("DELETE FROM employee_knowledge_access WHERE token_hash = ?")
2531
2568
  .run(crypto.createHash("sha256").update(token).digest("hex"));
2532
2569
  }
2570
+ /** Inference-only capability; never accepted by general HTTP authentication. */
2571
+ issueDecisionAccess(sessionId, now = Date.now()) {
2572
+ if (!this.getSessionSlim(sessionId))
2573
+ throw new Error("决策调用所属会话不存在。");
2574
+ this.db.prepare("DELETE FROM decision_access WHERE expires_at <= ? OR session_id = ?").run(now, sessionId);
2575
+ const token = `wd_${crypto.randomBytes(32).toString("base64url")}`;
2576
+ this.db.prepare("INSERT INTO decision_access (token_hash, session_id, expires_at) VALUES (?, ?, ?)")
2577
+ .run(crypto.createHash("sha256").update(token).digest("hex"), sessionId, now + 6 * 60 * 60 * 1000);
2578
+ return token;
2579
+ }
2580
+ resolveDecisionAccess(token, now = Date.now()) {
2581
+ if (!/^wd_[A-Za-z0-9_-]{43}$/.test(token))
2582
+ return null;
2583
+ const row = this.db.prepare("SELECT session_id FROM decision_access WHERE token_hash = ? AND expires_at > ?")
2584
+ .get(crypto.createHash("sha256").update(token).digest("hex"), now);
2585
+ return typeof row?.session_id === "string" ? row.session_id : null;
2586
+ }
2587
+ revokeDecisionAccess(token) {
2588
+ this.db.prepare("DELETE FROM decision_access WHERE token_hash = ?")
2589
+ .run(crypto.createHash("sha256").update(token).digest("hex"));
2590
+ }
2533
2591
  // ============ User short-term memory ============
2534
2592
  /** No IO on input hot paths; queued observations still CAS against the database before writing. */
2535
2593
  userMemoryCaptureState() {
@@ -2644,13 +2702,14 @@ export class WandStorage {
2644
2702
  // ============ AI Teams ============
2645
2703
  listAiTeams() {
2646
2704
  const rows = this.db.prepare("SELECT * FROM ai_teams ORDER BY created_at ASC").all();
2647
- return rows.map(mapAiTeamRow);
2705
+ return rows.map((row) => projectTeamDefinition(mapAiTeamRow(row), this));
2648
2706
  }
2649
2707
  getAiTeam(id) {
2650
2708
  const row = this.db.prepare("SELECT * FROM ai_teams WHERE id = ?").get(id);
2651
- return row ? mapAiTeamRow(row) : null;
2709
+ return row ? projectTeamDefinition(mapAiTeamRow(row), this) : null;
2652
2710
  }
2653
2711
  saveAiTeam(team) {
2712
+ team = freezeTeamEmployees(team, this);
2654
2713
  this.db.prepare(`INSERT INTO ai_teams (
2655
2714
  id, name, description, instructions, members_json, require_plan_approval, max_steps,
2656
2715
  created_at, updated_at
@@ -2678,7 +2737,7 @@ export class WandStorage {
2678
2737
  steps_used = excluded.steps_used, step_limit = excluded.step_limit,
2679
2738
  format_retries = excluded.format_retries, plan_approved = excluded.plan_approved,
2680
2739
  chat_session_id = excluded.chat_session_id, pending_notes_json = excluded.pending_notes_json,
2681
- updated_at = excluded.updated_at`).run(run.id, run.teamId, run.taskId, JSON.stringify(run.team), run.objective, run.cwd, run.status, run.statusDetail, run.stepsUsed, run.stepLimit, run.formatRetries, run.planApproved ? 1 : 0, run.chatSessionId, JSON.stringify(run.pendingNotes), run.createdAt, run.updatedAt);
2740
+ updated_at = excluded.updated_at`).run(run.id, run.teamId, run.taskId, serializeExecutionTeam(run.team), run.objective, run.cwd, run.status, run.statusDetail, run.stepsUsed, run.stepLimit, run.formatRetries, run.planApproved ? 1 : 0, run.chatSessionId, JSON.stringify(run.pendingNotes), run.createdAt, run.updatedAt);
2682
2741
  }
2683
2742
  /** 群聊会话对应的最近一次运行(同一个群聊可以接着开新运行)。 */
2684
2743
  getLatestAiTeamRunByChat(sessionId) {
@@ -2741,7 +2800,12 @@ export class WandStorage {
2741
2800
  // ai_teams.members_json 存的是裸成员数组;team_json 存的是整份团队对象。
2742
2801
  currentTeams.set(runId, safeJsonParse(typeof row.current_members_json === "string" ? row.current_members_json : null));
2743
2802
  }
2744
- const memberName = teamMemberName(currentTeams.get(runId), memberId)
2803
+ const frozenMember = normalizeAiTeamMembers(runTeam?.members).find((member) => member.id === memberId);
2804
+ const currentMember = normalizeAiTeamMembers(currentTeams.get(runId)).find((member) => member.id === memberId);
2805
+ const employee = frozenMember?.employeeId ? this.getSiliconEmployee(frozenMember.employeeId) : null;
2806
+ const memberName = (frozenMember?.employeeId
2807
+ ? employee?.name ?? frozenMember.name
2808
+ : currentMember?.employeeId ? undefined : teamMemberName(currentTeams.get(runId), memberId))
2745
2809
  ?? teamMemberName(runTeam?.members, memberId)
2746
2810
  ?? memberId;
2747
2811
  const runStatus = String(row.run_status ?? "");
@@ -2909,6 +2973,26 @@ export class WandStorage {
2909
2973
  else
2910
2974
  this.db.prepare("UPDATE agent_activity SET read_at = ? WHERE read_at IS NULL").run(at);
2911
2975
  }
2976
+ /** Completion/read state is independent of runner checkpoints and survives restarts. */
2977
+ getSessionCompletion(id) {
2978
+ const row = this.db.prepare(`SELECT completion_revision AS completionRevision,
2979
+ viewed_completion_revision AS viewedCompletionRevision FROM command_sessions WHERE id = ?`)
2980
+ .get(id);
2981
+ return row ? { ...row } : null;
2982
+ }
2983
+ recordSessionCompletion(id) {
2984
+ const row = this.db.prepare(`UPDATE command_sessions SET completion_revision = completion_revision + 1
2985
+ WHERE id = ? RETURNING completion_revision AS completionRevision,
2986
+ viewed_completion_revision AS viewedCompletionRevision`)
2987
+ .get(id);
2988
+ return row ? { ...row } : null;
2989
+ }
2990
+ /** A delayed view of generation N must never consume a newer completion N+1. */
2991
+ markSessionCompletionViewed(id, revision) {
2992
+ return this.db.prepare(`UPDATE command_sessions SET viewed_completion_revision = ?
2993
+ WHERE id = ? AND completion_revision = ? AND viewed_completion_revision < ?`)
2994
+ .run(revision, id, revision, revision).changes > 0;
2995
+ }
2912
2996
  saveSession(snapshot) {
2913
2997
  const isNew = !this.getSessionWorkspace(snapshot.id);
2914
2998
  // A single SQLite statement is already atomic. Avoid BEGIN IMMEDIATE in
@@ -3029,6 +3113,8 @@ function mapSiliconEmployeeRow(row) {
3029
3113
  .filter((candidate) => candidate !== null)
3030
3114
  : [];
3031
3115
  const systemKey = typeof row.system_key === "string" && row.system_key ? row.system_key : undefined;
3116
+ const rawTags = safeJsonParse(typeof row.tags_json === "string" ? row.tags_json : null);
3117
+ const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [];
3032
3118
  return {
3033
3119
  id: String(row.id),
3034
3120
  name: String(row.name),
@@ -3037,6 +3123,7 @@ function mapSiliconEmployeeRow(row) {
3037
3123
  avatar: String(row.avatar ?? ""),
3038
3124
  agents,
3039
3125
  systemKey,
3126
+ tags: siliconEmployeeTags({ systemKey, tags }),
3040
3127
  archivedAt: typeof row.archived_at === "string" && row.archived_at ? row.archived_at : undefined,
3041
3128
  createdAt: String(row.created_at),
3042
3129
  updatedAt: String(row.updated_at),
@@ -3085,6 +3172,20 @@ function normalizeAiTeamMember(raw) {
3085
3172
  member.avatar = value.avatar;
3086
3173
  if (isTeamMemberRole(value.role))
3087
3174
  member.role = value.role;
3175
+ if (typeof value.employeeId === "string" && value.employeeId.trim()) {
3176
+ member.employeeId = value.employeeId.trim();
3177
+ const raw = value._employeeSnapshot;
3178
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
3179
+ const snapshot = raw;
3180
+ if (snapshot.id === member.employeeId && typeof snapshot.prompt === "string") {
3181
+ restoreTeamMemberEmployee(member, {
3182
+ id: member.employeeId, name: member.name, avatar: member.avatar ?? "",
3183
+ prompt: snapshot.prompt, duty: String(snapshot.duty ?? ""), agents,
3184
+ createdAt: String(snapshot.createdAt ?? ""), updatedAt: String(snapshot.updatedAt ?? ""),
3185
+ });
3186
+ }
3187
+ }
3188
+ }
3088
3189
  return member;
3089
3190
  }
3090
3191
  function normalizeAiTeamMembers(raw) {
@@ -3386,6 +3487,8 @@ const SCHEMA_MIGRATIONS = [
3386
3487
  ["session_options", `ALTER TABLE command_sessions ADD COLUMN session_options TEXT NOT NULL DEFAULT '{"schemaVersion":1}'`],
3387
3488
  ["workspace_id", "ALTER TABLE command_sessions ADD COLUMN workspace_id TEXT"],
3388
3489
  ["workspace_task_id", "ALTER TABLE command_sessions ADD COLUMN workspace_task_id TEXT"],
3490
+ ["completion_revision", "ALTER TABLE command_sessions ADD COLUMN completion_revision INTEGER NOT NULL DEFAULT 0"],
3491
+ ["viewed_completion_revision", "ALTER TABLE command_sessions ADD COLUMN viewed_completion_revision INTEGER NOT NULL DEFAULT 0"],
3389
3492
  ];
3390
3493
  /** 首页目录组顺序的偏好键(app_config 表,与其它 UI 偏好同一套读写)。 */
3391
3494
  const WORKSPACE_GROUP_ORDER_KEY = "workspaceGroupOrder";
@@ -1,4 +1,6 @@
1
+ import { withToolPreview } from "./tool-preview.js";
1
2
  import { asRecord, isStructuredImagePart } from "./structured-content.js";
3
+ import { isDecisionToolCall, decisionCardSummary } from "./decision-tool.js";
2
4
  export const WAND_PROTOCOL_VERSION = 2;
3
5
  function text(value) {
4
6
  return typeof value === "string" && value ? value : undefined;
@@ -255,9 +257,42 @@ function rewriteInlineToolImageUrls(messages, sessionId) {
255
257
  * 传 sessionId 时额外把内联图片改写成取图 URL(见 rewriteInlineToolImageUrls)。
256
258
  */
257
259
  export function enrichStructuredMessages(messages, sessionId) {
260
+ // 决策卡缩略投影:入参与结果可能分属不同 turn(流式/分页),先按调用 id 配对,
261
+ // 让两个块拿到同一份摘要;只有窗口里存在的那一侧才参与计算。
262
+ const decisionCalls = new Map();
263
+ for (const turn of messages) {
264
+ for (const block of turn.content) {
265
+ if (block.type === "tool_use" && isDecisionToolCall(block)) {
266
+ const call = decisionCalls.get(block.id) ?? {};
267
+ call.use = block;
268
+ decisionCalls.set(block.id, call);
269
+ }
270
+ else if (block.type === "tool_result") {
271
+ const call = decisionCalls.get(block.tool_use_id);
272
+ if (call)
273
+ call.result = block;
274
+ }
275
+ }
276
+ }
277
+ const decisionIds = new Set(decisionCalls.keys());
278
+ const decisionSummaries = new Map([...decisionCalls].map(([id, call]) => [id, decisionCardSummary(call.use?.input, call.result)]));
279
+ const decisionSemantic = (id) => {
280
+ const summary = decisionSummaries.get(id);
281
+ return summary ? { kind: "decision", summary } : { kind: "decision" };
282
+ };
258
283
  const enriched = stampDerivedSubagents(messages).map((turn) => ({
259
284
  ...turn,
260
- content: turn.content.map((block) => {
285
+ content: turn.content.map(withToolPreview).map((block) => {
286
+ if (block.type === "tool_use" && decisionIds.has(block.id)) {
287
+ return { ...block, semantic: decisionSemantic(block.id), activity: undefined };
288
+ }
289
+ if (block.type === "tool_result" && decisionIds.has(block.tool_use_id)) {
290
+ return { ...block, semantic: decisionSemantic(block.tool_use_id) };
291
+ }
292
+ if (block.type === "tool_result" && block.semantic?.kind === "decision") {
293
+ const summary = decisionSummaries.get(block.tool_use_id) ?? block.semantic.summary;
294
+ return { ...block, semantic: { kind: "decision", ...(summary ? { summary } : {}) } };
295
+ }
261
296
  if (block.type !== "tool_use" || block.name !== "AskUserQuestion")
262
297
  return block;
263
298
  const questions = questionsFromInput(block.input);
@@ -1,5 +1,5 @@
1
+ import { isNativeThinkingEffort } from "./provider-catalog.js";
1
2
  import { shellQuote } from "./shell-quote.js";
2
- const NATIVE_THINKING_EFFORT = /^(claude|codex|opencode|grok|qoder|pi):[a-z0-9][a-z0-9_-]{0,31}$/;
3
3
  /**
4
4
  * 把会话级系统提示交给 CLI 的开关。Claude / Qoder / Pi 用 `--append-system-prompt`,
5
5
  * Grok 用 `--rules`;Codex / OpenCode / Gemini 没有这个入口,只能退回并入本轮消息
@@ -58,7 +58,7 @@ export function isThinkingEffort(value) {
58
58
  || value === "standard"
59
59
  || value === "deep"
60
60
  || value === "max"
61
- || (typeof value === "string" && NATIVE_THINKING_EFFORT.test(value));
61
+ || isNativeThinkingEffort(value);
62
62
  }
63
63
  /** 只取本 provider 的原生档位。别的 CLI 的前缀不能串过去。 */
64
64
  export function prefixedThinkingEffort(provider, effort) {
@@ -127,7 +127,7 @@ export function normalizeThinkingEffort(value) {
127
127
  const normalized = value.trim().toLowerCase();
128
128
  if (normalized === "off" || normalized === "standard" || normalized === "deep" || normalized === "max")
129
129
  return normalized;
130
- if (NATIVE_THINKING_EFFORT.test(normalized))
130
+ if (isNativeThinkingEffort(normalized))
131
131
  return normalized;
132
132
  return null;
133
133
  }
@@ -3,6 +3,7 @@ import { SessionLogger } from "./session-logger.js";
3
3
  import { WandStorage } from "./storage.js";
4
4
  import { ConversationTurn, ExecutionMode, ProcessEvent, SessionProvider, SessionRunner, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
5
5
  import type { WandTaskAgent } from "./task-types.js";
6
+ import { type DecisionRuntimeAccess } from "./decision-runner.js";
6
7
  import { type StructuredExecHost } from "./structured-exec-host.js";
7
8
  import type { StructuredRunnerAdapter } from "./structured-runner.js";
8
9
  export interface StructuredSessionManagerRunners {
@@ -69,6 +70,7 @@ export declare class StructuredSessionManager {
69
70
  private readonly execHost?;
70
71
  private readonly sessions;
71
72
  private readonly pendingRunnerExecutions;
73
+ private readonly unacceptedTeamStarts;
72
74
  private readonly interruptedWith;
73
75
  private readonly preserveQueueOnInterrupt;
74
76
  /** Last wall-clock time (ms) a streaming checkpoint reached SQLite. */
@@ -102,7 +104,7 @@ export declare class StructuredSessionManager {
102
104
  private detachedRecoveryRetryTimer;
103
105
  private detachedRecoveryFailureLogged;
104
106
  private disposed;
105
- constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, runners?: StructuredSessionManagerRunners, execHost?: StructuredExecHost | undefined);
107
+ constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, runners?: StructuredSessionManagerRunners, execHost?: StructuredExecHost | undefined, decisionRuntime?: () => DecisionRuntimeAccess | null);
106
108
  private archiveExpiredSessions;
107
109
  setEventEmitter(emitEvent: (event: ProcessEvent) => void): void;
108
110
  /** Stop every runner and flush terminal state before storage is closed. */
@@ -128,6 +130,8 @@ export declare class StructuredSessionManager {
128
130
  get(id: string): SessionSnapshot | null;
129
131
  setSessionWorkspace(id: string, membership: Pick<SessionSnapshot, "workspaceId" | "workspaceTaskId">): void;
130
132
  setSessionTopic(id: string, title: string, description: string): SessionSnapshot;
133
+ /** 归档 / 取消归档:只写标记并广播,不中断运行中的 run,也不删历史。 */
134
+ setSessionArchived(id: string, archived: boolean): SessionSnapshot;
131
135
  private setSessionTopicGenerating;
132
136
  /**
133
137
  * Update worktree merge progress on the canonical in-memory snapshot before
@@ -144,6 +148,10 @@ export declare class StructuredSessionManager {
144
148
  automationId: string;
145
149
  title: string;
146
150
  }): SessionSnapshot;
151
+ /** Server-only generation anchor, including a failed unaccepted turn whose activeRequestId is cleared. */
152
+ teamRequestId(id: string): string | null;
153
+ /** Consume only the exact failed request observed by the team's dispatch, never error-text inference. */
154
+ consumeUnacceptedTeamStartup(id: string, requestId: string): boolean;
147
155
  /** Only an unstarted employee conversation may change CLI after a failed first turn. */
148
156
  private retryEmployeeCandidate;
149
157
  /** 往转发会话里追加别的参与者的发言。 */
@@ -153,6 +161,7 @@ export declare class StructuredSessionManager {
153
161
  idempotencyKey?: string;
154
162
  preserveQueue?: boolean;
155
163
  queueAlreadyRemoved?: boolean;
164
+ teamRequestStarted?: (sessionId: string, requestId: string) => void;
156
165
  }): Promise<SessionSnapshot>;
157
166
  /**
158
167
  * Reorder the pending queued messages. `order` is a permutation of the current
@@ -175,6 +184,8 @@ export declare class StructuredSessionManager {
175
184
  promoteQueuedMessage(sessionId: string, index: number, expectedText?: string, idempotencyKey?: string): Promise<SessionSnapshot>;
176
185
  /** Clear all queued messages. No-op when queue is already empty. */
177
186
  clearQueuedMessages(sessionId: string): SessionSnapshot;
187
+ /** 新建空白对话可原位换 CLI;接受过输入、恢复会话和自动化不跨 provider 搬历史。 */
188
+ setSessionProvider(sessionId: string, provider: SessionProvider): SessionSnapshot;
178
189
  /** Update the selected model for a structured session. Takes effect on the next spawn. */
179
190
  setSessionModel(sessionId: string, model: string | null): SessionSnapshot;
180
191
  /**
@@ -196,6 +207,7 @@ export declare class StructuredSessionManager {
196
207
  denyPermission(sessionId: string): SessionSnapshot;
197
208
  /** Resolve a specific escalation by requestId. */
198
209
  resolveEscalation(sessionId: string, requestId: string, resolution: unknown): SessionSnapshot;
210
+ private requireRecoveryControl;
199
211
  stop(id: string): SessionSnapshot;
200
212
  delete(id: string): void;
201
213
  private requireSession;