@co0ontty/wand 2.11.1 → 2.12.0

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": "91ceeeaabff61dfaa09abd5bf1327dda55f51879",
3
- "builtAt": "2026-07-12T01:48:23.296Z",
4
- "version": "2.11.1",
2
+ "commit": "2ffe360ae10acb539bd5e5fe785073348e2b0482",
3
+ "builtAt": "2026-07-12T04:24:13.405Z",
4
+ "version": "2.12.0",
5
5
  "channel": "stable"
6
6
  }
@@ -18,7 +18,6 @@ import path from "node:path";
18
18
  import process from "node:process";
19
19
  import { promisify } from "node:util";
20
20
  import { whichSync } from "./path-repair.js";
21
- import { compareSemver } from "./version-utils.js";
22
21
  import { getErrorMessage } from "./error-utils.js";
23
22
  const execFileAsync = promisify(execFile);
24
23
  export const PACKAGE_NAME = "@co0ontty/wand";
@@ -51,27 +50,13 @@ function computeUpdateAvailable(currentVersion, latestVersion, channel) {
51
50
  if (!latestVersion)
52
51
  return false;
53
52
  const current = cleanVersion(currentVersion);
54
- const latest = cleanVersion(latestVersion);
55
- const currentTag = getStableTagVersion(current);
56
- const latestTag = getStableTagVersion(latest);
57
- const tagCompare = compareSemver(latestTag, currentTag);
58
- if (channel === "beta") {
59
- if (tagCompare < 0)
60
- return false;
61
- if (tagCompare > 0)
62
- return true;
63
- // Beta follows the beta npm dist-tag exactly. The suffix contains the short
64
- // git SHA, so recency comes from the dist-tag pointer instead of semver order.
65
- return latest !== current;
66
- }
67
- if (tagCompare < 0)
68
- return false;
69
- if (tagCompare > 0)
70
- return true;
71
- // Stable intentionally tracks only the pure tag version. If the current build
72
- // is a beta with the same base tag, switching back to stable should reinstall
73
- // the clean npm @latest package.
74
- return current !== latestTag;
53
+ const target = channel === "stable"
54
+ ? getStableTagVersion(latestVersion)
55
+ : cleanVersion(latestVersion);
56
+ // npm's selected dist-tag is authoritative. Manual/local builds can have a
57
+ // numerically higher, lower, invalid, or suffixed version; any mismatch must
58
+ // still allow switching to the exact package selected by @latest or @beta.
59
+ return current !== target;
75
60
  }
