@co0ontty/wand 4.89.5 → 4.90.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.
Files changed (197) hide show
  1. package/README.md +5 -3
  2. package/dist/agent-dispatch.js +4 -9
  3. package/dist/ai-team-employee-binding.js +10 -9
  4. package/dist/ai-team-runner.d.ts +2 -1
  5. package/dist/ai-team-runner.js +38 -2
  6. package/dist/ai-team-types.js +3 -2
  7. package/dist/ai-text-runner.d.ts +61 -0
  8. package/dist/ai-text-runner.js +653 -0
  9. package/dist/avatar-input.d.ts +2 -0
  10. package/dist/avatar-input.js +19 -0
  11. package/dist/bounded-process-output.d.ts +5 -0
  12. package/dist/bounded-process-output.js +9 -0
  13. package/dist/build-info.json +3 -3
  14. package/dist/command-error.d.ts +6 -0
  15. package/dist/command-error.js +8 -0
  16. package/dist/config.js +11 -42
  17. package/dist/conversation-service.d.ts +12 -2
  18. package/dist/conversation-service.js +104 -25
  19. package/dist/conversation-session-preview.d.ts +2 -0
  20. package/dist/conversation-session-preview.js +4 -0
  21. package/dist/decision-client.js +22 -6
  22. package/dist/decision-expert-employee.js +2 -2
  23. package/dist/decision-expert-identity.d.ts +1 -1
  24. package/dist/decision-expert-identity.js +1 -1
  25. package/dist/decision-expert-service.d.ts +1 -0
  26. package/dist/decision-expert-service.js +82 -76
  27. package/dist/decision-hardware.js +4 -3
  28. package/dist/decision-types.d.ts +4 -0
  29. package/dist/decision-types.js +4 -0
  30. package/dist/deployment-lock.d.ts +13 -0
  31. package/dist/deployment-lock.js +189 -0
  32. package/dist/employee-text.js +1 -1
  33. package/dist/fixed-employee-avatar.d.ts +7 -0
  34. package/dist/fixed-employee-avatar.js +13 -0
  35. package/dist/git-quick-commit.d.ts +5 -40
  36. package/dist/git-quick-commit.js +8 -636
  37. package/dist/hr-employee-identity.d.ts +5 -0
  38. package/dist/hr-employee-identity.js +5 -0
  39. package/dist/hr-employee.d.ts +5 -0
  40. package/dist/hr-employee.js +21 -0
  41. package/dist/middleware/path-safety.js +1 -1
  42. package/dist/milestone-scope.js +1 -1
  43. package/dist/model-auto-assign.d.ts +1 -1
  44. package/dist/model-auto-assign.js +1 -1
  45. package/dist/models.d.ts +4 -0
  46. package/dist/models.js +21 -1
  47. package/dist/npm-update-utils.js +5 -44
  48. package/dist/pi-auto-resources.js +6 -3
  49. package/dist/plush-avatar.d.ts +10 -1
  50. package/dist/plush-avatar.js +13 -1
  51. package/dist/plush-cat-appearance.d.ts +97 -0
  52. package/dist/plush-cat-appearance.js +118 -0
  53. package/dist/plush-cat-whiskers.d.ts +6 -0
  54. package/dist/plush-cat-whiskers.js +42 -0
  55. package/dist/process-manager.d.ts +22 -21
  56. package/dist/process-manager.js +49 -83
  57. package/dist/prompt-optimizer.d.ts +1 -1
  58. package/dist/prompt-optimizer.js +1 -1
  59. package/dist/render-host.js +1 -19
  60. package/dist/scheduled-task-service.d.ts +41 -0
  61. package/dist/scheduled-task-service.js +294 -0
  62. package/dist/scheduled-task-state.d.ts +11 -0
  63. package/dist/scheduled-task-state.js +65 -0
  64. package/dist/scheduled-task-time.d.ts +4 -0
  65. package/dist/scheduled-task-time.js +82 -0
  66. package/dist/scheduled-task-types.d.ts +71 -0
  67. package/dist/scheduled-task-types.js +1 -0
  68. package/dist/server-ai-team-routes.js +3 -17
  69. package/dist/server-employee-routes.d.ts +3 -0
  70. package/dist/server-employee-routes.js +20 -38
  71. package/dist/server-file-routes.js +44 -1
  72. package/dist/server-pi-recommendation-routes.js +13 -4
  73. package/dist/server-request.d.ts +2 -0
  74. package/dist/server-request.js +9 -0
  75. package/dist/server-resume-routes.js +1 -1
  76. package/dist/server-scheduled-task-routes.d.ts +4 -0
  77. package/dist/server-scheduled-task-routes.js +34 -0
  78. package/dist/server-session-routes.d.ts +5 -13
  79. package/dist/server-session-routes.js +35 -70
  80. package/dist/server-task-routes.d.ts +6 -21
  81. package/dist/server-task-routes.js +25 -120
  82. package/dist/server-team-dispatch-routes.js +12 -2
  83. package/dist/server-workspace-routes.d.ts +1 -1
  84. package/dist/server-workspace-routes.js +33 -78
  85. package/dist/server.js +55 -20
  86. package/dist/session-model.d.ts +15 -0
  87. package/dist/session-model.js +23 -0
  88. package/dist/session-registry.d.ts +5 -1
  89. package/dist/session-registry.js +20 -11
  90. package/dist/session-request.d.ts +9 -0
  91. package/dist/session-request.js +27 -0
  92. package/dist/session-topic.d.ts +1 -1
  93. package/dist/session-topic.js +1 -1
  94. package/dist/session-transport.d.ts +3 -1
  95. package/dist/session-transport.js +11 -5
  96. package/dist/silicon-employee-draft.d.ts +2 -2
  97. package/dist/silicon-employee-draft.js +3 -3
  98. package/dist/socket-probe.d.ts +2 -0
  99. package/dist/socket-probe.js +20 -0
  100. package/dist/storage-schedules.d.ts +20 -0
  101. package/dist/storage-schedules.js +78 -0
  102. package/dist/storage-schema.d.ts +3 -0
  103. package/dist/storage-schema.js +702 -0
  104. package/dist/storage.d.ts +42 -8
  105. package/dist/storage.js +182 -715
  106. package/dist/structured-claude-adapter.js +12 -0
  107. package/dist/structured-claude-protocol.d.ts +4 -0
  108. package/dist/structured-claude-protocol.js +18 -4
  109. package/dist/structured-codex-adapter.js +11 -0
  110. package/dist/structured-exec-pump.js +79 -38
  111. package/dist/structured-failure.d.ts +21 -0
  112. package/dist/structured-failure.js +14 -0
  113. package/dist/structured-gemini-adapter.js +12 -2
  114. package/dist/structured-grok-adapter.js +12 -2
  115. package/dist/structured-opencode-adapter.js +8 -1
  116. package/dist/structured-qoder-adapter.js +14 -0
  117. package/dist/structured-session-manager.d.ts +13 -18
  118. package/dist/structured-session-manager.js +147 -517
  119. package/dist/system-employee.d.ts +1 -1
  120. package/dist/system-employee.js +1 -1
  121. package/dist/task-archive-policy.js +1 -1
  122. package/dist/task-read-model.d.ts +27 -0
  123. package/dist/task-read-model.js +47 -0
  124. package/dist/task-retention.d.ts +1 -1
  125. package/dist/task-retention.js +1 -1
  126. package/dist/task-title-service.d.ts +30 -0
  127. package/dist/task-title-service.js +122 -0
  128. package/dist/task-title.d.ts +1 -1
  129. package/dist/task-title.js +1 -1
  130. package/dist/terminal-daemon-client.js +1 -18
  131. package/dist/user-memory.js +1 -1
  132. package/dist/web-ui/asset-runtime.d.ts +12 -0
  133. package/dist/web-ui/asset-runtime.js +6 -0
  134. package/dist/web-ui/composer-avatar-activity.d.ts +6 -0
  135. package/dist/web-ui/composer-avatar-activity.js +29 -0
  136. package/dist/web-ui/content/ai-teams.js +13 -13
  137. package/dist/web-ui/content/module-manifest.json +1 -0
  138. package/dist/web-ui/content/modules/5478be410b112154/chat-render-PY6DIXNZ.js +24 -0
  139. package/dist/web-ui/content/modules/5478be410b112154/chunk-2YZLCGYN.js +1 -0
  140. package/dist/web-ui/content/modules/5478be410b112154/chunk-6CFHW3FA.js +111 -0
  141. package/dist/web-ui/content/modules/5478be410b112154/chunk-6XQEBIEX.js +1 -0
  142. package/dist/web-ui/content/modules/5478be410b112154/chunk-DDSO7OX7.js +1 -0
  143. package/dist/web-ui/content/modules/5478be410b112154/chunk-G3CXNMA2.js +20 -0
  144. package/dist/web-ui/content/modules/5478be410b112154/chunk-LGVAPHRS.js +36 -0
  145. package/dist/web-ui/content/modules/5478be410b112154/chunk-NURCNMRU.js +1 -0
  146. package/dist/web-ui/content/modules/5478be410b112154/chunk-PCMFGZKX.js +1 -0
  147. package/dist/web-ui/content/modules/5478be410b112154/chunk-QRNA4D7E.js +1 -0
  148. package/dist/web-ui/content/modules/5478be410b112154/chunk-TMRPHNJO.js +1 -0
  149. package/dist/web-ui/content/modules/5478be410b112154/chunk-WC7RK7LP.js +1 -0
  150. package/dist/web-ui/content/modules/5478be410b112154/chunk-YOXXNXOX.js +2 -0
  151. package/dist/web-ui/content/modules/5478be410b112154/chunk-YX475LXO.js +1 -0
  152. package/dist/web-ui/content/modules/5478be410b112154/employee-card-MDWKU4V3.js +1 -0
  153. package/dist/web-ui/content/modules/5478be410b112154/host-56WPSON2.js +1 -0
  154. package/dist/web-ui/content/modules/5478be410b112154/host-5QOHKHNC.js +1 -0
  155. package/dist/web-ui/content/modules/5478be410b112154/host-AWIJ5L2K.js +1 -0
  156. package/dist/web-ui/content/modules/5478be410b112154/host-BOZLGIT5.js +69 -0
  157. package/dist/web-ui/content/modules/5478be410b112154/host-EZV22Q7Y.js +4 -0
  158. package/dist/web-ui/content/modules/5478be410b112154/host-IW43Z5ZF.js +1 -0
  159. package/dist/web-ui/content/modules/5478be410b112154/host-JT7NIUQL.js +1 -0
  160. package/dist/web-ui/content/modules/5478be410b112154/host-JWEIDB55.js +3 -0
  161. package/dist/web-ui/content/modules/5478be410b112154/host-OU5TRYH6.js +2 -0
  162. package/dist/web-ui/content/modules/5478be410b112154/host-PRSTCPVE.js +5 -0
  163. package/dist/web-ui/content/modules/5478be410b112154/host-TWPJI3HE.js +1 -0
  164. package/dist/web-ui/content/modules/5478be410b112154/host-WZGJSNML.js +1 -0
  165. package/dist/web-ui/content/modules/5478be410b112154/host-X6KQVFD6.js +1 -0
  166. package/dist/web-ui/content/modules/5478be410b112154/host-YKCQFMQR.js +1 -0
  167. package/dist/web-ui/content/modules/5478be410b112154/host-bridge-2LZX6UUZ.js +1 -0
  168. package/dist/web-ui/content/modules/5478be410b112154/library-bridge-MQFJGTGY.js +4 -0
  169. package/dist/web-ui/content/modules/5478be410b112154/page-T3VUCU2W.js +3 -0
  170. package/dist/web-ui/content/modules/5478be410b112154/panel-HJBGBBPY.js +1 -0
  171. package/dist/web-ui/content/modules/5478be410b112154/runtime.js +920 -0
  172. package/dist/web-ui/content/modules/5478be410b112154/sha256-ANXV72UC.js +1 -0
  173. package/dist/web-ui/content/modules/5478be410b112154/task-board-host-AVMDIHIV.js +2 -0
  174. package/dist/web-ui/content/modules/5478be410b112154/workspace-agent-picker-KI7QVSK3.js +1 -0
  175. package/dist/web-ui/content/modules/5478be410b112154/workspace-window-L542FGP2.js +1 -0
  176. package/dist/web-ui/content/plush-avatar.js +177 -177
  177. package/dist/web-ui/content/scripts.js +1 -994
  178. package/dist/web-ui/content/styles.css +1 -1
  179. package/dist/web-ui/content/tailwind.css +1 -1
  180. package/dist/web-ui/embedded-assets.d.ts +32 -0
  181. package/dist/web-ui/embedded-assets.js +10 -8
  182. package/dist/web-ui/markdown.d.ts +3 -2
  183. package/dist/web-ui/markdown.js +61 -48
  184. package/dist/web-ui/message-file-target.d.ts +13 -0
  185. package/dist/web-ui/message-file-target.js +85 -0
  186. package/dist/web-ui/provider-identity.d.ts +1 -1
  187. package/dist/web-ui/provider-identity.js +3 -4
  188. package/dist/web-ui/react/local-preview/controller.d.ts +2 -0
  189. package/dist/web-ui/react/local-preview/controller.js +12 -0
  190. package/dist/web-ui/scripts.d.ts +10 -0
  191. package/dist/web-ui/scripts.js +42 -5
  192. package/package.json +6 -3
  193. package/browser-extension/icons/wand-128.png +0 -0
  194. package/browser-extension/icons/wand-16.png +0 -0
  195. package/browser-extension/icons/wand-32.png +0 -0
  196. package/browser-extension/icons/wand-48.png +0 -0
  197. package/dist/web-ui/content/licenses/codex-LICENSE.txt +0 -21
