@co0ontty/wand 4.89.0-beta.gde0b3cd → 4.89.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/dist/agent-dispatch.d.ts +0 -5
- package/dist/agent-dispatch.js +0 -8
- package/dist/ai-team-runner.d.ts +1 -1
- package/dist/ai-team-runner.js +5 -13
- package/dist/ai-team-types.js +2 -3
- package/dist/build-info.json +4 -4
- package/dist/commit-task-archive.d.ts +0 -3
- package/dist/commit-task-archive.js +0 -15
- package/dist/config.js +0 -1
- package/dist/decision-expert-employee.d.ts +1 -1
- package/dist/decision-expert-employee.js +3 -3
- package/dist/decision-expert-service.d.ts +5 -6
- package/dist/decision-expert-service.js +49 -75
- package/dist/decision-types.d.ts +1 -2
- package/dist/git-quick-commit.d.ts +4 -13
- package/dist/git-quick-commit.js +33 -112
- package/dist/local-model-setup.d.ts +1 -7
- package/dist/local-model-setup.js +7 -54
- package/dist/local-model-types.d.ts +1 -2
- package/dist/process-manager.d.ts +1 -4
- package/dist/process-manager.js +6 -5
- package/dist/server-employee-routes.d.ts +0 -2
- package/dist/server-employee-routes.js +45 -19
- package/dist/server-file-routes.js +25 -61
- package/dist/server-local-model-routes.js +0 -12
- package/dist/server-session-routes.d.ts +1 -2
- package/dist/server-session-routes.js +10 -13
- package/dist/server-speech-routes.d.ts +0 -2
- package/dist/server-speech-routes.js +1 -7
- package/dist/server-task-routes.d.ts +0 -3
- package/dist/server-task-routes.js +10 -13
- package/dist/server.js +8 -20
- package/dist/session-ai-context.d.ts +4 -6
- package/dist/session-ai-context.js +9 -10
- package/dist/silicon-employee-dispatch.d.ts +6 -4
- package/dist/silicon-employee-dispatch.js +6 -9
- package/dist/speech-service.d.ts +1 -3
- package/dist/speech-service.js +3 -14
- package/dist/speech-types.d.ts +0 -5
- package/dist/storage.d.ts +0 -2
- package/dist/storage.js +1 -18
- package/dist/structured-session-manager.d.ts +2 -3
- package/dist/structured-session-manager.js +7 -11
- package/dist/system-employee.d.ts +4 -6
- package/dist/system-employee.js +5 -8
- package/dist/types.d.ts +1 -3
- package/dist/user-memory.d.ts +0 -2
- package/dist/user-memory.js +1 -1
- package/dist/web-ui/content/ai-teams.js +10 -10
- package/dist/web-ui/content/scripts.js +204 -316
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +0 -1
- package/dist/web-ui/embedded-assets.js +3 -4
- package/dist/web-ui/index.js +3 -3
- package/dist/web-ui/provider-identity.d.ts +0 -2
- package/dist/web-ui/provider-identity.js +1 -7
- package/dist/web-ui/scripts.d.ts +0 -2
- package/dist/web-ui/scripts.js +0 -5
- package/package.json +1 -1
- package/dist/employee-text.d.ts +0 -28
- package/dist/employee-text.js +0 -124
- package/dist/speech-polisher-employee.d.ts +0 -6
- package/dist/speech-polisher-employee.js +0 -23
- package/dist/speech-polisher-identity.d.ts +0 -4
- package/dist/speech-polisher-identity.js +0 -4
- package/dist/speech-polisher-service.d.ts +0 -31
- package/dist/speech-polisher-service.js +0 -88
- package/dist/speech-support.d.ts +0 -6
- package/dist/speech-support.js +0 -26
package/dist/git-quick-commit.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import { existsSync
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
5
2
|
import { spawn } from "node:child_process";
|
|
6
|
-
import { abortableEmployeeText, prepareEmployeeTextCandidate } from "./employee-text.js";
|
|
7
3
|
import { buildChildEnv } from "./env-utils.js";
|
|
8
4
|
import { buildLanguageDirective } from "./language-prompt.js";
|
|
9
5
|
import { isSessionProvider, providerCliInstalled } from "./session-provider.js";
|
|
@@ -310,7 +306,7 @@ async function callClaudeText(request, cwd, language, opts) {
|
|
|
310
306
|
const stdout = await runCliText("claude", args, request.prompt, {
|
|
311
307
|
cwd,
|
|
312
308
|
timeoutMs: cliTextTimeoutMs(CLAUDE_MESSAGE_TIMEOUT_MS, opts),
|
|
313
|
-
inheritEnv: opts.inheritEnv,
|
|
309
|
+
inheritEnv: opts.inheritEnv,
|
|
314
310
|
});
|
|
315
311
|
const text = stdout.trim();
|
|
316
312
|
if (!text)
|
|
@@ -542,8 +538,6 @@ function buildGrokTextArgs(request, opts, allowTools = false) {
|
|
|
542
538
|
args.push(...systemPromptArgsFrom("grok", request.system));
|
|
543
539
|
if (allowTools)
|
|
544
540
|
args.push("--always-approve");
|
|
545
|
-
else
|
|
546
|
-
args.push("--tools", "", "--disallowed-tools", "run_terminal_cmd,read_file,search_replace,grep,list_dir,web_search,web_fetch,todo_write,task,Agent", "--deny", "MCPTool", "--disable-web-search", "--no-subagents");
|
|
547
541
|
return args;
|
|
548
542
|
}
|
|
549
543
|
/**
|
|
@@ -574,8 +568,6 @@ function buildQoderTextArgs(request, opts, allowTools = false) {
|
|
|
574
568
|
args.push(...systemPromptArgsFrom("qoder", request.system));
|
|
575
569
|
if (allowTools)
|
|
576
570
|
args.push("--permission-mode", "bypass_permissions");
|
|
577
|
-
else
|
|
578
|
-
args.push("--tools", "", "--strict-mcp-config");
|
|
579
571
|
return args;
|
|
580
572
|
}
|
|
581
573
|
/** provider 自己的系统提示开关 + 规则文本;没有开关的 provider 返回空。 */
|
|
@@ -591,48 +583,35 @@ function contentWithSystemPrompt(provider, request) {
|
|
|
591
583
|
return systemPromptFlag(provider) ? request.prompt : composeSystemFallback(request.system, request.prompt);
|
|
592
584
|
}
|
|
593
585
|
function runCliText(command, args, prompt, opts) {
|
|
594
|
-
opts.signal?.throwIfAborted();
|
|
595
586
|
return new Promise((resolve, reject) => {
|
|
596
|
-
const baseEnv = buildChildEnv(opts.inheritEnv !== false);
|
|
597
587
|
const child = spawn(command, args, {
|
|
598
588
|
cwd: opts.cwd,
|
|
599
|
-
env: opts.
|
|
589
|
+
env: buildChildEnv(opts.inheritEnv !== false),
|
|
600
590
|
stdio: ["pipe", "pipe", "pipe"],
|
|
601
591
|
});
|
|
602
592
|
let stdout = "";
|
|
603
593
|
let stderr = "";
|
|
604
594
|
let settled = false;
|
|
605
|
-
const cancelled = () => {
|
|
606
|
-
if (settled)
|
|
607
|
-
return;
|
|
608
|
-
settled = true;
|
|
609
|
-
clearTimeout(timeout);
|
|
610
|
-
child.kill("SIGTERM");
|
|
611
|
-
reject(opts.signal?.reason ?? new Error("AI 文本请求已取消。"));
|
|
612
|
-
};
|
|
613
|
-
const cleanup = () => { clearTimeout(timeout); opts.signal?.removeEventListener("abort", cancelled); };
|
|
614
595
|
const timeout = setTimeout(() => {
|
|
615
596
|
settled = true;
|
|
616
|
-
opts.signal?.removeEventListener("abort", cancelled);
|
|
617
597
|
child.kill("SIGTERM");
|
|
618
598
|
reject(new QuickCommitError(`${command} 调用超时。`, "CLAUDE_TIMEOUT"));
|
|
619
599
|
}, opts.timeoutMs);
|
|
620
|
-
opts.signal?.addEventListener("abort", cancelled, { once: true });
|
|
621
600
|
child.stdout?.on("data", (chunk) => { stdout += chunk.toString(); });
|
|
622
601
|
child.stderr?.on("data", (chunk) => { stderr += chunk.toString(); });
|
|
623
602
|
child.on("error", (error) => {
|
|
624
603
|
if (settled)
|
|
625
604
|
return;
|
|
626
605
|
settled = true;
|
|
627
|
-
|
|
606
|
+
clearTimeout(timeout);
|
|
628
607
|
const code = error.code === "ENOENT" ? "CLAUDE_CLI_MISSING" : "CLAUDE_CLI_FAILED";
|
|
629
608
|
reject(new QuickCommitError(error.code === "ENOENT" ? `未找到 ${command} CLI。` : `${command} CLI 失败:${error.message}`, code));
|
|
630
609
|
});
|
|
631
610
|
child.on("close", (code) => {
|
|
632
|
-
cleanup();
|
|
633
611
|
if (settled)
|
|
634
612
|
return;
|
|
635
613
|
settled = true;
|
|
614
|
+
clearTimeout(timeout);
|
|
636
615
|
if (code === 0) {
|
|
637
616
|
resolve(stdout);
|
|
638
617
|
return;
|
|
@@ -653,7 +632,7 @@ function runCliText(command, args, prompt, opts) {
|
|
|
653
632
|
if (settled || !expectsStdin)
|
|
654
633
|
return;
|
|
655
634
|
settled = true;
|
|
656
|
-
|
|
635
|
+
clearTimeout(timeout);
|
|
657
636
|
child.kill("SIGTERM");
|
|
658
637
|
reject(new QuickCommitError(`${command} CLI stdin failed: ${error.code ?? "write error"}`, "CLAUDE_CLI_FAILED"));
|
|
659
638
|
});
|
|
@@ -675,7 +654,7 @@ async function callCodexText(request, cwd, opts) {
|
|
|
675
654
|
const stdout = await runCliText("codex", args, contentWithSystemPrompt("codex", request), {
|
|
676
655
|
cwd,
|
|
677
656
|
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
678
|
-
inheritEnv: opts.inheritEnv,
|
|
657
|
+
inheritEnv: opts.inheritEnv,
|
|
679
658
|
});
|
|
680
659
|
const text = extractCodexText(stdout);
|
|
681
660
|
if (!text) {
|
|
@@ -684,8 +663,7 @@ async function callCodexText(request, cwd, opts) {
|
|
|
684
663
|
return text;
|
|
685
664
|
}
|
|
686
665
|
async function callOpenCodeText(request, cwd, opts) {
|
|
687
|
-
const
|
|
688
|
-
const args = ["run", "--format", "json", "--pure", "--agent", agent];
|
|
666
|
+
const args = ["run", "--format", "json"];
|
|
689
667
|
const model = opts.model?.trim();
|
|
690
668
|
if (model && model !== "default")
|
|
691
669
|
args.push("--model", model);
|
|
@@ -695,18 +673,7 @@ async function callOpenCodeText(request, cwd, opts) {
|
|
|
695
673
|
const stdout = await runCliText("opencode", args, contentWithSystemPrompt("opencode", request), {
|
|
696
674
|
cwd,
|
|
697
675
|
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
698
|
-
inheritEnv: opts.inheritEnv,
|
|
699
|
-
// A dedicated per-invocation agent and scalar deny replace rather than merge
|
|
700
|
-
// wildcard/specific allow rules. Preserve inherited provider/model config.
|
|
701
|
-
env: base => {
|
|
702
|
-
const configured = base.OPENCODE_CONFIG_CONTENT ? JSON.parse(base.OPENCODE_CONFIG_CONTENT) : {};
|
|
703
|
-
if (!configured || typeof configured !== "object" || Array.isArray(configured))
|
|
704
|
-
throw new Error("OpenCode 内联配置无效。");
|
|
705
|
-
return { ...base, OPENCODE_PERMISSION: JSON.stringify("deny"), OPENCODE_CONFIG_CONTENT: JSON.stringify({
|
|
706
|
-
...configured, permission: "deny", autoupdate: false, share: "disabled",
|
|
707
|
-
agent: { ...configured.agent, [agent]: { mode: "primary", permission: "deny", prompt: "Return only the requested text. Do not use tools." } },
|
|
708
|
-
}) };
|
|
709
|
-
},
|
|
676
|
+
inheritEnv: opts.inheritEnv,
|
|
710
677
|
});
|
|
711
678
|
const text = extractOpenCodeText(stdout);
|
|
712
679
|
if (!text)
|
|
@@ -717,7 +684,7 @@ async function callGrokText(request, cwd, opts) {
|
|
|
717
684
|
const stdout = await runCliText("grok", buildGrokTextArgs(request, opts), "", {
|
|
718
685
|
cwd,
|
|
719
686
|
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
720
|
-
inheritEnv: opts.inheritEnv,
|
|
687
|
+
inheritEnv: opts.inheritEnv,
|
|
721
688
|
});
|
|
722
689
|
const text = extractGrokText(stdout);
|
|
723
690
|
if (!text)
|
|
@@ -728,7 +695,7 @@ async function callQoderText(request, cwd, opts) {
|
|
|
728
695
|
const stdout = await runCliText("qodercli", buildQoderTextArgs(request, opts), "", {
|
|
729
696
|
cwd,
|
|
730
697
|
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
731
|
-
inheritEnv: opts.inheritEnv,
|
|
698
|
+
inheritEnv: opts.inheritEnv,
|
|
732
699
|
});
|
|
733
700
|
const text = extractQoderText(stdout);
|
|
734
701
|
if (!text)
|
|
@@ -737,42 +704,25 @@ async function callQoderText(request, cwd, opts) {
|
|
|
737
704
|
}
|
|
738
705
|
async function callPiText(request, cwd, opts) {
|
|
739
706
|
const stdout = await runCliText("pi", buildPiTextArgs(request, opts), "", {
|
|
740
|
-
cwd, timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts), inheritEnv: opts.inheritEnv,
|
|
707
|
+
cwd, timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts), inheritEnv: opts.inheritEnv,
|
|
741
708
|
});
|
|
742
709
|
const text = extractPiText(stdout);
|
|
743
710
|
if (!text)
|
|
744
711
|
throw new QuickCommitError("Pi 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
745
712
|
return text;
|
|
746
713
|
}
|
|
747
|
-
export function assertGeminiTextCapability() {
|
|
748
|
-
// Gemini ignores supplemental admin policy when a central policy exists.
|
|
749
|
-
// Refuse that unsupported text-only path rather than launching without deny.
|
|
750
|
-
const systemPolicies = process.platform === "darwin" ? "/Library/Application Support/GeminiCli/policies"
|
|
751
|
-
: process.platform === "win32" ? join(process.env.ProgramData ?? "C:\\ProgramData", "gemini-cli", "policies") : "/etc/gemini-cli/policies";
|
|
752
|
-
if (existsSync(systemPolicies) && readdirSync(systemPolicies).some(file => file.endsWith(".toml"))) {
|
|
753
|
-
throw new QuickCommitError("Gemini 中央策略阻止注入纯文本工具边界,请配置可用的文本候选。", "AI_FALLBACK_FAILED");
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
714
|
async function callGeminiText(request, cwd, opts) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
const text = extractGeminiText(stdout);
|
|
767
|
-
if (!text)
|
|
768
|
-
throw new QuickCommitError("Gemini 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
769
|
-
return text;
|
|
770
|
-
}
|
|
771
|
-
finally {
|
|
772
|
-
rmSync(root, { recursive: true, force: true });
|
|
773
|
-
}
|
|
715
|
+
const stdout = await runCliText("gemini", buildGeminiTextArgs(opts), contentWithSystemPrompt("gemini", request), {
|
|
716
|
+
cwd,
|
|
717
|
+
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
718
|
+
inheritEnv: opts.inheritEnv,
|
|
719
|
+
});
|
|
720
|
+
const text = extractGeminiText(stdout);
|
|
721
|
+
if (!text)
|
|
722
|
+
throw new QuickCommitError("Gemini 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
723
|
+
return text;
|
|
774
724
|
}
|
|
775
|
-
|
|
725
|
+
async function callCliAiText(request, cwd, language, opts) {
|
|
776
726
|
const provider = defaultProvider(opts.provider);
|
|
777
727
|
if (provider === "codex") {
|
|
778
728
|
return callCodexText(request, cwd, opts);
|
|
@@ -824,56 +774,28 @@ function singleCandidate(opts) {
|
|
|
824
774
|
* 只有一条候选时保持原行为(错误原样抛出,不摘要)。
|
|
825
775
|
* 仅用于一次性文本生成;执行工具的任务不能使用这条无条件重试路径。
|
|
826
776
|
*/
|
|
827
|
-
async function
|
|
828
|
-
opts.signal?.throwIfAborted();
|
|
777
|
+
async function callCliCandidates(request, cwd, language, opts, parseOutput) {
|
|
829
778
|
const { resolveModelGroupModels } = await import("./model-groups.js");
|
|
830
|
-
const modelGroups = opts.modelGroups ?? opts.employeeText?.config.modelGroups;
|
|
831
|
-
const seen = new Set();
|
|
832
779
|
const chain = (opts.cliCandidates?.length ? opts.cliCandidates : [singleCandidate(opts)])
|
|
833
|
-
.flatMap((candidate) => resolveModelGroupModels(modelGroups, candidate.provider, candidate.model, { preferDefault: !candidate.model || candidate.model === "default" })
|
|
834
|
-
.map((model) => ({ ...candidate, model: model || undefined })))
|
|
835
|
-
.filter(candidate => {
|
|
836
|
-
const key = JSON.stringify([candidate.provider, candidate.engine ?? "cli", candidate.model ?? "", candidate.thinkingEffort ?? opts.thinkingEffort]);
|
|
837
|
-
if (seen.has(key))
|
|
838
|
-
return false;
|
|
839
|
-
seen.add(key);
|
|
840
|
-
return true;
|
|
841
|
-
});
|
|
780
|
+
.flatMap((candidate) => resolveModelGroupModels(opts.modelGroups, candidate.provider, candidate.model, { preferDefault: !candidate.model || candidate.model === "default" })
|
|
781
|
+
.map((model) => ({ ...candidate, model: model || undefined })));
|
|
842
782
|
if (opts.employeeChannelOnly && !opts.cliCandidates?.length) {
|
|
843
|
-
throw new QuickCommitError("
|
|
783
|
+
throw new QuickCommitError("系统员工没有可用的 CLI 候选,已跳过 Wand Agent。", "AI_FALLBACK_FAILED");
|
|
844
784
|
}
|
|
845
785
|
const deadline = Date.now() + Math.max(1, opts.budgetMs ?? CLI_CHAIN_BUDGET_MS);
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const timer = setTimeout(() => controller.abort(new QuickCommitError("Wand Agent 调用超时。", "CLAUDE_TIMEOUT")), Math.max(1, deadline - Date.now()));
|
|
851
|
-
const signal = opts.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;
|
|
852
|
-
try {
|
|
853
|
-
const generate = await abortableEmployeeText(prepareEmployeeTextCandidate({
|
|
854
|
-
...opts.employeeText,
|
|
855
|
-
config: { ...opts.employeeText?.config, defaultCwd: cwd, inheritEnv: opts.inheritEnv ?? opts.employeeText?.config.inheritEnv, modelGroups },
|
|
856
|
-
}, { ...candidate, model: candidate.model ?? "default", thinkingEffort: candidate.thinkingEffort ?? opts.thinkingEffort ?? "off", mode: "default", kind: "structured" }, request, signal), signal);
|
|
857
|
-
signal.throwIfAborted();
|
|
858
|
-
return await abortableEmployeeText(generate(), signal);
|
|
859
|
-
}
|
|
860
|
-
finally {
|
|
861
|
-
clearTimeout(timer);
|
|
862
|
-
}
|
|
863
|
-
};
|
|
864
|
-
if (chain.length === 1)
|
|
865
|
-
return parseOutput(await call(chain[0]));
|
|
866
|
-
const installed = chain.filter((candidate) => candidate.engine === "sdk" || providerCliInstalled(candidate.provider));
|
|
786
|
+
if (chain.length === 1) {
|
|
787
|
+
return parseOutput(await callCliAiText(request, cwd, language, { ...candidateOptions(opts, chain[0]), deadline }));
|
|
788
|
+
}
|
|
789
|
+
const installed = chain.filter((candidate) => providerCliInstalled(candidate.provider));
|
|
867
790
|
const attempts = installed.length ? installed : chain;
|
|
868
791
|
const errors = [];
|
|
869
792
|
for (const candidate of attempts) {
|
|
870
|
-
opts.signal?.throwIfAborted();
|
|
871
793
|
if (Date.now() >= deadline) {
|
|
872
794
|
errors.push("已超过等待上限");
|
|
873
795
|
break;
|
|
874
796
|
}
|
|
875
797
|
try {
|
|
876
|
-
const text = await
|
|
798
|
+
const text = await callCliAiText(request, cwd, language, { ...candidateOptions(opts, candidate), deadline });
|
|
877
799
|
const result = await parseOutput(text);
|
|
878
800
|
if (candidate !== attempts[0]) {
|
|
879
801
|
console.info(`[SystemAi] 候选 ${chain.indexOf(candidate) + 1}/${chain.length} (${candidate.provider}) 成功`);
|
|
@@ -881,17 +803,16 @@ async function callTextCandidates(request, cwd, language, opts, parseOutput) {
|
|
|
881
803
|
return result;
|
|
882
804
|
}
|
|
883
805
|
catch (error) {
|
|
884
|
-
opts.signal?.throwIfAborted();
|
|
885
806
|
errors.push(`${candidate.provider}: ${getGitErrorMessage(error)}`);
|
|
886
807
|
// 不记录提示词、路径、CLI 原始响应或可能带鉴权信息的错误正文。
|
|
887
808
|
const code = error instanceof QuickCommitError ? error.code : "INVALID_AI_RESULT";
|
|
888
809
|
console.warn(`[SystemAi] 候选 ${chain.indexOf(candidate) + 1}/${chain.length} (${candidate.provider}) 失败: ${code}`);
|
|
889
810
|
}
|
|
890
811
|
}
|
|
891
|
-
throw new QuickCommitError(
|
|
812
|
+
throw new QuickCommitError(`所有 CLI 候选均失败:${errors.join(";")}`, "AI_FALLBACK_FAILED");
|
|
892
813
|
}
|
|
893
814
|
export async function callConfiguredAiText(request, cwd, language, opts, parseOutput = (raw) => raw) {
|
|
894
|
-
return
|
|
815
|
+
return callCliCandidates(withOpsPersona(request, opts.opsPersona), cwd, language, opts, parseOutput);
|
|
895
816
|
}
|
|
896
817
|
/** Read the unstaged + staged tree without touching the index. */
|
|
897
818
|
async function collectWorkingTreeDiff(cwd) {
|
|
@@ -3,7 +3,6 @@ import type { DecisionService } from "./decision-service.js";
|
|
|
3
3
|
import { type ModelFile } from "./laya-model-manifest.js";
|
|
4
4
|
import { runModelSetup } from "./model-setup-process.js";
|
|
5
5
|
import type { SpeechService } from "./speech-service.js";
|
|
6
|
-
import { type SpeechSupport } from "./speech-support.js";
|
|
7
6
|
import type { LocalModelKind, LocalModelsStatus } from "./local-model-types.js";
|
|
8
7
|
export interface LocalModelSetupDeps {
|
|
9
8
|
configDir: string;
|
|
@@ -18,7 +17,6 @@ export interface LocalModelSetupDeps {
|
|
|
18
17
|
run?: typeof runModelSetup;
|
|
19
18
|
checkRuntime?: (python: string, signal: AbortSignal) => Promise<boolean>;
|
|
20
19
|
timeoutMs?: number;
|
|
21
|
-
speechSupport?: (runtimeAvailable: boolean) => Promise<SpeechSupport>;
|
|
22
20
|
}
|
|
23
21
|
/** Deployment jobs only; existing inference owners retain their protocols, rate limits and capabilities. */
|
|
24
22
|
export declare class LocalModelSetupService {
|
|
@@ -30,18 +28,14 @@ export declare class LocalModelSetupService {
|
|
|
30
28
|
private readonly jobs;
|
|
31
29
|
private sequence;
|
|
32
30
|
private disposed;
|
|
33
|
-
private speechEnableRevision;
|
|
34
|
-
private supportCache;
|
|
35
31
|
constructor(deps: LocalModelSetupDeps);
|
|
36
32
|
private supported;
|
|
37
33
|
private managedModel;
|
|
38
34
|
private layaModel;
|
|
39
35
|
private python;
|
|
40
36
|
status(): Promise<LocalModelsStatus>;
|
|
41
|
-
private speechSupport;
|
|
42
|
-
setSpeechEnabled(enabled: boolean): Promise<void>;
|
|
43
37
|
private parse;
|
|
44
|
-
start(kind: LocalModelKind, action: "download" | "initialize"
|
|
38
|
+
start(kind: LocalModelKind, action: "download" | "initialize", raw: unknown): void;
|
|
45
39
|
private update;
|
|
46
40
|
private work;
|
|
47
41
|
private checkRuntime;
|
|
@@ -7,7 +7,6 @@ import { LAYA_FILES, LAYA_REPOSITORY, layaFileUrl } from "./laya-model-manifest.
|
|
|
7
7
|
import { ModelFileStore } from "./model-file-store.js";
|
|
8
8
|
import { runModelSetup } from "./model-setup-process.js";
|
|
9
9
|
import { speechModel } from "./speech-models.js";
|
|
10
|
-
import { speechSupport } from "./speech-support.js";
|
|
11
10
|
import { systemEnvValue } from "./env-utils.js";
|
|
12
11
|
import { decisionHardware } from "./decision-hardware.js";
|
|
13
12
|
import { DECISION_EXPERT_ID } from "./decision-expert-identity.js";
|
|
@@ -21,8 +20,6 @@ export class LocalModelSetupService {
|
|
|
21
20
|
jobs = new Map();
|
|
22
21
|
sequence = 0;
|
|
23
22
|
disposed = false;
|
|
24
|
-
speechEnableRevision = 0;
|
|
25
|
-
supportCache = null;
|
|
26
23
|
constructor(deps) {
|
|
27
24
|
this.deps = deps;
|
|
28
25
|
this.files = new ModelFileStore(deps.fetch);
|
|
@@ -58,7 +55,6 @@ export class LocalModelSetupService {
|
|
|
58
55
|
const hardware = decisionHardware();
|
|
59
56
|
const operation = this.operations.get("laya") ?? null;
|
|
60
57
|
const selected = speech.models.find((value) => value.id === speech.settings.model);
|
|
61
|
-
const support = await this.speechSupport(speech.runtime.available);
|
|
62
58
|
return {
|
|
63
59
|
laya: { kind: "laya", label: "LAYA 本地决策", supported,
|
|
64
60
|
reason: !supported ? hardware.message
|
|
@@ -66,38 +62,12 @@ export class LocalModelSetupService {
|
|
|
66
62
|
enabled: decision.enabled, model: LAYA_REPOSITORY, modelSize: this.manifest.reduce((sum, file) => sum + file.size, 0),
|
|
67
63
|
downloaded: model.downloaded, runtimeAvailable: !!python, initialized: decision.state === "ready", busy: this.jobs.has("laya") || decision.queued > 0 || decision.state === "loading",
|
|
68
64
|
operation: operation ? { ...operation } : null, hardware, decisionEmployeeId: DECISION_EXPERT_ID },
|
|
69
|
-
speech: { kind: "speech", label: "服务端语音识别", supported:
|
|
65
|
+
speech: { kind: "speech", label: "服务端语音识别", supported: ["darwin", "linux", "win32"].includes(process.platform), reason: speech.reason,
|
|
70
66
|
enabled: speech.settings.enabled, model: speech.settings.model, modelSize: selected?.size ?? 0,
|
|
71
67
|
downloaded: selected?.downloaded ?? false, runtimeAvailable: speech.runtime.available, initialized: speech.initialized === true,
|
|
72
68
|
busy: speech.busy || this.jobs.has("speech"), operation: this.operations.has("speech") ? { ...this.operations.get("speech") } : null },
|
|
73
69
|
};
|
|
74
70
|
}
|
|
75
|
-
speechSupport(runtimeAvailable) {
|
|
76
|
-
if (!this.supportCache || this.supportCache.expires < Date.now() || this.supportCache.runtimeAvailable !== runtimeAvailable) {
|
|
77
|
-
this.supportCache = { expires: Date.now() + 30_000, runtimeAvailable, value: (this.deps.speechSupport ?? speechSupport)(runtimeAvailable) };
|
|
78
|
-
}
|
|
79
|
-
return this.supportCache.value;
|
|
80
|
-
}
|
|
81
|
-
async setSpeechEnabled(enabled) {
|
|
82
|
-
const revision = ++this.speechEnableRevision;
|
|
83
|
-
const settingsRevision = this.deps.speech.settingsRevision();
|
|
84
|
-
if (this.disposed)
|
|
85
|
-
throw new DecisionError("UNAVAILABLE", "模型管理已关闭。", 503);
|
|
86
|
-
if (!enabled) {
|
|
87
|
-
this.cancel("speech");
|
|
88
|
-
this.deps.speech.configure({ enabled: false });
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const status = await this.deps.speech.status();
|
|
92
|
-
const support = await this.speechSupport(status.runtime.available);
|
|
93
|
-
if (revision !== this.speechEnableRevision || this.disposed || settingsRevision !== this.deps.speech.settingsRevision())
|
|
94
|
-
return;
|
|
95
|
-
if (!support.supported)
|
|
96
|
-
throw new DecisionError("UNSUPPORTED", support.reason || "本机不支持语音识别。", 409);
|
|
97
|
-
if (status.busy || status.download?.phase === "downloading")
|
|
98
|
-
throw new DecisionError("BUSY", "语音识别或模型下载正在进行,请稍后启用。", 409);
|
|
99
|
-
this.start("speech", "activate", {});
|
|
100
|
-
}
|
|
101
71
|
parse(kind, raw) {
|
|
102
72
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
103
73
|
throw new DecisionError("INVALID_REQUEST", "模型操作参数无效。");
|
|
@@ -120,8 +90,6 @@ export class LocalModelSetupService {
|
|
|
120
90
|
throw new DecisionError("UNAVAILABLE", "模型管理已关闭。", 503);
|
|
121
91
|
if (kind === "laya" && !this.supported())
|
|
122
92
|
throw new DecisionError("UNSUPPORTED", `LAYA-MLX 需要 Apple Silicon macOS 与 Metal;${decisionHardware().message}`, 409);
|
|
123
|
-
if (kind === "laya" && action === "activate")
|
|
124
|
-
throw new DecisionError("INVALID_REQUEST", "一键启用仅用于语音识别。");
|
|
125
93
|
const input = this.parse(kind, raw);
|
|
126
94
|
if (this.jobs.has(kind))
|
|
127
95
|
throw new DecisionError("BUSY", "该模型已有安装任务,请等待或取消。", 409);
|
|
@@ -151,8 +119,6 @@ export class LocalModelSetupService {
|
|
|
151
119
|
timer.unref();
|
|
152
120
|
const signal = abort.signal;
|
|
153
121
|
const phase = (value, message) => this.update(kind, id, { phase: value, message });
|
|
154
|
-
let activationMaintenance = false;
|
|
155
|
-
const settingsRevision = this.deps.speech.settingsRevision();
|
|
156
122
|
try {
|
|
157
123
|
if (kind === "laya") {
|
|
158
124
|
let model = await this.layaModel();
|
|
@@ -204,7 +170,7 @@ export class LocalModelSetupService {
|
|
|
204
170
|
}
|
|
205
171
|
else {
|
|
206
172
|
const selected = input.model ?? this.deps.speech.settings().model;
|
|
207
|
-
if (action === "download"
|
|
173
|
+
if (action === "download") {
|
|
208
174
|
phase("downloading", "下载并校验语音模型");
|
|
209
175
|
await this.deps.speech.startDownload(selected);
|
|
210
176
|
while (true) {
|
|
@@ -222,12 +188,7 @@ export class LocalModelSetupService {
|
|
|
222
188
|
});
|
|
223
189
|
}
|
|
224
190
|
}
|
|
225
|
-
|
|
226
|
-
if (action === "activate") {
|
|
227
|
-
signal.throwIfAborted();
|
|
228
|
-
this.deps.speech.acquireMaintenance();
|
|
229
|
-
activationMaintenance = true;
|
|
230
|
-
}
|
|
191
|
+
else {
|
|
231
192
|
const status = await this.deps.speech.status();
|
|
232
193
|
signal.throwIfAborted();
|
|
233
194
|
const backend = input.backend === "auto" || !input.backend ? process.platform === "darwin" ? "metal" : "cpu" : input.backend;
|
|
@@ -246,30 +207,22 @@ export class LocalModelSetupService {
|
|
|
246
207
|
await rm(work, { recursive: true, force: true }).catch(() => { });
|
|
247
208
|
}
|
|
248
209
|
}
|
|
249
|
-
phase("initializing",
|
|
250
|
-
await this.deps.speech.initializeModel(selected, signal
|
|
251
|
-
if (action === "activate") {
|
|
252
|
-
signal.throwIfAborted();
|
|
253
|
-
if (settingsRevision !== this.deps.speech.settingsRevision())
|
|
254
|
-
throw new DecisionError("SETTINGS_CHANGED", "语音设置已由其他操作修改,本次启用已取消。");
|
|
255
|
-
this.deps.speech.configure({ enabled: true, acceleration: "auto" });
|
|
256
|
-
}
|
|
210
|
+
phase("initializing", "加载所选语音模型并用合成静音检查,未启用识别或重启服务");
|
|
211
|
+
await this.deps.speech.initializeModel(selected, signal);
|
|
257
212
|
}
|
|
258
213
|
}
|
|
259
214
|
signal.throwIfAborted();
|
|
260
|
-
phase("completed", action === "
|
|
215
|
+
phase("completed", action === "download" ? "模型下载及完整性校验完成;可继续初始化" : "运行时和模型初始化检查完成;启用状态保持不变");
|
|
261
216
|
}
|
|
262
217
|
catch (error) {
|
|
263
218
|
const cancelled = signal.aborted;
|
|
264
219
|
const message = cancelled ? "安装任务已取消,完整文件保留,可重试" : error instanceof DecisionError || (error instanceof Error && /请先|校验|自定义|独立/.test(error.message))
|
|
265
220
|
? error.message : "安装或初始化失败,请检查网络、编译工具/Python版本、磁盘空间和平台能力,再重试。";
|
|
266
221
|
this.update(kind, id, { phase: cancelled ? "cancelled" : "failed", message, ...(cancelled ? {} : { error: message }) });
|
|
267
|
-
if (kind === "speech" &&
|
|
222
|
+
if (kind === "speech" && action === "download" && cancelled)
|
|
268
223
|
this.deps.speech.cancelDownload();
|
|
269
224
|
}
|
|
270
225
|
finally {
|
|
271
|
-
if (activationMaintenance)
|
|
272
|
-
this.deps.speech.releaseMaintenance();
|
|
273
226
|
clearTimeout(timer);
|
|
274
227
|
}
|
|
275
228
|
}
|
|
@@ -3,7 +3,7 @@ export type LocalModelKind = "laya" | "speech";
|
|
|
3
3
|
export type ModelSetupPhase = "downloading" | "verifying" | "runtime" | "initializing" | "completed" | "failed" | "cancelled";
|
|
4
4
|
export interface ModelSetupOperation {
|
|
5
5
|
id: number;
|
|
6
|
-
action: "download" | "initialize"
|
|
6
|
+
action: "download" | "initialize";
|
|
7
7
|
phase: ModelSetupPhase;
|
|
8
8
|
message: string;
|
|
9
9
|
received: number;
|
|
@@ -14,7 +14,6 @@ export interface LocalModelStatus {
|
|
|
14
14
|
kind: LocalModelKind;
|
|
15
15
|
label: string;
|
|
16
16
|
supported: boolean;
|
|
17
|
-
supportReason?: string | null;
|
|
18
17
|
reason: string | null;
|
|
19
18
|
enabled: boolean;
|
|
20
19
|
model: string;
|
|
@@ -3,7 +3,6 @@ import { EventEmitter } from "node:events";
|
|
|
3
3
|
import { WandStorage } from "./storage.js";
|
|
4
4
|
import { ExecutionMode, ProcessEventHandler, SessionProvider, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
|
|
5
5
|
import { type PermissionResolution } from "./claude-pty-bridge.js";
|
|
6
|
-
import type { EmployeeTextDeps } from "./employee-text.js";
|
|
7
6
|
import { type PtyTerminalSnapshot, type PtyHistoryPage } from "./pty-terminal-state.js";
|
|
8
7
|
import { samplePtyForegrounds } from "./pty-foreground.js";
|
|
9
8
|
import { type TerminalHost } from "./terminal-host.js";
|
|
@@ -35,7 +34,6 @@ export declare const PTY_TURN_IDLE_MS = 3000;
|
|
|
35
34
|
*/
|
|
36
35
|
export declare const PTY_FOREGROUND_SAMPLE_MS = 1000;
|
|
37
36
|
export interface ProcessManagerOptions {
|
|
38
|
-
free?: EmployeeTextDeps["free"];
|
|
39
37
|
/** Overrides the non-Claude PTY turn quiet window (tests use a short one). */
|
|
40
38
|
ptyTurnIdleMs?: number;
|
|
41
39
|
/** Overrides the foreground-process-group sampling interval (tests use a short one). */
|
|
@@ -57,7 +55,6 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
57
55
|
private readonly ptyForegroundSampleMs;
|
|
58
56
|
/** Foreground probe; injectable so tests can drive it without a real process table. */
|
|
59
57
|
private readonly probePtyForegrounds;
|
|
60
|
-
private readonly free;
|
|
61
58
|
private readonly resolveDecisionEvaluate;
|
|
62
59
|
/** 24h archive scan timer */
|
|
63
60
|
private archiveTimer;
|
|
@@ -180,7 +177,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
180
177
|
*/
|
|
181
178
|
setSessionModel(id: string, model: string | null): SessionSnapshot;
|
|
182
179
|
/**
|
|
183
|
-
*
|
|
180
|
+
* 归档会先停掉还在跑的终端,但保留会话 ID 和 provider session id,恢复时用它们续上。
|
|
184
181
|
* 取消归档只清标记;重新拉起由恢复入口负责。
|
|
185
182
|
*/
|
|
186
183
|
setSessionArchived(id: string, archived: boolean): SessionSnapshot;
|
package/dist/process-manager.js
CHANGED
|
@@ -563,7 +563,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
563
563
|
ptyForegroundSampleMs;
|
|
564
564
|
/** Foreground probe; injectable so tests can drive it without a real process table. */
|
|
565
565
|
probePtyForegrounds;
|
|
566
|
-
free;
|
|
567
566
|
resolveDecisionEvaluate;
|
|
568
567
|
/** 24h archive scan timer */
|
|
569
568
|
archiveTimer = null;
|
|
@@ -596,7 +595,6 @@ export class ProcessManager extends EventEmitter {
|
|
|
596
595
|
this.ptyTurnIdleMs = options.ptyTurnIdleMs ?? PTY_TURN_IDLE_MS;
|
|
597
596
|
this.ptyForegroundSampleMs = options.ptyForegroundSampleMs ?? PTY_FOREGROUND_SAMPLE_MS;
|
|
598
597
|
this.probePtyForegrounds = options.samplePtyForegrounds ?? samplePtyForegrounds;
|
|
599
|
-
this.free = options.free;
|
|
600
598
|
this.resolveDecisionEvaluate = options.resolveDecisionEvaluate ?? (() => undefined);
|
|
601
599
|
this.terminalHost = terminalHost ?? new InProcessTerminalHost();
|
|
602
600
|
this.logger = new SessionLogger(configDir || path.join(process.env.HOME || process.cwd(), ".wand"), config.shortcutLogMaxBytes);
|
|
@@ -1231,7 +1229,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
1231
1229
|
ai: resolveSystemAiContext({
|
|
1232
1230
|
provider: groupProvider, runner: undefined, command: providerCliCommand(groupProvider),
|
|
1233
1231
|
structuredState: undefined, selectedModel: null, thinkingEffort: opts?.thinkingEffort,
|
|
1234
|
-
}, this.config, this.storage.getSystemSiliconEmployee()
|
|
1232
|
+
}, this.config, this.storage.getSystemSiliconEmployee()),
|
|
1235
1233
|
cwd: resolveSessionCwd(cwd, this.config.defaultCwd),
|
|
1236
1234
|
language: this.config.language,
|
|
1237
1235
|
});
|
|
@@ -1777,10 +1775,13 @@ export class ProcessManager extends EventEmitter {
|
|
|
1777
1775
|
return this.snapshot(record);
|
|
1778
1776
|
}
|
|
1779
1777
|
/**
|
|
1780
|
-
*
|
|
1778
|
+
* 归档会先停掉还在跑的终端,但保留会话 ID 和 provider session id,恢复时用它们续上。
|
|
1781
1779
|
* 取消归档只清标记;重新拉起由恢复入口负责。
|
|
1782
1780
|
*/
|
|
1783
1781
|
setSessionArchived(id, archived) {
|
|
1782
|
+
if (archived && this.mustGet(id).status === "running") {
|
|
1783
|
+
this.stop(id);
|
|
1784
|
+
}
|
|
1784
1785
|
const record = this.mustGet(id);
|
|
1785
1786
|
record.archived = archived;
|
|
1786
1787
|
record.archivedAt = archived ? (record.archivedAt ?? new Date().toISOString()) : null;
|
|
@@ -2353,7 +2354,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
2353
2354
|
input: prompt,
|
|
2354
2355
|
cwd: record.cwd,
|
|
2355
2356
|
language: this.config.language,
|
|
2356
|
-
ai: resolveSystemAiContext(record, this.config, this.storage.getSystemSiliconEmployee()
|
|
2357
|
+
ai: resolveSystemAiContext(record, this.config, this.storage.getSystemSiliconEmployee()),
|
|
2357
2358
|
readNativeTitle: () => readNativeSessionTitle(resolveSessionProvider(record), record.claudeSessionId, record.cwd),
|
|
2358
2359
|
hasNativeTitle,
|
|
2359
2360
|
onGenerating: (generating) => {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Express } from "express";
|
|
2
2
|
import { type SiliconEmployee, type SiliconEmployeeDraft } from "./ai-team-types.js";
|
|
3
|
-
import type { OpenRouterFreeModelsService } from "./openrouter-free-models.js";
|
|
4
3
|
import type { WandStorage } from "./storage.js";
|
|
5
4
|
import type { WandTaskAgent } from "./task-types.js";
|
|
6
5
|
import type { WandConfig } from "./types.js";
|
|
@@ -18,7 +17,6 @@ export declare function registerSiliconEmployeeRoutes(app: Express, deps: {
|
|
|
18
17
|
storage: WandStorage;
|
|
19
18
|
notifyEmployeeChanged?: (employeeId: string) => void;
|
|
20
19
|
config?: WandConfig;
|
|
21
|
-
free?: Pick<OpenRouterFreeModelsService, "resolveForCall">;
|
|
22
20
|
/** 测试注入点:默认按系统 AI 配置真实调用模型。 */
|
|
23
21
|
generateDraft?: (request: EmployeeDraftRequest) => Promise<SiliconEmployeeDraft>;
|
|
24
22
|
/** 测试注入点:CLI 可用性探测。 */
|