@co0ontty/wand 2.4.3 → 2.4.4

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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "commit": "d0058288d0bce20e25fd06fd06f62de794d06790",
3
- "builtAt": "2026-07-07T00:10:50.544Z",
4
- "version": "2.4.3",
2
+ "commit": "c470535a559107d4b5f27a9cc7c7a4e7b057352e",
3
+ "builtAt": "2026-07-10T13:26:51.037Z",
4
+ "version": "2.4.4",
5
5
  "channel": "stable"
6
6
  }
@@ -11,7 +11,7 @@ interface QuickCommitOptions {
11
11
  autoMessage: boolean;
12
12
  customMessage?: string;
13
13
  tag?: string;
14
- /** When `tag` is empty, ask Claude to generate one based on the diff + commit message. */
14
+ /** When `tag` is empty, ask the session provider to generate one based on the diff + commit message. */
15
15
  autoTag?: boolean;
16
16
  push?: boolean;
17
17
  /**
@@ -39,7 +39,11 @@ export declare function generateCommitMessageOnly(cwd: string, language: string,
39
39
  interface TagHeadOptions {
40
40
  cwd: string;
41
41
  language: string;
42
- /** Explicit tag name. If empty and `autoTag` is true, ask Claude to generate one. */
42
+ provider?: SessionProvider;
43
+ model?: string | null;
44
+ thinkingEffort?: SessionSnapshot["thinkingEffort"];
45
+ inheritEnv?: boolean;
46
+ /** Explicit tag name. If empty and `autoTag` is true, ask the session provider to generate one. */
43
47
  tag?: string;
44
48
  autoTag?: boolean;
45
49
  /** Push only this tag to its upstream remote after creating it. */
@@ -492,7 +492,7 @@ export async function generateCommitMessageOnly(cwd, language, ai = {}) {
492
492
  return generateCommitMessageWithTag(cwd, language, ai);
493
493
  }
494
494
  /**
495
- * Ask Claude for a single tag string. Called from `runQuickCommit` after the commit has
495
+ * Ask the session provider for a single tag string. Called from `runQuickCommit` after the commit has
496
496
  * already landed, so we look at `git show HEAD` and use `HEAD~1` for the previous tag.
497
497
  */
498
498
  async function generateTagAfterCommit(cwd, language, commitMessage, ai = {}) {
@@ -613,7 +613,12 @@ export async function runTagHead(opts) {
613
613
  catch {
614
614
  headSubject = "";
615
615
  }
616
- tagName = await generateTagAfterCommit(cwd, language, headSubject || "");
616
+ tagName = await generateTagAfterCommit(cwd, language, headSubject || "", {
617
+ provider: opts.provider,
618
+ model: opts.model,
619
+ thinkingEffort: opts.thinkingEffort,
620
+ inheritEnv: opts.inheritEnv,
621
+ });
617
622
  }
618
623
  if (!tagName) {
619
624
  throw new QuickCommitError("请填写 tag 名称,或开启 AI 生成。", "EMPTY_TAG");
@@ -1096,7 +1101,7 @@ export async function runQuickCommit(opts) {
1096
1101
  catch {
1097
1102
  commitHash = "";
1098
1103
  }
1099
- // Tag: explicit `tag` wins; if empty + autoTag, ask Claude; otherwise skip.
1104
+ // Tag: explicit `tag` wins; if empty + autoTag, ask the session provider; otherwise skip.
1100
1105
  let tagName = (tag || "").trim();
1101
1106
  if (!tagName && autoTag) {
1102
1107
  tagName = await generateTagAfterCommit(cwd, language, message, ai);
@@ -58,8 +58,9 @@ export declare function repairRuntimePath(): PathRepairResult;
58
58
  *
59
59
  * 行为:
60
60
  * - 跑 `${shell} -l -c '...'` 拉 $PATH 和 command -v claude/codex(4s 超时)
61
- * - login shell PATH 里我们还没收录的目录 **前插** 到 process.env.PATH(注意
62
- * 是前插,不是追加 —— 它比 unit 里的更可信)
61
+ * - login shell PATH 顺序重排 process.env.PATH,再把仅存在于 service 的
62
+ * 目录追加回去。不能只前插新增目录:同一个 CLI 同时装在 nvm 和 Homebrew 时,
63
+ * 两个目录本来都存在,旧实现不会重排,structured 与 PTY 会命中不同版本。
63
64
  * - 失败时静默走同步那一版结果
64
65
  *
65
66
  * 接受一个已经跑过同步阶段的 result,在上面 mutate。
@@ -153,8 +153,9 @@ export function repairRuntimePath() {
153
153
  *
154
154
  * 行为:
155
155
  * - 跑 `${shell} -l -c '...'` 拉 $PATH 和 command -v claude/codex(4s 超时)
156
- * - login shell PATH 里我们还没收录的目录 **前插** 到 process.env.PATH(注意
157
- * 是前插,不是追加 —— 它比 unit 里的更可信)
156
+ * - login shell PATH 顺序重排 process.env.PATH,再把仅存在于 service 的
157
+ * 目录追加回去。不能只前插新增目录:同一个 CLI 同时装在 nvm 和 Homebrew 时,
158
+ * 两个目录本来都存在,旧实现不会重排,structured 与 PTY 会命中不同版本。
158
159
  * - 失败时静默走同步那一版结果
159
160
  *
160
161
  * 接受一个已经跑过同步阶段的 result,在上面 mutate。
@@ -184,10 +185,11 @@ export async function deepRepairRuntimePath(result, opts = {}) {
184
185
  return result;
185
186
  }
186
187
  const delim = path.delimiter;
187
- const existing = new Set((process.env.PATH ?? "")
188
+ const currentSegments = (process.env.PATH ?? "")
188
189
  .split(delim)
189
190
  .map((seg) => seg.trim())
190
- .filter((seg) => seg.length > 0));
191
+ .filter((seg) => seg.length > 0);
192
+ const existing = new Set(currentSegments);
191
193
  // login shell 报告的所有 PATH 段 + claude/codex 解析出的目录都纳入候选。
192
194
  const fromShell = [];
193
195
  for (const seg of probe.path.split(delim).map((s) => s.trim()).filter(Boolean)) {
@@ -197,9 +199,10 @@ export async function deepRepairRuntimePath(result, opts = {}) {
197
199
  fromShell.push(path.dirname(probe.claude));
198
200
  if (probe.codex)
199
201
  fromShell.push(path.dirname(probe.codex));
200
- const additions = [];
202
+ const preferred = [];
203
+ const preferredSet = new Set();
201
204
  for (const dir of fromShell) {
202
- if (!dir || existing.has(dir))
205
+ if (!dir || preferredSet.has(dir))
203
206
  continue;
204
207
  let ok = false;
205
208
  try {
@@ -210,19 +213,22 @@ export async function deepRepairRuntimePath(result, opts = {}) {
210
213
  }
211
214
  if (!ok)
212
215
  continue;
213
- existing.add(dir);
214
- additions.push(dir);
216
+ preferredSet.add(dir);
217
+ preferred.push(dir);
218
+ }
219
+ const additions = preferred.filter((dir) => !existing.has(dir));
220
+ const serviceOnly = currentSegments.filter((dir) => !preferredSet.has(dir));
221
+ const reordered = [...preferred, ...serviceOnly];
222
+ const nextPath = reordered.join(delim);
223
+ if (nextPath && nextPath !== (process.env.PATH ?? "")) {
224
+ process.env.PATH = nextPath;
215
225
  }
216
226
  if (additions.length > 0) {
217
- // 前插:login shell 的 PATH 优先级比 unit 写死的高,让 claude 解析到用户期望的版本。
218
- const prefix = additions.join(delim);
219
- const currentPath = process.env.PATH ?? "";
220
- process.env.PATH = currentPath ? `${prefix}${delim}${currentPath}` : prefix;
221
227
  result.added.push(...additions);
222
228
  }
223
229
  result.finalPath = process.env.PATH ?? "";
224
- // 修复完再 probe 一次,让 resolved 字段反映最终能找到的位置(之前 sync 阶段
225
- // 可能 claude 还是 missing,login shell 注入 nvm 目录后这次能命中)。
230
+ // 修复完再 probe 一次,让 resolved 字段反映最终能找到的位置;目录即使原本已在
231
+ // service PATH 中,也可能因本次重排而从旧的 nvm 副本切换到 Homebrew 副本。
226
232
  result.resolved = probeCommands();
227
233
  result.deepProbe = "success";
228
234
  return result;
@@ -4,6 +4,7 @@ import { getDefaultModelForProvider, normalizeMode } from "./config.js";
4
4
  import { blockWindowMessagesForTransport, sliceTurnBlocksForTransport, truncateMessagesForTransport, windowMessagesForTransport } from "./message-truncator.js";
5
5
  import { checkSessionWorktreeMergeability, cleanupSessionWorktree, getWorktreeMergeErrorCode, mergeSessionWorktree, WorktreeMergeError } from "./git-worktree.js";
6
6
  import { resolveSessionCwd } from "./session-cwd.js";
7
+ import { resolveSessionAiContext } from "./session-ai-context.js";
7
8
  import { getGitStatus, QuickCommitError, runQuickCommitWithFallback, runTagHead, runPush, generateCommitMessageOnly, } from "./git-quick-commit.js";
8
9
  import { getErrorMessage } from "./error-utils.js";
9
10
  export { getErrorMessage };
@@ -537,13 +538,11 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
537
538
  }
538
539
  const body = (req.body ?? {});
539
540
  try {
541
+ const ai = resolveSessionAiContext(snapshot, config);
540
542
  const result = await runQuickCommitWithFallback({
541
543
  cwd: snapshot.cwd,
542
544
  language: config.language ?? "",
543
- provider: snapshot.provider,
544
- model: snapshot.selectedModel ?? snapshot.structuredState?.model ?? getDefaultModelForProvider(config, snapshot.provider),
545
- thinkingEffort: snapshot.thinkingEffort ?? config.defaultThinkingEffort,
546
- inheritEnv: config.inheritEnv,
545
+ ...ai,
547
546
  autoMessage: body.autoMessage !== false,
548
547
  customMessage: typeof body.customMessage === "string" ? body.customMessage : undefined,
549
548
  tag: typeof body.tag === "string" ? body.tag : undefined,
@@ -573,11 +572,9 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
573
572
  return;
574
573
  }
575
574
  try {
575
+ const ai = resolveSessionAiContext(snapshot, config);
576
576
  const result = await generateCommitMessageOnly(snapshot.cwd, config.language ?? "", {
577
- provider: snapshot.provider,
578
- model: snapshot.selectedModel ?? snapshot.structuredState?.model ?? getDefaultModelForProvider(config, snapshot.provider),
579
- thinkingEffort: snapshot.thinkingEffort ?? config.defaultThinkingEffort,
580
- inheritEnv: config.inheritEnv,
577
+ ...ai,
581
578
  });
582
579
  res.json(result);
583
580
  }
@@ -601,9 +598,11 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
601
598
  }
602
599
  const body = (req.body ?? {});
603
600
  try {
601
+ const ai = resolveSessionAiContext(snapshot, config);
604
602
  const result = await runTagHead({
605
603
  cwd: snapshot.cwd,
606
604
  language: config.language ?? "",
605
+ ...ai,
607
606
  tag: typeof body.tag === "string" ? body.tag : undefined,
608
607
  autoTag: !!body.autoTag,
609
608
  push: !!body.push,
@@ -0,0 +1,15 @@
1
+ import type { SessionProvider, SessionSnapshot, WandConfig } from "./types.js";
2
+ export interface SessionAiContext {
3
+ provider: SessionProvider;
4
+ model?: string;
5
+ thinkingEffort: SessionSnapshot["thinkingEffort"];
6
+ inheritEnv?: boolean;
7
+ }
8
+ /**
9
+ * Resolve the provider from every representation used by current and legacy
10
+ * sessions. Older persisted sessions may not have the top-level provider, but
11
+ * still identify Codex through structuredState, runner, or command.
12
+ */
13
+ export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command">): SessionProvider;
14
+ /** Build the provider-specific settings used by session-adjacent AI actions. */
15
+ export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
@@ -0,0 +1,36 @@
1
+ import { getDefaultModelForProvider } from "./config.js";
2
+ /**
3
+ * Resolve the provider from every representation used by current and legacy
4
+ * sessions. Older persisted sessions may not have the top-level provider, but
5
+ * still identify Codex through structuredState, runner, or command.
6
+ */
7
+ export function resolveSessionProvider(snapshot) {
8
+ if (snapshot.provider === "claude" || snapshot.provider === "codex") {
9
+ return snapshot.provider;
10
+ }
11
+ if (snapshot.structuredState?.provider === "claude" || snapshot.structuredState?.provider === "codex") {
12
+ return snapshot.structuredState.provider;
13
+ }
14
+ const runner = snapshot.runner ?? snapshot.structuredState?.runner;
15
+ if (runner === "codex-cli-exec")
16
+ return "codex";
17
+ if (runner === "claude-cli" || runner === "claude-cli-print" || runner === "claude-sdk")
18
+ return "claude";
19
+ return /^codex\b/i.test(snapshot.command.trim()) ? "codex" : "claude";
20
+ }
21
+ function normalizeModel(value) {
22
+ const model = value?.trim();
23
+ return model && model !== "default" ? model : undefined;
24
+ }
25
+ /** Build the provider-specific settings used by session-adjacent AI actions. */
26
+ export function resolveSessionAiContext(snapshot, config) {
27
+ const provider = resolveSessionProvider(snapshot);
28
+ const sessionModel = normalizeModel(snapshot.selectedModel) ?? normalizeModel(snapshot.structuredState?.model);
29
+ const defaultModel = normalizeModel(getDefaultModelForProvider(config, provider));
30
+ return {
31
+ provider,
32
+ model: sessionModel ?? defaultModel,
33
+ thinkingEffort: snapshot.thinkingEffort ?? config.defaultThinkingEffort,
34
+ inheritEnv: config.inheritEnv,
35
+ };
36
+ }
package/dist/storage.js CHANGED
@@ -24,7 +24,12 @@ function inferSessionProvider(row) {
24
24
  if (row.runner === "claude-cli" || row.runner === "claude-cli-print") {
25
25
  return "claude";
26
26
  }
27
- return /^claude\b/.test(row.command.trim()) ? "claude" : undefined;
27
+ if (row.runner === "codex-cli-exec") {
28
+ return "codex";
29
+ }
30
+ if (/^codex\b/i.test(row.command.trim()))
31
+ return "codex";
32
+ return /^claude\b/i.test(row.command.trim()) ? "claude" : undefined;
28
33
  }
29
34
  function parseWorktreeInfo(raw) {
30
35
  const parsed = safeJsonParse(raw);