@co0ontty/wand 4.93.0 → 4.94.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 +25 -30
- package/dist/ai-text-runner.d.ts +2 -10
- package/dist/ai-text-runner.js +13 -74
- package/dist/build-info.json +3 -3
- package/dist/cli.js +47 -9
- package/dist/config.d.ts +2 -3
- package/dist/config.js +26 -5
- package/dist/core-status-cli.d.ts +1 -1
- package/dist/core-status-cli.js +3 -1
- package/dist/daemon-connection.d.ts +10 -0
- package/dist/daemon-connection.js +28 -0
- package/dist/diagnostic-logs.d.ts +10 -0
- package/dist/diagnostic-logs.js +121 -0
- package/dist/employee-text.js +1 -3
- package/dist/git-quick-commit.d.ts +1 -1
- package/dist/git-quick-commit.js +2 -9
- package/dist/layout-tree.d.ts +3 -1
- package/dist/layout-tree.js +8 -0
- package/dist/local-model-process.js +20 -0
- package/dist/local-model-setup.js +5 -3
- package/dist/managed-temp.d.ts +32 -0
- package/dist/managed-temp.js +176 -0
- package/dist/middleware/api-scope.js +1 -0
- package/dist/models/shared.d.ts +0 -6
- package/dist/models/shared.js +0 -12
- package/dist/models/thinking.d.ts +0 -2
- package/dist/models/thinking.js +0 -2
- package/dist/models.d.ts +0 -1
- package/dist/models.js +3 -11
- package/dist/native-session-title.d.ts +1 -1
- package/dist/native-session-title.js +1 -1
- package/dist/pi-model-discovery.js +5 -17
- package/dist/pi-resource-recommendation.d.ts +1 -1
- package/dist/pi-resource-recommendation.js +129 -42
- package/dist/process-manager-native-history.js +1 -6
- package/dist/process-manager.d.ts +0 -1
- package/dist/process-manager.js +5 -18
- package/dist/provider-catalog.d.ts +1 -2
- package/dist/provider-catalog.js +1 -6
- package/dist/provider-cli-updater.js +1 -9
- package/dist/provider-history-scanner.d.ts +0 -3
- package/dist/provider-history-scanner.js +0 -26
- package/dist/render-daemon-client.js +6 -29
- package/dist/render-structured-client.js +2 -7
- package/dist/retention.js +1 -6
- package/dist/run-command.d.ts +5 -0
- package/dist/run-command.js +14 -1
- package/dist/server-session-lifecycle-routes.js +1 -1
- package/dist/server-storage-cleanup-routes.d.ts +3 -0
- package/dist/server-storage-cleanup-routes.js +19 -0
- package/dist/server-workspace-routes.d.ts +4 -1
- package/dist/server-workspace-routes.js +75 -9
- package/dist/server.js +21 -16
- package/dist/service-command-privilege.d.ts +17 -0
- package/dist/service-command-privilege.js +55 -0
- package/dist/service-manager/index.d.ts +3 -0
- package/dist/service-manager/index.js +3 -0
- package/dist/service-manager/launchd.d.ts +1 -0
- package/dist/service-manager/launchd.js +15 -0
- package/dist/service-manager/systemd.d.ts +1 -0
- package/dist/service-manager/systemd.js +11 -0
- package/dist/session-ai-context.js +17 -6
- package/dist/session-completion-state.d.ts +2 -0
- package/dist/session-completion-state.js +6 -2
- package/dist/session-logger.js +7 -1
- package/dist/session-model.d.ts +4 -0
- package/dist/session-model.js +13 -4
- package/dist/session-registry.d.ts +1 -1
- package/dist/session-registry.js +0 -3
- package/dist/silicon-employee-draft.js +0 -1
- package/dist/speech-service.js +18 -8
- package/dist/storage/mappers.js +6 -10
- package/dist/storage/migrations.js +14 -0
- package/dist/storage/uploads-repository.d.ts +36 -0
- package/dist/storage/uploads-repository.js +98 -0
- package/dist/storage-cleanup.d.ts +50 -0
- package/dist/storage-cleanup.js +344 -0
- package/dist/storage.d.ts +3 -1
- package/dist/storage.js +4 -0
- package/dist/structured-failure.d.ts +2 -0
- package/dist/structured-failure.js +9 -0
- package/dist/structured-grok-adapter.js +4 -4
- package/dist/structured-opencode-adapter.js +4 -9
- package/dist/structured-provider-common.d.ts +1 -2
- package/dist/structured-provider-common.js +3 -6
- package/dist/structured-session-manager-replay.js +2 -6
- package/dist/structured-session-manager.d.ts +1 -2
- package/dist/structured-session-manager.js +32 -25
- package/dist/task-organize-contract.d.ts +41 -0
- package/dist/task-organize-contract.js +4 -0
- package/dist/task-title-organize-service.d.ts +21 -0
- package/dist/task-title-organize-service.js +168 -0
- package/dist/task-title-organize.d.ts +77 -0
- package/dist/task-title-organize.js +346 -0
- package/dist/terminal-daemon-client.js +10 -48
- package/dist/time-utils.d.ts +2 -0
- package/dist/time-utils.js +7 -0
- package/dist/tui/attach.js +6 -5
- package/dist/tui/commands.d.ts +4 -1
- package/dist/tui/commands.js +36 -25
- package/dist/tui/index.js +2 -2
- package/dist/tui/service-actions.js +5 -4
- package/dist/tui/service-command.d.ts +6 -0
- package/dist/tui/service-command.js +40 -0
- package/dist/tui/service-panel.js +9 -8
- package/dist/types.d.ts +4 -4
- package/dist/upload-reference-guard.d.ts +4 -0
- package/dist/upload-reference-guard.js +29 -0
- package/dist/upload-routes.d.ts +4 -0
- package/dist/upload-routes.js +121 -21
- package/dist/user-profile.d.ts +1 -1
- package/dist/user-profile.js +8 -3
- package/dist/web-ui/content/ai-teams.js +9 -9
- package/dist/web-ui/content/module-manifest.json +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chat-render-EODYMQF6.js → 0051bd3d55f10ab7/chat-render-RY2I2CF3.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-ZTEQSXPA.js → 0051bd3d55f10ab7/chunk-2F6J2MKX.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-A33YPCY6.js → 0051bd3d55f10ab7/chunk-7AFM245W.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-QNUWUMKW.js → 0051bd3d55f10ab7/chunk-MHX2QXIS.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-WGADUEIT.js → 0051bd3d55f10ab7/chunk-RAAPETNH.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-I3UYDQLS.js → 0051bd3d55f10ab7/chunk-RXKBHYUR.js} +2 -2
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-3TCUG766.js → 0051bd3d55f10ab7/chunk-U4EDP4TI.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-HK7RBU3M.js → 0051bd3d55f10ab7/chunk-WHXQR7JT.js} +1 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/employee-card-NV34TZIH.js +1 -0
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-2PU6TV2I.js → 0051bd3d55f10ab7/host-2IVIZ2YC.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-BPYNMKFC.js → 0051bd3d55f10ab7/host-AZXNFCRW.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-HZIKPWYY.js → 0051bd3d55f10ab7/host-EPEQX22R.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-YELXZID4.js → 0051bd3d55f10ab7/host-FZYIMOLC.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-OGD6AWHL.js → 0051bd3d55f10ab7/host-G6GL4SNL.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-E3SCPOIU.js → 0051bd3d55f10ab7/host-PEILC5NU.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-OWD32FZ3.js → 0051bd3d55f10ab7/host-PHHNGVNL.js} +2 -2
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-CXDAFMXX.js → 0051bd3d55f10ab7/host-Q524TMSR.js} +1 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-RO5MB6RB.js +69 -0
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-J5XRAQFP.js → 0051bd3d55f10ab7/host-WOQRK6NG.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-HKXE2DOT.js → 0051bd3d55f10ab7/host-XNIR3NTJ.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-4E4H4JZJ.js → 0051bd3d55f10ab7/host-XRVOIEP6.js} +1 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-ZDKOQEMS.js +1 -0
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-bridge-PMQZRG5G.js +1 -0
- package/dist/web-ui/content/modules/{2548dbe0d0783577/library-bridge-Y3Y24CPQ.js → 0051bd3d55f10ab7/library-bridge-DTMQIHVJ.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/page-GQBUU7T7.js → 0051bd3d55f10ab7/page-PY4JUL4W.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/panel-PG662OC5.js → 0051bd3d55f10ab7/panel-BBQFC55Q.js} +1 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/runtime.js +974 -0
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/task-board-host-PXZSWBBG.js +2 -0
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/workspace-agent-picker-DJSECSY6.js +1 -0
- package/dist/web-ui/content/modules/{2548dbe0d0783577/workspace-window-HV4T3WKR.js → 0051bd3d55f10ab7/workspace-window-MNMKSOBG.js} +1 -1
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +27 -27
- package/dist/web-ui/embedded-assets.js +4 -4
- package/dist/web-ui/provider-identity.d.ts +4 -5
- package/dist/web-ui/provider-identity.js +4 -10
- package/dist/web-ui/react/workspaces/sidebar-manage.d.ts +39 -0
- package/dist/web-ui/react/workspaces/sidebar-manage.js +149 -0
- package/dist/web-ui/react/workspaces/types.d.ts +408 -0
- package/dist/web-ui/react/workspaces/types.js +16 -0
- package/dist/web-ui/session-activity.js +1 -1
- package/package.json +2 -2
- package/dist/structured-gemini-adapter.d.ts +0 -40
- package/dist/structured-gemini-adapter.js +0 -208
- package/dist/web-ui/content/modules/2548dbe0d0783577/chunk-XGVT4ZWT.js +0 -1
- package/dist/web-ui/content/modules/2548dbe0d0783577/employee-card-LJ3TX72B.js +0 -1
- package/dist/web-ui/content/modules/2548dbe0d0783577/host-ITV2QZ4A.js +0 -1
- package/dist/web-ui/content/modules/2548dbe0d0783577/host-XNWC7KPR.js +0 -69
- package/dist/web-ui/content/modules/2548dbe0d0783577/host-bridge-G5HEVADT.js +0 -1
- package/dist/web-ui/content/modules/2548dbe0d0783577/runtime.js +0 -946
- package/dist/web-ui/content/modules/2548dbe0d0783577/task-board-host-RAN2Z2KR.js +0 -2
- package/dist/web-ui/content/modules/2548dbe0d0783577/workspace-agent-picker-FXLH6N5K.js +0 -1
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-YX475LXO.js → 0051bd3d55f10ab7/chunk-6C6A2RHG.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-NP35GDQ3.js → 0051bd3d55f10ab7/chunk-ECWYA3U5.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-47EVOSJS.js → 0051bd3d55f10ab7/chunk-T35D57FW.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577 → 0051bd3d55f10ab7}/chunk-YOXXNXOX.js +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-C3K34WJD.js → 0051bd3d55f10ab7/chunk-YQJ6XYV7.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/host-J6HBDK4A.js → 0051bd3d55f10ab7/host-2AQAIUNO.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577 → 0051bd3d55f10ab7}/sha256-ANXV72UC.js +0 -0
package/dist/run-command.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFile, spawnSync, } from "node:child_process";
|
|
1
|
+
import { execFile, spawn, spawnSync, } from "node:child_process";
|
|
2
2
|
import { buildChildEnv } from "./env-utils.js";
|
|
3
3
|
/** Default cap for captured stdout/stderr of one-shot CLI probes (1 MiB). */
|
|
4
4
|
export const RUN_COMMAND_MAX_BUFFER = 1024 * 1024;
|
|
@@ -30,6 +30,19 @@ export function runCommandSync(file, args, options = {}) {
|
|
|
30
30
|
shell: false,
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
+
/** Foreground operations such as sudo: the terminal owns input (including passwords).
|
|
34
|
+
* No capture, shell, or timeout: cancellation belongs to the user, and Core drain
|
|
35
|
+
* may legitimately wait longer than the bounded probe timeout.
|
|
36
|
+
*/
|
|
37
|
+
export function runCommandInteractive(file, args) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const child = spawn(file, [...args], {
|
|
40
|
+
env: commandEnv({}), stdio: "inherit", shell: false,
|
|
41
|
+
});
|
|
42
|
+
child.once("error", reject);
|
|
43
|
+
child.once("exit", (code, signal) => resolve(code ?? (signal === "SIGINT" ? 130 : 1)));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
33
46
|
/** Async variant with the same env/timeout/output-cap contract; never rejects. */
|
|
34
47
|
export function runCommand(file, args, options = {}) {
|
|
35
48
|
const execOptions = {
|
|
@@ -38,7 +38,7 @@ export function registerSessionLifecycleRoutes(app, ctx) {
|
|
|
38
38
|
const selectedEmployeeCandidate = employee ? selectEmployeeCandidate(employee) : null;
|
|
39
39
|
const employeeAgent = selectedEmployeeCandidate?.agent;
|
|
40
40
|
if (body.provider && !isSessionProvider(body.provider)) {
|
|
41
|
-
res.status(400).json({ error: "结构化会话当前仅支持 Claude、Codex、OpenCode、Grok、Qoder
|
|
41
|
+
res.status(400).json({ error: "结构化会话当前仅支持 Claude、Codex、OpenCode、Grok、Qoder 或 Pi provider。" });
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
const overrideCli = body.overrideCli === true;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { asyncRoute } from "./express-async.js";
|
|
2
|
+
import { HttpError } from "./http-error.js";
|
|
3
|
+
export function registerStorageCleanupRoutes(app, cleanup, requireAdmin) {
|
|
4
|
+
app.use("/api/storage-cleanup", requireAdmin, (_req, res, next) => { res.set("Cache-Control", "no-store"); next(); });
|
|
5
|
+
app.get("/api/storage-cleanup/status", (_req, res) => { res.json(cleanup.status()); });
|
|
6
|
+
app.get("/api/storage-cleanup/preview", asyncRoute(async (_req, res) => {
|
|
7
|
+
if (cleanup.status().running)
|
|
8
|
+
throw new HttpError(409, "cleanup_busy", "清理正在进行,请稍后重试。");
|
|
9
|
+
res.json(await cleanup.run());
|
|
10
|
+
}));
|
|
11
|
+
app.post("/api/storage-cleanup/run", asyncRoute(async (req, res) => {
|
|
12
|
+
if (req.body !== undefined && (!req.body || typeof req.body !== "object" || Array.isArray(req.body) || Object.keys(req.body).length)) {
|
|
13
|
+
throw new HttpError(400, "bad_request", "清理范围和保留期由服务端固定,不接受路径或命令参数。");
|
|
14
|
+
}
|
|
15
|
+
if (cleanup.status().running)
|
|
16
|
+
throw new HttpError(409, "cleanup_busy", "清理正在进行,请稍后重试。");
|
|
17
|
+
res.json(await cleanup.run(true));
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { SessionRegistry } from "./session-registry.js";
|
|
|
3
3
|
import type { WandStorage } from "./storage.js";
|
|
4
4
|
import { type LayoutNode, type TaskWindowLayout } from "./types.js";
|
|
5
5
|
import { type AutoTaskTitleOptions } from "./task-title-service.js";
|
|
6
|
+
import { type TitleJudge } from "./task-title-organize.js";
|
|
6
7
|
/** 校验并清洗前端提交的布局树;非法输入返回 null(空工作空间)。 */
|
|
7
8
|
export declare function sanitizeLayout(value: unknown): LayoutNode | null;
|
|
8
9
|
/** 校验任务级工作窗口集合;旧版单棵布局会兼容升级成一个 window。 */
|
|
@@ -14,4 +15,6 @@ export declare function sanitizeTaskLayout(value: unknown): TaskWindowLayout | n
|
|
|
14
15
|
* - POST 只建项目实体,**不启动任何会话**;会话由工作空间内「+」标签按需创建并绑定 workspaceId。
|
|
15
16
|
* - 布局树由前端 allotment 构造,PUT 时经 sanitizeLayout 校验/规整后落库。
|
|
16
17
|
*/
|
|
17
|
-
export declare function registerWorkspaceRoutes(app: Express, storage: WandStorage, sessions?: SessionRegistry, titleOptions?: AutoTaskTitleOptions
|
|
18
|
+
export declare function registerWorkspaceRoutes(app: Express, storage: WandStorage, sessions?: SessionRegistry, titleOptions?: AutoTaskTitleOptions, organize?: {
|
|
19
|
+
judge?: TitleJudge;
|
|
20
|
+
}): void;
|
|
@@ -16,9 +16,11 @@ import { attachUnboundSessionsToWorkspace, normalizeProjectCwd, projectCwdForSes
|
|
|
16
16
|
import { defaultMilestoneIdForWrite, resolvedMilestoneFields, scopedMilestoneId } from "./milestone-scope.js";
|
|
17
17
|
import { isUnnamedWorkspaceTaskName, UNNAMED_WORKSPACE_TASK_NAME } from "./wand-task-sync.js";
|
|
18
18
|
import { isSessionProvider } from "./session-provider.js";
|
|
19
|
-
import { firstLayoutTabId } from "./layout-tree.js";
|
|
19
|
+
import { firstLayoutTabId, wrapLegacyTaskLayout } from "./layout-tree.js";
|
|
20
20
|
import { parentTaskIdFrom } from "./task-agent-parsing.js";
|
|
21
21
|
import { taskTitleService, refreshAutoBoardTaskTitles } from "./task-title-service.js";
|
|
22
|
+
import { organizeSelectedByTitle, titleJudgeForStorage } from "./task-title-organize-service.js";
|
|
23
|
+
import { TITLE_ORGANIZE_MAX_SESSIONS, TITLE_ORGANIZE_MAX_TASKS } from "./task-title-organize.js";
|
|
22
24
|
import { provisionalTaskTitleFromDescription } from "./task-title.js";
|
|
23
25
|
import { badRequest, surfaceDomainError } from "./http-error.js";
|
|
24
26
|
function workspaceSessionTitle(session, names = {}) {
|
|
@@ -286,13 +288,8 @@ function layoutHasTab(node, tabId) {
|
|
|
286
288
|
/** 校验任务级工作窗口集合;旧版单棵布局会兼容升级成一个 window。 */
|
|
287
289
|
export function sanitizeTaskLayout(value) {
|
|
288
290
|
const legacy = sanitizeLayoutNode(value);
|
|
289
|
-
if (legacy)
|
|
290
|
-
return
|
|
291
|
-
type: "windows",
|
|
292
|
-
windows: [{ id: "window-legacy", layout: legacy, activeTabId: firstLayoutTabId(legacy) }],
|
|
293
|
-
activeWindowId: "window-legacy",
|
|
294
|
-
};
|
|
295
|
-
}
|
|
291
|
+
if (legacy)
|
|
292
|
+
return wrapLegacyTaskLayout(legacy);
|
|
296
293
|
if (!value || typeof value !== "object")
|
|
297
294
|
return null;
|
|
298
295
|
const record = value;
|
|
@@ -327,7 +324,7 @@ export function sanitizeTaskLayout(value) {
|
|
|
327
324
|
* - POST 只建项目实体,**不启动任何会话**;会话由工作空间内「+」标签按需创建并绑定 workspaceId。
|
|
328
325
|
* - 布局树由前端 allotment 构造,PUT 时经 sanitizeLayout 校验/规整后落库。
|
|
329
326
|
*/
|
|
330
|
-
export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {}) {
|
|
327
|
+
export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {}, organize = {}) {
|
|
331
328
|
taskTitleService(storage, titleOptions);
|
|
332
329
|
// 列出所有项目(按创建时间降序:新建在前,打开不会改位置)
|
|
333
330
|
app.get("/api/workspaces", (req, res) => {
|
|
@@ -475,6 +472,75 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
475
472
|
res.json({ ok: true, layout });
|
|
476
473
|
});
|
|
477
474
|
// ── 任务(Task = 命名 + 独立 worktree + 一组标签)──
|
|
475
|
+
// 侧边栏多选后的按标题归类:任务归入同类父任务,会话归入标题对应的任务。
|
|
476
|
+
// 不确定的不移动。选中任务时,其下会话即使没有被逐条勾选也在范围内。
|
|
477
|
+
app.post("/api/tasks/organize-by-title", asyncRoute(async (req, res) => {
|
|
478
|
+
const body = req.body;
|
|
479
|
+
if (body?.mode !== undefined && body.mode !== "auto" && body.mode !== "all") {
|
|
480
|
+
res.status(400).json({ error: "归类范围必须是 auto 或 all。" });
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const ids = (value, limit) => {
|
|
484
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string"))
|
|
485
|
+
return null;
|
|
486
|
+
return [...new Set(value.map((item) => item.trim()).filter(Boolean))].slice(0, limit + 1);
|
|
487
|
+
};
|
|
488
|
+
let taskIds = ids(body?.taskIds, TITLE_ORGANIZE_MAX_TASKS);
|
|
489
|
+
let sessionIds = body?.sessionIds === undefined ? [] : ids(body.sessionIds, TITLE_ORGANIZE_MAX_SESSIONS);
|
|
490
|
+
if (body?.target !== undefined) {
|
|
491
|
+
const target = body.target;
|
|
492
|
+
if (!target || typeof target !== "object" || Array.isArray(target)
|
|
493
|
+
|| typeof target.kind !== "string" || !["all", "workspace", "directory"].includes(target.kind)
|
|
494
|
+
|| (target.kind === "workspace" && (typeof target.workspaceId !== "string" || !storage.getWorkspace(target.workspaceId)))
|
|
495
|
+
|| (target.kind === "directory" && (typeof target.cwd !== "string" || !target.cwd.trim()))
|
|
496
|
+
|| body.taskIds !== undefined || body.sessionIds !== undefined) {
|
|
497
|
+
res.status(400).json({ error: "归类目标无效。" });
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const cwd = typeof target.cwd === "string" ? normalizeProjectCwd(target.cwd) : "";
|
|
501
|
+
taskIds = storage.listAllWorkspaceTasks().filter(task => {
|
|
502
|
+
if (task.archived)
|
|
503
|
+
return false;
|
|
504
|
+
if (target.kind === "all")
|
|
505
|
+
return true;
|
|
506
|
+
if (target.kind === "workspace")
|
|
507
|
+
return task.workspaceId === target.workspaceId;
|
|
508
|
+
const workspace = storage.getWorkspace(task.workspaceId);
|
|
509
|
+
// Match the same grouping as GET /api/tasks, including synthetic global directories.
|
|
510
|
+
return normalizeProjectCwd(workspace && !isGlobalWorkspace(workspace)
|
|
511
|
+
? workspace.cwd : taskRuntimeCwd(task, workspace)) === cwd;
|
|
512
|
+
}).map(task => task.id);
|
|
513
|
+
sessionIds = storage.loadSessionsSlim().filter(session => {
|
|
514
|
+
if (session.archived || session.workspaceTaskId)
|
|
515
|
+
return false;
|
|
516
|
+
if (target.kind === "all")
|
|
517
|
+
return true;
|
|
518
|
+
const directory = target.kind === "workspace" ? storage.getWorkspace(String(target.workspaceId))?.cwd : cwd;
|
|
519
|
+
return projectCwdForSession(session) === normalizeProjectCwd(directory || "");
|
|
520
|
+
}).map(session => session.id);
|
|
521
|
+
}
|
|
522
|
+
if (!taskIds || !sessionIds) {
|
|
523
|
+
res.status(400).json({ error: "需要任务 id 列表,会话 id 列表可选。" });
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (!taskIds.length && body?.target === undefined) {
|
|
527
|
+
res.status(400).json({ error: "请选择要归类的任务。" });
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (body?.target === undefined && (taskIds.length > TITLE_ORGANIZE_MAX_TASKS || sessionIds.length > TITLE_ORGANIZE_MAX_SESSIONS)) {
|
|
531
|
+
res.status(400).json({ error: `一次最多归类 ${TITLE_ORGANIZE_MAX_TASKS} 个任务、${TITLE_ORGANIZE_MAX_SESSIONS} 个会话。` });
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const result = await organizeSelectedByTitle(storage, { taskIds, sessionIds, mode: body?.mode === "all" ? "all" : "auto" }, {
|
|
535
|
+
judge: organize.judge ?? titleJudgeForStorage(storage, titleOptions),
|
|
536
|
+
refreshSession: (sessionId) => sessions?.refreshSessionWorkspace(sessionId),
|
|
537
|
+
});
|
|
538
|
+
if (!result.foundTasks && body?.target === undefined) {
|
|
539
|
+
res.status(404).json({ error: "未找到所选任务。" });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
res.json(result);
|
|
543
|
+
}));
|
|
478
544
|
// 不依赖项目的独立任务:挂到隐藏的全局空间,目录可省略(使用全局临时目录)。
|
|
479
545
|
app.post("/api/tasks", asyncRoute(async (req, res) => {
|
|
480
546
|
const workspace = storage.ensureGlobalWorkspace();
|
package/dist/server.js
CHANGED
|
@@ -16,7 +16,7 @@ import { createExtensionCors } from "./middleware/cors.js";
|
|
|
16
16
|
import { ensureCertificates } from "./cert.js";
|
|
17
17
|
import { getDefaultModelForProvider, getProviderDefaultModels, isExecutionMode, resolveConfigDir, writePreferenceToStorage, } from "./config.js";
|
|
18
18
|
import { ModelCatalogService } from "./models.js";
|
|
19
|
-
import { fallbackProviderModel, sessionEmployeeFields } from "./session-model.js";
|
|
19
|
+
import { fallbackProviderModel, readProviderModelPreferences, sessionEmployeeFields } from "./session-model.js";
|
|
20
20
|
import { ProcessManager } from "./process-manager.js";
|
|
21
21
|
import { SessionLogger } from "./session-logger.js";
|
|
22
22
|
import { coreHarnessAgentDir } from "./harness-engine.js";
|
|
@@ -35,6 +35,8 @@ import { registerSpeechRoutes } from "./server-speech-routes.js";
|
|
|
35
35
|
import { SpeechPolisherService } from "./speech-polisher-service.js";
|
|
36
36
|
import { SPEECH_POLISHER_KEY } from "./speech-polisher-identity.js";
|
|
37
37
|
import { registerLocalModelRoutes } from "./server-local-model-routes.js";
|
|
38
|
+
import { createStorageCleanup } from "./storage-cleanup.js";
|
|
39
|
+
import { registerStorageCleanupRoutes } from "./server-storage-cleanup-routes.js";
|
|
38
40
|
import { LocalModelSetupService } from "./local-model-setup.js";
|
|
39
41
|
import { DecisionExpertService } from "./decision-expert-service.js";
|
|
40
42
|
import { DECISION_EXPERT_KEY } from "./decision-expert-identity.js";
|
|
@@ -80,7 +82,9 @@ import { safeServiceInstalled } from "./tui/runtime-utils.js";
|
|
|
80
82
|
import { toSessionDetailDTO } from "./session-transport.js";
|
|
81
83
|
import { compactToolMessagesForTransport, windowMessagesForTransport } from "./message-truncator.js";
|
|
82
84
|
import { enrichStructuredMessages } from "./structured-client-protocol.js";
|
|
83
|
-
import { registerUploadRoutes } from "./upload-routes.js";
|
|
85
|
+
import { registerUploadRoutes, invalidateUploadUsage } from "./upload-routes.js";
|
|
86
|
+
import { uploadReferenceGuard } from "./upload-reference-guard.js";
|
|
87
|
+
import { configTemporaryRoot } from "./managed-temp.js";
|
|
84
88
|
import { optimizePrompt, PromptOptimizeError } from "./prompt-optimizer.js";
|
|
85
89
|
import { resolveDatabasePath, WandStorage } from "./storage.js";
|
|
86
90
|
import { deepRepairRuntimePath, formatPathRepairSummary, repairRuntimePath } from "./path-repair.js";
|
|
@@ -250,19 +254,9 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
250
254
|
const decisions = new DecisionService(config.localDecision);
|
|
251
255
|
const decisionExpert = new DecisionExpertService({ local: decisions, free: openRouter, config,
|
|
252
256
|
employee: () => storage.getSystemSiliconEmployee(DECISION_EXPERT_KEY) });
|
|
253
|
-
// 默认模型优先读存储(UI 改设置后实时生效),未设置时由 getPreference 回落到 config。
|
|
254
|
-
const getCurrentDefaultModels = () => ({
|
|
255
|
-
claude: storage.getPreference("pref:defaultModel", config.defaultModel ?? ""),
|
|
256
|
-
codex: storage.getPreference("pref:defaultCodexModel", config.defaultCodexModel ?? ""),
|
|
257
|
-
opencode: storage.getPreference("pref:defaultOpenCodeModel", config.defaultOpenCodeModel ?? ""),
|
|
258
|
-
grok: storage.getPreference("pref:defaultGrokModel", config.defaultGrokModel ?? ""),
|
|
259
|
-
qoder: storage.getPreference("pref:defaultQoderModel", config.defaultQoderModel ?? ""),
|
|
260
|
-
pi: storage.getPreference("pref:defaultPiModel", config.defaultPiModel ?? ""),
|
|
261
|
-
gemini: storage.getPreference("pref:defaultGeminiModel", config.defaultGeminiModel ?? ""),
|
|
262
|
-
});
|
|
263
257
|
const getModelRefreshOptions = () => {
|
|
264
258
|
const injected = options.modelRefreshOptions?.() ?? {};
|
|
265
|
-
const currentDefaults =
|
|
259
|
+
const currentDefaults = readProviderModelPreferences(config, storage);
|
|
266
260
|
return {
|
|
267
261
|
storage,
|
|
268
262
|
managedPiModels: () => openRouter.catalog(),
|
|
@@ -284,6 +278,9 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
284
278
|
// Clients receive its persisted snapshot via GET /api/models.
|
|
285
279
|
const modelCatalog = new ModelCatalogService(getModelRefreshOptions);
|
|
286
280
|
const configDir = resolveConfigDir(configPath);
|
|
281
|
+
const storageCleanup = createStorageCleanup(configDir, { uploads: storage.uploads,
|
|
282
|
+
uploadsActive: () => sessionRegistry.listSlim().some(session => (session.sessionKind === "structured" && session.status === "running") || (session.queuedMessages?.length ?? 0) > 0),
|
|
283
|
+
onUploadRemoved: invalidateUploadUsage });
|
|
287
284
|
const speech = new SpeechService(storage, configDir);
|
|
288
285
|
const localModels = new LocalModelSetupService({ configDir, speech, decisions,
|
|
289
286
|
decisionConfig: () => config.localDecision ?? { enabled: false, pythonPath: "", modelPath: "" },
|
|
@@ -348,7 +345,6 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
348
345
|
grok: "grokModels",
|
|
349
346
|
qoder: "qoderModels",
|
|
350
347
|
pi: "piModels",
|
|
351
|
-
gemini: "geminiModels",
|
|
352
348
|
}[provider];
|
|
353
349
|
return cache[key].map((entry) => entry.id);
|
|
354
350
|
};
|
|
@@ -454,6 +450,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
454
450
|
// Every /api family declares its scope in src/middleware/api-scope.ts; unlisted
|
|
455
451
|
// paths are admin-only for connected apps (default-deny).
|
|
456
452
|
app.use("/api", createApiScopeMiddleware());
|
|
453
|
+
app.use("/api", uploadReferenceGuard(storage.uploads));
|
|
457
454
|
app.use(userMemoryOperationLog(storage));
|
|
458
455
|
const userMemory = new UserMemoryService({
|
|
459
456
|
storage, config, free: openRouter,
|
|
@@ -583,11 +580,12 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
583
580
|
free: openRouter,
|
|
584
581
|
});
|
|
585
582
|
registerClaudeHistoryRoutes(app, { processes, storage });
|
|
586
|
-
registerWorkspaceRoutes(app, storage, sessionRegistry, { config });
|
|
583
|
+
registerWorkspaceRoutes(app, storage, sessionRegistry, { config, free: openRouter });
|
|
587
584
|
registerMissionRoutes(app, missions);
|
|
588
|
-
registerUploadRoutes(app, sessionRegistry);
|
|
585
|
+
registerUploadRoutes(app, sessionRegistry, { uploads: storage.uploads, temporaryRoot: configTemporaryRoot(configDir) });
|
|
589
586
|
registerSpeechRoutes(app, { speech, requireSessions, requireAdmin, polisher: speechPolisher });
|
|
590
587
|
registerLocalModelRoutes(app, { models: localModels, requireSessions, requireAdmin });
|
|
588
|
+
registerStorageCleanupRoutes(app, storageCleanup, requireAdmin);
|
|
591
589
|
app.post("/api/optimize-prompt", asyncRoute(async (req, res) => {
|
|
592
590
|
const body = (req.body ?? {});
|
|
593
591
|
const text = typeof body.text === "string" ? body.text : "";
|
|
@@ -633,6 +631,10 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
633
631
|
res.status(400).json({ error: "PTY 只能选择 CLI 工具。" });
|
|
634
632
|
return;
|
|
635
633
|
}
|
|
634
|
+
if (body.provider !== undefined && !isSessionProvider(body.provider)) {
|
|
635
|
+
res.status(400).json({ error: "请选择有效的执行工具。" });
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
636
638
|
const interactiveShell = body.shell === true;
|
|
637
639
|
// 连接应用只能启动 Provider CLI 会话(provider 字段 + 该 CLI 的标准可执行名)。
|
|
638
640
|
// 任意自定义命令字符串需要 admin。shell:true 是原生客户端“空白终端”(服务端配置的
|
|
@@ -1012,6 +1014,8 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1012
1014
|
// preferences, or stop a live execution. Startup recovery is already attached.
|
|
1013
1015
|
if (!testMode)
|
|
1014
1016
|
daemonMaintenance.start();
|
|
1017
|
+
if (!testMode && config.autoCleanupTemporaryFiles !== false)
|
|
1018
|
+
storageCleanup.start();
|
|
1015
1019
|
// ── Auto-update logic ──
|
|
1016
1020
|
// 同一目标版本自动安装失败后指数退避(30 分钟起,最长 6 小时),避免每轮轮询都重试
|
|
1017
1021
|
// 同一个必失败的安装并刷屏;用户主动触发(切通道/打开开关)时清零重试。
|
|
@@ -1136,6 +1140,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1136
1140
|
const teamShutdown = aiTeams.dispose();
|
|
1137
1141
|
await scheduledTasks.dispose();
|
|
1138
1142
|
await daemonMaintenance.stop();
|
|
1143
|
+
await storageCleanup.stop();
|
|
1139
1144
|
decisions.dispose();
|
|
1140
1145
|
openRouter.dispose();
|
|
1141
1146
|
await userMemory.dispose();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ServiceScope } from "./service-manager/index.js";
|
|
2
|
+
export declare function resolveServiceCommandScope(command: string, args: string[]): ServiceScope;
|
|
3
|
+
export interface ServicePrivilegeRuntime {
|
|
4
|
+
platform: NodeJS.Platform;
|
|
5
|
+
uid: number | undefined;
|
|
6
|
+
interactive: boolean;
|
|
7
|
+
node: string;
|
|
8
|
+
cli: string;
|
|
9
|
+
execArgv: string[];
|
|
10
|
+
run(file: string, args: readonly string[]): Promise<number>;
|
|
11
|
+
report(message: string): void;
|
|
12
|
+
}
|
|
13
|
+
/** null means continue in this process; a number is the elevated child's exit code.
|
|
14
|
+
* Re-enter the same CLI (not a PATH-resolved wand) before loading service code.
|
|
15
|
+
* Scope/config are pinned so sudo's HOME/PATH cannot target a different service.
|
|
16
|
+
*/
|
|
17
|
+
export declare function elevateServiceCommand(command: string, args: string[], configPath: string, scope: ServiceScope, runtime?: ServicePrivilegeRuntime): Promise<number | null>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { DEFAULT_SERVICE_SCOPE, detectInstalledScope } from "./service-manager/index.js";
|
|
4
|
+
import { runCommandInteractive } from "./run-command.js";
|
|
5
|
+
const MUTATING_SERVICE_COMMANDS = new Set([
|
|
6
|
+
"service:install", "service:uninstall", "service:start", "service:stop", "service:restart",
|
|
7
|
+
]);
|
|
8
|
+
export function resolveServiceCommandScope(command, args) {
|
|
9
|
+
if (args.includes("--user"))
|
|
10
|
+
return "user";
|
|
11
|
+
if (args.includes("--system"))
|
|
12
|
+
return "system";
|
|
13
|
+
return command === "service:install" ? DEFAULT_SERVICE_SCOPE : detectInstalledScope() ?? DEFAULT_SERVICE_SCOPE;
|
|
14
|
+
}
|
|
15
|
+
/** null means continue in this process; a number is the elevated child's exit code.
|
|
16
|
+
* Re-enter the same CLI (not a PATH-resolved wand) before loading service code.
|
|
17
|
+
* Scope/config are pinned so sudo's HOME/PATH cannot target a different service.
|
|
18
|
+
*/
|
|
19
|
+
export async function elevateServiceCommand(command, args, configPath, scope, runtime = {
|
|
20
|
+
platform: process.platform, uid: process.getuid?.(),
|
|
21
|
+
interactive: Boolean(process.stdin.isTTY && process.stderr.isTTY),
|
|
22
|
+
node: process.execPath, cli: process.argv[1], execArgv: process.execArgv,
|
|
23
|
+
run: runCommandInteractive, report: (message) => process.stderr.write(`[wand] ${message}\n`),
|
|
24
|
+
}) {
|
|
25
|
+
if (!MUTATING_SERVICE_COMMANDS.has(command) || scope !== "system" || runtime.uid === 0
|
|
26
|
+
|| !["darwin", "linux"].includes(runtime.platform))
|
|
27
|
+
return null;
|
|
28
|
+
const forwarded = [];
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
if (args[i] === "--user" || args[i] === "--system")
|
|
31
|
+
continue;
|
|
32
|
+
if (args[i] === "-c" || args[i] === "--config") {
|
|
33
|
+
i++;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
forwarded.push(args[i]);
|
|
37
|
+
}
|
|
38
|
+
if (runtime.interactive)
|
|
39
|
+
runtime.report("系统服务操作需要管理员权限,正在申请 sudo;请在终端输入密码(输入不会显示)。");
|
|
40
|
+
try {
|
|
41
|
+
const code = await runtime.run("sudo", [
|
|
42
|
+
...(runtime.interactive ? [] : ["-n"]), "--", runtime.node, ...runtime.execArgv,
|
|
43
|
+
path.resolve(runtime.cli), ...forwarded, "--system", "-c", path.resolve(configPath),
|
|
44
|
+
]);
|
|
45
|
+
if (code !== 0)
|
|
46
|
+
runtime.report(runtime.interactive
|
|
47
|
+
? "管理员授权或服务操作未完成;服务命令已停止,请查看上方错误。"
|
|
48
|
+
: "当前没有交互终端,无法输入 sudo 密码。请在终端运行同一命令以自动申请授权。");
|
|
49
|
+
return code;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
runtime.report(`无法申请 sudo:${error instanceof Error ? error.message : String(error)}`);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -11,6 +11,8 @@ export interface ServiceManager {
|
|
|
11
11
|
unitPath(scope: ServiceScope): string;
|
|
12
12
|
/** Command the installed unit runs, or null when absent/unreadable. */
|
|
13
13
|
readEntrypoint(scope: ServiceScope): string | null;
|
|
14
|
+
/** Config used by the installed Server; restart must drain this exact process. */
|
|
15
|
+
readConfigPath(scope: ServiceScope): string | null;
|
|
14
16
|
}
|
|
15
17
|
/** The manager for the current platform, or null where Wand has no service integration. */
|
|
16
18
|
export declare function currentServiceManager(platform?: NodeJS.Platform): ServiceManager | null;
|
|
@@ -20,3 +22,4 @@ export declare function detectInstalledServiceScopes(): ServiceScope[];
|
|
|
20
22
|
/** Auto-detect an installed scope; system preferred. */
|
|
21
23
|
export declare function detectInstalledScope(): ServiceScope | null;
|
|
22
24
|
export declare function readServiceEntrypoint(scope: ServiceScope): string | null;
|
|
25
|
+
export declare function readServiceConfigPath(scope: ServiceScope): string | null;
|
|
@@ -34,3 +34,6 @@ export function detectInstalledScope() {
|
|
|
34
34
|
export function readServiceEntrypoint(scope) {
|
|
35
35
|
return currentServiceManager()?.readEntrypoint(scope) ?? null;
|
|
36
36
|
}
|
|
37
|
+
export function readServiceConfigPath(scope) {
|
|
38
|
+
return currentServiceManager()?.readConfigPath(scope) ?? null;
|
|
39
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { ServiceManager, ServiceScope } from "./index.js";
|
|
|
2
2
|
export declare class LaunchdServiceManager implements ServiceManager {
|
|
3
3
|
readonly platform: "darwin";
|
|
4
4
|
unitPath(scope: ServiceScope): string;
|
|
5
|
+
readConfigPath(scope: ServiceScope): string | null;
|
|
5
6
|
readEntrypoint(scope: ServiceScope): string | null;
|
|
6
7
|
}
|
|
7
8
|
/** PID of the loaded com.wand.web job in a launchd domain ("system" or "gui/<uid>"), or null. */
|
|
@@ -8,6 +8,21 @@ export class LaunchdServiceManager {
|
|
|
8
8
|
? path.join(os.homedir(), "Library/LaunchAgents/com.wand.web.plist")
|
|
9
9
|
: "/Library/LaunchDaemons/com.wand.web.plist";
|
|
10
10
|
}
|
|
11
|
+
readConfigPath(scope) {
|
|
12
|
+
const result = runCommandSync("plutil", ["-extract", "ProgramArguments", "json", "-o", "-", this.unitPath(scope)], { timeout: 5_000 });
|
|
13
|
+
if (result.status !== 0)
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
const args = JSON.parse(result.stdout);
|
|
17
|
+
if (!Array.isArray(args) || !args.every((arg) => typeof arg === "string"))
|
|
18
|
+
return null;
|
|
19
|
+
const at = args.findIndex((arg) => arg === "-c" || arg === "--config");
|
|
20
|
+
return at >= 0 && args[at + 1] ? path.resolve(args[at + 1]) : null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
11
26
|
readEntrypoint(scope) {
|
|
12
27
|
const result = runCommandSync("plutil", ["-extract", "ProgramArguments.1", "raw", "-o", "-", this.unitPath(scope)], { timeout: 5_000 });
|
|
13
28
|
return result.status === 0 ? (result.stdout || "").trim() || null : null;
|
|
@@ -2,5 +2,6 @@ import type { ServiceManager, ServiceScope } from "./index.js";
|
|
|
2
2
|
export declare class SystemdServiceManager implements ServiceManager {
|
|
3
3
|
readonly platform: "linux";
|
|
4
4
|
unitPath(scope: ServiceScope): string;
|
|
5
|
+
readConfigPath(scope: ServiceScope): string | null;
|
|
5
6
|
readEntrypoint(scope: ServiceScope): string | null;
|
|
6
7
|
}
|
|
@@ -8,6 +8,17 @@ export class SystemdServiceManager {
|
|
|
8
8
|
? path.join(os.homedir(), ".config/systemd/user/wand.service")
|
|
9
9
|
: "/etc/systemd/system/wand.service";
|
|
10
10
|
}
|
|
11
|
+
readConfigPath(scope) {
|
|
12
|
+
try {
|
|
13
|
+
const execStart = readFileSync(this.unitPath(scope), "utf8").split("\n").find((line) => line.startsWith("ExecStart="));
|
|
14
|
+
const match = execStart?.match(/(?:^|\s)(?:-c|--config)(?:=|\s+)(?:"([^"\\]+)"|'([^'\\]+)'|([^\s"'\\]+))(?=\s|$)/);
|
|
15
|
+
const config = match?.[1] ?? match?.[2] ?? match?.[3];
|
|
16
|
+
return config ? path.resolve(config) : null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
11
22
|
readEntrypoint(scope) {
|
|
12
23
|
try {
|
|
13
24
|
const execStart = readFileSync(this.unitPath(scope), "utf8").split("\n").find((line) => line.startsWith("ExecStart="));
|
|
@@ -8,13 +8,24 @@ import { inferProviderFromCommand, inferProviderFromRunner } from "./session-pro
|
|
|
8
8
|
* still identify Codex through structuredState, runner, or command.
|
|
9
9
|
*/
|
|
10
10
|
export function resolveSessionProvider(snapshot) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
for (const provider of [snapshot.provider, snapshot.structuredState?.provider]) {
|
|
12
|
+
if (provider == null)
|
|
13
|
+
continue;
|
|
14
|
+
if (!isSessionProvider(provider))
|
|
15
|
+
throw new Error(`不支持的会话 provider:${String(provider)}`);
|
|
16
|
+
return provider;
|
|
17
|
+
}
|
|
16
18
|
const runner = snapshot.runner ?? snapshot.structuredState?.runner;
|
|
17
|
-
|
|
19
|
+
if (runner && runner !== "pty") {
|
|
20
|
+
const provider = inferProviderFromRunner(runner);
|
|
21
|
+
if (!provider)
|
|
22
|
+
throw new Error(`不支持的会话 runner:${String(runner)}`);
|
|
23
|
+
return provider;
|
|
24
|
+
}
|
|
25
|
+
const provider = inferProviderFromCommand(snapshot.command);
|
|
26
|
+
if (!provider)
|
|
27
|
+
throw new Error("不支持的会话命令,无法选择 AI provider。");
|
|
28
|
+
return provider;
|
|
18
29
|
}
|
|
19
30
|
function normalizeModel(value) {
|
|
20
31
|
const model = value?.trim();
|
|
@@ -7,7 +7,9 @@ export interface SessionCompletionState {
|
|
|
7
7
|
export declare function createSessionCompletionViewIntent(): {
|
|
8
8
|
open(id: string): void;
|
|
9
9
|
clear(): void;
|
|
10
|
+
loaded(id: string): void;
|
|
10
11
|
isOpen(id: string): boolean;
|
|
12
|
+
isLoaded(id: string): boolean;
|
|
11
13
|
};
|
|
12
14
|
/** Delayed HTTP/WS projections may advance generations, but must never move them backwards. */
|
|
13
15
|
export declare function mergeSessionCompletionState(current: SessionCompletionState | null | undefined, incoming: SessionCompletionState): SessionCompletionState;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/** Explicit navigation, not a list refresh choosing its preferred/default session. */
|
|
2
2
|
export function createSessionCompletionViewIntent() {
|
|
3
3
|
let openedId = null;
|
|
4
|
+
let loaded = false;
|
|
4
5
|
return {
|
|
5
|
-
open(id) { openedId = id; },
|
|
6
|
-
clear() { openedId = null; },
|
|
6
|
+
open(id) { openedId = id; loaded = false; },
|
|
7
|
+
clear() { openedId = null; loaded = false; },
|
|
8
|
+
loaded(id) { if (openedId === id)
|
|
9
|
+
loaded = true; },
|
|
7
10
|
isOpen(id) { return openedId === id; },
|
|
11
|
+
isLoaded(id) { return openedId === id && loaded; },
|
|
8
12
|
};
|
|
9
13
|
}
|
|
10
14
|
/** Delayed HTTP/WS projections may advance generations, but must never move them backwards. */
|
package/dist/session-logger.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, rmSync, appendFileSync, writeFileSync, readFileSync, existsSync, statSync, renameSync, unlinkSync, openSync, readSync, closeSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
+
import { appendBoundedDiagnostic } from "./diagnostic-logs.js";
|
|
4
5
|
// ── Constants ──
|
|
5
6
|
/** Max size for a single PTY log file before rotation (50 MB) */
|
|
6
7
|
const PTY_LOG_MAX_SIZE = 50 * 1024 * 1024;
|
|
@@ -52,7 +53,12 @@ export class SessionLogger {
|
|
|
52
53
|
this.totalBufferMaxBytes = positiveInteger(options.totalBufferMaxBytes, DEFAULT_TOTAL_BUFFER_MAX_BYTES);
|
|
53
54
|
this.ptyLogMaxBytes = positiveInteger(options.ptyLogMaxBytes, PTY_LOG_MAX_SIZE);
|
|
54
55
|
this.ptyLogMaxRotations = positiveInteger(options.ptyLogMaxRotations, PTY_LOG_MAX_ROTATIONS);
|
|
55
|
-
this.appendFile = options.appendFile ?? ((filePath, data) =>
|
|
56
|
+
this.appendFile = options.appendFile ?? ((filePath, data) => {
|
|
57
|
+
if (["structured-stdout.log", "structured-stderr.log", "structured-spawns.jsonl"].includes(path.basename(filePath)))
|
|
58
|
+
appendBoundedDiagnostic(filePath, data);
|
|
59
|
+
else
|
|
60
|
+
appendFileSync(filePath, data);
|
|
61
|
+
});
|
|
56
62
|
try {
|
|
57
63
|
mkdirSync(this.baseDir, { recursive: true });
|
|
58
64
|
}
|
package/dist/session-model.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { type ProviderModelField } from "./config.js";
|
|
1
2
|
import type { SessionProvider, WandConfig } from "./types.js";
|
|
3
|
+
import type { WandStorage } from "./storage.js";
|
|
4
|
+
/** Read raw live preferences without the configured-model trim/default projection. */
|
|
5
|
+
export declare function readProviderModelPreferences(config: Pick<WandConfig, ProviderModelField>, storage: Pick<WandStorage, "getPreference">): Record<SessionProvider, string>;
|
|
2
6
|
/** The repeated `group selector || provider default` tail. Callers keep their own explicit-model precedence. */
|
|
3
7
|
export declare function fallbackProviderModel(config: WandConfig, provider: SessionProvider): string | undefined;
|
|
4
8
|
/** Employee and conversation creates treat blank and `default` as the configured fallback. */
|
package/dist/session-model.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import { getDefaultModelForProvider } from "./config.js";
|
|
1
|
+
import { getDefaultModelForProvider, PROVIDER_MODEL_PREFERENCE_FIELDS } from "./config.js";
|
|
2
2
|
import { defaultModelGroupSelector } from "./model-groups.js";
|
|
3
|
+
import { SESSION_PROVIDERS } from "./session-provider.js";
|
|
4
|
+
/** Read raw live preferences without the configured-model trim/default projection. */
|
|
5
|
+
export function readProviderModelPreferences(config, storage) {
|
|
6
|
+
const models = {};
|
|
7
|
+
for (const provider of SESSION_PROVIDERS) {
|
|
8
|
+
const field = PROVIDER_MODEL_PREFERENCE_FIELDS[provider];
|
|
9
|
+
models[provider] = storage.getPreference(`pref:${field}`, config[field] ?? "");
|
|
10
|
+
}
|
|
11
|
+
return models;
|
|
12
|
+
}
|
|
3
13
|
/** The repeated `group selector || provider default` tail. Callers keep their own explicit-model precedence. */
|
|
4
14
|
export function fallbackProviderModel(config, provider) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|| undefined;
|
|
15
|
+
const defaultModel = getDefaultModelForProvider(config, provider);
|
|
16
|
+
return defaultModelGroupSelector(config.modelGroups, provider, defaultModel) || defaultModel || undefined;
|
|
8
17
|
}
|
|
9
18
|
/** Employee and conversation creates treat blank and `default` as the configured fallback. */
|
|
10
19
|
export function resolveExplicitOrFallbackModel(config, provider, explicit) {
|
|
@@ -3,7 +3,7 @@ import type { WandStorage } from "./storage.js";
|
|
|
3
3
|
import type { StructuredSessionManager } from "./structured-session-manager.js";
|
|
4
4
|
import type { ExecutionMode, SessionSnapshot } from "./types.js";
|
|
5
5
|
export type SessionOwner = "structured" | "pty" | "storage";
|
|
6
|
-
export type ProviderHistoryDeleter = Pick<ProcessManager, "deleteClaudeHistoryFiles" | "deleteCodexHistoryFiles" | "deleteOpenCodeHistorySessions" | "deleteQoderHistoryFiles" | "deletePiHistoryFiles" | "deleteGrokHistoryFiles"
|
|
6
|
+
export type ProviderHistoryDeleter = Pick<ProcessManager, "deleteClaudeHistoryFiles" | "deleteCodexHistoryFiles" | "deleteOpenCodeHistorySessions" | "deleteQoderHistoryFiles" | "deletePiHistoryFiles" | "deleteGrokHistoryFiles">;
|
|
7
7
|
export declare function deleteProviderNativeHistory(deleter: ProviderHistoryDeleter, provider: string, providerSessionId: string, cwd: string): void;
|
|
8
8
|
type HiddenSessionStore = Pick<WandStorage, "getConfigValue" | "setConfigValue">;
|
|
9
9
|
/** The owner deletes the managed session first; native history/tombstones have one implementation. */
|
package/dist/session-registry.js
CHANGED