@co0ontty/wand 1.69.0-beta.g4eaa62d → 1.69.0-beta.gecf86ef

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": "4eaa62d08d904483178a08c677c6a7b785f3d351",
3
- "builtAt": "2026-06-17T03:53:37.154Z",
4
- "version": "1.69.0-beta.g4eaa62d",
2
+ "commit": "ecf86ef6a882537ff9c72de8c11b2ee8b962a5a6",
3
+ "builtAt": "2026-06-18T12:24:25.305Z",
4
+ "version": "1.69.0-beta.gecf86ef",
5
5
  "channel": "beta"
6
6
  }
@@ -2,5 +2,7 @@
2
2
  export declare function isPathWithinBase(targetPath: string, basePath: string): boolean;
3
3
  /** Check if targetPath is inside any blocked system folder. */
4
4
  export declare function isBlockedFolderPath(targetPath: string): boolean;
5
+ /** Expand shell-style home shortcuts accepted by the UI. */
6
+ export declare function expandHomePath(inputPath: string): string;
5
7
  /** Normalize a folder path to its absolute form. */
6
- export declare function normalizeFolderPath(inputPath: string): string;
8
+ export declare function normalizeFolderPath(inputPath: string, basePath?: string): string;
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import os from "node:os";
2
3
  /** Check that targetPath is within basePath (or equal to it). */
3
4
  export function isPathWithinBase(targetPath, basePath) {
4
5
  const relativePath = path.relative(basePath, targetPath);
@@ -13,7 +14,17 @@ export function isBlockedFolderPath(targetPath) {
13
14
  return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
14
15
  });
15
16
  }
17
+ /** Expand shell-style home shortcuts accepted by the UI. */
18
+ export function expandHomePath(inputPath) {
19
+ const trimmed = inputPath.trim();
20
+ if (trimmed === "~")
21
+ return os.homedir();
22
+ if (trimmed.startsWith("~/") || trimmed.startsWith("~\\")) {
23
+ return path.join(os.homedir(), trimmed.slice(2));
24
+ }
25
+ return trimmed;
26
+ }
16
27
  /** Normalize a folder path to its absolute form. */
