c0de-agent 1.8.0 → 1.10.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.
Files changed (96) hide show
  1. package/README.md +21 -7
  2. package/dist/cli/commands/acp.js +2 -0
  3. package/dist/cli/commands/chat.js +2 -0
  4. package/dist/cli/commands/config.js +8 -4
  5. package/dist/cli/commands/serve.d.ts +1 -0
  6. package/dist/cli/commands/serve.js +4 -1
  7. package/dist/cli/deps.d.ts +7 -5
  8. package/dist/cli/deps.js +23 -7
  9. package/dist/cli/index.d.ts +1 -1
  10. package/dist/cli/index.js +68 -37
  11. package/dist/cli/modes/print.d.ts +3 -1
  12. package/dist/cli/modes/print.js +21 -4
  13. package/dist/core/agent.js +3 -4
  14. package/dist/core/config.d.ts +18 -1
  15. package/dist/core/config.js +55 -4
  16. package/dist/core/loop/compaction.d.ts +31 -0
  17. package/dist/core/loop/compaction.js +137 -0
  18. package/dist/core/loop/persist.d.ts +11 -0
  19. package/dist/core/loop/persist.js +107 -0
  20. package/dist/core/loop/segment.d.ts +8 -0
  21. package/dist/core/loop/segment.js +72 -0
  22. package/dist/core/loop/stream-collect.d.ts +47 -0
  23. package/dist/core/loop/stream-collect.js +183 -0
  24. package/dist/core/loop/subagent.d.ts +12 -0
  25. package/dist/core/loop/subagent.js +171 -0
  26. package/dist/core/loop/todo.d.ts +5 -0
  27. package/dist/core/loop/todo.js +26 -0
  28. package/dist/core/loop.d.ts +2 -20
  29. package/dist/core/loop.js +13 -659
  30. package/dist/core/prompt-registry.d.ts +1 -1
  31. package/dist/core/prompt-registry.js +10 -0
  32. package/dist/core/slash.js +1 -1
  33. package/dist/core/todo-tags.d.ts +46 -0
  34. package/dist/core/todo-tags.js +192 -0
  35. package/dist/core/workflows/builtins.js +4 -4
  36. package/dist/core/workflows/discovery.d.ts +1 -1
  37. package/dist/core/workflows/discovery.js +4 -1
  38. package/dist/core/workflows/index.d.ts +1 -1
  39. package/dist/db/schema.d.ts +17 -0
  40. package/dist/db/schema.js +3 -1
  41. package/dist/llm/registry.d.ts +30 -3
  42. package/dist/llm/registry.js +25 -7
  43. package/dist/llm/retry.d.ts +4 -2
  44. package/dist/llm/retry.js +7 -6
  45. package/dist/llm/schema/errors.d.ts +13 -3
  46. package/dist/llm/schema/errors.js +35 -3
  47. package/dist/llm/transport.js +5 -1
  48. package/dist/project/resolve.js +2 -2
  49. package/dist/server/agent-manager.d.ts +15 -0
  50. package/dist/server/agent-manager.js +35 -11
  51. package/dist/server/app.js +2 -1
  52. package/dist/server/context.js +2 -0
  53. package/dist/server/middleware/cors.d.ts +11 -2
  54. package/dist/server/middleware/cors.js +15 -12
  55. package/dist/server/routes/chat.js +212 -145
  56. package/dist/server/routes/config.js +7 -3
  57. package/dist/server/routes/session.js +6 -2
  58. package/dist/server/routes/terminal.js +12 -0
  59. package/dist/server/routes/todo.js +5 -2
  60. package/dist/server/routes/update.d.ts +1 -0
  61. package/dist/server/routes/update.js +11 -4
  62. package/dist/server/routes/workflows.js +2 -2
  63. package/dist/server/server.d.ts +28 -3
  64. package/dist/server/server.js +127 -38
  65. package/dist/server/types.d.ts +9 -1
  66. package/dist/session/branch.d.ts +10 -2
  67. package/dist/session/branch.js +98 -29
  68. package/dist/session/compaction.js +1 -0
  69. package/dist/session/context.d.ts +3 -2
  70. package/dist/session/context.js +36 -3
  71. package/dist/session/snapshot.d.ts +9 -3
  72. package/dist/session/snapshot.js +45 -4
  73. package/dist/session/squash.js +32 -24
  74. package/dist/session/types.d.ts +2 -0
  75. package/dist/shared/types/agent.d.ts +11 -0
  76. package/dist/shared/types/config.d.ts +7 -6
  77. package/dist/shared/utils/path.d.ts +2 -3
  78. package/dist/shared/utils/path.js +20 -4
  79. package/dist/tools/builtin/todo.d.ts +7 -0
  80. package/dist/tools/builtin/todo.js +28 -20
  81. package/dist/tools/index.d.ts +14 -3
  82. package/dist/tools/index.js +36 -14
  83. package/dist/update/index.d.ts +1 -1
  84. package/dist/update/index.js +1 -1
  85. package/dist/update/ipc.d.ts +32 -5
  86. package/dist/update/ipc.js +53 -7
  87. package/dist/update/updater.d.ts +7 -3
  88. package/dist/update/updater.js +14 -6
  89. package/dist/update/version.d.ts +6 -1
  90. package/dist/update/version.js +18 -3
  91. package/drizzle/0005_snapshot_mtime.sql +1 -0
  92. package/drizzle/meta/0005_snapshot.json +855 -0
  93. package/drizzle/meta/_journal.json +7 -0
  94. package/package.json +1 -7
  95. package/dist/mcp/index.d.ts +0 -1
  96. package/dist/mcp/index.js +0 -3
