@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
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { OpenRouterFreeModelsService } from "./openrouter-free-models.js";
|
|
1
2
|
import type { Express, Request, RequestHandler } from "express";
|
|
2
3
|
import { type BuildInfo } from "./build-info.js";
|
|
3
4
|
import { type ModelCatalogService } from "./models.js";
|
|
4
5
|
import { type RuntimeConfigState } from "./runtime-config.js";
|
|
5
6
|
import type { WandStorage } from "./storage.js";
|
|
7
|
+
import type { RetentionResult } from "./retention.js";
|
|
6
8
|
import type { WandConfig } from "./types.js";
|
|
7
9
|
import type { GithubConnectorStatus } from "./github-connector.js";
|
|
8
10
|
interface SettingsDistributionPayload {
|
|
@@ -33,10 +35,13 @@ export interface ServerSettingsRoutesDependencies {
|
|
|
33
35
|
getDistributionSettings(): Promise<SettingsDistributionPayload>;
|
|
34
36
|
getGithubConnector(): GithubConnectorStatus;
|
|
35
37
|
modelCatalog: ModelCatalogService;
|
|
38
|
+
openRouter: OpenRouterFreeModelsService;
|
|
36
39
|
resolveAppConnectCode(req: Request): {
|
|
37
40
|
code: string;
|
|
38
41
|
url: string;
|
|
39
42
|
};
|
|
43
|
+
/** 任务保留设置已写入当前进程后调用。失败不得回滚已保存的偏好。 */
|
|
44
|
+
sweepTaskRetention?: () => RetentionResult;
|
|
40
45
|
}
|
|
41
46
|
export declare function registerSettingsRoutes(app: Express, deps: ServerSettingsRoutesDependencies): void;
|
|
42
47
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeModelGroups } from "./model-groups.js";
|
|
1
2
|
import { existsSync, writeFileSync } from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { buildInfoPayload } from "./build-info.js";
|
|
@@ -62,6 +63,7 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
62
63
|
restartRequired: runtimeConfig.hasPendingRestart(),
|
|
63
64
|
hasCert: existsSync(path.join(configDir, "server.key")) && existsSync(path.join(configDir, "server.crt")),
|
|
64
65
|
githubConnector: deps.getGithubConnector(),
|
|
66
|
+
openRouter: deps.openRouter.status(),
|
|
65
67
|
updateAvailable: cachedUpdate?.updateAvailable ?? false,
|
|
66
68
|
latestVersion: cachedUpdate?.latest ?? null,
|
|
67
69
|
updateChannel: deps.getUpdateChannel(),
|
|
@@ -125,6 +127,11 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
125
127
|
};
|
|
126
128
|
let touchedDeployField = false;
|
|
127
129
|
try {
|
|
130
|
+
if (body.modelGroups !== undefined && body.expectedModelGroups !== undefined
|
|
131
|
+
&& JSON.stringify(normalizeModelGroups(body.expectedModelGroups)) !== JSON.stringify(config.modelGroups ?? [])) {
|
|
132
|
+
res.status(409).json({ error: "模型分组已在其他设备修改,请先刷新;当前草稿已保留。" });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
128
135
|
for (const field of DEPLOYMENT_CONFIG_KEYS) {
|
|
129
136
|
if (!(field in body) || body[field] === undefined)
|
|
130
137
|
continue;
|
|
@@ -189,12 +196,27 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
189
196
|
});
|
|
190
197
|
}
|
|
191
198
|
runtimeConfig.commit(candidateConfig, stagedPreferenceFields);
|
|
199
|
+
if (stagedPreferenceFields.has("modelGroups"))
|
|
200
|
+
deps.modelCatalog.publishModelGroups();
|
|
201
|
+
let retention;
|
|
202
|
+
let retentionError;
|
|
203
|
+
if (stagedPreferenceFields.has("taskRetention") && deps.sweepTaskRetention) {
|
|
204
|
+
try {
|
|
205
|
+
retention = deps.sweepTaskRetention();
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
retentionError = error instanceof Error ? error.message : String(error);
|
|
209
|
+
console.error(`[retention] save sweep failed: ${retentionError}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
192
212
|
res.json({
|
|
193
213
|
ok: true,
|
|
194
214
|
config: publicConfig(candidateConfig),
|
|
195
215
|
desiredConfig: publicConfig(candidateConfig),
|
|
196
216
|
activeConfig: publicConfig(config),
|
|
197
217
|
restartRequired: runtimeConfig.hasPendingRestart(),
|
|
218
|
+
...(retention ? { retention } : {}),
|
|
219
|
+
...(retentionError ? { retentionError: "设置已保存,但立即扫描失败,将在下次定时扫描时重试。" } : {}),
|
|
198
220
|
});
|
|
199
221
|
}
|
|
200
222
|
catch (error) {
|
|
@@ -207,6 +229,30 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
207
229
|
sendRouteError(res, error, "保存配置失败。", 500);
|
|
208
230
|
}
|
|
209
231
|
}));
|
|
232
|
+
app.get("/api/settings/openrouter", requireAdmin, (_req, res) => {
|
|
233
|
+
res.set("Cache-Control", "no-store");
|
|
234
|
+
res.json(deps.openRouter.status());
|
|
235
|
+
});
|
|
236
|
+
app.post("/api/settings/openrouter", requireAdmin, asyncRoute(async (req, res) => {
|
|
237
|
+
try {
|
|
238
|
+
res.json(await deps.openRouter.saveKey(req.body?.apiKey));
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
res.status(error instanceof TypeError ? 400 : 500).json({
|
|
242
|
+
error: error instanceof TypeError ? "请填写有效的 OpenRouter Key。" : "保存 OpenRouter 配置失败。",
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}));
|
|
246
|
+
app.post("/api/settings/openrouter/refresh", requireAdmin, asyncRoute(async (_req, res) => {
|
|
247
|
+
if (!deps.openRouter.status().configured) {
|
|
248
|
+
res.status(400).json({ error: "请先保存 OpenRouter Key。" });
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
res.json(await deps.openRouter.refresh());
|
|
252
|
+
}));
|
|
253
|
+
app.delete("/api/settings/openrouter", requireAdmin, (_req, res) => {
|
|
254
|
+
res.json(deps.openRouter.clearKey());
|
|
255
|
+
});
|
|
210
256
|
app.get("/api/models", (_req, res) => {
|
|
211
257
|
// Every client reads the server's persisted snapshot. Avoid browser/proxy
|
|
212
258
|
// cache races after an administrator or scheduled server refresh.
|
|
@@ -2,6 +2,8 @@ import { dispatchAgentForTask, resolveTaskDispatchTarget } from "./agent-dispatc
|
|
|
2
2
|
import { asyncRoute } from "./express-async.js";
|
|
3
3
|
import { getErrorMessage } from "./error-utils.js";
|
|
4
4
|
import { bodyObject, sendRouteError, text } from "./server-request.js";
|
|
5
|
+
import { cleanupWorktreeSync } from "./git-worktree.js";
|
|
6
|
+
import { isRetentionBusy } from "./retention.js";
|
|
5
7
|
import { parseWandTaskAgent } from "./storage.js";
|
|
6
8
|
import { defaultMilestoneIdForWrite, resolvedMilestoneFields, scopedMilestoneId } from "./milestone-scope.js";
|
|
7
9
|
import { generateWandTaskTitle, provisionalTaskTitleFromDescription, TASK_TITLE_MAX_LENGTH } from "./task-title.js";
|
|
@@ -10,7 +12,7 @@ import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
|
10
12
|
import { memberAgents } from "./ai-team-types.js";
|
|
11
13
|
import { selectEmployeeCandidate } from "./silicon-employee-dispatch.js";
|
|
12
14
|
import { defaultRoleForCli } from "./default-employee.js";
|
|
13
|
-
import { DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_AGENT_MODE, DEFAULT_WAND_TASK_PRIORITY, isWandTaskAgentKind, isWandTaskAgentMode, normalizeWandTaskAgentMode, WAND_MILESTONE_NAME_MAX_LENGTH } from "./task-types.js";
|
|
15
|
+
import { DEFAULT_WAND_TASK_AGENT_KIND, DEFAULT_WAND_TASK_AGENT_MODE, DEFAULT_WAND_TASK_PRIORITY, isWandTaskAgentEngine, isWandTaskAgentKind, isWandTaskAgentMode, normalizeWandTaskAgentMode, WAND_MILESTONE_NAME_MAX_LENGTH } from "./task-types.js";
|
|
14
16
|
import { isAutoNameableBoardTask, taskAutoNameSignature, taskAutoNameSourceText } from "./wand-task-sync.js";
|
|
15
17
|
import { isSessionProvider } from "./session-provider.js";
|
|
16
18
|
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
@@ -187,7 +189,14 @@ export function parseTaskAgent(value, fallbackMode = DEFAULT_WAND_TASK_AGENT_MOD
|
|
|
187
189
|
const rawKind = body.kind === undefined || body.kind === null || body.kind === "" ? fallbackKind : body.kind;
|
|
188
190
|
if (!isWandTaskAgentKind(rawKind))
|
|
189
191
|
throw new Error("会话形态无效。");
|
|
190
|
-
|
|
192
|
+
const rawEngine = body.engine === undefined || body.engine === null || body.engine === "" ? "cli" : body.engine;
|
|
193
|
+
if (!isWandTaskAgentEngine(rawEngine))
|
|
194
|
+
throw new Error("执行引擎无效。");
|
|
195
|
+
if (rawEngine === "sdk" && (provider !== "pi" || rawKind !== "structured")) {
|
|
196
|
+
throw new Error("SDK 执行引擎仅支持 Pi 结构化会话。");
|
|
197
|
+
}
|
|
198
|
+
const engine = rawEngine === "sdk" ? rawEngine : undefined;
|
|
199
|
+
return { provider, model, thinkingEffort, mode, kind: rawKind, ...(engine ? { engine } : {}) };
|
|
191
200
|
}
|
|
192
201
|
function taskDto({ storage, sessions }, task) {
|
|
193
202
|
if (!task)
|
|
@@ -580,6 +589,42 @@ export function registerTaskRoutes(app, deps) {
|
|
|
580
589
|
sendRouteError(res, error, "无法更新任务。");
|
|
581
590
|
}
|
|
582
591
|
});
|
|
592
|
+
/**
|
|
593
|
+
* 清空归档目录:硬删除所有 archived 卡片(可按项目范围)。
|
|
594
|
+
* 名下会话还在处理的卡片跳过,不静默打断执行;其余按单卡删除的同一套归属清理。
|
|
595
|
+
* 必须注册在 `/api/wand-tasks/:id` 之前,否则 "archived" 会被当成任务 id。
|
|
596
|
+
*/
|
|
597
|
+
app.delete("/api/wand-tasks/archived", (req, res) => {
|
|
598
|
+
try {
|
|
599
|
+
const workspaceId = typeof req.query.workspaceId === "string" && req.query.workspaceId
|
|
600
|
+
? req.query.workspaceId
|
|
601
|
+
: undefined;
|
|
602
|
+
const archived = storage.listWandTasks(workspaceId).filter((task) => task.status === "archived");
|
|
603
|
+
const busy = new Set(sessions?.listSlim().filter(isRetentionBusy).map((session) => session.id) ?? []);
|
|
604
|
+
const deletable = [];
|
|
605
|
+
let skipped = 0;
|
|
606
|
+
for (const task of archived) {
|
|
607
|
+
const bound = storage.listWandTaskSessionIds(task.id);
|
|
608
|
+
if (bound.some((sessionId) => busy.has(sessionId))) {
|
|
609
|
+
skipped += 1;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
const container = task.workspaceTaskId ? storage.getWorkspaceTask(task.workspaceTaskId) : null;
|
|
613
|
+
if (container?.worktree) {
|
|
614
|
+
try {
|
|
615
|
+
cleanupWorktreeSync(container.worktree);
|
|
616
|
+
}
|
|
617
|
+
catch { /* worktree 可能已被手动删除 */ }
|
|
618
|
+
}
|
|
619
|
+
deletable.push(task.id);
|
|
620
|
+
}
|
|
621
|
+
const deleted = storage.deleteWandTasks(deletable);
|
|
622
|
+
res.json({ ok: true, deleted: deleted.length, skipped });
|
|
623
|
+
}
|
|
624
|
+
catch (error) {
|
|
625
|
+
sendRouteError(res, error, "无法清空归档任务。");
|
|
626
|
+
}
|
|
627
|
+
});
|
|
583
628
|
app.delete("/api/wand-tasks/:id", (req, res) => {
|
|
584
629
|
const archived = storage.updateWandTask(req.params.id, { status: "archived" });
|
|
585
630
|
if (!archived) {
|
|
@@ -694,12 +739,12 @@ export function registerTaskRoutes(app, deps) {
|
|
|
694
739
|
if (subject?.type === "employee" && (!employee || employee.archivedAt)) {
|
|
695
740
|
throw new Error("硅基员工不存在或已归档。");
|
|
696
741
|
}
|
|
697
|
-
const selected = employee ? selectEmployeeCandidate(employee) : null;
|
|
742
|
+
const selected = employee ? selectEmployeeCandidate(employee, undefined, { skipSdk: true }) : null;
|
|
698
743
|
const cliDefault = subject?.type === "cli"
|
|
699
744
|
? parseTaskAgent({ ...cliAgentForSubject(subject, task.agent), ...(body.kind === "pty" ? { kind: "pty" } : {}) })
|
|
700
745
|
: null;
|
|
701
746
|
const fallbackCandidate = !subject && body.agent === undefined && !task.agent
|
|
702
|
-
&& !body.provider ? selectEmployeeCandidate(defaultRoleForCli(storage, config.defaultProvider)).agent : null;
|
|
747
|
+
&& !body.provider ? selectEmployeeCandidate(defaultRoleForCli(storage, config.defaultProvider), undefined, { skipSdk: true }).agent : null;
|
|
703
748
|
const fallbackAgent = fallbackCandidate && body.kind === "pty"
|
|
704
749
|
? { ...fallbackCandidate, kind: "pty" } : fallbackCandidate;
|
|
705
750
|
const agent = selected?.agent ?? (body.agent === undefined
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Express } from "express";
|
|
2
|
+
import type { DecisionService } from "./decision-service.js";
|
|
3
|
+
import type { WandStorage } from "./storage.js";
|
|
4
|
+
/**
|
|
5
|
+
* 无指派派工:不预设员工,由本地决策模型按任务描述给出建议名单,确认后才开工。
|
|
6
|
+
* 「建议」和「开工」是两个独立请求,中间不需要服务端保存计划:
|
|
7
|
+
* 开工时重新校验名单(员工存在、未归档、非系统运维、含负责人)。
|
|
8
|
+
*/
|
|
9
|
+
export declare function registerTeamDispatchRoutes(app: Express, deps: {
|
|
10
|
+
storage: WandStorage;
|
|
11
|
+
runner: {
|
|
12
|
+
start(input: {
|
|
13
|
+
teamId: string;
|
|
14
|
+
taskId: string;
|
|
15
|
+
note?: string;
|
|
16
|
+
}): Promise<unknown>;
|
|
17
|
+
};
|
|
18
|
+
decisions: Pick<DecisionService, "evaluate" | "status">;
|
|
19
|
+
}): void;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { DecisionError } from "./decision-types.js";
|
|
2
|
+
import { asyncRoute } from "./express-async.js";
|
|
3
|
+
import { getErrorMessage } from "./error-utils.js";
|
|
4
|
+
import { bodyObject, sendRouteError, text } from "./server-request.js";
|
|
5
|
+
import { assertDispatchDecisionReady, planTeamDispatch, startTeamDispatch, } from "./team-dispatch.js";
|
|
6
|
+
function sendDispatchError(res, error) {
|
|
7
|
+
const message = getErrorMessage(error, "派工失败。");
|
|
8
|
+
// 这里所有错误都是“请求体/名单不合法”或决策服务不可用;不当成资源路径缺失(404),
|
|
9
|
+
// 也不把“员工不存在”(名单里的 id)混同于“项目不存在”。
|
|
10
|
+
if (error instanceof DecisionError) {
|
|
11
|
+
res.status(error.status).json({ error: message, code: error.code });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
sendRouteError(res, error, "派工失败。", 400);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 无指派派工:不预设员工,由本地决策模型按任务描述给出建议名单,确认后才开工。
|
|
18
|
+
* 「建议」和「开工」是两个独立请求,中间不需要服务端保存计划:
|
|
19
|
+
* 开工时重新校验名单(员工存在、未归档、非系统运维、含负责人)。
|
|
20
|
+
*/
|
|
21
|
+
export function registerTeamDispatchRoutes(app, deps) {
|
|
22
|
+
app.post("/api/team-dispatch/plan", asyncRoute(async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const body = bodyObject(req.body ?? {});
|
|
25
|
+
const note = text(body.note);
|
|
26
|
+
const maxMembers = typeof body.maxMembers === "number" ? body.maxMembers : undefined;
|
|
27
|
+
const threshold = typeof body.threshold === "number" ? body.threshold : undefined;
|
|
28
|
+
assertDispatchDecisionReady(deps.decisions);
|
|
29
|
+
const plan = await planTeamDispatch({
|
|
30
|
+
storage: deps.storage,
|
|
31
|
+
note,
|
|
32
|
+
...(maxMembers === undefined ? {} : { maxMembers }),
|
|
33
|
+
...(threshold === undefined ? {} : { threshold }),
|
|
34
|
+
evaluate: (value, caller, signal) => deps.decisions.evaluate(value, caller, signal),
|
|
35
|
+
});
|
|
36
|
+
res.json(plan);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
sendDispatchError(res, error);
|
|
40
|
+
}
|
|
41
|
+
}));
|
|
42
|
+
app.post("/api/team-dispatch/start", asyncRoute(async (req, res) => {
|
|
43
|
+
try {
|
|
44
|
+
const body = bodyObject(req.body ?? {});
|
|
45
|
+
const workspaceId = text(body.workspaceId);
|
|
46
|
+
if (!workspaceId)
|
|
47
|
+
throw new Error("请选择工作项目。");
|
|
48
|
+
const rawMembers = Array.isArray(body.members) ? body.members : [];
|
|
49
|
+
const members = rawMembers.map((raw) => {
|
|
50
|
+
const record = bodyObject(raw);
|
|
51
|
+
return {
|
|
52
|
+
employeeId: text(record.employeeId),
|
|
53
|
+
...(record.isLeader === true ? { isLeader: true } : {}),
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
const name = text(body.name);
|
|
57
|
+
const result = await startTeamDispatch({
|
|
58
|
+
storage: deps.storage,
|
|
59
|
+
runner: deps.runner,
|
|
60
|
+
workspaceId,
|
|
61
|
+
note: text(body.note),
|
|
62
|
+
members,
|
|
63
|
+
...(name ? { name } : {}),
|
|
64
|
+
});
|
|
65
|
+
res.status(202).json({ ...result.run, teamId: result.teamId, taskId: result.taskId });
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
sendDispatchError(res, error);
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
@@ -39,7 +39,7 @@ export interface PublicUpdateRoutesDependencies {
|
|
|
39
39
|
resolveLatestIpa(): Promise<ResolvedUpdateAsset | null>;
|
|
40
40
|
resolveIosDownload(): Promise<DownloadAsset | null>;
|
|
41
41
|
}
|
|
42
|
-
export declare function registerPublicUpdateRoutes(app: Express, deps: PublicUpdateRoutesDependencies): void;
|
|
42
|
+
export declare function registerPublicUpdateRoutes(app: Express, deps: PublicUpdateRoutesDependencies, publicOrigin?: string): void;
|
|
43
43
|
export declare class ServerUpdateState {
|
|
44
44
|
providerCliUpdateCache: {
|
|
45
45
|
items: ProviderCliUpdateStatus[];
|
|
@@ -63,7 +63,7 @@ function requireCurrentVersion(req, res) {
|
|
|
63
63
|
}
|
|
64
64
|
return currentVersion;
|
|
65
65
|
}
|
|
66
|
-
export function registerPublicUpdateRoutes(app, deps) {
|
|
66
|
+
export function registerPublicUpdateRoutes(app, deps, publicOrigin) {
|
|
67
67
|
app.get("/api/android-apk-update", asyncRoute(async (req, res) => {
|
|
68
68
|
const currentVersion = requireCurrentVersion(req, res);
|
|
69
69
|
if (currentVersion === null)
|
|
@@ -220,7 +220,7 @@ export function registerPublicUpdateRoutes(app, deps) {
|
|
|
220
220
|
const currentVersion = requireCurrentVersion(req, res);
|
|
221
221
|
if (currentVersion === null)
|
|
222
222
|
return;
|
|
223
|
-
const payload = await resolveIosOtaPayload(req, deps);
|
|
223
|
+
const payload = await resolveIosOtaPayload(req, deps, publicOrigin);
|
|
224
224
|
if (!payload) {
|
|
225
225
|
res.json({
|
|
226
226
|
updateAvailable: false,
|
|
@@ -259,7 +259,7 @@ export function registerPublicUpdateRoutes(app, deps) {
|
|
|
259
259
|
});
|
|
260
260
|
}));
|
|
261
261
|
app.get("/ios/manifest.plist", asyncRoute(async (req, res) => {
|
|
262
|
-
const payload = await resolveIosOtaPayload(req, deps);
|
|
262
|
+
const payload = await resolveIosOtaPayload(req, deps, publicOrigin);
|
|
263
263
|
if (!payload) {
|
|
264
264
|
res.status(404).type("text/plain").send("当前没有可安装的 IPA 文件。");
|
|
265
265
|
return;
|
|
@@ -269,7 +269,7 @@ export function registerPublicUpdateRoutes(app, deps) {
|
|
|
269
269
|
res.send(payload.manifest);
|
|
270
270
|
}));
|
|
271
271
|
app.get("/ios/install", asyncRoute(async (req, res) => {
|
|
272
|
-
const payload = await resolveIosOtaPayload(req, deps);
|
|
272
|
+
const payload = await resolveIosOtaPayload(req, deps, publicOrigin);
|
|
273
273
|
if (!payload) {
|
|
274
274
|
res.status(404).type("text/plain").send("当前没有可安装的 IPA 文件。");
|
|
275
275
|
return;
|
|
@@ -301,12 +301,17 @@ export function registerPublicUpdateRoutes(app, deps) {
|
|
|
301
301
|
});
|
|
302
302
|
}));
|
|
303
303
|
}
|
|
304
|
-
async function resolveIosOtaPayload(req, deps) {
|
|
304
|
+
async function resolveIosOtaPayload(req, deps, publicOrigin) {
|
|
305
305
|
const latest = await deps.resolveLatestIpa();
|
|
306
306
|
const local = await deps.resolveIosDownload();
|
|
307
307
|
if (!latest && !local)
|
|
308
308
|
return null;
|
|
309
|
+
// Explicit ?origin= wins; then the deployment's configured publicOrigin
|
|
310
|
+
// (same precedence as the connection-code logic in server-app-connect);
|
|
311
|
+
// request-derived headers are the last resort — L4 proxies may not forward
|
|
312
|
+
// x-forwarded-proto, which would otherwise emit http:// manifest URLs.
|
|
309
313
|
const origin = normalizePublicOrigin(typeof req.query.origin === "string" ? req.query.origin : undefined)
|
|
314
|
+
?? normalizePublicOrigin(publicOrigin)
|
|
310
315
|
?? publicOriginFromRequest(req)
|
|
311
316
|
?? "";
|
|
312
317
|
const source = local ? "local" : (latest?.source ?? "github");
|
|
@@ -67,8 +67,11 @@ function teamMarkersFor(markers, sessionId) {
|
|
|
67
67
|
const step = markers.step.get(sessionId);
|
|
68
68
|
return step ? { step } : {};
|
|
69
69
|
}
|
|
70
|
-
function cheapTasksRevision(storage, registry) {
|
|
70
|
+
function cheapTasksRevision(storage, registry, includeArchived = false) {
|
|
71
71
|
const fingerprint = [
|
|
72
|
+
// Invalidate pre-filter client caches even if no stored row has changed.
|
|
73
|
+
"visible-sessions-v2",
|
|
74
|
+
includeArchived,
|
|
72
75
|
storage.tasksAggregateFingerprint(),
|
|
73
76
|
// 团队标记(群聊入口、成员步骤与运行状态)也在列表里。
|
|
74
77
|
storage.teamSessionFingerprint(),
|
|
@@ -229,12 +232,11 @@ function createTaskForWorkspace(storage, workspace, body) {
|
|
|
229
232
|
isolated: worktree !== null,
|
|
230
233
|
};
|
|
231
234
|
}
|
|
232
|
-
function workspaceWithCounts(storage, workspace, sessionCounts) {
|
|
235
|
+
function workspaceWithCounts(storage, workspace, sessionCounts, includeArchived = false) {
|
|
233
236
|
const worktreeCount = storage.listWorkspaceTasks(workspace.id)
|
|
234
|
-
.filter((task) => task.worktree !== null)
|
|
237
|
+
.filter((task) => (includeArchived || !task.archived) && task.worktree !== null)
|
|
235
238
|
.length;
|
|
236
|
-
const sessionCount = sessionCounts
|
|
237
|
-
?? storage.listSessionsByWorkspace(workspace.id).length;
|
|
239
|
+
const sessionCount = (sessionCounts ?? storage.countSessionsByWorkspace({ includeArchived })).get(workspace.id) ?? 0;
|
|
238
240
|
return { ...workspace, worktreeCount, sessionCount };
|
|
239
241
|
}
|
|
240
242
|
// ── Layout validation / sanitization(前端 PUT 与测试共用)──
|
|
@@ -344,12 +346,13 @@ export function sanitizeTaskLayout(value) {
|
|
|
344
346
|
*/
|
|
345
347
|
export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {}) {
|
|
346
348
|
// 列出所有项目(按创建时间降序:新建在前,打开不会改位置)
|
|
347
|
-
app.get("/api/workspaces", (
|
|
349
|
+
app.get("/api/workspaces", (req, res) => {
|
|
348
350
|
backfillSessionWorkspaces(storage);
|
|
349
|
-
const
|
|
351
|
+
const includeArchived = req.query.includeArchived === "1";
|
|
352
|
+
const sessionCounts = storage.countSessionsByWorkspace({ includeArchived });
|
|
350
353
|
res.json(storage.listWorkspaces()
|
|
351
354
|
.filter((workspace) => !isGlobalWorkspace(workspace))
|
|
352
|
-
.map((workspace) => workspaceWithCounts(storage, workspace, sessionCounts)));
|
|
355
|
+
.map((workspace) => workspaceWithCounts(storage, workspace, sessionCounts, includeArchived)));
|
|
353
356
|
});
|
|
354
357
|
// 新建项目:名称 + 目录 + 默认 IDE,不启动会话
|
|
355
358
|
app.post("/api/workspaces", asyncRoute(async (req, res) => {
|
|
@@ -371,8 +374,8 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
371
374
|
const workspace = storage.createWorkspace({ name, cwd, defaultProvider });
|
|
372
375
|
// 项目名就是工作区显示名:目录自定义名一起写,保证各端显示一致。
|
|
373
376
|
storage.setSessionDirectoryName(cwd, name);
|
|
374
|
-
|
|
375
|
-
res.status(201).json(
|
|
377
|
+
attachUnboundSessionsToWorkspace(storage, workspace);
|
|
378
|
+
res.status(201).json(workspaceWithCounts(storage, workspace));
|
|
376
379
|
}));
|
|
377
380
|
// 项目详情:meta + 会话 + 布局;访问即更新 lastOpenedAt
|
|
378
381
|
app.get("/api/workspaces/:id", (req, res) => {
|
|
@@ -382,13 +385,19 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
382
385
|
return;
|
|
383
386
|
}
|
|
384
387
|
storage.touchWorkspace(workspace.id);
|
|
388
|
+
const includeArchived = req.query.includeArchived === "1";
|
|
389
|
+
const archivedTaskIds = new Set(storage.listWorkspaceTasks(workspace.id)
|
|
390
|
+
.filter((task) => task.archived).map((task) => task.id));
|
|
385
391
|
const teamMarkers = teamSessionMarkers(storage);
|
|
392
|
+
const visibleSessions = storage.listSessionsByWorkspace(workspace.id).map((session) => ({
|
|
393
|
+
...workspaceSessionSummary(session, { workspaceName: workspace.name }, sessions, teamMarkersFor(teamMarkers, session.id)),
|
|
394
|
+
workspaceTaskId: session.workspaceTaskId,
|
|
395
|
+
})).filter((session) => includeArchived || (!session.archived
|
|
396
|
+
&& (!session.workspaceTaskId || !archivedTaskIds.has(session.workspaceTaskId))));
|
|
386
397
|
res.json({
|
|
387
|
-
...workspaceWithCounts(storage, workspace),
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
workspaceTaskId: session.workspaceTaskId,
|
|
391
|
-
})),
|
|
398
|
+
...workspaceWithCounts(storage, workspace, undefined, includeArchived),
|
|
399
|
+
sessionCount: visibleSessions.length,
|
|
400
|
+
sessions: visibleSessions,
|
|
392
401
|
});
|
|
393
402
|
});
|
|
394
403
|
// 改名 / 目录 / 默认 IDE
|
|
@@ -514,10 +523,11 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
514
523
|
}
|
|
515
524
|
// 查询参数:workspaceId 过滤单目录;limit 截断每目录任务数;
|
|
516
525
|
// maxSessions 截断每任务内嵌会话数(大数据量时控制响应体积)。
|
|
526
|
+
const includeArchived = req.query.includeArchived === "1";
|
|
517
527
|
const hasRevisionQuery = typeof req.query.revision === "string";
|
|
518
528
|
const requestedRevision = hasRevisionQuery ? req.query.revision : "";
|
|
519
529
|
if (hasRevisionQuery && requestedRevision) {
|
|
520
|
-
const cheap = cheapTasksRevision(storage, sessions);
|
|
530
|
+
const cheap = cheapTasksRevision(storage, sessions, includeArchived);
|
|
521
531
|
if (requestedRevision === cheap) {
|
|
522
532
|
res.json({ unchanged: true, revision: cheap, groups: [] });
|
|
523
533
|
return;
|
|
@@ -534,6 +544,9 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
534
544
|
// 目录组只需要会话元数据:用 slim 读,否则每次轮询都要把每个会话的
|
|
535
545
|
// messages 大字段 JSON 解析一遍(单会话可达十几 MB,整表 ~30MB)。
|
|
536
546
|
const allSessions = storage.loadSessionsSlim();
|
|
547
|
+
const liveArchiveFlags = new Map((sessions?.listSlim() ?? []).map((session) => [session.id, session.archived]));
|
|
548
|
+
const sessionVisible = (session) => includeArchived
|
|
549
|
+
|| !(liveArchiveFlags.get(session.id) ?? session.archived);
|
|
537
550
|
const workspaces = storage.listWorkspaces();
|
|
538
551
|
// 群聊与成员派发标记一次查全:列表里每条会话只做 Map 命中,不逐个查库。
|
|
539
552
|
const teamMarkers = teamSessionMarkers(storage);
|
|
@@ -596,8 +609,8 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
596
609
|
return snapshots;
|
|
597
610
|
};
|
|
598
611
|
const appendTask = (target, workspace, task) => {
|
|
599
|
-
const
|
|
600
|
-
const sessions =
|
|
612
|
+
const visibleSessions = taskSessions(task.id).filter(sessionVisible);
|
|
613
|
+
const sessions = visibleSessions
|
|
601
614
|
.slice(0, sessionLimit ?? undefined)
|
|
602
615
|
.map((session) => ({
|
|
603
616
|
...summarize(session, { taskName: task.name, workspaceName: isGlobalWorkspace(workspace) ? undefined : workspace.name }),
|
|
@@ -610,7 +623,7 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
610
623
|
cwd: taskRuntimeCwd(task, workspace),
|
|
611
624
|
isolated: task.worktree !== null,
|
|
612
625
|
sessions,
|
|
613
|
-
totalSessions:
|
|
626
|
+
totalSessions: visibleSessions.length,
|
|
614
627
|
});
|
|
615
628
|
if (target.synthetic)
|
|
616
629
|
rememberCreatedAt(target, task.createdAt);
|
|
@@ -620,7 +633,11 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
620
633
|
if (!base)
|
|
621
634
|
continue;
|
|
622
635
|
const global = isGlobalWorkspace(workspace);
|
|
623
|
-
|
|
636
|
+
// Filter before pagination. Keep the full membership map below so hidden
|
|
637
|
+
// task sessions cannot reappear as standalone conversations.
|
|
638
|
+
const visibleTasks = tasksByWorkspace.get(workspace.id)
|
|
639
|
+
.filter((task) => includeArchived || !task.archived);
|
|
640
|
+
for (const task of visibleTasks.slice(0, taskLimit ?? undefined)) {
|
|
624
641
|
const taskCwd = taskRuntimeCwd(task, workspace);
|
|
625
642
|
const target = global && taskCwd
|
|
626
643
|
&& normalizeProjectCwd(taskCwd) !== normalizeProjectCwd(workspace.cwd)
|
|
@@ -638,7 +655,7 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
638
655
|
}
|
|
639
656
|
}
|
|
640
657
|
for (const session of allSessions) {
|
|
641
|
-
if (taskBoundSessionIds.has(session.id))
|
|
658
|
+
if (taskBoundSessionIds.has(session.id) || !sessionVisible(session))
|
|
642
659
|
continue;
|
|
643
660
|
const direct = session.workspaceId ? groups.get(session.workspaceId) : undefined;
|
|
644
661
|
const resolved = projectCwdForSession(session);
|
|
@@ -688,7 +705,7 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
688
705
|
.filter((group) => !group.global || group.tasks.length > 0 || group.standaloneSessions.length > 0);
|
|
689
706
|
const payload = sortTaskDirectoryGroups(orderedGroups, storage.getWorkspaceGroupOrder());
|
|
690
707
|
if (hasRevisionQuery) {
|
|
691
|
-
res.json({ unchanged: false, revision: cheapTasksRevision(storage, sessions), groups: payload });
|
|
708
|
+
res.json({ unchanged: false, revision: cheapTasksRevision(storage, sessions, includeArchived), groups: payload });
|
|
692
709
|
return;
|
|
693
710
|
}
|
|
694
711
|
res.json(payload);
|
|
@@ -723,7 +740,9 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
723
740
|
res.status(404).json({ error: "未找到该项目。" });
|
|
724
741
|
return;
|
|
725
742
|
}
|
|
726
|
-
res.json(storage.listWorkspaceTasks(workspace.id)
|
|
743
|
+
res.json(storage.listWorkspaceTasks(workspace.id)
|
|
744
|
+
.filter((task) => req.query.includeArchived === "1" || !task.archived)
|
|
745
|
+
.map((task) => ({
|
|
727
746
|
...task,
|
|
728
747
|
...resolvedMilestoneFields(storage, task.milestoneId),
|
|
729
748
|
})));
|
|
@@ -735,7 +754,8 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
735
754
|
res.status(404).json({ error: "未找到该项目。" });
|
|
736
755
|
return;
|
|
737
756
|
}
|
|
738
|
-
const tasks = storage.listWorkspaceTasks(workspace.id)
|
|
757
|
+
const tasks = storage.listWorkspaceTasks(workspace.id)
|
|
758
|
+
.filter((task) => (req.query.includeArchived === "1" || !task.archived) && task.worktree !== null);
|
|
739
759
|
if (tasks.length === 0) {
|
|
740
760
|
res.json({ workspaceId: workspace.id, repoRoot: workspace.cwd, targetBranch: "", worktrees: [] });
|
|
741
761
|
return;
|
package/dist/server.d.ts
CHANGED
|
@@ -33,5 +33,6 @@ export declare function isPortInUseError(error: unknown): error is PortInUseErro
|
|
|
33
33
|
export declare function startServer(config: WandConfig, configPath: string, options?: {
|
|
34
34
|
modelRefreshOptions?: () => Partial<ModelRefreshOptions>;
|
|
35
35
|
terminalHost?: TerminalHost;
|
|
36
|
+
openRouterFetch?: typeof fetch;
|
|
36
37
|
}): Promise<ServerHandle>;
|
|
37
38
|
export {};
|