@co0ontty/wand 4.83.1 → 4.84.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 +4 -0
- package/dist/agent-dispatch.js +5 -1
- package/dist/ai-team-availability.d.ts +1 -1
- package/dist/ai-team-prompts.js +13 -4
- package/dist/ai-team-runner.d.ts +80 -5
- package/dist/ai-team-runner.js +332 -55
- package/dist/ai-team-types.d.ts +92 -5
- package/dist/ai-team-types.js +9 -2
- package/dist/build-info.json +3 -3
- package/dist/cli-api.d.ts +1 -0
- package/dist/cli-api.js +3 -0
- package/dist/cli.js +18 -1
- package/dist/commit-task-archive.d.ts +3 -1
- package/dist/commit-task-archive.js +21 -10
- package/dist/config.d.ts +1 -1
- package/dist/config.js +73 -0
- package/dist/conversation-mentions.d.ts +16 -0
- package/dist/conversation-mentions.js +32 -0
- package/dist/conversation-service.d.ts +90 -0
- package/dist/conversation-service.js +759 -0
- package/dist/conversation-session-preview.d.ts +9 -0
- package/dist/conversation-session-preview.js +47 -0
- package/dist/conversation-task-preview.d.ts +5 -0
- package/dist/conversation-task-preview.js +8 -0
- package/dist/conversation-types.d.ts +80 -0
- package/dist/conversation-types.js +7 -0
- package/dist/core-extension-host.d.ts +27 -0
- package/dist/core-extension-host.js +138 -0
- package/dist/core-runner.d.ts +86 -0
- package/dist/core-runner.js +757 -0
- package/dist/core-status-cli.d.ts +9 -0
- package/dist/core-status-cli.js +280 -0
- package/dist/core-turn-tracker.d.ts +12 -0
- package/dist/core-turn-tracker.js +36 -0
- package/dist/decision-runner.d.ts +6 -0
- package/dist/decision-runner.js +3 -1
- package/dist/decision-tool.d.ts +2 -0
- package/dist/decision-tool.js +49 -22
- package/dist/env-utils.d.ts +7 -3
- package/dist/env-utils.js +30 -5
- package/dist/git-quick-commit.d.ts +5 -0
- package/dist/git-quick-commit.js +9 -3
- package/dist/git-utils.js +2 -1
- package/dist/harness-engine.d.ts +110 -0
- package/dist/harness-engine.js +331 -0
- package/dist/mission-diff.js +2 -1
- package/dist/model-auto-assign.d.ts +50 -0
- package/dist/model-auto-assign.js +163 -0
- package/dist/model-group-runner.d.ts +4 -0
- package/dist/model-group-runner.js +72 -0
- package/dist/model-groups.d.ts +38 -0
- package/dist/model-groups.js +138 -0
- package/dist/models.d.ts +12 -0
- package/dist/models.js +102 -8
- package/dist/npm-update-utils.js +2 -1
- package/dist/openrouter-free-cli.d.ts +25 -0
- package/dist/openrouter-free-cli.js +109 -0
- package/dist/openrouter-free-models.d.ts +85 -0
- package/dist/openrouter-free-models.js +458 -0
- package/dist/openrouter-free-selection.d.ts +5 -0
- package/dist/openrouter-free-selection.js +7 -0
- package/dist/path-repair.d.ts +10 -0
- package/dist/path-repair.js +50 -5
- package/dist/pi-auto-resources.d.ts +12 -0
- package/dist/pi-auto-resources.js +151 -0
- package/dist/pi-execution-types.d.ts +48 -0
- package/dist/pi-execution-types.js +1 -0
- package/dist/pi-execution.d.ts +9 -0
- package/dist/pi-execution.js +187 -0
- package/dist/pi-extension-resources.d.ts +13 -0
- package/dist/pi-extension-resources.js +71 -0
- package/dist/pi-openrouter-free-bridge.d.ts +2 -0
- package/dist/pi-openrouter-free-bridge.js +80 -0
- package/dist/pi-resource-bridge.d.ts +2 -0
- package/dist/pi-resource-bridge.js +73 -0
- package/dist/pi-resource-catalog.d.ts +18 -0
- package/dist/pi-resource-catalog.js +82 -0
- package/dist/pi-resource-recommendation.d.ts +29 -0
- package/dist/pi-resource-recommendation.js +169 -0
- package/dist/pi-resource-run.d.ts +8 -0
- package/dist/pi-resource-run.js +28 -0
- package/dist/pi-session-settings.d.ts +157 -0
- package/dist/pi-session-settings.js +166 -0
- package/dist/platform-action-catalog.d.ts +24 -0
- package/dist/platform-action-catalog.js +141 -0
- package/dist/platform-action-service.d.ts +37 -0
- package/dist/platform-action-service.js +138 -0
- package/dist/platform-action-types.d.ts +125 -0
- package/dist/platform-action-types.js +7 -0
- package/dist/process-manager.d.ts +21 -1
- package/dist/process-manager.js +168 -15
- package/dist/provider-catalog.js +1 -1
- package/dist/provider-cli-updater.js +29 -8
- package/dist/provider-history-scanner.d.ts +9 -0
- package/dist/provider-history-scanner.js +107 -1
- package/dist/retention.d.ts +19 -7
- package/dist/retention.js +171 -28
- package/dist/server-ai-team-routes.js +6 -2
- package/dist/server-conversation-routes.d.ts +5 -0
- package/dist/server-conversation-routes.js +91 -0
- package/dist/server-employee-routes.d.ts +2 -0
- package/dist/server-employee-routes.js +85 -11
- package/dist/server-pi-recommendation-routes.d.ts +13 -0
- package/dist/server-pi-recommendation-routes.js +67 -0
- package/dist/server-resume-routes.js +1 -1
- package/dist/server-session-routes.d.ts +4 -3
- package/dist/server-session-routes.js +182 -37
- package/dist/server-settings-routes.d.ts +5 -0
- package/dist/server-settings-routes.js +46 -0
- package/dist/server-task-routes.js +49 -4
- package/dist/server-team-dispatch-routes.d.ts +19 -0
- package/dist/server-team-dispatch-routes.js +71 -0
- package/dist/server-update-routes.d.ts +1 -1
- package/dist/server-update-routes.js +10 -5
- package/dist/server-workspace-routes.js +44 -24
- package/dist/server.d.ts +1 -0
- package/dist/server.js +148 -30
- package/dist/session-ai-context.d.ts +5 -1
- package/dist/session-ai-context.js +9 -4
- package/dist/session-registry.d.ts +4 -0
- package/dist/session-registry.js +47 -13
- package/dist/session-transport.js +8 -1
- package/dist/settings-web-access.d.ts +15 -0
- package/dist/settings-web-access.js +37 -0
- package/dist/silicon-employee-defaults.d.ts +29 -0
- package/dist/silicon-employee-defaults.js +41 -0
- package/dist/silicon-employee-dispatch.d.ts +5 -1
- package/dist/silicon-employee-dispatch.js +10 -4
- package/dist/silicon-employee-draft.d.ts +21 -4
- package/dist/silicon-employee-draft.js +103 -16
- package/dist/storage.d.ts +73 -2
- package/dist/storage.js +362 -26
- package/dist/structured-claude-protocol.js +1 -1
- package/dist/structured-client-protocol.js +11 -33
- package/dist/structured-content.d.ts +23 -0
- package/dist/structured-content.js +72 -0
- package/dist/structured-failure.d.ts +16 -0
- package/dist/structured-failure.js +36 -0
- package/dist/structured-pi-adapter.d.ts +8 -3
- package/dist/structured-pi-adapter.js +131 -30
- package/dist/structured-provider-common.d.ts +6 -0
- package/dist/structured-provider-common.js +23 -1
- package/dist/structured-runner.d.ts +45 -0
- package/dist/structured-session-manager.d.ts +78 -4
- package/dist/structured-session-manager.js +513 -129
- package/dist/structured-thinking.d.ts +15 -0
- package/dist/structured-thinking.js +46 -0
- package/dist/subagent-dispatch.d.ts +56 -0
- package/dist/subagent-dispatch.js +102 -0
- package/dist/system-employee.d.ts +4 -3
- package/dist/system-employee.js +7 -4
- package/dist/task-retention.d.ts +23 -0
- package/dist/task-retention.js +69 -0
- package/dist/task-types.d.ts +4 -0
- package/dist/task-types.js +3 -0
- package/dist/team-dispatch.d.ts +101 -0
- package/dist/team-dispatch.js +269 -0
- package/dist/tool-preview.js +7 -1
- package/dist/tui/commands.js +50 -1
- package/dist/tui/ipc-protocol.d.ts +1 -1
- package/dist/tui/ipc-server.d.ts +4 -0
- package/dist/tui/ipc-server.js +23 -0
- package/dist/turn-heartbeat.d.ts +35 -0
- package/dist/turn-heartbeat.js +64 -0
- package/dist/types.d.ts +147 -1
- package/dist/user-profile.d.ts +33 -0
- package/dist/user-profile.js +70 -0
- package/dist/web-ui/chat-message-meta.d.ts +11 -0
- package/dist/web-ui/chat-message-meta.js +47 -0
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/scripts.js +795 -95
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +1 -6808
- package/dist/web-ui/content/vendor/qrcode/qrcode.bundle.js +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +5 -5
- package/dist/web-ui/index.d.ts +1 -1
- package/dist/web-ui/index.js +3 -3
- package/dist/web-ui/markdown.d.ts +2 -0
- package/dist/web-ui/markdown.js +111 -0
- package/dist/web-ui/page.d.ts +4 -0
- package/dist/web-ui/page.js +8 -0
- package/dist/web-ui/react/local-preview/controller.d.ts +30 -0
- package/dist/web-ui/react/local-preview/controller.js +159 -0
- package/dist/web-ui/running-activity.d.ts +63 -0
- package/dist/web-ui/running-activity.js +134 -0
- package/dist/web-ui/styles.js +2 -11
- package/package.json +7 -2
package/dist/server.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OpenRouterFreeModelsService } from "./openrouter-free-models.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import compression from "compression";
|
|
3
4
|
import express from "express";
|
|
@@ -13,12 +14,15 @@ import { AuthService, BROWSER_ADMIN_PRINCIPAL, CONNECTED_APP_PRINCIPAL, principa
|
|
|
13
14
|
import { ensureCertificates } from "./cert.js";
|
|
14
15
|
import { getDefaultModelForProvider, getProviderDefaultModels, isExecutionMode, resolveConfigDir, } from "./config.js";
|
|
15
16
|
import { ModelCatalogService } from "./models.js";
|
|
17
|
+
import { defaultModelGroupSelector } from "./model-groups.js";
|
|
16
18
|
import { ProcessManager } from "./process-manager.js";
|
|
17
19
|
import { SessionLogger } from "./session-logger.js";
|
|
20
|
+
import { coreHarnessAgentDir, warmCoreHarness } from "./harness-engine.js";
|
|
18
21
|
import { SessionRegistry } from "./session-registry.js";
|
|
19
22
|
import { SessionCompletionTracker } from "./session-completion.js";
|
|
20
23
|
import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
21
24
|
import { StructuredSessionManager } from "./structured-session-manager.js";
|
|
25
|
+
import { SettingsWebAccess, registerSettingsWebAccessRoute } from "./settings-web-access.js";
|
|
22
26
|
import { DecisionService } from "./decision-service.js";
|
|
23
27
|
import { registerDecisionRoutes } from "./server-decision-routes.js";
|
|
24
28
|
import { recordRecentPath, registerFileRoutes } from "./server-file-routes.js";
|
|
@@ -33,8 +37,12 @@ import { registerTaskRoutes } from "./server-task-routes.js";
|
|
|
33
37
|
import { getGithubConnectorStatus } from "./github-connector.js";
|
|
34
38
|
import { registerMissionRoutes } from "./server-mission-routes.js";
|
|
35
39
|
import { Missions } from "./missions.js";
|
|
36
|
-
import { createAiTeamRunner } from "./ai-team-runner.js";
|
|
40
|
+
import { AI_TEAM_CHAT_PREFIX, createAiTeamRunner } from "./ai-team-runner.js";
|
|
41
|
+
import { ConversationService } from "./conversation-service.js";
|
|
42
|
+
import { CONVERSATION_RELAY_PREFIX } from "./conversation-types.js";
|
|
43
|
+
import { forwardConversationRelay, registerConversationRoutes } from "./server-conversation-routes.js";
|
|
37
44
|
import { registerAiTeamRoutes } from "./server-ai-team-routes.js";
|
|
45
|
+
import { registerTeamDispatchRoutes } from "./server-team-dispatch-routes.js";
|
|
38
46
|
import { registerSiliconEmployeeRoutes } from "./server-employee-routes.js";
|
|
39
47
|
import { defaultRoleForCli } from "./default-employee.js";
|
|
40
48
|
import { UserMemoryService } from "./user-memory.js";
|
|
@@ -50,7 +58,7 @@ import { asyncRoute, jsonErrorHandler } from "./express-async.js";
|
|
|
50
58
|
import { checkPackageUpdateAsync, installPackageGloballyAsync, normalizeUpdateChannel, resolveGlobalWandCli, } from "./npm-update-utils.js";
|
|
51
59
|
import { repairServiceUnitAfterUpdate } from "./service-self-repair.js";
|
|
52
60
|
import { computeRelaunch } from "./relaunch.js";
|
|
53
|
-
import { startRetentionTimer } from "./retention.js";
|
|
61
|
+
import { createRetentionSweep, startRetentionTimer } from "./retention.js";
|
|
54
62
|
import { RuntimeConfigState } from "./runtime-config.js";
|
|
55
63
|
import { safeServiceInstalled } from "./tui/runtime-utils.js";
|
|
56
64
|
import { checkManagedServiceUpdatePreflight, } from "./update-helper.js";
|
|
@@ -73,6 +81,7 @@ import { createUpgradeAwareTerminalHost } from "./render-host.js";
|
|
|
73
81
|
import { createUpgradeAwareStructuredHost } from "./render-structured-host.js";
|
|
74
82
|
import { checkPasswordRateLimit, recordFailedPassword, resetPasswordRateLimit } from "./middleware/rate-limit.js";
|
|
75
83
|
import { updateProviderClis, verifyProviderCliUpdateResults, } from "./provider-cli-updater.js";
|
|
84
|
+
import { userAuthor } from "./user-profile.js";
|
|
76
85
|
const SERVER_MODULE_DIR = path.dirname(new URL(import.meta.url).pathname);
|
|
77
86
|
const RUNTIME_ROOT_DIR = path.resolve(SERVER_MODULE_DIR, "..");
|
|
78
87
|
// ── Package info ──
|
|
@@ -133,9 +142,12 @@ const DISPLAY_VERSION = BUILD_INFO.version || PKG_VERSION;
|
|
|
133
142
|
const SERVER_INSTANCE_ID = crypto.randomUUID();
|
|
134
143
|
// ── Auth helpers ──
|
|
135
144
|
const requestPrincipals = new WeakMap();
|
|
136
|
-
function buildRequireAuth(useHttps, storage, config, authService) {
|
|
145
|
+
function buildRequireAuth(useHttps, storage, config, authService, settingsAccess) {
|
|
137
146
|
return function requireAuth(req, res, next) {
|
|
138
|
-
const
|
|
147
|
+
const sessionToken = readSessionCookie(req, useHttps);
|
|
148
|
+
const sessionPrincipal = authService.authenticateSession(sessionToken);
|
|
149
|
+
const principal = (sessionPrincipal && principalHasScope(sessionPrincipal, "session-preferences") &&
|
|
150
|
+
settingsAccess.accepts(sessionToken, req.headers.cookie) ? BROWSER_ADMIN_PRINCIPAL : sessionPrincipal)
|
|
139
151
|
?? authenticateBearerAppToken(req, storage, config);
|
|
140
152
|
if (!principal) {
|
|
141
153
|
res.status(401).json({ error: "未授权,请先登录。" });
|
|
@@ -272,6 +284,8 @@ export function isPortInUseError(error) {
|
|
|
272
284
|
&& error.code === "EADDRINUSE");
|
|
273
285
|
}
|
|
274
286
|
export async function startServer(config, configPath, options = {}) {
|
|
287
|
+
if (config.shell?.trim())
|
|
288
|
+
process.env.SHELL = config.shell.trim();
|
|
275
289
|
// 关键:在创建 ProcessManager / 任何 spawn 之前先修 PATH。
|
|
276
290
|
// 服务被注册为 systemd / launchd 时,unit 文件里的 PATH 是安装那一刻烧死的,
|
|
277
291
|
// 之后用户切 node 版本 / 重装 wand / 把 claude 装到新位置都不会更新 unit,
|
|
@@ -299,7 +313,9 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
299
313
|
app.set("trust proxy", "loopback, 172.16.0.0/12");
|
|
300
314
|
const storage = new WandStorage(resolveDatabasePath(configPath));
|
|
301
315
|
const runtimeConfig = new RuntimeConfigState(config);
|
|
316
|
+
const openRouter = new OpenRouterFreeModelsService(storage, options.openRouterFetch);
|
|
302
317
|
const authService = new AuthService(storage);
|
|
318
|
+
const settingsAccess = new SettingsWebAccess();
|
|
303
319
|
const decisions = new DecisionService(config.localDecision);
|
|
304
320
|
// 默认模型优先读存储(UI 改设置后实时生效),未设置时由 getPreference 回落到 config。
|
|
305
321
|
const getCurrentDefaultModels = () => ({
|
|
@@ -316,11 +332,14 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
316
332
|
const currentDefaults = getCurrentDefaultModels();
|
|
317
333
|
return {
|
|
318
334
|
storage,
|
|
335
|
+
managedPiModels: () => openRouter.catalog(),
|
|
336
|
+
managedPiModelMembers: () => openRouter.members(),
|
|
337
|
+
modelGroups: () => config.modelGroups ?? [],
|
|
319
338
|
inheritEnv: config.inheritEnv !== false,
|
|
320
339
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
321
340
|
baseUrl: process.env.ANTHROPIC_BASE_URL,
|
|
322
341
|
...injected,
|
|
323
|
-
piEndpointDiscovery: injected.piEndpointDiscovery ?? { enabled: !testMode },
|
|
342
|
+
piEndpointDiscovery: injected.piEndpointDiscovery ?? { enabled: !testMode, agentDir: coreHarnessAgentDir(config.harness) },
|
|
324
343
|
configuredClaudeModels: [
|
|
325
344
|
currentDefaults.claude,
|
|
326
345
|
config.commitCli === "claude" ? (storage.getPreference("pref:commitModel", config.commitModel) ?? "") : undefined,
|
|
@@ -351,22 +370,36 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
351
370
|
knownSessionIds: knownPtySessionIds,
|
|
352
371
|
});
|
|
353
372
|
const terminalHost = ptyHosts.host;
|
|
354
|
-
|
|
373
|
+
let decisionRuntime = null;
|
|
374
|
+
let autoAssignEvaluate;
|
|
375
|
+
const processes = new ProcessManager(config, storage, configDir, terminalHost, { resolveDecisionEvaluate: () => autoAssignEvaluate });
|
|
355
376
|
const structuredLogger = new SessionLogger(configDir, config.shortcutLogMaxBytes);
|
|
356
377
|
// Production startup provides a daemon-backed host for structured CLI runs
|
|
357
378
|
// even when Render owns every PTY. In-process hosts are for test injection.
|
|
358
379
|
const legacyStructuredHost = ptyHosts.legacyHost
|
|
359
380
|
?? (terminalHost instanceof TerminalDaemonClient ? terminalHost : null);
|
|
360
381
|
const structuredHosts = await createUpgradeAwareStructuredHost(configPath, legacyStructuredHost, config.structured?.processHost);
|
|
361
|
-
|
|
362
|
-
|
|
382
|
+
const structuredSessions = new StructuredSessionManager(storage, config, structuredLogger, {}, structuredHosts.host, () => decisionRuntime, openRouter, () => autoAssignEvaluate);
|
|
383
|
+
// core harness 预热:只读本机 Pi 认证与模型目录,不联网。失败不影响启动,
|
|
384
|
+
// 但要在开始接输入前得到确定结论,否则首轮引擎裁决会因缓存未就绪而退回 CLI。
|
|
385
|
+
const coreHarnessStatus = await warmCoreHarness(config.harness);
|
|
386
|
+
if (coreHarnessStatus) {
|
|
387
|
+
console.log(coreHarnessStatus.available
|
|
388
|
+
? `[wand] core harness 可用(Pi provider ${coreHarnessStatus.providerCount} 个、模型 ${coreHarnessStatus.modelCount} 个,agentDir ${coreHarnessStatus.agentDir});pi 结构化会话将使用进程内引擎。`
|
|
389
|
+
: `[wand] core harness 不可用,pi 结构化会话回退 CLI:${coreHarnessStatus.reason}`);
|
|
390
|
+
}
|
|
363
391
|
const sessionRegistry = new SessionRegistry(processes, structuredSessions, storage);
|
|
364
392
|
const sessionCompletions = new SessionCompletionTracker(storage, (id) => sessionRegistry.get(id));
|
|
365
393
|
const missions = new Missions(storage, structuredSessions, sessionRegistry);
|
|
394
|
+
// 任务保留的立即扫描也晚绑定:设置路由先注册,WebSocket 和会话表稍后才就绪。
|
|
395
|
+
let runTaskRetentionSweep = () => ({
|
|
396
|
+
archivedSessions: 0, purgedSessions: 0, archivedTasks: 0, purgedTasks: 0, purgedTeamRuns: 0,
|
|
397
|
+
});
|
|
366
398
|
// wsManager 在后面才建:团队运行的变更通知经由这个转发口,接好之前静默丢弃。
|
|
367
399
|
let notifyAiTeamRun = (_data) => { };
|
|
368
400
|
// 运行中步骤的 live 文本推送(§4.9),同样走系统通知,接好之前静默丢弃。
|
|
369
401
|
let notifyAiTeamRunLive = (_update) => { };
|
|
402
|
+
let notifyConversationSession = (_update) => { };
|
|
370
403
|
// 团队降级的 model-unknown 事前比对只看这份已发现的清单;没刷新过就是空,判定方向是放行。
|
|
371
404
|
const aiTeamModelIds = (provider) => {
|
|
372
405
|
const cache = modelCatalog.snapshot();
|
|
@@ -381,14 +414,27 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
381
414
|
}[provider];
|
|
382
415
|
return cache[key].map((entry) => entry.id);
|
|
383
416
|
};
|
|
417
|
+
let conversations = null;
|
|
384
418
|
const aiTeams = createAiTeamRunner({
|
|
385
419
|
storage, config, structured: structuredSessions, processes, sessions: sessionRegistry,
|
|
386
|
-
|
|
420
|
+
selfAuthor: () => userAuthor(config.userProfile),
|
|
421
|
+
notify: (run) => {
|
|
422
|
+
conversations?.importRun(run);
|
|
423
|
+
notifyAiTeamRun({ kind: "ai-team-run", runId: run.id, taskId: run.taskId });
|
|
424
|
+
},
|
|
387
425
|
notifyLive: (update) => notifyAiTeamRunLive(update),
|
|
388
426
|
models: aiTeamModelIds,
|
|
427
|
+
chatHistoryTurns: id => conversations?.history(id) ?? [],
|
|
389
428
|
// 成员名单里的模型名:`default` 哨兵换成服务端为该 CLI 配置的默认模型。
|
|
390
429
|
defaultModelOf: (provider) => getDefaultModelForProvider(config, provider),
|
|
391
430
|
});
|
|
431
|
+
const conversationService = new ConversationService({ storage, config, structured: structuredSessions, runner: aiTeams,
|
|
432
|
+
notifySession: update => notifyConversationSession(update), deleteSession: id => { sessionRegistry.deleteWithProviderHistory(id); } });
|
|
433
|
+
conversations = conversationService;
|
|
434
|
+
conversationService.migrateExistingChats();
|
|
435
|
+
for (const prefix of [CONVERSATION_RELAY_PREFIX, AI_TEAM_CHAT_PREFIX]) {
|
|
436
|
+
structuredSessions.registerRelay(prefix, (sessionId, input) => forwardConversationRelay(conversationService, sessionId, input));
|
|
437
|
+
}
|
|
392
438
|
const updateState = new ServerUpdateState();
|
|
393
439
|
const getUpdateChannel = () => normalizeUpdateChannel(storage.getConfigValue("updateChannel"));
|
|
394
440
|
let disconnectAuthenticatedSockets = () => { };
|
|
@@ -397,7 +443,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
397
443
|
};
|
|
398
444
|
const useHttps = config.https === true;
|
|
399
445
|
const protocol = useHttps ? "https" : "http";
|
|
400
|
-
const requireAuth = buildRequireAuth(useHttps, storage, config, authService);
|
|
446
|
+
const requireAuth = buildRequireAuth(useHttps, storage, config, authService, settingsAccess);
|
|
401
447
|
const requireAdmin = buildRequireScope("admin");
|
|
402
448
|
const requireSessions = buildRequireScope("sessions");
|
|
403
449
|
const requireFiles = buildRequireScope("files");
|
|
@@ -468,9 +514,9 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
468
514
|
res.type("application/javascript").send(chunk.content);
|
|
469
515
|
});
|
|
470
516
|
// ── Web UI endpoints ──
|
|
471
|
-
app.get("/", (
|
|
517
|
+
app.get(["/", "/settings"], (req, res) => {
|
|
472
518
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
473
|
-
res.type("html").send(renderApp(configPath));
|
|
519
|
+
res.type("html").send(renderApp(configPath, /^\/settings\/?$/.test(req.path) ? "settings" : "console", req.query.client === "app" ? "client" : "browser"));
|
|
474
520
|
});
|
|
475
521
|
app.get("/api/structured-chat-avatar/:role", requireAuth, asyncRoute(async (req, res) => {
|
|
476
522
|
const role = req.params.role === "user" || req.params.role === "assistant"
|
|
@@ -597,11 +643,21 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
597
643
|
res.json({ ok: true, reauthenticationRequired: true });
|
|
598
644
|
});
|
|
599
645
|
// ── Android APK update & download (no auth required) ──
|
|
600
|
-
registerPublicUpdateRoutes(app, distributionManager);
|
|
646
|
+
registerPublicUpdateRoutes(app, distributionManager, config.publicOrigin);
|
|
601
647
|
// Public probe so the unauthenticated browser does not log a 401 on /api/config
|
|
602
648
|
app.get("/api/session-check", (req, res) => {
|
|
603
649
|
res.json({ authed: authService.validateSession(readSessionCookie(req, useHttps)) });
|
|
604
650
|
});
|
|
651
|
+
registerSettingsWebAccessRoute(app, {
|
|
652
|
+
requireAuth, useHttps, access: settingsAccess,
|
|
653
|
+
authenticateSession: (token) => authService.authenticateSession(token),
|
|
654
|
+
});
|
|
655
|
+
// Count-only compatibility probe. Local control (including session IDs and
|
|
656
|
+
// admission barriers) uses the owner-only Unix IPC socket.
|
|
657
|
+
app.get("/api/core-status", (_req, res) => {
|
|
658
|
+
const { hasActiveTurns, activeTurnCount } = structuredSessions.getCoreTurnStatus();
|
|
659
|
+
res.set("Cache-Control", "no-store").json({ hasActiveTurns, activeTurnCount });
|
|
660
|
+
});
|
|
605
661
|
registerDecisionRoutes(app, { storage, decisions, requireAuth, requireSessions });
|
|
606
662
|
app.use("/api", requireAuth);
|
|
607
663
|
// Connected apps receive only the route families used by native clients and
|
|
@@ -613,6 +669,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
613
669
|
"/api/session-list",
|
|
614
670
|
"/api/session-directories",
|
|
615
671
|
"/api/structured-sessions",
|
|
672
|
+
"/api/conversations",
|
|
616
673
|
"/api/commands",
|
|
617
674
|
"/api/user-memory",
|
|
618
675
|
"/api/claude-history",
|
|
@@ -691,6 +748,8 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
691
748
|
],
|
|
692
749
|
structuredChatPersona,
|
|
693
750
|
cardDefaults: config.cardDefaults,
|
|
751
|
+
// 会话署名与头像:客户端只做展示投影,不解释成任何执行身份。
|
|
752
|
+
userProfile: config.userProfile ?? {},
|
|
694
753
|
// 把语言偏好暴露给前端做 UI 文案 i18n。后端原本只用它给 Claude 拼 system prompt,
|
|
695
754
|
// 前端没收到 → "SUBAGENT" / "Read" 这些 UI label 一直是英文,跟用户设的中文不匹配。
|
|
696
755
|
language: config.language ?? "",
|
|
@@ -722,7 +781,9 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
722
781
|
getDistributionSettings,
|
|
723
782
|
getGithubConnector: () => getGithubConnectorStatus(storage),
|
|
724
783
|
modelCatalog,
|
|
784
|
+
openRouter,
|
|
725
785
|
resolveAppConnectCode: (req) => resolveAppConnectCode(req, config, useHttps, getEffectivePassword(storage, config)),
|
|
786
|
+
sweepTaskRetention: () => runTaskRetentionSweep(),
|
|
726
787
|
});
|
|
727
788
|
registerGithubRoutes(app, { storage, requireAdmin, sessions: sessionRegistry });
|
|
728
789
|
// 任务管理与 Missions 一样只需登录:原生 connected-app 也要能列/建/派发。
|
|
@@ -732,12 +793,15 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
732
793
|
runner: aiTeams,
|
|
733
794
|
notifyTeamChanged: (teamId) => notifyAiTeamRun({ kind: "ai-team-definition", teamId }),
|
|
734
795
|
});
|
|
796
|
+
// 无指派派工:本地决策模型建议名单 → 确认开工(不自动派工,见 AGENTS)。
|
|
797
|
+
registerTeamDispatchRoutes(app, { storage, runner: aiTeams, decisions });
|
|
735
798
|
registerSiliconEmployeeRoutes(app, {
|
|
736
799
|
storage,
|
|
737
800
|
config,
|
|
738
801
|
notifyEmployeeChanged: (employeeId) => notifyAiTeamRun({ kind: "silicon-employee-definition", employeeId }),
|
|
739
802
|
});
|
|
740
803
|
registerAttentionRoutes(app, { sessions: sessionRegistry, runner: aiTeams });
|
|
804
|
+
registerConversationRoutes(app, conversationService);
|
|
741
805
|
registerAdminUpdateRoutes(app, {
|
|
742
806
|
storage,
|
|
743
807
|
config,
|
|
@@ -764,7 +828,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
764
828
|
}
|
|
765
829
|
registerSessionRoutes(app, processes, structuredSessions, storage, config.defaultMode, config, sessionRegistry, (cwd) => {
|
|
766
830
|
recordRecentPath(storage, cwd);
|
|
767
|
-
}, (event) => wsManager.emitEvent(event));
|
|
831
|
+
}, (event) => wsManager.emitEvent(event), decisions);
|
|
768
832
|
registerClaudeHistoryRoutes(app, processes, storage);
|
|
769
833
|
registerWorkspaceRoutes(app, storage, sessionRegistry, { config });
|
|
770
834
|
registerMissionRoutes(app, missions);
|
|
@@ -837,7 +901,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
837
901
|
? "qodercli"
|
|
838
902
|
: rawCommand;
|
|
839
903
|
const effectiveModel = provider
|
|
840
|
-
? rawModel || getDefaultModelForProvider(config, provider) || undefined
|
|
904
|
+
? rawModel || defaultModelGroupSelector(config.modelGroups, provider, getDefaultModelForProvider(config, provider)) || getDefaultModelForProvider(config, provider) || undefined
|
|
841
905
|
: undefined;
|
|
842
906
|
const reqCols = typeof body.cols === "number" && Number.isFinite(body.cols) ? body.cols : undefined;
|
|
843
907
|
const reqRows = typeof body.rows === "number" && Number.isFinite(body.rows) ? body.rows : undefined;
|
|
@@ -957,6 +1021,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
957
1021
|
wsManager.emitEvent(event);
|
|
958
1022
|
});
|
|
959
1023
|
structuredSessions.setEventEmitter((event) => {
|
|
1024
|
+
conversationService.ingestSessionEvent(event);
|
|
960
1025
|
sessionCompletions.ingest(event);
|
|
961
1026
|
missions.ingest(event);
|
|
962
1027
|
aiTeams.ingest(event);
|
|
@@ -970,10 +1035,14 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
970
1035
|
type: "notification", sessionId: "__system__", data: { kind: "ai-team-step-live", ...update },
|
|
971
1036
|
});
|
|
972
1037
|
};
|
|
1038
|
+
notifyConversationSession = update => {
|
|
1039
|
+
wsManager.emitEvent({ type: "notification", sessionId: "__system__", data: { kind: "conversation-session-preview", ...update } });
|
|
1040
|
+
};
|
|
973
1041
|
// Re-attach structured CLI runs that kept going inside terminald while the
|
|
974
1042
|
// previous web process was down; fire-and-forget, failures are logged inside.
|
|
975
1043
|
void structuredSessions.recoverDetachedRuns();
|
|
976
1044
|
aiTeams.reconcile();
|
|
1045
|
+
structuredSessions.resumeQueuedPiMessages();
|
|
977
1046
|
// ── Restart endpoint (needs server + wss in scope) ──
|
|
978
1047
|
/**
|
|
979
1048
|
* 统一的关服 + 重启。重启方式由 computeRelaunch 决定:
|
|
@@ -981,12 +1050,28 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
981
1050
|
* 重写的)ExecStart 拉起,避免再 spawn detached 子进程与 systemd 抢单实例 pidfile;
|
|
982
1051
|
* - 否则 → spawn 一个 detached 子进程(bin 优先全局安装,确保更新后跑到新版)再退出。
|
|
983
1052
|
*/
|
|
1053
|
+
let restartPending = false;
|
|
984
1054
|
function relaunchAfterShutdown() {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
void
|
|
1055
|
+
if (restartPending)
|
|
1056
|
+
return;
|
|
1057
|
+
restartPending = true;
|
|
1058
|
+
const releaseDrain = structuredSessions.beginCoreRestartDrain();
|
|
1059
|
+
void (async () => {
|
|
1060
|
+
if (structuredSessions.getCoreTurnStatus().hasActiveTurns) {
|
|
1061
|
+
process.stdout.write("[wand] 等待原生 Core 回合完成后再重启…\n");
|
|
1062
|
+
}
|
|
1063
|
+
while (structuredSessions.getCoreTurnStatus().hasActiveTurns) {
|
|
1064
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1065
|
+
}
|
|
1066
|
+
// Do not dispose tools/services or arm the force-exit timer while a native
|
|
1067
|
+
// turn is still running. The same barrier covers manual and auto restart.
|
|
1068
|
+
const plan = computeRelaunch({
|
|
1069
|
+
serviceInstalled: safeServiceInstalled(),
|
|
1070
|
+
globalCli: resolveGlobalWandCli(),
|
|
1071
|
+
});
|
|
1072
|
+
const forceExitTimer = setTimeout(() => process.exit(0), 5000);
|
|
1073
|
+
forceExitTimer.unref?.();
|
|
1074
|
+
await close();
|
|
990
1075
|
if (plan.mode === "spawn") {
|
|
991
1076
|
spawn(process.execPath, [plan.bin ?? "", ...(plan.args ?? [])], {
|
|
992
1077
|
detached: true,
|
|
@@ -996,10 +1081,11 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
996
1081
|
}).unref();
|
|
997
1082
|
}
|
|
998
1083
|
process.exit(0);
|
|
1084
|
+
})().catch((error) => {
|
|
1085
|
+
restartPending = false;
|
|
1086
|
+
releaseDrain();
|
|
1087
|
+
wandError("等待 Core 回合后重启失败", getErrorMessage(error));
|
|
999
1088
|
});
|
|
1000
|
-
// Force exit after 5s if graceful shutdown stalls
|
|
1001
|
-
const forceExitTimer = setTimeout(() => process.exit(0), 5000);
|
|
1002
|
-
forceExitTimer.unref?.();
|
|
1003
1089
|
}
|
|
1004
1090
|
app.post("/api/restart", requireAdmin, asyncRoute(async (_req, res) => {
|
|
1005
1091
|
res.json({ ok: true, message: "服务正在重启..." });
|
|
@@ -1019,10 +1105,12 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1019
1105
|
const cleanupFailedListen = () => {
|
|
1020
1106
|
shuttingDown = true;
|
|
1021
1107
|
decisions.dispose();
|
|
1108
|
+
openRouter.dispose();
|
|
1022
1109
|
try {
|
|
1023
1110
|
processes.dispose();
|
|
1024
1111
|
}
|
|
1025
1112
|
catch { /* noop */ }
|
|
1113
|
+
conversationService.dispose();
|
|
1026
1114
|
try {
|
|
1027
1115
|
structuredSessions.dispose();
|
|
1028
1116
|
}
|
|
@@ -1069,10 +1157,18 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1069
1157
|
const address = server.address();
|
|
1070
1158
|
const actualPort = typeof address === "object" && address ? address.port : config.port;
|
|
1071
1159
|
bindAddr = `${config.host}:${actualPort}`;
|
|
1160
|
+
const decisionStatus = decisions.status();
|
|
1072
1161
|
if (config.localDecision?.enabled) {
|
|
1073
1162
|
const localHost = config.host === "::1" ? "[::1]" : config.host === "0.0.0.0" || config.host === "::" ? "127.0.0.1" : config.host;
|
|
1074
1163
|
decisionRuntime = { url: `${protocol}://${localHost}:${actualPort}`,
|
|
1075
|
-
...(activeSslCertPath ? { caPath: activeSslCertPath } : {})
|
|
1164
|
+
...(activeSslCertPath ? { caPath: activeSslCertPath } : {}),
|
|
1165
|
+
// core 会话直接调进程内决策服务,不再绕出 HTTP + env token。
|
|
1166
|
+
evaluate: (value, caller, signal) => decisions.evaluate(value, caller, signal) };
|
|
1167
|
+
}
|
|
1168
|
+
// 「智能分配」只要真正能跑(启用 + 平台/运行环境就绪)才由本地决策负责;
|
|
1169
|
+
// 否则转给系统员工的一次性文本回退,而不是每次白等一个必然失败的调用。
|
|
1170
|
+
if (config.localDecision?.enabled && decisionStatus.supported && decisionStatus.configured) {
|
|
1171
|
+
autoAssignEvaluate = (value, caller, signal) => decisions.evaluate(value, caller, signal);
|
|
1076
1172
|
}
|
|
1077
1173
|
const scheme = useHttps ? "HTTPS" : "HTTP";
|
|
1078
1174
|
// 主 URL:本机回环;若绑定 0.0.0.0 再补一个对外提示。
|
|
@@ -1108,6 +1204,9 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1108
1204
|
data: { kind: "models", revision: result.revision, refreshedAt: result.refreshedAt },
|
|
1109
1205
|
});
|
|
1110
1206
|
});
|
|
1207
|
+
openRouter.onChanged(() => modelCatalog.publishManagedPiModels());
|
|
1208
|
+
if (!testMode)
|
|
1209
|
+
openRouter.start();
|
|
1111
1210
|
let modelCatalogRefreshTimer = null;
|
|
1112
1211
|
if (!testMode) {
|
|
1113
1212
|
void modelCatalog.refresh().catch(() => { });
|
|
@@ -1117,10 +1216,29 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1117
1216
|
}, MODEL_CATALOG_AUTO_REFRESH_INTERVAL_MS);
|
|
1118
1217
|
modelCatalogRefreshTimer.unref();
|
|
1119
1218
|
}
|
|
1120
|
-
//
|
|
1219
|
+
// 会话仍按固定 7 天归档再清理;任务窗口每次扫描读取当前设置。
|
|
1220
|
+
// 保存任务保留设置后走同一条扫描,不等下一小时。
|
|
1221
|
+
const retentionDeps = {
|
|
1222
|
+
storage,
|
|
1223
|
+
sessions: sessionRegistry,
|
|
1224
|
+
taskRetention: () => config.taskRetention,
|
|
1225
|
+
};
|
|
1226
|
+
const sweepRetention = createRetentionSweep(retentionDeps);
|
|
1227
|
+
runTaskRetentionSweep = () => {
|
|
1228
|
+
const result = sweepRetention();
|
|
1229
|
+
try {
|
|
1230
|
+
wsManager.emitEvent({
|
|
1231
|
+
type: "notification",
|
|
1232
|
+
sessionId: "__system__",
|
|
1233
|
+
data: { kind: "task-retention", ...result },
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
catch { /* 扫描结果已经返回给保存请求,通知失败不回滚 */ }
|
|
1237
|
+
return result;
|
|
1238
|
+
};
|
|
1121
1239
|
let retentionTimer = null;
|
|
1122
1240
|
if (!testMode) {
|
|
1123
|
-
retentionTimer = startRetentionTimer(
|
|
1241
|
+
retentionTimer = startRetentionTimer(retentionDeps, 60 * 60 * 1000, sweepRetention);
|
|
1124
1242
|
}
|
|
1125
1243
|
// Express 4 does not forward rejected route promises automatically. Every
|
|
1126
1244
|
// async route above is wrapped with asyncRoute, and this final middleware
|
|
@@ -1194,10 +1312,8 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1194
1312
|
previousInstanceId: SERVER_INSTANCE_ID,
|
|
1195
1313
|
},
|
|
1196
1314
|
});
|
|
1197
|
-
//
|
|
1198
|
-
const restartTimer = setTimeout(() =>
|
|
1199
|
-
relaunchAfterShutdown();
|
|
1200
|
-
}, 1000);
|
|
1315
|
+
// Manual and automatic restarts share the same live admission barrier.
|
|
1316
|
+
const restartTimer = setTimeout(() => relaunchAfterShutdown(), 1000);
|
|
1201
1317
|
restartTimer.unref?.();
|
|
1202
1318
|
}
|
|
1203
1319
|
catch (error) {
|
|
@@ -1269,6 +1385,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1269
1385
|
closePromise = (async () => {
|
|
1270
1386
|
shuttingDown = true;
|
|
1271
1387
|
decisions.dispose();
|
|
1388
|
+
openRouter.dispose();
|
|
1272
1389
|
await userMemory.dispose();
|
|
1273
1390
|
if (updateCheckTimer) {
|
|
1274
1391
|
clearInterval(updateCheckTimer);
|
|
@@ -1318,6 +1435,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1318
1435
|
processes.dispose();
|
|
1319
1436
|
}
|
|
1320
1437
|
catch { /* best-effort shutdown */ }
|
|
1438
|
+
conversationService.dispose();
|
|
1321
1439
|
try {
|
|
1322
1440
|
structuredSessions.dispose();
|
|
1323
1441
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ModelGroup } from "./model-groups.js";
|
|
1
2
|
import type { SiliconEmployee } from "./ai-team-types.js";
|
|
2
3
|
import type { AiCliCandidate, SessionProvider, SessionSnapshot, WandConfig } from "./types.js";
|
|
3
4
|
export interface SessionAiContext {
|
|
@@ -9,6 +10,9 @@ export interface SessionAiContext {
|
|
|
9
10
|
opsPersona?: string;
|
|
10
11
|
/** CLI 降级链(按顺序)。为空时只用 provider/model 这一次调用。 */
|
|
11
12
|
cliCandidates?: AiCliCandidate[];
|
|
13
|
+
/** 系统应用必须经员工 CLI 渠道;候选为空时禁止退回当前会话/默认 provider。 */
|
|
14
|
+
employeeChannelOnly?: boolean;
|
|
15
|
+
modelGroups?: ModelGroup[];
|
|
12
16
|
}
|
|
13
17
|
/**
|
|
14
18
|
* Resolve the provider from every representation used by current and legacy
|
|
@@ -17,6 +21,6 @@ export interface SessionAiContext {
|
|
|
17
21
|
*/
|
|
18
22
|
export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command">): SessionProvider;
|
|
19
23
|
/** Build the provider-specific settings used by session-adjacent AI actions. */
|
|
20
|
-
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
|
|
24
|
+
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultThinkingEffort" | "inheritEnv" | "modelGroups">): SessionAiContext;
|
|
21
25
|
/** Build the source order for Wand-owned AI features such as titles. */
|
|
22
26
|
export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0], config: Parameters<typeof resolveSessionAiContext>[1] & Pick<WandConfig, "systemAiCli" | "systemAiModel">, systemEmployee?: SiliconEmployee | null): SessionAiContext;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDefaultModelForProvider } from "./config.js";
|
|
2
|
+
import { resolveModelGroupModels } from "./model-groups.js";
|
|
2
3
|
import { providerCliInstalled, isSessionProvider } from "./session-provider.js";
|
|
3
4
|
import { isSystemSiliconEmployee, systemEmployeeCliCandidates } from "./system-employee.js";
|
|
4
5
|
import { inferProviderFromCommand, inferProviderFromRunner } from "./session-provider.js";
|
|
@@ -30,6 +31,7 @@ export function resolveSessionAiContext(snapshot, config) {
|
|
|
30
31
|
model: sessionModel ?? defaultModel,
|
|
31
32
|
thinkingEffort: snapshot.thinkingEffort ?? config.defaultThinkingEffort,
|
|
32
33
|
inheritEnv: config.inheritEnv,
|
|
34
|
+
...(config.modelGroups?.length ? { modelGroups: config.modelGroups } : {}),
|
|
33
35
|
};
|
|
34
36
|
}
|
|
35
37
|
/** Build the source order for Wand-owned AI features such as titles. */
|
|
@@ -37,13 +39,14 @@ export function resolveSystemAiContext(snapshot, config, systemEmployee) {
|
|
|
37
39
|
const sessionContext = resolveSessionAiContext(snapshot, config);
|
|
38
40
|
// 候选里的「跟随默认模型」在这里就换成具体的 Wand 默认模型:降级到下一个 provider
|
|
39
41
|
// 时不能拿上一个 provider 的模型,也不能把决定权交给 CLI 自己的默认值。
|
|
40
|
-
const chain = systemEmployeeCliCandidates(systemEmployee).
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const chain = systemEmployeeCliCandidates(systemEmployee).flatMap((candidate) => resolveModelGroupModels(config.modelGroups, candidate.provider, candidate.model ?? normalizeModel(getDefaultModelForProvider(config, candidate.provider)), {
|
|
43
|
+
preferDefault: !candidate.model || candidate.model === "default",
|
|
44
|
+
})
|
|
45
|
+
.map((model) => ({ ...candidate, model: normalizeModel(model) })));
|
|
44
46
|
const owned = {
|
|
45
47
|
...sessionContext,
|
|
46
48
|
...(chain.length ? { cliCandidates: chain } : {}),
|
|
49
|
+
...(systemEmployee ? { employeeChannelOnly: true } : {}),
|
|
47
50
|
...(isSystemSiliconEmployee(systemEmployee) ? { opsPersona: systemEmployee.prompt } : {}),
|
|
48
51
|
};
|
|
49
52
|
// 内置员工可用时,Wand 自有调用一律按它的候选链:provider/model 取首个已安装的
|
|
@@ -56,6 +59,8 @@ export function resolveSystemAiContext(snapshot, config, systemEmployee) {
|
|
|
56
59
|
}
|
|
57
60
|
if (chain.length)
|
|
58
61
|
return owned;
|
|
62
|
+
if (systemEmployee)
|
|
63
|
+
return owned;
|
|
59
64
|
// Older installations without a system AI CLI preference still follow the
|
|
60
65
|
// current session. A chosen CLI must never inherit another provider's model.
|
|
61
66
|
if (!isSessionProvider(config.systemAiCli))
|
|
@@ -3,6 +3,8 @@ import type { StructuredSessionManager } from "./structured-session-manager.js";
|
|
|
3
3
|
import type { WandStorage } from "./storage.js";
|
|
4
4
|
import type { ExecutionMode, SessionSnapshot } from "./types.js";
|
|
5
5
|
export type SessionOwner = "structured" | "pty" | "storage";
|
|
6
|
+
export type ProviderHistoryDeleter = Pick<ProcessManager, "deleteClaudeHistoryFiles" | "deleteCodexHistoryFiles" | "deleteOpenCodeHistorySessions" | "deleteQoderHistoryFiles" | "deletePiHistoryFiles" | "deleteGrokHistoryFiles" | "deleteGeminiHistoryFiles">;
|
|
7
|
+
export declare function deleteProviderNativeHistory(deleter: ProviderHistoryDeleter, provider: string, providerSessionId: string, cwd: string): void;
|
|
6
8
|
type HiddenSessionStore = Pick<WandStorage, "getConfigValue" | "setConfigValue">;
|
|
7
9
|
/** 读取 `hidden_claude_session_ids`;坏数据 / 空值按空集合处理,不抛错。 */
|
|
8
10
|
export declare function readHiddenSessionIds(storage: Pick<HiddenSessionStore, "getConfigValue">): Set<string>;
|
|
@@ -35,6 +37,8 @@ export declare class SessionRegistry {
|
|
|
35
37
|
* 归档 / 取消归档一个会话:只写标记,不杀进程、不删历史;真正的清理留给保留期扫描。
|
|
36
38
|
* 会话可能只存在于 storage(重启后未加载),因此 storage-only 也要能改。
|
|
37
39
|
*/
|
|
40
|
+
/** 停掉空闲 PTY 壳。保留期只对「进程还在、但没有正在处理的回合」调用。 */
|
|
41
|
+
stop(id: string): SessionSnapshot | null;
|
|
38
42
|
setArchived(id: string, archived: boolean): SessionSnapshot | null;
|
|
39
43
|
updateWorktreeState(id: string, status: SessionSnapshot["worktreeMergeStatus"], info: SessionSnapshot["worktreeMergeInfo"]): SessionSnapshot | null;
|
|
40
44
|
delete(id: string): SessionSnapshot | null;
|
package/dist/session-registry.js
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { cleanupWorktreeSync } from "./git-worktree.js";
|
|
2
2
|
import { inferProviderFromCommand } from "./session-provider.js";
|
|
3
|
+
export function deleteProviderNativeHistory(deleter, provider, providerSessionId, cwd) {
|
|
4
|
+
const id = providerSessionId.trim();
|
|
5
|
+
if (!id)
|
|
6
|
+
return;
|
|
7
|
+
switch (provider) {
|
|
8
|
+
case "claude":
|
|
9
|
+
deleter.deleteClaudeHistoryFiles([{ claudeSessionId: id, cwd }]);
|
|
10
|
+
break;
|
|
11
|
+
case "codex":
|
|
12
|
+
deleter.deleteCodexHistoryFiles([id]);
|
|
13
|
+
break;
|
|
14
|
+
case "opencode":
|
|
15
|
+
deleter.deleteOpenCodeHistorySessions([id]);
|
|
16
|
+
break;
|
|
17
|
+
case "qoder":
|
|
18
|
+
deleter.deleteQoderHistoryFiles([id]);
|
|
19
|
+
break;
|
|
20
|
+
case "pi":
|
|
21
|
+
deleter.deletePiHistoryFiles([id]);
|
|
22
|
+
break;
|
|
23
|
+
case "grok":
|
|
24
|
+
deleter.deleteGrokHistoryFiles([id]);
|
|
25
|
+
break;
|
|
26
|
+
case "gemini":
|
|
27
|
+
deleter.deleteGeminiHistoryFiles([id]);
|
|
28
|
+
break;
|
|
29
|
+
default:
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
3
33
|
/** 读取 `hidden_claude_session_ids`;坏数据 / 空值按空集合处理,不抛错。 */
|
|
4
34
|
export function readHiddenSessionIds(storage) {
|
|
5
35
|
const raw = storage.getConfigValue("hidden_claude_session_ids");
|
|
@@ -143,6 +173,12 @@ export class SessionRegistry {
|
|
|
143
173
|
* 归档 / 取消归档一个会话:只写标记,不杀进程、不删历史;真正的清理留给保留期扫描。
|
|
144
174
|
* 会话可能只存在于 storage(重启后未加载),因此 storage-only 也要能改。
|
|
145
175
|
*/
|
|
176
|
+
/** 停掉空闲 PTY 壳。保留期只对「进程还在、但没有正在处理的回合」调用。 */
|
|
177
|
+
stop(id) {
|
|
178
|
+
if (this.ownerOf(id) !== "pty")
|
|
179
|
+
return this.get(id);
|
|
180
|
+
return this.processes.stop(id);
|
|
181
|
+
}
|
|
146
182
|
setArchived(id, archived) {
|
|
147
183
|
const owner = this.ownerOf(id);
|
|
148
184
|
if (owner === "structured")
|
|
@@ -184,12 +220,18 @@ export class SessionRegistry {
|
|
|
184
220
|
catch { /* never block deletion */ }
|
|
185
221
|
}
|
|
186
222
|
const owner = this.ownerOf(id);
|
|
187
|
-
if (owner === "structured")
|
|
223
|
+
if (owner === "structured") {
|
|
188
224
|
this.structured.delete(id);
|
|
189
|
-
|
|
225
|
+
}
|
|
226
|
+
else if (owner === "pty") {
|
|
227
|
+
this.processes.delete(id);
|
|
228
|
+
}
|
|
229
|
+
else if (snapshot.sessionKind === "structured") {
|
|
230
|
+
this.structured.delete(id);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
190
233
|
this.processes.delete(id);
|
|
191
|
-
|
|
192
|
-
this.storage.deleteSession(id);
|
|
234
|
+
}
|
|
193
235
|
return snapshot;
|
|
194
236
|
}
|
|
195
237
|
deleteWithProviderHistory(id) {
|
|
@@ -201,15 +243,7 @@ export class SessionRegistry {
|
|
|
201
243
|
?? snapshot.structuredState?.provider
|
|
202
244
|
?? inferProviderFromCommand(snapshot.command)
|
|
203
245
|
?? "claude";
|
|
204
|
-
|
|
205
|
-
this.processes.deleteClaudeHistoryFiles([{ claudeSessionId: providerSessionId, cwd: snapshot.cwd }]);
|
|
206
|
-
}
|
|
207
|
-
else if (provider === "codex") {
|
|
208
|
-
this.processes.deleteCodexHistoryFiles([providerSessionId]);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
return snapshot;
|
|
212
|
-
}
|
|
246
|
+
deleteProviderNativeHistory(this.processes, provider, providerSessionId, snapshot.cwd);
|
|
213
247
|
addHiddenSessionIds(this.storage, [providerSessionId]);
|
|
214
248
|
return snapshot;
|
|
215
249
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { enrichStructuredMessages, WAND_PROTOCOL_VERSION } from "./structured-client-protocol.js";
|
|
2
|
+
import { normalizeTurnAnchors } from "./structured-provider-common.js";
|
|
2
3
|
import { collectSessionTopicBlocklist, shouldAcceptGeneratedSessionTitle, summarizeSessionTitleFromInput, } from "./session-topic.js";
|
|
3
4
|
export const SESSION_TRANSPORT_OUTPUT_LIMIT = 200_000;
|
|
4
5
|
function cleanDisplayTitle(value) {
|
|
@@ -35,6 +36,10 @@ function sessionBase(snapshot) {
|
|
|
35
36
|
providerCliActive: snapshot.providerCliActive,
|
|
36
37
|
providerCliExitCode: snapshot.providerCliExitCode,
|
|
37
38
|
ptyBusy: snapshot.ptyBusy === true,
|
|
39
|
+
// 与 ptyBusy 同生命周期的本轮锚点(运行时事实,存储里没有对应列)。和 ptyBusy 一起
|
|
40
|
+
// 读成 null,客户端才不会在一条已经 idle 的会话上算出「已运行 N 分钟」。
|
|
41
|
+
ptyTurnStartedAt: snapshot.ptyBusy === true ? snapshot.ptyTurnStartedAt ?? null : null,
|
|
42
|
+
ptyLastActivityAt: snapshot.ptyBusy === true ? snapshot.ptyLastActivityAt ?? null : null,
|
|
38
43
|
runner: snapshot.runner,
|
|
39
44
|
command: snapshot.command,
|
|
40
45
|
cwd: snapshot.cwd,
|
|
@@ -60,7 +65,8 @@ function sessionBase(snapshot) {
|
|
|
60
65
|
claudeSessionId: snapshot.claudeSessionId,
|
|
61
66
|
providerSessionId: snapshot.claudeSessionId,
|
|
62
67
|
queuedMessages: snapshot.queuedMessages,
|
|
63
|
-
|
|
68
|
+
// inFlight 之外的回合锚点一律读成 null(兜底历史库残值与重启降级窗口)。
|
|
69
|
+
structuredState: normalizeTurnAnchors(snapshot.structuredState),
|
|
64
70
|
resumedFromSessionId: snapshot.resumedFromSessionId,
|
|
65
71
|
autoRecovered: snapshot.autoRecovered,
|
|
66
72
|
autoApprovePermissions: snapshot.autoApprovePermissions,
|
|
@@ -74,6 +80,7 @@ function sessionBase(snapshot) {
|
|
|
74
80
|
workspaceTaskId: snapshot.workspaceTaskId,
|
|
75
81
|
selectedModel: snapshot.selectedModel,
|
|
76
82
|
thinkingEffort: snapshot.thinkingEffort,
|
|
83
|
+
piSettings: snapshot.piSettings,
|
|
77
84
|
ptyCols: snapshot.ptyCols,
|
|
78
85
|
ptyRows: snapshot.ptyRows,
|
|
79
86
|
};
|