@co0ontty/wand 4.76.0 → 4.77.0-beta.g7fb0b70

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.
Files changed (71) hide show
  1. package/dist/agent-dispatch.d.ts +46 -0
  2. package/dist/agent-dispatch.js +84 -0
  3. package/dist/ai-team-availability.d.ts +69 -0
  4. package/dist/ai-team-availability.js +100 -0
  5. package/dist/ai-team-prompts.d.ts +113 -0
  6. package/dist/ai-team-prompts.js +435 -0
  7. package/dist/ai-team-runner.d.ts +241 -0
  8. package/dist/ai-team-runner.js +1370 -0
  9. package/dist/ai-team-types.d.ts +147 -0
  10. package/dist/ai-team-types.js +32 -0
  11. package/dist/attention.d.ts +40 -0
  12. package/dist/attention.js +86 -0
  13. package/dist/build-info.json +4 -4
  14. package/dist/claude-sdk-runner.d.ts +5 -0
  15. package/dist/claude-sdk-runner.js +2 -1
  16. package/dist/commit-task-archive.d.ts +3 -0
  17. package/dist/commit-task-archive.js +43 -0
  18. package/dist/config.js +0 -2
  19. package/dist/git-quick-commit.d.ts +2 -1
  20. package/dist/git-quick-commit.js +72 -60
  21. package/dist/message-truncator.d.ts +41 -5
  22. package/dist/message-truncator.js +255 -38
  23. package/dist/missions.d.ts +4 -2
  24. package/dist/missions.js +2 -2
  25. package/dist/process-manager.d.ts +1 -0
  26. package/dist/process-manager.js +17 -8
  27. package/dist/prompt-optimizer.js +13 -13
  28. package/dist/server-ai-team-routes.d.ts +11 -0
  29. package/dist/server-ai-team-routes.js +283 -0
  30. package/dist/server-attention-routes.d.ts +9 -0
  31. package/dist/server-attention-routes.js +26 -0
  32. package/dist/server-file-routes.js +1 -1
  33. package/dist/server-session-routes.js +41 -5
  34. package/dist/server-settings-routes.js +1 -1
  35. package/dist/server-task-routes.d.ts +2 -0
  36. package/dist/server-task-routes.js +6 -40
  37. package/dist/server-workspace-routes.js +13 -5
  38. package/dist/server.js +43 -0
  39. package/dist/session-topic.js +13 -13
  40. package/dist/session-transport.d.ts +3 -0
  41. package/dist/session-transport.js +1 -0
  42. package/dist/storage.d.ts +54 -1
  43. package/dist/storage.js +413 -2
  44. package/dist/structured-claude-adapter.d.ts +5 -0
  45. package/dist/structured-claude-adapter.js +13 -2
  46. package/dist/structured-claude-protocol.d.ts +2 -0
  47. package/dist/structured-claude-protocol.js +4 -0
  48. package/dist/structured-codex-adapter.js +2 -2
  49. package/dist/structured-grok-adapter.js +2 -1
  50. package/dist/structured-opencode-adapter.js +2 -2
  51. package/dist/structured-pi-adapter.js +2 -1
  52. package/dist/structured-provider-common.d.ts +17 -0
  53. package/dist/structured-provider-common.js +52 -0
  54. package/dist/structured-qoder-adapter.js +2 -1
  55. package/dist/structured-session-manager.d.ts +18 -1
  56. package/dist/structured-session-manager.js +67 -10
  57. package/dist/system-ai.d.ts +13 -2
  58. package/dist/system-ai.js +9 -5
  59. package/dist/task-title.js +10 -10
  60. package/dist/types.d.ts +25 -2
  61. package/dist/wand-task-sync.d.ts +1 -0
  62. package/dist/wand-task-sync.js +1 -0
  63. package/dist/web-ui/content/ai-teams.js +12 -0
  64. package/dist/web-ui/content/scripts.js +86 -5301
  65. package/dist/web-ui/content/styles.css +1 -1
  66. package/dist/web-ui/embedded-assets.d.ts +1 -0
  67. package/dist/web-ui/embedded-assets.js +3 -2
  68. package/dist/web-ui/scripts.d.ts +8 -0
  69. package/dist/web-ui/scripts.js +28 -2
  70. package/dist/ws-broadcast.js +52 -19
  71. package/package.json +1 -1
