@co0ontty/wand 4.65.0 → 4.66.0-beta.g19d226c

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": "5afcc2a2c3f4ef09735c9d7eca22dee2a159ffa5",
3
- "builtAt": "2026-09-16T02:00:36.092Z",
4
- "version": "4.65.0",
5
- "channel": "stable"
2
+ "commit": "19d226c7839da00873a245da7e252395f095d730",
3
+ "builtAt": "2026-09-16T23:39:13.899Z",
4
+ "version": "4.66.0-beta.g19d226c",
5
+ "channel": "beta"
6
6
  }
@@ -409,7 +409,16 @@ export async function mergeSessionWorktreeAsync(options) {
409
409
  }
410
410
  export function prepareSessionWorktree(options) {
411
411
  const resolvedCwd = path.resolve(options.cwd);
412
- const repoRoot = runGit(["rev-parse", "--show-toplevel"], resolvedCwd);
412
+ let repoRoot;
413
+ try {
414
+ repoRoot = runGit(["rev-parse", "--show-toplevel"], resolvedCwd);
415
+ }
416
+ catch (error) {
417
+ if (/not a git repository/i.test(getGitErrorMessage(error))) {
418
+ throw new Error("当前目录不是 Git 仓库。请选择 Git 仓库,或关闭“独立 worktree 隔离”后重试。");
419
+ }
420
+ throw error;
421
+ }
413
422
  if (!repoRoot || !existsSync(repoRoot)) {
414
423
  throw new Error("当前目录不在 git 仓库中,无法启用 worktree 模式。");
415
424
  }
@@ -401,7 +401,8 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
401
401
  }
402
402
  try {
403
403
  const tree = currentDirectoryTree();
404
- if (!directoryTreeContainsPath(tree.roots, directoryPath)) {
404
+ const hasWorkspace = storage.listWorkspaces().some((workspace) => workspace.kind !== "global" && normalizeSessionDirectory(workspace.cwd) === directoryPath);
405
+ if (!directoryTreeContainsPath(tree.roots, directoryPath) && !hasWorkspace) {
405
406
  res.status(404).json({ error: "未找到该会话目录。" });
406
407
  return;
407
408
  }
@@ -105,30 +105,36 @@ export function registerSettingsRoutes(app, deps) {
105
105
  });
106
106
  }));
107
107
  app.get("/api/settings/env-preview", (req, res, next) => {
108
- if (req.query.reveal === "1")
109
- return requireAdmin(req, res, next);
110
- next();
111
- }, (req, res) => {
112
- const inheritEnv = config.inheritEnv !== false;
113
- const env = buildChildEnv(inheritEnv, {
114
- WAND_MODE: "<runtime>",
115
- WAND_AUTO_CONFIRM: "<runtime>",
116
- WAND_AUTO_EDIT: "<runtime>",
117
- });
118
108
  const reveal = req.query.reveal === "1" || req.query.reveal === "true";
119
- const sensitivePattern = /(KEY|TOKEN|SECRET|PASSWORD|AUTH|CREDENTIAL|COOKIE|SESSION)/i;
120
- const entries = Object.keys(env).sort().map((name) => {
121
- const raw = env[name] ?? "";
122
- const sensitive = sensitivePattern.test(name);
123
- const placeholder = raw.startsWith("<") && raw.endsWith(">");
124
- return {
125
- name,
126
- value: sensitive && !reveal && !placeholder ? "***" : raw,
127
- length: raw.length,
128
- sensitive,
129
- };
130
- });
131
- res.json({ inheritEnv, total: entries.length, reveal, entries });
109
+ if (reveal) {
110
+ return requireAdmin(req, res, (error) => {
111
+ if (error)
112
+ return next(error);
113
+ sendEnvironmentPreview(true);
114
+ });
115
+ }
116
+ sendEnvironmentPreview(false);
117
+ function sendEnvironmentPreview(reveal) {
118
+ const inheritEnv = config.inheritEnv !== false;
119
+ const env = buildChildEnv(inheritEnv, {
120
+ WAND_MODE: "<runtime>",
121
+ WAND_AUTO_CONFIRM: "<runtime>",
122
+ WAND_AUTO_EDIT: "<runtime>",
123
+ });
124
+ const sensitivePattern = /(KEY|TOKEN|SECRET|PASSWORD|AUTH|CREDENTIAL|COOKIE|SESSION)/i;
125
+ const entries = Object.keys(env).sort().map((name) => {
126
+ const raw = env[name] ?? "";
127
+ const sensitive = sensitivePattern.test(name);
128
+ const placeholder = raw.startsWith("<") && raw.endsWith(">");
129
+ return {
130
+ name,
131
+ value: sensitive && !reveal && !placeholder ? "***" : raw,
132
+ length: raw.length,
133
+ sensitive,
134
+ };
135
+ });
136
+ res.json({ inheritEnv, total: entries.length, reveal, entries });
137
+ }
132
138
  });
