@co0ontty/wand 4.10.3 → 4.11.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 -4
- package/dist/build-info.json +3 -3
- package/dist/config.d.ts +4 -3
- package/dist/config.js +20 -3
- package/dist/models.d.ts +3 -0
- package/dist/models.js +38 -1
- package/dist/path-repair.js +6 -3
- package/dist/process-manager.js +17 -1
- package/dist/provider-cli-updater.d.ts +1 -1
- package/dist/provider-cli-updater.js +9 -1
- package/dist/server-session-routes.js +3 -3
- package/dist/server-settings-routes.js +5 -0
- package/dist/server-update-routes.js +1 -1
- package/dist/server.js +6 -1
- package/dist/session-ai-context.d.ts +2 -2
- package/dist/session-ai-context.js +8 -2
- package/dist/session-registry.js +3 -1
- package/dist/storage.js +5 -1
- package/dist/structured-provider-common.js +5 -1
- package/dist/structured-qoder-adapter.d.ts +10 -0
- package/dist/structured-qoder-adapter.js +124 -0
- package/dist/structured-session-manager.d.ts +2 -0
- package/dist/structured-session-manager.js +32 -13
- package/dist/system-ai.js +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/web-ui/content/scripts.js +26 -26
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { ClaudeCliProtocolReducer } from "./structured-claude-protocol.js";
|
|
3
|
+
export function buildQoderArgs(session, prompt) {
|
|
4
|
+
const args = ["-p", prompt, "--output-format", "stream-json"];
|
|
5
|
+
const model = session.selectedModel?.trim();
|
|
6
|
+
if (model && model !== "default")
|
|
7
|
+
args.push("--model", model);
|
|
8
|
+
if (session.autoApprovePermissions === true
|
|
9
|
+
|| session.mode === "full-access"
|
|
10
|
+
|| session.mode === "managed") {
|
|
11
|
+
args.push("--permission-mode", "bypass_permissions");
|
|
12
|
+
}
|
|
13
|
+
else if (session.mode === "auto-edit") {
|
|
14
|
+
args.push("--permission-mode", "accept_edits");
|
|
15
|
+
}
|
|
16
|
+
if (session.claudeSessionId)
|
|
17
|
+
args.push("-r", session.claudeSessionId);
|
|
18
|
+
return args;
|
|
19
|
+
}
|
|
20
|
+
/** Owns the official Qoder CLI print process and its stream-json protocol. */
|
|
21
|
+
export class QoderRunner {
|
|
22
|
+
spawnProcess;
|
|
23
|
+
constructor(spawnProcess = spawn) {
|
|
24
|
+
this.spawnProcess = spawnProcess;
|
|
25
|
+
}
|
|
26
|
+
start(context, observer) {
|
|
27
|
+
const args = buildQoderArgs(context.session, context.prompt);
|
|
28
|
+
const spawnedAt = new Date().toISOString();
|
|
29
|
+
const child = this.spawnProcess("qodercli", args, {
|
|
30
|
+
cwd: context.session.cwd,
|
|
31
|
+
env: context.env,
|
|
32
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
33
|
+
});
|
|
34
|
+
const reducer = new ClaudeCliProtocolReducer(context.session);
|
|
35
|
+
let lineBuffer = "";
|
|
36
|
+
let stderr = "";
|
|
37
|
+
let stdoutTail = "";
|
|
38
|
+
let primaryError = null;
|
|
39
|
+
let settled = false;
|
|
40
|
+
const result = (exitCode, signal, spawnError) => ({
|
|
41
|
+
state: reducer.state,
|
|
42
|
+
exitCode,
|
|
43
|
+
signal,
|
|
44
|
+
stderr,
|
|
45
|
+
stdoutTail,
|
|
46
|
+
primaryError,
|
|
47
|
+
...(spawnError ? { spawnError } : {}),
|
|
48
|
+
});
|
|
49
|
+
const processLine = (line) => {
|
|
50
|
+
if (!observer.isActive())
|
|
51
|
+
return;
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (!trimmed)
|
|
54
|
+
return;
|
|
55
|
+
let event;
|
|
56
|
+
try {
|
|
57
|
+
event = JSON.parse(trimmed);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (event && typeof event === "object" && !Array.isArray(event)) {
|
|
63
|
+
const record = event;
|
|
64
|
+
observer.onEvent?.(record);
|
|
65
|
+
if (record.type === "result" && record.subtype !== "success") {
|
|
66
|
+
const errors = Array.isArray(record.errors)
|
|
67
|
+
? record.errors.filter((item) => typeof item === "string")
|
|
68
|
+
: [];
|
|
69
|
+
primaryError = errors.join("\n") || "Qoder CLI execution failed";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (reducer.apply(event, context.session.mode === "managed"))
|
|
73
|
+
observer.onUpdate(reducer.state);
|
|
74
|
+
};
|
|
75
|
+
const completion = new Promise((resolve) => {
|
|
76
|
+
child.stdout?.on("data", (chunk) => {
|
|
77
|
+
if (!observer.isActive())
|
|
78
|
+
return;
|
|
79
|
+
const text = chunk.toString();
|
|
80
|
+
observer.onStdout?.(text);
|
|
81
|
+
const trimmed = text.trim();
|
|
82
|
+
if (trimmed)
|
|
83
|
+
stdoutTail = trimmed.slice(-1024);
|
|
84
|
+
lineBuffer += text;
|
|
85
|
+
const lines = lineBuffer.split("\n");
|
|
86
|
+
lineBuffer = lines.pop() ?? "";
|
|
87
|
+
for (const line of lines)
|
|
88
|
+
processLine(line);
|
|
89
|
+
});
|
|
90
|
+
child.stderr?.on("data", (chunk) => {
|
|
91
|
+
if (!observer.isActive())
|
|
92
|
+
return;
|
|
93
|
+
const text = chunk.toString();
|
|
94
|
+
observer.onStderr?.(text);
|
|
95
|
+
stderr += text;
|
|
96
|
+
});
|
|
97
|
+
child.on("error", (error) => {
|
|
98
|
+
if (settled)
|
|
99
|
+
return;
|
|
100
|
+
settled = true;
|
|
101
|
+
resolve(result(null, null, error));
|
|
102
|
+
});
|
|
103
|
+
child.on("close", (exitCode, signal) => {
|
|
104
|
+
if (settled)
|
|
105
|
+
return;
|
|
106
|
+
settled = true;
|
|
107
|
+
if (lineBuffer.trim())
|
|
108
|
+
processLine(lineBuffer);
|
|
109
|
+
lineBuffer = "";
|
|
110
|
+
resolve(result(exitCode, signal));
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
args,
|
|
115
|
+
spawnedAt,
|
|
116
|
+
pid: child.pid ?? null,
|
|
117
|
+
completion,
|
|
118
|
+
interrupt: () => { try {
|
|
119
|
+
child.kill("SIGTERM");
|
|
120
|
+
}
|
|
121
|
+
catch { /* best effort */ } },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -9,6 +9,7 @@ export interface StructuredSessionManagerRunners {
|
|
|
9
9
|
codex?: StructuredRunnerAdapter;
|
|
10
10
|
opencode?: StructuredRunnerAdapter;
|
|
11
11
|
grok?: StructuredRunnerAdapter;
|
|
12
|
+
qoder?: StructuredRunnerAdapter;
|
|
12
13
|
}
|
|
13
14
|
interface CreateStructuredSessionOptions {
|
|
14
15
|
cwd: string;
|
|
@@ -83,6 +84,7 @@ export declare class StructuredSessionManager {
|
|
|
83
84
|
private readonly codexRunner;
|
|
84
85
|
private readonly openCodeRunner;
|
|
85
86
|
private readonly grokRunner;
|
|
87
|
+
private readonly qoderRunner;
|
|
86
88
|
private disposed;
|
|
87
89
|
constructor(storage: WandStorage, config: WandConfig, logger?: SessionLogger | null, sdkQueryFactory?: typeof sdkQuery, runners?: StructuredSessionManagerRunners);
|
|
88
90
|
private archiveExpiredSessions;
|
|
@@ -13,6 +13,7 @@ import { buildAppendSystemPromptParts, buildClaudeSdkThinking, ClaudeCliRunner,
|
|
|
13
13
|
import { captureTaskMeta, extractClaudeAssistantMessage, extractClaudeModelName, normalizeClaudeToolInput, stampParentTaskResults, stampSelfTask, tagSubagentBlocks, } from "./structured-claude-protocol.js";
|
|
14
14
|
import { OpenCodeRunner } from "./structured-opencode-adapter.js";
|
|
15
15
|
import { GrokRunner } from "./structured-grok-adapter.js";
|
|
16
|
+
import { QoderRunner } from "./structured-qoder-adapter.js";
|
|
16
17
|
import { defaultStructuredRunner, defaultStructuredState, isStructuredRunnerForProvider, normalizeThinkingEffort, resolveStructuredRunner, } from "./structured-provider-common.js";
|
|
17
18
|
import { enrichStructuredMessages, WAND_PROTOCOL_VERSION } from "./structured-client-protocol.js";
|
|
18
19
|
export { isStructuredRunnerForProvider, normalizeThinkingEffort, resolveStructuredRunner, thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToOpenCodeVariant, thinkingEffortToSdkBudget, } from "./structured-provider-common.js";
|
|
@@ -203,6 +204,7 @@ export class StructuredSessionManager {
|
|
|
203
204
|
codexRunner;
|
|
204
205
|
openCodeRunner;
|
|
205
206
|
grokRunner;
|
|
207
|
+
qoderRunner;
|
|
206
208
|
disposed = false;
|
|
207
209
|
constructor(storage, config, logger = null, sdkQueryFactory = sdkQuery, runners = {}) {
|
|
208
210
|
this.storage = storage;
|
|
@@ -213,12 +215,13 @@ export class StructuredSessionManager {
|
|
|
213
215
|
this.codexRunner = runners.codex ?? new CodexRunner();
|
|
214
216
|
this.openCodeRunner = runners.opencode ?? new OpenCodeRunner();
|
|
215
217
|
this.grokRunner = runners.grok ?? new GrokRunner();
|
|
218
|
+
this.qoderRunner = runners.qoder ?? new QoderRunner();
|
|
216
219
|
for (const snapshot of this.storage.loadSessions()) {
|
|
217
220
|
if ((snapshot.sessionKind ?? "pty") !== "structured")
|
|
218
221
|
continue;
|
|
219
222
|
const restoredStatus = snapshot.status === "running" ? "idle" : snapshot.status;
|
|
220
223
|
const storedProvider = snapshot.provider ?? snapshot.structuredState?.provider;
|
|
221
|
-
const provider = storedProvider === "codex" || storedProvider === "opencode" || storedProvider === "grok"
|
|
224
|
+
const provider = storedProvider === "codex" || storedProvider === "opencode" || storedProvider === "grok" || storedProvider === "qoder"
|
|
222
225
|
? storedProvider
|
|
223
226
|
: "claude";
|
|
224
227
|
const storedRunner = snapshot.runner ?? snapshot.structuredState?.runner;
|
|
@@ -498,7 +501,7 @@ export class StructuredSessionManager {
|
|
|
498
501
|
const id = randomUUID();
|
|
499
502
|
const startedAt = new Date().toISOString();
|
|
500
503
|
const requestedProvider = options.provider ?? "claude";
|
|
501
|
-
if (requestedProvider !== "claude" && requestedProvider !== "codex" && requestedProvider !== "opencode" && requestedProvider !== "grok") {
|
|
504
|
+
if (requestedProvider !== "claude" && requestedProvider !== "codex" && requestedProvider !== "opencode" && requestedProvider !== "grok" && requestedProvider !== "qoder") {
|
|
502
505
|
throw new Error(`不支持的结构化 provider: ${String(requestedProvider)}`);
|
|
503
506
|
}
|
|
504
507
|
const provider = requestedProvider;
|
|
@@ -522,9 +525,11 @@ export class StructuredSessionManager {
|
|
|
522
525
|
? "opencode run --format json"
|
|
523
526
|
: provider === "grok"
|
|
524
527
|
? "grok -p --output-format streaming-json"
|
|
525
|
-
:
|
|
526
|
-
? "
|
|
527
|
-
: "claude
|
|
528
|
+
: provider === "qoder"
|
|
529
|
+
? "qodercli -p --output-format stream-json"
|
|
530
|
+
: runner === "claude-sdk"
|
|
531
|
+
? "claude-agent-sdk (stream-json)"
|
|
532
|
+
: "claude -p --output-format stream-json",
|
|
528
533
|
cwd: worktreeSetup?.cwd ?? baseCwd,
|
|
529
534
|
mode: options.mode,
|
|
530
535
|
worktreeEnabled: Boolean(worktreeSetup),
|
|
@@ -728,6 +733,15 @@ export class StructuredSessionManager {
|
|
|
728
733
|
else if (provider === "grok") {
|
|
729
734
|
await this.runGrokStreaming(id, updated, prompt, requestId);
|
|
730
735
|
}
|
|
736
|
+
else if (provider === "qoder") {
|
|
737
|
+
await this.runClaudeStreaming(id, updated, prompt, requestId, {
|
|
738
|
+
runner: this.qoderRunner,
|
|
739
|
+
provider: "qoder",
|
|
740
|
+
commandLabel: "qodercli -p",
|
|
741
|
+
logKind: "qoder-print",
|
|
742
|
+
installHint: "请安装 @qoder-ai/qodercli,或重跑 `wand service:install` 刷新服务的 PATH",
|
|
743
|
+
});
|
|
744
|
+
}
|
|
731
745
|
else if (runner === "claude-sdk") {
|
|
732
746
|
await this.runClaudeSdkStreaming(id, updated, prompt, requestId);
|
|
733
747
|
}
|
|
@@ -1632,7 +1646,7 @@ export class StructuredSessionManager {
|
|
|
1632
1646
|
* - Root: --permission-mode acceptEdits + --allowedTools (extends approval
|
|
1633
1647
|
* outside CWD). stdin is always "ignore" — no ACP bidirectional control.
|
|
1634
1648
|
*/
|
|
1635
|
-
async runClaudeStreaming(sessionId, session, prompt, requestId) {
|
|
1649
|
+
async runClaudeStreaming(sessionId, session, prompt, requestId, options = {}) {
|
|
1636
1650
|
let emitTimer = null;
|
|
1637
1651
|
const syncSnapshot = (turnState) => {
|
|
1638
1652
|
const current = this.currentSessionForRequest(sessionId, requestId);
|
|
@@ -1681,7 +1695,11 @@ export class StructuredSessionManager {
|
|
|
1681
1695
|
if (!emitTimer)
|
|
1682
1696
|
emitTimer = this.trackStreamEmitTimer(setTimeout(flushEmit, STREAM_EMIT_DEBOUNCE_MS));
|
|
1683
1697
|
};
|
|
1684
|
-
const
|
|
1698
|
+
const runner = options.runner ?? this.claudeCliRunner;
|
|
1699
|
+
const provider = options.provider ?? "claude";
|
|
1700
|
+
const commandLabel = options.commandLabel ?? "claude -p";
|
|
1701
|
+
const logKind = options.logKind ?? "claude-print";
|
|
1702
|
+
const execution = runner.start({
|
|
1685
1703
|
session,
|
|
1686
1704
|
prompt,
|
|
1687
1705
|
env: buildChildEnv(this.config.inheritEnv !== false),
|
|
@@ -1697,8 +1715,8 @@ export class StructuredSessionManager {
|
|
|
1697
1715
|
});
|
|
1698
1716
|
this.pendingRunnerExecutions.set(sessionId, execution);
|
|
1699
1717
|
this.logger?.appendStructuredSpawn(sessionId, {
|
|
1700
|
-
kind:
|
|
1701
|
-
provider
|
|
1718
|
+
kind: logKind,
|
|
1719
|
+
provider,
|
|
1702
1720
|
pid: execution.pid,
|
|
1703
1721
|
cwd: session.cwd,
|
|
1704
1722
|
args: execution.args,
|
|
@@ -1724,12 +1742,12 @@ export class StructuredSessionManager {
|
|
|
1724
1742
|
flushEmit();
|
|
1725
1743
|
if (result.spawnError) {
|
|
1726
1744
|
const hint = result.spawnError.code === "ENOENT"
|
|
1727
|
-
?
|
|
1745
|
+
? `(PATH 中找不到 ${provider === "qoder" ? "qodercli" : "claude"} 可执行文件;${options.installHint ?? "请确认 claude 已安装,或重跑 `wand service:install` 刷新服务的 PATH"})`
|
|
1728
1746
|
: "";
|
|
1729
|
-
throw new Error(
|
|
1747
|
+
throw new Error(`${commandLabel} 启动失败:${result.spawnError.message}${hint}`);
|
|
1730
1748
|
}
|
|
1731
1749
|
this.logger?.appendStructuredSpawn(sessionId, {
|
|
1732
|
-
kind:
|
|
1750
|
+
kind: `${logKind}-close`,
|
|
1733
1751
|
pid: execution.pid,
|
|
1734
1752
|
spawnedAt: execution.spawnedAt,
|
|
1735
1753
|
closedAt: new Date().toISOString(),
|
|
@@ -1743,9 +1761,10 @@ export class StructuredSessionManager {
|
|
|
1743
1761
|
const interruptedForQuestion = result.stopReason === "ask-user-question";
|
|
1744
1762
|
const failedExit = (result.exitCode !== null && result.exitCode !== 0) || result.signal !== null;
|
|
1745
1763
|
if (failedExit && !interruptedByUser && !interruptedForQuestion) {
|
|
1746
|
-
const errorText = this.formatStructuredExitError(
|
|
1764
|
+
const errorText = this.formatStructuredExitError(commandLabel, result.exitCode, result.signal, {
|
|
1747
1765
|
stderr: result.stderr,
|
|
1748
1766
|
stdoutTail: result.stdoutTail,
|
|
1767
|
+
primary: result.primaryError,
|
|
1749
1768
|
});
|
|
1750
1769
|
const failed = this.finishStructuredFailure(current, typeof result.exitCode === "number" ? result.exitCode : 1, errorText, result.state);
|
|
1751
1770
|
this.sessions.set(sessionId, failed);
|
package/dist/system-ai.js
CHANGED
|
@@ -99,7 +99,7 @@ export function discoverCliSystemAiConfig(preferred, home = os.homedir()) {
|
|
|
99
99
|
const discoverers = { claude: discoverClaude, codex: discoverCodex, opencode: discoverOpenCode };
|
|
100
100
|
const order = [preferred ?? "claude", "claude", "opencode", "codex"];
|
|
101
101
|
for (const provider of [...new Set(order)]) {
|
|
102
|
-
if (provider === "grok")
|
|
102
|
+
if (provider === "grok" || provider === "qoder")
|
|
103
103
|
continue;
|
|
104
104
|
const found = discoverers[provider](home);
|
|
105
105
|
if (found)
|
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type SessionKind = "pty" | "structured";
|
|
2
|
-
export type SessionProvider = "claude" | "codex" | "opencode" | "grok";
|
|
2
|
+
export type SessionProvider = "claude" | "codex" | "opencode" | "grok" | "qoder";
|
|
3
3
|
export type CommitAiSource = "cli" | "api";
|
|
4
|
-
export type SessionRunner = "claude-cli" | "claude-cli-print" | "claude-sdk" | "codex-cli-exec" | "opencode-cli-run" | "grok-cli-headless" | "pty";
|
|
4
|
+
export type SessionRunner = "claude-cli" | "claude-cli-print" | "claude-sdk" | "codex-cli-exec" | "opencode-cli-run" | "grok-cli-headless" | "qoder-cli-print" | "pty";
|
|
5
5
|
export type SessionSource = "interactive" | "automation" | "startup";
|
|
6
6
|
export type ExecutionMode = "assist" | "agent" | "agent-max" | "default" | "auto-edit" | "full-access" | "native" | "managed";
|
|
7
7
|
export type AutonomyPolicy = "assist" | "agent" | "agent-max";
|
|
@@ -114,6 +114,8 @@ export interface WandConfig {
|
|
|
114
114
|
defaultOpenCodeModel?: string;
|
|
115
115
|
/** 新建 Grok 会话时默认使用的模型。留空则不传 --model,由 grok 自行决定。 */
|
|
116
116
|
defaultGrokModel?: string;
|
|
117
|
+
/** 新建 Qoder 会话时默认使用的模型层级。留空则由 qodercli 自行决定。 */
|
|
118
|
+
defaultQoderModel?: string;
|
|
117
119
|
/** 快捷提交生成 commit message / tag 时使用的 CLI。 */
|
|
118
120
|
commitCli?: SessionProvider;
|
|
119
121
|
/** 快捷提交专用模型。留空则跟随所选 CLI 的默认模型。 */
|