c0de-agent 1.10.0 → 1.11.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 +5 -2
- 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 +8 -0
- package/dist/cli/commands/config.d.ts +2 -9
- package/dist/cli/commands/config.js +9 -52
- 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 +35 -0
- package/dist/cli/commands/update.js +18 -10
- package/dist/cli/index.js +30 -2
- package/dist/cli/modes/print.js +11 -1
- package/dist/core/config-path.d.ts +9 -0
- package/dist/core/config-path.js +48 -0
- package/dist/core/config.d.ts +10 -2
- package/dist/core/config.js +32 -10
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/loop.js +13 -3
- package/dist/core/slash.js +92 -30
- package/dist/core/title.js +15 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/db/client.d.ts +4 -0
- package/dist/db/client.js +5 -0
- package/dist/db/schema.d.ts +34 -0
- package/dist/db/schema.js +4 -0
- package/dist/server/agent-manager.d.ts +5 -0
- package/dist/server/agent-manager.js +28 -0
- package/dist/server/app.js +12 -2
- package/dist/server/auth-manager.d.ts +79 -0
- package/dist/server/auth-manager.js +270 -0
- package/dist/server/context.js +1 -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 +2 -0
- package/dist/server/permission/interactive.js +5 -1
- package/dist/server/permission/store.d.ts +4 -0
- package/dist/server/permission/store.js +1 -0
- package/dist/server/routes/auth.d.ts +4 -0
- package/dist/server/routes/auth.js +94 -0
- package/dist/server/routes/chat.d.ts +6 -1
- package/dist/server/routes/chat.js +87 -7
- package/dist/server/routes/config.js +59 -11
- package/dist/server/routes/permissions.d.ts +4 -1
- package/dist/server/routes/permissions.js +27 -4
- package/dist/server/routes/project.js +56 -2
- package/dist/server/routes/session.js +31 -7
- package/dist/server/routes/terminal.js +17 -2
- package/dist/server/routes/update.js +23 -1
- package/dist/server/server.js +47 -8
- package/dist/server/types.d.ts +8 -1
- package/dist/session/archive.d.ts +1 -1
- package/dist/session/branch.d.ts +1 -0
- package/dist/session/branch.js +12 -5
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js +1 -1
- package/dist/session/session.d.ts +25 -5
- package/dist/session/session.js +129 -10
- package/dist/session/types.d.ts +1 -1
- package/dist/shared/types/agent.d.ts +11 -0
- package/dist/shared/types/config.d.ts +5 -0
- package/dist/shared/types/message.d.ts +6 -2
- package/dist/tools/index.js +2 -3
- package/dist/update/index.d.ts +1 -1
- package/dist/update/index.js +1 -1
- package/dist/update/ipc.d.ts +6 -0
- package/dist/update/ipc.js +7 -4
- package/dist/update/snapshot.d.ts +4 -0
- package/dist/update/snapshot.js +8 -0
- package/dist/update/updater.d.ts +55 -8
- package/dist/update/updater.js +230 -27
- package/drizzle/0006_sour_susan_delgado.sql +2 -0
- package/drizzle/0007_same_wilson_fisk.sql +2 -0
- package/drizzle/meta/0006_snapshot.json +867 -0
- package/drizzle/meta/0007_snapshot.json +869 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +1 -1
|
@@ -6,13 +6,33 @@ import type { LastRun, Session } from '../shared/types/message.js';
|
|
|
6
6
|
/** Convert a DB row (with Date timestamps) to the shared Session type (with number timestamps). */
|
|
7
7
|
export declare function rowToSession(row: typeof sessions.$inferSelect): Session;
|
|
8
8
|
/** Create a new root session. */
|
|
9
|
-
declare function createSession(handle: DB, title: string, projectId?: string, agentType?: string): Promise<Session>;
|
|
9
|
+
declare function createSession(handle: DB, title: string, projectId?: string, agentType?: string, source?: 'web' | 'cli'): Promise<Session>;
|
|
10
10
|
/** Get a session by id, or null if not found. */
|
|
11
11
|
declare function getSession(handle: DB, id: string): Promise<Session | null>;
|
|
12
|
-
/** List all sessions. */
|
|
12
|
+
/** List all active sessions (未软删除、非 CLI 来源;source 为 NULL 的旧数据视为 web). */
|
|
13
13
|
declare function listSessions(handle: DB): Promise<Session[]>;
|
|
14
|
-
/**
|
|
15
|
-
declare function
|
|
14
|
+
/** 列出所有未软删除会话(含 CLI 来源;供 ACP/CLI 等非 Web 消费者使用)。 */
|
|
15
|
+
declare function listAllSessions(handle: DB): Promise<Session[]>;
|
|
16
|
+
/** List soft-deleted sessions (回收站). */
|
|
17
|
+
declare function listDeletedSessions(handle: DB): Promise<Session[]>;
|
|
18
|
+
/**
|
|
19
|
+
* 软删除会话(级联其所有 fork 后代)。设置 deletedAt = now;
|
|
20
|
+
* 30 天后由 purgeDeletedSessions 物理清除。
|
|
21
|
+
* 会话不存在或已在回收站 → 返回 false(调用方按 404 处理)。
|
|
22
|
+
*/
|
|
23
|
+
declare function softDeleteSession(handle: DB, id: string): Promise<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* 从回收站恢复会话:默认连带恢复其已软删除的祖先链——
|
|
26
|
+
* 否则恢复的会话因父仍在回收站而游离于会话树之外(P2-1:UI 不可达)。
|
|
27
|
+
* 祖先中未删除的(活跃)节点不需要也不应该被改动。
|
|
28
|
+
*/
|
|
29
|
+
declare function restoreSession(handle: DB, id: string): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* 物理清除回收站中超过保留期(默认 30 天)的会话。
|
|
32
|
+
* 子会话先于父会话删除(自引用 FK 要求)。
|
|
33
|
+
* 返回清除数量。启动时与每日定时调用。
|
|
34
|
+
*/
|
|
35
|
+
declare function purgeDeletedSessions(handle: DB, retentionMs?: number): Promise<number>;
|
|
16
36
|
/** Update a session's title. */
|
|
17
37
|
declare function updateSessionTitle(handle: DB, id: string, title: string): Promise<void>;
|
|
18
38
|
/** Bump updatedAt to now (used after appending messages). */
|
|
@@ -36,4 +56,4 @@ export declare function saveLLMSegments(handle: DB, id: string, segments: LLMSeg
|
|
|
36
56
|
/** 更新会话 metadata.lastRun(agent run 开始/结束时写入;重启后检测中断用)。 */
|
|
37
57
|
declare function updateSessionLastRun(handle: DB, id: string, lastRun: LastRun): Promise<void>;
|
|
38
58
|
declare function listSessionsByProject(handle: DB, projectId: string): Promise<Session[]>;
|
|
39
|
-
export { createSession,
|
|
59
|
+
export { createSession, getSession, listAllSessions, listDeletedSessions, listSessions, listSessionsByProject, purgeDeletedSessions, restoreSession, softDeleteSession, touchLastOpened, touchSession, updateSessionLastRun, updateSessionTitle, };
|
package/dist/session/session.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { eq } from 'drizzle-orm';
|
|
1
|
+
import { and, eq, gt, inArray, isNull, lt, ne, or } from 'drizzle-orm';
|
|
2
2
|
import { sessions } from '../db/schema.js';
|
|
3
3
|
import { generateId } from '../shared/index.js';
|
|
4
4
|
/** Convert a DB row (with Date timestamps) to the shared Session type (with number timestamps). */
|
|
5
5
|
export function rowToSession(row) {
|
|
6
6
|
const created = row.createdAt instanceof Date ? row.createdAt.getTime() : new Date(row.createdAt).getTime();
|
|
7
7
|
const updated = row.updatedAt instanceof Date ? row.updatedAt.getTime() : new Date(row.updatedAt).getTime();
|
|
8
|
+
const deleted = row.deletedAt
|
|
9
|
+
? row.deletedAt instanceof Date
|
|
10
|
+
? row.deletedAt.getTime()
|
|
11
|
+
: new Date(row.deletedAt).getTime()
|
|
12
|
+
: null;
|
|
8
13
|
return {
|
|
9
14
|
id: row.id,
|
|
10
15
|
title: row.title,
|
|
@@ -14,15 +19,22 @@ export function rowToSession(row) {
|
|
|
14
19
|
metadata: (row.metadata ?? {}),
|
|
15
20
|
agentType: row.agentType ?? null,
|
|
16
21
|
worktreePath: row.worktreePath ?? null,
|
|
22
|
+
source: row.source === 'web' || row.source === 'cli' ? row.source : null,
|
|
23
|
+
deletedAt: deleted,
|
|
17
24
|
createdAt: created,
|
|
18
25
|
updatedAt: updated,
|
|
19
26
|
};
|
|
20
27
|
}
|
|
21
28
|
/** Create a new root session. */
|
|
22
|
-
async function createSession(handle, title, projectId, agentType) {
|
|
29
|
+
async function createSession(handle, title, projectId, agentType, source) {
|
|
23
30
|
const [row] = await handle.db
|
|
24
31
|
.insert(sessions)
|
|
25
|
-
.values({
|
|
32
|
+
.values({
|
|
33
|
+
title,
|
|
34
|
+
projectId: projectId ?? null,
|
|
35
|
+
agentType: agentType ?? null,
|
|
36
|
+
source: source ?? null,
|
|
37
|
+
})
|
|
26
38
|
.returning();
|
|
27
39
|
if (!row)
|
|
28
40
|
throw new Error('Failed to insert session');
|
|
@@ -33,14 +45,118 @@ async function getSession(handle, id) {
|
|
|
33
45
|
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
34
46
|
return row ? rowToSession(row) : null;
|
|
35
47
|
}
|
|
36
|
-
/** List all sessions. */
|
|
48
|
+
/** List all active sessions (未软删除、非 CLI 来源;source 为 NULL 的旧数据视为 web). */
|
|
37
49
|
async function listSessions(handle) {
|
|
38
|
-
const rows = await handle.db
|
|
50
|
+
const rows = await handle.db
|
|
51
|
+
.select()
|
|
52
|
+
.from(sessions)
|
|
53
|
+
.where(and(isNull(sessions.deletedAt), or(isNull(sessions.source), ne(sessions.source, 'cli'))));
|
|
39
54
|
return rows.map(rowToSession);
|
|
40
55
|
}
|
|
41
|
-
/**
|
|
42
|
-
async function
|
|
43
|
-
await handle.db.
|
|
56
|
+
/** 列出所有未软删除会话(含 CLI 来源;供 ACP/CLI 等非 Web 消费者使用)。 */
|
|
57
|
+
async function listAllSessions(handle) {
|
|
58
|
+
const rows = await handle.db.select().from(sessions).where(isNull(sessions.deletedAt));
|
|
59
|
+
return rows.map(rowToSession);
|
|
60
|
+
}
|
|
61
|
+
/** List soft-deleted sessions (回收站). */
|
|
62
|
+
async function listDeletedSessions(handle) {
|
|
63
|
+
const rows = await handle.db
|
|
64
|
+
.select()
|
|
65
|
+
.from(sessions)
|
|
66
|
+
.where(gt(sessions.deletedAt, new Date(0)));
|
|
67
|
+
return rows.map(rowToSession);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 软删除会话(级联其所有 fork 后代)。设置 deletedAt = now;
|
|
71
|
+
* 30 天后由 purgeDeletedSessions 物理清除。
|
|
72
|
+
* 会话不存在或已在回收站 → 返回 false(调用方按 404 处理)。
|
|
73
|
+
*/
|
|
74
|
+
async function softDeleteSession(handle, id) {
|
|
75
|
+
const [row] = await handle.db
|
|
76
|
+
.select({ id: sessions.id, deletedAt: sessions.deletedAt })
|
|
77
|
+
.from(sessions)
|
|
78
|
+
.where(eq(sessions.id, id));
|
|
79
|
+
if (!row || row.deletedAt)
|
|
80
|
+
return false;
|
|
81
|
+
const ids = new Set([id]);
|
|
82
|
+
let frontier = [id];
|
|
83
|
+
while (frontier.length > 0) {
|
|
84
|
+
// 用 parentId 过滤:收集下一层子会话
|
|
85
|
+
const children = await handle.db
|
|
86
|
+
.select({ id: sessions.id })
|
|
87
|
+
.from(sessions)
|
|
88
|
+
.where(and(isNull(sessions.deletedAt), inArray(sessions.parentId, frontier)));
|
|
89
|
+
frontier = children.map((r) => r.id).filter((cid) => !ids.has(cid));
|
|
90
|
+
for (const r of children)
|
|
91
|
+
ids.add(r.id);
|
|
92
|
+
}
|
|
93
|
+
const now = new Date();
|
|
94
|
+
for (const sid of ids) {
|
|
95
|
+
await handle.db.update(sessions).set({ deletedAt: now }).where(eq(sessions.id, sid));
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 从回收站恢复会话:默认连带恢复其已软删除的祖先链——
|
|
101
|
+
* 否则恢复的会话因父仍在回收站而游离于会话树之外(P2-1:UI 不可达)。
|
|
102
|
+
* 祖先中未删除的(活跃)节点不需要也不应该被改动。
|
|
103
|
+
*/
|
|
104
|
+
async function restoreSession(handle, id) {
|
|
105
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
106
|
+
if (!row || !row.deletedAt)
|
|
107
|
+
return false;
|
|
108
|
+
const ids = new Set([id]);
|
|
109
|
+
let parentId = row.parentId;
|
|
110
|
+
while (parentId) {
|
|
111
|
+
const [parent] = await handle.db.select().from(sessions).where(eq(sessions.id, parentId));
|
|
112
|
+
if (!parent)
|
|
113
|
+
break;
|
|
114
|
+
if (parent.deletedAt)
|
|
115
|
+
ids.add(parent.id);
|
|
116
|
+
parentId = parent.parentId;
|
|
117
|
+
}
|
|
118
|
+
for (const sid of ids) {
|
|
119
|
+
await handle.db.update(sessions).set({ deletedAt: null }).where(eq(sessions.id, sid));
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 物理清除回收站中超过保留期(默认 30 天)的会话。
|
|
125
|
+
* 子会话先于父会话删除(自引用 FK 要求)。
|
|
126
|
+
* 返回清除数量。启动时与每日定时调用。
|
|
127
|
+
*/
|
|
128
|
+
async function purgeDeletedSessions(handle, retentionMs = 30 * 24 * 60 * 60 * 1000) {
|
|
129
|
+
const cutoff = new Date(Date.now() - retentionMs);
|
|
130
|
+
const rows = await handle.db
|
|
131
|
+
.select({ id: sessions.id, parentId: sessions.parentId })
|
|
132
|
+
.from(sessions)
|
|
133
|
+
.where(lt(sessions.deletedAt, cutoff));
|
|
134
|
+
if (rows.length === 0)
|
|
135
|
+
return 0;
|
|
136
|
+
// 拓扑序:无子会话的先删
|
|
137
|
+
const remaining = new Set(rows.map((r) => r.id));
|
|
138
|
+
let deleted = 0;
|
|
139
|
+
while (remaining.size > 0) {
|
|
140
|
+
const hasChildParent = new Set(rows.filter((r) => r.parentId && remaining.has(r.parentId)).map((r) => r.parentId));
|
|
141
|
+
const leaves = rows
|
|
142
|
+
.filter((r) => remaining.has(r.id) && !hasChildParent.has(r.id))
|
|
143
|
+
.map((r) => r.id);
|
|
144
|
+
if (leaves.length === 0) {
|
|
145
|
+
// 循环引用兜底:强制按 id 逐个删除(自引用环数据异常场景)
|
|
146
|
+
for (const id of Array.from(remaining)) {
|
|
147
|
+
await handle.db.delete(sessions).where(eq(sessions.id, id));
|
|
148
|
+
remaining.delete(id);
|
|
149
|
+
deleted += 1;
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
for (const id of leaves) {
|
|
154
|
+
await handle.db.delete(sessions).where(eq(sessions.id, id));
|
|
155
|
+
remaining.delete(id);
|
|
156
|
+
deleted += 1;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return deleted;
|
|
44
160
|
}
|
|
45
161
|
/** Update a session's title. */
|
|
46
162
|
async function updateSessionTitle(handle, id, title) {
|
|
@@ -149,7 +265,10 @@ async function updateSessionLastRun(handle, id, lastRun) {
|
|
|
149
265
|
.where(eq(sessions.id, id));
|
|
150
266
|
}
|
|
151
267
|
async function listSessionsByProject(handle, projectId) {
|
|
152
|
-
const rows = await handle.db
|
|
268
|
+
const rows = await handle.db
|
|
269
|
+
.select()
|
|
270
|
+
.from(sessions)
|
|
271
|
+
.where(and(eq(sessions.projectId, projectId), isNull(sessions.deletedAt), or(isNull(sessions.source), ne(sessions.source, 'cli'))));
|
|
153
272
|
return rows.map(rowToSession);
|
|
154
273
|
}
|
|
155
|
-
export { createSession,
|
|
274
|
+
export { createSession, getSession, listAllSessions, listDeletedSessions, listSessions, listSessionsByProject, purgeDeletedSessions, restoreSession, softDeleteSession, touchLastOpened, touchSession, updateSessionLastRun, updateSessionTitle, };
|
package/dist/session/types.d.ts
CHANGED
|
@@ -101,7 +101,7 @@ type CompactionArchive = {
|
|
|
101
101
|
id: string;
|
|
102
102
|
sessionId: string;
|
|
103
103
|
compactionId: string;
|
|
104
|
-
archiveType: 'compaction' | 'squash' | 'shake';
|
|
104
|
+
archiveType: 'compaction' | 'squash' | 'shake' | 'clear';
|
|
105
105
|
originalEntries: SessionEntry[];
|
|
106
106
|
summary: string;
|
|
107
107
|
tokenCount: number;
|
|
@@ -148,6 +148,13 @@ 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;
|
|
151
158
|
} | {
|
|
152
159
|
_tag: 'error';
|
|
153
160
|
error: AgentError;
|
|
@@ -245,5 +252,9 @@ type AgentState = {
|
|
|
245
252
|
remaining: number;
|
|
246
253
|
noTextStreak: number;
|
|
247
254
|
};
|
|
255
|
+
/** 本次 run 开始时间戳(ms)。暂停持久化 lastRun 时回填 startedAt。 */
|
|
256
|
+
lastRunStartedAt?: number;
|
|
257
|
+
/** 暂停状态是否已 yield 给前端(loop 内用,防重复 status_change)。 */
|
|
258
|
+
pauseYielded?: boolean;
|
|
248
259
|
};
|
|
249
260
|
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 = {
|
|
@@ -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;
|
|
@@ -67,6 +67,10 @@ type Session = {
|
|
|
67
67
|
agentType: string | null;
|
|
68
68
|
/** 隔离 worktree 路径(null=共享父 cwd)。 */
|
|
69
69
|
worktreePath: string | null;
|
|
70
|
+
/** 会话来源:'web'(Web UI)/ 'cli'(CLI print 模式);null=旧数据视为 web。 */
|
|
71
|
+
source: 'web' | 'cli' | null;
|
|
72
|
+
/** 软删除时间戳(ms);null=未删除。 */
|
|
73
|
+
deletedAt: number | null;
|
|
70
74
|
createdAt: number;
|
|
71
75
|
updatedAt: number;
|
|
72
76
|
};
|
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
|
}
|
package/dist/update/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ 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
7
|
export type { HotUpdateOptions, HotUpdateResult, SpawnFn } from './updater.js';
|
|
8
|
-
export { cleanupSnapshot, performHotUpdate } from './updater.js';
|
|
8
|
+
export { cleanupSnapshot, 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
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
export { 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, performHotUpdate, performInstall } from './updater.js';
|
|
6
6
|
export { checkForUpdate, compareSemver, getCurrentVersion } from './version.js';
|
package/dist/update/ipc.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ 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
25
|
* 退出延迟 250ms 保证 HTTP 响应刷出;不设时仅响应不退出(测试用)。
|
package/dist/update/ipc.js
CHANGED
|
@@ -16,12 +16,15 @@ 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
21
|
if (req.method === 'POST' && req.url === '/handoff') {
|
|
24
|
-
|
|
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;
|
|
@@ -7,6 +7,10 @@ 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;
|
|
10
14
|
createdAt: number;
|
|
11
15
|
updatedAt: number;
|
|
12
16
|
};
|
package/dist/update/snapshot.js
CHANGED
|
@@ -15,6 +15,10 @@ 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,
|
|
18
22
|
createdAt: toDateMs(row.createdAt),
|
|
19
23
|
updatedAt: toDateMs(row.updatedAt),
|
|
20
24
|
};
|
|
@@ -75,6 +79,10 @@ async function restoreSessions(handle, snapshot) {
|
|
|
75
79
|
projectId: s.projectId,
|
|
76
80
|
branchPoint: s.branchPoint,
|
|
77
81
|
metadata: s.metadata,
|
|
82
|
+
agentType: s.agentType,
|
|
83
|
+
worktreePath: s.worktreePath,
|
|
84
|
+
source: s.source,
|
|
85
|
+
deletedAt: s.deletedAt != null ? new Date(s.deletedAt) : null,
|
|
78
86
|
createdAt: new Date(s.createdAt),
|
|
79
87
|
updatedAt: new Date(s.updatedAt),
|
|
80
88
|
})
|
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,47 @@ type HotUpdateOptions = {
|
|
|
35
53
|
port?: number;
|
|
36
54
|
/** 传给新实例的认证 token(经环境变量 C0DE_AUTH_TOKEN),保证新旧实例握手同 token。 */
|
|
37
55
|
authToken?: string;
|
|
56
|
+
/** 手动安装等待程序文件变更的超时(ms);默认 10 分钟,超时放弃等待。 */
|
|
57
|
+
manualWaitTimeoutMs?: number;
|
|
58
|
+
};
|
|
59
|
+
/** 安装方式(P0-2:识别当前进程安装器;unknown = 不自动安装,提示手动更新)。 */
|
|
60
|
+
type InstallMethod = {
|
|
61
|
+
kind: 'npm';
|
|
62
|
+
} | {
|
|
63
|
+
kind: 'pnpm';
|
|
64
|
+
} | {
|
|
65
|
+
kind: 'unknown';
|
|
66
|
+
hint: string;
|
|
38
67
|
};
|
|
39
68
|
/**
|
|
40
|
-
*
|
|
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
|
+
* 执行热更新(spec §18.1-18.2 + P0-2 修订流程):
|
|
41
80
|
* 1. 序列化当前会话状态到快照文件
|
|
42
|
-
* 2.
|
|
43
|
-
* 3.
|
|
81
|
+
* 2. 识别当前进程安装方式(npm/pnpm/unknown)
|
|
82
|
+
* 3. 已知方式 → 自动安装;unknown → 不安装,等待用户手动安装
|
|
83
|
+
* 4. watch 当前执行程序文件变更 → 触发滚动切换
|
|
84
|
+
* (detached 启动新实例 + restore 快照 + handoff 端口接管)
|
|
44
85
|
*
|
|
45
86
|
* 注:旧实例的 graceful shutdown / 端口让渡由新实例启动后通过 IPC 协调
|
|
46
|
-
* (见 ipc.ts),本函数只负责"序列化 + 安装 + 接力"。
|
|
87
|
+
* (见 ipc.ts),本函数只负责"序列化 + 安装 + 等待程序变更 + 接力"。
|
|
47
88
|
*/
|
|
48
89
|
declare function performHotUpdate(snapshot: SessionSnapshot, opts?: HotUpdateOptions): Promise<HotUpdateResult>;
|
|
90
|
+
/**
|
|
91
|
+
* 仅安装新版本(P2-7:无活跃 serve 的 `c0de update --apply`)。
|
|
92
|
+
* 不序列化快照、不 spawn `serve`——此前独立 CLI 执行会拉起一个用户未请求的常驻服务。
|
|
93
|
+
* 运行中的 serve 由 `/api/update/apply` 走 performHotUpdate 热更新路径。
|
|
94
|
+
*/
|
|
95
|
+
declare function performInstall(opts?: HotUpdateOptions): Promise<InstallOnlyResult>;
|
|
49
96
|
/** 清理快照临时目录(热更新完成或放弃后调用)。 */
|
|
50
97
|
declare function cleanupSnapshot(snapshotPath: string): Promise<void>;
|
|
51
|
-
export type { HotUpdateOptions, HotUpdateResult, SpawnFn };
|
|
52
|
-
export { cleanupSnapshot, performHotUpdate };
|
|
98
|
+
export type { HotUpdateOptions, HotUpdateResult, InstallMethod, InstallOnlyResult, SpawnFn };
|
|
99
|
+
export { cleanupSnapshot, detectInstallMethod, manualInstallCommand, performHotUpdate, performInstall, };
|