76
61
  export function buildPackageUpdateInfo(currentVersion, channel, latestVersion) {
77
62
  const latest = latestVersion?.trim() || null;
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { WandStorage } from "./storage.js";
3
- import { ExecutionMode, ProcessEventHandler, SessionProvider, SessionSnapshot, WandConfig } from "./types.js";
3
+ import { ExecutionMode, ProcessEventHandler, SessionProvider, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
4
4
  export type { ProcessEvent, ProcessEventHandler } from "./types.js";
5
5
  /** Human-readable task information for the UI */
6
6
  export interface TaskInfo {
@@ -68,6 +68,8 @@ export declare class ProcessManager extends EventEmitter {
68
68
  cols?: number;
69
69
  rows?: number;
70
70
  thinkingEffort?: SessionSnapshot["thinkingEffort"];
71
+ sessionSource?: SessionSource;
72
+ automationId?: string;
71
73
  }): SessionSnapshot;
72
74
  list(): SessionSnapshot[];
73
75
  /** Return lightweight snapshots for the session list (no output/messages). */
@@ -701,6 +701,7 @@ export class ProcessManager extends EventEmitter {
701
701
  const recoveredMessages = recoverMessagesFromSnapshot(snapshot);
702
702
  const updated = {
703
703
  ...snapshot,
704
+ sessionSource: snapshot.sessionSource ?? "interactive",
704
705
  status: "exited",
705
706
  endedAt: orphanEndedAt,
706
707
  claudeSessionId: restoredSessionId ?? null,
@@ -713,6 +714,7 @@ export class ProcessManager extends EventEmitter {
713
714
  }
714
715
  this.sessions.set(snapshot.id, {
715
716
  ...updated,
717
+ sessionSource: snapshot.sessionSource ?? "interactive",
716
718
  provider,
717
719
  processId: null,
718
720
  ptyProcess: null,
@@ -871,15 +873,22 @@ export class ProcessManager extends EventEmitter {
871
873
  // re-prints its own banner and replayed history into the new PTY, and
872
874
  // mixing the two would surface every line twice in the terminal view.
873
875
  let priorMessages = [];
876
+ let inheritedSessionSource;
877
+ let inheritedAutomationId;
874
878
  if (opts?.reuseId) {
875
879
  const oldRecord = this.sessions.get(id);
876
880
  if (oldRecord) {
877
881
  priorMessages = oldRecord.ptyBridge?.getMessages() ?? oldRecord.messages ?? [];
882
+ inheritedSessionSource = oldRecord.sessionSource;
883
+ inheritedAutomationId = oldRecord.automationId;
878
884
  this.cleanupRecord(oldRecord);
879
885
  this.sessions.delete(id);
880
886
  }
881
887
  else {
882
- priorMessages = this.storage.getSession(id)?.messages ?? [];
888
+ const stored = this.storage.getSession(id);
889
+ priorMessages = stored?.messages ?? [];
890
+ inheritedSessionSource = stored?.sessionSource;
891
+ inheritedAutomationId = stored?.automationId;
883
892
  }
884
893
  }
885
894
  const worktreeSetup = opts?.worktreeEnabled
@@ -908,6 +917,8 @@ export class ProcessManager extends EventEmitter {
908
917
  const startedAt = new Date().toISOString();
909
918
  const record = {
910
919
  id,
920
+ sessionSource: opts?.sessionSource ?? inheritedSessionSource ?? "interactive",
921
+ automationId: opts?.automationId ?? inheritedAutomationId,
911
922
  provider,
912
923
  command,
913
924
  cwd: resolvedCwd,
@@ -1672,7 +1683,7 @@ export class ProcessManager extends EventEmitter {
1672
1683
  }
1673
1684
  }
1674
1685
  runStartupCommands() {
1675
- return this.config.startupCommands.map((command) => this.start(command, this.config.defaultCwd, this.config.defaultMode));
1686
+ return this.config.startupCommands.map((command) => this.start(command, this.config.defaultCwd, this.config.defaultMode, undefined, { sessionSource: "startup" }));
1676
1687
  }
1677
1688
  snapshot(record) {
1678
1689
  // Get messages from bridge if available, otherwise use stored messages
@@ -1680,6 +1691,8 @@ export class ProcessManager extends EventEmitter {
1680
1691
  return {
1681
1692
  id: record.id,
1682
1693
  sessionKind: "pty",
1694
+ sessionSource: record.sessionSource ?? "interactive",
1695
+ automationId: record.automationId,
1683
1696
  provider: record.provider,
1684
1697
  runner: "pty",
1685
1698
  command: record.command,
@@ -2,8 +2,15 @@ import { Express } from "express";
2
2
  import { ProcessManager } from "./process-manager.js";
3
3
  import { StructuredSessionManager } from "./structured-session-manager.js";
4
4
  import { WandStorage } from "./storage.js";
5
- import { ExecutionMode, WandConfig } from "./types.js";
5
+ import { ExecutionMode, SessionSource, WandConfig } from "./types.js";
6
6
  import { getErrorMessage } from "./error-utils.js";
7
7
  export { getErrorMessage };
8
+ export declare function parseSessionCreationOrigin(body: {
9
+ sessionSource?: unknown;
10
+ automationId?: unknown;
11
+ } | null | undefined): {
12
+ sessionSource: SessionSource;
13
+ automationId?: string;
14
+ };
8
15
  export declare function registerSessionRoutes(app: Express, processes: ProcessManager, structured: StructuredSessionManager, storage: WandStorage, defaultMode: ExecutionMode, config: WandConfig, onSessionCreated?: (cwd: string | undefined | null) => void): void;
9
16
  export declare function registerClaudeHistoryRoutes(app: Express, processes: ProcessManager, storage: WandStorage): void;
@@ -8,6 +8,24 @@ import { resolveCommitAiContext } from "./session-ai-context.js";
8
8
  import { getGitStatus, QuickCommitError, runQuickCommitWithFallback, runTagHead, runPush, generateCommitMessageOnly, } from "./git-quick-commit.js";
9
9
  import { getErrorMessage } from "./error-utils.js";
10
10
  export { getErrorMessage };
11
+ export function parseSessionCreationOrigin(body) {
12
+ const rawSource = body?.sessionSource;
13
+ if (rawSource !== undefined && rawSource !== "interactive" && rawSource !== "automation" && rawSource !== "startup") {
14
+ throw new Error("sessionSource 必须是 interactive、automation 或 startup。");
15
+ }
16
+ const rawAutomationId = body?.automationId;
17
+ if (rawAutomationId !== undefined && typeof rawAutomationId !== "string") {
18
+ throw new Error("automationId 必须是非空字符串。");
19
+ }
20
+ const automationId = rawAutomationId?.trim();
21
+ if (rawAutomationId !== undefined && !automationId) {
22
+ throw new Error("automationId 必须是非空字符串。");
23
+ }
24
+ return {
25
+ sessionSource: rawSource ?? "interactive",
26
+ ...(automationId ? { automationId } : {}),
27
+ };
28
+ }
11
29
  function getInputErrorResponse(error, sessionId) {
12
30
  if (error instanceof SessionInputError) {
13
31
  const statusCode = error.code === "SESSION_NOT_FOUND" ? 404 : 409;
@@ -235,6 +253,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
235
253
  }
236
254
  const provider = body.provider === "codex" ? "codex" : "claude";
237
255
  const rawModel = typeof body.model === "string" ? body.model.trim() : "";
256
+ const origin = parseSessionCreationOrigin(body);
238
257
  const snapshot = structured.createSession({
239
258
  cwd: resolveSessionCwd(body.cwd, config.defaultCwd),
240
259
  mode: normalizeMode(body.mode, defaultMode),
@@ -245,6 +264,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
245
264
  thinkingEffort: typeof body.thinkingEffort === "string"
246
265
  ? body.thinkingEffort
247
266
  : config.defaultThinkingEffort,
267
+ ...origin,
248
268
  });
249
269
  onSessionCreated?.(snapshot.cwd);
250
270
  const prompt = body.prompt?.trim();
@@ -811,6 +831,9 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
811
831
  const claudeSessionId = String(req.params.claudeSessionId || "").trim();
812
832
  const body = req.body;
813
833
  try {
834
+ const requestedOrigin = body.sessionSource !== undefined || body.automationId !== undefined
835
+ ? parseSessionCreationOrigin(body)
836
+ : null;
814
837
  if (!claudeSessionId) {
815
838
  res.status(400).json({ error: "Claude 会话 ID 不能为空。" });
816
839
  return;
@@ -838,7 +861,12 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
838
861
  const resumeCommand = `${command} --resume ${claudeSessionId}`;
839
862
  const reqCols = typeof body.cols === "number" && Number.isFinite(body.cols) ? body.cols : undefined;
840
863
  const reqRows = typeof body.rows === "number" && Number.isFinite(body.rows) ? body.rows : undefined;
841
- const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, { reuseId: existingSession.id, cols: reqCols, rows: reqRows });
864
+ const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, {
865
+ reuseId: existingSession.id,
866
+ cols: reqCols,
867
+ rows: reqRows,
868
+ ...(requestedOrigin ?? {}),
869
+ });
842
870
  res.status(201).json({ resumedClaudeSessionId: claudeSessionId, ...newSnapshot });
843
871
  }
844
872
  else {
@@ -851,7 +879,11 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
851
879
  const resumeCommand = `claude --resume ${claudeSessionId}`;
852
880
  const reqCols = typeof body.cols === "number" && Number.isFinite(body.cols) ? body.cols : undefined;
853
881
  const reqRows = typeof body.rows === "number" && Number.isFinite(body.rows) ? body.rows : undefined;
854
- const newSnapshot = processes.start(resumeCommand, cwd, newMode, undefined, { cols: reqCols, rows: reqRows });
882
+ const newSnapshot = processes.start(resumeCommand, cwd, newMode, undefined, {
883
+ cols: reqCols,
884
+ rows: reqRows,
885
+ ...(requestedOrigin ?? {}),
886
+ });
855
887
  res.status(201).json({ resumedClaudeSessionId: claudeSessionId, ...newSnapshot });
856
888
  }
857
889
  }
@@ -910,6 +942,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
910
942
  return;
911
943
  }
912
944
  const newMode = normalizeMode(body.mode, defaultMode);
945
+ const origin = parseSessionCreationOrigin(body);
913
946
  const snapshot = structured.createSession({
914
947
  cwd,
915
948
  mode: newMode,
@@ -917,6 +950,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
917
950
  runner: "codex-cli-exec",
918
951
  worktreeEnabled: body.worktreeEnabled === true,
919
952
  claudeSessionId: threadId,
953
+ ...origin,
920
954
  });
921
955
  onSessionCreated?.(cwd);
922
956
  res.status(201).json({ resumedClaudeSessionId: threadId, ...snapshot });
package/dist/server.js CHANGED
@@ -20,7 +20,7 @@ import { getCachedModels, refreshModels } from "./models.js";
20
20
  import { ProcessManager } from "./process-manager.js";
21
21
  import { SessionLogger } from "./session-logger.js";
22
22
  import { StructuredSessionManager } from "./structured-session-manager.js";
23
- import { getErrorMessage, registerClaudeHistoryRoutes, registerSessionRoutes } from "./server-session-routes.js";
23
+ import { getErrorMessage, parseSessionCreationOrigin, registerClaudeHistoryRoutes, registerSessionRoutes } from "./server-session-routes.js";
24
24
  import { checkPackageUpdateAsync, installPackageGloballyAsync, normalizeUpdateChannel, resolveGlobalWandCli, } from "./npm-update-utils.js";
25
25
  import { repairServiceUnitAfterUpdate } from "./service-self-repair.js";
26
26
  import { computeRelaunch } from "./relaunch.js";
@@ -1851,11 +1851,8 @@ export async function startServer(config, configPath) {
1851
1851
  res.status(502).json({ error: "无法连接到 npm registry。" });
1852
1852
  return;
1853
1853
  }
1854
- if (!info.updateAvailable) {
1855
- res.json({ ok: true, message: channel === "beta" ? "已是最新 Beta 版本。" : "已经是最新版本。" });
1856
- return;
1857
- }
1858
1854
  const targetLabel = info.latest;
1855
+ const reinstalling = !info.updateAvailable;
1859
1856
  if (!canUseDetachedUpdateHelper()) {
1860
1857
  res.status(500).json({ error: "当前平台暂不支持 Web 异步更新,请在终端运行 install.sh 更新。" });
1861
1858
  return;
@@ -1886,7 +1883,7 @@ export async function startServer(config, configPath) {
1886
1883
  });
1887
1884
  res.json({
1888
1885
  ok: true,
1889
- message: `已开始更新到 ${targetLabel}`,
1886
+ message: reinstalling ? `已开始重新安装 ${targetLabel}` : `已开始更新到 ${targetLabel}`,
1890
1887
  restartRequired: false,
1891
1888
  detachedUpdate: true,
1892
1889
  version: targetLabel,
@@ -2355,6 +2352,7 @@ export async function startServer(config, configPath) {
2355
2352
  }
2356
2353
  const initialInput = body.initialInput?.trim();
2357
2354
  try {
2355
+ const origin = parseSessionCreationOrigin(body);
2358
2356
  const rawModel = typeof body.model === "string" ? body.model.trim() : "";
2359
2357
  const provider = body.provider === "codex" || /^codex\b/.test(body.command.trim()) ? "codex" : "claude";
2360
2358
  const effectiveModel = rawModel || getDefaultModelForProvider(config, provider) || undefined;
@@ -2367,6 +2365,7 @@ export async function startServer(config, configPath) {
2367
2365
  cols: reqCols,
2368
2366
  rows: reqRows,
2369
2367
  thinkingEffort: body.thinkingEffort ?? config.defaultThinkingEffort,
2368
+ ...origin,
2370
2369
  });
2371
2370
  recordRecentPath(storage, snapshot.cwd);
2372
2371
  res.status(201).json(snapshot);
package/dist/storage.js CHANGED
@@ -53,6 +53,9 @@ function normalizeWorktreeMergeStatus(raw) {
53
53
  }
54
54
  return undefined;
55
55
  }
56
+ function normalizeSessionSource(raw) {
57
+ return raw === "automation" || raw === "startup" || raw === "interactive" ? raw : "interactive";
58
+ }
56
59
  function mapWorktreeMergeFields(row) {
57
60
  return {
58
61
  worktreeMergeStatus: normalizeWorktreeMergeStatus(row.worktree_merge_status),
@@ -60,16 +63,18 @@ function mapWorktreeMergeFields(row) {
60
63
  };
61
64
  }
62
65
  function sessionSelectFields() {
63
- return `id, provider, session_kind, runner, command, cwd, mode, status, exit_code, started_at, ended_at, output, archived, archived_at, claude_session_id, messages, queued_messages, structured_state
66
+ return `id, session_source, automation_id, provider, session_kind, runner, command, cwd, mode, status, exit_code, started_at, ended_at, output, archived, archived_at, claude_session_id, messages, queued_messages, structured_state
64
67
  , resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description`;
65
68
  }
66
69
  function sessionPersistFields() {
67
- return `id, command, cwd, mode, status, exit_code, started_at, ended_at, output
70
+ return `id, session_source, automation_id, command, cwd, mode, status, exit_code, started_at, ended_at, output
68
71
  , archived, archived_at, claude_session_id, provider, session_kind, runner, messages, queued_messages, structured_state
69
72
  , resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description`;
70
73
  }
71
74
  function sessionPersistAssignments() {
72
- return `command = excluded.command,
75
+ return `session_source = excluded.session_source,
76
+ automation_id = excluded.automation_id,
77
+ command = excluded.command,
73
78
  cwd = excluded.cwd,
74
79
  mode = excluded.mode,
75
80
  status = excluded.status,
@@ -96,7 +101,8 @@ function sessionPersistAssignments() {
96
101
  description = excluded.description`;
97
102
  }
98
103
  function sessionMetadataAssignments() {
99
- return `command = ?, cwd = ?, mode = ?, status = ?, exit_code = ?,
104
+ return `session_source = ?, automation_id = ?,
105
+ command = ?, cwd = ?, mode = ?, status = ?, exit_code = ?,
100
106
  started_at = ?, ended_at = ?, output = ?,
101
107
  archived = ?, archived_at = ?, claude_session_id = ?,
102
108
  provider = ?, session_kind = ?, runner = ?, structured_state = ?,
@@ -107,6 +113,8 @@ function sessionMetadataAssignments() {
107
113
  function sessionPersistValues(snapshot) {
108
114
  return [
109
115
  snapshot.id,
116
+ normalizeSessionSource(snapshot.sessionSource),
117
+ snapshot.automationId ?? null,
110
118
  snapshot.command,
111
119
  snapshot.cwd,
112
120
  snapshot.mode,
@@ -136,6 +144,8 @@ function sessionPersistValues(snapshot) {
136
144
  }
137
145
  function sessionMetadataValues(snapshot) {
138
146
  return [
147
+ normalizeSessionSource(snapshot.sessionSource),
148
+ snapshot.automationId ?? null,
139
149
  snapshot.command,
140
150
  snapshot.cwd,
141
151
  snapshot.mode,
@@ -166,6 +176,8 @@ function mapSessionCore(row) {
166
176
  const provider = inferSessionProvider(row);
167
177
  return {
168
178
  id: row.id,
179
+ sessionSource: normalizeSessionSource(row.session_source),
180
+ automationId: row.automation_id ?? undefined,
169
181
  sessionKind: row.session_kind ?? "pty",
170
182
  provider,
171
183
  runner: row.runner ?? undefined,
@@ -207,6 +219,8 @@ const INIT_SQL = `
207
219
 
208
220
  CREATE TABLE IF NOT EXISTS command_sessions (
209
221
  id TEXT PRIMARY KEY,
222
+ session_source TEXT NOT NULL DEFAULT 'interactive',
223
+ automation_id TEXT,
210
224
  command TEXT NOT NULL,
211
225
  cwd TEXT NOT NULL,
212
226
  mode TEXT NOT NULL,
@@ -506,7 +520,7 @@ export class WandStorage {
506
520
  this.db
507
521
  .prepare(`INSERT INTO command_sessions (
508
522
  ${sessionPersistFields()}
509
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
523
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
510
524
  ON CONFLICT(id) DO UPDATE SET
511
525
  ${sessionPersistAssignments()}`)
512
526
  .run(...sessionPersistValues(snapshot));
@@ -603,6 +617,8 @@ function mapPasswordItemRow(row) {
603
617
  };
604
618
  }
605
619
  const SCHEMA_MIGRATIONS = [
620
+ ["session_source", "ALTER TABLE command_sessions ADD COLUMN session_source TEXT NOT NULL DEFAULT 'interactive'"],
621
+ ["automation_id", "ALTER TABLE command_sessions ADD COLUMN automation_id TEXT"],
606
622
  ["archived", "ALTER TABLE command_sessions ADD COLUMN archived INTEGER NOT NULL DEFAULT 0"],
607
623
  ["archived_at", "ALTER TABLE command_sessions ADD COLUMN archived_at TEXT"],
608
624
  ["claude_session_id", "ALTER TABLE command_sessions ADD COLUMN claude_session_id TEXT"],
@@ -1,6 +1,6 @@
1
1
  import { SessionLogger } from "./session-logger.js";
2
2
  import { WandStorage } from "./storage.js";
3
- import { ContentBlock, ExecutionMode, ProcessEvent, SessionProvider, SessionRunner, SessionSnapshot, WandConfig } from "./types.js";
3
+ import { ContentBlock, ExecutionMode, ProcessEvent, SessionProvider, SessionRunner, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
4
4
  interface CreateStructuredSessionOptions {
5
5
  cwd: string;
6
6
  mode: ExecutionMode;
@@ -12,6 +12,8 @@ interface CreateStructuredSessionOptions {
12
12
  model?: string;
13
13
  /** 用户预设的思考深度。留空 / null 视为 off。 */
14
14
  thinkingEffort?: SessionSnapshot["thinkingEffort"];
15
+ sessionSource?: SessionSource;
16
+ automationId?: string;
15
17
  /**
16
18
  * 恢复用的初始会话 id:
17
19
  * - Codex:历史 thread id,首条消息即 `codex exec ... resume <id>` 续接。
@@ -805,6 +805,8 @@ export class StructuredSessionManager {
805
805
  const restored = {
806
806
  ...snapshot,
807
807
  sessionKind: "structured",
808
+ sessionSource: snapshot.sessionSource ?? "interactive",
809
+ automationId: snapshot.automationId,
808
810
  provider: snapshot.provider ?? snapshot.structuredState?.provider ?? "claude",
809
811
  runner: snapshot.runner ?? snapshot.structuredState?.runner ?? defaultStructuredRunner(snapshot.provider ?? snapshot.structuredState?.provider ?? "claude"),
810
812
  status: restoredStatus,
@@ -923,6 +925,8 @@ export class StructuredSessionManager {
923
925
  const snapshot = {
924
926
  id,
925
927
  sessionKind: "structured",
928
+ sessionSource: options.sessionSource ?? "interactive",
929
+ automationId: options.automationId,
926
930
  provider,
927
931
  runner,
928
932
  command: provider === "codex"
package/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type SessionKind = "pty" | "structured";
2
2
  export type SessionProvider = "claude" | "codex";
3
3
  export type SessionRunner = "claude-cli" | "claude-cli-print" | "claude-sdk" | "codex-cli-exec" | "pty";
4
+ export type SessionSource = "interactive" | "automation" | "startup";
4
5
  export type ExecutionMode = "assist" | "agent" | "agent-max" | "default" | "auto-edit" | "full-access" | "native" | "managed";
5
6
  export type AutonomyPolicy = "assist" | "agent" | "agent-max";
6
7
  export type ApprovalPolicy = "ask-every-time" | "approve-once" | "remember-this-turn";
@@ -403,6 +404,10 @@ export interface StructuredSessionState {
403
404
  }
404
405
  export interface SessionSnapshot {
405
406
  id: string;
407
+ /** 会话创建来源。旧数据和缺省值按 interactive 处理。 */
408
+ sessionSource?: SessionSource;
409
+ /** 自动化创建会话时关联的自动化任务 ID。 */
410
+ automationId?: string;
406
411
  sessionKind?: SessionKind;
407
412
  provider?: SessionProvider;
408
413
  runner?: SessionRunner;
@@ -4,6 +4,7 @@ export interface DetachedUpdateOptions {
4
4
  configPath: string;
5
5
  parentPid: number;
6
6
  cliArgs: string[];
7
+ nodeLoaderArgs?: string[];
7
8
  cwd: string;
8
9
  env: NodeJS.ProcessEnv;
9
10
  timeoutMs?: number;
@@ -143,7 +143,23 @@ export function checkManagedServiceUpdatePreflight() {
143
143
  export function shouldUseSystemdRunForDetachedUpdate(serviceScope) {
144
144
  return serviceScope !== null;
145
145
  }
146
- const DEFAULT_UPDATE_UTILS_PATH = path.join(path.dirname(fileURLToPath(import.meta.url)), "npm-update-utils.js");
146
+ function resolveDefaultUpdateUtilsPath() {
147
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
148
+ const compiledPath = path.join(moduleDir, "npm-update-utils.js");
149
+ if (existsSync(compiledPath))
150
+ return compiledPath;
151
+ const sourcePath = path.join(moduleDir, "npm-update-utils.ts");
152
+ return existsSync(sourcePath) ? sourcePath : compiledPath;
153
+ }
154
+ function resolveTypeScriptLoaderArgs() {
155
+ try {
156
+ return ["--import", import.meta.resolve("tsx")];
157
+ }
158
+ catch {
159
+ return [];
160
+ }
161
+ }
162
+ const DEFAULT_UPDATE_UTILS_PATH = resolveDefaultUpdateUtilsPath();
147
163
  /** Exported so the safety-critical update flow can be exercised without starting a detached process. */
148
164
  export function buildDetachedUpdateHelperScript(opts, logPath, serviceScope, updateUtilsPath = DEFAULT_UPDATE_UTILS_PATH) {
149
165
  if (!Number.isSafeInteger(opts.parentPid) || opts.parentPid <= 1) {
@@ -155,6 +171,12 @@ export function buildDetachedUpdateHelperScript(opts, logPath, serviceScope, upd
155
171
  const timeoutMs = Number.isFinite(requestedTimeout)
156
172
  ? Math.max(30_000, Math.trunc(requestedTimeout))
157
173
  : 300_000;
174
+ const nodeLoaderArgs = opts.nodeLoaderArgs
175
+ ?? (updateUtilsPath.endsWith(".ts") ? resolveTypeScriptLoaderArgs() : []);
176
+ if (updateUtilsPath.endsWith(".ts") && nodeLoaderArgs.length === 0) {
177
+ throw new Error("本地 TypeScript 更新 helper 无法解析 tsx loader,请先运行 npm install。");
178
+ }
179
+ const nodeLoaderCommand = nodeLoaderArgs.length > 0 ? `${shellArray(nodeLoaderArgs)} ` : "";
158
180
  return `#!/usr/bin/env bash
159
181
  set -euo pipefail
160
182
  LOG=${shellQuote(logPath)}
@@ -220,16 +242,18 @@ resolve_global_cli() {
220
242
 
221
243
  main() {
222
244
  echo "[wand-update] installing while parent service stays online"
223
- if run "$NODE_BIN" --input-type=module - "$UPDATE_UTILS" "$INSTALL_SPEC" "$TIMEOUT_MS" <<'WAND_INSTALL_NODE'
245
+ if run "$NODE_BIN" ${nodeLoaderCommand}--input-type=module - "$UPDATE_UTILS" "$INSTALL_SPEC" "$TIMEOUT_MS" <<'WAND_INSTALL_NODE'
224
246
  import { pathToFileURL } from "node:url";
225
247
 
226
248
  const [modulePath, installSpec, timeoutValue] = process.argv.slice(2);
227
249
  const timeoutMs = Number(timeoutValue);
228
250
  const updateUtils = await import(pathToFileURL(modulePath).href);
229
- if (typeof updateUtils.installPackageGloballyAsync !== "function") {
251
+ const installPackageGloballyAsync = updateUtils.installPackageGloballyAsync
252
+ ?? updateUtils.default?.installPackageGloballyAsync;
253
+ if (typeof installPackageGloballyAsync !== "function") {
230
254
  throw new Error("installPackageGloballyAsync is unavailable in " + modulePath);
231
255
  }
232
- await updateUtils.installPackageGloballyAsync(installSpec, timeoutMs, (line) => {
256
+ await installPackageGloballyAsync(installSpec, timeoutMs, (line) => {
233
257
  process.stdout.write(String(line) + "\\n");
234
258
  });
235
259
  WAND_INSTALL_NODE