@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.
- package/dist/build-info.json +3 -3
- package/dist/npm-update-utils.js +7 -22
- package/dist/process-manager.d.ts +3 -1
- package/dist/process-manager.js +15 -2
- package/dist/server-session-routes.d.ts +8 -1
- package/dist/server-session-routes.js +36 -2
- package/dist/server.js +5 -6
- package/dist/storage.js +21 -5
- package/dist/structured-session-manager.d.ts +3 -1
- package/dist/structured-session-manager.js +4 -0
- package/dist/types.d.ts +5 -0
- package/dist/update-helper.d.ts +1 -0
- package/dist/update-helper.js +28 -4
- package/dist/web-ui/content/scripts.js +32 -32
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-07-
|
|
4
|
-
"version": "2.
|
|
2
|
+
"commit": "2ffe360ae10acb539bd5e5fe785073348e2b0482",
|
|
3
|
+
"builtAt": "2026-07-12T04:24:13.405Z",
|
|
4
|
+
"version": "2.12.0",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
package/dist/npm-update-utils.js
CHANGED
|
@@ -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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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). */
|
package/dist/process-manager.js
CHANGED
|
@@ -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
|
-
|
|
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, {
|
|
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, {
|
|
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 `
|
|
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 `
|
|
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;
|
package/dist/update-helper.d.ts
CHANGED
package/dist/update-helper.js
CHANGED
|
@@ -143,7 +143,23 @@ export function checkManagedServiceUpdatePreflight() {
|
|
|
143
143
|
export function shouldUseSystemdRunForDetachedUpdate(serviceScope) {
|
|
144
144
|
return serviceScope !== null;
|
|
145
145
|
}
|
|
146
|
-
|
|
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
|
-
|
|
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
|
|
256
|
+
await installPackageGloballyAsync(installSpec, timeoutMs, (line) => {
|
|
233
257
|
process.stdout.write(String(line) + "\\n");
|
|
234
258
|
});
|
|
235
259
|
WAND_INSTALL_NODE
|