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
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
/** 默认 pending 超时时长:5 分钟(300_000ms)。
|
|
8
8
|
* 超过后自动 resolve 为 deny 并清理 store 条目,避免 promise 永久挂起与 pending Map 泄漏。 */
|
|
9
9
|
const DEFAULT_PERMISSION_TIMEOUT_MS = 5 * 60 * 1000;
|
|
10
|
+
/** 首层超时(仅提示)后到兜底自动拒绝的宽限期:25 分钟。双层超时总窗口 30 分钟。 */
|
|
11
|
+
const DEFAULT_PERMISSION_EXPIRE_GRACE_MS = 25 * 60 * 1000;
|
|
10
12
|
function createPermissionStore(opts = {}) {
|
|
11
13
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_PERMISSION_TIMEOUT_MS;
|
|
14
|
+
const expireGraceMs = opts.expireGraceMs ?? DEFAULT_PERMISSION_EXPIRE_GRACE_MS;
|
|
12
15
|
const pending = new Map();
|
|
13
16
|
// 统一终结路径:clearTimeout → 删除条目 → resolve。
|
|
14
17
|
// 三处调用(confirm、超时回调、abort 联动)都汇聚于此:
|
|
@@ -28,9 +31,27 @@ function createPermissionStore(opts = {}) {
|
|
|
28
31
|
const existing = pending.get(toolCallId);
|
|
29
32
|
if (existing)
|
|
30
33
|
clearTimeout(existing.timer);
|
|
34
|
+
// P0 双层超时:首层仅提示(pending 保持,用户可「重新询问」);提示后仍无响应
|
|
35
|
+
// 则宽限期后兜底自动拒绝——否则用户离开页面后 run 被永久阻塞、会话 409 锁死。
|
|
36
|
+
// 无 onTimeout(非交互式 store)保留旧的单层自动拒绝语义。
|
|
31
37
|
const timer = setTimeout(() => {
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
if (!entry.onTimeout) {
|
|
39
|
+
settle(toolCallId, { _tag: 'deny', reason: 'Permission request timed out' });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
entry.onTimeout(entry.request);
|
|
43
|
+
// 首层提示后:重新挂宽限定时器(句柄回写 entry,settle 才能清理它)
|
|
44
|
+
const expireTimer = setTimeout(() => {
|
|
45
|
+
const cur = pending.get(toolCallId);
|
|
46
|
+
if (!cur)
|
|
47
|
+
return;
|
|
48
|
+
pending.set(toolCallId, { ...cur, timer: expireTimer });
|
|
49
|
+
cur.onExpired?.(cur.request);
|
|
50
|
+
settle(toolCallId, { _tag: 'deny', reason: 'Permission request expired' });
|
|
51
|
+
}, expireGraceMs);
|
|
52
|
+
const cur = pending.get(toolCallId);
|
|
53
|
+
if (cur)
|
|
54
|
+
pending.set(toolCallId, { ...cur, timer: expireTimer });
|
|
34
55
|
}, timeoutMs);
|
|
35
56
|
pending.set(toolCallId, { ...entry, timer });
|
|
36
57
|
},
|
|
@@ -43,6 +64,13 @@ function createPermissionStore(opts = {}) {
|
|
|
43
64
|
size() {
|
|
44
65
|
return pending.size;
|
|
45
66
|
},
|
|
67
|
+
pendingForSession(sessionId) {
|
|
68
|
+
for (const p of pending.values()) {
|
|
69
|
+
if (p.request.sessionId === sessionId)
|
|
70
|
+
return p.request;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
},
|
|
46
74
|
dispose() {
|
|
47
75
|
// dev 热重载重建前调用:所有 pending settle 为 deny(避免悬空 Promise +
|
|
48
76
|
// timer 泄漏),clearTimeout 后清空 Map。
|
|
@@ -54,4 +82,4 @@ function createPermissionStore(opts = {}) {
|
|
|
54
82
|
},
|
|
55
83
|
};
|
|
56
84
|
}
|
|
57
|
-
export { createPermissionStore, DEFAULT_PERMISSION_TIMEOUT_MS };
|
|
85
|
+
export { createPermissionStore, DEFAULT_PERMISSION_EXPIRE_GRACE_MS, DEFAULT_PERMISSION_TIMEOUT_MS };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Registry } from '../llm/registry.js';
|
|
2
|
+
import type { Config } from '../shared/types/config.js';
|
|
3
|
+
import type { ProviderConfig } from '../shared/types/llm.js';
|
|
4
|
+
/** 把 config.providers 注册到新建的 LLM registry(修复此前空 registry 的遗漏)。 */
|
|
5
|
+
declare function buildRegistryFromConfig(config: Config): Registry;
|
|
6
|
+
declare function registerProviderFromConfig(registry: Registry, p: ProviderConfig): void;
|
|
7
|
+
/**
|
|
8
|
+
* config 变更后原子地同步 registry:在隔离的 next registry 上重建全部路由,
|
|
9
|
+
* 完成后一次性替换 registry 内部 table 引用。运行中的 resolveRoute 任何时刻
|
|
10
|
+
* 看到的都是完整的旧表或完整的新表,不会读到「已清空但未注册完」的半状态,
|
|
11
|
+
* 因此不会把本可用的 provider 误判为 NoRoute。ServerContext 立即生效,无需重启。
|
|
12
|
+
*/
|
|
13
|
+
declare function syncRegistryFromConfig(registry: Registry, config: Config): void;
|
|
14
|
+
export { buildRegistryFromConfig, registerProviderFromConfig, syncRegistryFromConfig };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/server/registry-config.ts
|
|
2
|
+
// config.providers ↔ LLM registry 的构建/同步逻辑(P1-1 从 server.ts 拆出,
|
|
3
|
+
// 供 chat 路由按项目配置构建注册表,避免 routes → server.ts 循环依赖)。
|
|
4
|
+
import { decryptSecret } from '../core/secret.js';
|
|
5
|
+
import { createRegistry, overrideToCapabilities, rebuildRegistry, registerProvider, } from '../llm/registry.js';
|
|
6
|
+
/** 把 config.providers 注册到新建的 LLM registry(修复此前空 registry 的遗漏)。 */
|
|
7
|
+
function buildRegistryFromConfig(config) {
|
|
8
|
+
const registry = createRegistry();
|
|
9
|
+
for (const p of config.providers) {
|
|
10
|
+
registerProviderFromConfig(registry, p);
|
|
11
|
+
}
|
|
12
|
+
return registry;
|
|
13
|
+
}
|
|
14
|
+
function registerProviderFromConfig(registry, p) {
|
|
15
|
+
// 兼容 config.json 中以 _tag 标识 provider 的格式(name 缺失时回退到 _tag)
|
|
16
|
+
const name = p.name || p._tag;
|
|
17
|
+
if (!name || !p.baseURL)
|
|
18
|
+
return;
|
|
19
|
+
// baseURL 已含 /v1 时用 /chat/completions,避免 /v1/v1 双重前缀
|
|
20
|
+
const path = p.baseURL.replace(/\/+$/, '').endsWith('/v1') ? '/chat/completions' : undefined;
|
|
21
|
+
registerProvider(registry, {
|
|
22
|
+
name,
|
|
23
|
+
baseURL: p.baseURL,
|
|
24
|
+
apiKey: p.apiKey ? decryptSecret(p.apiKey) : p.apiKey,
|
|
25
|
+
...(path ? { path } : {}),
|
|
26
|
+
// 传递用户配置的 per-model capabilities(contextWindow 等),
|
|
27
|
+
// 否则 resolveRoute 回退到 DEFAULT_MODEL_CAPABILITIES,可能导致预算过小。
|
|
28
|
+
...(p.models ? { models: overrideToCapabilities(p.models) } : {}),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* config 变更后原子地同步 registry:在隔离的 next registry 上重建全部路由,
|
|
33
|
+
* 完成后一次性替换 registry 内部 table 引用。运行中的 resolveRoute 任何时刻
|
|
34
|
+
* 看到的都是完整的旧表或完整的新表,不会读到「已清空但未注册完」的半状态,
|
|
35
|
+
* 因此不会把本可用的 provider 误判为 NoRoute。ServerContext 立即生效,无需重启。
|
|
36
|
+
*/
|
|
37
|
+
function syncRegistryFromConfig(registry, config) {
|
|
38
|
+
rebuildRegistry(registry, (next) => {
|
|
39
|
+
for (const p of config.providers) {
|
|
40
|
+
registerProviderFromConfig(next, p);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export { buildRegistryFromConfig, registerProviderFromConfig, syncRegistryFromConfig };
|
|
@@ -10,8 +10,20 @@
|
|
|
10
10
|
// GET /api/auth/pairing 列出待审批配对
|
|
11
11
|
// POST /api/auth/pairing/approve 审批通过(签发设备 token)
|
|
12
12
|
// POST /api/auth/pairing/deny 拒绝配对
|
|
13
|
+
// GET /api/auth/devices 列出已授权设备
|
|
14
|
+
// DELETE /api/auth/devices/:id 撤销设备(立即生效)
|
|
13
15
|
import { Hono } from 'hono';
|
|
14
16
|
import { apiError } from '../middleware/error.js';
|
|
17
|
+
/** 尽力而为的请求来源:x-forwarded-for 首跳(反代场景),否则视为本地回环。仅展示/软限流。 */
|
|
18
|
+
function requestSource(c) {
|
|
19
|
+
const fwd = c.req.header('x-forwarded-for');
|
|
20
|
+
if (fwd) {
|
|
21
|
+
const first = fwd.split(',')[0]?.trim();
|
|
22
|
+
if (first)
|
|
23
|
+
return first;
|
|
24
|
+
}
|
|
25
|
+
return 'local';
|
|
26
|
+
}
|
|
15
27
|
function createAuthRoute(ctx) {
|
|
16
28
|
const app = new Hono();
|
|
17
29
|
// 首设备注册:凭 bootstrap token(URL ?token=)换发设备 token,bootstrap 随即轮换失效。
|
|
@@ -28,7 +40,8 @@ function createAuthRoute(ctx) {
|
|
|
28
40
|
// bootstrap 已失效/已注册过设备 → 引导走配对审批
|
|
29
41
|
return apiError(c, 403, 'BOOTSTRAP_CONSUMED', 'bootstrap token 已失效(已有设备注册)。请在新设备发起配对,由已授权设备审批。');
|
|
30
42
|
}
|
|
31
|
-
|
|
43
|
+
// P1-3:回传注册成功的设备名,前端展示一次性确认(用户可核对注册的是否自己)。
|
|
44
|
+
return c.json({ deviceToken, deviceName: body.deviceName ?? '设备' });
|
|
32
45
|
});
|
|
33
46
|
// 新设备发起配对请求(公开)。
|
|
34
47
|
app.post('/pairing/request', async (c) => {
|
|
@@ -36,7 +49,7 @@ function createAuthRoute(ctx) {
|
|
|
36
49
|
return apiError(c, 400, 'AUTH_DISABLED', '认证未启用');
|
|
37
50
|
}
|
|
38
51
|
const body = (await c.req.json().catch(() => ({})));
|
|
39
|
-
const result = ctx.authManager.requestPairing(body.deviceName ?? '新设备');
|
|
52
|
+
const result = ctx.authManager.requestPairing(body.deviceName ?? '新设备', requestSource(c));
|
|
40
53
|
if (!result) {
|
|
41
54
|
return apiError(c, 429, 'PAIRING_LIMIT', '待审批的配对请求过多,请稍后再试');
|
|
42
55
|
}
|
|
@@ -89,6 +102,23 @@ function createAuthRoute(ctx) {
|
|
|
89
102
|
return apiError(c, 404, 'PAIRING_NOT_FOUND', '配对请求不存在或已过期');
|
|
90
103
|
return c.json({ ok: true });
|
|
91
104
|
});
|
|
105
|
+
// 列出已授权设备(需认证;设置页「已授权设备」面板)。
|
|
106
|
+
app.get('/devices', (c) => {
|
|
107
|
+
if (!ctx.authManager) {
|
|
108
|
+
return apiError(c, 400, 'AUTH_DISABLED', '认证未启用');
|
|
109
|
+
}
|
|
110
|
+
return c.json({ devices: ctx.authManager.listDevices() });
|
|
111
|
+
});
|
|
112
|
+
// 撤销设备(需认证)。撤销唯一设备后如需重新注册首设备,请运行 c0de auth reset。
|
|
113
|
+
app.delete('/devices/:id', async (c) => {
|
|
114
|
+
if (!ctx.authManager) {
|
|
115
|
+
return apiError(c, 400, 'AUTH_DISABLED', '认证未启用');
|
|
116
|
+
}
|
|
117
|
+
const ok = ctx.authManager.revokeDevice(c.req.param('id'));
|
|
118
|
+
if (!ok)
|
|
119
|
+
return apiError(c, 404, 'NOT_FOUND', 'Device not found');
|
|
120
|
+
return c.json({ ok: true });
|
|
121
|
+
});
|
|
92
122
|
return app;
|
|
93
123
|
}
|
|
94
124
|
export { createAuthRoute };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
+
import { type SSEStreamingApi } from 'hono/streaming';
|
|
2
3
|
import type { ServerContext } from '../types.js';
|
|
3
4
|
/** 按 session.projectId / worktreePath 解析 agent 工作目录:
|
|
4
5
|
* - 有 projectId → 项目 worktree(缺失/目录失效时抛错,由上层转 409)。
|
|
@@ -9,5 +10,16 @@ export declare function resolveAgentCwd(ctx: ServerContext, session: {
|
|
|
9
10
|
projectId: string | null;
|
|
10
11
|
worktreePath?: string | null;
|
|
11
12
|
}): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* SSE 心跳间隔。前端有 90s 静默看门狗(web/services/chat.ts),而服务端各
|
|
15
|
+
* 等待语义远长于此:bash 默认超时 120s、权限确认 5 分钟、子 agent 执行数分钟。
|
|
16
|
+
* 这些等待期间没有业务事件帧——若无心跳,前端会在 90s 时 abort 连接,
|
|
17
|
+
* 服务端 stream.onAbort 随即中止 agent,长命令/权限弹窗/多 agent 全部误杀。
|
|
18
|
+
* 30s 心跳保证看门狗只对「连接真正死亡」(连续 3 个心跳周期无数据)生效。
|
|
19
|
+
*/
|
|
20
|
+
declare const SSE_HEARTBEAT_INTERVAL_MS = 30000;
|
|
21
|
+
/** 启动心跳定时器;返回停止函数(幂等)。流关闭后写入会被吞掉,安全。 */
|
|
22
|
+
declare function startHeartbeat(stream: SSEStreamingApi): () => void;
|
|
23
|
+
export { SSE_HEARTBEAT_INTERVAL_MS, startHeartbeat };
|
|
12
24
|
declare function createChatRoute(ctx: ServerContext): Hono;
|
|
13
25
|
export { createChatRoute };
|