c0de-agent 1.10.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 +28 -6
- package/dist/cli/commands/acp.js +3 -3
- package/dist/cli/commands/auth.d.ts +17 -0
- package/dist/cli/commands/auth.js +73 -0
- package/dist/cli/commands/chat.js +95 -1
- package/dist/cli/commands/config.d.ts +2 -9
- package/dist/cli/commands/config.js +25 -54
- package/dist/cli/commands/init.js +4 -2
- package/dist/cli/commands/plugin.js +8 -4
- package/dist/cli/commands/sessions.d.ts +10 -0
- package/dist/cli/commands/sessions.js +94 -0
- package/dist/cli/commands/update.js +18 -10
- package/dist/cli/index.js +51 -4
- package/dist/cli/modes/print.js +19 -2
- package/dist/core/compact.d.ts +2 -0
- package/dist/core/compact.js +1 -0
- package/dist/core/config-path.d.ts +9 -0
- package/dist/core/config-path.js +48 -0
- package/dist/core/config.d.ts +20 -3
- package/dist/core/config.js +103 -14
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- 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/loop.js +13 -3
- package/dist/core/redact.d.ts +11 -0
- package/dist/core/redact.js +49 -0
- package/dist/core/slash.js +167 -63
- package/dist/core/title.js +22 -1
- package/dist/core/types.d.ts +4 -0
- package/dist/db/client.d.ts +4 -0
- package/dist/db/client.js +5 -0
- package/dist/db/schema.d.ts +51 -0
- package/dist/db/schema.js +6 -0
- 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 +14 -0
- package/dist/server/agent-manager.js +41 -0
- package/dist/server/app.js +12 -2
- package/dist/server/auth-manager.d.ts +85 -0
- package/dist/server/auth-manager.js +289 -0
- package/dist/server/context.js +4 -0
- package/dist/server/middleware/auth.d.ts +4 -3
- package/dist/server/middleware/auth.js +5 -5
- package/dist/server/permission/interactive.d.ts +7 -2
- package/dist/server/permission/interactive.js +25 -5
- package/dist/server/permission/store.d.ts +15 -1
- package/dist/server/permission/store.js +31 -2
- package/dist/server/registry-config.d.ts +14 -0
- package/dist/server/registry-config.js +44 -0
- package/dist/server/routes/auth.d.ts +4 -0
- package/dist/server/routes/auth.js +124 -0
- package/dist/server/routes/chat.d.ts +18 -1
- package/dist/server/routes/chat.js +295 -37
- package/dist/server/routes/commands.d.ts +4 -2
- package/dist/server/routes/commands.js +10 -3
- package/dist/server/routes/config.js +143 -15
- package/dist/server/routes/files.js +22 -4
- package/dist/server/routes/kanban.js +68 -6
- package/dist/server/routes/permissions.d.ts +0 -1
- package/dist/server/routes/permissions.js +153 -4
- package/dist/server/routes/project.js +106 -3
- package/dist/server/routes/provider.js +48 -3
- package/dist/server/routes/session.js +319 -19
- package/dist/server/routes/terminal.js +17 -2
- package/dist/server/routes/update.d.ts +11 -5
- package/dist/server/routes/update.js +98 -16
- package/dist/server/server.d.ts +4 -12
- package/dist/server/server.js +235 -100
- package/dist/server/types.d.ts +15 -1
- package/dist/session/archive.d.ts +4 -2
- package/dist/session/archive.js +13 -1
- package/dist/session/branch.d.ts +1 -0
- package/dist/session/branch.js +32 -7
- 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 +143 -6
- package/dist/session/session.js +561 -11
- package/dist/session/snapshot.d.ts +5 -2
- package/dist/session/snapshot.js +10 -2
- package/dist/session/types.d.ts +12 -2
- package/dist/shared/types/agent.d.ts +22 -0
- package/dist/shared/types/config.d.ts +10 -1
- package/dist/shared/types/kanban.d.ts +13 -0
- package/dist/shared/types/message.d.ts +15 -2
- package/dist/tools/index.js +2 -3
- 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 +19 -2
- package/dist/update/ipc.js +43 -6
- package/dist/update/snapshot.d.ts +5 -0
- package/dist/update/snapshot.js +10 -0
- package/dist/update/updater.d.ts +69 -8
- package/dist/update/updater.js +263 -32
- package/drizzle/0006_sour_susan_delgado.sql +2 -0
- package/drizzle/0007_same_wilson_fisk.sql +2 -0
- package/drizzle/0008_tranquil_lily_hollister.sql +1 -0
- package/drizzle/meta/0006_snapshot.json +867 -0
- package/drizzle/meta/0007_snapshot.json +869 -0
- package/drizzle/meta/0008_snapshot.json +875 -0
- package/drizzle/meta/_journal.json +21 -0
- package/package.json +41 -42
package/dist/session/types.d.ts
CHANGED
|
@@ -91,17 +91,27 @@ type ArchiveRef = {
|
|
|
91
91
|
type: 'archive' | 'squash';
|
|
92
92
|
id: string;
|
|
93
93
|
};
|
|
94
|
+
/** 会话级用量汇总(P0 审查:列表视图此前无法回答「会话花了多少 token/钱」)。
|
|
95
|
+
* 从 metadata.segments 聚合,零额外查询。 */
|
|
96
|
+
type SessionUsage = {
|
|
97
|
+
inputTokens: number;
|
|
98
|
+
outputTokens: number;
|
|
99
|
+
cacheRead: number;
|
|
100
|
+
cost: number;
|
|
101
|
+
calls: number;
|
|
102
|
+
};
|
|
94
103
|
/** A node in the session tree. */
|
|
95
104
|
type SessionTreeNode = {
|
|
96
105
|
session: Session;
|
|
97
106
|
children: SessionTreeNode[];
|
|
107
|
+
usage: SessionUsage;
|
|
98
108
|
};
|
|
99
109
|
/** A decoded compaction archive row. */
|
|
100
110
|
type CompactionArchive = {
|
|
101
111
|
id: string;
|
|
102
112
|
sessionId: string;
|
|
103
113
|
compactionId: string;
|
|
104
|
-
archiveType: 'compaction' | 'squash' | 'shake';
|
|
114
|
+
archiveType: 'compaction' | 'squash' | 'shake' | 'clear';
|
|
105
115
|
originalEntries: SessionEntry[];
|
|
106
116
|
summary: string;
|
|
107
117
|
tokenCount: number;
|
|
@@ -121,4 +131,4 @@ type FileSnapshot = {
|
|
|
121
131
|
mtimeMs?: number;
|
|
122
132
|
createdAt: number;
|
|
123
133
|
};
|
|
124
|
-
export type { ArchiveRef, BranchSummaryEntry, CompactionArchive, CompactionConfig, CompactionEntry, CompactionResult, FileSnapshot, HotFile, MessageInput, SessionEntry, SessionTreeNode, SquashConfig, SquashEntry, SteeringEntry, Summarizer, };
|
|
134
|
+
export type { ArchiveRef, BranchSummaryEntry, CompactionArchive, CompactionConfig, CompactionEntry, CompactionResult, FileSnapshot, HotFile, MessageInput, SessionEntry, SessionTreeNode, SessionUsage, SquashConfig, SquashEntry, SteeringEntry, Summarizer, };
|
|
@@ -148,6 +148,20 @@ type AgentEvent = {
|
|
|
148
148
|
toolCallId: string;
|
|
149
149
|
tool: string;
|
|
150
150
|
input: unknown;
|
|
151
|
+
}
|
|
152
|
+
/** P1-6:权限确认超时(5 分钟)被自动拒绝时通知前端,提供「重新询问」入口。 */
|
|
153
|
+
| {
|
|
154
|
+
_tag: 'permission_timeout';
|
|
155
|
+
toolCallId: string;
|
|
156
|
+
tool: string;
|
|
157
|
+
input: unknown;
|
|
158
|
+
}
|
|
159
|
+
/** P0 双层超时:提示后仍无响应,pending 已兜底自动拒绝(run 继续)——前端须清理弹窗状态。 */
|
|
160
|
+
| {
|
|
161
|
+
_tag: 'permission_expired';
|
|
162
|
+
toolCallId: string;
|
|
163
|
+
tool: string;
|
|
164
|
+
input: unknown;
|
|
151
165
|
} | {
|
|
152
166
|
_tag: 'error';
|
|
153
167
|
error: AgentError;
|
|
@@ -195,6 +209,10 @@ type AgentEvent = {
|
|
|
195
209
|
status: string;
|
|
196
210
|
}[];
|
|
197
211
|
}[];
|
|
212
|
+
}
|
|
213
|
+
/** SSE 心跳(服务端防前端 90s 静默看门狗误杀长任务);前端无 UI 变化,直接忽略。 */
|
|
214
|
+
| {
|
|
215
|
+
_tag: 'heartbeat';
|
|
198
216
|
} | {
|
|
199
217
|
_tag: 'done';
|
|
200
218
|
};
|
|
@@ -245,5 +263,9 @@ type AgentState = {
|
|
|
245
263
|
remaining: number;
|
|
246
264
|
noTextStreak: number;
|
|
247
265
|
};
|
|
266
|
+
/** 本次 run 开始时间戳(ms)。暂停持久化 lastRun 时回填 startedAt。 */
|
|
267
|
+
lastRunStartedAt?: number;
|
|
268
|
+
/** 暂停状态是否已 yield 给前端(loop 内用,防重复 status_change)。 */
|
|
269
|
+
pauseYielded?: boolean;
|
|
248
270
|
};
|
|
249
271
|
export type { AgentConfig, AgentError, AgentEvent, AgentState, AgentStatus, LLMCall, LLMSegment, PendingToolCall, SegmentTrigger, TokenBudget, };
|
|
@@ -64,6 +64,11 @@ type UpdateConfig = {
|
|
|
64
64
|
intervalMs: number;
|
|
65
65
|
/** 启动后首次检查的延迟(毫秒),默认 10 秒。 */
|
|
66
66
|
initialDelayMs: number;
|
|
67
|
+
/** 热更新前暂停活跃 run 的等待超时(毫秒),默认 30 秒。
|
|
68
|
+
* 暂停会等当前原子操作(工具执行)完成;超时仍未暂停的 run 将被强制中止。 */
|
|
69
|
+
pauseTimeoutMs?: number;
|
|
70
|
+
/** 手动安装等待程序文件变更的超时(毫秒),默认 10 分钟。 */
|
|
71
|
+
manualWaitTimeoutMs?: number;
|
|
67
72
|
};
|
|
68
73
|
/** Server security configuration (spec §24.2)。 */
|
|
69
74
|
type SecurityConfig = {
|
|
@@ -76,6 +81,11 @@ type SecurityConfig = {
|
|
|
76
81
|
token?: string;
|
|
77
82
|
/** 额外允许的 CORS origin(本地回环始终允许;局域网/远程访问需显式添加)。 */
|
|
78
83
|
allowedOrigins: string[];
|
|
84
|
+
/** 首设备 bootstrap token 有效期(毫秒)。bootstrap 自生成(auth-token 文件
|
|
85
|
+
* mtime)起超过此时长即拒绝首设备注册。未设置时默认 5 分钟(auth-manager 内置);
|
|
86
|
+
* 无已注册设备时每次 serve 都会重新生成 bootstrap,故重启即可恢复注册。
|
|
87
|
+
* 用于共享主机等 URL 可能泄漏的场景,缩短「先到先得」竞态窗口。 */
|
|
88
|
+
firstDeviceTtlMs?: number;
|
|
79
89
|
};
|
|
80
90
|
/** Global application configuration. */
|
|
81
91
|
type Config = {
|
|
@@ -116,6 +126,5 @@ type Config = {
|
|
|
116
126
|
permission: PermissionConfig;
|
|
117
127
|
update: UpdateConfig;
|
|
118
128
|
theme: 'light' | 'dark' | 'system';
|
|
119
|
-
locale: string;
|
|
120
129
|
};
|
|
121
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 };
|
|
@@ -35,9 +35,9 @@ type Message = {
|
|
|
35
35
|
tokenCount: number;
|
|
36
36
|
createdAt: number;
|
|
37
37
|
};
|
|
38
|
-
/** 上次 agent run
|
|
38
|
+
/** 上次 agent run 的持久化状态(服务重启后检测中断/恢复用)。 */
|
|
39
39
|
type LastRun = {
|
|
40
|
-
status: 'running' | 'completed';
|
|
40
|
+
status: 'running' | 'completed' | 'paused';
|
|
41
41
|
agentName?: string;
|
|
42
42
|
provider?: string;
|
|
43
43
|
model?: string;
|
|
@@ -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 = {
|
|
@@ -67,6 +76,10 @@ type Session = {
|
|
|
67
76
|
agentType: string | null;
|
|
68
77
|
/** 隔离 worktree 路径(null=共享父 cwd)。 */
|
|
69
78
|
worktreePath: string | null;
|
|
79
|
+
/** 会话来源:'web'(Web UI)/ 'cli'(CLI print 模式);null=旧数据视为 web。 */
|
|
80
|
+
source: 'web' | 'cli' | null;
|
|
81
|
+
/** 软删除时间戳(ms);null=未删除。 */
|
|
82
|
+
deletedAt: number | null;
|
|
70
83
|
createdAt: number;
|
|
71
84
|
updatedAt: number;
|
|
72
85
|
};
|
package/dist/tools/index.js
CHANGED
|
@@ -75,8 +75,7 @@ export function createDefaultRegistry(config = DEFAULT_CONFIG) {
|
|
|
75
75
|
export function resolveEnabledToolNames(registry, config, explicit) {
|
|
76
76
|
const all = listTools(registry).map((t) => t.name);
|
|
77
77
|
const disabled = new Set(config.tools?.disabled ?? []);
|
|
78
|
-
const base = explicit ??
|
|
79
|
-
? config.tools.enabled
|
|
80
|
-
: all);
|
|
78
|
+
const base = explicit ??
|
|
79
|
+
(config.tools?.enabled && config.tools.enabled.length > 0 ? config.tools.enabled : all);
|
|
81
80
|
return base.filter((n) => all.includes(n) && !disabled.has(n));
|
|
82
81
|
}
|
|
@@ -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 } 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 } 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
|
@@ -14,11 +14,22 @@ type HandoffOptions = {
|
|
|
14
14
|
* 旧实例与新实例读同一 auth-token 文件(或同一 config token),天然匹配。
|
|
15
15
|
*/
|
|
16
16
|
expectedToken?: string;
|
|
17
|
+
/**
|
|
18
|
+
* 自定义校验函数(P2-16:token 轮换后新旧实例 bootstrap 可能不同,
|
|
19
|
+
* 用 authManager.verifyHandoff 接受当前/历史 bootstrap 与设备 token)。
|
|
20
|
+
* 提供时优先于 expectedToken。
|
|
21
|
+
*/
|
|
22
|
+
verify?: (token: string | undefined) => boolean;
|
|
17
23
|
/**
|
|
18
24
|
* 响应发出后退出进程(旧实例完整让渡:主服务与资源已在 onHandoff 中关闭)。
|
|
19
|
-
* 退出延迟 250ms 保证 HTTP
|
|
25
|
+
* 退出延迟 250ms 保证 HTTP 响应刷出;不设时仅响应不退出(测试用 / 两阶段交接)。
|
|
20
26
|
*/
|
|
21
27
|
exitAfterResponse?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 收到 POST /handoff-confirm 时调用(两阶段交接第二阶段):
|
|
30
|
+
* 新实例绑定端口成功后的确认。调用方在此清理定时器并退出进程。
|
|
31
|
+
*/
|
|
32
|
+
onConfirm?: () => void | Promise<void>;
|
|
22
33
|
};
|
|
23
34
|
/**
|
|
24
35
|
* requestHandoff 抛出的可区分错误(调用方据此分流处理):
|
|
@@ -38,5 +49,11 @@ type HandoffError = Error & {
|
|
|
38
49
|
declare function createHandoffServer(onHandoff: () => Promise<void>, opts?: HandoffOptions): Promise<HandoffServer>;
|
|
39
50
|
/** 新实例请求旧实例让出端口(graceful shutdown)。失败抛可区分的 HandoffError。 */
|
|
40
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>;
|
|
41
58
|
export type { HandoffError, HandoffOptions, HandoffServer };
|
|
42
|
-
export { createHandoffServer, requestHandoff };
|
|
59
|
+
export { confirmHandoff, createHandoffServer, requestHandoff };
|
package/dist/update/ipc.js
CHANGED
|
@@ -16,16 +16,37 @@ function authHeaderMatches(actual, expected) {
|
|
|
16
16
|
*/
|
|
17
17
|
function createHandoffServer(onHandoff, opts = {}) {
|
|
18
18
|
return new Promise((resolve, reject) => {
|
|
19
|
-
const expected = opts.expectedToken && opts.expectedToken.length > 0
|
|
20
|
-
? `Bearer ${opts.expectedToken}`
|
|
21
|
-
: '';
|
|
19
|
+
const expected = opts.expectedToken && opts.expectedToken.length > 0 ? `Bearer ${opts.expectedToken}` : '';
|
|
22
20
|
const handler = async (req, res) => {
|
|
23
|
-
if (req.method === 'POST' && req.url === '/handoff') {
|
|
24
|
-
|
|
21
|
+
if (req.method === 'POST' && (req.url === '/handoff' || req.url === '/handoff-confirm')) {
|
|
22
|
+
const authorized = opts.verify
|
|
23
|
+
? opts.verify(req.headers.authorization?.replace(/^Bearer\s+/i, ''))
|
|
24
|
+
: expected.length > 0
|
|
25
|
+
? authHeaderMatches(req.headers.authorization, expected)
|
|
26
|
+
: true;
|
|
27
|
+
if (!authorized) {
|
|
25
28
|
res.writeHead(401, { 'content-type': 'application/json' });
|
|
26
29
|
res.end(JSON.stringify({ ok: false, error: 'unauthorized' }));
|
|
27
30
|
return;
|
|
28
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
|
+
}
|
|
29
50
|
try {
|
|
30
51
|
await onHandoff();
|
|
31
52
|
res.writeHead(200, { 'content-type': 'application/json' });
|
|
@@ -84,4 +105,20 @@ async function requestHandoff(port, host = '127.0.0.1', token) {
|
|
|
84
105
|
throw error;
|
|
85
106
|
}
|
|
86
107
|
}
|
|
87
|
-
|
|
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 };
|
|
@@ -7,6 +7,11 @@ type SerializedSession = {
|
|
|
7
7
|
projectId: string | null;
|
|
8
8
|
branchPoint: number | null;
|
|
9
9
|
metadata: unknown;
|
|
10
|
+
agentType: string | null;
|
|
11
|
+
worktreePath: string | null;
|
|
12
|
+
source: string | null;
|
|
13
|
+
deletedAt: number | null;
|
|
14
|
+
deletedBatchId: string | null;
|
|
10
15
|
createdAt: number;
|
|
11
16
|
updatedAt: number;
|
|
12
17
|
};
|
package/dist/update/snapshot.js
CHANGED
|
@@ -15,6 +15,11 @@ function toSerializedSession(row) {
|
|
|
15
15
|
projectId: row.projectId,
|
|
16
16
|
branchPoint: row.branchPoint,
|
|
17
17
|
metadata: row.metadata,
|
|
18
|
+
agentType: row.agentType,
|
|
19
|
+
worktreePath: row.worktreePath,
|
|
20
|
+
source: row.source,
|
|
21
|
+
deletedAt: row.deletedAt ? toDateMs(row.deletedAt) : null,
|
|
22
|
+
deletedBatchId: row.deletedBatchId ?? null,
|
|
18
23
|
createdAt: toDateMs(row.createdAt),
|
|
19
24
|
updatedAt: toDateMs(row.updatedAt),
|
|
20
25
|
};
|
|
@@ -75,6 +80,11 @@ async function restoreSessions(handle, snapshot) {
|
|
|
75
80
|
projectId: s.projectId,
|
|
76
81
|
branchPoint: s.branchPoint,
|
|
77
82
|
metadata: s.metadata,
|
|
83
|
+
agentType: s.agentType,
|
|
84
|
+
worktreePath: s.worktreePath,
|
|
85
|
+
source: s.source,
|
|
86
|
+
deletedAt: s.deletedAt != null ? new Date(s.deletedAt) : null,
|
|
87
|
+
deletedBatchId: s.deletedBatchId ?? null,
|
|
78
88
|
createdAt: new Date(s.createdAt),
|
|
79
89
|
updatedAt: new Date(s.updatedAt),
|
|
80
90
|
})
|
package/dist/update/updater.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { SessionSnapshot } from './snapshot.js';
|
|
|
3
3
|
type HotUpdateResult = {
|
|
4
4
|
_tag: 'success';
|
|
5
5
|
snapshotPath: string;
|
|
6
|
+
installMethod: string;
|
|
6
7
|
} | {
|
|
7
8
|
_tag: 'install_failed';
|
|
8
9
|
error: string;
|
|
@@ -11,6 +12,23 @@ type HotUpdateResult = {
|
|
|
11
12
|
_tag: 'spawn_failed';
|
|
12
13
|
error: string;
|
|
13
14
|
snapshotPath: string;
|
|
15
|
+
} | {
|
|
16
|
+
_tag: 'manual_install_required';
|
|
17
|
+
error: string;
|
|
18
|
+
snapshotPath: string;
|
|
19
|
+
command: string;
|
|
20
|
+
};
|
|
21
|
+
/** 仅安装结果(无活跃 serve 的 CLI `c0de update --apply` 使用;不 spawn 新实例)。 */
|
|
22
|
+
type InstallOnlyResult = {
|
|
23
|
+
_tag: 'success';
|
|
24
|
+
installMethod: string;
|
|
25
|
+
} | {
|
|
26
|
+
_tag: 'install_failed';
|
|
27
|
+
error: string;
|
|
28
|
+
} | {
|
|
29
|
+
_tag: 'manual_install_required';
|
|
30
|
+
error: string;
|
|
31
|
+
command: string;
|
|
14
32
|
};
|
|
15
33
|
/**
|
|
16
34
|
* 启动新实例的函数签名。
|
|
@@ -20,8 +38,8 @@ type HotUpdateResult = {
|
|
|
20
38
|
*/
|
|
21
39
|
type SpawnFn = (snapshotPath: string, argv: string[], opts?: HotUpdateOptions) => Promise<void>;
|
|
22
40
|
type HotUpdateOptions = {
|
|
23
|
-
/**
|
|
24
|
-
installFn?: (pkg: string) => Promise<void>;
|
|
41
|
+
/** 自更新安装函数;默认按检测到的安装方式(npm/pnpm)执行。 */
|
|
42
|
+
installFn?: (pkg: string, method: InstallMethod) => Promise<void>;
|
|
25
43
|
/** 启动新实例的函数;默认 detached spawn 当前进程入口。 */
|
|
26
44
|
spawnNewInstanceFn?: SpawnFn;
|
|
27
45
|
packageName?: string;
|
|
@@ -35,18 +53,61 @@ type HotUpdateOptions = {
|
|
|
35
53
|
port?: number;
|
|
36
54
|
/** 传给新实例的认证 token(经环境变量 C0DE_AUTH_TOKEN),保证新旧实例握手同 token。 */
|
|
37
55
|
authToken?: string;
|
|
56
|
+
/** 手动安装等待程序文件变更的超时(ms);默认 10 分钟,超时放弃等待。 */
|
|
57
|
+
manualWaitTimeoutMs?: number;
|
|
38
58
|
};
|
|
59
|
+
/** 安装方式(P0-2:识别当前进程安装器;unknown = 不自动安装,提示手动更新)。 */
|
|
60
|
+
type InstallMethod = {
|
|
61
|
+
kind: 'npm';
|
|
62
|
+
} | {
|
|
63
|
+
kind: 'pnpm';
|
|
64
|
+
} | {
|
|
65
|
+
kind: 'unknown';
|
|
66
|
+
hint: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* 识别当前进程的安装方式:
|
|
70
|
+
* - 入口脚本 realpath 含 node_modules/.pnpm → pnpm
|
|
71
|
+
* - 含 node_modules(非 .pnpm)→ npm
|
|
72
|
+
* - 否则 unknown(npx 缓存、源码 dev、其他)→ 不自动安装
|
|
73
|
+
* 检测不到时返回 unknown,由上层提示用户手动安装。
|
|
74
|
+
*/
|
|
75
|
+
declare function detectInstallMethod(): InstallMethod;
|
|
76
|
+
/** 手动安装提示命令(unknown 安装方式时返回给前端展示)。 */
|
|
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>;
|
|
39
92
|
/**
|
|
40
|
-
* 执行热更新(spec §18.1-18.2
|
|
93
|
+
* 执行热更新(spec §18.1-18.2 + P0-2 修订流程):
|
|
41
94
|
* 1. 序列化当前会话状态到快照文件
|
|
42
|
-
* 2.
|
|
43
|
-
* 3.
|
|
95
|
+
* 2. 识别当前进程安装方式(npm/pnpm/unknown)
|
|
96
|
+
* 3. 已知方式 → 自动安装;unknown → 不安装,等待用户手动安装
|
|
97
|
+
* 4. watch 当前执行程序文件变更 → 触发滚动切换
|
|
98
|
+
* (detached 启动新实例 + restore 快照 + handoff 端口接管)
|
|
44
99
|
*
|
|
45
100
|
* 注:旧实例的 graceful shutdown / 端口让渡由新实例启动后通过 IPC 协调
|
|
46
|
-
* (见 ipc.ts),本函数只负责"序列化 + 安装 + 接力"。
|
|
101
|
+
* (见 ipc.ts),本函数只负责"序列化 + 安装 + 等待程序变更 + 接力"。
|
|
47
102
|
*/
|
|
48
103
|
declare function performHotUpdate(snapshot: SessionSnapshot, opts?: HotUpdateOptions): Promise<HotUpdateResult>;
|
|
104
|
+
/**
|
|
105
|
+
* 仅安装新版本(P2-7:无活跃 serve 的 `c0de update --apply`)。
|
|
106
|
+
* 不序列化快照、不 spawn `serve`——此前独立 CLI 执行会拉起一个用户未请求的常驻服务。
|
|
107
|
+
* 运行中的 serve 由 `/api/update/apply` 走 performHotUpdate 热更新路径。
|
|
108
|
+
*/
|
|
109
|
+
declare function performInstall(opts?: HotUpdateOptions): Promise<InstallOnlyResult>;
|
|
49
110
|
/** 清理快照临时目录(热更新完成或放弃后调用)。 */
|
|
50
111
|
declare function cleanupSnapshot(snapshotPath: string): Promise<void>;
|
|
51
|
-
export type { HotUpdateOptions, HotUpdateResult, SpawnFn };
|
|
52
|
-
export { cleanupSnapshot, performHotUpdate };
|
|
112
|
+
export type { HotUpdateOptions, HotUpdateResult, InstallMethod, InstallOnlyResult, SpawnFn };
|
|
113
|
+
export { cleanupSnapshot, detectInstallMethod, manualInstallCommand, performHandoff, performHotUpdate, performInstall, };
|