@co0ontty/wand 4.84.0 → 4.85.1-beta.g18a596a

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": "a87dbb00803c14eb160778bbc2097270457b3b7a",
3
- "builtAt": "2026-10-07T23:20:56.996Z",
4
- "version": "4.84.0",
5
- "channel": "stable"
2
+ "commit": "18a596a0dddbd8bd978f9e023fa2e163e33ee531",
3
+ "builtAt": "2026-10-08T02:06:19.406Z",
4
+ "version": "4.85.1-beta.g18a596a",
5
+ "channel": "beta"
6
6
  }
@@ -555,6 +555,31 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
555
555
  sendRouteError(res, error, "无法启动结构化会话。");
556
556
  }
557
557
  }));
558
+ app.post("/api/sessions/:id/directory", (req, res) => {
559
+ const cwd = req.body?.cwd;
560
+ if (typeof cwd !== "string" || !cwd.trim()) {
561
+ res.status(400).json({ error: "请选择有效的运行目录。" });
562
+ return;
563
+ }
564
+ const id = req.params.id;
565
+ const owner = sessions.ownerOf(id);
566
+ if (!owner) {
567
+ res.status(404).json({ error: "未找到该会话。" });
568
+ return;
569
+ }
570
+ if (owner !== "structured") {
571
+ res.status(400).json({ error: "只有新建空白结构化对话可以更换运行目录。" });
572
+ return;
573
+ }
574
+ try {
575
+ const updated = structured.setSessionDirectory(id, cwd);
576
+ onSessionCreated?.(updated.cwd);
577
+ res.json(sessionResponseDTO(updated, req));
578
+ }
579
+ catch (error) {
580
+ sendRouteError(res, error, "切换运行目录失败。");
581
+ }
582
+ });
558
583
  app.post("/api/sessions/:id/provider", (req, res) => {
559
584
  const provider = req.body?.provider;
560
585
  if (!isSessionProvider(provider)) {
package/dist/storage.d.ts CHANGED
@@ -169,6 +169,8 @@ export declare class WandStorage {
169
169
  listSessionsByWorkspace(workspaceId: string): SessionSnapshot[];
170
170
  /** 显式更新某会话的工作空间归属(用于创建时绑定)。 */
171
171
  getSessionWorkspace(sessionId: string): Pick<SessionSnapshot, "workspaceId" | "workspaceTaskId"> | null;
172
+ /** 空白独立会话的目录与项目归属一次落库;创建新项目失败也随事务回滚。 */
173
+ updateBlankSessionDirectory(sessionId: string, cwd: string): string;
172
174
  setSessionWorkspaceId(sessionId: string, workspaceId: string | null): void;
173
175
  /** Normal workspace counts exclude archived sessions and sessions in archived tasks. */
174
176
  countSessionsByWorkspace(options?: {
package/dist/storage.js CHANGED
@@ -5,6 +5,7 @@ import path from "node:path";
5
5
  import { DatabaseSync } from "node:sqlite";
6
6
  import { GLOBAL_WORKSPACE_ID } from "./types.js";
7
7
  import { normalizeSessionDirectory } from "./session-directory-tree.js";
8
+ import { ensureWorkspaceForCwd } from "./workspace-binding.js";
8
9
  import { defaultPiCliSessionSettings, defaultPiSessionSettings, patchPiSessionSettings } from "./pi-session-settings.js";
9
10
  import { parseHarnessExtensionState } from "./core-extension-host.js";
10
11
  import { inferProviderFromCommand, inferProviderFromRunner, isSessionProvider, SESSION_PROVIDERS } from "./session-provider.js";
@@ -1694,6 +1695,19 @@ export class WandStorage {
1694
1695
  .get(sessionId);
1695
1696
  return row ? { workspaceId: row.workspace_id ?? undefined, workspaceTaskId: row.workspace_task_id ?? undefined } : null;
1696
1697
  }
1698
+ /** 空白独立会话的目录与项目归属一次落库;创建新项目失败也随事务回滚。 */
1699
+ updateBlankSessionDirectory(sessionId, cwd) {
1700
+ return this.transaction(() => {
1701
+ const session = this.getSession(sessionId);
1702
+ if (!session || session.workspaceTaskId || session.worktreeEnabled || session.worktree) {
1703
+ throw new Error("任务或工作树会话必须保留原运行目录。");
1704
+ }
1705
+ const workspace = ensureWorkspaceForCwd(this, cwd);
1706
+ this.db.prepare("UPDATE command_sessions SET cwd = ?, workspace_id = ? WHERE id = ?")
1707
+ .run(cwd, workspace.id, sessionId);
1708
+ return workspace.id;
1709
+ });
1710
+ }
1697
1711
  setSessionWorkspaceId(sessionId, workspaceId) {
1698
1712
  const binding = this.getSessionWorkspace(sessionId);
1699
1713
  const task = binding?.workspaceTaskId ? this.getWorkspaceTask(binding.workspaceTaskId) : null;
@@ -225,6 +225,8 @@ export declare class StructuredSessionManager {
225
225
  promoteQueuedMessage(sessionId: string, index: number, expectedText?: string, idempotencyKey?: string): Promise<SessionSnapshot>;
226
226
  /** Clear all queued messages. No-op when queue is already empty. */
227
227
  clearQueuedMessages(sessionId: string): SessionSnapshot;
228
+ /** 仅空白独立对话能改目录:不建替代会话、不迁移任务/worktree 或已接受的输入。 */
229
+ setSessionDirectory(sessionId: string, cwd: string): SessionSnapshot;
228
230
  /** 新建空白对话可原位换 CLI;接受过输入、恢复会话和自动化不跨 provider 搬历史。 */
229
231
  setSessionProvider(sessionId: string, provider: SessionProvider): SessionSnapshot;
230
232
  getPiSettings(sessionId: string): {
@@ -2037,6 +2037,32 @@ export class StructuredSessionManager {
2037
2037
  this.emitStructuredSnapshot(updated);
2038
2038
  return updated;
2039
2039
  }
2040
+ /** 仅空白独立对话能改目录:不建替代会话、不迁移任务/worktree 或已接受的输入。 */
2041
+ setSessionDirectory(sessionId, cwd) {
2042
+ if (typeof cwd !== "string" || !cwd.trim())
2043
+ throw new Error("请选择有效的运行目录。");
2044
+ const session = this.requireSession(sessionId);
2045
+ if (session.status !== "idle" || session.archived || session.structuredState?.inFlight ||
2046
+ this.pendingRunnerExecutions.has(sessionId) || session.claudeSessionId ||
2047
+ session.resumedFromSessionId || session.autoRecovered ||
2048
+ (session.messages?.length ?? 0) > 0 || (session.queuedMessages?.length ?? 0) > 0 ||
2049
+ session.automationId || (session.sessionSource && session.sessionSource !== "interactive") ||
2050
+ this.relayHandlerFor(session)) {
2051
+ throw new Error("只有尚未发送消息的新建空白对话可以更换运行目录。");
2052
+ }
2053
+ if (session.workspaceTaskId || session.worktreeEnabled || session.worktree) {
2054
+ throw new Error("任务或工作树会话必须保留原运行目录。");
2055
+ }
2056
+ const resolved = resolveSessionCwd(cwd, null);
2057
+ const workspaceId = this.storage.updateBlankSessionDirectory(sessionId, resolved);
2058
+ const updated = { ...session, cwd: resolved, workspaceId };
2059
+ this.sessions.set(sessionId, updated);
2060
+ this.emit({
2061
+ type: "status", sessionId,
2062
+ data: { sessionKind: "structured", cwd: resolved, workspaceId },
2063
+ });
2064
+ return updated;
2065
+ }
2040
2066
  /** 新建空白对话可原位换 CLI;接受过输入、恢复会话和自动化不跨 provider 搬历史。 */
2041
2067
  setSessionProvider(sessionId, provider) {
2042
2068
  const session = this.requireSession(sessionId);