@@ -5,7 +5,7 @@ import { callSystemAiTextWithFallback } from "./system-ai.js";
5
5
  import { buildChildEnv } from "./env-utils.js";
6
6
  import { isSessionProvider } from "./session-provider.js";
7
7
  import { runGitAsync as runGitAsyncBase, runGitRawAsync as runGitRawAsyncBase, getGitErrorMessage, } from "./git-utils.js";
8
- import { thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToGrokEffort, thinkingEffortToOpenCodeVariant, thinkingEffortToQoderEffort, thinkingEffortToPiLevel, } from "./structured-provider-common.js";
8
+ import { composeSystemFallback, systemPromptFlag, thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToGrokEffort, thinkingEffortToOpenCodeVariant, thinkingEffortToQoderEffort, thinkingEffortToPiLevel, } from "./structured-provider-common.js";
9
9
  /** provider 缺省值:无法识别时回落到 Claude。 */
10
10
  function defaultProvider(provider) {
11
11
  return isSessionProvider(provider) ? provider : "claude";
@@ -283,13 +283,14 @@ export class QuickCommitError extends Error {
283
283
  }
284
284
  }
285
285
  // ── AI commit message generation ──
286
- async function callClaudeText(prompt, cwd, language, opts) {
286
+ async function callClaudeText(request, cwd, language, opts) {
287
287
  try {
288
288
  const effort = thinkingEffortToClaudeCliEffort(opts.thinkingEffort ?? "off");
289
- return await runClaudePrint(prompt, {
289
+ return await runClaudePrint(request.prompt, {
290
290
  cwd,
291
291
  timeoutMs: CLAUDE_MESSAGE_TIMEOUT_MS,
292
292
  language,
293
+ systemInstructions: request.system,
293
294
  model: opts.model ?? undefined,
294
295
  ...(effort ? { effort } : {}),
295
296
  });
@@ -421,7 +422,7 @@ function extractPiText(stdout) {
421
422
  }
422
423
  return text.trim();
423
424
  }
424
- function buildPiTextArgs(prompt, opts, allowTools = false) {
425
+ function buildPiTextArgs(request, opts, allowTools = false) {
425
426
  const args = ["--mode", "json", "--print", "--no-session"];
426
427
  if (!allowTools)
427
428
  args.push("--no-tools");
@@ -431,33 +432,48 @@ function buildPiTextArgs(prompt, opts, allowTools = false) {
431
432
  const level = thinkingEffortToPiLevel(opts.thinkingEffort ?? "off");
432
433
  if (level)
433
434
  args.push("--thinking", level);
434
- args.push(prompt);
435
+ args.push(...systemPromptArgsFrom("pi", request.system));
436
+ args.push(request.prompt);
435
437
  return args;
436
438
  }
437
- function buildGrokTextArgs(prompt, opts, allowTools = false) {
438
- const args = ["--no-auto-update", "-p", prompt, "--output-format", "streaming-json"];
439
+ function buildGrokTextArgs(request, opts, allowTools = false) {
440
+ const args = ["--no-auto-update", "-p", request.prompt, "--output-format", "streaming-json"];
439
441
  const model = opts.model?.trim();
440
442
  if (model && model !== "default")
441
443
  args.push("--model", model);
442
444
  const effort = thinkingEffortToGrokEffort(opts.thinkingEffort ?? "off");
443
445
  if (effort)
444
446
  args.push("--effort", effort);
447
+ args.push(...systemPromptArgsFrom("grok", request.system));
445
448
  if (allowTools)
446
449
  args.push("--always-approve");
447
450
  return args;
448
451
  }
449
- function buildQoderTextArgs(prompt, opts, allowTools = false) {
450
- const args = ["-p", prompt, "--output-format", "stream-json", "--no-session-persistence"];
452
+ function buildQoderTextArgs(request, opts, allowTools = false) {
453
+ const args = ["-p", request.prompt, "--output-format", "stream-json", "--no-session-persistence"];
451
454
  const model = opts.model?.trim();
452
455
  if (model && model !== "default")
453
456
  args.push("--model", model);
454
457
  const effort = thinkingEffortToQoderEffort(opts.thinkingEffort ?? "off");
455
458
  if (effort)
456
459
  args.push("--reasoning-effort", effort);
460
+ args.push(...systemPromptArgsFrom("qoder", request.system));
457
461
  if (allowTools)
458
462
  args.push("--permission-mode", "bypass_permissions");
459
463
  return args;
460
464
  }
465
+ /** provider 自己的系统提示开关 + 规则文本;没有开关的 provider 返回空。 */
466
+ function systemPromptArgsFrom(provider, system) {
467
+ const text = system?.trim();
468
+ if (!text)
469
+ return [];
470
+ const flag = systemPromptFlag(provider);
471
+ return flag ? [flag, text] : [];
472
+ }
473
+ /** 没有系统提示开关的 provider(codex / opencode):规则只能并进内容前面。 */
474
+ function contentWithSystemPrompt(provider, request) {
475
+ return systemPromptFlag(provider) ? request.prompt : composeSystemFallback(request.system, request.prompt);
476
+ }
461
477
  function runCliText(command, args, prompt, opts) {
462
478
  return new Promise((resolve, reject) => {
463
479
  const child = spawn(command, args, {
@@ -509,7 +525,7 @@ function runCliText(command, args, prompt, opts) {
509
525
  child.stdin?.end(prompt);
510
526
  });
511
527
  }
512
- async function callCodexText(prompt, cwd, opts) {
528
+ async function callCodexText(request, cwd, opts) {
513
529
  // Quick commit is an internal one-shot request, not a user conversation. Keep
514
530
  // Codex from persisting it into ~/.codex/sessions, where Wand would otherwise
515
531
  // surface the generated prompt as a recoverable session.
@@ -521,7 +537,7 @@ async function callCodexText(prompt, cwd, opts) {
521
537
  if (reasoningEffort)
522
538
  args.push("-c", `model_reasoning_effort=${reasoningEffort}`);
523
539
  args.push("-");
524
- const stdout = await runCliText("codex", args, prompt, {
540
+ const stdout = await runCliText("codex", args, contentWithSystemPrompt("codex", request), {
525
541
  cwd,
526
542
  timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
527
543
  inheritEnv: opts.inheritEnv,
@@ -532,7 +548,7 @@ async function callCodexText(prompt, cwd, opts) {
532
548
  }
533
549
  return text;
534
550
  }
535
- async function callOpenCodeText(prompt, cwd, opts) {
551
+ async function callOpenCodeText(request, cwd, opts) {
536
552
  const args = ["run", "--format", "json"];
537
553
  const model = opts.model?.trim();
538
554
  if (model && model !== "default")
@@ -540,7 +556,7 @@ async function callOpenCodeText(prompt, cwd, opts) {
540
556
  const variant = thinkingEffortToOpenCodeVariant(opts.thinkingEffort ?? "off");
541
557
  if (variant)
542
558
  args.push("--variant", variant);
543
- const stdout = await runCliText("opencode", args, prompt, {
559
+ const stdout = await runCliText("opencode", args, contentWithSystemPrompt("opencode", request), {
544
560
  cwd,
545
561
  timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
546
562
  inheritEnv: opts.inheritEnv,
@@ -550,8 +566,8 @@ async function callOpenCodeText(prompt, cwd, opts) {
550
566
  throw new QuickCommitError("OpenCode 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
551
567
  return text;
552
568
  }
553
- async function callGrokText(prompt, cwd, opts) {
554
- const stdout = await runCliText("grok", buildGrokTextArgs(prompt, opts), "", {
569
+ async function callGrokText(request, cwd, opts) {
570
+ const stdout = await runCliText("grok", buildGrokTextArgs(request, opts), "", {
555
571
  cwd,
556
572
  timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
557
573
  inheritEnv: opts.inheritEnv,
@@ -561,8 +577,8 @@ async function callGrokText(prompt, cwd, opts) {
561
577
  throw new QuickCommitError("Grok 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
562
578
  return text;
563
579
  }
564
- async function callQoderText(prompt, cwd, opts) {
565
- const stdout = await runCliText("qodercli", buildQoderTextArgs(prompt, opts), "", {
580
+ async function callQoderText(request, cwd, opts) {
581
+ const stdout = await runCliText("qodercli", buildQoderTextArgs(request, opts), "", {
566
582
  cwd,
567
583
  timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
568
584
  inheritEnv: opts.inheritEnv,
@@ -572,30 +588,30 @@ async function callQoderText(prompt, cwd, opts) {
572
588
  throw new QuickCommitError("Qoder 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
573
589
  return text;
574
590
  }
575
- async function callPiText(prompt, cwd, opts) {
576
- const stdout = await runCliText("pi", buildPiTextArgs(prompt, opts), "", { cwd, timeoutMs: CODEX_MESSAGE_TIMEOUT_MS, inheritEnv: opts.inheritEnv });
591
+ async function callPiText(request, cwd, opts) {
592
+ const stdout = await runCliText("pi", buildPiTextArgs(request, opts), "", { cwd, timeoutMs: CODEX_MESSAGE_TIMEOUT_MS, inheritEnv: opts.inheritEnv });
577
593
  const text = extractPiText(stdout);
578
594
  if (!text)
579
595
  throw new QuickCommitError("Pi 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
580
596
  return text;
581
597
  }
582
- async function callCliAiText(prompt, cwd, language, opts) {
598
+ async function callCliAiText(request, cwd, language, opts) {
583
599
  const provider = defaultProvider(opts.provider);
584
600
  if (provider === "codex") {
585
- return callCodexText(prompt, cwd, opts);
601
+ return callCodexText(request, cwd, opts);
586
602
  }
587
603
  if (provider === "opencode")
588
- return callOpenCodeText(prompt, cwd, opts);
604
+ return callOpenCodeText(request, cwd, opts);
589
605
  if (provider === "grok")
590
- return callGrokText(prompt, cwd, opts);
606
+ return callGrokText(request, cwd, opts);
591
607
  if (provider === "qoder")
592
- return callQoderText(prompt, cwd, opts);
608
+ return callQoderText(request, cwd, opts);
593
609
  if (provider === "pi")
594
- return callPiText(prompt, cwd, opts);
595
- return callClaudeText(prompt, cwd, language, opts);
610
+ return callPiText(request, cwd, opts);
611
+ return callClaudeText(request, cwd, language, opts);
596
612
  }
597
- async function callDirectApiText(prompt, systemAi) {
598
- const text = await callSystemAiTextWithFallback(prompt, systemAi, DIRECT_API_PROFILE_TIMEOUT_MS);
613
+ async function callDirectApiText(request, systemAi) {
614
+ const text = await callSystemAiTextWithFallback(request, systemAi, DIRECT_API_PROFILE_TIMEOUT_MS);
599
615
  if (!text.trim()) {
600
616
  throw new QuickCommitError("直连 API 返回了空结果。", "EMPTY_AI_MESSAGE");
601
617
  }
@@ -610,23 +626,23 @@ function aiFallbackFailed(primary, primaryError, fallbackError) {
610
626
  * exhausts its profile chain and then falls back once to the current-session
611
627
  * CLI. CLI mode uses only that CLI.
612
628
  */
613
- export async function callConfiguredAiText(prompt, cwd, language, opts) {
629
+ export async function callConfiguredAiText(request, cwd, language, opts) {
614
630
  if (opts.systemAi?.enabled) {
615
631
  try {
616
- return await callDirectApiText(prompt, opts.systemAi);
632
+ return await callDirectApiText(request, opts.systemAi);
617
633
  }
618
634
  catch (apiError) {
619
635
  try {
620
636
  // The user selected the direct API first. If it is unavailable or
621
637
  // empty, retry this exact request through their selected CLI.
622
- return await callCliAiText(prompt, cwd, language, opts);
638
+ return await callCliAiText(request, cwd, language, opts);
623
639
  }
624
640
  catch (cliError) {
625
641
  throw aiFallbackFailed("直连 API", apiError, cliError);
626
642
  }
627
643
  }
628
644
  }
629
- return callCliAiText(prompt, cwd, language, opts);
645
+ return callCliAiText(request, cwd, language, opts);
630
646
  }
631
647
  /** Read the unstaged + staged tree without touching the index. */
632
648
  async function collectWorkingTreeDiff(cwd) {
@@ -717,8 +733,7 @@ function commitTaskLine(lang, usedIteration) {
717
733
  async function generateCommitMessage(cwd, language, ai = {}, options = {}) {
718
734
  const { input, usedIteration } = await buildCommitPromptInput(cwd, options);
719
735
  const lang = language.trim() || "中文";
720
- const prompt = `${commitTaskLine(lang, usedIteration)}\n\n${input}`;
721
- const raw = await callConfiguredAiText(prompt, cwd, language, ai);
736
+ const raw = await callConfiguredAiText({ system: commitTaskLine(lang, usedIteration), prompt: input }, cwd, language, ai);
722
737
  const message = normalizeAiText(raw);
723
738
  if (!message) {
724
739
  throw new QuickCommitError("AI 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
@@ -765,15 +780,13 @@ async function generateCommitMessageWithTag(cwd, language, ai = {}, options = {}
765
780
  const tagHint = latestTag
766
781
  ? `当前最新 tag 是 \`${latestTag}\`,请基于它给出下一个版本号(保持原有前缀风格,例如有 \`v\` 就保留 \`v\`)。`
767
782
  : `仓库还没有任何 tag,请直接给一个起始版本号(建议 \`v0.0.1\` / \`v0.1.0\` / \`v1.0.0\` 之一,按改动幅度选择)。`;
768
- const prompt = `阅读以下输入,完成两件事:
783
+ const system = `阅读本轮用户消息,完成两件事:
769
784
  1. 用${lang}写一条简洁的 commit message(祈使句,不超过 50 字,描述「做了什么」)。
770
- 2. 根据改动幅度推荐下一个语义化版本 tag(破坏性变更 → 升 major;新增功能 → 升 minor;修复 / 文档 / 重构 / 维护 → 升 patch)。${tagHint}
785
+ 2. 根据改动幅度推荐下一个语义化版本 tag(破坏性变更 → 升 major;新增功能 → 升 minor;修复 / 文档 / 重构 / 维护 → 升 patch)。
771
786
 
772
787
  请严格输出**单行 JSON 对象**,不要 Markdown 代码块、不要任何解释文字、不要多余引号。格式:
773
- {"message":"...","tag":"v1.2.3"}
774
-
775
- ${input}`;
776
- const raw = await callConfiguredAiText(prompt, cwd, language, ai);
788
+ {"message":"...","tag":"v1.2.3"}`;
789
+ const raw = await callConfiguredAiText({ system, prompt: `${tagHint}\n\n${input}` }, cwd, language, ai);
777
790
  const parsed = tryParseJson(raw);
778
791
  let message;
779
792
  let suggestedTag;
@@ -833,16 +846,12 @@ async function generateTagAfterCommit(cwd, language, commitMessage, ai = {}) {
833
846
  const tagHint = latestTag
834
847
  ? `当前最新 tag 是 \`${latestTag}\`,请基于它给出下一个版本号(保持原有前缀风格,例如有 \`v\` 就保留 \`v\`)。`
835
848
  : `仓库还没有任何 tag,请给一个起始版本号(建议 \`v0.0.1\` / \`v0.1.0\` / \`v1.0.0\` 之一,按改动幅度选择)。`;
836
- const prompt = `根据以下 commit message 和 git diff 推荐一个语义化版本 tag(破坏性变更 → 升 major;新增功能 → 升 minor;修复 / 文档 / 重构 / 维护 → 升 patch)。${tagHint}
849
+ const system = `根据本轮用户消息里的 commit message 和 git diff 推荐一个语义化版本 tag(破坏性变更 → 升 major;新增功能 → 升 minor;修复 / 文档 / 重构 / 维护 → 升 patch)。
837
850
 
838
851
  请用${lang}思考但严格输出**单行 JSON 对象**,不要 Markdown 代码块、不要任何解释文字、不要多余引号。格式:
839
- {"tag":"v1.2.3"}
840
-
841
- commit message:${commitMessage}
842
-
843
- git diff:
844
- ${diff}`;
845
- const raw = await callConfiguredAiText(prompt, cwd, language, ai);
852
+ {"tag":"v1.2.3"}`;
853
+ const prompt = `${tagHint}\n\ncommit message:${commitMessage}\n\ngit diff:\n${diff}`;
854
+ const raw = await callConfiguredAiText({ system, prompt }, cwd, language, ai);
846
855
  const parsed = tryParseJson(raw);
847
856
  let suggested;
848
857
  if (parsed && typeof parsed.tag === "string") {
@@ -1212,15 +1221,18 @@ function buildFallbackPrompt(opts, priorError) {
1212
1221
  const submoduleLine = opts.submodule
1213
1222
  ? "- 如果 submodule 内部也有改动,先在对应 submodule 内 add/commit,再提交父仓库里的 submodule 指针"
1214
1223
  : "- 不进入 submodule 内部提交,只提交父仓库自身已纳入的改动";
1215
- return [
1224
+ // 角色、约束、输出格式走系统提示;本次要做什么、为什么走到兜底走用户消息。
1225
+ const system = [
1216
1226
  "你正在作为 Wand 的快捷提交兜底执行器运行。前置的内置快捷提交流程失败了,现在请直接用 CLI 工具完成同一件事。",
1217
1227
  "",
1218
1228
  "约束:",
1219
1229
  "- 只允许执行与 git 快捷提交直接相关的命令,例如 git status、git diff、git add、git commit、git tag、git push、git submodule status。",
1220
1230
  "- 不要修改源代码内容,不要运行测试,不要安装依赖,不要重构文件。",
1221
1231
  "- 如果没有可提交改动,明确说明并停止,不要创建空 commit。",
1222
- "- commit message 和自然语言输出使用 " + lang + "。",
1223
- "",
1232
+ "- 完成后只输出一行 JSON:{\"ok\":true,\"message\":\"...\",\"tag\":\"...\"}。失败时输出一行 JSON:{\"ok\":false,\"error\":\"...\"}。",
1233
+ `- commit message 和自然语言输出使用 ${lang}。`,
1234
+ ].join("\n");
1235
+ const prompt = [
1224
1236
  "任务:",
1225
1237
  "- 执行 git add -A 纳入当前改动。",
1226
1238
  messageLine,
@@ -1232,9 +1244,8 @@ function buildFallbackPrompt(opts, priorError) {
1232
1244
  : []),
1233
1245
  "",
1234
1246
  `内置流程失败原因:${priorError}`,
1235
- "",
1236
- "完成后只输出一行 JSON:{\"ok\":true,\"message\":\"...\",\"tag\":\"...\"}。失败时输出一行 JSON:{\"ok\":false,\"error\":\"...\"}。",
1237
1247
  ].join("\n");
1248
+ return { system, prompt };
1238
1249
  }
1239
1250
  async function getHead(cwd) {
1240
1251
  try {
@@ -1265,7 +1276,7 @@ async function getLatestTagAtHead(cwd) {
1265
1276
  async function runQuickCommitFallbackCli(opts, priorError) {
1266
1277
  await assertGitWorkTreeAsync(opts.cwd);
1267
1278
  const beforeHead = await getHead(opts.cwd);
1268
- const prompt = buildFallbackPrompt(opts, priorError);
1279
+ const request = buildFallbackPrompt(opts, priorError);
1269
1280
  const provider = defaultProvider(opts.provider);
1270
1281
  if (provider === "codex") {
1271
1282
  const args = ["exec", "--ephemeral", "--json", "--color", "never", "--skip-git-repo-check", "--dangerously-bypass-approvals-and-sandbox"];
@@ -1276,7 +1287,7 @@ async function runQuickCommitFallbackCli(opts, priorError) {
1276
1287
  if (reasoningEffort)
1277
1288
  args.push("-c", `model_reasoning_effort=${reasoningEffort}`);
1278
1289
  args.push("-");
1279
- await runCliText("codex", args, prompt, {
1290
+ await runCliText("codex", args, contentWithSystemPrompt("codex", request), {
1280
1291
  cwd: opts.cwd,
1281
1292
  timeoutMs: QUICK_COMMIT_CLI_TIMEOUT_MS,
1282
1293
  inheritEnv: opts.inheritEnv,
@@ -1290,28 +1301,28 @@ async function runQuickCommitFallbackCli(opts, priorError) {
1290
1301
  const variant = thinkingEffortToOpenCodeVariant(opts.thinkingEffort ?? "off");
1291
1302
  if (variant)
1292
1303
  args.push("--variant", variant);
1293
- await runCliText("opencode", args, prompt, {
1304
+ await runCliText("opencode", args, contentWithSystemPrompt("opencode", request), {
1294
1305
  cwd: opts.cwd,
1295
1306
  timeoutMs: QUICK_COMMIT_CLI_TIMEOUT_MS,
1296
1307
  inheritEnv: opts.inheritEnv,
1297
1308
  });
1298
1309
  }
1299
1310
  else if (provider === "grok") {
1300
- await runCliText("grok", buildGrokTextArgs(prompt, opts, true), "", {
1311
+ await runCliText("grok", buildGrokTextArgs(request, opts, true), "", {
1301
1312
  cwd: opts.cwd,
1302
1313
  timeoutMs: QUICK_COMMIT_CLI_TIMEOUT_MS,
1303
1314
  inheritEnv: opts.inheritEnv,
1304
1315
  });
1305
1316
  }
1306
1317
  else if (provider === "qoder") {
1307
- await runCliText("qodercli", buildQoderTextArgs(prompt, opts, true), "", {
1318
+ await runCliText("qodercli", buildQoderTextArgs(request, opts, true), "", {
1308
1319
  cwd: opts.cwd,
1309
1320
  timeoutMs: QUICK_COMMIT_CLI_TIMEOUT_MS,
1310
1321
  inheritEnv: opts.inheritEnv,
1311
1322
  });
1312
1323
  }
1313
1324
  else if (provider === "pi") {
1314
- await runCliText("pi", buildPiTextArgs(prompt, opts, true), "", {
1325
+ await runCliText("pi", buildPiTextArgs(request, opts, true), "", {
1315
1326
  cwd: opts.cwd,
1316
1327
  timeoutMs: QUICK_COMMIT_CLI_TIMEOUT_MS,
1317
1328
  inheritEnv: opts.inheritEnv,
@@ -1334,7 +1345,8 @@ async function runQuickCommitFallbackCli(opts, priorError) {
1334
1345
  const claudeEffort = thinkingEffortToClaudeCliEffort(opts.thinkingEffort ?? "off");
1335
1346
  if (claudeEffort)
1336
1347
  args.push("--effort", claudeEffort);
1337
- await runCliText("claude", args, prompt, {
1348
+ args.push(...systemPromptArgsFrom("claude", request.system));
1349
+ await runCliText("claude", args, request.prompt, {
1338
1350
  cwd: opts.cwd,
1339
1351
  timeoutMs: QUICK_COMMIT_CLI_TIMEOUT_MS,
1340
1352
  inheritEnv: opts.inheritEnv,
@@ -17,14 +17,50 @@ export interface BlockWindowedMessages extends WindowedMessages {
17
17
  leadingBlockOffset: number;
18
18
  /** turn messageOffset 的完整块数(客户端据此判断该 turn 是否已全部加载)。 */
19
19
  leadingBlockTotal: number;
20
+ /**
21
+ * 被切掉的头部里「用户可感知」的块数:默认收起的工具 / 思考块不计入(它们在客户端
22
+ * 合并成一条折叠条,不该被当成一条更早消息)。客户端据此显示「还有 N 条」。
23
+ */
24
+ leadingVisibleCount: number;
20
25
  }
21
26
  /**
22
- * 块级窗口:取完整历史「最近 blockBudget 个内容块」并做 transport 截断。
23
- * 从最新 turn 往回累计块数,能整条放下就整条放,放不下的那条(最旧的入窗 turn)
24
- * 只取其尾部若干块,并通过 leadingBlockOffset 告知客户端「这条 turn 还有更早的块」。
25
- * 客户端先按块翻完这条 turn 的头部,再按 turn 往前翻更早的整条。
27
+ * 首次下发(首屏)的载荷上限:截断后的聊天 JSON 体积。整段历史在这个体积内就不做窗口化 ——
28
+ * 一条提示词 + 一段长回复的会话不该因为几十个默认收起的工具块就出现「更早消息」。
29
+ * 超出的会话才按下面的预算从尾部切:客户端先翻这条 turn 的头部块,再按 turn 往前翻。
26
30
  */
27
- export declare function blockWindowMessagesForTransport(all: ConversationTurn[] | undefined, cardDefaults: CardExpandDefaults, blockBudget?: number): BlockWindowedMessages;
31
+ export declare const MESSAGE_FIRST_PAINT_BYTES: number;
32
+ /** 每个块在客户端是否「默认收起」(思考块 + 默认收起的工具卡片,与传输截断同一口径)。 */
33
+ export declare function collapsedBlockFlags(content: ContentBlock[], cardDefaults: CardExpandDefaults): boolean[];
34
+ /**
35
+ * 一段内容在传输层的估算体积。`limit` 用来提前收尾:一旦确认超过它就不再往下算
36
+ * (预算判定只需要知道「超了」,不需要精确值)。
37
+ */
38
+ export declare function contentTransportBytes(content: ContentBlock[], cardDefaults: CardExpandDefaults, limit?: number): number;
39
+ /**
40
+ * 把块级窗口 / 翻页的起点吸附到「语义干净」的位置:
41
+ *
42
+ * 1. 起点落在默认收起的折叠段中间 → 回到该段起点(整段入窗)。半截工具段在客户端
43
+ * 会渲染成一个少了前几个调用的折叠条,用户看着就是「更早消息缺了一块」。
44
+ * 2. 起点是配对的 tool_result(它的 tool_use 被切在前面)→ 回到 tool_use,否则客户端
45
+ * 顶部会冒出一张「无头结果」卡。
46
+ * 3. 需要往回吃掉的体积超过 MAX_BLOCK_SNAP_BACK_BYTES(或本页范围内吃不下)时不再
47
+ * 往回吸附,改为跳到该段之后(整段都是默认收起的内容,跳过去不会藏掉可见内容)。
48
+ *
49
+ * 返回吸附后的起点;起点越界时回退到 0(整条 turn 入窗,保证窗口不为空)。
50
+ */
51
+ export declare function alignedBlockStart(content: ContentBlock[], cardDefaults: CardExpandDefaults, rawStart: number, forwardLimit?: number): number;
52
+ /** [0, end) 里用户可感知的块数(默认收起的工具 / 思考块不计入)。 */
53
+ export declare function visibleBlockCount(content: ContentBlock[], cardDefaults: CardExpandDefaults, end: number): number;
54
+ /**
55
+ * 块级窗口:默认取整段历史(只要它在预算内),超出的才从最新 turn 往回累计,
56
+ * 能整条放下就整条放,放不下的那条(最旧的入窗 turn)只取其尾部若干块,
57
+ * 并通过 leadingBlockOffset 告知客户端「这条 turn 还有更早的块」。
58
+ *
59
+ * 预算口径:`blockBudget` 数的是**用户可感知的条数**(默认收起的工具 / 思考块不计入),
60
+ * 另外叠加一层载荷上限 —— 否则一段长工具调用会把预算吃光,把用户自己的提示词挤出首屏,
61
+ * 一条提示词的会话也会莫名出现「更早消息」。
62
+ */
63
+ export declare function blockWindowMessagesForTransport(all: ConversationTurn[] | undefined, cardDefaults: CardExpandDefaults, blockBudget?: number, byteBudget?: number): BlockWindowedMessages;
28
64
  /**
29
65
  * 块级翻页:取某条 turn 的 content[start, end) 这一段(已做 transport 截断)。
30
66
  * 客户端滚动到顶、且当前最旧 turn 仍有更早块时调用,end = 客户端当前 leadingBlockOffset。