@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
|
@@ -5,7 +5,11 @@ import { asyncRoute } from "./express-async.js";
|
|
|
5
5
|
import { bodyObject, sendRouteError, text } from "./server-request.js";
|
|
6
6
|
import { parseTaskAgent } from "./server-task-routes.js";
|
|
7
7
|
import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
8
|
-
import {
|
|
8
|
+
import { employeeCliAvailable } from "./silicon-employee-dispatch.js";
|
|
9
|
+
import { EMPLOYEE_CREATION_DEFAULTS_PREF, resolveSiliconEmployeeDefaults, } from "./silicon-employee-defaults.js";
|
|
10
|
+
import { availableEmployeeProviders, generateSiliconEmployeeDraft, SiliconEmployeeDraftError, validateStructuredEmployeeDraft, } from "./silicon-employee-draft.js";
|
|
11
|
+
import { SESSION_PROVIDERS } from "./session-provider.js";
|
|
12
|
+
import { DEFAULT_WAND_TASK_AGENT_KIND } from "./task-types.js";
|
|
9
13
|
import { EMPLOYEE_KNOWLEDGE_MAX_ENTRIES } from "./employee-knowledge-types.js";
|
|
10
14
|
/** 员工起草没有会话上下文,按「默认 provider」解析,与任务标题 / 提示词优化一致。 */
|
|
11
15
|
function employeeDraftAiOptions(storage, config) {
|
|
@@ -21,10 +25,22 @@ function employeeDraftAiOptions(storage, config) {
|
|
|
21
25
|
thinkingEffort: config.defaultThinkingEffort,
|
|
22
26
|
}, config, storage.getSystemSiliconEmployee());
|
|
23
27
|
}
|
|
24
|
-
function sendEmployeeError(res, error) {
|
|
28
|
+
function sendEmployeeError(res, error, context) {
|
|
25
29
|
const message = getErrorMessage(error, "硅基员工操作失败。");
|
|
26
30
|
const status = /不存在/.test(message) ? 404 : 400;
|
|
27
|
-
|
|
31
|
+
const code = error instanceof SiliconEmployeeDraftError ? error.code : undefined;
|
|
32
|
+
const field = error instanceof SiliconEmployeeDraftError ? error.field : undefined;
|
|
33
|
+
if (!code && !context?.expectation) {
|
|
34
|
+
sendRouteError(res, error, "硅基员工操作失败。", status);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// 结构化可恢复错误:带 code/field,并回显原口语输入,客户端可继续编辑重试。
|
|
38
|
+
res.status(status).json({
|
|
39
|
+
error: message,
|
|
40
|
+
...(code ? { code } : {}),
|
|
41
|
+
...(field ? { field } : {}),
|
|
42
|
+
...(context?.expectation ? { expectation: context.expectation } : {}),
|
|
43
|
+
});
|
|
28
44
|
}
|
|
29
45
|
/** 内置员工是 Wand 自己的执行者,不能被归档或删除。 */
|
|
30
46
|
function rejectSystemEmployeeMutation(existing, action) {
|
|
@@ -82,7 +98,7 @@ export function parseSystemEmployeeAgents(value, existing) {
|
|
|
82
98
|
throw new Error("该员工是内置的,身份不可修改。");
|
|
83
99
|
}
|
|
84
100
|
if (!Array.isArray(body.agents))
|
|
85
|
-
throw new Error("
|
|
101
|
+
throw new Error("请提供执行候选数组(CLI / SDK)。");
|
|
86
102
|
if (body.agents.length === 0)
|
|
87
103
|
throw new Error("至少需要一个执行候选。");
|
|
88
104
|
if (body.agents.length > AI_TEAM_MAX_CANDIDATES) {
|
|
@@ -91,7 +107,7 @@ export function parseSystemEmployeeAgents(value, existing) {
|
|
|
91
107
|
const agents = body.agents.map((rawAgent) => {
|
|
92
108
|
const parsed = parseTaskAgent(rawAgent ?? {});
|
|
93
109
|
if (!parsed)
|
|
94
|
-
throw new Error("请选择有效的 CLI
|
|
110
|
+
throw new Error("请选择有效的 CLI 工具或 SDK 执行引擎。");
|
|
95
111
|
if (parsed.kind !== "structured")
|
|
96
112
|
throw new Error("硅基员工只支持结构化会话。");
|
|
97
113
|
return parsed;
|
|
@@ -120,7 +136,7 @@ export function parseSiliconEmployeeInput(value, existing, now) {
|
|
|
120
136
|
agents = body.agents.map((rawAgent) => {
|
|
121
137
|
const parsed = parseTaskAgent(rawAgent ?? {});
|
|
122
138
|
if (!parsed)
|
|
123
|
-
throw new Error("请选择有效的 CLI
|
|
139
|
+
throw new Error("请选择有效的 CLI 工具或 SDK 执行引擎。");
|
|
124
140
|
if (parsed.kind !== "structured")
|
|
125
141
|
throw new Error("硅基员工只支持结构化会话。");
|
|
126
142
|
return parsed;
|
|
@@ -132,7 +148,7 @@ export function parseSiliconEmployeeInput(value, existing, now) {
|
|
|
132
148
|
else if (body.agent !== undefined && body.agent !== null) {
|
|
133
149
|
const singleAgent = parseTaskAgent(body.agent);
|
|
134
150
|
if (!singleAgent)
|
|
135
|
-
throw new Error("请选择有效的 CLI
|
|
151
|
+
throw new Error("请选择有效的 CLI 工具或 SDK 执行引擎。");
|
|
136
152
|
if (singleAgent.kind !== "structured")
|
|
137
153
|
throw new Error("硅基员工只支持结构化会话。");
|
|
138
154
|
agents = [singleAgent];
|
|
@@ -162,11 +178,31 @@ export function parseSiliconEmployeeInput(value, existing, now) {
|
|
|
162
178
|
}
|
|
163
179
|
export function registerSiliconEmployeeRoutes(app, deps) {
|
|
164
180
|
const { storage, notifyEmployeeChanged, config } = deps;
|
|
181
|
+
const isProviderAvailable = deps.isProviderAvailable ?? employeeCliAvailable;
|
|
182
|
+
/** 已保存的员工创建配置优先;没有就用服务端 defaultProvider;再没有就要求用户选择。 */
|
|
183
|
+
const savedEmployeeAgent = () => {
|
|
184
|
+
const raw = storage.getPreference(EMPLOYEE_CREATION_DEFAULTS_PREF, null);
|
|
185
|
+
if (!raw)
|
|
186
|
+
return null;
|
|
187
|
+
try {
|
|
188
|
+
const agent = parseTaskAgent(raw, undefined, DEFAULT_WAND_TASK_AGENT_KIND);
|
|
189
|
+
return agent?.kind === "structured" ? agent : null;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const resolveDefaults = () => resolveSiliconEmployeeDefaults({ savedAgent: savedEmployeeAgent(), config });
|
|
196
|
+
const allowedProviders = () => {
|
|
197
|
+
const available = availableEmployeeProviders(resolveDefaults().provider, isProviderAvailable);
|
|
198
|
+
return available.length ? available : [...SESSION_PROVIDERS];
|
|
199
|
+
};
|
|
165
200
|
const generateDraft = deps.generateDraft ?? ((request) => generateSiliconEmployeeDraft(request.expectation, employeeDraftAiOptions(storage, config), {
|
|
166
201
|
cwd: config?.defaultCwd || process.cwd(),
|
|
167
202
|
language: config?.language ?? "",
|
|
168
203
|
existingNames: request.existingNames,
|
|
169
|
-
preferredProvider:
|
|
204
|
+
preferredProvider: resolveDefaults().provider,
|
|
205
|
+
isProviderAvailable,
|
|
170
206
|
}));
|
|
171
207
|
const notifyEmployee = (employeeId) => {
|
|
172
208
|
try {
|
|
@@ -186,17 +222,55 @@ export function registerSiliconEmployeeRoutes(app, deps) {
|
|
|
186
222
|
sendEmployeeError(res, error);
|
|
187
223
|
}
|
|
188
224
|
});
|
|
225
|
+
/** 两端唯一的默认执行配置读取点;未配置时如实返回需要用户选择。 */
|
|
226
|
+
app.get("/api/silicon-employees/draft/defaults", (_req, res) => {
|
|
227
|
+
res.json(resolveDefaults());
|
|
228
|
+
});
|
|
229
|
+
/** 显式保存员工创建默认配置(只落偏好,不创建员工实体)。 */
|
|
230
|
+
app.put("/api/silicon-employees/draft/defaults", (req, res) => {
|
|
231
|
+
try {
|
|
232
|
+
const agent = parseTaskAgent(req.body ?? {}, undefined, DEFAULT_WAND_TASK_AGENT_KIND);
|
|
233
|
+
if (!agent)
|
|
234
|
+
throw new Error("请选择有效的执行候选。");
|
|
235
|
+
if (agent.kind !== "structured")
|
|
236
|
+
throw new Error("硅基员工只支持结构化会话。");
|
|
237
|
+
storage.setPreference(EMPLOYEE_CREATION_DEFAULTS_PREF, {
|
|
238
|
+
provider: agent.provider,
|
|
239
|
+
model: agent.model,
|
|
240
|
+
thinkingEffort: agent.thinkingEffort,
|
|
241
|
+
mode: agent.mode,
|
|
242
|
+
kind: agent.kind,
|
|
243
|
+
...(agent.engine ? { engine: agent.engine } : {}),
|
|
244
|
+
});
|
|
245
|
+
res.json(resolveDefaults());
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
sendEmployeeError(res, error);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
189
251
|
app.post("/api/silicon-employees/draft", asyncRoute(async (req, res) => {
|
|
252
|
+
let expectation = "";
|
|
190
253
|
try {
|
|
191
254
|
const body = bodyObject(req.body);
|
|
192
|
-
|
|
255
|
+
expectation = text(body.expectation);
|
|
193
256
|
const existingNames = storage.listSiliconEmployees({ includeArchived: true })
|
|
194
257
|
.map((employee) => employee.name);
|
|
258
|
+
// HR 已给完整结构化草稿:走同一校验,不再调用模型。
|
|
259
|
+
if (body.draft !== undefined && body.draft !== null) {
|
|
260
|
+
const draft = validateStructuredEmployeeDraft(body.draft, {
|
|
261
|
+
allowedProviders: allowedProviders(), existingNames,
|
|
262
|
+
});
|
|
263
|
+
res.json({ draft, usage: { generateCalls: 0, path: "validated" } });
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (!expectation) {
|
|
267
|
+
throw new SiliconEmployeeDraftError("先说说你对这位员工的期望吧。", "EMPTY_EXPECTATION", "expectation");
|
|
268
|
+
}
|
|
195
269
|
const draft = await generateDraft({ expectation, existingNames });
|
|
196
|
-
res.json({ draft });
|
|
270
|
+
res.json({ draft, usage: { generateCalls: 1, path: "generated" } });
|
|
197
271
|
}
|
|
198
272
|
catch (error) {
|
|
199
|
-
sendEmployeeError(res, error);
|
|
273
|
+
sendEmployeeError(res, error, { expectation });
|
|
200
274
|
}
|
|
201
275
|
}));
|
|
202
276
|
app.post("/api/silicon-employees", (req, res) => {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Express } from "express";
|
|
2
|
+
import type { DecisionService } from "./decision-service.js";
|
|
3
|
+
import type { StructuredSessionManager } from "./structured-session-manager.js";
|
|
4
|
+
import type { SessionRegistry } from "./session-registry.js";
|
|
5
|
+
import type { WandConfig } from "./types.js";
|
|
6
|
+
export type PiRecommendationRuntime = Pick<DecisionService, "evaluate" | "status">;
|
|
7
|
+
/** Uses the parent's login + sessions-scope middleware; inference-only tokens cannot enter this route. */
|
|
8
|
+
export declare function registerPiRecommendationRoute(app: Express, deps: {
|
|
9
|
+
structured: StructuredSessionManager;
|
|
10
|
+
sessions: SessionRegistry;
|
|
11
|
+
config: WandConfig;
|
|
12
|
+
decisions?: PiRecommendationRuntime;
|
|
13
|
+
}): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { DecisionError } from "./decision-types.js";
|
|
2
|
+
import { discoverPiResources, resolvePiResourceSelection } from "./pi-resource-catalog.js";
|
|
3
|
+
import { parsePiRecommendationRequest, recommendPiResources } from "./pi-resource-recommendation.js";
|
|
4
|
+
/** Uses the parent's login + sessions-scope middleware; inference-only tokens cannot enter this route. */
|
|
5
|
+
export function registerPiRecommendationRoute(app, deps) {
|
|
6
|
+
const active = new Set();
|
|
7
|
+
app.post("/api/sessions/:id/pi-settings/recommend", async (req, res) => {
|
|
8
|
+
const id = req.params.id;
|
|
9
|
+
const abort = new AbortController();
|
|
10
|
+
const disconnected = () => { if (!res.writableEnded)
|
|
11
|
+
abort.abort(); };
|
|
12
|
+
let timer;
|
|
13
|
+
let owns = false;
|
|
14
|
+
res.set("Cache-Control", "no-store");
|
|
15
|
+
res.once("close", disconnected);
|
|
16
|
+
try {
|
|
17
|
+
const owner = deps.sessions.ownerOf(id);
|
|
18
|
+
if (!owner)
|
|
19
|
+
throw new DecisionError("NOT_FOUND", "未找到该会话。", 404);
|
|
20
|
+
if (owner !== "structured" || deps.structured.get(id)?.provider !== "pi") {
|
|
21
|
+
throw new DecisionError("UNSUPPORTED", "推荐仅适用于 Pi 结构化会话。");
|
|
22
|
+
}
|
|
23
|
+
const { settings, resolution } = deps.structured.getPiSettings(id);
|
|
24
|
+
if (deps.structured.get(id)?.archived || resolution.engine !== "cli") {
|
|
25
|
+
throw new DecisionError("UNSUPPORTED", "请使用未归档的 Pi CLI 会话进行资源推荐。");
|
|
26
|
+
}
|
|
27
|
+
const status = deps.decisions?.status();
|
|
28
|
+
if (!deps.decisions || !status?.enabled || !status.supported || !status.configured || settings.localDecision === false) {
|
|
29
|
+
throw new DecisionError("UNAVAILABLE", "本地决策未启用或不可用,原选择未改变。", 503);
|
|
30
|
+
}
|
|
31
|
+
const input = parsePiRecommendationRequest(req.body);
|
|
32
|
+
if (active.has(id))
|
|
33
|
+
throw new DecisionError("BUSY", "本会话正在推荐,请等待当前请求完成。", 409);
|
|
34
|
+
active.add(id);
|
|
35
|
+
owns = true;
|
|
36
|
+
timer = setTimeout(() => abort.abort(), 15_000);
|
|
37
|
+
const inventory = await discoverPiResources(deps.config, deps.structured.get(id).cwd);
|
|
38
|
+
resolvePiResourceSelection(inventory, input.candidates);
|
|
39
|
+
const result = await recommendPiResources({ ...input, catalog: inventory.catalog,
|
|
40
|
+
evaluate: (value, caller, signal) => deps.decisions.evaluate(value, caller, signal),
|
|
41
|
+
caller: `pi-recommend:${id}`, signal: abort.signal });
|
|
42
|
+
const latest = deps.structured.get(id);
|
|
43
|
+
if (!latest || latest.archived || latest.provider !== "pi" || latest.piSettings?.localDecision === false
|
|
44
|
+
|| deps.structured.getPiSettings(id).resolution.engine !== "cli") {
|
|
45
|
+
throw new DecisionError("CANCELLED", "会话已变化,请重新打开设置面板。", 409);
|
|
46
|
+
}
|
|
47
|
+
if (abort.signal.aborted)
|
|
48
|
+
throw new DecisionError("TIMEOUT", "推荐已取消或超时,原选择未改变。", 504);
|
|
49
|
+
if (!res.destroyed)
|
|
50
|
+
res.json(result);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (res.destroyed)
|
|
54
|
+
return;
|
|
55
|
+
const failure = error instanceof DecisionError ? error
|
|
56
|
+
: new DecisionError("RECOMMEND_FAILED", "推荐失败,请重新打开面板核对资源;原选择未改变。", 400);
|
|
57
|
+
res.status(failure.status).json({ error: failure.message, code: failure.code });
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
if (timer)
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
res.off("close", disconnected);
|
|
63
|
+
if (owns)
|
|
64
|
+
active.delete(id);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -4,7 +4,8 @@ import { StructuredSessionManager } from "./structured-session-manager.js";
|
|
|
4
4
|
import { WandStorage } from "./storage.js";
|
|
5
5
|
import { ExecutionMode, ProcessEvent, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
|
|
6
6
|
import { toSessionListItemDTO } from "./session-transport.js";
|
|
7
|
-
import { SessionRegistry } from "./session-registry.js";
|
|
7
|
+
import { type ProviderHistoryDeleter, SessionRegistry } from "./session-registry.js";
|
|
8
|
+
import { type PiRecommendationRuntime } from "./server-pi-recommendation-routes.js";
|
|
8
9
|
import { type SessionDirectoryNode } from "./session-directory-tree.js";
|
|
9
10
|
export declare function parseExecutionMode(value: unknown, fallback: ExecutionMode): ExecutionMode;
|
|
10
11
|
export declare function parseSessionCreationOrigin(body: {
|
|
@@ -14,7 +15,7 @@ export declare function parseSessionCreationOrigin(body: {
|
|
|
14
15
|
sessionSource: SessionSource;
|
|
15
16
|
automationId?: string;
|
|
16
17
|
};
|
|
17
|
-
type SessionDeletionProcesses = Pick<ProcessManager, "get" | "delete"
|
|
18
|
+
type SessionDeletionProcesses = Pick<ProcessManager, "get" | "delete"> & ProviderHistoryDeleter;
|
|
18
19
|
type SessionDeletionStructured = Pick<StructuredSessionManager, "get" | "delete">;
|
|
19
20
|
/**
|
|
20
21
|
* A Wand-managed session and its provider-native history represent the same
|
|
@@ -45,6 +46,6 @@ export interface SessionDirectoryTreeResponse {
|
|
|
45
46
|
}
|
|
46
47
|
export declare function buildSessionListPage(sessions: SessionSnapshot[], offset: number, limit: number): SessionListPage;
|
|
47
48
|
export declare function buildSessionDirectoryTree(sessions: SessionSnapshot[], customNames?: ReadonlyMap<string, string>): SessionDirectoryTreeResponse;
|
|
48
|
-
export declare function registerSessionRoutes(app: Express, processes: ProcessManager, structured: StructuredSessionManager, storage: WandStorage, defaultMode: ExecutionMode, config: WandConfig, sessions: SessionRegistry, onSessionCreated?: (cwd: string | undefined | null) => void, onCompletionViewed?: (event: ProcessEvent) => void): void;
|
|
49
|
+
export declare function registerSessionRoutes(app: Express, processes: ProcessManager, structured: StructuredSessionManager, storage: WandStorage, defaultMode: ExecutionMode, config: WandConfig, sessions: SessionRegistry, onSessionCreated?: (cwd: string | undefined | null) => void, onCompletionViewed?: (event: ProcessEvent) => void, decisions?: PiRecommendationRuntime): void;
|
|
49
50
|
export declare function registerClaudeHistoryRoutes(app: Express, processes: ProcessManager, storage: WandStorage): void;
|
|
50
51
|
export {};
|
|
@@ -2,6 +2,8 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { PtyInputDeliveryError, SessionInputError } from "./process-manager.js";
|
|
3
3
|
import { getDefaultModelForProvider, isExecutionMode } from "./config.js";
|
|
4
4
|
import { defaultRoleForCli } from "./default-employee.js";
|
|
5
|
+
import { defaultModelGroupSelector } from "./model-groups.js";
|
|
6
|
+
import { inspectPiExecution } from "./pi-execution.js";
|
|
5
7
|
import { alignedBlockStart, blockWindowMessagesForTransport, compactToolMessagesForTransport, sliceTurnBlocksForTransport, truncateMessagesForTransport, visibleBlockCount, windowMessagesForTransport } from "./message-truncator.js";
|
|
6
8
|
import { toSessionDetailDTO, toSessionListItemDTO } from "./session-transport.js";
|
|
7
9
|
import { checkSessionWorktreeMergeabilityAsync, cleanupSessionWorktreeAsync, getWorktreeMergeErrorCode, mergeSessionWorktreeAsync, WorktreeMergeError, } from "./git-worktree.js";
|
|
@@ -18,9 +20,13 @@ import { parseBoundedInteger } from "./request-limits.js";
|
|
|
18
20
|
import { asyncRoute } from "./express-async.js";
|
|
19
21
|
import { sendRouteError } from "./server-request.js";
|
|
20
22
|
import { registerStructuredResumeRoutes } from "./server-resume-routes.js";
|
|
21
|
-
import { addHiddenSessionIds, removeHiddenSessionIds } from "./session-registry.js";
|
|
23
|
+
import { addHiddenSessionIds, deleteProviderNativeHistory, removeHiddenSessionIds } from "./session-registry.js";
|
|
22
24
|
import { enrichStructuredMessages, WAND_PROTOCOL_VERSION } from "./structured-client-protocol.js";
|
|
23
25
|
import { asRecord, isStructuredImagePart } from "./structured-content.js";
|
|
26
|
+
import { installedPiExtensions } from "./pi-extension-resources.js";
|
|
27
|
+
import { registerPiRecommendationRoute } from "./server-pi-recommendation-routes.js";
|
|
28
|
+
import { discoverPiResources, resolvePiResourceSelection } from "./pi-resource-catalog.js";
|
|
29
|
+
import { effectivePiSessionSettings, patchPiSessionSettings } from "./pi-session-settings.js";
|
|
24
30
|
import { buildDirectoryTree, normalizeSessionDirectory, } from "./session-directory-tree.js";
|
|
25
31
|
import { projectCwdForSession, renameWorkspaceDirectory, resolveWorkspaceIdForNewSession, } from "./workspace-binding.js";
|
|
26
32
|
export function parseExecutionMode(value, fallback) {
|
|
@@ -138,24 +144,7 @@ export function deleteSessionWithProviderHistory(processes, structured, storage,
|
|
|
138
144
|
?? snapshot.structuredState?.provider
|
|
139
145
|
?? inferProviderFromCommand(snapshot.command)
|
|
140
146
|
?? "claude";
|
|
141
|
-
|
|
142
|
-
processes.deleteClaudeHistoryFiles([{
|
|
143
|
-
claudeSessionId: providerSessionId,
|
|
144
|
-
cwd: snapshot.cwd,
|
|
145
|
-
}]);
|
|
146
|
-
}
|
|
147
|
-
else if (provider === "codex") {
|
|
148
|
-
processes.deleteCodexHistoryFiles([providerSessionId]);
|
|
149
|
-
}
|
|
150
|
-
else if (provider === "opencode") {
|
|
151
|
-
processes.deleteOpenCodeHistorySessions([providerSessionId]);
|
|
152
|
-
}
|
|
153
|
-
else if (provider === "qoder") {
|
|
154
|
-
processes.deleteQoderHistoryFiles([providerSessionId]);
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
147
|
+
deleteProviderNativeHistory(processes, provider, providerSessionId, snapshot.cwd);
|
|
159
148
|
// History deletion is intentionally best-effort at the filesystem layer.
|
|
160
149
|
// Keep a tombstone as a fallback for permission errors or process tail writes.
|
|
161
150
|
addHiddenSessionIds(storage, [providerSessionId]);
|
|
@@ -320,6 +309,33 @@ async function startResumedPtySession(processes, storage, existingSession, sessi
|
|
|
320
309
|
workspaceTaskId: existingSession.workspaceTaskId,
|
|
321
310
|
});
|
|
322
311
|
}
|
|
312
|
+
/** 取消归档后,用原来的 Wand 会话 ID 把终端拉起来;有 provider session id 就续接那次对话。 */
|
|
313
|
+
async function restoreArchivedSession(sessions, processes, storage, sessionId, defaultMode) {
|
|
314
|
+
const existing = sessions.get(sessionId) ?? storage.getSession(sessionId);
|
|
315
|
+
if (!existing)
|
|
316
|
+
return null;
|
|
317
|
+
const cleared = sessions.setArchived(sessionId, false);
|
|
318
|
+
if (!cleared)
|
|
319
|
+
return null;
|
|
320
|
+
if ((existing.sessionKind ?? "pty") !== "pty" || cleared.status === "running")
|
|
321
|
+
return cleared;
|
|
322
|
+
const command = existing.command.trim();
|
|
323
|
+
if (!command)
|
|
324
|
+
return cleared;
|
|
325
|
+
const provider = existing.provider ?? inferProviderFromCommand(command) ?? undefined;
|
|
326
|
+
if (existing.claudeSessionId && provider && isPtyProviderCommand(provider, command)) {
|
|
327
|
+
return startResumedPtySession(processes, storage, { ...existing, ...cleared }, sessionId, defaultMode, {});
|
|
328
|
+
}
|
|
329
|
+
return processes.start(command, existing.cwd, parseExecutionMode(existing.mode, defaultMode), undefined, {
|
|
330
|
+
reuseId: sessionId,
|
|
331
|
+
provider,
|
|
332
|
+
model: existing.selectedModel ?? undefined,
|
|
333
|
+
thinkingEffort: existing.thinkingEffort ?? undefined,
|
|
334
|
+
systemPrompt: existing.systemPrompt ?? undefined,
|
|
335
|
+
workspaceId: existing.workspaceId,
|
|
336
|
+
workspaceTaskId: existing.workspaceTaskId,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
323
339
|
function getAutoResumeInitialInput(snapshot, input, view, shortcutKey) {
|
|
324
340
|
if (view === "terminal") {
|
|
325
341
|
const isCodexPty = Boolean(snapshot?.provider === "codex" || /^codex\b/.test(snapshot?.command.trim() ?? ""));
|
|
@@ -349,7 +365,8 @@ function canMergeSession(snapshot) {
|
|
|
349
365
|
function isMergeActionAllowed(snapshot) {
|
|
350
366
|
return snapshot.status !== "running";
|
|
351
367
|
}
|
|
352
|
-
export function registerSessionRoutes(app, processes, structured, storage, defaultMode, config, sessions, onSessionCreated, onCompletionViewed) {
|
|
368
|
+
export function registerSessionRoutes(app, processes, structured, storage, defaultMode, config, sessions, onSessionCreated, onCompletionViewed, decisions) {
|
|
369
|
+
registerPiRecommendationRoute(app, { structured, sessions, config, decisions });
|
|
353
370
|
const wantsCompactTools = (req) => req.get("X-Wand-Tool-Projection") === "compact" || req.query.compactTools === "1";
|
|
354
371
|
const sessionResponseDTO = (snapshot, req) => {
|
|
355
372
|
const enriched = enrichStructuredMessages(snapshot.messages ?? [], snapshot.id);
|
|
@@ -462,11 +479,15 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
462
479
|
throw new Error("硅基员工不存在或已归档。");
|
|
463
480
|
const selectedEmployeeCandidate = employee ? selectEmployeeCandidate(employee) : null;
|
|
464
481
|
const employeeAgent = selectedEmployeeCandidate?.agent;
|
|
465
|
-
if (
|
|
482
|
+
if (body.provider && !isSessionProvider(body.provider)) {
|
|
466
483
|
res.status(400).json({ error: "结构化会话当前仅支持 Claude、Codex、OpenCode、Grok、Qoder、Pi 或 Gemini provider。" });
|
|
467
484
|
return;
|
|
468
485
|
}
|
|
469
|
-
const
|
|
486
|
+
const overrideCli = body.overrideCli === true;
|
|
487
|
+
const rawProvider = typeof body.provider === "string" ? body.provider.trim() : "";
|
|
488
|
+
const requestedProvider = overrideCli && isSessionProvider(rawProvider) ? rawProvider : null;
|
|
489
|
+
const provider = requestedProvider
|
|
490
|
+
?? employeeAgent?.provider
|
|
470
491
|
?? (isSessionProvider(body.provider) ? body.provider : config.defaultProvider ?? "claude");
|
|
471
492
|
const rawModel = typeof body.model === "string" ? body.model.trim() : "";
|
|
472
493
|
const origin = parseSessionCreationOrigin(body);
|
|
@@ -477,15 +498,19 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
477
498
|
const cwd = resolveSessionCwd(body.cwd, config.defaultCwd);
|
|
478
499
|
const snapshot = structured.createSession({
|
|
479
500
|
cwd,
|
|
480
|
-
mode:
|
|
501
|
+
mode: (requestedProvider && body.mode ? parseExecutionMode(body.mode, defaultMode) : null)
|
|
502
|
+
?? employeeAgent?.mode
|
|
503
|
+
?? parseExecutionMode(body.mode, defaultMode),
|
|
481
504
|
provider,
|
|
482
505
|
// Omit runner to let StructuredSessionManager apply the configured
|
|
483
506
|
// Claude default; explicit values are validated against the provider.
|
|
484
|
-
runner: employee ? undefined : body.runner,
|
|
507
|
+
runner: employee && !requestedProvider ? undefined : body.runner,
|
|
485
508
|
worktreeEnabled: body.worktreeEnabled === true,
|
|
486
|
-
model:
|
|
487
|
-
?
|
|
488
|
-
:
|
|
509
|
+
model: rawModel && rawModel !== "default"
|
|
510
|
+
? rawModel
|
|
511
|
+
: (employeeAgent && !requestedProvider
|
|
512
|
+
? employeeAgent.model === "default" ? defaultModelGroupSelector(config.modelGroups, provider, getDefaultModelForProvider(config, provider)) || getDefaultModelForProvider(config, provider) || undefined : employeeAgent.model
|
|
513
|
+
: rawModel || defaultModelGroupSelector(config.modelGroups, provider, getDefaultModelForProvider(config, provider)) || getDefaultModelForProvider(config, provider) || undefined),
|
|
489
514
|
thinkingEffort: employeeAgent?.thinkingEffort ?? (typeof body.thinkingEffort === "string"
|
|
490
515
|
? body.thinkingEffort
|
|
491
516
|
: config.defaultThinkingEffort),
|
|
@@ -497,7 +522,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
497
522
|
employeeName: role?.name,
|
|
498
523
|
employeeAvatar: role?.avatar,
|
|
499
524
|
employeeCandidates: employee?.agents,
|
|
500
|
-
employeeCandidateIndex: selectedEmployeeCandidate?.index,
|
|
525
|
+
employeeCandidateIndex: requestedProvider ? undefined : selectedEmployeeCandidate?.index,
|
|
501
526
|
...origin,
|
|
502
527
|
});
|
|
503
528
|
onSessionCreated?.(snapshot.cwd);
|
|
@@ -553,6 +578,105 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
553
578
|
sendRouteError(res, error, "切换工具失败。");
|
|
554
579
|
}
|
|
555
580
|
});
|
|
581
|
+
const piSettingsResponse = async (id) => {
|
|
582
|
+
const { settings, resolution } = structured.getPiSettings(id);
|
|
583
|
+
const snapshot = structured.get(id);
|
|
584
|
+
const installed = await installedPiExtensions(config, snapshot.cwd);
|
|
585
|
+
const sdk = resolution.engine === "core";
|
|
586
|
+
const archived = snapshot.archived;
|
|
587
|
+
const modifiable = !archived;
|
|
588
|
+
const goalAvailable = installed.some((item) => item.goal);
|
|
589
|
+
const localDecisionAvailable = config.localDecision?.enabled === true;
|
|
590
|
+
const inventory = await discoverPiResources(config, snapshot.cwd);
|
|
591
|
+
const resourceCatalog = { ...inventory.catalog, supported: modifiable && !sdk && inventory.catalog.supported,
|
|
592
|
+
reason: archived ? "请先恢复已归档的会话。" : sdk ? "当前 SDK 候选尚不支持 Skills / MCP 会话选择。" : inventory.catalog.reason };
|
|
593
|
+
const controls = {
|
|
594
|
+
// 基础工具与 CodeMode 在两种引擎下都能表达(CLI 通过 --tools 白名单)。
|
|
595
|
+
tools: modifiable,
|
|
596
|
+
codemode: modifiable,
|
|
597
|
+
codemodeOnly: modifiable && sdk,
|
|
598
|
+
codemodeOverride: modifiable && !sdk,
|
|
599
|
+
globalTools: modifiable,
|
|
600
|
+
goalMode: modifiable && goalAvailable,
|
|
601
|
+
localDecision: modifiable && localDecisionAvailable,
|
|
602
|
+
autoCompaction: modifiable && sdk,
|
|
603
|
+
};
|
|
604
|
+
const status = decisions?.status();
|
|
605
|
+
const recommendationAvailable = resourceCatalog.supported && settings.localDecision !== false
|
|
606
|
+
&& status?.enabled === true && status.supported && status.configured;
|
|
607
|
+
const recommendationReason = !resourceCatalog.supported ? resourceCatalog.reason
|
|
608
|
+
: settings.localDecision === false ? "本会话已关闭本地决策。"
|
|
609
|
+
: !recommendationAvailable ? "本地决策未启用或运行环境不可用。" : "";
|
|
610
|
+
return { recommendationAvailable, recommendationReason,
|
|
611
|
+
autoResourcesAvailable: recommendationAvailable, autoResourcesReason: recommendationReason,
|
|
612
|
+
autoCodemodeAvailable: recommendationAvailable, skillLocksAvailable: resourceCatalog.supported,
|
|
613
|
+
settings: effectivePiSessionSettings(settings, resolution.engine), engine: resolution.engine,
|
|
614
|
+
available: sdk && modifiable, toolsAvailable: !sdk && modifiable,
|
|
615
|
+
reason: archived ? "请先恢复已归档的会话。" : resolution.reason,
|
|
616
|
+
goalAvailable, globalExtensions: [...new Set(installed.filter((item) => !item.goal).map((item) => item.name))],
|
|
617
|
+
localDecisionAvailable, controls, resourceCatalog };
|
|
618
|
+
};
|
|
619
|
+
const requirePiSession = (id) => {
|
|
620
|
+
const owner = sessions.ownerOf(id);
|
|
621
|
+
if (!owner)
|
|
622
|
+
throw Object.assign(new Error("未找到该会话。"), { status: 404 });
|
|
623
|
+
if (owner !== "structured" || structured.get(id)?.provider !== "pi") {
|
|
624
|
+
throw new Error("这些设置仅适用于 Pi 结构化会话,不会改变 PTY 或其他工具。");
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
app.get("/api/sessions/:id/pi-settings", asyncRoute(async (req, res) => {
|
|
628
|
+
try {
|
|
629
|
+
requirePiSession(req.params.id);
|
|
630
|
+
res.set("Cache-Control", "no-store");
|
|
631
|
+
res.json(await piSettingsResponse(req.params.id));
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
sendRouteError(res, error, "读取 Pi 设置失败。", error.status ?? 400);
|
|
635
|
+
}
|
|
636
|
+
}));
|
|
637
|
+
app.patch("/api/sessions/:id/pi-settings", asyncRoute(async (req, res) => {
|
|
638
|
+
try {
|
|
639
|
+
const id = req.params.id;
|
|
640
|
+
requirePiSession(id);
|
|
641
|
+
const { settings } = structured.getPiSettings(id);
|
|
642
|
+
const next = patchPiSessionSettings(settings, req.body);
|
|
643
|
+
if (req.body.autoResources === true) {
|
|
644
|
+
const capabilities = await piSettingsResponse(id);
|
|
645
|
+
const runtime = decisions?.status();
|
|
646
|
+
if (!capabilities.resourceCatalog?.supported || next.localDecision === false
|
|
647
|
+
|| !runtime?.enabled || !runtime.supported || !runtime.configured) {
|
|
648
|
+
throw new Error(next.localDecision === false ? "本会话已关闭本地决策。"
|
|
649
|
+
: capabilities.autoResourcesReason || "自动选择暂不可用。");
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (req.body.resources !== undefined || req.body.lockedSkills !== undefined) {
|
|
653
|
+
const inventory = await discoverPiResources(config, structured.get(id).cwd);
|
|
654
|
+
// Removed resources may be retained only when already selected, so users can deselect stale
|
|
655
|
+
// items one at a time. Any NEW ID must resolve; the runner always revalidates the full list.
|
|
656
|
+
if (req.body.resources !== undefined)
|
|
657
|
+
resolvePiResourceSelection(inventory, {
|
|
658
|
+
skills: next.resources.skills.filter((id) => !settings.resources?.skills.includes(id)),
|
|
659
|
+
mcpServers: next.resources.mcpServers.filter((id) => !settings.resources?.mcpServers.includes(id)),
|
|
660
|
+
});
|
|
661
|
+
if (req.body.lockedSkills !== undefined)
|
|
662
|
+
resolvePiResourceSelection(inventory, {
|
|
663
|
+
skills: next.lockedSkills.filter((id) => !settings.lockedSkills?.includes(id)), mcpServers: [],
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (next.goalMode && !settings.goalMode) {
|
|
667
|
+
const installed = await installedPiExtensions(config, structured.get(id).cwd);
|
|
668
|
+
if (!installed.some((item) => item.goal))
|
|
669
|
+
throw new Error("目标模式不可用:请先在 Pi 中安装并启用 pi-goal。");
|
|
670
|
+
}
|
|
671
|
+
structured.setPiSettings(id, req.body);
|
|
672
|
+
// 回复「实际生效」的设置(CLI 的 CodeMode 只归一到 off/on),不把存库原值当生效值展示。
|
|
673
|
+
const saved = structured.getPiSettings(id);
|
|
674
|
+
res.json({ settings: effectivePiSessionSettings(saved.settings, saved.resolution.engine) });
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
sendRouteError(res, error, "保存 Pi 设置失败。", error.status ?? 400);
|
|
678
|
+
}
|
|
679
|
+
}));
|
|
556
680
|
app.post("/api/sessions/:id/model", (req, res) => {
|
|
557
681
|
const body = req.body;
|
|
558
682
|
const rawModel = typeof body?.model === "string" ? body.model.trim() : null;
|
|
@@ -741,6 +865,20 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
741
865
|
}
|
|
742
866
|
});
|
|
743
867
|
// ── Tool content lazy-load endpoint ──
|
|
868
|
+
app.get("/api/sessions/:id/pi-execution/:toolUseId", asyncRoute(async (req, res) => {
|
|
869
|
+
const snapshot = sessions.get(req.params.id);
|
|
870
|
+
if (!snapshot) {
|
|
871
|
+
res.status(404).json({ error: "未找到该会话。" });
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
const execution = await inspectPiExecution(snapshot.messages ?? [], req.params.toolUseId);
|
|
875
|
+
if (!execution) {
|
|
876
|
+
res.status(404).json({ error: "未找到该 Pi 执行调用。" });
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
res.setHeader("Cache-Control", "no-store");
|
|
880
|
+
res.json(execution);
|
|
881
|
+
}));
|
|
744
882
|
app.get("/api/sessions/:id/tool-content/:toolUseId", (req, res) => {
|
|
745
883
|
const snapshot = sessions.get(req.params.id);
|
|
746
884
|
if (!snapshot) {
|
|
@@ -933,7 +1071,10 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
933
1071
|
try {
|
|
934
1072
|
// A commit is already durable, even when push failed. Archiving must not turn that
|
|
935
1073
|
// successful commit into a retryable HTTP error (which would duplicate the commit).
|
|
936
|
-
|
|
1074
|
+
const openedTaskId = typeof body.workspaceTaskId === "string" ? body.workspaceTaskId.trim() : "";
|
|
1075
|
+
archivedTaskIds = archiveCommitTasks(storage, snapshot, context.entryIds, {
|
|
1076
|
+
workspaceTaskId: openedTaskId || null,
|
|
1077
|
+
});
|
|
937
1078
|
}
|
|
938
1079
|
catch (error) {
|
|
939
1080
|
archiveError = getErrorMessage(error, "归档关联任务失败。");
|
|
@@ -1379,7 +1520,8 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1379
1520
|
try {
|
|
1380
1521
|
if (structured.get(sessionId)) {
|
|
1381
1522
|
const completion = structured.sendMessage(sessionId, input);
|
|
1382
|
-
if (body.respondImmediately === true)
|
|
1523
|
+
if (body.respondImmediately === true || (structured.get(sessionId)?.provider === "pi"
|
|
1524
|
+
&& structured.get(sessionId)?.piSettings?.autoResources === true)) {
|
|
1383
1525
|
// sendMessage updates the canonical snapshot synchronously before it
|
|
1384
1526
|
// starts awaiting the runner. Native clients should not hold this
|
|
1385
1527
|
// request open for an entire model turn (which can exceed their HTTP
|
|
@@ -1537,7 +1679,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1537
1679
|
sendRouteError(res, error, "无法停止会话。");
|
|
1538
1680
|
}
|
|
1539
1681
|
});
|
|
1540
|
-
//
|
|
1682
|
+
// 归档会停止会话,但保留 Wand 会话 ID 和 provider session id。恢复时用这个 ID 续上。
|
|
1541
1683
|
app.post("/api/sessions/:id/archive", (req, res) => {
|
|
1542
1684
|
try {
|
|
1543
1685
|
const updated = sessions.setArchived(req.params.id, true);
|
|
@@ -1551,9 +1693,9 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1551
1693
|
sendRouteError(res, error, "无法归档会话。");
|
|
1552
1694
|
}
|
|
1553
1695
|
});
|
|
1554
|
-
app.post("/api/sessions/:id/unarchive", (req, res) => {
|
|
1696
|
+
app.post("/api/sessions/:id/unarchive", asyncRoute(async (req, res) => {
|
|
1555
1697
|
try {
|
|
1556
|
-
const updated = sessions
|
|
1698
|
+
const updated = await restoreArchivedSession(sessions, processes, storage, req.params.id, defaultMode);
|
|
1557
1699
|
if (!updated) {
|
|
1558
1700
|
res.status(404).json({ error: "未找到该会话。" });
|
|
1559
1701
|
return;
|
|
@@ -1563,9 +1705,9 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1563
1705
|
catch (error) {
|
|
1564
1706
|
sendRouteError(res, error, "无法恢复会话。");
|
|
1565
1707
|
}
|
|
1566
|
-
});
|
|
1708
|
+
}));
|
|
1567
1709
|
// 批量归档 / 恢复:与 /api/sessions/batch-delete 对称,返回失败项让客户端保留选择。
|
|
1568
|
-
app.post("/api/sessions/batch-archive", (req, res) => {
|
|
1710
|
+
app.post("/api/sessions/batch-archive", asyncRoute(async (req, res) => {
|
|
1569
1711
|
const body = req.body;
|
|
1570
1712
|
const ids = Array.isArray(body?.sessionIds)
|
|
1571
1713
|
? body.sessionIds.filter((value) => typeof value === "string" && value.trim().length > 0)
|
|
@@ -1579,7 +1721,10 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1579
1721
|
let changed = 0;
|
|
1580
1722
|
for (const id of ids) {
|
|
1581
1723
|
try {
|
|
1582
|
-
|
|
1724
|
+
const updated = archived
|
|
1725
|
+
? sessions.setArchived(id, true)
|
|
1726
|
+
: await restoreArchivedSession(sessions, processes, storage, id, defaultMode);
|
|
1727
|
+
if (updated)
|
|
1583
1728
|
changed += 1;
|
|
1584
1729
|
else
|
|
1585
1730
|
failed.push(id);
|
|
@@ -1589,7 +1734,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
1589
1734
|
}
|
|
1590
1735
|
}
|
|
1591
1736
|
res.json({ ok: true, archived: changed, ...(failed.length > 0 ? { failed } : {}) });
|
|
1592
|
-
});
|
|
1737
|
+
}));
|
|
1593
1738
|
app.delete("/api/sessions/:id", (req, res) => {
|
|
1594
1739
|
try {
|
|
1595
1740
|
sessions.deleteWithProviderHistory(req.params.id);
|