package/dist/storage.js CHANGED
@@ -1,33 +1,37 @@
1
1
  import crypto from "node:crypto";
2
- import { FIXED_EMPLOYEE_AVATARS, employeeAvatarValue, fixedEmployeeAvatar, isFixedAvatarEmployee } from "./fixed-employee-avatar.js";
3
- import { CONVERSATION_OWNER } from "./conversation-types.js";
4
- import { chmodSync, existsSync, mkdirSync } from "node:fs";
2
+ import { chmodSync, mkdirSync } from "node:fs";
5
3
  import path from "node:path";
6
4
  import { DatabaseSync } from "node:sqlite";
7
- import { GLOBAL_WORKSPACE_ID } from "./types.js";
8
- import { normalizeSessionDirectory } from "./session-directory-tree.js";
9
- import { ensureWorkspaceForCwd } from "./workspace-binding.js";
10
- import { defaultPiCliSessionSettings, defaultPiSessionSettings, patchPiSessionSettings } from "./pi-session-settings.js";
11
- import { parseHarnessExtensionState } from "./core-extension-host.js";
12
- import { inferProviderFromCommand, inferProviderFromRunner, isSessionProvider, SESSION_PROVIDERS } from "./session-provider.js";
13
- import { DEFAULT_ITERATION_NAME, DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_PRIORITY, isWandTaskAgentEngine, isWandTaskAgentKind, normalizeWandTaskAgentMode } from "./task-types.js";
14
- import { firstLayoutTabId } from "./layout-tree.js";
15
- import { isUnnamedWorkspaceTaskName } from "./wand-task-sync.js";
16
- import { AI_TEAM_DEFAULT_MAX_STEPS, AI_TEAM_TERMINAL_RUN_STATUSES, aiTeamChatTitle, isBuiltinSiliconEmployee, isTeamMemberRole, memberAgents, parseSiliconEmployeeTags, siliconEmployeeTags } from "./ai-team-types.js";
17
5
  import { freezeTeamEmployees, projectTeamDefinition, restoreTeamMemberEmployee, serializeExecutionTeam } from "./ai-team-employee-binding.js";
18
- import { isTaskExecutionSubject } from "./task-types.js";
19
- import { SYSTEM_EMPLOYEE_KEY, systemEmployeeDefinition, systemEmployeeSeedAgents, } from "./system-employee.js";
20
- import { isThinkingEffort } from "./structured-provider-common.js";
21
- import { DEFAULT_EMPLOYEE_KEY } from "./ai-team-types.js";
22
- import { defaultEmployeeDefinition, legacyPtyRoleIdentity } from "./default-employee.js";
23
- import { DECISION_EXPERT_KEY } from "./decision-expert-identity.js";
24
- import { SPEECH_POLISHER_KEY } from "./speech-polisher-identity.js";
25
- import { speechPolisherDefinition } from "./speech-polisher-employee.js";
6
+ import { AI_TEAM_DEFAULT_MAX_STEPS, AI_TEAM_TERMINAL_RUN_STATUSES, DEFAULT_EMPLOYEE_KEY, aiTeamChatTitle, isBuiltinSiliconEmployee, isTeamMemberRole, memberAgents, parseSiliconEmployeeTags, siliconEmployeeTags } from "./ai-team-types.js";
7
+ import { CONVERSATION_OWNER } from "./conversation-types.js";
8
+ import { parseHarnessExtensionState } from "./core-extension-host.js";
26
9
  import { decisionExpertDefinition } from "./decision-expert-employee.js";
10
+ import { DECISION_EXPERT_KEY } from "./decision-expert-identity.js";
11
+ import { defaultEmployeeDefinition, legacyPtyRoleIdentity } from "./default-employee.js";
27
12
  import { normalizeEmployeeKnowledge } from "./employee-knowledge-content.js";
28
13
  import { EMPLOYEE_KNOWLEDGE_MAX_ENTRIES } from "./employee-knowledge-types.js";
29
- import { USER_MEMORY_MAX_EVENTS, USER_MEMORY_RETENTION_MS, } from "./user-memory-types.js";
14
+ import { FIXED_EMPLOYEE_AVATARS, employeeAvatarValue, fixedEmployeeAvatar, isFixedAvatarEmployee } from "./fixed-employee-avatar.js";
15
+ import { HR_EMPLOYEE_KEY } from "./hr-employee-identity.js";
16
+ import { hrEmployeeDefinition } from "./hr-employee.js";
17
+ import { firstLayoutTabId } from "./layout-tree.js";
30
18
  import { DEFAULT_PASSWORD_VAULT_ID, DEFAULT_PASSWORD_VAULT_NAME, decryptVaultSecret, encryptVaultSecret, itemMatchesFilter, normalizePasswordItemInput, normalizeVaultName, nowIso, } from "./password-manager.js";