133
139
  app.post("/api/settings/system-ai/import", requireAdmin, (_req, res) => {
134
140
  const imported = discoverCliSystemAiConfigs();
@@ -2,12 +2,17 @@ import { spawn } from "node:child_process";
2
2
  import type { StructuredExecHost } from "./structured-exec-host.js";
3
3
  import type { StructuredRunnerAdapter, StructuredRunnerContext, StructuredRunnerExecution, StructuredRunnerObserver, StructuredRunnerTurnState } from "./structured-runner.js";
4
4
  import type { SessionSnapshot } from "./types.js";
5
+ type PiTurnState = StructuredRunnerTurnState & {
6
+ pendingSessionId?: string;
7
+ };
8
+ export declare function isMissingPiSession(error: string | null | undefined, sessionId: string | null | undefined): boolean;
5
9
  export declare function buildPiArgs(session: SessionSnapshot, prompt: string): string[];
6
10
  export declare function piToolName(name: string): string;
7
- export declare function applyPiEvent(state: StructuredRunnerTurnState, event: Record<string, unknown>): string | null;
11
+ export declare function applyPiEvent(state: PiTurnState, event: Record<string, unknown>): string | null;
8
12
  export declare class PiRunner implements StructuredRunnerAdapter {
9
13
  private readonly spawnProcess;
10
14
  private readonly execHost?;
11
15
  constructor(spawnProcess?: typeof spawn, execHost?: StructuredExecHost | undefined);
12
16
  start(context: StructuredRunnerContext, observer: StructuredRunnerObserver): StructuredRunnerExecution;
13
17
  }
18
+ export {};
@@ -2,6 +2,9 @@ import { spawn } from "node:child_process";
2
2
  import { startStructuredCli } from "./structured-exec-pump.js";
3
3
  import { asRecord } from "./structured-content.js";
4
4
  import { thinkingEffortToPiLevel } from "./structured-provider-common.js";
5
+ export function isMissingPiSession(error, sessionId) {
6
+ return !!sessionId && !!error?.includes(`No session found matching '${sessionId}'`);
7
+ }
5
8
  function textContent(value) {
6
9
  if (typeof value === "string")
7
10
  return value;
@@ -88,8 +91,10 @@ function applyPiAssistantMessage(state, message) {
88
91
  return null;
89
92
  }
90
93
  export function applyPiEvent(state, event) {
94
+ // Pi announces the ID before it writes a session file. The file is only
95
+ // created when an assistant message is saved, so this ID is not resumable yet.
91
96
  if (event.type === "session" && typeof event.id === "string")
92
- state.sessionId = event.id;
97
+ state.pendingSessionId = event.id;
93
98
  if (event.type === "message_update") {
94
99
  const update = asRecord(event.assistantMessageEvent);
95
100
  const delta = typeof update?.delta === "string" ? update.delta : "";
@@ -136,13 +141,18 @@ export function applyPiEvent(state, event) {
136
141
  }
137
142
  if (event.type === "message_end" || event.type === "turn_end") {
138
143
  const message = asRecord(event.message);
139
- if (message?.role === "assistant")
144
+ if (message?.role === "assistant") {
145
+ if (state.pendingSessionId)
146
+ state.sessionId = state.pendingSessionId;
140
147
  return applyPiAssistantMessage(state, message);
148
+ }
141
149
  }
142
150
  if (event.type === "agent_end" && Array.isArray(event.messages)) {
143
151
  for (const raw of event.messages) {
144
152
  const message = asRecord(raw);
145
153
  if (message?.role === "assistant") {
154
+ if (state.pendingSessionId)
155
+ state.sessionId = state.pendingSessionId;
146
156
  const error = applyPiAssistantMessage(state, message);
147
157
  if (error)
148
158
  return error;
@@ -19,7 +19,7 @@ import { captureTaskMeta, extractClaudeAssistantMessage, extractClaudeModelName,
19
19
  import { OpenCodeRunner, applyOpenCodeEvent } from "./structured-opencode-adapter.js";
20
20
  import { GrokRunner, applyGrokEvent } from "./structured-grok-adapter.js";
21
21
  import { QoderRunner } from "./structured-qoder-adapter.js";
22
- import { PiRunner, applyPiEvent } from "./structured-pi-adapter.js";
22
+ import { PiRunner, applyPiEvent, isMissingPiSession } from "./structured-pi-adapter.js";
23
23
  import { structuredRunId, } from "./structured-exec-host.js";
24
24
  import { defaultStructuredRunner, defaultStructuredState, isStructuredRunnerForProvider, normalizeThinkingEffort, resolveStructuredRunner, } from "./structured-provider-common.js";
25
25
  import { enrichStructuredMessages, WAND_PROTOCOL_VERSION } from "./structured-client-protocol.js";
@@ -1137,6 +1137,9 @@ export class StructuredSessionManager {
1137
1137
  content: [{ type: "text", text: prompt }],
1138
1138
  };
1139
1139
  const requestId = randomUUID();
1140
+ if (session.provider === "pi" && isMissingPiSession(session.structuredState?.lastError, session.claudeSessionId)) {
1141
+ session = { ...session, claudeSessionId: null };
1142
+ }
1140
1143
  const updated = {
1141
1144
  ...session,
1142
1145
  status: "running",
@@ -2408,6 +2411,9 @@ export class StructuredSessionManager {
2408
2411
  primary: result.primaryError,
2409
2412
  });
2410
2413
  const failed = this.finishStructuredFailure(current, typeof result.exitCode === "number" ? result.exitCode : 1, errorText, result.state);
2414
+ if (provider === "pi" && isMissingPiSession(result.stderr, current.claudeSessionId)) {
2415
+ failed.claudeSessionId = null;
2416
+ }
2411
2417
  this.sessions.set(sessionId, failed);
2412
2418
  this.saveAuthoritativeSession(failed);
2413
2419
  this.emitStructuredSnapshot(failed);
@@ -1,5 +1,6 @@
1
1
  import { provisionalTaskTitleFromDescription } from "./task-title.js";
2
- import { isClosedWandTaskStatus } from "./task-types.js";
2
+ import { isSessionProvider } from "./session-provider.js";
3
+ import { isClosedWandTaskStatus, normalizeWandTaskAgentMode } from "./task-types.js";
3
4
  const UNNAMED_TASK_NAME = "未命名任务";
4
5
  function boardDescriptionFor(task, workspace) {
5
6
  const lines = [`项目:${workspace.name}`];
@@ -98,6 +99,17 @@ function promoteUngroupedCard(storage, card, patch) {
98
99
  workspaceTaskId: patch.workspaceTaskId !== undefined ? patch.workspaceTaskId : card.workspaceTaskId,
99
100
  }) ?? card;
100
101
  }
102
+ function agentFromSession(session) {
103
+ if (!isSessionProvider(session.provider))
104
+ return null;
105
+ const effort = session.thinkingEffort;
106
+ return {
107
+ provider: session.provider,
108
+ model: session.selectedModel || session.structuredState?.model || "default",
109
+ thinkingEffort: effort === "standard" || effort === "deep" || effort === "max" ? effort : "off",
110
+ mode: normalizeWandTaskAgentMode(session.provider, session.mode),
111
+ };
112
+ }
101
113
  /** 侧栏工作任务与看板卡片对账:已关联则复用,同项目同名未关联则挂上,否则新建。 */
102
114
  export function ensureBoardTaskForWorkspaceTask(storage, task, workspace) {
103
115
  const linked = storage.getWandTaskByWorkspaceTaskId(task.id);
@@ -119,7 +131,7 @@ export function ensureBoardTaskForWorkspaceTask(storage, task, workspace) {
119
131
  workspaceTaskId: task.id,
120
132
  title: task.name,
121
133
  description: boardDescriptionFor(task, workspace),
122
- status: task.status === "done" ? "done" : "todo",
134
+ status: task.status === "done" ? "done" : "doing",
123
135
  milestoneId: task.milestoneId ?? null,
124
136
  });
125
137
  }
@@ -132,9 +144,21 @@ export function syncUngroupedSessionsToBoard(storage) {
132
144
  const bound = new Set(storage.listBoundWandTaskSessionIds());
133
145
  for (const workspace of storage.listWorkspaces()) {
134
146
  for (const task of storage.listWorkspaceTasks(workspace.id)) {
135
- if (!isUnnamedWorkspaceTaskName(task.name))
136
- continue;
137
147
  const sessions = storage.listSessionsByWorkspaceTask(task.id);
148
+ if (!isUnnamedWorkspaceTaskName(task.name)) {
149
+ let card = ensureBoardTaskForWorkspaceTask(storage, task, workspace);
150
+ if (card && !card.agent) {
151
+ const agent = sessions.map(agentFromSession).find((value) => value !== null);
152
+ if (agent)
153
+ card = storage.updateWandTask(card.id, { agent }) ?? card;
154
+ }
155
+ if (card) {
156
+ bindSessions(storage, card.id, sessions.map((session) => session.id));
157
+ for (const session of sessions)
158
+ bound.add(session.id);
159
+ }
160
+ continue;
161
+ }
138
162
  const best = pickBestSession(sessions);
139
163
  const title = best ? boardTitleFromSession(best) : "";
140
164
  if (!best || !title)