@co0ontty/wand 4.63.0 → 4.64.0-beta.g3402a64
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/dist/build-info.d.ts +18 -0
- package/dist/build-info.js +13 -0
- package/dist/build-info.json +4 -4
- package/dist/config.js +6 -11
- package/dist/env-utils.d.ts +5 -0
- package/dist/env-utils.js +4 -0
- package/dist/git-quick-commit.js +7 -11
- package/dist/ios-ota.js +8 -29
- package/dist/layout-tree.d.ts +6 -0
- package/dist/layout-tree.js +9 -0
- package/dist/missions.js +2 -2
- package/dist/models.js +2 -5
- package/dist/path-repair.js +2 -1
- package/dist/process-manager.js +10 -20
- package/dist/provider-cli-updater.js +3 -6
- package/dist/pty-shell-launch.js +3 -5
- package/dist/server-app-connect.js +11 -46
- package/dist/server-github-routes.js +12 -11
- package/dist/server-local-preview-routes.js +6 -1
- package/dist/server-request.d.ts +4 -2
- package/dist/server-request.js +25 -4
- package/dist/server-session-routes.js +14 -78
- package/dist/server-settings-routes.d.ts +2 -6
- package/dist/server-settings-routes.js +3 -8
- package/dist/server-task-routes.d.ts +9 -6
- package/dist/server-task-routes.js +50 -43
- package/dist/server-update-routes.d.ts +2 -5
- package/dist/server-update-routes.js +42 -9
- package/dist/server-workspace-routes.js +4 -12
- package/dist/server.js +33 -99
- package/dist/session-ai-context.js +6 -38
- package/dist/session-provider.d.ts +16 -0
- package/dist/session-provider.js +49 -0
- package/dist/session-registry.d.ts +10 -0
- package/dist/session-registry.js +41 -14
- package/dist/session-topic.js +3 -11
- package/dist/shell-quote.d.ts +9 -0
- package/dist/shell-quote.js +13 -0
- package/dist/signal-utils.d.ts +10 -0
- package/dist/signal-utils.js +38 -0
- package/dist/storage.js +17 -55
- package/dist/structured-client-protocol.js +4 -8
- package/dist/structured-codex-protocol.js +1 -6
- package/dist/structured-content.d.ts +2 -0
- package/dist/structured-content.js +6 -0
- package/dist/structured-exec-host.d.ts +0 -2
- package/dist/structured-exec-host.js +2 -11
- package/dist/structured-exec-pump.js +2 -12
- package/dist/structured-grok-adapter.js +1 -5
- package/dist/structured-opencode-adapter.js +1 -5
- package/dist/structured-pi-adapter.js +9 -11
- package/dist/structured-provider-common.d.ts +11 -3
- package/dist/structured-provider-common.js +16 -23
- package/dist/structured-qoder-adapter.js +3 -8
- package/dist/structured-session-manager.js +6 -17
- package/dist/task-title.js +4 -12
- package/dist/task-types.d.ts +23 -1
- package/dist/task-types.js +26 -0
- package/dist/terminal-daemon-protocol.d.ts +0 -6
- package/dist/terminal-daemon-server.js +1 -9
- package/dist/text-utils.d.ts +7 -0
- package/dist/text-utils.js +15 -0
- package/dist/tui/attach.js +17 -93
- package/dist/tui/commands.d.ts +2 -0
- package/dist/tui/commands.js +3 -2
- package/dist/tui/index.js +28 -115
- package/dist/tui/runtime-utils.d.ts +7 -0
- package/dist/tui/runtime-utils.js +33 -0
- package/dist/tui/service-actions.d.ts +31 -0
- package/dist/tui/service-actions.js +86 -0
- package/dist/tui/snapshot.js +1 -8
- package/dist/update-helper.js +6 -35
- package/dist/web-ui/content/scripts.js +397 -497
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +7210 -0
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/web-ui/index.js +3 -3
- package/dist/web-ui/provider-identity.d.ts +4 -0
- package/dist/web-ui/provider-identity.js +9 -0
- package/dist/web-ui/styles.js +24 -7
- package/dist/workspace-binding.d.ts +2 -0
- package/dist/workspace-binding.js +4 -3
- package/package.json +9 -12
package/dist/storage.js
CHANGED
|
@@ -4,6 +4,10 @@ import path from "node:path";
|
|
|
4
4
|
import { DatabaseSync } from "node:sqlite";
|
|
5
5
|
import { GLOBAL_WORKSPACE_ID } from "./types.js";
|
|
6
6
|
import { normalizeSessionDirectory } from "./session-directory-tree.js";
|
|
7
|
+
import { inferProviderFromCommand, inferProviderFromRunner, isSessionProvider } from "./session-provider.js";
|
|
8
|
+
import { normalizeWandTaskAgentMode } from "./task-types.js";
|
|
9
|
+
import { firstLayoutTabId } from "./layout-tree.js";
|
|
10
|
+
import { isThinkingEffort } from "./structured-provider-common.js";
|
|
7
11
|
import { DEFAULT_PASSWORD_VAULT_ID, DEFAULT_PASSWORD_VAULT_NAME, decryptVaultSecret, encryptVaultSecret, itemMatchesFilter, normalizePasswordItemInput, normalizeVaultName, nowIso, } from "./password-manager.js";
|
|
8
12
|
function safeJsonParse(raw) {
|
|
9
13
|
if (!raw)
|
|
@@ -26,13 +30,15 @@ export function parseWandTaskAgent(raw) {
|
|
|
26
30
|
const provider = value.provider;
|
|
27
31
|
const model = value.model;
|
|
28
32
|
const thinkingEffort = value.thinkingEffort;
|
|
29
|
-
if (provider
|
|
33
|
+
if (!isSessionProvider(provider))
|
|
30
34
|
return null;
|
|
31
35
|
if (typeof model !== "string" || !model.trim() || model.trim().length > 128)
|
|
32
36
|
return null;
|
|
33
37
|
if (thinkingEffort !== "off" && thinkingEffort !== "standard" && thinkingEffort !== "deep" && thinkingEffort !== "max")
|
|
34
38
|
return null;
|
|
35
|
-
|
|
39
|
+
// mode 是后加列:历史行没有该字段时按标准模式读取,不因此整条配置退化成 null。
|
|
40
|
+
const mode = normalizeWandTaskAgentMode(provider, value.mode);
|
|
41
|
+
return { provider, model: model.trim(), thinkingEffort, mode };
|
|
36
42
|
}
|
|
37
43
|
/** `wand_milestones` 行 → 领域对象;名字必须非空,脏行直接跳过。 */
|
|
38
44
|
function mapWandMilestoneRow(row) {
|
|
@@ -120,13 +126,6 @@ function parseApprovalStats(value) {
|
|
|
120
126
|
total: value.total,
|
|
121
127
|
};
|
|
122
128
|
}
|
|
123
|
-
function isThinkingEffort(value) {
|
|
124
|
-
return value === "off"
|
|
125
|
-
|| value === "standard"
|
|
126
|
-
|| value === "deep"
|
|
127
|
-
|| value === "max"
|
|
128
|
-
|| (typeof value === "string" && /^codex:[a-z0-9][a-z0-9_-]{0,31}$/.test(value));
|
|
129
|
-
}
|
|
130
129
|
function serializeSessionOptions(snapshot) {
|
|
131
130
|
const options = {
|
|
132
131
|
schemaVersion: SESSION_OPTIONS_SCHEMA_VERSION,
|
|
@@ -224,37 +223,6 @@ function parseQueuedMessageSkills(raw, queueLength) {
|
|
|
224
223
|
: [];
|
|
225
224
|
});
|
|
226
225
|
}
|
|
227
|
-
function inferSessionProvider(row) {
|
|
228
|
-
if (row.provider === "claude" || row.provider === "codex" || row.provider === "opencode" || row.provider === "grok" || row.provider === "qoder" || row.provider === "pi") {
|
|
229
|
-
return row.provider;
|
|
230
|
-
}
|
|
231
|
-
if (row.runner === "claude-cli" || row.runner === "claude-cli-print") {
|
|
232
|
-
return "claude";
|
|
233
|
-
}
|
|
234
|
-
if (row.runner === "codex-cli-exec") {
|
|
235
|
-
return "codex";
|
|
236
|
-
}
|
|
237
|
-
if (row.runner === "opencode-cli-run") {
|
|
238
|
-
return "opencode";
|
|
239
|
-
}
|
|
240
|
-
if (row.runner === "grok-cli-headless")
|
|
241
|
-
return "grok";
|
|
242
|
-
if (row.runner === "qoder-cli-print")
|
|
243
|
-
return "qoder";
|
|
244
|
-
if (row.runner === "pi-cli-json")
|
|
245
|
-
return "pi";
|
|
246
|
-
if (/^codex\b/i.test(row.command.trim()))
|
|
247
|
-
return "codex";
|
|
248
|
-
if (/^opencode\b/i.test(row.command.trim()))
|
|
249
|
-
return "opencode";
|
|
250
|
-
if (/^grok\b/i.test(row.command.trim()))
|
|
251
|
-
return "grok";
|
|
252
|
-
if (/^qodercli\b/i.test(row.command.trim()))
|
|
253
|
-
return "qoder";
|
|
254
|
-
if (/^pi\b/i.test(row.command.trim()))
|
|
255
|
-
return "pi";
|
|
256
|
-
return /^claude\b/i.test(row.command.trim()) ? "claude" : undefined;
|
|
257
|
-
}
|
|
258
226
|
function parseWorktreeInfo(raw) {
|
|
259
227
|
const parsed = safeJsonParse(raw);
|
|
260
228
|
if (parsed && typeof parsed.branch === "string" && typeof parsed.path === "string") {
|
|
@@ -265,11 +233,8 @@ function parseWorktreeInfo(raw) {
|
|
|
265
233
|
function parseWorktreeMergeInfo(raw) {
|
|
266
234
|
return safeJsonParse(raw);
|
|
267
235
|
}
|
|
268
|
-
function
|
|
269
|
-
return
|
|
270
|
-
}
|
|
271
|
-
function serializeWorktreeInfo(info) {
|
|
272
|
-
return info ? JSON.stringify(info) : null;
|
|
236
|
+
function serializeJsonOrNull(value) {
|
|
237
|
+
return value ? JSON.stringify(value) : null;
|
|
273
238
|
}
|
|
274
239
|
function normalizeWorktreeMergeStatus(raw) {
|
|
275
240
|
if (raw === "ready" || raw === "checking" || raw === "merging" || raw === "merged" || raw === "failed") {
|
|
@@ -311,11 +276,6 @@ function mapWorkspaceTaskWorktree(raw) {
|
|
|
311
276
|
return null;
|
|
312
277
|
return parsed;
|
|
313
278
|
}
|
|
314
|
-
function firstLayoutTabId(node) {
|
|
315
|
-
if (node.type === "pane")
|
|
316
|
-
return node.tabs[node.active]?.id ?? node.tabs[0]?.id;
|
|
317
|
-
return firstLayoutTabId(node.children[0]) ?? firstLayoutTabId(node.children[1]);
|
|
318
|
-
}
|
|
319
279
|
/** 读取旧版单棵分屏树时就地包成一个工作窗口,避免升级后丢失布局。 */
|
|
320
280
|
function mapWorkspaceTaskLayout(raw) {
|
|
321
281
|
const parsed = safeJsonParse(raw);
|
|
@@ -427,9 +387,9 @@ function sessionPersistValues(snapshot) {
|
|
|
427
387
|
snapshot.resumedFromSessionId ?? null,
|
|
428
388
|
snapshot.autoRecovered ? 1 : 0,
|
|
429
389
|
snapshot.worktreeEnabled ? 1 : 0,
|
|
430
|
-
|
|
390
|
+
serializeJsonOrNull(snapshot.worktree),
|
|
431
391
|
snapshot.worktreeMergeStatus ?? null,
|
|
432
|
-
|
|
392
|
+
serializeJsonOrNull(snapshot.worktreeMergeInfo),
|
|
433
393
|
snapshot.title ?? null,
|
|
434
394
|
snapshot.description ?? null,
|
|
435
395
|
serializeSessionOptions(snapshot),
|
|
@@ -460,9 +420,9 @@ function sessionRuntimeMetadataValues(snapshot) {
|
|
|
460
420
|
snapshot.resumedFromSessionId ?? null,
|
|
461
421
|
snapshot.autoRecovered ? 1 : 0,
|
|
462
422
|
snapshot.worktreeEnabled ? 1 : 0,
|
|
463
|
-
|
|
423
|
+
serializeJsonOrNull(snapshot.worktree),
|
|
464
424
|
snapshot.worktreeMergeStatus ?? null,
|
|
465
|
-
|
|
425
|
+
serializeJsonOrNull(snapshot.worktreeMergeInfo),
|
|
466
426
|
snapshot.title ?? null,
|
|
467
427
|
snapshot.description ?? null,
|
|
468
428
|
serializeSessionOptions(snapshot),
|
|
@@ -470,7 +430,9 @@ function sessionRuntimeMetadataValues(snapshot) {
|
|
|
470
430
|
];
|
|
471
431
|
}
|
|
472
432
|
function mapSessionCore(row) {
|
|
473
|
-
const provider =
|
|
433
|
+
const provider = isSessionProvider(row.provider)
|
|
434
|
+
? row.provider
|
|
435
|
+
: (inferProviderFromRunner(row.runner) ?? inferProviderFromCommand(row.command));
|
|
474
436
|
const sessionOptions = parseSessionOptions(row.session_options);
|
|
475
437
|
const queuedMessages = parseQueuedMessages(row.queued_messages);
|
|
476
438
|
return {
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
+
import { asRecord } from "./structured-content.js";
|
|
1
2
|
export const WAND_PROTOCOL_VERSION = 2;
|
|
2
|
-
function record(value) {
|
|
3
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
4
|
-
? value
|
|
5
|
-
: null;
|
|
6
|
-
}
|
|
7
3
|
function text(value) {
|
|
8
4
|
return typeof value === "string" && value ? value : undefined;
|
|
9
5
|
}
|
|
@@ -24,11 +20,11 @@ function questionsFromInput(input) {
|
|
|
24
20
|
const rawQuestions = arrayValue(input.questions) ?? [];
|
|
25
21
|
const questions = [];
|
|
26
22
|
for (const rawQuestion of rawQuestions) {
|
|
27
|
-
const question =
|
|
23
|
+
const question = asRecord(rawQuestion);
|
|
28
24
|
if (!question)
|
|
29
25
|
continue;
|
|
30
26
|
const options = (arrayValue(question.options) ?? []).flatMap((rawOption, index) => {
|
|
31
|
-
const option =
|
|
27
|
+
const option = asRecord(rawOption);
|
|
32
28
|
if (!option)
|
|
33
29
|
return [];
|
|
34
30
|
return [{
|
|
@@ -72,7 +68,7 @@ function tasksFromSegment(messages, start, end) {
|
|
|
72
68
|
if (latestTodoWrite) {
|
|
73
69
|
const todos = arrayValue(latestTodoWrite.input.todos) ?? arrayValue(latestTodoWrite.input.plan) ?? [];
|
|
74
70
|
const items = todos.flatMap((rawTodo, index) => {
|
|
75
|
-
const todo =
|
|
71
|
+
const todo = asRecord(rawTodo);
|
|
76
72
|
if (!todo)
|
|
77
73
|
return [];
|
|
78
74
|
return [{
|
|
@@ -2,12 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { getErrorMessage } from "./error-utils.js";
|
|
5
|
-
import { normalizeStructuredToolResultContent } from "./structured-content.js";
|
|
6
|
-
function asRecord(value) {
|
|
7
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
8
|
-
? value
|
|
9
|
-
: null;
|
|
10
|
-
}
|
|
5
|
+
import { asRecord, normalizeStructuredToolResultContent } from "./structured-content.js";
|
|
11
6
|
function getString(value) {
|
|
12
7
|
return typeof value === "string" ? value : "";
|
|
13
8
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** 把 unknown 收敛成普通对象;数组 / null / 原始值都返回 null。 */
|
|
2
|
+
export declare function asRecord(value: unknown): Record<string, unknown> | null;
|
|
1
3
|
/** Preserve both Responses content parts and arbitrary structured tool output. */
|
|
2
4
|
export declare function normalizeStructuredToolResultContent(content: unknown): string | Array<{
|
|
3
5
|
type: string;
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/** 把 unknown 收敛成普通对象;数组 / null / 原始值都返回 null。 */
|
|
2
|
+
export function asRecord(value) {
|
|
3
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
4
|
+
? value
|
|
5
|
+
: null;
|
|
6
|
+
}
|
|
1
7
|
/** Preserve both Responses content parts and arbitrary structured tool output. */
|
|
2
8
|
export function normalizeStructuredToolResultContent(content) {
|
|
3
9
|
if (typeof content === "string")
|
|
@@ -83,5 +83,3 @@ export declare class InProcessStructuredExecHost implements StructuredExecHost {
|
|
|
83
83
|
listRuns(): Promise<StructuredRunState[]>;
|
|
84
84
|
forgetRun(runId: string): void;
|
|
85
85
|
}
|
|
86
|
-
/** Map a NodeJS.Signals name to the numeric value used by waitpid-style APIs. */
|
|
87
|
-
export declare function osSignalNumber(signal: NodeJS.Signals): number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { StringDecoder } from "node:string_decoder";
|
|
4
|
+
import { signalNumberFromName } from "./signal-utils.js";
|
|
4
5
|
/**
|
|
5
6
|
* Ownership seam for structured CLI runs, mirroring TerminalHost for PTYs.
|
|
6
7
|
* Persistent adapters (terminald) outlive web restarts; the in-process adapter
|
|
@@ -74,7 +75,7 @@ class InProcessStructuredExecProcess {
|
|
|
74
75
|
this.child.on("close", (code, signal) => {
|
|
75
76
|
settleExit({
|
|
76
77
|
exitCode: code,
|
|
77
|
-
signal: signal === null || signal === undefined ? null :
|
|
78
|
+
signal: signal === null || signal === undefined ? null : signalNumberFromName(signal),
|
|
78
79
|
});
|
|
79
80
|
});
|
|
80
81
|
this.child.on("error", () => settleExit({ exitCode: null, signal: null }));
|
|
@@ -140,13 +141,3 @@ export class InProcessStructuredExecHost {
|
|
|
140
141
|
wrapped.interrupt();
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
|
-
/** Map a NodeJS.Signals name to the numeric value used by waitpid-style APIs. */
|
|
144
|
-
export function osSignalNumber(signal) {
|
|
145
|
-
const table = {
|
|
146
|
-
SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGILL: 4, SIGTRAP: 5, SIGABRT: 6,
|
|
147
|
-
SIGBUS: 7, SIGFPE: 8, SIGKILL: 9, SIGUSR1: 10, SIGSEGV: 11, SIGUSR2: 12,
|
|
148
|
-
SIGPIPE: 13, SIGALRM: 14, SIGTERM: 15, SIGCHLD: 17, SIGCONT: 18,
|
|
149
|
-
SIGSTOP: 19, SIGTSTP: 20,
|
|
150
|
-
};
|
|
151
|
-
return table[signal] ?? 0;
|
|
152
|
-
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createUtf8TextDecoder, } from "./structured-exec-host.js";
|
|
3
|
+
import { signalNameFromNumber } from "./signal-utils.js";
|
|
3
4
|
export function startStructuredCli(options) {
|
|
4
5
|
const spawnedAt = new Date().toISOString();
|
|
5
6
|
const observer = options.observer;
|
|
@@ -117,17 +118,6 @@ function wireRemoteHandle(handle, onStdout, onStderr, finish) {
|
|
|
117
118
|
onStderr(event.data);
|
|
118
119
|
});
|
|
119
120
|
handle.onExit((event) => {
|
|
120
|
-
finish(event.exitCode,
|
|
121
|
+
finish(event.exitCode, signalNameFromNumber(event.signal));
|
|
121
122
|
});
|
|
122
123
|
}
|
|
123
|
-
function numericToSignalName(signal) {
|
|
124
|
-
if (signal === null || signal === 0)
|
|
125
|
-
return null;
|
|
126
|
-
const names = {
|
|
127
|
-
1: "SIGHUP", 2: "SIGINT", 3: "SIGQUIT", 4: "SIGILL", 5: "SIGTRAP", 6: "SIGABRT",
|
|
128
|
-
7: "SIGBUS", 8: "SIGFPE", 9: "SIGKILL", 10: "SIGUSR1", 11: "SIGSEGV", 12: "SIGUSR2",
|
|
129
|
-
13: "SIGPIPE", 14: "SIGALRM", 15: "SIGTERM", 17: "SIGCHLD", 18: "SIGCONT",
|
|
130
|
-
19: "SIGSTOP", 20: "SIGTSTP",
|
|
131
|
-
};
|
|
132
|
-
return names[signal] ?? "SIGTERM";
|
|
133
|
-
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { startStructuredCli } from "./structured-exec-pump.js";
|
|
3
|
+
import { asRecord } from "./structured-content.js";
|
|
3
4
|
import { thinkingEffortToGrokEffort } from "./structured-provider-common.js";
|
|
4
|
-
function asRecord(value) {
|
|
5
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
6
|
-
? value
|
|
7
|
-
: null;
|
|
8
|
-
}
|
|
9
5
|
function grokText(value) {
|
|
10
6
|
if (typeof value === "string")
|
|
11
7
|
return value;
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { startStructuredCli } from "./structured-exec-pump.js";
|
|
4
|
+
import { asRecord } from "./structured-content.js";
|
|
4
5
|
import { thinkingEffortToOpenCodeVariant } from "./structured-provider-common.js";
|
|
5
|
-
function asRecord(value) {
|
|
6
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
7
|
-
? value
|
|
8
|
-
: null;
|
|
9
|
-
}
|
|
10
6
|
function extractStructuredText(value) {
|
|
11
7
|
if (typeof value === "string")
|
|
12
8
|
return value;
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { startStructuredCli } from "./structured-exec-pump.js";
|
|
3
|
+
import { asRecord } from "./structured-content.js";
|
|
3
4
|
import { thinkingEffortToPiLevel } from "./structured-provider-common.js";
|
|
4
|
-
function record(value) {
|
|
5
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
6
|
-
}
|
|
7
5
|
function textContent(value) {
|
|
8
6
|
if (typeof value === "string")
|
|
9
7
|
return value;
|
|
10
8
|
if (Array.isArray(value))
|
|
11
9
|
return value.map((item) => textContent(item)).filter(Boolean).join("\n");
|
|
12
|
-
const item =
|
|
10
|
+
const item = asRecord(value);
|
|
13
11
|
if (!item)
|
|
14
12
|
return "";
|
|
15
13
|
if (item.type === "text" && typeof item.text === "string")
|
|
@@ -36,8 +34,8 @@ export function piToolName(name) {
|
|
|
36
34
|
function applyPiAssistantMessage(state, message) {
|
|
37
35
|
if (typeof message.model === "string")
|
|
38
36
|
state.model = message.model;
|
|
39
|
-
const usage =
|
|
40
|
-
const cost =
|
|
37
|
+
const usage = asRecord(message.usage);
|
|
38
|
+
const cost = asRecord(usage?.cost);
|
|
41
39
|
if (usage) {
|
|
42
40
|
state.usage = {
|
|
43
41
|
inputTokens: typeof usage.input === "number" ? usage.input : 0,
|
|
@@ -51,7 +49,7 @@ function applyPiAssistantMessage(state, message) {
|
|
|
51
49
|
const thinkings = [];
|
|
52
50
|
const parts = Array.isArray(message.content) ? message.content : [];
|
|
53
51
|
for (const part of parts) {
|
|
54
|
-
const item =
|
|
52
|
+
const item = asRecord(part);
|
|
55
53
|
if (!item)
|
|
56
54
|
continue;
|
|
57
55
|
if (item.type === "text") {
|
|
@@ -93,7 +91,7 @@ export function applyPiEvent(state, event) {
|
|
|
93
91
|
if (event.type === "session" && typeof event.id === "string")
|
|
94
92
|
state.sessionId = event.id;
|
|
95
93
|
if (event.type === "message_update") {
|
|
96
|
-
const update =
|
|
94
|
+
const update = asRecord(event.assistantMessageEvent);
|
|
97
95
|
const delta = typeof update?.delta === "string" ? update.delta : "";
|
|
98
96
|
if (update?.type === "text_delta" && delta) {
|
|
99
97
|
const last = state.blocks.at(-1);
|
|
@@ -130,20 +128,20 @@ export function applyPiEvent(state, event) {
|
|
|
130
128
|
if (event.type === "tool_execution_start") {
|
|
131
129
|
const id = typeof event.toolCallId === "string" ? event.toolCallId : crypto.randomUUID();
|
|
132
130
|
const name = typeof event.toolName === "string" ? event.toolName : "tool";
|
|
133
|
-
state.blocks.push({ type: "tool_use", id, name: piToolName(name), input:
|
|
131
|
+
state.blocks.push({ type: "tool_use", id, name: piToolName(name), input: asRecord(event.args) ?? {} });
|
|
134
132
|
}
|
|
135
133
|
if (event.type === "tool_execution_end") {
|
|
136
134
|
const id = typeof event.toolCallId === "string" ? event.toolCallId : "unknown";
|
|
137
135
|
state.blocks.push({ type: "tool_result", tool_use_id: id, content: textContent(event.result), is_error: event.isError === true });
|
|
138
136
|
}
|
|
139
137
|
if (event.type === "message_end" || event.type === "turn_end") {
|
|
140
|
-
const message =
|
|
138
|
+
const message = asRecord(event.message);
|
|
141
139
|
if (message?.role === "assistant")
|
|
142
140
|
return applyPiAssistantMessage(state, message);
|
|
143
141
|
}
|
|
144
142
|
if (event.type === "agent_end" && Array.isArray(event.messages)) {
|
|
145
143
|
for (const raw of event.messages) {
|
|
146
|
-
const message =
|
|
144
|
+
const message = asRecord(raw);
|
|
147
145
|
if (message?.role === "assistant") {
|
|
148
146
|
const error = applyPiAssistantMessage(state, message);
|
|
149
147
|
if (error)
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { SessionProvider, SessionRunner, SessionSnapshot, StructuredSessionState, WandConfig } from "./types.js";
|
|
2
|
+
/** 判断任意值是否为合法的 thinking effort 字面量(含 `codex:<level>` 形式)。 */
|
|
3
|
+
export declare function isThinkingEffort(value: unknown): value is NonNullable<SessionSnapshot["thinkingEffort"]>;
|
|
2
4
|
export declare function isStructuredRunnerForProvider(provider: SessionProvider, runner: unknown): runner is SessionRunner;
|
|
3
5
|
export declare function defaultStructuredRunner(provider: SessionProvider, configuredClaudeRunner?: WandConfig["structuredRunner"]): SessionRunner;
|
|
4
6
|
export declare function resolveStructuredRunner(provider: SessionProvider, requestedRunner: unknown, configuredClaudeRunner?: WandConfig["structuredRunner"]): SessionRunner;
|
|
@@ -8,7 +10,13 @@ export declare function thinkingEffortToSdkBudget(effort: SessionSnapshot["think
|
|
|
8
10
|
export declare function thinkingEffortToClaudeCliEffort(effort: SessionSnapshot["thinkingEffort"]): "low" | "medium" | "max" | null;
|
|
9
11
|
export declare function thinkingEffortToClaudeSlashEffort(effort: SessionSnapshot["thinkingEffort"]): string;
|
|
10
12
|
export declare function thinkingEffortToCodexReasoningEffort(effort: SessionSnapshot["thinkingEffort"]): string | null;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* OpenCode / Grok / Qoder 三家共用同一套 effort 级别(off/standard/deep/max →
|
|
15
|
+
* null/low/high/max),`codex:<level>` 透传 level。
|
|
16
|
+
*/
|
|
17
|
+
declare function thinkingEffortToLowHighMax(effort: SessionSnapshot["thinkingEffort"]): string | null;
|
|
18
|
+
export declare const thinkingEffortToOpenCodeVariant: typeof thinkingEffortToLowHighMax;
|
|
19
|
+
export declare const thinkingEffortToGrokEffort: typeof thinkingEffortToLowHighMax;
|
|
20
|
+
export declare const thinkingEffortToQoderEffort: typeof thinkingEffortToLowHighMax;
|
|
14
21
|
export declare function thinkingEffortToPiLevel(effort: SessionSnapshot["thinkingEffort"]): string | null;
|
|
22
|
+
export {};
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/** 判断任意值是否为合法的 thinking effort 字面量(含 `codex:<level>` 形式)。 */
|
|
2
|
+
export function isThinkingEffort(value) {
|
|
3
|
+
return value === "off"
|
|
4
|
+
|| value === "standard"
|
|
5
|
+
|| value === "deep"
|
|
6
|
+
|| value === "max"
|
|
7
|
+
|| (typeof value === "string" && /^codex:[a-z0-9][a-z0-9_-]{0,31}$/.test(value));
|
|
8
|
+
}
|
|
1
9
|
export function isStructuredRunnerForProvider(provider, runner) {
|
|
2
10
|
if (provider === "claude")
|
|
3
11
|
return runner === "claude-sdk" || runner === "claude-cli-print";
|
|
@@ -76,29 +84,11 @@ export function thinkingEffortToCodexReasoningEffort(effort) {
|
|
|
76
84
|
return "xhigh";
|
|
77
85
|
return null;
|
|
78
86
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (effort === "deep")
|
|
85
|
-
return "high";
|
|
86
|
-
if (effort === "max")
|
|
87
|
-
return "max";
|
|
88
|
-
return effort.startsWith("codex:") ? effort.slice("codex:".length) || null : null;
|
|
89
|
-
}
|
|
90
|
-
export function thinkingEffortToGrokEffort(effort) {
|
|
91
|
-
if (!effort || effort === "off")
|
|
92
|
-
return null;
|
|
93
|
-
if (effort === "standard")
|
|
94
|
-
return "low";
|
|
95
|
-
if (effort === "deep")
|
|
96
|
-
return "high";
|
|
97
|
-
if (effort === "max")
|
|
98
|
-
return "max";
|
|
99
|
-
return effort.startsWith("codex:") ? effort.slice("codex:".length) || null : null;
|
|
100
|
-
}
|
|
101
|
-
export function thinkingEffortToQoderEffort(effort) {
|
|
87
|
+
/**
|
|
88
|
+
* OpenCode / Grok / Qoder 三家共用同一套 effort 级别(off/standard/deep/max →
|
|
89
|
+
* null/low/high/max),`codex:<level>` 透传 level。
|
|
90
|
+
*/
|
|
91
|
+
function thinkingEffortToLowHighMax(effort) {
|
|
102
92
|
if (!effort || effort === "off")
|
|
103
93
|
return null;
|
|
104
94
|
if (effort === "standard")
|
|
@@ -109,6 +99,9 @@ export function thinkingEffortToQoderEffort(effort) {
|
|
|
109
99
|
return "max";
|
|
110
100
|
return effort.startsWith("codex:") ? effort.slice("codex:".length) || null : null;
|
|
111
101
|
}
|
|
102
|
+
export const thinkingEffortToOpenCodeVariant = thinkingEffortToLowHighMax;
|
|
103
|
+
export const thinkingEffortToGrokEffort = thinkingEffortToLowHighMax;
|
|
104
|
+
export const thinkingEffortToQoderEffort = thinkingEffortToLowHighMax;
|
|
112
105
|
export function thinkingEffortToPiLevel(effort) {
|
|
113
106
|
if (!effort || effort === "off")
|
|
114
107
|
return "off";
|
|
@@ -10,14 +10,9 @@ export function buildQoderArgs(session, prompt) {
|
|
|
10
10
|
const effort = thinkingEffortToQoderEffort(session.thinkingEffort);
|
|
11
11
|
if (effort)
|
|
12
12
|
args.push("--reasoning-effort", effort);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
args.push("--permission-mode", "bypass_permissions");
|
|
17
|
-
}
|
|
18
|
-
else if (session.mode === "auto-edit") {
|
|
19
|
-
args.push("--permission-mode", "accept_edits");
|
|
20
|
-
}
|
|
13
|
+
// 与 PTY runner 保持一致:Qoder 默认 yolo(bypass_permissions)启动。结构化 runner
|
|
14
|
+
// 没有权限桥,--print 下未批准的调用只会被拒绝,所以除显式 auto-edit 外一律 bypass。
|
|
15
|
+
args.push("--permission-mode", session.mode === "auto-edit" ? "accept_edits" : "bypass_permissions");
|
|
21
16
|
if (session.claudeSessionId)
|
|
22
17
|
args.push("-r", session.claudeSessionId);
|
|
23
18
|
return args;
|
|
@@ -4,9 +4,11 @@ import { prepareSessionWorktree } from "./git-worktree.js";
|
|
|
4
4
|
import { truncateMessagesForTransport } from "./message-truncator.js";
|
|
5
5
|
import { buildChildEnv } from "./env-utils.js";
|
|
6
6
|
import { getErrorMessage } from "./error-utils.js";
|
|
7
|
+
import { signalNameFromNumber } from "./signal-utils.js";
|
|
7
8
|
import { resolveSdkClaudeBinary } from "./claude-sdk-runner.js";
|
|
8
9
|
import { provisionalSessionTopic, SessionTopicCoordinator, sessionTopicBlocklistForSnapshot, shouldAcceptGeneratedSessionTitle, } from "./session-topic.js";
|
|
9
10
|
import { resolveSessionCwd } from "./session-cwd.js";
|
|
11
|
+
import { isSessionProvider } from "./session-provider.js";
|
|
10
12
|
import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
11
13
|
import { CodexRunner } from "./structured-codex-adapter.js";
|
|
12
14
|
import { CodexProtocolReducer } from "./structured-codex-protocol.js";
|
|
@@ -133,17 +135,6 @@ function recoveredCommandLabel(runner) {
|
|
|
133
135
|
default: return "claude -p";
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
|
-
function numericToSignal(signal) {
|
|
137
|
-
if (signal === null || signal === 0)
|
|
138
|
-
return null;
|
|
139
|
-
const names = {
|
|
140
|
-
1: "SIGHUP", 2: "SIGINT", 3: "SIGQUIT", 4: "SIGILL", 5: "SIGTRAP", 6: "SIGABRT",
|
|
141
|
-
7: "SIGBUS", 8: "SIGFPE", 9: "SIGKILL", 10: "SIGUSR1", 11: "SIGSEGV", 12: "SIGUSR2",
|
|
142
|
-
13: "SIGPIPE", 14: "SIGALRM", 15: "SIGTERM", 17: "SIGCHLD", 18: "SIGCONT",
|
|
143
|
-
19: "SIGSTOP", 20: "SIGTSTP",
|
|
144
|
-
};
|
|
145
|
-
return names[signal] ?? "SIGTERM";
|
|
146
|
-
}
|
|
147
138
|
const STREAM_EMIT_DEBOUNCE_MS = 16;
|
|
148
139
|
/** Min interval between full saveSession() calls for an in-progress streaming turn.
|
|
149
140
|
* saveSession serializes the entire messages array, so doing it on every NDJSON
|
|
@@ -375,9 +366,7 @@ export class StructuredSessionManager {
|
|
|
375
366
|
continue;
|
|
376
367
|
const restoredStatus = snapshot.status === "running" ? "idle" : snapshot.status;
|
|
377
368
|
const storedProvider = snapshot.provider ?? snapshot.structuredState?.provider;
|
|
378
|
-
const provider = storedProvider
|
|
379
|
-
? storedProvider
|
|
380
|
-
: "claude";
|
|
369
|
+
const provider = isSessionProvider(storedProvider) ? storedProvider : "claude";
|
|
381
370
|
const storedRunner = snapshot.runner ?? snapshot.structuredState?.runner;
|
|
382
371
|
// Legacy/corrupt snapshots are normalized on restore so send dispatch can
|
|
383
372
|
// rely on the provider/runner invariant without making startup fail.
|
|
@@ -666,7 +655,7 @@ export class StructuredSessionManager {
|
|
|
666
655
|
feedFullLog();
|
|
667
656
|
this.finalizeRecoveredRun(sessionId, requestId, processor, {
|
|
668
657
|
exitCode: initialState.exitCode,
|
|
669
|
-
signal: initialState.signal === null ? null :
|
|
658
|
+
signal: initialState.signal === null ? null : signalNameFromNumber(initialState.signal),
|
|
670
659
|
stderr: initialState.stderrLog,
|
|
671
660
|
stdoutTruncated: initialState.stdoutTruncated,
|
|
672
661
|
});
|
|
@@ -707,7 +696,7 @@ export class StructuredSessionManager {
|
|
|
707
696
|
carry = "";
|
|
708
697
|
this.finalizeRecoveredRun(sessionId, requestId, processor, {
|
|
709
698
|
exitCode: event.exitCode,
|
|
710
|
-
signal: event.signal === null ? null :
|
|
699
|
+
signal: event.signal === null ? null : signalNameFromNumber(event.signal),
|
|
711
700
|
stderr: processor.stderr,
|
|
712
701
|
stdoutTruncated: initialState.stdoutTruncated,
|
|
713
702
|
});
|
|
@@ -947,7 +936,7 @@ export class StructuredSessionManager {
|
|
|
947
936
|
const id = randomUUID();
|
|
948
937
|
const startedAt = new Date().toISOString();
|
|
949
938
|
const requestedProvider = options.provider ?? "claude";
|
|
950
|
-
if (requestedProvider
|
|
939
|
+
if (!isSessionProvider(requestedProvider)) {
|
|
951
940
|
throw new Error(`不支持的结构化 provider: ${String(requestedProvider)}`);
|
|
952
941
|
}
|
|
953
942
|
const provider = requestedProvider;
|
package/dist/task-title.js
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import { callConfiguredAiText } from "./git-quick-commit.js";
|
|
2
|
+
import { clipAtWordBoundary } from "./text-utils.js";
|
|
2
3
|
/** 任务标题留空时由描述自动生成;面板上显示得下,也不至于截断成半句话。 */
|
|
3
4
|
export const TASK_TITLE_MAX_LENGTH = 40;
|
|
4
5
|
const DESCRIPTION_HINT_MAX = 2_000;
|
|
5
|
-
function clip(value, maxLength = TASK_TITLE_MAX_LENGTH) {
|
|
6
|
-
if (value.length <= maxLength)
|
|
7
|
-
return value;
|
|
8
|
-
const sliced = value.slice(0, maxLength);
|
|
9
|
-
const lastSpace = sliced.lastIndexOf(" ");
|
|
10
|
-
if (lastSpace >= Math.floor(maxLength * 0.55))
|
|
11
|
-
return sliced.slice(0, lastSpace);
|
|
12
|
-
return sliced;
|
|
13
|
-
}
|
|
14
6
|
function firstMeaningfulLine(description) {
|
|
15
7
|
const lines = description
|
|
16
8
|
.split(/\r?\n/)
|
|
@@ -28,11 +20,11 @@ function firstMeaningfulLine(description) {
|
|
|
28
20
|
* 后台自动生成成功后再覆盖它;失败或超时就保留这个兜底。
|
|
29
21
|
*/
|
|
30
22
|
export function provisionalTaskTitleFromDescription(description) {
|
|
31
|
-
return
|
|
23
|
+
return clipAtWordBoundary(firstMeaningfulLine(description), TASK_TITLE_MAX_LENGTH);
|
|
32
24
|
}
|
|
33
25
|
/** 模型输出可能带引号、代码块或「标题:」前缀,逐层剥掉再按单行裁剪。 */
|
|
34
26
|
export function parseGeneratedTaskTitle(raw) {
|
|
35
|
-
return
|
|
27
|
+
return clipAtWordBoundary(stripGeneratedTitle(raw), TASK_TITLE_MAX_LENGTH);
|
|
36
28
|
}
|
|
37
29
|
function stripGeneratedTitle(raw) {
|
|
38
30
|
const withoutFences = raw.trim().replace(/^```(?:json|text)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
@@ -92,5 +84,5 @@ export async function generateWandTaskTitle(description, cwd, language = "", ai
|
|
|
92
84
|
// 先判可信度再裁剪:截断过的长句看起来就像标题,会绕过长度检查。
|
|
93
85
|
if (!isPlausibleTaskTitle(stripped))
|
|
94
86
|
throw new Error("模型返回的任务标题不可用。");
|
|
95
|
-
return
|
|
87
|
+
return clipAtWordBoundary(stripped, TASK_TITLE_MAX_LENGTH);
|
|
96
88
|
}
|
package/dist/task-types.d.ts
CHANGED
|
@@ -6,12 +6,34 @@ export type WandTaskPriority = "none" | "low" | "medium" | "high" | "urgent";
|
|
|
6
6
|
export type WandTaskAgentProvider = "claude" | "codex" | "opencode" | "grok" | "qoder" | "pi";
|
|
7
7
|
export type WandTaskAgentModel = string;
|
|
8
8
|
export type WandTaskAgentEffort = "off" | "standard" | "deep" | "max";
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* 派发时的执行模式,只暴露三种:
|
|
11
|
+
* - managed:托管,自动确认全部权限并使用完全自主提示词;
|
|
12
|
+
* - full-access:全限,自动确认权限但仍保留交互语义;
|
|
13
|
+
* - default:标准,逐步确认操作。
|
|
14
|
+
*/
|
|
15
|
+
export type WandTaskAgentMode = "managed" | "full-access" | "default";
|
|
16
|
+
/** 合法执行模式清单;顺序即下拉顺序。 */
|
|
17
|
+
export declare const WAND_TASK_AGENT_MODES: readonly WandTaskAgentMode[];
|
|
18
|
+
/** 历史行没有 mode 字段时的兜底:标准模式,与旧 `mode: "agent"` 的权限姿态一致。 */
|
|
19
|
+
export declare const DEFAULT_WAND_TASK_AGENT_MODE: WandTaskAgentMode;
|
|
20
|
+
export declare function isWandTaskAgentMode(value: unknown): value is WandTaskAgentMode;
|
|
21
|
+
/**
|
|
22
|
+
* 各 provider 实际支持的工作模式。Codex 只有 full-access 一个有效值
|
|
23
|
+
* (与新建会话的 `supportedModes` / `/api/sessions/:id/mode` 一致);
|
|
24
|
+
* 否则 `default` 会让 codex 以 `--sandbox read-only` 跑,派发的任务根本改不了代码。
|
|
25
|
+
*/
|
|
26
|
+
export declare function supportedWandTaskAgentModes(provider: WandTaskAgentProvider): readonly WandTaskAgentMode[];
|
|
27
|
+
/** 把任意(含旧数据 / 其它客户端缺省的)模式夹到该 provider 真正支持的值。 */
|
|
28
|
+
export declare function normalizeWandTaskAgentMode(provider: WandTaskAgentProvider, mode: unknown): WandTaskAgentMode;
|
|
29
|
+
/** 任务上的默认派发配置:先选工具 / 模型 / 思考深度 / 工作模式,再一键交给 Agent。 */
|
|
10
30
|
export interface WandTaskAgent {
|
|
11
31
|
provider: WandTaskAgentProvider;
|
|
12
32
|
/** 具体模型 ID;"default" 表示跟随服务端为该 provider 选择的默认模型。 */
|
|
13
33
|
model: WandTaskAgentModel;
|
|
14
34
|
thinkingEffort: WandTaskAgentEffort;
|
|
35
|
+
/** 执行模式;缺省时按标准模式处理。 */
|
|
36
|
+
mode: WandTaskAgentMode;
|
|
15
37
|
}
|
|
16
38
|
/** 任务标题来源;标题是可选字段,留空时由服务端按描述自动生成。 */
|
|
17
39
|
export type WandTaskTitleSource = "user" | "auto";
|
package/dist/task-types.js
CHANGED
|
@@ -2,5 +2,31 @@
|
|
|
2
2
|
export function isClosedWandTaskStatus(status) {
|
|
3
3
|
return status === "done" || status === "archived";
|
|
4
4
|
}
|
|
5
|
+
/** 合法执行模式清单;顺序即下拉顺序。 */
|
|
6
|
+
export const WAND_TASK_AGENT_MODES = ["managed", "full-access", "default"];
|
|
7
|
+
/** 历史行没有 mode 字段时的兜底:标准模式,与旧 `mode: "agent"` 的权限姿态一致。 */
|
|
8
|
+
export const DEFAULT_WAND_TASK_AGENT_MODE = "default";
|
|
9
|
+
export function isWandTaskAgentMode(value) {
|
|
10
|
+
return value === "managed" || value === "full-access" || value === "default";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 各 provider 实际支持的工作模式。Codex 只有 full-access 一个有效值
|
|
14
|
+
* (与新建会话的 `supportedModes` / `/api/sessions/:id/mode` 一致);
|
|
15
|
+
* 否则 `default` 会让 codex 以 `--sandbox read-only` 跑,派发的任务根本改不了代码。
|
|
16
|
+
*/
|
|
17
|
+
export function supportedWandTaskAgentModes(provider) {
|
|
18
|
+
if (provider === "codex")
|
|
19
|
+
return ["full-access"];
|
|
20
|
+
return WAND_TASK_AGENT_MODES;
|
|
21
|
+
}
|
|
22
|
+
/** 把任意(含旧数据 / 其它客户端缺省的)模式夹到该 provider 真正支持的值。 */
|
|
23
|
+
export function normalizeWandTaskAgentMode(provider, mode) {
|
|
24
|
+
const supported = supportedWandTaskAgentModes(provider);
|
|
25
|
+
if (isWandTaskAgentMode(mode) && supported.includes(mode))
|
|
26
|
+
return mode;
|
|
27
|
+
return supported.includes(DEFAULT_WAND_TASK_AGENT_MODE)
|
|
28
|
+
? DEFAULT_WAND_TASK_AGENT_MODE
|
|
29
|
+
: supported[0];
|
|
30
|
+
}
|
|
5
31
|
/** 里程碑名上限;所有任务面板的新增入口共用同一约束。 */
|
|
6
32
|
export const WAND_MILESTONE_NAME_MAX_LENGTH = 60;
|