19
+ import { defaultPiCliSessionSettings, defaultPiSessionSettings, patchPiSessionSettings } from "./pi-session-settings.js";
20
+ import { normalizeSessionDirectory } from "./session-directory-tree.js";
21
+ import { SESSION_PROVIDERS, inferProviderFromCommand, inferProviderFromRunner, isSessionProvider } from "./session-provider.js";
22
+ import { speechPolisherDefinition } from "./speech-polisher-employee.js";
23
+ import { SPEECH_POLISHER_KEY } from "./speech-polisher-identity.js";
24
+ import { ScheduledTaskRepository } from "./storage-schedules.js";
25
+ import { initializeStorageSchema } from "./storage-schema.js";
26
+ import { isThinkingEffort } from "./structured-provider-common.js";
27
+ import { SYSTEM_EMPLOYEE_KEY, systemEmployeeDefinition, systemEmployeeSeedAgents, } from "./system-employee.js";
28
+ import { DEFAULT_ITERATION_NAME, DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_PRIORITY, isTaskExecutionSubject, isWandTaskAgentEngine, isWandTaskAgentKind, normalizeWandTaskAgentMode } from "./task-types.js";
29
+ import { GLOBAL_WORKSPACE_ID } from "./types.js";
30
+ import { USER_MEMORY_MAX_EVENTS, USER_MEMORY_RETENTION_MS, } from "./user-memory-types.js";
31
+ import { isUnnamedWorkspaceTaskName } from "./wand-task-sync.js";
32
+ import { ensureWorkspaceForCwd } from "./workspace-binding.js";
33
+ // These system profiles are code-owned; candidates and timestamps are not part of the lock.
34
+ const LOCKED_SYSTEM_EMPLOYEE_DEFINITIONS = [decisionExpertDefinition(""), speechPolisherDefinition(""), hrEmployeeDefinition("")];
31
35
  function safeJsonParse(raw) {
32
36
  if (!raw)
33
37
  return undefined;
@@ -585,590 +589,7 @@ const DEFAULT_DB_FILE = "wand.db";
585
589
  export function resolveDatabasePath(configPath) {
586
590
  return path.resolve(path.dirname(configPath), DEFAULT_DB_FILE);
587
591
  }
588
- const INIT_SQL = `
589
- CREATE TABLE IF NOT EXISTS auth_sessions (
590
- token TEXT PRIMARY KEY,
591
- expires_at INTEGER NOT NULL,
592
- kind TEXT NOT NULL DEFAULT 'browser-admin',
593
- scopes TEXT NOT NULL DEFAULT '["admin"]'
594
- );
595
-
596
- CREATE TABLE IF NOT EXISTS command_sessions (
597
- id TEXT PRIMARY KEY,
598
- session_source TEXT NOT NULL DEFAULT 'interactive',
599
- automation_id TEXT,
600
- command TEXT NOT NULL,
601
- cwd TEXT NOT NULL,
602
- mode TEXT NOT NULL,
603
- status TEXT NOT NULL,
604
- exit_code INTEGER,
605
- started_at TEXT NOT NULL,
606
- ended_at TEXT,
607
- output TEXT NOT NULL,
608
- pty_output_seq INTEGER NOT NULL DEFAULT 0,
609
- archived INTEGER NOT NULL DEFAULT 0,
610
- archived_at TEXT,
611
- claude_session_id TEXT,
612
- provider TEXT,
613
- session_kind TEXT NOT NULL DEFAULT 'pty',
614
- runner TEXT,
615
- messages TEXT,
616
- queued_messages TEXT,
617
- queued_message_skills TEXT,
618
- structured_state TEXT,
619
- resumed_from_session_id TEXT,
620
- resumed_to_session_id TEXT,
621
- auto_recovered INTEGER NOT NULL DEFAULT 0,
622
- worktree_enabled INTEGER NOT NULL DEFAULT 0,
623
- worktree_info TEXT,
624
- worktree_merge_status TEXT,
625
- worktree_merge_info TEXT,
626
- title TEXT,
627
- description TEXT,
628
- session_options TEXT NOT NULL DEFAULT '{"schemaVersion":1}'
629
- );
630
-
631
- CREATE TABLE IF NOT EXISTS app_config (
632
- key TEXT PRIMARY KEY,
633
- value TEXT NOT NULL
634
- );
635
-
636
- CREATE TABLE IF NOT EXISTS session_directory_names (
637
- path TEXT PRIMARY KEY,
638
- name TEXT NOT NULL,
639
- updated_at TEXT NOT NULL
640
- );
641
-
642
- CREATE TABLE IF NOT EXISTS password_vaults (
643
- id TEXT PRIMARY KEY,
644
- name TEXT NOT NULL,
645
- created_at TEXT NOT NULL,
646
- updated_at TEXT NOT NULL
647
- );
648
-
649
- CREATE TABLE IF NOT EXISTS connectors (
650
- provider TEXT PRIMARY KEY,
651
- token TEXT NOT NULL DEFAULT '',
652
- api_url TEXT NOT NULL DEFAULT '',
653
- username TEXT,
654
- connected_at TEXT,
655
- created_at TEXT NOT NULL,
656
- updated_at TEXT NOT NULL
657
- );
658
-
659
- CREATE TABLE IF NOT EXISTS password_items (
660
- id TEXT PRIMARY KEY,
661
- vault_id TEXT NOT NULL,
662
- type TEXT NOT NULL,
663
- title TEXT NOT NULL,
664
- username TEXT,
665
- password TEXT,
666
- urls TEXT NOT NULL DEFAULT '[]',
667
- notes TEXT,
668
- fields TEXT NOT NULL DEFAULT '{}',
669
- tags TEXT NOT NULL DEFAULT '[]',
670
- favorite INTEGER NOT NULL DEFAULT 0,
671
- archived INTEGER NOT NULL DEFAULT 0,
672
- created_at TEXT NOT NULL,
673
- updated_at TEXT NOT NULL,
674
- last_used_at TEXT,
675
- password_updated_at TEXT,
676
- FOREIGN KEY(vault_id) REFERENCES password_vaults(id)
677
- );
678
-
679
- CREATE INDEX IF NOT EXISTS idx_password_items_vault ON password_items(vault_id);
680
- CREATE INDEX IF NOT EXISTS idx_password_items_type ON password_items(type);
681
- CREATE INDEX IF NOT EXISTS idx_password_items_updated ON password_items(updated_at);
682
-
683
- CREATE TABLE IF NOT EXISTS missions (
684
- id TEXT PRIMARY KEY,
685
- title TEXT NOT NULL,
686
- prompt TEXT NOT NULL,
687
- cwd TEXT NOT NULL,
688
- status TEXT NOT NULL,
689
- task_id TEXT,
690
- milestone_id TEXT,
691
- base_ref TEXT,
692
- shared_directories TEXT NOT NULL DEFAULT '[]',
693
- copy_paths TEXT NOT NULL DEFAULT '[]',
694
- created_at TEXT NOT NULL,
695
- updated_at TEXT NOT NULL
696
- );
697
-
698
- CREATE TABLE IF NOT EXISTS mission_attempts (
699
- id TEXT PRIMARY KEY,
700
- mission_id TEXT NOT NULL,
701
- session_id TEXT,
702
- provider TEXT NOT NULL,
703
- state TEXT NOT NULL,
704
- branch TEXT,
705
- worktree_path TEXT,
706
- base_ref TEXT,
707
- summary TEXT,
708
- error TEXT,
709
- created_at TEXT NOT NULL,
710
- updated_at TEXT NOT NULL,
711
- FOREIGN KEY(mission_id) REFERENCES missions(id)
712
- );
713
-
714
- CREATE TABLE IF NOT EXISTS mission_review_comments (
715
- id TEXT PRIMARY KEY,
716
- mission_id TEXT NOT NULL,
717
- attempt_id TEXT NOT NULL,
718
- file_path TEXT NOT NULL,
719
- line INTEGER,
720
- side TEXT NOT NULL DEFAULT 'new',
721
- body TEXT NOT NULL,
722
- status TEXT NOT NULL DEFAULT 'pending',
723
- created_at TEXT NOT NULL,
724
- sent_at TEXT,
725
- resolved_at TEXT,
726
- FOREIGN KEY(mission_id) REFERENCES missions(id),
727
- FOREIGN KEY(attempt_id) REFERENCES mission_attempts(id)
728
- );
729
-
730
- CREATE TABLE IF NOT EXISTS agent_activity (
731
- session_id TEXT PRIMARY KEY,
732
- mission_id TEXT,
733
- attempt_id TEXT,
734
- state TEXT NOT NULL,
735
- title TEXT NOT NULL,
736
- summary TEXT,
737
- provider TEXT,
738
- cwd TEXT,
739
- updated_at TEXT NOT NULL,
740
- read_at TEXT
741
- );
742
-
743
- CREATE INDEX IF NOT EXISTS idx_missions_updated ON missions(updated_at);
744
- CREATE INDEX IF NOT EXISTS idx_mission_attempts_mission ON mission_attempts(mission_id);
745
- CREATE UNIQUE INDEX IF NOT EXISTS idx_mission_attempts_session ON mission_attempts(session_id) WHERE session_id IS NOT NULL;
746
- CREATE INDEX IF NOT EXISTS idx_mission_comments_attempt ON mission_review_comments(attempt_id, status);
747
- CREATE INDEX IF NOT EXISTS idx_agent_activity_state ON agent_activity(state, updated_at);
748
-
749
- CREATE TABLE IF NOT EXISTS workspaces (
750
- id TEXT PRIMARY KEY,
751
- name TEXT NOT NULL,
752
- cwd TEXT NOT NULL,
753
- kind TEXT NOT NULL DEFAULT 'project',
754
- default_provider TEXT,
755
- layout_json TEXT,
756
- created_at TEXT NOT NULL,
757
- last_opened_at TEXT
758
- );
759
-
760
- CREATE INDEX IF NOT EXISTS idx_workspaces_cwd ON workspaces(cwd);
761
- CREATE INDEX IF NOT EXISTS idx_workspaces_last_opened ON workspaces(last_opened_at);
762
-
763
- CREATE TABLE IF NOT EXISTS workspace_tasks (
764
- id TEXT PRIMARY KEY,
765
- workspace_id TEXT NOT NULL,
766
- name TEXT NOT NULL,
767
- worktree_json TEXT,
768
- layout_json TEXT,
769
- status TEXT NOT NULL DEFAULT 'active',
770
- cwd TEXT,
771
- milestone_id TEXT,
772
- created_at TEXT NOT NULL,
773
- last_opened_at TEXT,
774
- layout_revision INTEGER NOT NULL DEFAULT 0,
775
- FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE CASCADE
776
- );
777
-
778
- CREATE INDEX IF NOT EXISTS idx_workspace_tasks_workspace ON workspace_tasks(workspace_id);
779
- CREATE INDEX IF NOT EXISTS idx_workspace_tasks_last_opened ON workspace_tasks(last_opened_at);
780
-
781
- CREATE TABLE IF NOT EXISTS github_issue_bindings (
782
- owner TEXT NOT NULL,
783
- repo TEXT NOT NULL,
784
- issue_number INTEGER NOT NULL,
785
- session_id TEXT NOT NULL,
786
- bound_at TEXT NOT NULL,
787
- PRIMARY KEY (owner, repo, issue_number, session_id),
788
- FOREIGN KEY (session_id) REFERENCES command_sessions(id) ON DELETE CASCADE
789
- );
790
- CREATE INDEX IF NOT EXISTS idx_github_issue_bindings_session ON github_issue_bindings(session_id);
791
-
792
- CREATE TABLE IF NOT EXISTS wand_milestones (
793
- id TEXT PRIMARY KEY,
794
- name TEXT NOT NULL,
795
- due_date TEXT,
796
- workspace_id TEXT,
797
- is_default INTEGER NOT NULL DEFAULT 0,
798
- created_at TEXT NOT NULL,
799
- updated_at TEXT NOT NULL
800
- );
801
- CREATE INDEX IF NOT EXISTS idx_wand_milestones_created ON wand_milestones(created_at DESC);
802
-
803
- -- 迭代提示词记录:用户在本轮迭代里发过的提示词标题,供 commit message / 汇报类生成复用。
804
- -- 不建外键:删除迭代只把记录改挂到默认迭代,绝不删记录。
805
- CREATE TABLE IF NOT EXISTS wand_iteration_prompts (
806
- id TEXT PRIMARY KEY,
807
- milestone_id TEXT NOT NULL,
808
- workspace_id TEXT,
809
- session_id TEXT,
810
- task_id TEXT,
811
- repo_key TEXT,
812
- cwd TEXT NOT NULL,
813
- title TEXT NOT NULL,
814
- detail TEXT NOT NULL DEFAULT '',
815
- source TEXT NOT NULL DEFAULT 'session',
816
- consumed_at TEXT,
817
- consumed_commit TEXT,
818
- created_at TEXT NOT NULL
819
- );
820
- CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_iteration ON wand_iteration_prompts(milestone_id, created_at DESC);
821
- CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_repo ON wand_iteration_prompts(repo_key, created_at DESC);
822
- CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_session ON wand_iteration_prompts(session_id, created_at DESC);
823
-
824
- CREATE TABLE IF NOT EXISTS wand_tasks (
825
- id TEXT PRIMARY KEY,
826
- identifier TEXT,
827
- workspace_id TEXT,
828
- title TEXT NOT NULL,
829
- description TEXT NOT NULL DEFAULT '',
830
- status TEXT NOT NULL DEFAULT 'todo',
831
- priority TEXT NOT NULL DEFAULT 'none',
832
- labels_json TEXT NOT NULL DEFAULT '[]',
833
- due_date TEXT,
834
- milestone_id TEXT,
835
- sort_order INTEGER NOT NULL DEFAULT 0,
836
- agent_json TEXT,
837
- execution_subject_json TEXT,
838
- workspace_task_id TEXT,
839
- parent_task_id TEXT,
840
- title_source TEXT,
841
- auto_title_signature TEXT,
842
- archived_at TEXT,
843
- created_at TEXT NOT NULL,
844
- updated_at TEXT NOT NULL,
845
- FOREIGN KEY (workspace_id) REFERENCES workspaces(id) ON DELETE SET NULL
846
- );
847
- CREATE INDEX IF NOT EXISTS idx_wand_tasks_board ON wand_tasks(workspace_id, status, sort_order, updated_at);
848
- CREATE TABLE IF NOT EXISTS wand_task_sessions (
849
- task_id TEXT NOT NULL,
850
- session_id TEXT NOT NULL,
851
- bound_at TEXT NOT NULL,
852
- PRIMARY KEY (task_id, session_id),
853
- FOREIGN KEY (task_id) REFERENCES wand_tasks(id) ON DELETE CASCADE,
854
- FOREIGN KEY (session_id) REFERENCES command_sessions(id) ON DELETE CASCADE
855
- );
856
- CREATE INDEX IF NOT EXISTS idx_wand_task_sessions_session ON wand_task_sessions(session_id);
857
-
858
- CREATE TABLE IF NOT EXISTS ai_teams (
859
- id TEXT PRIMARY KEY,
860
- name TEXT NOT NULL,
861
- description TEXT NOT NULL DEFAULT '',
862
- instructions TEXT NOT NULL DEFAULT '',
863
- members_json TEXT NOT NULL,
864
- require_plan_approval INTEGER NOT NULL DEFAULT 1,
865
- max_steps INTEGER NOT NULL DEFAULT 30,
866
- created_at TEXT NOT NULL,
867
- updated_at TEXT NOT NULL
868
- );
869
-
870
- CREATE TABLE IF NOT EXISTS silicon_employees (
871
- id TEXT PRIMARY KEY,
872
- name TEXT NOT NULL,
873
- duty TEXT NOT NULL DEFAULT '',
874
- prompt TEXT NOT NULL DEFAULT '',
875
- avatar TEXT NOT NULL DEFAULT '',
876
- agents_json TEXT NOT NULL,
877
- system_key TEXT,
878
- archived_at TEXT,
879
- created_at TEXT NOT NULL,
880
- updated_at TEXT NOT NULL
881
- );
882
- CREATE INDEX IF NOT EXISTS idx_silicon_employees_archived ON silicon_employees(archived_at);
883
-
884
- CREATE TABLE IF NOT EXISTS employee_knowledge (
885
- id TEXT PRIMARY KEY,
886
- employee_id TEXT NOT NULL REFERENCES silicon_employees(id) ON DELETE CASCADE,
887
- content TEXT NOT NULL,
888
- content_hash TEXT NOT NULL,
889
- created_at TEXT NOT NULL,
890
- UNIQUE (employee_id, content_hash)
891
- );
892
- CREATE INDEX IF NOT EXISTS idx_employee_knowledge_owner ON employee_knowledge(employee_id, created_at);
893
- CREATE TABLE IF NOT EXISTS employee_knowledge_access (
894
- token_hash TEXT PRIMARY KEY,
895
- employee_id TEXT NOT NULL REFERENCES silicon_employees(id) ON DELETE CASCADE,
896
- session_id TEXT NOT NULL REFERENCES command_sessions(id) ON DELETE CASCADE,
897
- expires_at INTEGER NOT NULL
898
- );
899
- CREATE INDEX IF NOT EXISTS idx_employee_knowledge_access_expiry ON employee_knowledge_access(expires_at);
900
-
901
- CREATE TABLE IF NOT EXISTS decision_access (
902
- token_hash TEXT PRIMARY KEY,
903
- session_id TEXT NOT NULL REFERENCES command_sessions(id) ON DELETE CASCADE,
904
- expires_at INTEGER NOT NULL
905
- );
906
- CREATE INDEX IF NOT EXISTS idx_decision_access_expiry ON decision_access(expires_at);
907
-
908
- CREATE TABLE IF NOT EXISTS openrouter_free_access (
909
- token_hash TEXT PRIMARY KEY,
910
- session_id TEXT NOT NULL REFERENCES command_sessions(id) ON DELETE CASCADE,
911
- selector TEXT NOT NULL,
912
- requirements_json TEXT NOT NULL,
913
- credential_hash TEXT NOT NULL,
914
- expires_at INTEGER NOT NULL
915
- );
916
- CREATE INDEX IF NOT EXISTS idx_openrouter_free_access_expiry ON openrouter_free_access(expires_at);
917
-
918
- CREATE TABLE IF NOT EXISTS user_memory_events (
919
- id INTEGER PRIMARY KEY AUTOINCREMENT,
920
- feature TEXT NOT NULL,
921
- text TEXT NOT NULL DEFAULT '',
922
- dedup_key TEXT NOT NULL,
923
- created_at INTEGER NOT NULL
924
- );
925
- CREATE INDEX IF NOT EXISTS idx_user_memory_events_created ON user_memory_events(created_at);
926
- CREATE INDEX IF NOT EXISTS idx_user_memory_events_dedup ON user_memory_events(dedup_key, created_at);
927
- CREATE TABLE IF NOT EXISTS user_memory_state (
928
- id INTEGER PRIMARY KEY CHECK (id = 1),
929
- enabled INTEGER NOT NULL DEFAULT 1,
930
- revision INTEGER NOT NULL DEFAULT 0,
931
- last_attempt_at INTEGER NOT NULL DEFAULT 0,
932
- source_id INTEGER NOT NULL DEFAULT 0,
933
- profile_json TEXT
934
- );
935
- INSERT OR IGNORE INTO user_memory_state (id) VALUES (1);
936
-
937
- CREATE TABLE IF NOT EXISTS ai_team_runs (
938
- id TEXT PRIMARY KEY,
939
- team_id TEXT NOT NULL,
940
- task_id TEXT NOT NULL,
941
- team_json TEXT NOT NULL,
942
- objective TEXT NOT NULL,
943
- cwd TEXT NOT NULL,
944
- status TEXT NOT NULL,
945
- status_detail TEXT NOT NULL DEFAULT '',
946
- steps_used INTEGER NOT NULL DEFAULT 0,
947
- step_limit INTEGER NOT NULL,
948
- format_retries INTEGER NOT NULL DEFAULT 0,
949
- plan_approved INTEGER NOT NULL DEFAULT 0,
950
- created_at TEXT NOT NULL,
951
- updated_at TEXT NOT NULL
952
- );
953
- CREATE INDEX IF NOT EXISTS idx_ai_team_runs_task ON ai_team_runs(task_id, created_at);
954
- CREATE INDEX IF NOT EXISTS idx_ai_team_runs_status ON ai_team_runs(status);
955
-
956
- CREATE TABLE IF NOT EXISTS ai_team_steps (
957
- id TEXT PRIMARY KEY,
958
- run_id TEXT NOT NULL,
959
- seq INTEGER NOT NULL,
960
- kind TEXT NOT NULL,
961
- member_id TEXT NOT NULL,
962
- title TEXT NOT NULL,
963
- instructions TEXT NOT NULL,
964
- session_id TEXT,
965
- status TEXT NOT NULL,
966
- report TEXT NOT NULL DEFAULT '',
967
- report_path TEXT NOT NULL,
968
- depends_on_json TEXT NOT NULL DEFAULT '[]',
969
- started_at TEXT,
970
- ended_at TEXT,
971
- UNIQUE (run_id, seq)
972
- );
973
- CREATE INDEX IF NOT EXISTS idx_ai_team_steps_session ON ai_team_steps(session_id, status);
974
- `;
975
- /** AI 团队表的后加列;只加不删,历史行取默认值。 */
976
- function ensureAiTeamSchema(db) {
977
- const teamColumns = new Set(db.prepare("PRAGMA table_info(ai_teams)").all().map((column) => column.name));
978
- if (!teamColumns.has("instructions"))
979
- db.exec("ALTER TABLE ai_teams ADD COLUMN instructions TEXT NOT NULL DEFAULT ''");
980
- const stepColumns = new Set(db.prepare("PRAGMA table_info(ai_team_steps)").all().map((column) => column.name));
981
- if (!stepColumns.has("depends_on_json"))
982
- db.exec("ALTER TABLE ai_team_steps ADD COLUMN depends_on_json TEXT NOT NULL DEFAULT '[]'");
983
- const runColumns = new Set(db.prepare("PRAGMA table_info(ai_team_runs)").all().map((column) => column.name));
984
- if (!runColumns.has("chat_session_id"))
985
- db.exec("ALTER TABLE ai_team_runs ADD COLUMN chat_session_id TEXT");
986
- if (!runColumns.has("pending_notes_json"))
987
- db.exec("ALTER TABLE ai_team_runs ADD COLUMN pending_notes_json TEXT NOT NULL DEFAULT '[]'");
988
- // v2 多候选降级:步骤实际候选与跳过记录(StepDispatchInfo)、run 级状态(黑名单等)。只加列,旧行取默认 '{}'。
989
- if (!stepColumns.has("dispatch_info_json"))
990
- db.exec("ALTER TABLE ai_team_steps ADD COLUMN dispatch_info_json TEXT NOT NULL DEFAULT '{}'");
991
- if (!runColumns.has("run_state_json"))
992
- db.exec("ALTER TABLE ai_team_runs ADD COLUMN run_state_json TEXT NOT NULL DEFAULT '{}'");
993
- // 硅基员工表结构保护(遵循只加不删)
994
- db.exec(`
995
- CREATE TABLE IF NOT EXISTS silicon_employees (
996
- id TEXT PRIMARY KEY,
997
- name TEXT NOT NULL,
998
- duty TEXT NOT NULL DEFAULT '',
999
- prompt TEXT NOT NULL DEFAULT '',
1000
- avatar TEXT NOT NULL DEFAULT '',
1001
- agents_json TEXT NOT NULL,
1002
- system_key TEXT,
1003
- archived_at TEXT,
1004
- created_at TEXT NOT NULL,
1005
- updated_at TEXT NOT NULL
1006
- );
1007
- CREATE INDEX IF NOT EXISTS idx_silicon_employees_archived ON silicon_employees(archived_at);
1008
- `);
1009
- // 内置员工标记(如 "wand-ops"):只加列,用户员工保持 NULL。
1010
- const employeeColumns = new Set(db.prepare("PRAGMA table_info(silicon_employees)").all().map((column) => column.name));
1011
- if (employeeColumns.size > 0 && !employeeColumns.has("system_key")) {
1012
- db.exec("ALTER TABLE silicon_employees ADD COLUMN system_key TEXT");
1013
- }
1014
- if (employeeColumns.size > 0 && !employeeColumns.has("tags_json")) {
1015
- db.exec("ALTER TABLE silicon_employees ADD COLUMN tags_json TEXT NOT NULL DEFAULT '[]'");
1016
- }
1017
- }
1018
- function ensureWandTaskSchema(db) {
1019
- const columns = db.prepare("PRAGMA table_info(wand_tasks)").all();
1020
- const names = new Set(columns.map((column) => column.name));
1021
- if (columns.length > 0 && !names.has("identifier"))
1022
- db.exec("ALTER TABLE wand_tasks ADD COLUMN identifier TEXT");
1023
- if (columns.length > 0 && !names.has("due_date"))
1024
- db.exec("ALTER TABLE wand_tasks ADD COLUMN due_date TEXT");
1025
- // 任务的默认派发配置(CLI 工具 / 模型 / 思考深度)。只加列,历史行保持 NULL。
1026
- if (columns.length > 0 && !names.has("agent_json"))
1027
- db.exec("ALTER TABLE wand_tasks ADD COLUMN agent_json TEXT");
1028
- if (columns.length > 0 && !names.has("execution_subject_json"))
1029
- db.exec("ALTER TABLE wand_tasks ADD COLUMN execution_subject_json TEXT");
1030
- if (columns.length > 0 && !names.has("workspace_task_id"))
1031
- db.exec("ALTER TABLE wand_tasks ADD COLUMN workspace_task_id TEXT");
1032
- // 父任务归属只加列;历史任务仍然是顶层任务。
1033
- if (columns.length > 0 && !names.has("parent_task_id"))
1034
- db.exec("ALTER TABLE wand_tasks ADD COLUMN parent_task_id TEXT");
1035
- // 标题来源:'user' = 用户手写,'auto' = 由描述/会话内容自动生成。只加列,历史行保持 NULL(读取时视为 user)。
1036
- if (columns.length > 0 && !names.has("title_source"))
1037
- db.exec("ALTER TABLE wand_tasks ADD COLUMN title_source TEXT");
1038
- // 自动标题最近一次的输入指纹:内容没变就不再重复总结(也避免自动标题来回震荡)。只加列。
1039
- if (columns.length > 0 && !names.has("auto_title_signature"))
1040
- db.exec("ALTER TABLE wand_tasks ADD COLUMN auto_title_signature TEXT");
1041
- // 里程碑:只加列,历史行保持 NULL;里程碑本体在 wand_milestones(INIT_SQL 建表)。
1042
- if (columns.length > 0 && !names.has("milestone_id"))
1043
- db.exec("ALTER TABLE wand_tasks ADD COLUMN milestone_id TEXT");
1044
- // 归档时间(自动/手动归档都写它):任务自动删除按它起算;历史归档行保持 NULL。
1045
- if (columns.length > 0 && !names.has("archived_at"))
1046
- db.exec("ALTER TABLE wand_tasks ADD COLUMN archived_at TEXT");
1047
- if (columns.length > 0) {
1048
- // Index creation must wait until the column exists on legacy databases.
1049
- // INIT_SQL cannot create it: CREATE TABLE IF NOT EXISTS is a no-op on old
1050
- // wand_tasks, and CREATE INDEX would then fail with "no such column".
1051
- db.exec("CREATE INDEX IF NOT EXISTS idx_wand_tasks_workspace_task ON wand_tasks(workspace_task_id)");
1052
- db.exec("CREATE INDEX IF NOT EXISTS idx_wand_tasks_parent ON wand_tasks(parent_task_id)");
1053
- const rows = db.prepare("SELECT id FROM wand_tasks WHERE identifier IS NULL OR identifier = '' ORDER BY created_at, id").all();
1054
- const update = db.prepare("UPDATE wand_tasks SET identifier = ? WHERE id = ?");
1055
- rows.forEach((row, index) => update.run(`TASK-${index + 1}`, row.id));
1056
- }
1057
- }
1058
- function ensureWorkspaceSchema(db) {
1059
- const workspaceColumns = db.prepare("PRAGMA table_info(workspaces)").all();
1060
- const workspaceNames = new Set(workspaceColumns.map((column) => column.name));
1061
- if (workspaceColumns.length > 0 && !workspaceNames.has("kind")) {
1062
- db.exec("ALTER TABLE workspaces ADD COLUMN kind TEXT NOT NULL DEFAULT 'project'");
1063
- }
1064
- const taskColumns = db.prepare("PRAGMA table_info(workspace_tasks)").all();
1065
- const taskNames = new Set(taskColumns.map((column) => column.name));
1066
- if (taskColumns.length > 0 && !taskNames.has("cwd")) {
1067
- db.exec("ALTER TABLE workspace_tasks ADD COLUMN cwd TEXT");
1068
- }
1069
- if (taskColumns.length > 0 && !taskNames.has("layout_revision")) {
1070
- db.exec("ALTER TABLE workspace_tasks ADD COLUMN layout_revision INTEGER NOT NULL DEFAULT 0");
1071
- }
1072
- // 侧栏任务也记里程碑:建任务时把它带到看板卡片上,命名任务与未命名任务保持一致。
1073
- if (taskColumns.length > 0 && !taskNames.has("milestone_id")) {
1074
- db.exec("ALTER TABLE workspace_tasks ADD COLUMN milestone_id TEXT");
1075
- }
1076
- }
1077
- function ensureWandMilestoneSchema(db) {
1078
- const columns = db.prepare("PRAGMA table_info(wand_milestones)").all();
1079
- const names = new Set(columns.map((column) => column.name));
1080
- // 里程碑归属工作区:只加列,历史行保持 NULL(读作全局里程碑,所有工作区可见)。
1081
- if (columns.length > 0 && !names.has("workspace_id")) {
1082
- db.exec("ALTER TABLE wand_milestones ADD COLUMN workspace_id TEXT");
1083
- }
1084
- // 默认迭代标记:只加列,历史行保持 0(读作普通迭代)。
1085
- if (columns.length > 0 && !names.has("is_default")) {
1086
- db.exec("ALTER TABLE wand_milestones ADD COLUMN is_default INTEGER NOT NULL DEFAULT 0");
1087
- }
1088
- if (columns.length > 0) {
1089
- // 索引必须等列存在后再建:老库的 CREATE TABLE IF NOT EXISTS 是空操作,
1090
- // 直接在 INIT_SQL 里建索引会因缺列报错。
1091
- db.exec("CREATE INDEX IF NOT EXISTS idx_wand_milestones_workspace ON wand_milestones(workspace_id, created_at DESC)");
1092
- }
1093
- }
1094
- /**
1095
- * 迭代提示词记录表:新库由 INIT_SQL 建好,老库在这里补建(只加表,不改表)。
1096
- * 空库(没建过任何表)时 PRAGMA 拿不到列,直接返回。
1097
- */
1098
- function ensureIterationPromptSchema(db) {
1099
- const columns = db.prepare("PRAGMA table_info(wand_iteration_prompts)").all();
1100
- if (columns.length > 0)
1101
- return;
1102
- db.exec(`
1103
- CREATE TABLE IF NOT EXISTS wand_iteration_prompts (
1104
- id TEXT PRIMARY KEY,
1105
- milestone_id TEXT NOT NULL,
1106
- workspace_id TEXT,
1107
- session_id TEXT,
1108
- task_id TEXT,
1109
- repo_key TEXT,
1110
- cwd TEXT NOT NULL,
1111
- title TEXT NOT NULL,
1112
- detail TEXT NOT NULL DEFAULT '',
1113
- source TEXT NOT NULL DEFAULT 'session',
1114
- consumed_at TEXT,
1115
- consumed_commit TEXT,
1116
- created_at TEXT NOT NULL
1117
- )
1118
- `);
1119
- db.exec("CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_iteration ON wand_iteration_prompts(milestone_id, created_at DESC)");
1120
- db.exec("CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_repo ON wand_iteration_prompts(repo_key, created_at DESC)");
1121
- db.exec("CREATE INDEX IF NOT EXISTS idx_wand_iteration_prompts_session ON wand_iteration_prompts(session_id, created_at DESC)");
1122
- }
1123
- function ensureConnectorSchema(db) {
1124
- // The table is created by INIT_SQL for new databases; this repairs an
1125
- // existing wand.db that predates the connector feature (additive-only).
1126
- const columns = db.prepare("PRAGMA table_info(connectors)").all();
1127
- const names = new Set(columns.map((column) => column.name));
1128
- if (names.size === 0)
1129
- return;
1130
- const migrations = [
1131
- ["api_url", "ALTER TABLE connectors ADD COLUMN api_url TEXT NOT NULL DEFAULT ''"],
1132
- ["username", "ALTER TABLE connectors ADD COLUMN username TEXT"],
1133
- ["connected_at", "ALTER TABLE connectors ADD COLUMN connected_at TEXT"],
1134
- ];
1135
- for (const [column, sql] of migrations) {
1136
- if (!names.has(column))
1137
- db.exec(sql);
1138
- }
1139
- }
1140
- export function ensureDatabaseFile(dbPath) {
1141
- const dir = path.dirname(dbPath);
1142
- mkdirSync(dir, { recursive: true, mode: 0o700 });
1143
- chmodSync(dir, 0o700);
1144
- const created = !existsSync(dbPath);
1145
- const db = new DatabaseSync(dbPath);
1146
- // DDL below takes the write lock; without this a concurrent CLI/Server writer
1147
- // aborts startup with SQLITE_BUSY instead of waiting its turn.
1148
- db.exec("PRAGMA busy_timeout = 5000");
1149
- db.exec(INIT_SQL);
1150
- ensureAuthSessionSchema(db);
1151
- ensureCommandSessionSchema(db);
1152
- ensureWorkspaceSchema(db);
1153
- ensureWandTaskSchema(db);
1154
- ensureWandMilestoneSchema(db);
1155
- ensureIterationPromptSchema(db);
1156
- ensureAiTeamSchema(db);
1157
- ensureConnectorSchema(db);
1158
- {
1159
- const missionColumns = db.prepare("PRAGMA table_info(missions)").all();
1160
- if (missionColumns.length > 0 && !missionColumns.some((column) => column.name === "task_id")) {
1161
- db.exec("ALTER TABLE missions ADD COLUMN task_id TEXT");
1162
- }
1163
- // 并行任务的里程碑与看板共用同一份列表;只加列,历史行保持 NULL。
1164
- if (missionColumns.length > 0 && !missionColumns.some((column) => column.name === "milestone_id")) {
1165
- db.exec("ALTER TABLE missions ADD COLUMN milestone_id TEXT");
1166
- }
1167
- }
1168
- db.close();
1169
- chmodSync(dbPath, 0o600);
1170
- return created;
1171
- }
592
+ export { ensureDatabaseFile } from "./storage-schema.js";
1172
593
  const WAND_TASK_FIELDS = `id, identifier, workspace_id, workspace_task_id, parent_task_id,
1173
594
  title, title_source, auto_title_signature, description, status, priority, labels_json,
1174
595
  due_date, milestone_id, sort_order, agent_json, execution_subject_json, archived_at, created_at, updated_at`;
@@ -1199,7 +620,46 @@ function withoutSessionTab(node, sessionId) {
1199
620
  return { ...node, tabs, active: active >= 0 ? active : Math.min(node.active, Math.max(0, tabs.length - 1)) };
1200
621
  }
1201
622
  export class WandStorage {
623
+ closeListeners = new Set();
624
+ taskTitleListeners = new Set();
625
+ titleNoticePending = false;
626
+ closed = false;
627
+ projectionEpoch = crypto.randomUUID();
628
+ /** Exact conservative invalidation: own writes + writes from other SQLite connections. */
629
+ projectionRevision() {
630
+ const own = this.db.prepare("SELECT total_changes() AS revision").get();
631
+ const external = this.db.prepare("PRAGMA data_version").get();
632
+ return `${this.projectionEpoch}:${own.revision}:${external.data_version}`;
633
+ }
634
+ onClose(listener) {
635
+ this.closeListeners.add(listener);
636
+ return () => { this.closeListeners.delete(listener); };
637
+ }
638
+ onTaskTitleChange(listener) {
639
+ this.taskTitleListeners.add(listener);
640
+ return () => { this.taskTitleListeners.delete(listener); };
641
+ }
642
+ /** Coalesce synchronous writes; observers read only after the transaction settles. */
643
+ notifyTaskTitleChange() {
644
+ if (this.closed || this.titleNoticePending || !this.taskTitleListeners.size)
645
+ return;
646
+ this.titleNoticePending = true;
647
+ queueMicrotask(() => {
648
+ this.titleNoticePending = false;
649
+ if (this.closed)
650
+ return;
651
+ for (const listener of this.taskTitleListeners) {
652
+ try {
653
+ listener();
654
+ }
655
+ catch {
656
+ console.error("[WandTask] title change observer failed");
657
+ }
658
+ }
659
+ });
660
+ }
1202
661
  db;
662
+ schedules;
1203
663
  dbPath;
1204
664
  memoryCapture = { enabled: true, revision: 0 };
1205
665
  constructor(dbPath) {
@@ -1208,59 +668,15 @@ export class WandStorage {
1208
668
  chmodSync(dir, 0o700);
1209
669
  this.dbPath = dbPath;
1210
670
  this.db = new DatabaseSync(dbPath);
671
+ this.schedules = new ScheduledTaskRepository(this.db, action => this.transaction(action));
1211
672
  this.db.exec("PRAGMA busy_timeout = 5000");
1212
673
  chmodSync(dbPath, 0o600);
1213
- this.db.exec(INIT_SQL);
1214
- ensureAuthSessionSchema(this.db);
1215
- ensureCommandSessionSchema(this.db);
1216
- ensureWorkspaceSchema(this.db);
1217
- ensureWandTaskSchema(this.db);
1218
- ensureWandMilestoneSchema(this.db);
1219
- ensureIterationPromptSchema(this.db);
1220
- ensureAiTeamSchema(this.db);
1221
- this.db.exec(`
1222
- CREATE TABLE IF NOT EXISTS conversation_list_state (
1223
- conversation_id TEXT PRIMARY KEY,
1224
- pinned_at TEXT,
1225
- hidden_at TEXT, dissolved_at TEXT, dissolved_by TEXT, deleting INTEGER NOT NULL DEFAULT 0
1226
- );
1227
- CREATE TABLE IF NOT EXISTS conversations (
1228
- id TEXT PRIMARY KEY, owner TEXT NOT NULL, kind TEXT NOT NULL,
1229
- peer_employee_id TEXT, session_id TEXT, instance_json TEXT NOT NULL
1230
- );
1231
- CREATE UNIQUE INDEX IF NOT EXISTS idx_conversation_peer ON conversations(owner, peer_employee_id)
1232
- WHERE kind = 'dm';
1233
- CREATE UNIQUE INDEX IF NOT EXISTS idx_conversation_session ON conversations(session_id)
1234
- WHERE session_id IS NOT NULL;
1235
- CREATE TABLE IF NOT EXISTS conversation_tasks (
1236
- conversation_id TEXT NOT NULL, task_id TEXT NOT NULL UNIQUE, linked_at TEXT NOT NULL,
1237
- PRIMARY KEY(conversation_id, task_id)
1238
- );
1239
- CREATE TABLE IF NOT EXISTS conversation_messages (
1240
- id TEXT PRIMARY KEY, conversation_id TEXT NOT NULL, turn_json TEXT NOT NULL, created_at TEXT NOT NULL
1241
- );
1242
- CREATE INDEX IF NOT EXISTS idx_conversation_messages ON conversation_messages(conversation_id, created_at);
1243
- CREATE TABLE IF NOT EXISTS conversation_requests (
1244
- id TEXT PRIMARY KEY, fingerprint TEXT NOT NULL, receipt_json TEXT NOT NULL, updated_at TEXT NOT NULL
1245
- );
1246
- `);
1247
- const conversationListColumns = new Set(this.db.prepare("PRAGMA table_info(conversation_list_state)").all().map(column => column.name));
1248
- for (const [name, type] of [["dissolved_at", "TEXT"], ["dissolved_by", "TEXT"], ["deleting", "INTEGER NOT NULL DEFAULT 0"]]) {
1249
- if (!conversationListColumns.has(name))
1250
- this.db.exec(`ALTER TABLE conversation_list_state ADD COLUMN ${name} ${type}`);
1251
- }
1252
- const conversationRunColumns = new Set(this.db.prepare("PRAGMA table_info(ai_team_runs)").all().map((column) => column.name));
1253
- if (!conversationRunColumns.has("conversation_id"))
1254
- this.db.exec("ALTER TABLE ai_team_runs ADD COLUMN conversation_id TEXT");
1255
- if (!conversationRunColumns.has("member_version"))
1256
- this.db.exec("ALTER TABLE ai_team_runs ADD COLUMN member_version INTEGER");
1257
- if (!conversationRunColumns.has("round_number"))
1258
- this.db.exec("ALTER TABLE ai_team_runs ADD COLUMN round_number INTEGER");
1259
- ensureConnectorSchema(this.db);
674
+ initializeStorageSchema(this.db);
1260
675
  this.ensureDefaultPasswordVault();
1261
676
  this.migrateTaskRecords();
1262
677
  const memory = this.getUserMemoryState();
1263
678
  this.memoryCapture = { enabled: memory.enabled, revision: memory.revision };
679
+ this.ensureDefaultWandMilestone();
1264
680
  }
1265
681
  directory() {
1266
682
  return path.dirname(this.dbPath);
@@ -1269,6 +685,17 @@ export class WandStorage {
1269
685
  return this.dbPath;
1270
686
  }
1271
687
  close() {
688
+ if (this.closed)
689
+ return;
690
+ this.closed = true;
691
+ for (const listener of this.closeListeners) {
692
+ try {
693
+ listener();
694
+ }
695
+ catch { /* finish closing other storage-owned consumers */ }
696
+ }
697
+ this.closeListeners.clear();
698
+ this.taskTitleListeners.clear();
1272
699
  this.db.close();
1273
700
  }
1274
701
  /**
@@ -1290,6 +717,40 @@ export class WandStorage {
1290
717
  throw error;
1291
718
  }
1292
719
  }
720
+ // ============ Persistent employee schedules ============
721
+ listScheduledTasks(...args) {
722
+ return this.schedules.listScheduledTasks(...args);
723
+ }
724
+ listDueScheduledTasks(...args) {
725
+ return this.schedules.listDueScheduledTasks(...args);
726
+ }
727
+ getScheduledTask(...args) {
728
+ return this.schedules.getScheduledTask(...args);
729
+ }
730
+ createScheduledTask(...args) {
731
+ return this.schedules.createScheduledTask(...args);
732
+ }
733
+ updateScheduledTask(...args) {
734
+ return this.schedules.updateScheduledTask(...args);
735
+ }
736
+ claimScheduledTaskRun(...args) {
737
+ return this.schedules.claimScheduledTaskRun(...args);
738
+ }
739
+ saveScheduledTaskRun(...args) {
740
+ return this.schedules.saveScheduledTaskRun(...args);
741
+ }
742
+ getScheduledTaskRun(...args) {
743
+ return this.schedules.getScheduledTaskRun(...args);
744
+ }
745
+ listOpenScheduledTaskRuns(...args) {
746
+ return this.schedules.listOpenScheduledTaskRuns(...args);
747
+ }
748
+ getOpenScheduledTaskRunsForSession(...args) {
749
+ return this.schedules.getOpenScheduledTaskRunsForSession(...args);
750
+ }
751
+ listScheduledTaskRuns(...args) {
752
+ return this.schedules.listScheduledTaskRuns(...args);
753
+ }
1293
754
  // ============ Stable conversations (no read-side repair) ============
1294
755
  conversationListState(id) {
1295
756
  const row = this.db.prepare("SELECT pinned_at, dissolved_at, dissolved_by, deleting FROM conversation_list_state WHERE conversation_id = ?")
@@ -1432,6 +893,33 @@ export class WandStorage {
1432
893
  return this.db.prepare("SELECT turn_json FROM conversation_messages WHERE conversation_id = ? ORDER BY created_at, rowid")
1433
894
  .all(id).map((row) => JSON.parse(row.turn_json));
1434
895
  }
896
+ /** List preview only needs the newest event, not the whole transcript. */
897
+ lastConversationEvent(id) {
898
+ const row = this.db.prepare(`SELECT turn_json FROM conversation_messages
899
+ WHERE conversation_id = ? ORDER BY created_at DESC, rowid DESC LIMIT 1`).get(id);
900
+ return row ? JSON.parse(row.turn_json) : null;
901
+ }
902
+ /**
903
+ * One turn from the persisted JSON array. Avoids materializing the transcript in the list path.
904
+ * Append-only sessions keep the comparator-max at the tail; role walks backward from that tail.
905
+ */
906
+ lastSessionTurn(id, role) {
907
+ const row = this.db.prepare("SELECT json_array_length(messages) AS length FROM command_sessions WHERE id = ?")
908
+ .get(id);
909
+ const length = row?.length ?? 0;
910
+ if (!length)
911
+ return null;
912
+ const read = this.db.prepare("SELECT json_extract(messages, ?) AS turn FROM command_sessions WHERE id = ?");
913
+ for (let index = length - 1; index >= 0; index -= 1) {
914
+ const item = read.get(`$[${index}]`, id);
915
+ if (!item?.turn)
916
+ continue;
917
+ const turn = JSON.parse(item.turn);
918
+ if (!role || turn.role === role)
919
+ return turn;
920
+ }
921
+ return null;
922
+ }
1435
923
  getConversationRequest(id) {
1436
924
  const row = this.db.prepare("SELECT * FROM conversation_requests WHERE id = ?").get(id);
1437
925
  return row ? { id: row.id, fingerprint: row.fingerprint, receipt: JSON.parse(row.receipt_json), updatedAt: row.updated_at } : null;
@@ -1698,6 +1186,11 @@ export class WandStorage {
1698
1186
  .all(workspaceId);
1699
1187
  return rows.map((row) => this.mapSessionRow(row));
1700
1188
  }
1189
+ listSessionsByWorkspaceSlim(workspaceId) {
1190
+ const rows = this.db.prepare(`SELECT ${sessionSelectFields(true)} FROM command_sessions
1191
+ WHERE workspace_id = ? ORDER BY started_at DESC`).all(workspaceId);
1192
+ return rows.map(mapSessionCore);
1193
+ }
1701
1194
  /** 显式更新某会话的工作空间归属(用于创建时绑定)。 */
1702
1195
  getSessionWorkspace(sessionId) {
1703
1196
  const row = this.db.prepare("SELECT workspace_id, workspace_task_id FROM command_sessions WHERE id = ?")
@@ -1773,6 +1266,11 @@ export class WandStorage {
1773
1266
  ORDER BY created_at DESC, _created_order DESC`).all(workspaceId);
1774
1267
  return rows.map(mapWorkspaceTaskRow);
1775
1268
  }
1269
+ listAllWorkspaceTasks() {
1270
+ const rows = this.db.prepare(`SELECT * FROM (${WORKSPACE_TASK_PROJECTION})
1271
+ ORDER BY created_at DESC, _created_order DESC`).all();
1272
+ return rows.map(mapWorkspaceTaskRow);
1273
+ }
1776
1274
  getWorkspaceTask(id) {
1777
1275
  const row = this.db.prepare(`${WORKSPACE_TASK_PROJECTION} WHERE wt.id = ?`)
1778
1276
  .get(id);
@@ -1972,6 +1470,7 @@ export class WandStorage {
1972
1470
  this.db.prepare(`INSERT INTO wand_tasks (${WAND_TASK_FIELDS})
1973
1471
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
1974
1472
  .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);
1473
+ this.notifyTaskTitleChange();
1975
1474
  return this.getWandTask(id);
1976
1475
  }
1977
1476
  createWandTask(input) {
@@ -2038,6 +1537,7 @@ export class WandStorage {
2038
1537
  this.db.prepare("UPDATE command_sessions SET workspace_id = ? WHERE workspace_task_id = ?")
2039
1538
  .run(workspaceId, next.workspaceTaskId);
2040
1539
  }
1540
+ this.notifyTaskTitleChange();
2041
1541
  return next;
2042
1542
  }
2043
1543
  updateWandTask(id, patch) {
@@ -2371,6 +1871,7 @@ export class WandStorage {
2371
1871
  }
2372
1872
  /** Atomic exclusive ownership change; cwd, output and execution are untouched. */
2373
1873
  moveSessionToWorkspaceTask(sessionId, taskId) {
1874
+ this.notifyTaskTitleChange();
2374
1875
  this.transaction(() => {
2375
1876
  const session = this.getSessionWorkspace(sessionId);
2376
1877
  if (!session)
@@ -2407,6 +1908,7 @@ export class WandStorage {
2407
1908
  recordTaskSession(session, newlyAssigned = true) {
2408
1909
  if (!session.workspaceTaskId)
2409
1910
  return;
1911
+ this.notifyTaskTitleChange();
2410
1912
  const card = this.getWandTaskByWorkspaceTaskId(session.workspaceTaskId);
2411
1913
  if (!card)
2412
1914
  return;
@@ -2783,8 +2285,32 @@ export class WandStorage {
2783
2285
  }
2784
2286
  return existing;
2785
2287
  }
2288
+ /** HR owns recruitment independently; seed from configured system candidates once. */
2289
+ ensureHrEmployee(seedAgents) {
2290
+ const existing = this.getSystemSiliconEmployee(HR_EMPLOYEE_KEY);
2291
+ const definition = hrEmployeeDefinition(new Date().toISOString(), existing, seedAgents);
2292
+ if (!existing || existing.name !== definition.name || existing.duty !== definition.duty || existing.prompt !== definition.prompt
2293
+ || existing.avatar !== definition.avatar || existing.archivedAt || !existing.agents.length) {
2294
+ this.saveSiliconEmployee(definition);
2295
+ return definition;
2296
+ }
2297
+ return existing;
2298
+ }
2786
2299
  saveSiliconEmployee(employee) {
2787
2300
  const existing = this.getSiliconEmployeeDefinition(employee.id);
2301
+ const locked = LOCKED_SYSTEM_EMPLOYEE_DEFINITIONS.find((definition) => definition.id === employee.id || definition.systemKey === employee.systemKey);
2302
+ if (locked) {
2303
+ if (employee.id !== locked.id || employee.systemKey !== locked.systemKey)
2304
+ throw new Error("内置员工身份不可修改或冒用。");
2305
+ if (existing && existing.systemKey !== locked.systemKey)
2306
+ throw new Error("内置员工身份冲突,不能覆盖已有记录。");
2307
+ for (const field of ["name", "duty", "prompt", "avatar", "tags"]) {
2308
+ if (JSON.stringify(employee[field]) !== JSON.stringify(locked[field]))
2309
+ throw new Error("该员工是内置的,基础资料不可修改。");
2310
+ }
2311
+ if (employee.archivedAt !== undefined)
2312
+ throw new Error("内置员工不可归档。");
2313
+ }
2788
2314
  const target = FIXED_EMPLOYEE_AVATARS.find((entry) => entry.id === employee.id || entry.systemKey === employee.systemKey);
2789
2315
  if (target) {
2790
2316
  if (employee.id !== target.id || employee.systemKey !== target.systemKey)
@@ -2846,17 +2372,17 @@ export class WandStorage {
2846
2372
  });
2847
2373
  }
2848
2374
  archiveSiliconEmployee(id, archivedAt = new Date().toISOString()) {
2849
- if (isFixedAvatarEmployee(this.getSiliconEmployeeDefinition(id) ?? {}))
2375
+ if (isBuiltinSiliconEmployee(this.getSiliconEmployeeDefinition(id)))
2850
2376
  throw new Error("内置员工不可归档。");
2851
2377
  this.db.prepare("UPDATE silicon_employees SET archived_at = ?, updated_at = ? WHERE id = ?").run(archivedAt, new Date().toISOString(), id);
2852
2378
  }
2853
2379
  unarchiveSiliconEmployee(id) {
2854
- if (isFixedAvatarEmployee(this.getSiliconEmployeeDefinition(id) ?? {}))
2380
+ if (isBuiltinSiliconEmployee(this.getSiliconEmployeeDefinition(id)))
2855
2381
  throw new Error("内置员工不能恢复归档。");
2856
2382
  this.db.prepare("UPDATE silicon_employees SET archived_at = NULL, updated_at = ? WHERE id = ?").run(new Date().toISOString(), id);
2857
2383
  }
2858
2384
  deleteSiliconEmployee(id) {
2859
- if (isFixedAvatarEmployee(this.getSiliconEmployeeDefinition(id) ?? {}))
2385
+ if (isBuiltinSiliconEmployee(this.getSiliconEmployeeDefinition(id)))
2860
2386
  throw new Error("内置员工不可删除。");
2861
2387
  this.db.prepare("DELETE FROM silicon_employees WHERE id = ?").run(id);
2862
2388
  }
@@ -3880,69 +3406,10 @@ function mapPasswordItemRow(row) {
3880
3406
  passwordUpdatedAt: row.password_updated_at ?? undefined,
3881
3407
  };
3882
3408
  }
3883
- const SCHEMA_MIGRATIONS = [
3884
- ["session_source", "ALTER TABLE command_sessions ADD COLUMN session_source TEXT NOT NULL DEFAULT 'interactive'"],
3885
- ["automation_id", "ALTER TABLE command_sessions ADD COLUMN automation_id TEXT"],
3886
- ["archived", "ALTER TABLE command_sessions ADD COLUMN archived INTEGER NOT NULL DEFAULT 0"],
3887
- ["archived_at", "ALTER TABLE command_sessions ADD COLUMN archived_at TEXT"],
3888
- ["claude_session_id", "ALTER TABLE command_sessions ADD COLUMN claude_session_id TEXT"],
3889
- ["provider", "ALTER TABLE command_sessions ADD COLUMN provider TEXT"],
3890
- ["session_kind", "ALTER TABLE command_sessions ADD COLUMN session_kind TEXT NOT NULL DEFAULT 'pty'"],
3891
- ["runner", "ALTER TABLE command_sessions ADD COLUMN runner TEXT"],
3892
- ["messages", "ALTER TABLE command_sessions ADD COLUMN messages TEXT"],
3893
- ["queued_messages", "ALTER TABLE command_sessions ADD COLUMN queued_messages TEXT"],
3894
- ["queued_message_skills", "ALTER TABLE command_sessions ADD COLUMN queued_message_skills TEXT"],
3895
- ["structured_state", "ALTER TABLE command_sessions ADD COLUMN structured_state TEXT"],
3896
- ["resumed_from_session_id", "ALTER TABLE command_sessions ADD COLUMN resumed_from_session_id TEXT"],
3897
- // Legacy column: never written by current persist helpers. Kept because
3898
- // schema migrations are additive-only and must not DROP columns.
3899
- ["resumed_to_session_id", "ALTER TABLE command_sessions ADD COLUMN resumed_to_session_id TEXT"],
3900
- ["auto_recovered", "ALTER TABLE command_sessions ADD COLUMN auto_recovered INTEGER NOT NULL DEFAULT 0"],
3901
- ["worktree_enabled", "ALTER TABLE command_sessions ADD COLUMN worktree_enabled INTEGER NOT NULL DEFAULT 0"],
3902
- ["worktree_info", "ALTER TABLE command_sessions ADD COLUMN worktree_info TEXT"],
3903
- ["worktree_merge_status", "ALTER TABLE command_sessions ADD COLUMN worktree_merge_status TEXT"],
3904
- ["worktree_merge_info", "ALTER TABLE command_sessions ADD COLUMN worktree_merge_info TEXT"],
3905
- ["title", "ALTER TABLE command_sessions ADD COLUMN title TEXT"],
3906
- ["description", "ALTER TABLE command_sessions ADD COLUMN description TEXT"],
3907
- ["pty_output_seq", "ALTER TABLE command_sessions ADD COLUMN pty_output_seq INTEGER NOT NULL DEFAULT 0"],
3908
- ["session_options", `ALTER TABLE command_sessions ADD COLUMN session_options TEXT NOT NULL DEFAULT '{"schemaVersion":1}'`],
3909
- ["workspace_id", "ALTER TABLE command_sessions ADD COLUMN workspace_id TEXT"],
3910
- ["workspace_task_id", "ALTER TABLE command_sessions ADD COLUMN workspace_task_id TEXT"],
3911
- ["completion_revision", "ALTER TABLE command_sessions ADD COLUMN completion_revision INTEGER NOT NULL DEFAULT 0"],
3912
- ["viewed_completion_revision", "ALTER TABLE command_sessions ADD COLUMN viewed_completion_revision INTEGER NOT NULL DEFAULT 0"],
3913
- ];
3914
3409
  /** 首页目录组顺序的偏好键(app_config 表,与其它 UI 偏好同一套读写)。 */
3915
3410
  const WORKSPACE_GROUP_ORDER_KEY = "workspaceGroupOrder";
3916
3411
  /** Pi 会话默认设置的偏好键,与其它 UI 偏好同一套读写,不出现在 config.json。 */
3917
3412
  const PI_SESSION_DEFAULTS_KEY = "pref:piSessionDefaults";
3918
- const AUTH_SESSION_MIGRATIONS = [
3919
- ["kind", "ALTER TABLE auth_sessions ADD COLUMN kind TEXT NOT NULL DEFAULT 'browser-admin'"],
3920
- ["scopes", `ALTER TABLE auth_sessions ADD COLUMN scopes TEXT NOT NULL DEFAULT '["admin"]'`],
3921
- ];
3922
- function ensureAuthSessionSchema(db) {
3923
- const columns = db.prepare("PRAGMA table_info(auth_sessions)").all();
3924
- const names = new Set(columns.map((column) => column.name));
3925
- for (const [column, sql] of AUTH_SESSION_MIGRATIONS) {
3926
- if (!names.has(column))
3927
- db.exec(sql);
3928
- }
3929
- }
3930
- function ensureCommandSessionSchema(db) {
3931
- const columns = db.prepare("PRAGMA table_info(command_sessions)").all();
3932
- const names = new Set(columns.map((column) => column.name));
3933
- for (const [column, sql] of SCHEMA_MIGRATIONS) {
3934
- if (!names.has(column)) {
3935
- db.exec(sql);
3936
- }
3937
- }
3938
- if (columns.length > 0) {
3939
- // 侧栏 / 任务面板按 workspace_task_id 反复拉会话:没有索引时每次都是
3940
- // 46MB 表全扫(单次 ~500ms),而 command_sessions 里有 10MB+ 的 output/
3941
- // messages 溢出行。索引只加不删,老库首次启动时建一次。
3942
- db.exec("CREATE INDEX IF NOT EXISTS idx_command_sessions_workspace_task ON command_sessions(workspace_task_id)");
3943
- db.exec("CREATE INDEX IF NOT EXISTS idx_command_sessions_workspace ON command_sessions(workspace_id)");
3944
- }
3945
- }
3946
3413
  const AUTH_SCOPES = new Set([
3947
3414
  "admin",
3948
3415
  "sessions",