17
- export function normalizeFolderPath(inputPath) {
18
- return path.resolve(inputPath);
28
+ export function normalizeFolderPath(inputPath, basePath = process.cwd()) {
29
+ return path.resolve(basePath, expandHomePath(inputPath));
19
30
  }
@@ -17,6 +17,7 @@ import { getCodexResumeCommandSessionId, getResumeCommandSessionId } from "./res
17
17
  import { applyThinkingEffortToPrompt, normalizeThinkingEffort } from "./structured-session-manager.js";
18
18
  import { generateSessionTopic } from "./session-topic.js";
19
19
  import { getErrorMessage } from "./error-utils.js";
20
+ import { resolveSessionCwd } from "./session-cwd.js";
20
21
  function resolveProviderFromCommand(command) {
21
22
  return /^codex\b/.test(command.trim()) ? "codex" : "claude";
22
23
  }
@@ -752,9 +753,7 @@ export class ProcessManager extends EventEmitter {
752
753
  }
753
754
  start(command, cwd, mode, initialInput, opts) {
754
755
  this.assertCommandAllowed(command);
755
- const baseCwd = cwd
756
- ? path.resolve(process.cwd(), cwd)
757
- : path.resolve(process.cwd(), this.config.defaultCwd);
756
+ const baseCwd = resolveSessionCwd(cwd, this.config.defaultCwd);
758
757
  const id = opts?.reuseId || randomUUID();
759
758
  // When a session is being resumed under the same id, capture its prior
760
759
  // structured messages so the new bridge can present them as the chat
@@ -3,6 +3,7 @@ import { SessionInputError } from "./process-manager.js";
3
3
  import { 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
+ import { resolveSessionCwd } from "./session-cwd.js";
6
7
  import { getGitStatus, QuickCommitError, runQuickCommit, runTagHead, runPush, generateCommitMessageOnly, } from "./git-quick-commit.js";
7
8
  import { getErrorMessage } from "./error-utils.js";
8
9
  export { getErrorMessage };
@@ -155,7 +156,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
155
156
  }
156
157
  const provider = body.provider === "codex" ? "codex" : "claude";
157
158
  const snapshot = structured.createSession({
158
- cwd: body.cwd?.trim() || process.cwd(),
159
+ cwd: resolveSessionCwd(body.cwd, config.defaultCwd),
159
160
  mode: normalizeMode(body.mode, defaultMode),
160
161
  provider,
161
162
  runner: body.runner ?? (provider === "codex" ? "codex-cli-exec" : "claude-cli-print"),
@@ -165,7 +166,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
165
166
  ? body.thinkingEffort
166
167
  : config.defaultThinkingEffort,
167
168
  });
168
- onSessionCreated?.(body.cwd ?? snapshot.cwd);
169
+ onSessionCreated?.(snapshot.cwd);
169
170
  const prompt = body.prompt?.trim();
170
171
  if (prompt) {
171
172
  const finished = await structured.sendMessage(snapshot.id, prompt);
package/dist/server.js CHANGED
@@ -566,12 +566,16 @@ async function resolveAndroidApkAsset(configDir, config, channel = "beta") {
566
566
  const fileStat = await stat(filePath);
567
567
  if (!fileStat.isFile())
568
568
  return null;
569
+ const fileName = path.basename(filePath);
570
+ const version = extractAndroidApkVersion(fileName);
571
+ if (channel === "stable" && isPrereleaseApkVersion(version))
572
+ return null;
569
573
  return {
570
- fileName: path.basename(filePath),
574
+ fileName,
571
575
  filePath,
572
576
  size: fileStat.size,
573
577
  updatedAt: fileStat.mtime.toISOString(),
574
- version: extractAndroidApkVersion(path.basename(filePath)),
578
+ version,
575
579
  downloadUrl: "/android/download",
576
580
  source: "local",
577
581
  };
@@ -710,7 +714,7 @@ async function resolveMacosDmgAsset(configDir, config) {
710
714
  async function listPathSuggestions(input, fallbackCwd) {
711
715
  const normalizedInput = input.trim();
712
716
  const baseInput = normalizedInput || fallbackCwd;
713
- const resolvedInput = path.resolve(process.cwd(), baseInput);
717
+ const resolvedInput = normalizeFolderPath(baseInput);
714
718
  const endsWithSeparator = /[\\/]$/.test(normalizedInput);
715
719
  let searchDir = resolvedInput;
716
720
  let partialName = "";
@@ -1256,6 +1260,7 @@ export async function startServer(config, configPath) {
1256
1260
  language: config.language ?? "",
1257
1261
  updateAvailable: cachedUpdateInfo?.updateAvailable ?? false,
1258
1262
  latestVersion: cachedUpdateInfo?.latest ?? null,
1263
+ updateChannel: getUpdateChannel(),
1259
1264
  currentVersion: DISPLAY_VERSION,
1260
1265
  });
1261
1266
  });
@@ -2092,7 +2097,7 @@ export async function startServer(config, configPath) {
2092
2097
  rows: reqRows,
2093
2098
  thinkingEffort: body.thinkingEffort ?? config.defaultThinkingEffort,
2094
2099
  });
2095
- recordRecentPath(storage, body.cwd ?? snapshot.cwd);
2100
+ recordRecentPath(storage, snapshot.cwd);
2096
2101
  res.status(201).json(snapshot);
2097
2102
  }
2098
2103
  catch (error) {
@@ -0,0 +1 @@
1
+ export declare function resolveSessionCwd(cwd: string | null | undefined, fallbackCwd: string | null | undefined): string;
@@ -0,0 +1,31 @@
1
+ import { statSync } from "node:fs";
2
+ import process from "node:process";
3
+ import { getErrorMessage } from "./error-utils.js";
4
+ import { normalizeFolderPath } from "./middleware/path-safety.js";
5
+ function getNodeErrorCode(error) {
6
+ return error && typeof error === "object" && "code" in error
7
+ ? String(error.code)
8
+ : "";
9
+ }
10
+ export function resolveSessionCwd(cwd, fallbackCwd) {
11
+ const raw = cwd?.trim() || fallbackCwd?.trim() || process.cwd();
12
+ const resolved = normalizeFolderPath(raw);
13
+ let fileStat;
14
+ try {
15
+ fileStat = statSync(resolved);
16
+ }
17
+ catch (error) {
18
+ const code = getNodeErrorCode(error);
19
+ if (code === "ENOENT") {
20
+ throw new Error(`工作目录不存在:${resolved}`);
21
+ }
22
+ if (code === "EACCES" || code === "EPERM") {
23
+ throw new Error(`没有权限访问工作目录:${resolved}`);
24
+ }
25
+ throw new Error(`无法访问工作目录:${resolved}(${getErrorMessage(error)})`);
26
+ }
27
+ if (!fileStat.isDirectory()) {
28
+ throw new Error(`工作目录不是目录:${resolved}`);
29
+ }
30
+ return resolved;
31
+ }
@@ -11,6 +11,7 @@ import { getErrorMessage } from "./error-utils.js";
11
11
  import { resolveSdkClaudeBinary } from "./claude-sdk-runner.js";
12
12
  import { buildLanguageDirective, buildManagedAutonomyDirective } from "./language-prompt.js";
13
13
  import { generateSessionTopic } from "./session-topic.js";
14
+ import { resolveSessionCwd } from "./session-cwd.js";
14
15
  function defaultStructuredRunner(provider) {
15
16
  return provider === "codex" ? "codex-cli-exec" : "claude-cli-print";
16
17
  }
@@ -541,8 +542,9 @@ export class StructuredSessionManager {
541
542
  const prompt = options.prompt?.trim();
542
543
  const provider = options.provider === "codex" ? "codex" : "claude";
543
544
  const runner = options.runner ?? defaultStructuredRunner(provider);
545
+ const baseCwd = resolveSessionCwd(options.cwd, this.config.defaultCwd);
544
546
  const worktreeSetup = options.worktreeEnabled
545
- ? prepareSessionWorktree({ cwd: options.cwd, sessionId: id })
547
+ ? prepareSessionWorktree({ cwd: baseCwd, sessionId: id })
546
548
  : null;
547
549
  const selectedModel = options.model?.trim() || null;
548
550
  const initialThinkingEffort = normalizeThinkingEffort(options.thinkingEffort);
@@ -556,7 +558,7 @@ export class StructuredSessionManager {
556
558
  : runner === "claude-sdk"
557
559
  ? "claude-agent-sdk (stream-json)"
558
560
  : "claude -p --output-format stream-json",
559
- cwd: worktreeSetup?.cwd ?? options.cwd,
561
+ cwd: worktreeSetup?.cwd ?? baseCwd,
560
562
  mode: options.mode,
561
563
  worktreeEnabled: Boolean(worktreeSetup),
562
564
  worktree: worktreeSetup?.worktree ?? null,