@@ -33,7 +33,11 @@ const sseFraming = async function* (stream) {
33
33
  }
34
34
  }
35
35
  finally {
36
- reader.releaseLock();
36
+ // Cancel the underlying transport and release the exclusive lock on every
37
+ // exit path (normal completion, thrown error, or early consumer break).
38
+ // cancel() rejects on an already-closed/errored stream — swallow that so it
39
+ // never masks the original error propagating through the generator.
40
+ await reader.cancel().catch(() => { });
37
41
  }
38
42
  };
39
43
  /** Join all `data:` lines of an SSE block into one string. */
@@ -59,14 +59,14 @@ export function getGitStatus(cwd) {
59
59
  let i = 0;
60
60
  while (i < tokens.length) {
61
61
  const token = tokens[i];
62
- if (token === '')
62
+ if (!token)
63
63
  break; // 末尾空 token
64
64
  const xy = token.slice(0, 2);
65
65
  const code = classifyStatus(xy);
66
66
  const isRename = xy[0] === 'R' || xy[0] === 'C';
67
67
  // 重命名/复制:"XY oldpath\0newpath",状态挂在 newpath
68
68
  if (isRename && i + 1 < tokens.length) {
69
- const newPath = tokens[i + 1];
69
+ const newPath = tokens[i + 1] ?? '';
70
70
  map[normalizePath(newPath)] = code;
71
71
  i += 2;
72
72
  }
@@ -14,9 +14,24 @@ type ActiveRun = {
14
14
  };
15
15
  /** Agent run 跟踪器 + 控制操作。 */
16
16
  type AgentManager = {
17
+ /**
18
+ * 同步原子占位(P0-4 POST /api/chat 并发守卫):sessionId 无条目时立即占位并返回
19
+ * true;已有条目(占位或活跃 run)时返回 false。has+set 一气呵成、全程无 await,
20
+ * 杜绝 check-then-act 竞态——此前守卫用 get 检查、真正注册在 SSE 回调内,中间隔
21
+ * 多个 await,双发 POST 均通过守卫后后注册覆盖前者(runs.set),且先结束者在
22
+ * finally unregister 时误删仍在跑的 run,abort/steer/pause 全部失效。
23
+ * 占位由 register 填充、unregister 释放,二者均幂等。
24
+ */
25
+ tryAcquire(sessionId: string): boolean;
26
+ /** 注册活跃 run;若该 sessionId 已被 tryAcquire 占位则填充该占位。 */
17
27
  register(run: ActiveRun): void;
28
+ /** 取活跃 run;占位期间(register 前)返回 undefined,视为无活跃 run。 */
18
29
  get(sessionId: string): ActiveRun | undefined;
30
+ /** 该 sessionId 是否处于占位状态(tryAcquire 后、register 前)。 */
31
+ isStarting(sessionId: string): boolean;
32
+ /** 释放条目(占位或活跃 run);幂等。 */
19
33
  unregister(sessionId: string): void;
34
+ /** 活跃 run 数(不含占位)。 */
20
35
  size(): number;
21
36
  abort(sessionId: string): boolean;
22
37
  pause(sessionId: string): boolean;
@@ -1,59 +1,83 @@
1
1
  // src/server/agent-manager.ts
2
2
  import { abortAgent, injectSteering, pauseAgent, resumeAgent } from '../core/index.js';
3
+ /** 取 slot 内的活跃 run;占位('starting')或不存在返回 undefined。 */
4
+ function slotRun(slot) {
5
+ return slot?.kind === 'run' ? slot.run : undefined;
6
+ }
3
7
  function createAgentManager() {
4
8
  const runs = new Map();
5
9
  return {
10
+ tryAcquire(sessionId) {
11
+ if (runs.has(sessionId))
12
+ return false;
13
+ runs.set(sessionId, { kind: 'starting' });
14
+ return true;
15
+ },
6
16
  register(run) {
7
- runs.set(run.sessionId, run);
17
+ runs.set(run.sessionId, { kind: 'run', run });
8
18
  },
9
19
  get(sessionId) {
10
- return runs.get(sessionId);
20
+ return slotRun(runs.get(sessionId));
21
+ },
22
+ isStarting(sessionId) {
23
+ return runs.get(sessionId)?.kind === 'starting';
11
24
  },
12
25
  unregister(sessionId) {
13
26
  runs.delete(sessionId);
14
27
  },
15
28
  size() {
16
- return runs.size;
29
+ let n = 0;
30
+ for (const slot of runs.values()) {
31
+ if (slot.kind === 'run')
32
+ n += 1;
33
+ }
34
+ return n;
17
35
  },
18
36
  abort(sessionId) {
19
- const run = runs.get(sessionId);
37
+ const run = slotRun(runs.get(sessionId));
20
38
  if (!run)
21
39
  return false;
22
40
  abortAgent(run.state);
23
41
  return true;
24
42
  },
25
43
  pause(sessionId) {
26
- const run = runs.get(sessionId);
44
+ const run = slotRun(runs.get(sessionId));
27
45
  if (!run)
28
46
  return false;
29
47
  pauseAgent(run.state);
30
48
  return true;
31
49
  },
32
50
  resume(sessionId) {
33
- const run = runs.get(sessionId);
51
+ const run = slotRun(runs.get(sessionId));
34
52
  if (!run)
35
53
  return false;
36
54
  resumeAgent(run.state);
37
55
  return true;
38
56
  },
39
57
  steer(sessionId, message) {
40
- const run = runs.get(sessionId);
58
+ const run = slotRun(runs.get(sessionId));
41
59
  if (!run)
42
60
  return false;
43
61
  injectSteering(run.state, message);
44
62
  return true;
45
63
  },
46
64
  children(parentSessionId) {
47
- return Array.from(runs.values()).filter((r) => r.parentSessionId === parentSessionId);
65
+ return Array.from(runs.values())
66
+ .map((slot) => slotRun(slot))
67
+ .filter((r) => r?.parentSessionId === parentSessionId);
48
68
  },
49
69
  backgroundJobs() {
50
- return Array.from(runs.values()).filter((r) => r.jobId !== undefined);
70
+ return Array.from(runs.values())
71
+ .map((slot) => slotRun(slot))
72
+ .filter((r) => r?.jobId !== undefined);
51
73
  },
52
74
  dispose() {
53
75
  // dev 热重载重建前调用:中止所有活跃 run(loop 在 turn/流边界检测 signal
54
76
  // → unwind → 调用方 finally 持久化 + unregister),然后清空 Map。
55
- for (const run of runs.values()) {
56
- abortAgent(run.state);
77
+ for (const slot of runs.values()) {
78
+ const run = slotRun(slot);
79
+ if (run)
80
+ abortAgent(run.state);
57
81
  }
58
82
  runs.clear();
59
83
  },
@@ -32,7 +32,8 @@ function createApp(ctx) {
32
32
  // spec §24.2:仅允许本地/可信 origin 跨域读,拒绝外部网页。
33
33
  app.use('*', createCORSMiddleware({ allowedOrigins: ctx.config.security.allowedOrigins }));
34
34
  // spec §24.2:Bearer token 认证(配置了 token 时生效;/api/health 探活放行)。
35
- app.use('/api/*', createAuthMiddleware(ctx.config.security.authEnabled ? ctx.config.security.token : undefined));
35
+ // token 来源:用户配置 security.token,或启动时自动生成(ctx.authToken)。
36
+ app.use('/api/*', createAuthMiddleware(ctx.authToken));
36
37
  // 路由
37
38
  app.route('/api/health', createHealthRoute());
38
39
  app.route('/api/agents', createAgentRoute(ctx));
@@ -35,6 +35,8 @@ function createServerContext(opts) {
35
35
  agentManager: createAgentManager(),
36
36
  permissionStore: createPermissionStore(),
37
37
  permissionMode: config.permission.defaultMode,
38
+ // 测试工厂不解析/生成 token(不触碰用户全局数据目录)。
39
+ authToken: undefined,
38
40
  agentRegistry,
39
41
  // 工作流注册表:惰性初始化,只含内置(测试工厂;生产路径走 buildServerContext)。
40
42
  get workflowRegistry() {
@@ -1,10 +1,19 @@
1
1
  import type { MiddlewareHandler } from 'hono';
2
2
  /**
3
3
  * 判断 origin 是否为本地/可信来源(spec §24.2「CORS 限制本地 origin」)。
4
- * 允许:本机回环任意端口、file://(PWA 安装后)、浏览器扩展、'null'
5
- * (file 协议/隐私沙箱发出的 Origin 头)。
4
+ *
5
+ * 仅允许本机回环任意端口 + 显式配置的 allowedOrigins。
6
+ * 显式不放行 'null'(sandboxed iframe)、file:// 与浏览器扩展前缀——
7
+ * 任意网页/扩展均可伪造这些来源,等价于完全关闭 CORS 防护。
8
+ * 认证未启用时(authEnabled=false 的显式选择),本函数仍是唯一的跨域读取防线。
6
9
  */
7
10
  export declare function isLocalOrigin(origin: string): boolean;
11
+ /**
12
+ * Origin 是否被允许(本地回环 + 配置的额外 origin)。
13
+ * 供 CORS 中间件与 WebSocket 升级路径共用——浏览器对 WS 不实施 CORS,
14
+ * 但会携带 Origin 头,必须在服务端显式校验。
15
+ */
16
+ export declare function isAllowedOrigin(origin: string, allowedOrigins?: string[]): boolean;
8
17
  export type CORSOptions = {
9
18
  /** 额外允许的 origin(如远程访问场景配置的域名)。 */
10
19
  allowedOrigins?: string[];
@@ -2,23 +2,26 @@
2
2
  const LOCAL_ORIGIN = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/;
3
3
  /**
4
4
  * 判断 origin 是否为本地/可信来源(spec §24.2「CORS 限制本地 origin」)。
5
- * 允许:本机回环任意端口、file://(PWA 安装后)、浏览器扩展、'null'
6
- * (file 协议/隐私沙箱发出的 Origin 头)。
5
+ *
6
+ * 仅允许本机回环任意端口 + 显式配置的 allowedOrigins。
7
+ * 显式不放行 'null'(sandboxed iframe)、file:// 与浏览器扩展前缀——
8
+ * 任意网页/扩展均可伪造这些来源,等价于完全关闭 CORS 防护。
9
+ * 认证未启用时(authEnabled=false 的显式选择),本函数仍是唯一的跨域读取防线。
7
10
  */
8
11
  export function isLocalOrigin(origin) {
9
12
  if (!origin)
10
13
  return false;
11
- if (LOCAL_ORIGIN.test(origin))
12
- return true;
13
- if (origin === 'null')
14
- return true;
15
- if (origin.startsWith('file://'))
16
- return true;
17
- if (origin.startsWith('chrome-extension://'))
18
- return true;
19
- if (origin.startsWith('moz-extension://'))
14
+ return LOCAL_ORIGIN.test(origin);
15
+ }
16
+ /**
17
+ * Origin 是否被允许(本地回环 + 配置的额外 origin)。
18
+ * 供 CORS 中间件与 WebSocket 升级路径共用——浏览器对 WS 不实施 CORS,
19
+ * 但会携带 Origin 头,必须在服务端显式校验。
20
+ */
21
+ export function isAllowedOrigin(origin, allowedOrigins) {
22
+ if (isLocalOrigin(origin))
20
23
  return true;
21
- return false;
24
+ return allowedOrigins?.includes(origin) ?? false;
22
25
  }
23
26
  /**
24
27
  * CORS 中间件:仅对本地/可信 origin 回显 Access-Control-Allow-Origin,