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
@@ -1,17 +1,45 @@
1
+ import { timingSafeEqual } from 'node:crypto';
1
2
  import { createServer } from 'node:http';
2
- /** 旧实例启动时创建 handoff 端点。收到 POST /handoff 即触发 onHandoff(序列化 + 退出)。 */
3
- function createHandoffServer(onHandoff) {
3
+ /**
4
+ * 恒时比较 Authorization 头与期望值:字符串直接比较可被时序攻击逐字符探测。
5
+ * 长度不等直接 false——timingSafeEqual 要求等长 buffer,先比较长度本身
6
+ * 不泄露内容信息。
7
+ */
8
+ function authHeaderMatches(actual, expected) {
9
+ if (typeof actual !== 'string' || actual.length !== expected.length)
10
+ return false;
11
+ return timingSafeEqual(Buffer.from(actual), Buffer.from(expected));
12
+ }
13
+ /**
14
+ * 旧实例启动时创建 handoff 端点。收到 POST /handoff 即触发 onHandoff
15
+ * (关闭主服务与资源),响应 200;exitAfterResponse 时随后退出进程。
16
+ */
17
+ function createHandoffServer(onHandoff, opts = {}) {
4
18
  return new Promise((resolve, reject) => {
19
+ const expected = opts.expectedToken && opts.expectedToken.length > 0
20
+ ? `Bearer ${opts.expectedToken}`
21
+ : '';
5
22
  const handler = async (req, res) => {
6
23
  if (req.method === 'POST' && req.url === '/handoff') {
24
+ if (expected && !authHeaderMatches(req.headers.authorization, expected)) {
25
+ res.writeHead(401, { 'content-type': 'application/json' });
26
+ res.end(JSON.stringify({ ok: false, error: 'unauthorized' }));
27
+ return;
28
+ }
7
29
  try {
8
30
  await onHandoff();
9
31
  res.writeHead(200, { 'content-type': 'application/json' });
10
32
  res.end(JSON.stringify({ ok: true }));
33
+ // 仅 200 才调度退出:500 时 exit(0) 会掩盖让渡失败——保留进程供
34
+ // 诊断/重试,由下方错误日志暴露 onHandoff 失败原因。
35
+ if (opts.exitAfterResponse) {
36
+ setTimeout(() => process.exit(0), 250);
37
+ }
11
38
  }
12
39
  catch (error) {
13
40
  res.writeHead(500, { 'content-type': 'application/json' });
14
41
  res.end(JSON.stringify({ ok: false, error: String(error) }));
42
+ console.error('[handoff] onHandoff failed, keeping process alive:', error);
15
43
  }
16
44
  return;
17
45
  }
@@ -32,10 +60,28 @@ function createHandoffServer(onHandoff) {
32
60
  });
33
61
  });
34
62
  }
35
- /** 新实例请求旧实例让出端口(graceful shutdown)。失败抛错。 */
36
- async function requestHandoff(port, host = '127.0.0.1') {
37
- const res = await fetch(`http://${host}:${port}/handoff`, { method: 'POST' });
38
- if (!res.ok)
39
- throw new Error(`handoff failed: HTTP ${res.status}`);
63
+ /** 新实例请求旧实例让出端口(graceful shutdown)。失败抛可区分的 HandoffError。 */
64
+ async function requestHandoff(port, host = '127.0.0.1', token) {
65
+ let res;
66
+ try {
67
+ res = await fetch(`http://${host}:${port}/handoff`, {
68
+ method: 'POST',
69
+ ...(token ? { headers: { Authorization: `Bearer ${token}` } } : {}),
70
+ });
71
+ }
72
+ catch (err) {
73
+ // 连接层失败(ECONNREFUSED 等):旧实例无 handoff 端点。抛 kind='connect'
74
+ // 让调用方(requestHandoffWithRetry)区分处理——直接尝试绑端口。
75
+ const error = new Error(`handoff connect failed: ${err instanceof Error ? err.message : String(err)}`);
76
+ error.kind = 'connect';
77
+ throw error;
78
+ }
79
+ if (!res.ok) {
80
+ // 收到 HTTP 响应但非 2xx(如 401 token 不匹配、500 onHandoff 失败)。
81
+ const error = new Error(`handoff failed: HTTP ${res.status}`);
82
+ error.kind = 'http';
83
+ error.status = res.status;
84
+ throw error;
85
+ }
40
86
  }
41
87
  export { createHandoffServer, requestHandoff };
@@ -16,8 +16,9 @@ type HotUpdateResult = {
16
16
  * 启动新实例的函数签名。
17
17
  * @param snapshotPath 快照文件路径(旧实例已写入)
18
18
  * @param argv 传给新进程的完整参数(含 restore flag + 可选 handoff flag)
19
+ * @param opts 热更新选项(authToken 等经环境变量传递给新实例)
19
20
  */
20
- type SpawnFn = (snapshotPath: string, argv: string[]) => Promise<void>;
21
+ type SpawnFn = (snapshotPath: string, argv: string[], opts?: HotUpdateOptions) => Promise<void>;
21
22
  type HotUpdateOptions = {
22
23
  /** 自更新安装函数;默认 `npm install -g <pkg>`。 */
23
24
  installFn?: (pkg: string) => Promise<void>;
@@ -30,6 +31,10 @@ type HotUpdateOptions = {
30
31
  restoreFlag?: string;
31
32
  /** 旧实例 handoff 端口;提供时新实例启动后请求旧实例 graceful shutdown。 */
32
33
  handoffPort?: number;
34
+ /** 旧实例主服务端口;新实例 --port 复用(保证接管同一地址)。 */
35
+ port?: number;
36
+ /** 传给新实例的认证 token(经环境变量 C0DE_AUTH_TOKEN),保证新旧实例握手同 token。 */
37
+ authToken?: string;
33
38
  };
34
39
  /**
35
40
  * 执行热更新(spec §18.1-18.2 流程):
@@ -38,8 +43,7 @@ type HotUpdateOptions = {
38
43
  * 3. detached 启动新版本实例,传入快照路径(新实例 restore + 端口接管)
39
44
  *
40
45
  * 注:旧实例的 graceful shutdown / 端口让渡由新实例启动后通过 IPC 协调
41
- * (见 ipc.ts),本函数只负责"序列化 + 安装 + 接力"。若提供 handoffPort,
42
- * argv 会带上 --handoff-port,新实例据此 requestHandoff 通知旧实例退出。
46
+ * (见 ipc.ts),本函数只负责"序列化 + 安装 + 接力"。
43
47
  */
44
48
  declare function performHotUpdate(snapshot: SessionSnapshot, opts?: HotUpdateOptions): Promise<HotUpdateResult>;
45
49
  /** 清理快照临时目录(热更新完成或放弃后调用)。 */
@@ -16,13 +16,17 @@ function defaultInstall(pkg) {
16
16
  });
17
17
  });
18
18
  }
