@co0ontty/wand 4.81.0 → 4.82.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.js +7 -4
- package/dist/ai-team-prompts.d.ts +1 -1
- package/dist/ai-team-prompts.js +2 -2
- package/dist/ai-team-runner.d.ts +2 -0
- package/dist/ai-team-runner.js +60 -7
- package/dist/ai-team-types.d.ts +6 -0
- package/dist/ai-team-types.js +11 -0
- package/dist/build-info.json +3 -3
- package/dist/cli.js +11 -0
- package/dist/config.js +2 -0
- package/dist/default-employee.d.ts +11 -0
- package/dist/default-employee.js +33 -0
- package/dist/employee-knowledge-cli.d.ts +2 -0
- package/dist/employee-knowledge-cli.js +52 -0
- package/dist/employee-knowledge-content.d.ts +4 -0
- package/dist/employee-knowledge-content.js +23 -0
- package/dist/employee-knowledge-types.d.ts +15 -0
- package/dist/employee-knowledge-types.js +3 -0
- package/dist/employee-knowledge.d.ts +6 -0
- package/dist/employee-knowledge.js +84 -0
- package/dist/git-quick-commit.d.ts +2 -0
- package/dist/git-quick-commit.js +121 -58
- package/dist/iteration-log.d.ts +5 -0
- package/dist/iteration-log.js +12 -1
- package/dist/message-truncator.js +13 -9
- package/dist/models.d.ts +4 -0
- package/dist/models.js +38 -10
- package/dist/pi-model-discovery.d.ts +12 -0
- package/dist/pi-model-discovery.js +281 -0
- package/dist/process-manager.d.ts +23 -1
- package/dist/process-manager.js +68 -1
- package/dist/prompt-optimizer.js +10 -11
- package/dist/pty-turn-activity.d.ts +27 -0
- package/dist/pty-turn-activity.js +31 -0
- package/dist/server-employee-routes.js +52 -6
- package/dist/server-session-routes.js +11 -5
- package/dist/server-task-routes.js +6 -1
- package/dist/server-user-memory.d.ts +9 -0
- package/dist/server-user-memory.js +96 -0
- package/dist/server.js +22 -2
- package/dist/session-topic.js +6 -5
- package/dist/silicon-employee-draft.js +1 -3
- package/dist/storage.d.ts +38 -0
- package/dist/storage.js +276 -3
- package/dist/structured-claude-adapter.d.ts +1 -1
- package/dist/structured-claude-adapter.js +2 -0
- package/dist/structured-provider-common.d.ts +2 -2
- package/dist/structured-provider-common.js +6 -6
- package/dist/structured-session-manager.js +5 -4
- package/dist/task-title.js +8 -7
- package/dist/team-report-file-link.d.ts +6 -0
- package/dist/team-report-file-link.js +54 -0
- package/dist/team-report-preview.d.ts +3 -0
- package/dist/team-report-preview.js +70 -0
- package/dist/types.d.ts +15 -0
- package/dist/user-memory-types.d.ts +42 -0
- package/dist/user-memory-types.js +6 -0
- package/dist/user-memory.d.ts +30 -0
- package/dist/user-memory.js +190 -0
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/scripts.js +59 -61
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/package.json +1 -1
package/dist/git-quick-commit.js
CHANGED
|
@@ -3,6 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { buildChildEnv } from "./env-utils.js";
|
|
4
4
|
import { buildLanguageDirective } from "./language-prompt.js";
|
|
5
5
|
import { isSessionProvider, providerCliInstalled } from "./session-provider.js";
|
|
6
|
+
import { asRecord } from "./structured-content.js";
|
|
6
7
|
import { runGitAsync as runGitAsyncBase, runGitRawAsync as runGitRawAsyncBase, getGitErrorMessage, } from "./git-utils.js";
|
|
7
8
|
import { composeSystemFallback, systemPromptFlag, thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToGrokEffort, thinkingEffortToOpenCodeVariant, thinkingEffortToQoderEffort, thinkingEffortToPiLevel, } from "./structured-provider-common.js";
|
|
8
9
|
/** provider 缺省值:无法识别时回落到 Claude。 */
|
|
@@ -277,7 +278,9 @@ export class QuickCommitError extends Error {
|
|
|
277
278
|
this.name = "QuickCommitError";
|
|
278
279
|
}
|
|
279
280
|
}
|
|
280
|
-
|
|
281
|
+
function cliTextTimeoutMs(defaultMs, opts) {
|
|
282
|
+
return opts.deadline === undefined ? defaultMs : Math.min(defaultMs, Math.max(1, opts.deadline - Date.now()));
|
|
283
|
+
}
|
|
281
284
|
/**
|
|
282
285
|
* 一次性 Claude 文本生成(commit message / tag / 提示词优化…)。
|
|
283
286
|
*
|
|
@@ -302,7 +305,7 @@ async function callClaudeText(request, cwd, language, opts) {
|
|
|
302
305
|
args.push("--effort", effort);
|
|
303
306
|
const stdout = await runCliText("claude", args, request.prompt, {
|
|
304
307
|
cwd,
|
|
305
|
-
timeoutMs: CLAUDE_MESSAGE_TIMEOUT_MS,
|
|
308
|
+
timeoutMs: cliTextTimeoutMs(CLAUDE_MESSAGE_TIMEOUT_MS, opts),
|
|
306
309
|
inheritEnv: opts.inheritEnv,
|
|
307
310
|
});
|
|
308
311
|
const text = stdout.trim();
|
|
@@ -316,7 +319,72 @@ function stripFences(raw) {
|
|
|
316
319
|
function normalizeAiText(raw) {
|
|
317
320
|
return stripFences(raw).replace(/^["'`]+|["'`]+$/g, "").trim();
|
|
318
321
|
}
|
|
322
|
+
/** Read explicit protocol failures, not error-looking prose that could be legitimate output. */
|
|
323
|
+
function cliProtocolErrorText(value) {
|
|
324
|
+
if (typeof value === "string")
|
|
325
|
+
return value.trim();
|
|
326
|
+
if (Array.isArray(value))
|
|
327
|
+
return value.map(cliProtocolErrorText).filter(Boolean).join("\n");
|
|
328
|
+
const record = asRecord(value);
|
|
329
|
+
if (!record)
|
|
330
|
+
return "";
|
|
331
|
+
for (const key of ["message", "error", "errors", "data", "result"]) {
|
|
332
|
+
const text = cliProtocolErrorText(record[key]);
|
|
333
|
+
if (text)
|
|
334
|
+
return text;
|
|
335
|
+
}
|
|
336
|
+
return "";
|
|
337
|
+
}
|
|
338
|
+
/** A zero process exit does not imply a successful model response. */
|
|
339
|
+
function assertCliProtocolSucceeded(stdout, provider) {
|
|
340
|
+
let failure = null;
|
|
341
|
+
const failed = (value) => cliProtocolErrorText(value) || `${provider} CLI execution failed`;
|
|
342
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
343
|
+
let event;
|
|
344
|
+
try {
|
|
345
|
+
event = asRecord(JSON.parse(line));
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (!event)
|
|
351
|
+
continue;
|
|
352
|
+
if (provider === "codex") {
|
|
353
|
+
if (event.type === "error" || event.type === "turn.failed")
|
|
354
|
+
failure = failed(event);
|
|
355
|
+
if (event.type === "turn.completed")
|
|
356
|
+
failure = null;
|
|
357
|
+
}
|
|
358
|
+
else if (provider === "qoder" && event.type === "result") {
|
|
359
|
+
failure = event.is_error === true || event.subtype !== "success" ? failed(event) : null;
|
|
360
|
+
}
|
|
361
|
+
else if (provider === "pi") {
|
|
362
|
+
const messages = event.type === "agent_end" && Array.isArray(event.messages)
|
|
363
|
+
? event.messages
|
|
364
|
+
: event.type === "message_end" || event.type === "turn_end" ? [event.message] : [];
|
|
365
|
+
for (const raw of messages) {
|
|
366
|
+
const message = asRecord(raw);
|
|
367
|
+
if (message?.role !== "assistant")
|
|
368
|
+
continue;
|
|
369
|
+
failure = message.stopReason === "error" || message.stopReason === "aborted"
|
|
370
|
+
? failed(message.errorMessage) : null;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else if (provider === "gemini") {
|
|
374
|
+
if (event.type === "error" && event.severity === "error")
|
|
375
|
+
failure = failed(event);
|
|
376
|
+
if (event.type === "result")
|
|
377
|
+
failure = event.status === "error" ? failed(event) : null;
|
|
378
|
+
}
|
|
379
|
+
else if ((provider === "grok" || provider === "opencode") && event.type === "error") {
|
|
380
|
+
failure = failed(event);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (failure)
|
|
384
|
+
throw new QuickCommitError(`${provider} CLI 失败:${failure}`, "CLAUDE_CLI_FAILED");
|
|
385
|
+
}
|
|
319
386
|
function extractCodexText(stdout) {
|
|
387
|
+
assertCliProtocolSucceeded(stdout, "codex");
|
|
320
388
|
let lastAgentText = "";
|
|
321
389
|
for (const line of stdout.split(/\r?\n/)) {
|
|
322
390
|
const trimmed = line.trim();
|
|
@@ -343,6 +411,7 @@ function extractCodexText(stdout) {
|
|
|
343
411
|
return "";
|
|
344
412
|
}
|
|
345
413
|
function extractOpenCodeText(stdout) {
|
|
414
|
+
assertCliProtocolSucceeded(stdout, "opencode");
|
|
346
415
|
const texts = [];
|
|
347
416
|
for (const line of stdout.split(/\r?\n/)) {
|
|
348
417
|
const trimmed = line.trim();
|
|
@@ -361,6 +430,7 @@ function extractOpenCodeText(stdout) {
|
|
|
361
430
|
return texts.join("\n").trim();
|
|
362
431
|
}
|
|
363
432
|
function extractGrokText(stdout) {
|
|
433
|
+
assertCliProtocolSucceeded(stdout, "grok");
|
|
364
434
|
let text = "";
|
|
365
435
|
for (const line of stdout.split(/\r?\n/)) {
|
|
366
436
|
const trimmed = line.trim();
|
|
@@ -378,6 +448,7 @@ function extractGrokText(stdout) {
|
|
|
378
448
|
return text.trim();
|
|
379
449
|
}
|
|
380
450
|
function extractQoderText(stdout) {
|
|
451
|
+
assertCliProtocolSucceeded(stdout, "qoder");
|
|
381
452
|
let resultText = "";
|
|
382
453
|
const assistantTexts = [];
|
|
383
454
|
for (const line of stdout.split(/\r?\n/)) {
|
|
@@ -408,6 +479,7 @@ function extractQoderText(stdout) {
|
|
|
408
479
|
return (resultText || assistantTexts.join("\n")).trim();
|
|
409
480
|
}
|
|
410
481
|
function extractPiText(stdout) {
|
|
482
|
+
assertCliProtocolSucceeded(stdout, "pi");
|
|
411
483
|
let text = "";
|
|
412
484
|
for (const line of stdout.split(/\r?\n/)) {
|
|
413
485
|
try {
|
|
@@ -425,6 +497,7 @@ function extractPiText(stdout) {
|
|
|
425
497
|
}
|
|
426
498
|
/** Gemini `--output-format stream-json`:assistant message 是增量 chunk,拼接即可。 */
|
|
427
499
|
function extractGeminiText(stdout) {
|
|
500
|
+
assertCliProtocolSucceeded(stdout, "gemini");
|
|
428
501
|
let text = "";
|
|
429
502
|
for (const line of stdout.split(/\r?\n/)) {
|
|
430
503
|
const trimmed = line.trim();
|
|
@@ -574,7 +647,7 @@ async function callCodexText(request, cwd, opts) {
|
|
|
574
647
|
args.push("-");
|
|
575
648
|
const stdout = await runCliText("codex", args, contentWithSystemPrompt("codex", request), {
|
|
576
649
|
cwd,
|
|
577
|
-
timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
|
|
650
|
+
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
578
651
|
inheritEnv: opts.inheritEnv,
|
|
579
652
|
});
|
|
580
653
|
const text = extractCodexText(stdout);
|
|
@@ -593,7 +666,7 @@ async function callOpenCodeText(request, cwd, opts) {
|
|
|
593
666
|
args.push("--variant", variant);
|
|
594
667
|
const stdout = await runCliText("opencode", args, contentWithSystemPrompt("opencode", request), {
|
|
595
668
|
cwd,
|
|
596
|
-
timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
|
|
669
|
+
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
597
670
|
inheritEnv: opts.inheritEnv,
|
|
598
671
|
});
|
|
599
672
|
const text = extractOpenCodeText(stdout);
|
|
@@ -604,7 +677,7 @@ async function callOpenCodeText(request, cwd, opts) {
|
|
|
604
677
|
async function callGrokText(request, cwd, opts) {
|
|
605
678
|
const stdout = await runCliText("grok", buildGrokTextArgs(request, opts), "", {
|
|
606
679
|
cwd,
|
|
607
|
-
timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
|
|
680
|
+
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
608
681
|
inheritEnv: opts.inheritEnv,
|
|
609
682
|
});
|
|
610
683
|
const text = extractGrokText(stdout);
|
|
@@ -615,7 +688,7 @@ async function callGrokText(request, cwd, opts) {
|
|
|
615
688
|
async function callQoderText(request, cwd, opts) {
|
|
616
689
|
const stdout = await runCliText("qodercli", buildQoderTextArgs(request, opts), "", {
|
|
617
690
|
cwd,
|
|
618
|
-
timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
|
|
691
|
+
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
619
692
|
inheritEnv: opts.inheritEnv,
|
|
620
693
|
});
|
|
621
694
|
const text = extractQoderText(stdout);
|
|
@@ -624,7 +697,9 @@ async function callQoderText(request, cwd, opts) {
|
|
|
624
697
|
return text;
|
|
625
698
|
}
|
|
626
699
|
async function callPiText(request, cwd, opts) {
|
|
627
|
-
const stdout = await runCliText("pi", buildPiTextArgs(request, opts), "", {
|
|
700
|
+
const stdout = await runCliText("pi", buildPiTextArgs(request, opts), "", {
|
|
701
|
+
cwd, timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts), inheritEnv: opts.inheritEnv,
|
|
702
|
+
});
|
|
628
703
|
const text = extractPiText(stdout);
|
|
629
704
|
if (!text)
|
|
630
705
|
throw new QuickCommitError("Pi 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
@@ -633,7 +708,7 @@ async function callPiText(request, cwd, opts) {
|
|
|
633
708
|
async function callGeminiText(request, cwd, opts) {
|
|
634
709
|
const stdout = await runCliText("gemini", buildGeminiTextArgs(opts), contentWithSystemPrompt("gemini", request), {
|
|
635
710
|
cwd,
|
|
636
|
-
timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
|
|
711
|
+
timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts),
|
|
637
712
|
inheritEnv: opts.inheritEnv,
|
|
638
713
|
});
|
|
639
714
|
const text = extractGeminiText(stdout);
|
|
@@ -689,41 +764,43 @@ function singleCandidate(opts) {
|
|
|
689
764
|
};
|
|
690
765
|
}
|
|
691
766
|
/**
|
|
692
|
-
* 按顺序尝试候选:跳过没安装的 CLI
|
|
767
|
+
* 按顺序尝试候选:跳过没安装的 CLI,协议与输出校验均成功才返回。
|
|
693
768
|
* 只有一条候选时保持原行为(错误原样抛出,不摘要)。
|
|
769
|
+
* 仅用于一次性文本生成;执行工具的任务不能使用这条无条件重试路径。
|
|
694
770
|
*/
|
|
695
|
-
async function callCliCandidates(request, cwd, language, opts) {
|
|
771
|
+
async function callCliCandidates(request, cwd, language, opts, parseOutput) {
|
|
696
772
|
const chain = opts.cliCandidates?.length ? opts.cliCandidates : [singleCandidate(opts)];
|
|
697
773
|
if (chain.length === 1) {
|
|
698
|
-
return callCliAiText(request, cwd, language, candidateOptions(opts, chain[0]));
|
|
774
|
+
return parseOutput(await callCliAiText(request, cwd, language, candidateOptions(opts, chain[0])));
|
|
699
775
|
}
|
|
700
776
|
const installed = chain.filter((candidate) => providerCliInstalled(candidate.provider));
|
|
701
777
|
const attempts = installed.length ? installed : chain;
|
|
702
778
|
const deadline = Date.now() + CLI_CHAIN_BUDGET_MS;
|
|
703
779
|
const errors = [];
|
|
704
780
|
for (const candidate of attempts) {
|
|
705
|
-
if (Date.now()
|
|
781
|
+
if (Date.now() >= deadline) {
|
|
706
782
|
errors.push("已超过等待上限");
|
|
707
783
|
break;
|
|
708
784
|
}
|
|
709
785
|
try {
|
|
710
|
-
const text = await callCliAiText(request, cwd, language, candidateOptions(opts, candidate));
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
786
|
+
const text = await callCliAiText(request, cwd, language, { ...candidateOptions(opts, candidate), deadline });
|
|
787
|
+
const result = await parseOutput(text);
|
|
788
|
+
if (candidate !== attempts[0]) {
|
|
789
|
+
console.info(`[SystemAi] 候选 ${chain.indexOf(candidate) + 1}/${chain.length} (${candidate.provider}) 成功`);
|
|
790
|
+
}
|
|
791
|
+
return result;
|
|
714
792
|
}
|
|
715
793
|
catch (error) {
|
|
716
794
|
errors.push(`${candidate.provider}: ${getGitErrorMessage(error)}`);
|
|
795
|
+
// 不记录提示词、路径、CLI 原始响应或可能带鉴权信息的错误正文。
|
|
796
|
+
const code = error instanceof QuickCommitError ? error.code : "INVALID_AI_RESULT";
|
|
797
|
+
console.warn(`[SystemAi] 候选 ${chain.indexOf(candidate) + 1}/${chain.length} (${candidate.provider}) 失败: ${code}`);
|
|
717
798
|
}
|
|
718
799
|
}
|
|
719
800
|
throw new QuickCommitError(`所有 CLI 候选均失败:${errors.join(";")}`, "AI_FALLBACK_FAILED");
|
|
720
801
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
* 所有 Wand 自有文本调用都走本机已安装的 CLI,没有任何直连 API 分支。
|
|
724
|
-
*/
|
|
725
|
-
export async function callConfiguredAiText(request, cwd, language, opts) {
|
|
726
|
-
return callCliCandidates(withOpsPersona(request, opts.opsPersona), cwd, language, opts);
|
|
802
|
+
export async function callConfiguredAiText(request, cwd, language, opts, parseOutput = (raw) => raw) {
|
|
803
|
+
return callCliCandidates(withOpsPersona(request, opts.opsPersona), cwd, language, opts, parseOutput);
|
|
727
804
|
}
|
|
728
805
|
/** Read the unstaged + staged tree without touching the index. */
|
|
729
806
|
async function collectWorkingTreeDiff(cwd) {
|
|
@@ -814,12 +891,12 @@ function commitTaskLine(lang, usedIteration) {
|
|
|
814
891
|
async function generateCommitMessage(cwd, language, ai = {}, options = {}) {
|
|
815
892
|
const { input, usedIteration } = await buildCommitPromptInput(cwd, options);
|
|
816
893
|
const lang = language.trim() || "中文";
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
894
|
+
return callConfiguredAiText({ system: commitTaskLine(lang, usedIteration), prompt: input }, cwd, language, ai, (raw) => {
|
|
895
|
+
const message = normalizeAiText(raw);
|
|
896
|
+
if (!message)
|
|
897
|
+
throw new QuickCommitError("AI 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
898
|
+
return message;
|
|
899
|
+
});
|
|
823
900
|
}
|
|
824
901
|
function tryParseJson(raw) {
|
|
825
902
|
let text = raw.trim();
|
|
@@ -867,23 +944,15 @@ async function generateCommitMessageWithTag(cwd, language, ai = {}, options = {}
|
|
|
867
944
|
|
|
868
945
|
请严格输出**单行 JSON 对象**,不要 Markdown 代码块、不要任何解释文字、不要多余引号。格式:
|
|
869
946
|
{"message":"...","tag":"v1.2.3"}`;
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
// Fallback: treat whole output as message, no tag suggestion
|
|
880
|
-
message = normalizeAiText(raw);
|
|
881
|
-
suggestedTag = undefined;
|
|
882
|
-
}
|
|
883
|
-
if (!message) {
|
|
884
|
-
throw new QuickCommitError("AI 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
885
|
-
}
|
|
886
|
-
return { message, suggestedTag };
|
|
947
|
+
return callConfiguredAiText({ system, prompt: `${tagHint}\n\n${input}` }, cwd, language, ai, (raw) => {
|
|
948
|
+
const parsed = tryParseJson(raw);
|
|
949
|
+
// Preserve the plain-message fallback for older CLIs that do not return JSON.
|
|
950
|
+
const message = normalizeAiText(parsed && typeof parsed.message === "string" ? parsed.message : raw);
|
|
951
|
+
const suggestedTag = parsed && typeof parsed.message === "string" ? sanitizeSuggestedTag(parsed.tag) : undefined;
|
|
952
|
+
if (!message)
|
|
953
|
+
throw new QuickCommitError("AI 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
954
|
+
return { message, suggestedTag };
|
|
955
|
+
});
|
|
887
956
|
}
|
|
888
957
|
export async function generateCommitMessageOnly(cwd, language, ai = {}, options = {}) {
|
|
889
958
|
if (!cwd || !existsSync(cwd)) {
|
|
@@ -932,19 +1001,13 @@ async function generateTagAfterCommit(cwd, language, commitMessage, ai = {}) {
|
|
|
932
1001
|
请用${lang}思考但严格输出**单行 JSON 对象**,不要 Markdown 代码块、不要任何解释文字、不要多余引号。格式:
|
|
933
1002
|
{"tag":"v1.2.3"}`;
|
|
934
1003
|
const prompt = `${tagHint}\n\ncommit message:${commitMessage}\n\ngit diff:\n${diff}`;
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
suggested = sanitizeSuggestedTag(raw);
|
|
943
|
-
}
|
|
944
|
-
if (!suggested) {
|
|
945
|
-
throw new QuickCommitError("AI 没有给出合法的 tag,请手动填写。", "INVALID_AI_TAG");
|
|
946
|
-
}
|
|
947
|
-
return suggested;
|
|
1004
|
+
return callConfiguredAiText({ system, prompt }, cwd, language, ai, (raw) => {
|
|
1005
|
+
const parsed = tryParseJson(raw);
|
|
1006
|
+
const suggested = sanitizeSuggestedTag(parsed && typeof parsed.tag === "string" ? parsed.tag : raw);
|
|
1007
|
+
if (!suggested)
|
|
1008
|
+
throw new QuickCommitError("AI 没有给出合法的 tag,请手动填写。", "INVALID_AI_TAG");
|
|
1009
|
+
return suggested;
|
|
1010
|
+
});
|
|
948
1011
|
}
|
|
949
1012
|
/**
|
|
950
1013
|
* Push current branch (with upstream auto-setup) and/or tags.
|
package/dist/iteration-log.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { WandStorage } from "./storage.js";
|
|
2
|
+
import type { SessionSource } from "./types.js";
|
|
2
3
|
import { type WandIterationPrompt, type WandIterationPromptSource } from "./task-types.js";
|
|
3
4
|
/** commit 生成的输入模式:iteration = 只用本轮提示词(默认,省 token);diff = 读完整 diff。 */
|
|
4
5
|
export type CommitContextMode = "iteration" | "diff";
|
|
@@ -13,6 +14,10 @@ export interface IterationPromptSession {
|
|
|
13
14
|
cwd?: string;
|
|
14
15
|
workspaceId?: string;
|
|
15
16
|
workspaceTaskId?: string;
|
|
17
|
+
sessionSource?: SessionSource;
|
|
18
|
+
automationId?: string;
|
|
19
|
+
interactiveShell?: boolean;
|
|
20
|
+
employeeId?: string;
|
|
16
21
|
}
|
|
17
22
|
export interface IterationPromptTarget {
|
|
18
23
|
milestoneId: string;
|
package/dist/iteration-log.js
CHANGED
|
@@ -9,6 +9,9 @@ import path from "node:path";
|
|
|
9
9
|
import { getErrorMessage } from "./error-utils.js";
|
|
10
10
|
import { runGitAsync } from "./git-utils.js";
|
|
11
11
|
import { shouldGenerateSessionTopicFromInput, summarizeSessionTitleFromInput } from "./session-topic.js";
|
|
12
|
+
import { recordUserMemory } from "./user-memory.js";
|
|
13
|
+
import { DEFAULT_EMPLOYEE_ID } from "./ai-team-types.js";
|
|
14
|
+
import { explicitEmployeeMemoryContent } from "./employee-knowledge-content.js";
|
|
12
15
|
import { WAND_ITERATION_PROMPT_DETAIL_MAX_LENGTH, WAND_ITERATION_PROMPT_TITLE_MAX_LENGTH, } from "./task-types.js";
|
|
13
16
|
/** 用户在上次选择后记住的模式;两条互不影响,互相切不会被覆盖。 */
|
|
14
17
|
export const COMMIT_CONTEXT_MODE_PREF_KEY = "pref:commitContextMode";
|
|
@@ -119,11 +122,19 @@ export function whenIterationPromptsSettled() {
|
|
|
119
122
|
*/
|
|
120
123
|
export function recordIterationPrompt(storage, session, prompt, source = "session", options = {}) {
|
|
121
124
|
const raw = prompt?.trim();
|
|
122
|
-
if (!raw)
|
|
125
|
+
if (!raw || explicitEmployeeMemoryContent(raw))
|
|
123
126
|
return;
|
|
127
|
+
// 明确记忆不是代码变更意图,不进入共享迭代/commit 摘要。
|
|
124
128
|
// 与会话标题共用同一把尺子:太短的输入不值得进迭代记录。
|
|
125
129
|
if (!options.skipFilter && !shouldGenerateSessionTopicFromInput(raw))
|
|
126
130
|
return;
|
|
131
|
+
// Only real interactive user prompts; never learn from team-generated instructions or shell input.
|
|
132
|
+
if (((!session.sessionSource || session.sessionSource === "interactive") && !session.automationId
|
|
133
|
+
|| session.automationId?.startsWith("wand-task:"))
|
|
134
|
+
&& (!session.employeeId || session.employeeId === DEFAULT_EMPLOYEE_ID)
|
|
135
|
+
&& !session.interactiveShell && !/^[\/!]/.test(raw)) {
|
|
136
|
+
recordUserMemory(storage, "session.prompt", raw, session.id);
|
|
137
|
+
}
|
|
127
138
|
const run = async () => {
|
|
128
139
|
try {
|
|
129
140
|
const detail = clip(cleanIterationPromptText(raw), WAND_ITERATION_PROMPT_DETAIL_MAX_LENGTH);
|
|
@@ -378,19 +378,23 @@ function toolActivity(use, hasImage) {
|
|
|
378
378
|
const occurredAt = use.occurredAt ?? use.activity?.occurredAt;
|
|
379
379
|
return {
|
|
380
380
|
kind,
|
|
381
|
-
label: activityLabel(kind),
|
|
381
|
+
label: activityLabel(kind, use, path),
|
|
382
382
|
...(fileKey ? { fileKey } : {}),
|
|
383
383
|
...(hasImage || imagePath || use.activity?.hasImage ? { hasImage: true } : {}),
|
|
384
|
-
...(
|
|
384
|
+
...(occurredAt ? { occurredAt } : {}),
|
|
385
385
|
};
|
|
386
386
|
}
|
|
387
|
-
function activityLabel(kind) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
387
|
+
function activityLabel(kind, use, path) {
|
|
388
|
+
// Only a bounded file/tool identity belongs in the timeline, never commands,
|
|
389
|
+
// patches, search queries or output. Keep labels stable when projecting twice.
|
|
390
|
+
if (!path && use.activity?.label)
|
|
391
|
+
return use.activity.label;
|
|
392
|
+
const file = path.replace(/\\/g, "/").split("/").filter(Boolean).slice(-2).join("/");
|
|
393
|
+
const name = use.name.replace(/\s+/g, " ").trim().slice(0, 80) || "工具";
|
|
394
|
+
const label = kind === "edit_file" ? `修改 ${file}`
|
|
395
|
+
: kind === "read_file" ? `查看 ${file}`
|
|
396
|
+
: kind === "run_command" ? `运行命令 · ${name}` : `调用 ${name}`;
|
|
397
|
+
return label.length > 120 ? `${label.slice(0, 119)}…` : label;
|
|
394
398
|
}
|
|
395
399
|
const INDEPENDENT_TOOL_NAMES = new Set([
|
|
396
400
|
"AskUserQuestion", "TodoWrite", "TaskCreate", "TaskUpdate", "TaskList",
|
package/dist/models.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PiModelEndpointDiscoveryOptions } from "./pi-model-discovery.js";
|
|
1
2
|
import { ClaudeModelInfo } from "./types.js";
|
|
2
3
|
/**
|
|
3
4
|
* The complete server-side model catalog. Keep this separate from the Claude
|
|
@@ -36,6 +37,9 @@ export interface ModelRefreshOptions {
|
|
|
36
37
|
commandRunner?: ModelCommandRunner;
|
|
37
38
|
modelsApi?: ClaudeModelsApi;
|
|
38
39
|
verifyClaudeCandidates?: boolean;
|
|
40
|
+
piEndpointDiscovery?: PiModelEndpointDiscoveryOptions & {
|
|
41
|
+
enabled: boolean;
|
|
42
|
+
};
|
|
39
43
|
now?: () => Date;
|
|
40
44
|
}
|
|
41
45
|
export interface ThinkingEffortLevel {
|
package/dist/models.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import Anthropic from "@anthropic-ai/sdk";
|
|
5
5
|
import { resolveChildEnv } from "./env-utils.js";
|
|
6
|
+
import { discoverPiEndpointModels } from "./pi-model-discovery.js";
|
|
6
7
|
import { extractSemver } from "./version-utils.js";
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
const CLAUDE_VERIFICATION_CACHE_KEY = "claude-model-verifications-v1";
|
|
@@ -384,20 +385,47 @@ async function probeQoderModels(runner, env) {
|
|
|
384
385
|
return { ok: false };
|
|
385
386
|
}
|
|
386
387
|
}
|
|
387
|
-
async function probePiModels(runner, env) {
|
|
388
|
+
async function probePiModels(runner, env, options) {
|
|
388
389
|
const rpcText = await commandText(runner, "pi", ["--mode", "rpc", "--no-session"], env, 12_000, `${JSON.stringify({ id: "wand-models", type: "get_available_models" })}\n`);
|
|
389
390
|
const fromRpc = parsePiRpcModels(rpcText);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
391
|
+
let configuredModels = fromRpc.length > 1 || (fromRpc.length === 1 && fromRpc[0]?.id !== "default")
|
|
392
|
+
? fromRpc
|
|
393
|
+
: [];
|
|
394
|
+
if (!configuredModels.length) {
|
|
395
|
+
try {
|
|
396
|
+
const { stdout } = await runner("pi", ["--list-models"], { env, timeout: 8000 });
|
|
397
|
+
const parsed = parsePiModels(stdout);
|
|
398
|
+
if (parsed.some((model) => model.id !== "default"))
|
|
399
|
+
configuredModels = parsed;
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
// The remote endpoint catalog can still be useful when the Pi CLI is temporarily unavailable.
|
|
403
|
+
}
|
|
397
404
|
}
|
|
398
|
-
|
|
405
|
+
const endpointModels = options.piEndpointDiscovery?.enabled
|
|
406
|
+
? await discoverPiEndpointModels({
|
|
407
|
+
agentDir: options.piEndpointDiscovery.agentDir,
|
|
408
|
+
env,
|
|
409
|
+
fetchImpl: options.piEndpointDiscovery.fetchImpl,
|
|
410
|
+
})
|
|
411
|
+
: [];
|
|
412
|
+
if (!configuredModels.length && !endpointModels.length)
|
|
399
413
|
return { ok: false };
|
|
414
|
+
return { ok: true, value: mergePiModelCatalog(configuredModels, endpointModels) };
|
|
415
|
+
}
|
|
416
|
+
function mergePiModelCatalog(configuredModels, endpointModels) {
|
|
417
|
+
const defaultModel = configuredModels.find((model) => model.id === "default")
|
|
418
|
+
?? cloneModels(PI_FALLBACK_MODELS)[0];
|
|
419
|
+
const models = new Map();
|
|
420
|
+
for (const model of configuredModels) {
|
|
421
|
+
if (model.id !== "default")
|
|
422
|
+
models.set(model.id, model);
|
|
423
|
+
}
|
|
424
|
+
for (const model of endpointModels) {
|
|
425
|
+
if (model.id !== "default" && !models.has(model.id))
|
|
426
|
+
models.set(model.id, model);
|
|
400
427
|
}
|
|
428
|
+
return [defaultModel, ...models.values()];
|
|
401
429
|
}
|
|
402
430
|
/** 从 CLI 的 help / 非法档位报错里抽出 `low, medium, high` 这种列表。 */
|
|
403
431
|
export function parseCliEffortList(text) {
|
|
@@ -1025,7 +1053,7 @@ async function discoverModelCache(options, previous) {
|
|
|
1025
1053
|
probeOpenCode(runner, env),
|
|
1026
1054
|
probeGrokModels(runner, env),
|
|
1027
1055
|
probeQoderModels(runner, env),
|
|
1028
|
-
probePiModels(runner, env),
|
|
1056
|
+
probePiModels(runner, env, options),
|
|
1029
1057
|
listClaudeModelsFromApi(options, env),
|
|
1030
1058
|
probeEffortList(runner, env, "claude", ["--effort", "__wand_probe__", "--help"]),
|
|
1031
1059
|
probeEffortList(runner, env, "grok", ["--effort", "__wand_probe__", "-p", "x", "--output-format", "streaming-json", "--max-turns", "1"]),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ClaudeModelInfo } from "./types.js";
|
|
2
|
+
export interface PiModelEndpointDiscoveryOptions {
|
|
3
|
+
/** Defaults to PI_CODING_AGENT_DIR or ~/.pi/agent. */
|
|
4
|
+
agentDir?: string;
|
|
5
|
+
env?: NodeJS.ProcessEnv;
|
|
6
|
+
fetchImpl?: typeof fetch;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Discovers additional chat model IDs exposed by the API endpoints configured in Pi's models.json.
|
|
10
|
+
* The endpoint's key stays in memory and is never returned, logged, or persisted in the model catalog.
|
|
11
|
+
*/
|
|
12
|
+
export declare function discoverPiEndpointModels(options?: PiModelEndpointDiscoveryOptions): Promise<ClaudeModelInfo[]>;
|