c0de-agent 1.11.0 → 1.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/README.md +24 -5
- package/dist/cli/commands/chat.js +87 -1
- package/dist/cli/commands/config.js +17 -3
- package/dist/cli/commands/sessions.js +62 -3
- package/dist/cli/index.js +26 -7
- package/dist/cli/modes/print.js +9 -2
- package/dist/core/compact.d.ts +2 -0
- package/dist/core/compact.js +1 -0
- package/dist/core/config.d.ts +11 -2
- package/dist/core/config.js +73 -6
- package/dist/core/loop/compaction.d.ts +2 -1
- package/dist/core/loop/compaction.js +19 -6
- package/dist/core/loop/stream-collect.js +9 -1
- package/dist/core/loop/subagent.js +23 -5
- package/dist/core/redact.d.ts +11 -0
- package/dist/core/redact.js +49 -0
- package/dist/core/slash.js +93 -51
- package/dist/core/title.js +7 -1
- package/dist/core/types.d.ts +2 -0
- package/dist/db/schema.d.ts +17 -0
- package/dist/db/schema.js +3 -1
- package/dist/kanban/index.d.ts +1 -1
- package/dist/kanban/index.js +1 -1
- package/dist/kanban/store.d.ts +5 -1
- package/dist/kanban/store.js +76 -2
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.js +1 -1
- package/dist/llm/provider.js +1 -1
- package/dist/llm/retry.d.ts +5 -2
- package/dist/llm/retry.js +6 -5
- package/dist/llm/routing.d.ts +24 -1
- package/dist/llm/routing.js +29 -1
- package/dist/project/index.d.ts +1 -1
- package/dist/project/index.js +1 -1
- package/dist/project/project.d.ts +14 -0
- package/dist/project/project.js +77 -1
- package/dist/server/agent-manager.d.ts +10 -1
- package/dist/server/agent-manager.js +14 -1
- package/dist/server/auth-manager.d.ts +11 -5
- package/dist/server/auth-manager.js +39 -20
- package/dist/server/context.js +3 -0
- package/dist/server/permission/interactive.d.ts +6 -3
- package/dist/server/permission/interactive.js +20 -4
- package/dist/server/permission/store.d.ts +12 -2
- package/dist/server/permission/store.js +31 -3
- package/dist/server/registry-config.d.ts +14 -0
- package/dist/server/registry-config.js +44 -0
- package/dist/server/routes/auth.js +32 -2
- package/dist/server/routes/chat.d.ts +12 -0
- package/dist/server/routes/chat.js +216 -38
- package/dist/server/routes/commands.d.ts +4 -2
- package/dist/server/routes/commands.js +10 -3
- package/dist/server/routes/config.js +103 -23
- package/dist/server/routes/files.js +22 -4
- package/dist/server/routes/kanban.js +68 -6
- package/dist/server/routes/permissions.d.ts +0 -4
- package/dist/server/routes/permissions.js +133 -7
- package/dist/server/routes/project.js +59 -10
- package/dist/server/routes/provider.js +48 -3
- package/dist/server/routes/session.js +298 -22
- package/dist/server/routes/update.d.ts +11 -5
- package/dist/server/routes/update.js +88 -28
- package/dist/server/server.d.ts +4 -12
- package/dist/server/server.js +198 -102
- package/dist/server/types.d.ts +7 -0
- package/dist/session/archive.d.ts +3 -1
- package/dist/session/archive.js +13 -1
- package/dist/session/branch.js +20 -2
- package/dist/session/import.d.ts +44 -0
- package/dist/session/import.js +83 -0
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js +1 -1
- package/dist/session/jobs.d.ts +12 -0
- package/dist/session/jobs.js +53 -0
- package/dist/session/message.d.ts +7 -1
- package/dist/session/message.js +21 -1
- package/dist/session/session.d.ts +128 -11
- package/dist/session/session.js +461 -30
- package/dist/session/snapshot.d.ts +5 -2
- package/dist/session/snapshot.js +10 -2
- package/dist/session/types.d.ts +11 -1
- package/dist/shared/types/agent.d.ts +11 -0
- package/dist/shared/types/config.d.ts +5 -1
- package/dist/shared/types/kanban.d.ts +13 -0
- package/dist/shared/types/message.d.ts +9 -0
- package/dist/tools/websearch/index.js +6 -3
- package/dist/update/index.d.ts +3 -3
- package/dist/update/index.js +2 -2
- package/dist/update/ipc.d.ts +13 -2
- package/dist/update/ipc.js +36 -2
- package/dist/update/snapshot.d.ts +1 -0
- package/dist/update/snapshot.js +2 -0
- package/dist/update/updater.d.ts +15 -1
- package/dist/update/updater.js +45 -17
- package/drizzle/0008_tranquil_lily_hollister.sql +1 -0
- package/drizzle/meta/0008_snapshot.json +875 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +41 -42
|
@@ -81,6 +81,11 @@ type SecurityConfig = {
|
|
|
81
81
|
token?: string;
|
|
82
82
|
/** 额外允许的 CORS origin(本地回环始终允许;局域网/远程访问需显式添加)。 */
|
|
83
83
|
allowedOrigins: string[];
|
|
84
|
+
/** 首设备 bootstrap token 有效期(毫秒)。bootstrap 自生成(auth-token 文件
|
|
85
|
+
* mtime)起超过此时长即拒绝首设备注册。未设置时默认 5 分钟(auth-manager 内置);
|
|
86
|
+
* 无已注册设备时每次 serve 都会重新生成 bootstrap,故重启即可恢复注册。
|
|
87
|
+
* 用于共享主机等 URL 可能泄漏的场景,缩短「先到先得」竞态窗口。 */
|
|
88
|
+
firstDeviceTtlMs?: number;
|
|
84
89
|
};
|
|
85
90
|
/** Global application configuration. */
|
|
86
91
|
type Config = {
|
|
@@ -121,6 +126,5 @@ type Config = {
|
|
|
121
126
|
permission: PermissionConfig;
|
|
122
127
|
update: UpdateConfig;
|
|
123
128
|
theme: 'light' | 'dark' | 'system';
|
|
124
|
-
locale: string;
|
|
125
129
|
};
|
|
126
130
|
export type { AgentsConfig, CompactionConfig, Config, MCPServerConfig, PermissionConfig, SecurityConfig, ToolMetricsConfig, UpdateConfig, WebSearchConfig, };
|
|
@@ -70,6 +70,19 @@ interface KanbanStore {
|
|
|
70
70
|
columns?: KanbanColumnDef[];
|
|
71
71
|
labels?: KanbanLabelDef[];
|
|
72
72
|
}): Promise<KanbanBoard>;
|
|
73
|
+
/** 导入/替换整板:列与标签配置 + 全部卡片原子替换(导入数据的落点)。 */
|
|
74
|
+
replaceBoard(input: {
|
|
75
|
+
columns: KanbanColumnDef[];
|
|
76
|
+
labels: KanbanLabelDef[];
|
|
77
|
+
cards: Array<{
|
|
78
|
+
title: string;
|
|
79
|
+
description?: string | null;
|
|
80
|
+
columnId: string;
|
|
81
|
+
priority: KanbanPriority;
|
|
82
|
+
position: number;
|
|
83
|
+
labels: string[];
|
|
84
|
+
}>;
|
|
85
|
+
}): Promise<KanbanBoardWithCards>;
|
|
73
86
|
}
|
|
74
87
|
export type { KanbanBoard, KanbanBoardWithCards, KanbanCard, KanbanColumnDef, KanbanLabelDef, KanbanPriority, KanbanStore, };
|
|
75
88
|
export { DEFAULT_KANBAN_COLUMNS, KANBAN_LABEL_COLORS };
|
|
@@ -54,6 +54,15 @@ type SessionMetadata = {
|
|
|
54
54
|
lastRun?: LastRun;
|
|
55
55
|
/** 上次打开时间戳(ms),用于会话列表按最近打开排序。 */
|
|
56
56
|
lastOpenedAt?: number;
|
|
57
|
+
/** P2:会话级授权模式覆盖('auto'/'default'),跨重启持久化。 */
|
|
58
|
+
permissionMode?: 'auto' | 'default';
|
|
59
|
+
/** 回收站条目首次被用户看到的时间戳(ms)。回收站保留期自此起算,而非删除时间——
|
|
60
|
+
* 避免「删除后长期不开服务,重启即被静默物理清除、从未见过倒计时」的墙钟缺陷。
|
|
61
|
+
* 只标记一次(touchTrashSeen 不重置),保证倒计时稳定可预期。 */
|
|
62
|
+
trashSeenAt?: number;
|
|
63
|
+
/** 回收站条目已到期、进入物理清除宽限期的时间戳(ms)。
|
|
64
|
+
* 到期先标记,宽限期(默认 7 天)内可在 UI 恢复;期满后由 purgeDeletedSessions 物理清除。 */
|
|
65
|
+
purgePendingAt?: number;
|
|
57
66
|
};
|
|
58
67
|
/** A conversation session (may have a parent for branching). */
|
|
59
68
|
type Session = {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decryptSecret } from '../../core/secret.js';
|
|
1
2
|
import { createFetch } from './fetch.js';
|
|
2
3
|
import { braveProvider } from './providers/brave.js';
|
|
3
4
|
import { duckduckgoProvider } from './providers/duckduckgo.js';
|
|
@@ -8,11 +9,13 @@ const PROVIDERS = {
|
|
|
8
9
|
tavily: tavilyProvider,
|
|
9
10
|
brave: braveProvider,
|
|
10
11
|
};
|
|
11
|
-
/** 环境变量名 → config key 字段映射。环境变量优先于 config。
|
|
12
|
+
/** 环境变量名 → config key 字段映射。环境变量优先于 config。
|
|
13
|
+
* config 值落盘时已加密(enc: 前缀),此处解密后使用(明文兼容透传)。 */
|
|
12
14
|
function resolveKeys(config) {
|
|
15
|
+
const decrypt = (v) => (v ? decryptSecret(v) : v);
|
|
13
16
|
return {
|
|
14
|
-
tavily: process.env.TAVILY_API_KEY ?? config.tavilyApiKey,
|
|
15
|
-
brave: process.env.BRAVE_API_KEY ?? config.braveApiKey,
|
|
17
|
+
tavily: process.env.TAVILY_API_KEY ?? decrypt(config.tavilyApiKey),
|
|
18
|
+
brave: process.env.BRAVE_API_KEY ?? decrypt(config.braveApiKey),
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
/** 按 preference + key 可用性解析目标 provider。auto 优先级:tavily > brave > duckduckgo。 */
|
package/dist/update/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export type { HandoffServer } from './ipc.js';
|
|
2
|
-
export { createHandoffServer, requestHandoff } from './ipc.js';
|
|
2
|
+
export { confirmHandoff, createHandoffServer, requestHandoff } from './ipc.js';
|
|
3
3
|
export type { UpdateScheduler, UpdateSchedulerOptions } from './scheduler.js';
|
|
4
4
|
export { createUpdateScheduler } from './scheduler.js';
|
|
5
5
|
export type { SerializedEntry, SerializedSession, SessionSnapshot } from './snapshot.js';
|
|
6
6
|
export { orderSessionsByParent, restoreSessions, serializeSessions } from './snapshot.js';
|
|
7
|
-
export type { HotUpdateOptions, HotUpdateResult, SpawnFn } from './updater.js';
|
|
8
|
-
export { cleanupSnapshot, performHotUpdate, performInstall } from './updater.js';
|
|
7
|
+
export type { HotUpdateOptions, HotUpdateResult, InstallMethod, InstallOnlyResult, SpawnFn, } from './updater.js';
|
|
8
|
+
export { cleanupSnapshot, manualInstallCommand, performHandoff, performHotUpdate, performInstall, } from './updater.js';
|
|
9
9
|
export type { UpdateCheckResult } from './version.js';
|
|
10
10
|
export { checkForUpdate, compareSemver, getCurrentVersion } from './version.js';
|
package/dist/update/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 热更新 + 会话迁移包(spec §18)。
|
|
2
|
-
export { createHandoffServer, requestHandoff } from './ipc.js';
|
|
2
|
+
export { confirmHandoff, createHandoffServer, requestHandoff } from './ipc.js';
|
|
3
3
|
export { createUpdateScheduler } from './scheduler.js';
|
|
4
4
|
export { orderSessionsByParent, restoreSessions, serializeSessions } from './snapshot.js';
|
|
5
|
-
export { cleanupSnapshot, performHotUpdate, performInstall } from './updater.js';
|
|
5
|
+
export { cleanupSnapshot, manualInstallCommand, performHandoff, performHotUpdate, performInstall, } from './updater.js';
|
|
6
6
|
export { checkForUpdate, compareSemver, getCurrentVersion } from './version.js';
|
package/dist/update/ipc.d.ts
CHANGED
|
@@ -22,9 +22,14 @@ type HandoffOptions = {
|
|
|
22
22
|
verify?: (token: string | undefined) => boolean;
|
|
23
23
|
/**
|
|
24
24
|
* 响应发出后退出进程(旧实例完整让渡:主服务与资源已在 onHandoff 中关闭)。
|
|
25
|
-
* 退出延迟 250ms 保证 HTTP
|
|
25
|
+
* 退出延迟 250ms 保证 HTTP 响应刷出;不设时仅响应不退出(测试用 / 两阶段交接)。
|
|
26
26
|
*/
|
|
27
27
|
exitAfterResponse?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 收到 POST /handoff-confirm 时调用(两阶段交接第二阶段):
|
|
30
|
+
* 新实例绑定端口成功后的确认。调用方在此清理定时器并退出进程。
|
|
31
|
+
*/
|
|
32
|
+
onConfirm?: () => void | Promise<void>;
|
|
28
33
|
};
|
|
29
34
|
/**
|
|
30
35
|
* requestHandoff 抛出的可区分错误(调用方据此分流处理):
|
|
@@ -44,5 +49,11 @@ type HandoffError = Error & {
|
|
|
44
49
|
declare function createHandoffServer(onHandoff: () => Promise<void>, opts?: HandoffOptions): Promise<HandoffServer>;
|
|
45
50
|
/** 新实例请求旧实例让出端口(graceful shutdown)。失败抛可区分的 HandoffError。 */
|
|
46
51
|
declare function requestHandoff(port: number, host?: string, token?: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* 两阶段交接第二阶段(P3 回滚修复):新实例绑定端口成功后 POST /handoff-confirm,
|
|
54
|
+
* 旧实例收到才退出进程。连接失败/旧实例已退出时静默——确认是尽力而为,
|
|
55
|
+
* 超时兜底由旧实例的复活窗口负责(端口占用状态是最终裁判)。
|
|
56
|
+
*/
|
|
57
|
+
declare function confirmHandoff(port: number, host?: string, token?: string): Promise<void>;
|
|
47
58
|
export type { HandoffError, HandoffOptions, HandoffServer };
|
|
48
|
-
export { createHandoffServer, requestHandoff };
|
|
59
|
+
export { confirmHandoff, createHandoffServer, requestHandoff };
|
package/dist/update/ipc.js
CHANGED
|
@@ -18,7 +18,7 @@ function createHandoffServer(onHandoff, opts = {}) {
|
|
|
18
18
|
return new Promise((resolve, reject) => {
|
|
19
19
|
const expected = opts.expectedToken && opts.expectedToken.length > 0 ? `Bearer ${opts.expectedToken}` : '';
|
|
20
20
|
const handler = async (req, res) => {
|
|
21
|
-
if (req.method === 'POST' && req.url === '/handoff') {
|
|
21
|
+
if (req.method === 'POST' && (req.url === '/handoff' || req.url === '/handoff-confirm')) {
|
|
22
22
|
const authorized = opts.verify
|
|
23
23
|
? opts.verify(req.headers.authorization?.replace(/^Bearer\s+/i, ''))
|
|
24
24
|
: expected.length > 0
|
|
@@ -29,6 +29,24 @@ function createHandoffServer(onHandoff, opts = {}) {
|
|
|
29
29
|
res.end(JSON.stringify({ ok: false, error: 'unauthorized' }));
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
|
+
if (req.url === '/handoff-confirm') {
|
|
33
|
+
// 两阶段交接第二阶段:新实例绑定成功后的确认。响应后由 onConfirm
|
|
34
|
+
// 调度退出;不设 onConfirm 时直接退出(与 exitAfterResponse 独立)。
|
|
35
|
+
try {
|
|
36
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
37
|
+
res.end(JSON.stringify({ ok: true }));
|
|
38
|
+
if (opts.onConfirm) {
|
|
39
|
+
await opts.onConfirm();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
setTimeout(() => process.exit(0), 250);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('[handoff] confirm handler failed:', error);
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
32
50
|
try {
|
|
33
51
|
await onHandoff();
|
|
34
52
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
@@ -87,4 +105,20 @@ async function requestHandoff(port, host = '127.0.0.1', token) {
|
|
|
87
105
|
throw error;
|
|
88
106
|
}
|
|
89
107
|
}
|
|
90
|
-
|
|
108
|
+
/**
|
|
109
|
+
* 两阶段交接第二阶段(P3 回滚修复):新实例绑定端口成功后 POST /handoff-confirm,
|
|
110
|
+
* 旧实例收到才退出进程。连接失败/旧实例已退出时静默——确认是尽力而为,
|
|
111
|
+
* 超时兜底由旧实例的复活窗口负责(端口占用状态是最终裁判)。
|
|
112
|
+
*/
|
|
113
|
+
async function confirmHandoff(port, host = '127.0.0.1', token) {
|
|
114
|
+
try {
|
|
115
|
+
await fetch(`http://${host}:${port}/handoff-confirm`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
...(token ? { headers: { Authorization: `Bearer ${token}` } } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// 旧实例已退出或不可达:无确认对象,忽略。
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export { confirmHandoff, createHandoffServer, requestHandoff };
|
package/dist/update/snapshot.js
CHANGED
|
@@ -19,6 +19,7 @@ function toSerializedSession(row) {
|
|
|
19
19
|
worktreePath: row.worktreePath,
|
|
20
20
|
source: row.source,
|
|
21
21
|
deletedAt: row.deletedAt ? toDateMs(row.deletedAt) : null,
|
|
22
|
+
deletedBatchId: row.deletedBatchId ?? null,
|
|
22
23
|
createdAt: toDateMs(row.createdAt),
|
|
23
24
|
updatedAt: toDateMs(row.updatedAt),
|
|
24
25
|
};
|
|
@@ -83,6 +84,7 @@ async function restoreSessions(handle, snapshot) {
|
|
|
83
84
|
worktreePath: s.worktreePath,
|
|
84
85
|
source: s.source,
|
|
85
86
|
deletedAt: s.deletedAt != null ? new Date(s.deletedAt) : null,
|
|
87
|
+
deletedBatchId: s.deletedBatchId ?? null,
|
|
86
88
|
createdAt: new Date(s.createdAt),
|
|
87
89
|
updatedAt: new Date(s.updatedAt),
|
|
88
90
|
})
|
package/dist/update/updater.d.ts
CHANGED
|
@@ -75,6 +75,20 @@ type InstallMethod = {
|
|
|
75
75
|
declare function detectInstallMethod(): InstallMethod;
|
|
76
76
|
/** 手动安装提示命令(unknown 安装方式时返回给前端展示)。 */
|
|
77
77
|
declare function manualInstallCommand(pkg: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* 分阶段热更新的第二阶段(P0 修复:install 与 pause 解耦)。
|
|
80
|
+
*
|
|
81
|
+
* 调用方(/api/update/apply)顺序:
|
|
82
|
+
* 1. checkNow → hasUpdate
|
|
83
|
+
* 2. performInstall(仅安装;失败时**尚未暂停任何会话**,直接 409 可操作错误)
|
|
84
|
+
* 3. 安装成功 → pauseAll(暂停活跃 run 至安全点)
|
|
85
|
+
* 4. serializeSessions → performHandoff(本函数:写快照 + spawn 新实例)
|
|
86
|
+
* 5. spawn 失败 → 调用方 resumeAll 回滚暂停的会话
|
|
87
|
+
*
|
|
88
|
+
* 对比旧流程(pause → 序列化 → performHotUpdate 内含 install):install 失败时
|
|
89
|
+
* 会话已被 pause/forcedAbort 且无回滚路径——更新失败顺带销毁进行中的工作。
|
|
90
|
+
*/
|
|
91
|
+
declare function performHandoff(snapshot: SessionSnapshot, method: InstallMethod, opts?: HotUpdateOptions): Promise<HotUpdateResult>;
|
|
78
92
|
/**
|
|
79
93
|
* 执行热更新(spec §18.1-18.2 + P0-2 修订流程):
|
|
80
94
|
* 1. 序列化当前会话状态到快照文件
|
|
@@ -96,4 +110,4 @@ declare function performInstall(opts?: HotUpdateOptions): Promise<InstallOnlyRes
|
|
|
96
110
|
/** 清理快照临时目录(热更新完成或放弃后调用)。 */
|
|
97
111
|
declare function cleanupSnapshot(snapshotPath: string): Promise<void>;
|
|
98
112
|
export type { HotUpdateOptions, HotUpdateResult, InstallMethod, InstallOnlyResult, SpawnFn };
|
|
99
|
-
export { cleanupSnapshot, detectInstallMethod, manualInstallCommand, performHotUpdate, performInstall, };
|
|
113
|
+
export { cleanupSnapshot, detectInstallMethod, manualInstallCommand, performHandoff, performHotUpdate, performInstall, };
|
package/dist/update/updater.js
CHANGED
|
@@ -184,6 +184,48 @@ function buildSpawnArgv(snapshotPath, opts) {
|
|
|
184
184
|
}
|
|
185
185
|
return argv;
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* 把快照写入临时目录(或 opts.snapshotPath),返回实际路径。
|
|
189
|
+
* 独立成函数供 performHotUpdate(一体流程)与 performHandoff(分阶段流程)共用。
|
|
190
|
+
*/
|
|
191
|
+
async function writeSnapshot(snapshot, opts) {
|
|
192
|
+
const snapshotPath = opts.snapshotPath ?? join(await mkdtemp(join(tmpdir(), 'c0de-update-')), 'snapshot.json');
|
|
193
|
+
await writeFile(snapshotPath, JSON.stringify(snapshot), 'utf8');
|
|
194
|
+
return snapshotPath;
|
|
195
|
+
}
|
|
196
|
+
/** 安装完成后的滚动切换:spawn 新实例(restore 快照 + handoff 端口接管由新实例发起)。 */
|
|
197
|
+
async function spawnAfterInstall(snapshotPath, method, opts) {
|
|
198
|
+
const spawnNew = opts.spawnNewInstanceFn ?? defaultSpawn;
|
|
199
|
+
const argv = buildSpawnArgv(snapshotPath, opts);
|
|
200
|
+
try {
|
|
201
|
+
await spawnNew(snapshotPath, argv, opts);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
return {
|
|
205
|
+
_tag: 'spawn_failed',
|
|
206
|
+
error: error instanceof Error ? error.message : String(error),
|
|
207
|
+
snapshotPath,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
return { _tag: 'success', snapshotPath, installMethod: method.kind };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 分阶段热更新的第二阶段(P0 修复:install 与 pause 解耦)。
|
|
214
|
+
*
|
|
215
|
+
* 调用方(/api/update/apply)顺序:
|
|
216
|
+
* 1. checkNow → hasUpdate
|
|
217
|
+
* 2. performInstall(仅安装;失败时**尚未暂停任何会话**,直接 409 可操作错误)
|
|
218
|
+
* 3. 安装成功 → pauseAll(暂停活跃 run 至安全点)
|
|
219
|
+
* 4. serializeSessions → performHandoff(本函数:写快照 + spawn 新实例)
|
|
220
|
+
* 5. spawn 失败 → 调用方 resumeAll 回滚暂停的会话
|
|
221
|
+
*
|
|
222
|
+
* 对比旧流程(pause → 序列化 → performHotUpdate 内含 install):install 失败时
|
|
223
|
+
* 会话已被 pause/forcedAbort 且无回滚路径——更新失败顺带销毁进行中的工作。
|
|
224
|
+
*/
|
|
225
|
+
async function performHandoff(snapshot, method, opts = {}) {
|
|
226
|
+
const snapshotPath = await writeSnapshot(snapshot, opts);
|
|
227
|
+
return spawnAfterInstall(snapshotPath, method, opts);
|
|
228
|
+
}
|
|
187
229
|
/**
|
|
188
230
|
* 执行热更新(spec §18.1-18.2 + P0-2 修订流程):
|
|
189
231
|
* 1. 序列化当前会话状态到快照文件
|
|
@@ -197,9 +239,7 @@ function buildSpawnArgv(snapshotPath, opts) {
|
|
|
197
239
|
*/
|
|
198
240
|
async function performHotUpdate(snapshot, opts = {}) {
|
|
199
241
|
const pkg = opts.packageName ?? DEFAULT_PACKAGE;
|
|
200
|
-
const
|
|
201
|
-
const snapshotPath = opts.snapshotPath ?? join(dir, 'snapshot.json');
|
|
202
|
-
await writeFile(snapshotPath, JSON.stringify(snapshot), 'utf8');
|
|
242
|
+
const snapshotPath = await writeSnapshot(snapshot, opts);
|
|
203
243
|
// 识别安装方式
|
|
204
244
|
const method = detectInstallMethod();
|
|
205
245
|
// 安装前采集程序文件基线(watch 依赖此判断「变更已发生」)
|
|
@@ -246,19 +286,7 @@ async function performHotUpdate(snapshot, opts = {}) {
|
|
|
246
286
|
}
|
|
247
287
|
}
|
|
248
288
|
// 程序文件已变更(自动安装落盘或用户手动安装完成)→ 滚动切换
|
|
249
|
-
|
|
250
|
-
const argv = buildSpawnArgv(snapshotPath, opts);
|
|
251
|
-
try {
|
|
252
|
-
await spawnNew(snapshotPath, argv, opts);
|
|
253
|
-
}
|
|
254
|
-
catch (error) {
|
|
255
|
-
return {
|
|
256
|
-
_tag: 'spawn_failed',
|
|
257
|
-
error: error instanceof Error ? error.message : String(error),
|
|
258
|
-
snapshotPath,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
return { _tag: 'success', snapshotPath, installMethod: method.kind };
|
|
289
|
+
return spawnAfterInstall(snapshotPath, method, opts);
|
|
262
290
|
}
|
|
263
291
|
/**
|
|
264
292
|
* 仅安装新版本(P2-7:无活跃 serve 的 `c0de update --apply`)。
|
|
@@ -291,4 +319,4 @@ async function performInstall(opts = {}) {
|
|
|
291
319
|
async function cleanupSnapshot(snapshotPath) {
|
|
292
320
|
await rm(join(snapshotPath, '..'), { recursive: true, force: true }).catch(() => { });
|
|
293
321
|
}
|
|
294
|
-
export { cleanupSnapshot, detectInstallMethod, manualInstallCommand, performHotUpdate, performInstall, };
|
|
322
|
+
export { cleanupSnapshot, detectInstallMethod, manualInstallCommand, performHandoff, performHotUpdate, performInstall, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE "sessions" ADD COLUMN "deleted_batch_id" uuid;
|