@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/server.js CHANGED
@@ -14,7 +14,7 @@ import { AuthService, BROWSER_ADMIN_PRINCIPAL, CONNECTED_APP_PRINCIPAL, principa
14
14
  import { ensureCertificates } from "./cert.js";
15
15
  import { getDefaultModelForProvider, getProviderDefaultModels, isExecutionMode, resolveConfigDir, writePreferenceToStorage, } from "./config.js";
16
16
  import { ModelCatalogService } from "./models.js";
17
- import { defaultModelGroupSelector } from "./model-groups.js";
17
+ import { fallbackProviderModel, sessionEmployeeFields } from "./session-model.js";
18
18
  import { ProcessManager } from "./process-manager.js";
19
19
  import { SessionLogger } from "./session-logger.js";
20
20
  import { coreHarnessAgentDir, warmCoreHarness } from "./harness-engine.js";
@@ -42,11 +42,14 @@ import { inferProviderFromCommand, isSessionProvider } from "./session-provider.
42
42
  import { registerVaultRoutes } from "./server-vault-routes.js";
43
43
  import { registerGithubRoutes } from "./server-github-routes.js";
44
44
  import { registerTaskRoutes } from "./server-task-routes.js";
45
+ import { taskTitleService } from "./task-title-service.js";
45
46
  import { getGithubConnectorStatus } from "./github-connector.js";
46
47
  import { registerMissionRoutes } from "./server-mission-routes.js";
47
48
  import { Missions } from "./missions.js";
48
49
  import { AI_TEAM_CHAT_PREFIX, createAiTeamRunner } from "./ai-team-runner.js";
49
50
  import { ConversationService } from "./conversation-service.js";
51
+ import { ScheduledTaskService } from "./scheduled-task-service.js";
52
+ import { registerScheduledTaskRoutes } from "./server-scheduled-task-routes.js";
50
53
  import { CONVERSATION_RELAY_PREFIX } from "./conversation-types.js";
51
54
  import { forwardConversationRelay, registerConversationRoutes } from "./server-conversation-routes.js";
52
55
  import { registerAiTeamRoutes } from "./server-ai-team-routes.js";
@@ -57,10 +60,11 @@ import { UserMemoryService } from "./user-memory.js";
57
60
  import { registerUserMemoryRoutes, userMemoryOperationLog } from "./server-user-memory.js";
58
61
  import { registerAttentionRoutes } from "./server-attention-routes.js";
59
62
  import { refreshProviderCliUpdateState, registerAdminUpdateRoutes, registerPublicUpdateRoutes, ServerUpdateState, } from "./server-update-routes.js";
60
- import { parseSessionCreationOrigin, registerClaudeHistoryRoutes, registerSessionRoutes } from "./server-session-routes.js";
63
+ import { registerClaudeHistoryRoutes, registerSessionRoutes } from "./server-session-routes.js";
64
+ import { parseSessionCreationOrigin } from "./session-request.js";
61
65
  import { registerWorkspaceRoutes } from "./server-workspace-routes.js";
62
66
  import { resolveSessionCwd } from "./session-cwd.js";
63
- import { resolveWorkspaceIdForNewSession } from "./workspace-binding.js";
67
+ import { backfillSessionWorkspaces, resolveWorkspaceIdForNewSession } from "./workspace-binding.js";
64
68
  import { getErrorMessage } from "./error-utils.js";
65
69
  import { asyncRoute, jsonErrorHandler } from "./express-async.js";
66
70
  import { checkPackageUpdateAsync, installPackageGloballyAsync, normalizeUpdateChannel, resolveGlobalWandCli, } from "./npm-update-utils.js";
@@ -81,7 +85,7 @@ import { DistributionManager } from "./distribution-manager.js";
81
85
  import { isLogBusActive, wandTuiLog } from "./tui/log-bus.js";
82
86
  import { EMBEDDED_WEB_ASSETS } from "./web-ui/embedded-assets.js";
83
87
  import { renderApp } from "./web-ui/index.js";
84
- import { getAiTeamsChunk, getPlushAvatarChunk, getScriptAsset, getThemePreloadAsset } from "./web-ui/scripts.js";
88
+ import { getAiTeamsChunk, getBrowserModule, getPlushAvatarChunk, getScriptAsset, getThemePreloadAsset } from "./web-ui/scripts.js";
85
89
  import { getStylesAsset } from "./web-ui/styles.js";
86
90
  import { WsBroadcastManager } from "./ws-broadcast.js";
87
91
  import { TerminalDaemonClient } from "./terminal-daemon-client.js";
@@ -125,7 +129,9 @@ async function checkLatestPackageVersion(channel, forceRefresh = false) {
125
129
  if (!forceRefresh && cached && now - cached.timestamp < CACHE_TTL_MS) {
126
130
  return cached.info;
127
131
  }
128
- const info = await checkPackageUpdateAsync(PKG_VERSION, channel);
132
+ // The immutable running build is also what the UI reports. Shared package metadata may
133
+ // belong to a different installer; it must not make a local Beta look like an older stable.
134
+ const info = await checkPackageUpdateAsync(DISPLAY_VERSION, channel);
129
135
  if (info.latest) {
130
136
  packageUpdateCache.set(channel, { info, timestamp: now });
131
137
  }
@@ -325,6 +331,11 @@ export async function startServer(config, configPath, options = {}) {
325
331
  const testMode = process.env.WAND_TEST_MODE === "1";
326
332
  app.set("trust proxy", "loopback, 172.16.0.0/12");
327
333
  const storage = new WandStorage(resolveDatabasePath(configPath));
334
+ if (!storage.getConfigValue("migration:session-workspaces:v1")) {
335
+ backfillSessionWorkspaces(storage);
336
+ storage.setConfigValue("migration:session-workspaces:v1", "done");
337
+ }
338
+ const taskTitles = taskTitleService(storage, { config });
328
339
  const runtimeConfig = new RuntimeConfigState(config);
329
340
  const openRouter = new OpenRouterFreeModelsService(storage, options.openRouterFetch);
330
341
  const speechPolisher = new SpeechPolisherService({ config, free: openRouter,
@@ -462,6 +473,8 @@ export async function startServer(config, configPath, options = {}) {
462
473
  notifySession: update => notifyConversationSession(update), deleteSession: id => { sessionRegistry.deleteWithProviderHistory(id); } });
463
474
  conversations = conversationService;
464
475
  conversationService.migrateExistingChats();
476
+ const scheduledTasks = new ScheduledTaskService({ storage, conversations: conversationService,
477
+ getSession: id => sessionRegistry.get(id), defaultCwd: () => config.defaultCwd });
465
478
  for (const prefix of [CONVERSATION_RELAY_PREFIX, AI_TEAM_CHAT_PREFIX]) {
466
479
  structuredSessions.registerRelay(prefix, (sessionId, input) => forwardConversationRelay(conversationService, sessionId, input));
467
480
  }
@@ -564,6 +577,18 @@ export async function startServer(config, configPath, options = {}) {
564
577
  setAssetCache(req, res, asset.hash, "private");
565
578
  res.type("application/javascript").send(asset.content);
566
579
  });
580
+ app.get("/assets/modules/:build/:file", (req, res) => {
581
+ const content = getBrowserModule(`modules/${req.params.build}/${req.params.file}`);
582
+ if (content === undefined) {
583
+ res.setHeader("Cache-Control", "no-store");
584
+ res.status(404).type("text/plain").send("Browser module not found; reload the page after updating.");
585
+ return;
586
+ }
587
+ // The directory fingerprints the entire immutable module graph. Never
588
+ // substitute current bytes for a missing older module under its cached URL.
589
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
590
+ res.type("application/javascript").send(content);
591
+ });
567
592
  app.get("/assets/ai-teams.js", (req, res) => {
568
593
  const chunk = getAiTeamsChunk(requestedHash(req));
569
594
  setAssetCache(req, res, chunk.hash, "public");
@@ -737,6 +762,7 @@ export async function startServer(config, configPath, options = {}) {
737
762
  "/api/session-directories",
738
763
  "/api/structured-sessions",
739
764
  "/api/conversations",
765
+ "/api/scheduled-tasks",
740
766
  "/api/commands",
741
767
  "/api/user-memory",
742
768
  "/api/claude-history",
@@ -773,6 +799,7 @@ export async function startServer(config, configPath, options = {}) {
773
799
  "/api/validate-path",
774
800
  "/api/file-search",
775
801
  "/api/local-file",
802
+ "/api/session-file",
776
803
  ], requireFiles);
777
804
  app.use("/api/browser-extension", requirePasswordVault);
778
805
  app.use("/api/silicon-employees/:employeeId/knowledge", requireSessions);
@@ -871,6 +898,7 @@ export async function startServer(config, configPath, options = {}) {
871
898
  });
872
899
  registerAttentionRoutes(app, { sessions: sessionRegistry, runner: aiTeams });
873
900
  registerConversationRoutes(app, conversationService);
901
+ registerScheduledTaskRoutes(app, scheduledTasks);
874
902
  registerAdminUpdateRoutes(app, {
875
903
  storage,
876
904
  config,
@@ -971,9 +999,7 @@ export async function startServer(config, configPath, options = {}) {
971
999
  const command = provider === "qoder" && rawCommand === "qoder"
972
1000
  ? "qodercli"
973
1001
  : rawCommand;
974
- const effectiveModel = provider
975
- ? rawModel || defaultModelGroupSelector(config.modelGroups, provider, getDefaultModelForProvider(config, provider)) || getDefaultModelForProvider(config, provider) || undefined
976
- : undefined;
1002
+ const effectiveModel = provider ? rawModel || fallbackProviderModel(config, provider) : undefined;
977
1003
  const reqCols = typeof body.cols === "number" && Number.isFinite(body.cols) ? body.cols : undefined;
978
1004
  const reqRows = typeof body.rows === "number" && Number.isFinite(body.rows) ? body.rows : undefined;
979
1005
  const sessionCwd = resolveSessionCwd(body.cwd, config.defaultCwd);
@@ -998,9 +1024,7 @@ export async function startServer(config, configPath, options = {}) {
998
1024
  rows: reqRows,
999
1025
  thinkingEffort: body.thinkingEffort ?? config.defaultThinkingEffort,
1000
1026
  systemPrompt: body.systemPrompt?.trim() || role?.prompt,
1001
- employeeId: role?.id,
1002
- employeeName: role?.name,
1003
- employeeAvatar: role?.avatar,
1027
+ ...sessionEmployeeFields(role),
1004
1028
  workspaceId,
1005
1029
  workspaceTaskId: body.workspaceTaskId,
1006
1030
  ...origin,
@@ -1086,13 +1110,16 @@ export async function startServer(config, configPath, options = {}) {
1086
1110
  });
1087
1111
  // Wire process events to WebSocket broadcast
1088
1112
  processes.on("process", (event) => {
1113
+ taskTitles.ingest(event);
1089
1114
  sessionCompletions.ingest(event);
1090
1115
  missions.ingest(event);
1091
1116
  aiTeams.ingest(event);
1092
1117
  wsManager.emitEvent(event);
1093
1118
  });
1094
1119
  structuredSessions.setEventEmitter((event) => {
1120
+ taskTitles.ingest(event);
1095
1121
  conversationService.ingestSessionEvent(event);
1122
+ scheduledTasks.ingestSessionEvent(event);
1096
1123
  sessionCompletions.ingest(event);
1097
1124
  missions.ingest(event);
1098
1125
  aiTeams.ingest(event);
@@ -1184,8 +1211,9 @@ export async function startServer(config, configPath, options = {}) {
1184
1211
  let bindAddr = config.host === "0.0.0.0" ? "0.0.0.0" : config.host;
1185
1212
  const collectedUrls = [];
1186
1213
  await new Promise((resolve, reject) => {
1187
- const cleanupFailedListen = () => {
1214
+ const cleanupFailedListen = async () => {
1188
1215
  shuttingDown = true;
1216
+ await scheduledTasks.dispose();
1189
1217
  decisions.dispose();
1190
1218
  openRouter.dispose();
1191
1219
  try {
@@ -1200,7 +1228,9 @@ export async function startServer(config, configPath, options = {}) {
1200
1228
  structuredSessions.dispose();
1201
1229
  }
1202
1230
  catch { /* noop */ }
1203
- aiTeams.dispose();
1231
+ taskTitles.dispose();
1232
+ modelCatalog.dispose();
1233
+ await aiTeams.dispose();
1204
1234
  try {
1205
1235
  structuredHosts.rustClient?.disconnect();
1206
1236
  }
@@ -1229,12 +1259,9 @@ export async function startServer(config, configPath, options = {}) {
1229
1259
  };
1230
1260
  const onListenError = (err) => {
1231
1261
  server.off("error", onListenError);
1232
- cleanupFailedListen();
1233
- if (err.code === "EADDRINUSE") {
1234
- reject(new PortInUseError(config.port, config.host));
1235
- return;
1236
- }
1237
- reject(err);
1262
+ void cleanupFailedListen().then(() => {
1263
+ reject(err.code === "EADDRINUSE" ? new PortInUseError(config.port, config.host) : err);
1264
+ }, reject);
1238
1265
  };
1239
1266
  server.once("error", onListenError);
1240
1267
  server.listen(config.port, config.host, () => {
@@ -1261,6 +1288,8 @@ export async function startServer(config, configPath, options = {}) {
1261
1288
  const updateChecksEnabled = !testMode && process.env.WAND_DISABLE_UPDATE_CHECK !== "1";
1262
1289
  if (!testMode)
1263
1290
  userMemory.start();
1291
+ if (!testMode)
1292
+ scheduledTasks.start();
1264
1293
  // Start configured background sessions after the server is already reachable.
1265
1294
  if (!testMode) {
1266
1295
  void processes.runStartupCommands().catch((error) => {
@@ -1309,6 +1338,8 @@ export async function startServer(config, configPath, options = {}) {
1309
1338
  catch { /* 扫描结果已经返回给保存请求,通知失败不回滚 */ }
1310
1339
  return result;
1311
1340
  };
1341
+ if (!testMode)
1342
+ taskTitles.refresh();
1312
1343
  let retentionTimer = null;
1313
1344
  if (!testMode) {
1314
1345
  retentionTimer = startRetentionTimer(retentionDeps, 60 * 60 * 1000, sweepRetention);
@@ -1461,6 +1492,10 @@ export async function startServer(config, configPath, options = {}) {
1461
1492
  return closePromise;
1462
1493
  closePromise = (async () => {
1463
1494
  shuttingDown = true;
1495
+ taskTitles.dispose();
1496
+ modelCatalog.dispose();
1497
+ const teamShutdown = aiTeams.dispose();
1498
+ await scheduledTasks.dispose();
1464
1499
  await daemonMaintenance.stop();
1465
1500
  decisions.dispose();
1466
1501
  openRouter.dispose();
@@ -1521,7 +1556,7 @@ export async function startServer(config, configPath, options = {}) {
1521
1556
  structuredSessions.dispose();
1522
1557
  }
1523
1558
  catch { /* best-effort shutdown */ }
1524
- aiTeams.dispose();
1559
+ await teamShutdown;
1525
1560
  try {
1526
1561
  structuredHosts.rustClient?.disconnect();
1527
1562
  }
@@ -0,0 +1,15 @@
1
+ import type { SessionProvider, WandConfig } from "./types.js";
2
+ /** The repeated `group selector || provider default` tail. Callers keep their own explicit-model precedence. */
3
+ export declare function fallbackProviderModel(config: WandConfig, provider: SessionProvider): string | undefined;
4
+ /** Employee and conversation creates treat blank and `default` as the configured fallback. */
5
+ export declare function resolveExplicitOrFallbackModel(config: WandConfig, provider: SessionProvider, explicit: string | null | undefined): string | undefined;
6
+ /** Identity stamped onto a new session. Execution stays with the caller's own start/createSession. */
7
+ export declare function sessionEmployeeFields(role: {
8
+ id?: string;
9
+ name?: string;
10
+ avatar?: string;
11
+ } | null | undefined): {
12
+ employeeId?: string;
13
+ employeeName?: string;
14
+ employeeAvatar?: string;
15
+ };
@@ -0,0 +1,23 @@
1
+ import { getDefaultModelForProvider } from "./config.js";
2
+ import { defaultModelGroupSelector } from "./model-groups.js";
3
+ /** The repeated `group selector || provider default` tail. Callers keep their own explicit-model precedence. */
4
+ export function fallbackProviderModel(config, provider) {
5
+ return defaultModelGroupSelector(config.modelGroups, provider, getDefaultModelForProvider(config, provider))
6
+ || getDefaultModelForProvider(config, provider)
7
+ || undefined;
8
+ }
9
+ /** Employee and conversation creates treat blank and `default` as the configured fallback. */
10
+ export function resolveExplicitOrFallbackModel(config, provider, explicit) {
11
+ const model = explicit?.trim() ?? "";
12
+ if (model && model !== "default")
13
+ return model;
14
+ return fallbackProviderModel(config, provider);
15
+ }
16
+ /** Identity stamped onto a new session. Execution stays with the caller's own start/createSession. */
17
+ export function sessionEmployeeFields(role) {
18
+ return {
19
+ employeeId: role?.id,
20
+ employeeName: role?.name,
21
+ employeeAvatar: role?.avatar,
22
+ };
23
+ }
@@ -1,11 +1,13 @@
1
1
  import type { ProcessManager } from "./process-manager.js";
2
- import type { StructuredSessionManager } from "./structured-session-manager.js";
3
2
  import type { WandStorage } from "./storage.js";
3
+ import type { StructuredSessionManager } from "./structured-session-manager.js";
4
4
  import type { ExecutionMode, SessionSnapshot } from "./types.js";
5
5
  export type SessionOwner = "structured" | "pty" | "storage";
6
6
  export type ProviderHistoryDeleter = Pick<ProcessManager, "deleteClaudeHistoryFiles" | "deleteCodexHistoryFiles" | "deleteOpenCodeHistorySessions" | "deleteQoderHistoryFiles" | "deletePiHistoryFiles" | "deleteGrokHistoryFiles" | "deleteGeminiHistoryFiles">;
7
7
  export declare function deleteProviderNativeHistory(deleter: ProviderHistoryDeleter, provider: string, providerSessionId: string, cwd: string): void;
8
8
  type HiddenSessionStore = Pick<WandStorage, "getConfigValue" | "setConfigValue">;
9
+ /** The owner deletes the managed session first; native history/tombstones have one implementation. */
10
+ export declare function deleteOwnedSessionWithHistory(remove: () => SessionSnapshot | null, deleter: ProviderHistoryDeleter, storage: HiddenSessionStore): SessionSnapshot | null;
9
11
  /** 读取 `hidden_claude_session_ids`;坏数据 / 空值按空集合处理,不抛错。 */
10
12
  export declare function readHiddenSessionIds(storage: Pick<HiddenSessionStore, "getConfigValue">): Set<string>;
11
13
  /** 写回 `hidden_claude_session_ids`(保持既有的 JSON 数组格式)。 */
@@ -26,6 +28,8 @@ export declare class SessionRegistry {
26
28
  ownerOf(id: string): SessionOwner | null;
27
29
  get(id: string): SessionSnapshot | null;
28
30
  getLatest(id: string): SessionSnapshot | null;
31
+ /** Runtime facts and membership only; list/policy reads must not hydrate transcripts. */
32
+ getSlim(id: string): SessionSnapshot | null;
29
33
  listSlim(): SessionSnapshot[];
30
34
  /** Membership belongs to storage; both runners only refresh their live projection. */
31
35
  refreshSessionWorkspace(id: string): void;
@@ -31,6 +31,20 @@ export function deleteProviderNativeHistory(deleter, provider, providerSessionId
31
31
  break;
32
32
  }
33
33
  }
34
+ /** The owner deletes the managed session first; native history/tombstones have one implementation. */
35
+ export function deleteOwnedSessionWithHistory(remove, deleter, storage) {
36
+ const snapshot = remove();
37
+ const providerSessionId = snapshot?.claudeSessionId?.trim();
38
+ if (!snapshot || !providerSessionId)
39
+ return snapshot;
40
+ const provider = snapshot.provider
41
+ ?? snapshot.structuredState?.provider
42
+ ?? inferProviderFromCommand(snapshot.command)
43
+ ?? "claude";
44
+ deleteProviderNativeHistory(deleter, provider, providerSessionId, snapshot.cwd);
45
+ addHiddenSessionIds(storage, [providerSessionId]);
46
+ return snapshot;
47
+ }
34
48
  /** 读取 `hidden_claude_session_ids`;坏数据 / 空值按空集合处理,不抛错。 */
35
49
  export function readHiddenSessionIds(storage) {
36
50
  const raw = storage.getConfigValue("hidden_claude_session_ids");
@@ -103,6 +117,11 @@ export class SessionRegistry {
103
117
  getLatest(id) {
104
118
  return this.get(id);
105
119
  }
120
+ /** Runtime facts and membership only; list/policy reads must not hydrate transcripts. */
121
+ getSlim(id) {
122
+ const snapshot = this.structured.getSlim(id) ?? this.processes.getOwnedSlim(id) ?? this.storage.getSessionSlim(id);
123
+ return snapshot ? { ...snapshot, ...this.storage.getSessionWorkspace(id), ...this.storage.getSessionCompletion(id) } : null;
124
+ }
106
125
  listSlim() {
107
126
  const byId = new Map();
108
127
  for (const snapshot of this.structured.listSlim())
@@ -242,17 +261,7 @@ export class SessionRegistry {
242
261
  return snapshot;
243
262
  }
244
263
  deleteWithProviderHistory(id) {
245
- const snapshot = this.delete(id);
246
- const providerSessionId = snapshot?.claudeSessionId?.trim();
247
- if (!snapshot || !providerSessionId)
248
- return snapshot;
249
- const provider = snapshot.provider
250
- ?? snapshot.structuredState?.provider
251
- ?? inferProviderFromCommand(snapshot.command)
252
- ?? "claude";
253
- deleteProviderNativeHistory(this.processes, provider, providerSessionId, snapshot.cwd);
254
- addHiddenSessionIds(this.storage, [providerSessionId]);
255
- return snapshot;
264
+ return deleteOwnedSessionWithHistory(() => this.delete(id), this.processes, this.storage);
256
265
  }
257
266
  updateStored(id, update) {
258
267
  const current = this.storage.getSession(id);
@@ -0,0 +1,9 @@
1
+ import type { ExecutionMode, SessionSource } from "./types.js";
2
+ export declare function parseExecutionMode(value: unknown, fallback: ExecutionMode): ExecutionMode;
3
+ export declare function parseSessionCreationOrigin(body: {
4
+ sessionSource?: unknown;
5
+ automationId?: unknown;
6
+ } | null | undefined): {
7
+ sessionSource: SessionSource;
8
+ automationId?: string;
9
+ };
@@ -0,0 +1,27 @@
1
+ import { isExecutionMode } from "./config.js";
2
+ export function parseExecutionMode(value, fallback) {
3
+ if (value === undefined)
4
+ return fallback;
5
+ if (!isExecutionMode(value)) {
6
+ throw new Error(`无效执行模式: ${String(value)}`);
7
+ }
8
+ return value;
9
+ }
10
+ export function parseSessionCreationOrigin(body) {
11
+ const rawSource = body?.sessionSource;
12
+ if (rawSource !== undefined && rawSource !== "interactive" && rawSource !== "automation" && rawSource !== "startup") {
13
+ throw new Error("sessionSource 必须是 interactive、automation 或 startup。");
14
+ }
15
+ const rawAutomationId = body?.automationId;
16
+ if (rawAutomationId !== undefined && typeof rawAutomationId !== "string") {
17
+ throw new Error("automationId 必须是非空字符串。");
18
+ }
19
+ const automationId = rawAutomationId?.trim();
20
+ if (rawAutomationId !== undefined && !automationId) {
21
+ throw new Error("automationId 必须是非空字符串。");
22
+ }
23
+ return {
24
+ sessionSource: rawSource ?? "interactive",
25
+ ...(automationId ? { automationId } : {}),
26
+ };
27
+ }
@@ -1,4 +1,4 @@
1
- import { type QuickCommitAiOptions } from "./git-quick-commit.js";
1
+ import { type QuickCommitAiOptions } from "./ai-text-runner.js";
2
2
  import type { ConversationTurn } from "./types.js";
3
3
  /**
4
4
  * PTY 底部输入框整段提交(shortcutKey=enter_text)会总结标题。
@@ -1,4 +1,4 @@
1
- import { callConfiguredAiText } from "./git-quick-commit.js";
1
+ import { callConfiguredAiText } from "./ai-text-runner.js";
2
2
  import { skipAnsiSequence } from "./pty-text-utils.js";
3
3
  import { clipAtWordBoundary } from "./text-utils.js";
4
4
  const MAX_PROMPT_LENGTH = 12_000;
@@ -26,7 +26,9 @@ export interface SessionDetailDTO extends SessionBaseDTO {
26
26
  }
27
27
  /** Resolve every client-visible title on the server so all clients show the same value. */
28
28
  export declare function resolveSessionDisplayTitle(snapshot: SessionSnapshot, blockedTitles?: readonly string[]): string;
29
- export declare function toSessionListItemDTO(snapshot: SessionSnapshot): SessionListItemDTO;
29
+ export declare function toSessionListItemDTO(snapshot: SessionSnapshot, options?: {
30
+ avatar?: "full" | "compact";
31
+ }): SessionListItemDTO;
30
32
  export interface SessionDetailDTOOptions {
31
33
  output?: string;
32
34
  messages?: ConversationTurn[];
@@ -1,3 +1,4 @@
1
+ import { compactTransportAvatar } from "./fixed-employee-avatar.js";
1
2
  import { enrichStructuredMessages, WAND_PROTOCOL_VERSION } from "./structured-client-protocol.js";
2
3
  import { normalizeTurnAnchors } from "./structured-provider-common.js";
3
4
  import { collectSessionTopicBlocklist, shouldAcceptGeneratedSessionTitle, summarizeSessionTitleFromInput, } from "./session-topic.js";
@@ -21,15 +22,20 @@ export function resolveSessionDisplayTitle(snapshot, blockedTitles = []) {
21
22
  return directory;
22
23
  return "会话";
23
24
  }
24
- /** Explicit allow-list separating the server's session object from its wire DTO. */
25
- function sessionBase(snapshot) {
25
+ /**
26
+ * Explicit allow-list separating the server's session object from its wire DTO.
27
+ * queuedMessages, piSettings and worktreeMergeInfo stay on this DTO: Web loads
28
+ * GET /api/sessions as the live session, and iOS decodes /api/session-list into
29
+ * the same snapshot. Nested task and workspace cards already use a slimmer summary.
30
+ */
31
+ function sessionBase(snapshot, avatarMode = "full") {
26
32
  return {
27
33
  id: snapshot.id,
28
34
  sessionSource: snapshot.sessionSource,
29
35
  automationId: snapshot.automationId,
30
36
  employeeId: snapshot.employeeId,
31
37
  employeeName: snapshot.employeeName,
32
- employeeAvatar: snapshot.employeeAvatar,
38
+ employeeAvatar: avatarMode === "compact" ? compactTransportAvatar(snapshot.employeeAvatar) : snapshot.employeeAvatar,
33
39
  employeeCandidateIndex: snapshot.employeeCandidateIndex,
34
40
  sessionKind: snapshot.sessionKind,
35
41
  provider: snapshot.provider,
@@ -86,8 +92,8 @@ function sessionBase(snapshot) {
86
92
  ptyRows: snapshot.ptyRows,
87
93
  };
88
94
  }
89
- export function toSessionListItemDTO(snapshot) {
90
- return { ...sessionBase(snapshot), output: "" };
95
+ export function toSessionListItemDTO(snapshot, options = {}) {
96
+ return { ...sessionBase(snapshot, options.avatar ?? "full"), output: "" };
91
97
  }
92
98
  export function toSessionDetailDTO(snapshot, options = {}) {
93
99
  const rawOutput = options.output ?? snapshot.output;
@@ -1,4 +1,4 @@
1
- import { type QuickCommitAiOptions } from "./git-quick-commit.js";
1
+ import { type QuickCommitAiOptions } from "./ai-text-runner.js";
2
2
  import { type SiliconEmployeeDraft } from "./ai-team-types.js";
3
3
  import type { AiTextRequest } from "./types.js";
4
4
  import type { WandTaskAgent } from "./task-types.js";
@@ -41,7 +41,7 @@ export declare function parseSiliconEmployeeDraft(raw: string, options?: {
41
41
  }): SiliconEmployeeDraft;
42
42
  /** HR / 客户端已给出完整结构化草稿时走同一校验,不再调用模型。 */
43
43
  export declare function validateStructuredEmployeeDraft(input: unknown, options?: Omit<EmployeeDraftNormalizeOptions, "strict">): SiliconEmployeeDraft;
44
- /** 规则走系统提示,用户的期望走用户消息;一次性调用沿用系统 AI 通道。 */
44
+ /** 招聘格式规则走系统提示,岗位需求走用户消息;执行通道由 HR 配置提供。 */
45
45
  export declare function buildEmployeeDraftPrompt(expectation: string, providers: SessionProvider[], existingNames?: string[]): AiTextRequest;
46
46
  /** 按自然语言期望生成员工配置;调用方负责随后落库。 */
47
47
  export declare function generateSiliconEmployeeDraft(expectation: string, ai?: QuickCommitAiOptions, options?: {
@@ -1,4 +1,4 @@
1
- import { callConfiguredAiText } from "./git-quick-commit.js";
1
+ import { callConfiguredAiText } from "./ai-text-runner.js";
2
2
  import { employeeCliAvailable } from "./silicon-employee-dispatch.js";
3
3
  import { SESSION_PROVIDERS, isSessionProvider } from "./session-provider.js";
4
4
  import { isThinkingEffort } from "./structured-provider-common.js";
@@ -186,11 +186,11 @@ export function validateStructuredEmployeeDraft(input, options = {}) {
186
186
  }
187
187
  return normalizeSiliconEmployeeDraft(input, { ...options, strict: true });
188
188
  }
189
- /** 规则走系统提示,用户的期望走用户消息;一次性调用沿用系统 AI 通道。 */
189
+ /** 招聘格式规则走系统提示,岗位需求走用户消息;执行通道由 HR 配置提供。 */
190
190
  export function buildEmployeeDraftPrompt(expectation, providers, existingNames = []) {
191
191
  const list = providers.length ? providers : [...SESSION_PROVIDERS];
192
192
  const lines = [
193
- "你正在帮用户创建一位「硅基员工」:一个长期负责某类工作的 AI 角色,之后会以结构化会话执行任务。",
193
+ "你正在为用户招聘一位「硅基员工」:一个长期负责某类工作的 AI 角色,之后会以结构化会话执行任务。这里只拟定人选资料,不办理入职。",
194
194
  "根据用户对这位员工的期望,设计一份配置,只输出一个 JSON 对象(不要 Markdown 代码块,不要任何解释):",
195
195
  '{"name":"员工名字","duty":"一句话职责","prompt":"角色设定","provider":"执行工具"}',
196
196
  "",
@@ -0,0 +1,2 @@
1
+ /** Read-only liveness probe; no authentication or daemon replacement. */
2
+ export declare function probeUnixSocket(socketPath: string): Promise<boolean>;
@@ -0,0 +1,20 @@
1
+ import net from "node:net";
2
+ /** Read-only liveness probe; no authentication or daemon replacement. */
3
+ export function probeUnixSocket(socketPath) {
4
+ return new Promise((resolve) => {
5
+ const socket = net.createConnection(socketPath);
6
+ let settled = false;
7
+ const finish = (alive) => {
8
+ if (settled)
9
+ return;
10
+ settled = true;
11
+ clearTimeout(timer);
12
+ socket.destroy();
13
+ resolve(alive);
14
+ };
15
+ socket.once("connect", () => finish(true));
16
+ socket.once("error", () => finish(false));
17
+ const timer = setTimeout(() => finish(false), 300);
18
+ timer.unref?.();
19
+ });
20
+ }
@@ -0,0 +1,20 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { ScheduledTask, ScheduledTaskRun } from "./scheduled-task-types.js";
3
+ /** Same SQLite connection and transaction owner as WandStorage; no separate lifecycle. */
4
+ export declare class ScheduledTaskRepository {
5
+ private db;
6
+ private transaction;
7
+ constructor(db: DatabaseSync, transaction: <T>(action: () => T) => T);
8
+ listScheduledTasks(includeCancelled?: boolean): ScheduledTask[];
9
+ listDueScheduledTasks(now: string): ScheduledTask[];
10
+ getScheduledTask(id: string): ScheduledTask | null;
11
+ createScheduledTask(task: ScheduledTask): boolean;
12
+ updateScheduledTask(task: ScheduledTask, expectedRevision: number): boolean;
13
+ /** Claim and advance together before any execution can escape the process. */
14
+ claimScheduledTaskRun(task: ScheduledTask, run: ScheduledTaskRun, nextRunAt: string | null): boolean;
15
+ saveScheduledTaskRun(run: ScheduledTaskRun): void;
16
+ getScheduledTaskRun(id: string): ScheduledTaskRun | null;
17
+ listOpenScheduledTaskRuns(taskId?: string): ScheduledTaskRun[];
18
+ getOpenScheduledTaskRunsForSession(sessionId: string): ScheduledTaskRun[];
19
+ listScheduledTaskRuns(taskId?: string, limit?: number): ScheduledTaskRun[];
20
+ }
@@ -0,0 +1,78 @@
1
+ /** Same SQLite connection and transaction owner as WandStorage; no separate lifecycle. */
2
+ export class ScheduledTaskRepository {
3
+ db;
4
+ transaction;
5
+ constructor(db, transaction) {
6
+ this.db = db;
7
+ this.transaction = transaction;
8
+ }
9
+ listScheduledTasks(includeCancelled = false) {
10
+ const rows = this.db.prepare(`SELECT task_json FROM scheduled_tasks ${includeCancelled ? "" : "WHERE cancelled_at IS NULL"} ORDER BY rowid DESC`)
11
+ .all();
12
+ return rows.map(row => JSON.parse(row.task_json));
13
+ }
14
+ listDueScheduledTasks(now) {
15
+ const rows = this.db.prepare(`SELECT task_json FROM scheduled_tasks
16
+ WHERE enabled = 1 AND cancelled_at IS NULL AND next_run_at <= ? ORDER BY rowid DESC`)
17
+ .all(now);
18
+ return rows.map(row => JSON.parse(row.task_json));
19
+ }
20
+ getScheduledTask(id) {
21
+ const row = this.db.prepare("SELECT task_json FROM scheduled_tasks WHERE id = ?").get(id);
22
+ return row ? JSON.parse(row.task_json) : null;
23
+ }
24
+ createScheduledTask(task) {
25
+ return this.db.prepare("INSERT OR IGNORE INTO scheduled_tasks(id, enabled, next_run_at, revision, cancelled_at, task_json) VALUES (?, ?, ?, ?, ?, ?)")
26
+ .run(task.id, Number(task.enabled), task.nextRunAt, task.revision, task.cancelledAt, JSON.stringify(task)).changes === 1;
27
+ }
28
+ updateScheduledTask(task, expectedRevision) {
29
+ return this.db.prepare(`UPDATE scheduled_tasks SET enabled = ?, next_run_at = ?, revision = ?, cancelled_at = ?, task_json = ?
30
+ WHERE id = ? AND revision = ?`).run(Number(task.enabled), task.nextRunAt, task.revision, task.cancelledAt, JSON.stringify(task), task.id, expectedRevision).changes === 1;
31
+ }
32
+ /** Claim and advance together before any execution can escape the process. */
33
+ claimScheduledTaskRun(task, run, nextRunAt) {
34
+ return this.transaction(() => {
35
+ const current = this.getScheduledTask(task.id);
36
+ if (!current?.enabled || current.cancelledAt || current.revision !== task.revision
37
+ || current.nextRunAt !== run.scheduledAt || current.nextRunAt !== task.nextRunAt)
38
+ return false;
39
+ const result = this.db.prepare("INSERT OR IGNORE INTO scheduled_task_runs(id, task_id, scheduled_at, run_json) VALUES (?, ?, ?, ?)")
40
+ .run(run.id, run.taskId, run.scheduledAt, JSON.stringify(run));
41
+ if (result.changes !== 1)
42
+ return false;
43
+ const advanced = { ...current, nextRunAt, enabled: nextRunAt !== null,
44
+ revision: current.revision + 1, updatedAt: run.claimedAt };
45
+ this.db.prepare("UPDATE scheduled_tasks SET enabled = ?, next_run_at = ?, revision = ?, task_json = ? WHERE id = ?")
46
+ .run(Number(advanced.enabled), nextRunAt, advanced.revision, JSON.stringify(advanced), task.id);
47
+ return true;
48
+ });
49
+ }
50
+ saveScheduledTaskRun(run) {
51
+ this.db.prepare("UPDATE scheduled_task_runs SET run_json = ? WHERE id = ? AND task_id = ?")
52
+ .run(JSON.stringify(run), run.id, run.taskId);
53
+ }
54
+ getScheduledTaskRun(id) {
55
+ const row = this.db.prepare("SELECT run_json FROM scheduled_task_runs WHERE id = ?").get(id);
56
+ return row ? JSON.parse(row.run_json) : null;
57
+ }
58
+ listOpenScheduledTaskRuns(taskId) {
59
+ const query = `SELECT run_json FROM scheduled_task_runs WHERE json_extract(run_json, '$.executionFinishedAt') IS NULL
60
+ AND json_extract(run_json, '$.state') IN ('claimed', 'accepted', 'unknown')`;
61
+ const rows = (taskId ? this.db.prepare(`${query} AND task_id = ? ORDER BY scheduled_at`).all(taskId)
62
+ : this.db.prepare(`${query} ORDER BY scheduled_at`).all());
63
+ return rows.map(row => JSON.parse(row.run_json));
64
+ }
65
+ getOpenScheduledTaskRunsForSession(sessionId) {
66
+ const rows = this.db.prepare(`SELECT run_json FROM scheduled_task_runs WHERE json_extract(run_json, '$.receipt.sessionId') = ?
67
+ AND json_extract(run_json, '$.executionFinishedAt') IS NULL`)
68
+ .all(sessionId);
69
+ return rows.map(row => JSON.parse(row.run_json));
70
+ }
71
+ listScheduledTaskRuns(taskId, limit = 50) {
72
+ const bounded = Math.max(1, Math.min(500, Number.isFinite(limit) ? Math.floor(limit) : 50));
73
+ const rows = (taskId
74
+ ? this.db.prepare("SELECT run_json FROM scheduled_task_runs WHERE task_id = ? ORDER BY scheduled_at DESC LIMIT ?").all(taskId, bounded)
75
+ : this.db.prepare("SELECT run_json FROM scheduled_task_runs ORDER BY scheduled_at DESC LIMIT ?").all(bounded));
76
+ return rows.map(row => JSON.parse(row.run_json));
77
+ }
78
+ }
@@ -0,0 +1,3 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ export declare function initializeStorageSchema(db: DatabaseSync): void;
3
+ export declare function ensureDatabaseFile(dbPath: string): boolean;