@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.
- package/README.md +5 -3
- package/dist/agent-dispatch.js +4 -9
- package/dist/ai-team-employee-binding.js +10 -9
- package/dist/ai-team-runner.d.ts +2 -1
- package/dist/ai-team-runner.js +38 -2
- package/dist/ai-team-types.js +3 -2
- package/dist/ai-text-runner.d.ts +61 -0
- package/dist/ai-text-runner.js +653 -0
- package/dist/avatar-input.d.ts +2 -0
- package/dist/avatar-input.js +19 -0
- package/dist/bounded-process-output.d.ts +5 -0
- package/dist/bounded-process-output.js +9 -0
- package/dist/build-info.json +3 -3
- package/dist/command-error.d.ts +6 -0
- package/dist/command-error.js +8 -0
- package/dist/config.js +11 -42
- package/dist/conversation-service.d.ts +12 -2
- package/dist/conversation-service.js +104 -25
- package/dist/conversation-session-preview.d.ts +2 -0
- package/dist/conversation-session-preview.js +4 -0
- package/dist/decision-client.js +22 -6
- package/dist/decision-expert-employee.js +2 -2
- package/dist/decision-expert-identity.d.ts +1 -1
- package/dist/decision-expert-identity.js +1 -1
- package/dist/decision-expert-service.d.ts +1 -0
- package/dist/decision-expert-service.js +82 -76
- package/dist/decision-hardware.js +4 -3
- package/dist/decision-types.d.ts +4 -0
- package/dist/decision-types.js +4 -0
- package/dist/deployment-lock.d.ts +13 -0
- package/dist/deployment-lock.js +189 -0
- package/dist/employee-text.js +1 -1
- package/dist/fixed-employee-avatar.d.ts +7 -0
- package/dist/fixed-employee-avatar.js +13 -0
- package/dist/git-quick-commit.d.ts +5 -40
- package/dist/git-quick-commit.js +8 -636
- package/dist/hr-employee-identity.d.ts +5 -0
- package/dist/hr-employee-identity.js +5 -0
- package/dist/hr-employee.d.ts +5 -0
- package/dist/hr-employee.js +21 -0
- package/dist/middleware/path-safety.js +1 -1
- package/dist/milestone-scope.js +1 -1
- package/dist/model-auto-assign.d.ts +1 -1
- package/dist/model-auto-assign.js +1 -1
- package/dist/models.d.ts +4 -0
- package/dist/models.js +21 -1
- package/dist/npm-update-utils.js +5 -44
- package/dist/pi-auto-resources.js +6 -3
- package/dist/plush-avatar.d.ts +10 -1
- package/dist/plush-avatar.js +13 -1
- package/dist/plush-cat-appearance.d.ts +97 -0
- package/dist/plush-cat-appearance.js +118 -0
- package/dist/plush-cat-whiskers.d.ts +6 -0
- package/dist/plush-cat-whiskers.js +42 -0
- package/dist/process-manager.d.ts +22 -21
- package/dist/process-manager.js +49 -83
- package/dist/prompt-optimizer.d.ts +1 -1
- package/dist/prompt-optimizer.js +1 -1
- package/dist/render-host.js +1 -19
- package/dist/scheduled-task-service.d.ts +41 -0
- package/dist/scheduled-task-service.js +294 -0
- package/dist/scheduled-task-state.d.ts +11 -0
- package/dist/scheduled-task-state.js +65 -0
- package/dist/scheduled-task-time.d.ts +4 -0
- package/dist/scheduled-task-time.js +82 -0
- package/dist/scheduled-task-types.d.ts +71 -0
- package/dist/scheduled-task-types.js +1 -0
- package/dist/server-ai-team-routes.js +3 -17
- package/dist/server-employee-routes.d.ts +3 -0
- package/dist/server-employee-routes.js +20 -38
- package/dist/server-file-routes.js +44 -1
- package/dist/server-pi-recommendation-routes.js +13 -4
- package/dist/server-request.d.ts +2 -0
- package/dist/server-request.js +9 -0
- package/dist/server-resume-routes.js +1 -1
- package/dist/server-scheduled-task-routes.d.ts +4 -0
- package/dist/server-scheduled-task-routes.js +34 -0
- package/dist/server-session-routes.d.ts +5 -13
- package/dist/server-session-routes.js +35 -70
- package/dist/server-task-routes.d.ts +6 -21
- package/dist/server-task-routes.js +25 -120
- package/dist/server-team-dispatch-routes.js +12 -2
- package/dist/server-workspace-routes.d.ts +1 -1
- package/dist/server-workspace-routes.js +33 -78
- package/dist/server.js +55 -20
- package/dist/session-model.d.ts +15 -0
- package/dist/session-model.js +23 -0
- package/dist/session-registry.d.ts +5 -1
- package/dist/session-registry.js +20 -11
- package/dist/session-request.d.ts +9 -0
- package/dist/session-request.js +27 -0
- package/dist/session-topic.d.ts +1 -1
- package/dist/session-topic.js +1 -1
- package/dist/session-transport.d.ts +3 -1
- package/dist/session-transport.js +11 -5
- package/dist/silicon-employee-draft.d.ts +2 -2
- package/dist/silicon-employee-draft.js +3 -3
- package/dist/socket-probe.d.ts +2 -0
- package/dist/socket-probe.js +20 -0
- package/dist/storage-schedules.d.ts +20 -0
- package/dist/storage-schedules.js +78 -0
- package/dist/storage-schema.d.ts +3 -0
- package/dist/storage-schema.js +702 -0
- package/dist/storage.d.ts +42 -8
- package/dist/storage.js +182 -715
- package/dist/structured-claude-adapter.js +12 -0
- package/dist/structured-claude-protocol.d.ts +4 -0
- package/dist/structured-claude-protocol.js +18 -4
- package/dist/structured-codex-adapter.js +11 -0
- package/dist/structured-exec-pump.js +79 -38
- package/dist/structured-failure.d.ts +21 -0
- package/dist/structured-failure.js +14 -0
- package/dist/structured-gemini-adapter.js +12 -2
- package/dist/structured-grok-adapter.js +12 -2
- package/dist/structured-opencode-adapter.js +8 -1
- package/dist/structured-qoder-adapter.js +14 -0
- package/dist/structured-session-manager.d.ts +13 -18
- package/dist/structured-session-manager.js +147 -517
- package/dist/system-employee.d.ts +1 -1
- package/dist/system-employee.js +1 -1
- package/dist/task-archive-policy.js +1 -1
- package/dist/task-read-model.d.ts +27 -0
- package/dist/task-read-model.js +47 -0
- package/dist/task-retention.d.ts +1 -1
- package/dist/task-retention.js +1 -1
- package/dist/task-title-service.d.ts +30 -0
- package/dist/task-title-service.js +122 -0
- package/dist/task-title.d.ts +1 -1
- package/dist/task-title.js +1 -1
- package/dist/terminal-daemon-client.js +1 -18
- package/dist/user-memory.js +1 -1
- package/dist/web-ui/asset-runtime.d.ts +12 -0
- package/dist/web-ui/asset-runtime.js +6 -0
- package/dist/web-ui/composer-avatar-activity.d.ts +6 -0
- package/dist/web-ui/composer-avatar-activity.js +29 -0
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/module-manifest.json +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/chat-render-PY6DIXNZ.js +24 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-2YZLCGYN.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-6CFHW3FA.js +111 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-6XQEBIEX.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-DDSO7OX7.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-G3CXNMA2.js +20 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-LGVAPHRS.js +36 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-NURCNMRU.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-PCMFGZKX.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-QRNA4D7E.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-TMRPHNJO.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-WC7RK7LP.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-YOXXNXOX.js +2 -0
- package/dist/web-ui/content/modules/5478be410b112154/chunk-YX475LXO.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/employee-card-MDWKU4V3.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-56WPSON2.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-5QOHKHNC.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-AWIJ5L2K.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-BOZLGIT5.js +69 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-EZV22Q7Y.js +4 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-IW43Z5ZF.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-JT7NIUQL.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-JWEIDB55.js +3 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-OU5TRYH6.js +2 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-PRSTCPVE.js +5 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-TWPJI3HE.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-WZGJSNML.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-X6KQVFD6.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-YKCQFMQR.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/host-bridge-2LZX6UUZ.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/library-bridge-MQFJGTGY.js +4 -0
- package/dist/web-ui/content/modules/5478be410b112154/page-T3VUCU2W.js +3 -0
- package/dist/web-ui/content/modules/5478be410b112154/panel-HJBGBBPY.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/runtime.js +920 -0
- package/dist/web-ui/content/modules/5478be410b112154/sha256-ANXV72UC.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/task-board-host-AVMDIHIV.js +2 -0
- package/dist/web-ui/content/modules/5478be410b112154/workspace-agent-picker-KI7QVSK3.js +1 -0
- package/dist/web-ui/content/modules/5478be410b112154/workspace-window-L542FGP2.js +1 -0
- package/dist/web-ui/content/plush-avatar.js +177 -177
- package/dist/web-ui/content/scripts.js +1 -994
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +32 -0
- package/dist/web-ui/embedded-assets.js +10 -8
- package/dist/web-ui/markdown.d.ts +3 -2
- package/dist/web-ui/markdown.js +61 -48
- package/dist/web-ui/message-file-target.d.ts +13 -0
- package/dist/web-ui/message-file-target.js +85 -0
- package/dist/web-ui/provider-identity.d.ts +1 -1
- package/dist/web-ui/provider-identity.js +3 -4
- package/dist/web-ui/react/local-preview/controller.d.ts +2 -0
- package/dist/web-ui/react/local-preview/controller.js +12 -0
- package/dist/web-ui/scripts.d.ts +10 -0
- package/dist/web-ui/scripts.js +42 -5
- package/package.json +6 -3
- package/browser-extension/icons/wand-128.png +0 -0
- package/browser-extension/icons/wand-16.png +0 -0
- package/browser-extension/icons/wand-32.png +0 -0
- package/browser-extension/icons/wand-48.png +0 -0
- 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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1234
|
-
|
|
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
|
-
|
|
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;
|
package/dist/session-registry.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/dist/session-topic.d.ts
CHANGED
package/dist/session-topic.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { callConfiguredAiText } from "./
|
|
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
|
|
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
|
-
/**
|
|
25
|
-
|
|
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 "./
|
|
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
|
-
/**
|
|
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 "./
|
|
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
|
-
/**
|
|
189
|
+
/** 招聘格式规则走系统提示,岗位需求走用户消息;执行通道由 HR 配置提供。 */
|
|
190
190
|
export function buildEmployeeDraftPrompt(expectation, providers, existingNames = []) {
|
|
191
191
|
const list = providers.length ? providers : [...SESSION_PROVIDERS];
|
|
192
192
|
const lines = [
|
|
193
|
-
"
|
|
193
|
+
"你正在为用户招聘一位「硅基员工」:一个长期负责某类工作的 AI 角色,之后会以结构化会话执行任务。这里只拟定人选资料,不办理入职。",
|
|
194
194
|
"根据用户对这位员工的期望,设计一份配置,只输出一个 JSON 对象(不要 Markdown 代码块,不要任何解释):",
|
|
195
195
|
'{"name":"员工名字","duty":"一句话职责","prompt":"角色设定","provider":"执行工具"}',
|
|
196
196
|
"",
|
|
@@ -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
|
+
}
|