19
- /** 默认启动新实例:detached spawn,argv 全部透传给新进程。 */
20
- function defaultSpawn(_snapshotPath, argv) {
19
+ /** 默认启动新实例:detached spawn CLI bin,argv = serve <restore> <handoff>。
20
+ * 必须以 `serve` 子命令开头(CLI dispatch 按首参选命令);
21
+ * --port 复用旧实例端口,--handoff-port 让新实例启动后请求旧实例退出。
22
+ * 认证 token 经环境变量传递(不落 argv,避免 ps 泄漏)。 */
23
+ function defaultSpawn(_snapshotPath, argv, opts) {
21
24
  return new Promise((resolve, reject) => {
22
25
  try {
23
26
  const child = spawn(process.argv0, [process.argv[1] ?? '', ...argv], {
24
27
  detached: true,
25
28
  stdio: 'ignore',
29
+ ...(opts?.authToken ? { env: { ...process.env, C0DE_AUTH_TOKEN: opts.authToken } } : {}),
26
30
  });
27
31
  child.unref();
28
32
  resolve();
@@ -39,8 +43,7 @@ function defaultSpawn(_snapshotPath, argv) {
39
43
  * 3. detached 启动新版本实例,传入快照路径(新实例 restore + 端口接管)
40
44
  *
41
45
  * 注:旧实例的 graceful shutdown / 端口让渡由新实例启动后通过 IPC 协调
42
- * (见 ipc.ts),本函数只负责"序列化 + 安装 + 接力"。若提供 handoffPort,
43
- * argv 会带上 --handoff-port,新实例据此 requestHandoff 通知旧实例退出。
46
+ * (见 ipc.ts),本函数只负责"序列化 + 安装 + 接力"。
44
47
  */
45
48
  async function performHotUpdate(snapshot, opts = {}) {
46
49
  const pkg = opts.packageName ?? DEFAULT_PACKAGE;
@@ -59,13 +62,18 @@ async function performHotUpdate(snapshot, opts = {}) {
59
62
  };
60
63
  }
61
64
  const restoreFlag = opts.restoreFlag ?? '--restore';
62
- const argv = [restoreFlag, snapshotPath];
65
+ const argv = [
66
+ 'serve',
67
+ restoreFlag,
68
+ snapshotPath,
69
+ ...(opts.port !== undefined ? ['--port', String(opts.port)] : []),
70
+ ];
63
71
  if (opts.handoffPort !== undefined) {
64
72
  argv.push('--handoff-port', String(opts.handoffPort));
65
73
  }
66
74
  const spawnNew = opts.spawnNewInstanceFn ?? defaultSpawn;
67
75
  try {
68
- await spawnNew(snapshotPath, argv);
76
+ await spawnNew(snapshotPath, argv, opts);
69
77
  }
70
78
  catch (error) {
71
79
  return {
@@ -10,9 +10,14 @@ type CheckOptions = {
10
10
  currentVersion?: string;
11
11
  registryUrl?: string;
12
12
  };
13
+ /**
14
+ * 读取当前安装版本(package.json 的 version 字段)。
15
+ * src/dev 与 dist 两种布局下 `../../package.json` 都指向包根;读取失败回退常量。
16
+ */
17
+ declare function getCurrentVersion(): string;
13
18
  /** 语义化版本比较:a < b → -1,a == b → 0,a > b → 1。忽略前导 v 和 prerelease。 */
14
19
  declare function compareSemver(a: string, b: string): number;
15
20
  /** 查询 npm registry 判断是否有新版本。网络/解析失败时返回 hasUpdate:false,绝不抛错。 */
16
21
  declare function checkForUpdate(opts?: CheckOptions): Promise<UpdateCheckResult>;
17
22
  export type { CheckOptions, UpdateCheckResult };
18
- export { checkForUpdate, compareSemver };
23
+ export { checkForUpdate, compareSemver, getCurrentVersion };
@@ -1,7 +1,22 @@
1
- // 版本检查(spec §18.1):查询 npm registry 比对当前版本。
1
+ import { readFileSync } from 'node:fs';
2
2
  const DEFAULT_PACKAGE = 'c0de-agent';
3
3
  const DEFAULT_VERSION = '0.1.0';
4
4
  const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
5
+ /**
6
+ * 读取当前安装版本(package.json 的 version 字段)。
7
+ * src/dev 与 dist 两种布局下 `../../package.json` 都指向包根;读取失败回退常量。
8
+ */
9
+ function getCurrentVersion() {
10
+ try {
11
+ const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf-8'));
12
+ if (typeof pkg.version === 'string' && pkg.version.length > 0)
13
+ return pkg.version;
14
+ }
15
+ catch {
16
+ // 打包布局异常:回退保守常量
17
+ }
18
+ return DEFAULT_VERSION;
19
+ }
5
20
  /** 取 semver 的数值核心(去前导 v 与 prerelease)。 */
6
21
  function semverCore(v) {
7
22
  return (v.replace(/^v/, '').split('-')[0] ?? '').split('.').map(Number);
@@ -23,7 +38,7 @@ function compareSemver(a, b) {
23
38
  /** 查询 npm registry 判断是否有新版本。网络/解析失败时返回 hasUpdate:false,绝不抛错。 */
24
39
  async function checkForUpdate(opts = {}) {
25
40
  const pkg = opts.packageName ?? DEFAULT_PACKAGE;
26
- const current = opts.currentVersion ?? DEFAULT_VERSION;
41
+ const current = opts.currentVersion ?? getCurrentVersion();
27
42
  const registry = opts.registryUrl ?? DEFAULT_REGISTRY;
28
43
  const fetchImpl = opts.fetchImpl ?? fetch;
29
44
  const result = {
@@ -46,4 +61,4 @@ async function checkForUpdate(opts = {}) {
46
61
  }
47
62
  return result;
48
63
  }
49
- export { checkForUpdate, compareSemver };
64
+ export { checkForUpdate, compareSemver, getCurrentVersion };
@@ -0,0 +1 @@
1
+ ALTER TABLE "file_snapshots" ADD COLUMN "mtime_ms" bigint;