c0de-agent 1.11.0 → 1.13.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 (177) hide show
  1. package/README.md +63 -16
  2. package/dist/cli/commands/acp.js +26 -8
  3. package/dist/cli/commands/chat.js +151 -1
  4. package/dist/cli/commands/config.d.ts +2 -0
  5. package/dist/cli/commands/config.js +63 -3
  6. package/dist/cli/commands/serve.js +11 -2
  7. package/dist/cli/commands/sessions.js +109 -3
  8. package/dist/cli/commands/trust.d.ts +11 -0
  9. package/dist/cli/commands/trust.js +31 -0
  10. package/dist/cli/commands/update.js +4 -0
  11. package/dist/cli/deps.d.ts +9 -6
  12. package/dist/cli/deps.js +66 -19
  13. package/dist/cli/index.d.ts +4 -1
  14. package/dist/cli/index.js +110 -11
  15. package/dist/cli/modes/acp.js +39 -8
  16. package/dist/cli/modes/print.d.ts +3 -0
  17. package/dist/cli/modes/print.js +78 -8
  18. package/dist/core/agent.d.ts +1 -1
  19. package/dist/core/agent.js +9 -4
  20. package/dist/core/agents/builtin.js +2 -2
  21. package/dist/core/agents/index.d.ts +0 -1
  22. package/dist/core/agents/index.js +0 -1
  23. package/dist/core/compact.d.ts +2 -0
  24. package/dist/core/compact.js +1 -0
  25. package/dist/core/config.d.ts +53 -2
  26. package/dist/core/config.js +235 -19
  27. package/dist/core/loop/compaction.d.ts +2 -1
  28. package/dist/core/loop/compaction.js +19 -6
  29. package/dist/core/loop/segment.d.ts +3 -1
  30. package/dist/core/loop/segment.js +24 -1
  31. package/dist/core/loop/stream-collect.js +9 -1
  32. package/dist/core/loop/subagent.js +88 -37
  33. package/dist/core/loop.d.ts +7 -0
  34. package/dist/core/loop.js +90 -6
  35. package/dist/core/redact.d.ts +11 -0
  36. package/dist/core/redact.js +49 -0
  37. package/dist/core/slash.d.ts +10 -0
  38. package/dist/core/slash.js +237 -65
  39. package/dist/core/title.js +7 -1
  40. package/dist/core/types.d.ts +22 -1
  41. package/dist/core/workflows/context.js +44 -4
  42. package/dist/core/workflows/discovery.d.ts +2 -2
  43. package/dist/core/workflows/discovery.js +5 -7
  44. package/dist/core/workflows/index.d.ts +2 -1
  45. package/dist/core/workflows/index.js +2 -1
  46. package/dist/core/workflows/registry.d.ts +13 -3
  47. package/dist/core/workflows/registry.js +14 -9
  48. package/dist/core/workflows/resolve.d.ts +22 -0
  49. package/dist/core/workflows/resolve.js +23 -0
  50. package/dist/core/workflows/runtime.d.ts +6 -1
  51. package/dist/core/workflows/runtime.js +19 -2
  52. package/dist/db/schema.d.ts +373 -1
  53. package/dist/db/schema.js +60 -4
  54. package/dist/kanban/index.d.ts +2 -1
  55. package/dist/kanban/index.js +1 -1
  56. package/dist/kanban/store.d.ts +87 -1
  57. package/dist/kanban/store.js +429 -22
  58. package/dist/llm/index.d.ts +1 -1
  59. package/dist/llm/index.js +1 -1
  60. package/dist/llm/provider.js +1 -1
  61. package/dist/llm/registry.d.ts +7 -1
  62. package/dist/llm/registry.js +7 -1
  63. package/dist/llm/retry.d.ts +5 -2
  64. package/dist/llm/retry.js +6 -5
  65. package/dist/llm/routing.d.ts +24 -1
  66. package/dist/llm/routing.js +29 -1
  67. package/dist/plugins/init.d.ts +7 -0
  68. package/dist/plugins/init.js +22 -1
  69. package/dist/plugins/loader.d.ts +7 -1
  70. package/dist/plugins/loader.js +12 -4
  71. package/dist/project/index.d.ts +1 -1
  72. package/dist/project/index.js +1 -1
  73. package/dist/project/project.d.ts +37 -0
  74. package/dist/project/project.js +142 -1
  75. package/dist/project/trust.d.ts +65 -0
  76. package/dist/project/trust.js +328 -0
  77. package/dist/server/agent-manager.d.ts +15 -2
  78. package/dist/server/agent-manager.js +18 -3
  79. package/dist/server/app.js +45 -30
  80. package/dist/server/auth-manager.d.ts +26 -9
  81. package/dist/server/auth-manager.js +78 -42
  82. package/dist/server/context.js +14 -1
  83. package/dist/server/index.d.ts +1 -1
  84. package/dist/server/index.js +1 -1
  85. package/dist/server/middleware/auth.d.ts +11 -1
  86. package/dist/server/middleware/auth.js +25 -3
  87. package/dist/server/permission/interactive.d.ts +11 -3
  88. package/dist/server/permission/interactive.js +23 -4
  89. package/dist/server/permission/store.d.ts +14 -2
  90. package/dist/server/permission/store.js +39 -3
  91. package/dist/server/registry-config.d.ts +14 -0
  92. package/dist/server/registry-config.js +44 -0
  93. package/dist/server/routes/auth.js +66 -10
  94. package/dist/server/routes/chat.d.ts +21 -0
  95. package/dist/server/routes/chat.js +685 -52
  96. package/dist/server/routes/commands.d.ts +4 -2
  97. package/dist/server/routes/commands.js +9 -4
  98. package/dist/server/routes/config.js +180 -23
  99. package/dist/server/routes/files.js +142 -28
  100. package/dist/server/routes/kanban.js +218 -26
  101. package/dist/server/routes/permissions.d.ts +0 -4
  102. package/dist/server/routes/permissions.js +140 -7
  103. package/dist/server/routes/project.js +132 -29
  104. package/dist/server/routes/provider.js +54 -3
  105. package/dist/server/routes/session.js +463 -23
  106. package/dist/server/routes/tool.js +24 -5
  107. package/dist/server/routes/update.d.ts +11 -5
  108. package/dist/server/routes/update.js +145 -29
  109. package/dist/server/routes/usage.d.ts +4 -0
  110. package/dist/server/routes/usage.js +133 -0
  111. package/dist/server/routes/workflows.js +476 -87
  112. package/dist/server/server.d.ts +16 -13
  113. package/dist/server/server.js +364 -107
  114. package/dist/server/terminal/pty-manager.d.ts +12 -0
  115. package/dist/server/terminal/pty-manager.js +63 -7
  116. package/dist/server/types.d.ts +16 -0
  117. package/dist/session/archive.d.ts +3 -1
  118. package/dist/session/archive.js +13 -1
  119. package/dist/session/branch.d.ts +2 -1
  120. package/dist/session/branch.js +92 -9
  121. package/dist/session/context.js +28 -2
  122. package/dist/session/import.d.ts +44 -0
  123. package/dist/session/import.js +83 -0
  124. package/dist/session/index.d.ts +1 -1
  125. package/dist/session/index.js +1 -1
  126. package/dist/session/jobs.d.ts +12 -0
  127. package/dist/session/jobs.js +53 -0
  128. package/dist/session/message.d.ts +7 -1
  129. package/dist/session/message.js +21 -1
  130. package/dist/session/session.d.ts +186 -12
  131. package/dist/session/session.js +577 -33
  132. package/dist/session/snapshot.d.ts +5 -2
  133. package/dist/session/snapshot.js +10 -2
  134. package/dist/session/types.d.ts +13 -1
  135. package/dist/session/usage.d.ts +62 -0
  136. package/dist/session/usage.js +194 -0
  137. package/dist/shared/types/agent.d.ts +29 -2
  138. package/dist/shared/types/config.d.ts +64 -5
  139. package/dist/shared/types/kanban.d.ts +17 -2
  140. package/dist/shared/types/message.d.ts +23 -0
  141. package/dist/tools/builtin/kanban.js +13 -2
  142. package/dist/tools/index.d.ts +14 -3
  143. package/dist/tools/index.js +23 -5
  144. package/dist/tools/websearch/index.d.ts +5 -1
  145. package/dist/tools/websearch/index.js +46 -7
  146. package/dist/tools/websearch/providers/brave.js +2 -2
  147. package/dist/tools/websearch/providers/duckduckgo.js +2 -1
  148. package/dist/tools/websearch/providers/tavily.js +2 -2
  149. package/dist/tools/websearch/types.d.ts +5 -1
  150. package/dist/tools/websearch/types.js +7 -1
  151. package/dist/update/index.d.ts +3 -3
  152. package/dist/update/index.js +2 -2
  153. package/dist/update/ipc.d.ts +13 -2
  154. package/dist/update/ipc.js +36 -2
  155. package/dist/update/scheduler.js +5 -2
  156. package/dist/update/snapshot.d.ts +18 -3
  157. package/dist/update/snapshot.js +5 -2
  158. package/dist/update/updater.d.ts +17 -1
  159. package/dist/update/updater.js +47 -18
  160. package/dist/update/version.d.ts +3 -0
  161. package/dist/update/version.js +9 -3
  162. package/drizzle/0008_tranquil_lily_hollister.sql +1 -0
  163. package/drizzle/0010_usage_events_ledger.sql +17 -0
  164. package/drizzle/0011_slow_slapstick.sql +12 -0
  165. package/drizzle/0012_chief_timeslip.sql +1 -0
  166. package/drizzle/0013_swift_zeigeist.sql +1 -0
  167. package/drizzle/0014_flimsy_legion.sql +1 -0
  168. package/drizzle/meta/0008_snapshot.json +875 -0
  169. package/drizzle/meta/0010_snapshot.json +1010 -0
  170. package/drizzle/meta/0011_snapshot.json +1055 -0
  171. package/drizzle/meta/0012_snapshot.json +1062 -0
  172. package/drizzle/meta/0013_snapshot.json +1068 -0
  173. package/drizzle/meta/0014_snapshot.json +1075 -0
  174. package/drizzle/meta/_journal.json +42 -0
  175. package/package.json +42 -42
  176. package/dist/core/agents/discovery.d.ts +0 -6
  177. package/dist/core/agents/discovery.js +0 -96
@@ -12,6 +12,9 @@ declare function getFileSnapshots(handle: DB, sessionId: string): Promise<FileSn
12
12
  declare function getLatestFileSnapshot(handle: DB, sessionId: string, filePath: string): Promise<FileSnapshot | null>;
13
13
  /** Quick check: return cached file content if a snapshot exists, else null. */
14
14
  declare function checkFileSnapshot(handle: DB, sessionId: string, filePath: string): Promise<string | null>;
15
- /** 把源会话每个文件的最新快照复制到目标会话(fork 分支用,P1-4)。 */
16
- declare function copyFileSnapshots(handle: DB, fromSessionId: string, toSessionId: string): Promise<void>;
15
+ /** 把源会话每个文件的最新快照复制到目标会话(fork 分支用,P1-4)。
16
+ * C3:beforeMs 提供时只复制创建时间 ≤ 分支点的快照版本——从较早消息点 fork 时,
17
+ * 分支点之后更新的快照反映的是「未来」文件状态,复制会污染分支上下文;
18
+ * 无合格版本的文件夹跳过(宁可缺快照由磁盘重读,也不注入错误内容)。 */
19
+ declare function copyFileSnapshots(handle: DB, fromSessionId: string, toSessionId: string, beforeMs?: number): Promise<void>;
17
20
  export { checkFileSnapshot, copyFileSnapshots, getFileSnapshots, getLatestFileSnapshot, upsertFileSnapshot, };
@@ -73,14 +73,22 @@ async function checkFileSnapshot(handle, sessionId, filePath) {
73
73
  const snapshot = await getLatestFileSnapshot(handle, sessionId, filePath);
74
74
  return snapshot?.content ?? null;
75
75
  }
76
- /** 把源会话每个文件的最新快照复制到目标会话(fork 分支用,P1-4)。 */
77
- async function copyFileSnapshots(handle, fromSessionId, toSessionId) {
76
+ /** 把源会话每个文件的最新快照复制到目标会话(fork 分支用,P1-4)。
77
+ * C3:beforeMs 提供时只复制创建时间 ≤ 分支点的快照版本——从较早消息点 fork 时,
78
+ * 分支点之后更新的快照反映的是「未来」文件状态,复制会污染分支上下文;
79
+ * 无合格版本的文件夹跳过(宁可缺快照由磁盘重读,也不注入错误内容)。 */
80
+ async function copyFileSnapshots(handle, fromSessionId, toSessionId, beforeMs) {
78
81
  const rows = await handle.db
79
82
  .select()
80
83
  .from(fileSnapshots)
81
84
  .where(eq(fileSnapshots.sessionId, fromSessionId));
82
85
  const latest = new Map();
83
86
  for (const r of rows) {
87
+ if (beforeMs !== undefined) {
88
+ const created = r.createdAt instanceof Date ? r.createdAt.getTime() : new Date(r.createdAt).getTime();
89
+ if (created > beforeMs)
90
+ continue;
91
+ }
84
92
  const prev = latest.get(r.filePath);
85
93
  if (!prev || (r.version ?? 0) > (prev.version ?? 0))
86
94
  latest.set(r.filePath, r);
@@ -91,10 +91,22 @@ 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
+ /** 价格未知(cost=null)的调用数:成本按 $0 计入,UI 需提示低估。 */
102
+ unknownCostCalls: number;
103
+ calls: number;
104
+ };
94
105
  /** A node in the session tree. */
95
106
  type SessionTreeNode = {
96
107
  session: Session;
97
108
  children: SessionTreeNode[];
109
+ usage: SessionUsage;
98
110
  };
99
111
  /** A decoded compaction archive row. */
100
112
  type CompactionArchive = {
@@ -121,4 +133,4 @@ type FileSnapshot = {
121
133
  mtimeMs?: number;
122
134
  createdAt: number;
123
135
  };
124
- export type { ArchiveRef, BranchSummaryEntry, CompactionArchive, CompactionConfig, CompactionEntry, CompactionResult, FileSnapshot, HotFile, MessageInput, SessionEntry, SessionTreeNode, SquashConfig, SquashEntry, SteeringEntry, Summarizer, };
136
+ export type { ArchiveRef, BranchSummaryEntry, CompactionArchive, CompactionConfig, CompactionEntry, CompactionResult, FileSnapshot, HotFile, MessageInput, SessionEntry, SessionTreeNode, SessionUsage, SquashConfig, SquashEntry, SteeringEntry, Summarizer, };
@@ -0,0 +1,62 @@
1
+ import type { DB } from '../db/client.js';
2
+ import type { UsageConfig } from '../shared/types/config.js';
3
+ /**
4
+ * 把 sessions.metadata.segments 中全部调用补齐进 usage_events(幂等)。
5
+ * 覆盖:升级前历史数据、fork 复制的 segments(display 用途)、导入会话携带的
6
+ * segments。callId 冲突(已入账)静默跳过。
7
+ * 返回本次新增行数。
8
+ */
9
+ export declare function backfillUsageEvents(handle: DB): Promise<number>;
10
+ /** 当月用量汇总(金额 + token),预算护栏判定用。
11
+ * cost:已发生成本——价格未知(cost=null)的调用按 $0 计入,故金额口径对自建
12
+ * 网关/未登记模型会系统性低估;tokens:input+output+cacheRead tokens 之和,
13
+ * 与价格无关,作为价格独立的兜底口径(缓存读取也按用量计费,须计入)。
14
+ * unknownCostCalls:当月价格未知的调用数——金额预算轴据此识别「口径低估」,
15
+ * 无 token 预算兜底时并入护栏告警文案(P2-2:预算静默失效不再无提示)。
16
+ */
17
+ export declare function monthUsage(handle: DB, opts: {
18
+ projectId?: string | null;
19
+ sinceMs: number;
20
+ }): Promise<{
21
+ cost: number;
22
+ tokens: number;
23
+ unknownCostCalls: number;
24
+ }>;
25
+ /** 当月累计用量(本月 1 日 00:00 本地时区起算)。
26
+ * 已知限制:跨时区/换机时「本月」边界随本地时区漂移,成本护栏阈值随之平移——
27
+ * 本地单机(服务端与用户同机)场景无影响。 */
28
+ export declare function currentMonthUsage(handle: DB, projectId?: string | null, now?: Date): Promise<{
29
+ cost: number;
30
+ tokens: number;
31
+ unknownCostCalls: number;
32
+ }>;
33
+ /** 预算护栏判定结果:parts 为超支描述片段,action 为实际超支轴中最严格的动作
34
+ * ('abort' > 'pause';'warn' 不阻断、不会进入本判定)。
35
+ * warnings:非阻断的告警片段(P2-2)——金额轴启用但当月存在价格未知调用且
36
+ * 无 token 预算兜底时,「金额口径低估」不再静默;消费方并入暂停/中止文案。 */
37
+ export type BudgetOverage = {
38
+ parts: string[];
39
+ action: 'pause' | 'abort';
40
+ warnings: string[];
41
+ };
42
+ /** 预算护栏判定:金额(USD)+ token 双口径,返回超支描述 + 阻断动作(空 parts =
43
+ * 无需阻断——可能超支但动作仅 warn)。
44
+ * 金额口径受 budgetAction 控制;token 口径受 tokenBudgetAction 控制(缺省回退
45
+ * budgetAction,向后兼容)。warn 动作只走前端徽标/面板告警,不进本判定。
46
+ * 供 Web loop 暂停/中止、CLI 拒绝、子 agent 提前中止共用。
47
+ * 项目口径仅在 projectId 可归属时检查(未归属会话只受全局口径兜底)。
48
+ * token 口径独立于价格,兜底自建网关/未登记模型(cost 恒 $0)的场景。
49
+ * P1:金额/token 两轴动作各自求值,多轴同时超支取更严格者(abort > pause),
50
+ * 不再被单一布尔预算标志坍缩降级。 */
51
+ export declare function budgetOverageParts(handle: DB, usage: UsageConfig, projectId: string | null | undefined, now?: Date): Promise<BudgetOverage>;
52
+ /** 本地时区的 YYYY-MM 月份键(与 usage 聚合、前端徽标同口径)。 */
53
+ export declare function localMonthKey(ts: number): string;
54
+ /** 一次性 backfill 标记键(app_meta)。 */
55
+ export declare const USAGE_BACKFILL_MARKER = "usage_backfill_v1";
56
+ /**
57
+ * P3-8:backfill 只在「升级后的首次启动」执行一次,之后跳过——
58
+ * backfill 的真实消费者只有升级前遗留 segments(fork 复制的 segments 经 callId
59
+ * 唯一约束去重本就是 no-op;导入会话的 segments 已剥离 call id 不产生账本行),
60
+ * 每次启动全表扫描是纯浪费。标记写入与 backfill 同事务,失败不落标记可重试。
61
+ */
62
+ export declare function backfillUsageEventsOnce(handle: DB): Promise<number>;
@@ -0,0 +1,194 @@
1
+ // src/session/usage.ts
2
+ // 成本账本工具:usage_events 的 backfill 与月度成本查询。
3
+ //
4
+ // usage_events(append-only,与 session 生命周期解耦)是成本聚合的权威数据源。
5
+ // 会话物理清除(回收站到期/彻底删除/临时会话清理)不触及它——成本是账本。
6
+ // 写路径在 loop manageSegment(每个 LLM 调用落一行);本模块负责两条辅助路径:
7
+ // 1. backfillUsageEvents:把 sessions.metadata.segments 中已有调用补齐进账本
8
+ // (升级前产生的历史数据 + fork 复制的 segments + 导入的会话)。
9
+ // 幂等:callId 唯一约束 + onConflictDoNothing,可安全重复执行。
10
+ // 2. budgetOverageParts:预算暂停/拒绝(usage.budgetAction='pause')用的当月
11
+ // 金额 + token 双口径超支判定(token 兜底价格未知的自建网关/未登记模型)。
12
+ import { and, eq, gte } from 'drizzle-orm';
13
+ import { appMeta, sessions, usageEvents } from '../db/schema.js';
14
+ /**
15
+ * 把 sessions.metadata.segments 中全部调用补齐进 usage_events(幂等)。
16
+ * 覆盖:升级前历史数据、fork 复制的 segments(display 用途)、导入会话携带的
17
+ * segments。callId 冲突(已入账)静默跳过。
18
+ * 返回本次新增行数。
19
+ */
20
+ export async function backfillUsageEvents(handle) {
21
+ const rows = await handle.db
22
+ .select({ id: sessions.id, projectId: sessions.projectId, metadata: sessions.metadata })
23
+ .from(sessions);
24
+ let added = 0;
25
+ for (const row of rows) {
26
+ const meta = (row.metadata ?? {});
27
+ for (const seg of meta.segments ?? []) {
28
+ if (typeof seg.provider !== 'string' || typeof seg.model !== 'string')
29
+ continue;
30
+ for (const call of seg.calls ?? []) {
31
+ if (typeof call.id !== 'string' || call.id.length === 0)
32
+ continue;
33
+ const ts = typeof call.timestamp === 'number' && call.timestamp > 0 ? call.timestamp : 0;
34
+ if (ts === 0)
35
+ continue;
36
+ const input = typeof call.usage?.input === 'number' ? call.usage.input : 0;
37
+ const output = typeof call.usage?.output === 'number' ? call.usage.output : 0;
38
+ const cacheRead = typeof call.usage?.cacheRead === 'number' ? call.usage.cacheRead : 0;
39
+ const cost = typeof call.cost === 'number' ? call.cost : null;
40
+ const result = await handle.db
41
+ .insert(usageEvents)
42
+ .values({
43
+ callId: call.id,
44
+ sessionId: row.id,
45
+ projectId: row.projectId,
46
+ provider: seg.provider,
47
+ model: seg.model,
48
+ inputTokens: input,
49
+ outputTokens: output,
50
+ cacheRead,
51
+ cost,
52
+ timestamp: ts,
53
+ })
54
+ .onConflictDoNothing();
55
+ if (result.rowCount && result.rowCount > 0)
56
+ added += result.rowCount;
57
+ }
58
+ }
59
+ }
60
+ return added;
61
+ }
62
+ /** 当月用量汇总(金额 + token),预算护栏判定用。
63
+ * cost:已发生成本——价格未知(cost=null)的调用按 $0 计入,故金额口径对自建
64
+ * 网关/未登记模型会系统性低估;tokens:input+output+cacheRead tokens 之和,
65
+ * 与价格无关,作为价格独立的兜底口径(缓存读取也按用量计费,须计入)。
66
+ * unknownCostCalls:当月价格未知的调用数——金额预算轴据此识别「口径低估」,
67
+ * 无 token 预算兜底时并入护栏告警文案(P2-2:预算静默失效不再无提示)。
68
+ */
69
+ export async function monthUsage(handle, opts) {
70
+ const conds = [gte(usageEvents.timestamp, opts.sinceMs)];
71
+ if (opts.projectId)
72
+ conds.push(eq(usageEvents.projectId, opts.projectId));
73
+ const rows = await handle.db
74
+ .select({
75
+ cost: usageEvents.cost,
76
+ inputTokens: usageEvents.inputTokens,
77
+ outputTokens: usageEvents.outputTokens,
78
+ cacheRead: usageEvents.cacheRead,
79
+ })
80
+ .from(usageEvents)
81
+ .where(and(...conds));
82
+ let cost = 0;
83
+ let tokens = 0;
84
+ let unknownCostCalls = 0;
85
+ for (const r of rows) {
86
+ if (typeof r.cost === 'number')
87
+ cost += r.cost;
88
+ else
89
+ unknownCostCalls += 1;
90
+ tokens += r.inputTokens + r.outputTokens + (r.cacheRead ?? 0);
91
+ }
92
+ return { cost, tokens, unknownCostCalls };
93
+ }
94
+ /** 当月累计用量(本月 1 日 00:00 本地时区起算)。
95
+ * 已知限制:跨时区/换机时「本月」边界随本地时区漂移,成本护栏阈值随之平移——
96
+ * 本地单机(服务端与用户同机)场景无影响。 */
97
+ export async function currentMonthUsage(handle, projectId, now = new Date()) {
98
+ const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).getTime();
99
+ return monthUsage(handle, { projectId, sinceMs: monthStart });
100
+ }
101
+ /** 预算护栏判定:金额(USD)+ token 双口径,返回超支描述 + 阻断动作(空 parts =
102
+ * 无需阻断——可能超支但动作仅 warn)。
103
+ * 金额口径受 budgetAction 控制;token 口径受 tokenBudgetAction 控制(缺省回退
104
+ * budgetAction,向后兼容)。warn 动作只走前端徽标/面板告警,不进本判定。
105
+ * 供 Web loop 暂停/中止、CLI 拒绝、子 agent 提前中止共用。
106
+ * 项目口径仅在 projectId 可归属时检查(未归属会话只受全局口径兜底)。
107
+ * token 口径独立于价格,兜底自建网关/未登记模型(cost 恒 $0)的场景。
108
+ * P1:金额/token 两轴动作各自求值,多轴同时超支取更严格者(abort > pause),
109
+ * 不再被单一布尔预算标志坍缩降级。 */
110
+ export async function budgetOverageParts(handle, usage, projectId, now = new Date()) {
111
+ const projectBudgetUsd = usage.monthlyBudgetUsd ?? 0;
112
+ const globalBudgetUsd = usage.globalMonthlyBudgetUsd ?? 0;
113
+ const projectTokenBudget = usage.monthlyTokenBudget ?? 0;
114
+ const globalTokenBudget = usage.globalMonthlyTokenBudget ?? 0;
115
+ const amountAction = usage.budgetAction ?? 'warn';
116
+ const tokenAction = usage.tokenBudgetAction ?? amountAction;
117
+ // P0-3:'pause' 与 'abort' 都是阻断动作(均需 loop 放行前检查);'warn' 不阻断。
118
+ const amountBlocks = amountAction === 'pause' || amountAction === 'abort';
119
+ const tokenBlocks = tokenAction === 'pause' || tokenAction === 'abort';
120
+ const projectScoped = projectId != null && (projectBudgetUsd > 0 || projectTokenBudget > 0);
121
+ const globalScoped = globalBudgetUsd > 0 || globalTokenBudget > 0;
122
+ if (!projectScoped && !globalScoped)
123
+ return { parts: [], action: 'pause', warnings: [] };
124
+ const [project, global] = await Promise.all([
125
+ projectScoped ? currentMonthUsage(handle, projectId, now) : Promise.resolve(null),
126
+ globalScoped ? currentMonthUsage(handle, undefined, now) : Promise.resolve(null),
127
+ ]);
128
+ // P2-2:金额轴阻断启用但价格未知调用无 token 兜底 → 「金额口径低估」告警。
129
+ // 仅当对应范围未设置 token 预算(token 兜底缺失)且当月确有 cost=null 调用时
130
+ // 追加——warnings 不触发阻断,只并入暂停/中止文案让用户知道护栏口径不可靠。
131
+ const warnings = [];
132
+ const amountAxisActive = amountBlocks && (globalBudgetUsd > 0 || (projectBudgetUsd > 0 && projectId != null));
133
+ if (amountAxisActive) {
134
+ const globalUnknown = global?.unknownCostCalls ?? 0;
135
+ if (globalBudgetUsd > 0 && globalTokenBudget <= 0 && globalUnknown > 0) {
136
+ warnings.push(`金额口径低估:本月有 ${globalUnknown} 次调用价格未知(按 $0 计入),且未设置全局 token 预算兜底`);
137
+ }
138
+ const projectUnknown = project?.unknownCostCalls ?? 0;
139
+ if (projectBudgetUsd > 0 &&
140
+ projectTokenBudget <= 0 &&
141
+ projectId != null &&
142
+ projectUnknown > 0) {
143
+ warnings.push(`金额口径低估:本项目有 ${projectUnknown} 次调用价格未知(按 $0 计入),且未设置项目 token 预算兜底`);
144
+ }
145
+ }
146
+ const parts = [];
147
+ // 实际超支轴中的最严格阻断动作:任一超支轴动作 === 'abort' 即整体 abort,否则 pause。
148
+ let action = 'pause';
149
+ if (amountBlocks && globalBudgetUsd > 0 && global && global.cost > globalBudgetUsd) {
150
+ parts.push(`全局预算 $${globalBudgetUsd.toFixed(2)}:本月全部项目已 $${global.cost.toFixed(2)}`);
151
+ if (amountAction === 'abort')
152
+ action = 'abort';
153
+ }
154
+ if (amountBlocks && projectBudgetUsd > 0 && project && project.cost > projectBudgetUsd) {
155
+ parts.push(`项目预算 $${projectBudgetUsd.toFixed(2)}:本项目已 $${project.cost.toFixed(2)}`);
156
+ if (amountAction === 'abort')
157
+ action = 'abort';
158
+ }
159
+ if (tokenBlocks && globalTokenBudget > 0 && global && global.tokens > globalTokenBudget) {
160
+ parts.push(`全局 token 预算 ${globalTokenBudget.toLocaleString()}:本月已 ${global.tokens.toLocaleString()} tokens`);
161
+ if (tokenAction === 'abort')
162
+ action = 'abort';
163
+ }
164
+ if (tokenBlocks && projectTokenBudget > 0 && project && project.tokens > projectTokenBudget) {
165
+ parts.push(`项目 token 预算 ${projectTokenBudget.toLocaleString()}:本项目已 ${project.tokens.toLocaleString()} tokens`);
166
+ if (tokenAction === 'abort')
167
+ action = 'abort';
168
+ }
169
+ return { parts, action, warnings };
170
+ }
171
+ /** 本地时区的 YYYY-MM 月份键(与 usage 聚合、前端徽标同口径)。 */
172
+ export function localMonthKey(ts) {
173
+ const d = new Date(ts);
174
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
175
+ }
176
+ /** 一次性 backfill 标记键(app_meta)。 */
177
+ export const USAGE_BACKFILL_MARKER = 'usage_backfill_v1';
178
+ /**
179
+ * P3-8:backfill 只在「升级后的首次启动」执行一次,之后跳过——
180
+ * backfill 的真实消费者只有升级前遗留 segments(fork 复制的 segments 经 callId
181
+ * 唯一约束去重本就是 no-op;导入会话的 segments 已剥离 call id 不产生账本行),
182
+ * 每次启动全表扫描是纯浪费。标记写入与 backfill 同事务,失败不落标记可重试。
183
+ */
184
+ export async function backfillUsageEventsOnce(handle) {
185
+ const [marker] = await handle.db
186
+ .select()
187
+ .from(appMeta)
188
+ .where(eq(appMeta.key, USAGE_BACKFILL_MARKER));
189
+ if (marker)
190
+ return 0;
191
+ const added = await backfillUsageEvents(handle);
192
+ await handle.db.insert(appMeta).values({ key: USAGE_BACKFILL_MARKER, value: String(Date.now()) });
193
+ return added;
194
+ }
@@ -84,7 +84,9 @@ type LLMCall = {
84
84
  firstToken: number;
85
85
  total: number;
86
86
  };
87
- cost: number;
87
+ /** 估算成本(USD)。null = 价格未知(provider 未注册或模型无价目),
88
+ * 聚合时按 $0 计入并单独计数,UI 显式提示——不再静默记 0 造成预算漏报。 */
89
+ cost: number | null;
88
90
  thinking?: string;
89
91
  /** 模型回复文本(替代完整 responseChunks;前端只用文本拼接)。 */
90
92
  responseText: string;
@@ -149,12 +151,23 @@ type AgentEvent = {
149
151
  tool: string;
150
152
  input: unknown;
151
153
  }
152
- /** P1-6:权限确认超时(5 分钟)被自动拒绝时通知前端,提供「重新询问」入口。 */
154
+ /** P1-6:权限确认超时(5 分钟)被自动拒绝时通知前端,提供「重新询问」入口。
155
+ * timeoutAction 告知兜底动作(pause=拒绝后暂停 run / deny=拒绝后继续),前端据此渲染文案。 */
153
156
  | {
154
157
  _tag: 'permission_timeout';
155
158
  toolCallId: string;
156
159
  tool: string;
157
160
  input: unknown;
161
+ timeoutAction: 'pause' | 'deny';
162
+ }
163
+ /** P0 双层超时:提示后仍无响应,pending 已兜底自动拒绝——前端须清理弹窗状态。
164
+ * timeoutAction='pause' 时后端同时暂停 run(status_change(paused) 事件随后到达)。 */
165
+ | {
166
+ _tag: 'permission_expired';
167
+ toolCallId: string;
168
+ tool: string;
169
+ input: unknown;
170
+ timeoutAction: 'pause' | 'deny';
158
171
  } | {
159
172
  _tag: 'error';
160
173
  error: AgentError;
@@ -162,6 +175,13 @@ type AgentEvent = {
162
175
  /** 通知前端:本轮 LLM 调用详情已持久化,应刷新调用详情面板。轻量通知,不带 payload。 */
163
176
  | {
164
177
  _tag: 'llm_detail';
178
+ }
179
+ /** 工作流执行进度(/workflow run 的 onProgress → SSE progress 事件):
180
+ * 前端以横幅展示当前阶段,不再对长工作流黑盒等待。 */
181
+ | {
182
+ _tag: 'progress';
183
+ message: string;
184
+ detail?: unknown;
165
185
  } | {
166
186
  _tag: 'subagent_start';
167
187
  childId: string;
@@ -202,6 +222,10 @@ type AgentEvent = {
202
222
  status: string;
203
223
  }[];
204
224
  }[];
225
+ }
226
+ /** SSE 心跳(服务端防前端 90s 静默看门狗误杀长任务);前端无 UI 变化,直接忽略。 */
227
+ | {
228
+ _tag: 'heartbeat';
205
229
  } | {
206
230
  _tag: 'done';
207
231
  };
@@ -237,6 +261,9 @@ type AgentState = {
237
261
  provider: string;
238
262
  model: string;
239
263
  };
264
+ /** P3 成本护栏:本 run 已因预算超支暂停过一次。恢复后不再重复暂停
265
+ * (用户点「恢复」即知情同意继续,反复暂停是骚扰而非护栏)。 */
266
+ budgetPauseTriggered?: boolean;
240
267
  /**
241
268
  * 压缩死锁标记:自动压缩成功后仍超阈值(如 keepRecentTokens 本身已超限)时置真,
242
269
  * 暂停后续自动压缩以防每轮重复触发(无限循环)。收到新用户消息(agentLoop 重入)时重置。
@@ -46,15 +46,30 @@ type WebSearchConfig = {
46
46
  };
47
47
  /** 多 agent 配置(spec: multi-agent-design §4.12)。 */
48
48
  type AgentsConfig = {
49
- /** agent markdown 目录(相对项目根),默认 '.c0de/agents'。 */
50
- dir: string;
51
49
  /** 并行子 agent 数上限,默认 3。 */
52
50
  subagentConcurrency: number;
53
51
  };
54
52
  /** 权限配置:控制工具执行授权的默认行为。 */
55
53
  type PermissionConfig = {
56
- /** 启动时的默认授权模式:'default' 逐个确认(默认);'auto' 自动放行 ask 工具(YOLO 自动授权)。 */
54
+ /**
55
+ * 启动时的默认授权模式:
56
+ * - 'default'(默认):只读工具自动放行;写/执行工具需确认。Web 为交互弹窗;
57
+ * CLI 非交互(`c0de chat` 未加 -y)下写/执行工具直接拒绝并提示加 -y 或改 auto。
58
+ * (`c0de acp` 恒为 full-auto,由编辑器侧自行控制执行授权,不受本项影响。)
59
+ * - 'auto':全部工具自动放行(YOLO)。克隆仓库前务必先审查其 .c0de/config.json。
60
+ */
57
61
  defaultMode: 'default' | 'auto';
62
+ /**
63
+ * 权限确认超时(提示后 25 分钟宽限期满)兜底拒绝后的动作:
64
+ * - 'pause'(默认):拒绝该工具并暂停 run,用户回来点「恢复」继续——
65
+ * 避免 default 模式下 agent 在用户缺席时继续自主执行后续工具;
66
+ * - 'deny':拒绝该工具,run 继续执行(旧行为,会话绝不挂起)。
67
+ */
68
+ timeoutAction?: 'pause' | 'deny';
69
+ /** 首层确认超时(毫秒)——超过仅提示、pending 保持;默认 5 分钟。 */
70
+ timeoutMs?: number;
71
+ /** 首层超时后到兜底自动拒绝的宽限期(毫秒);默认 25 分钟。 */
72
+ expireGraceMs?: number;
58
73
  };
59
74
  /** 自动升级配置(spec §18)。控制后台 npm registry 检查与无感知热更新行为。 */
60
75
  type UpdateConfig = {
@@ -81,6 +96,50 @@ type SecurityConfig = {
81
96
  token?: string;
82
97
  /** 额外允许的 CORS origin(本地回环始终允许;局域网/远程访问需显式添加)。 */
83
98
  allowedOrigins: string[];
99
+ /** 首设备 bootstrap token 有效期(毫秒)。bootstrap 自生成(auth-token 文件
100
+ * mtime)起超过此时长即拒绝首设备注册。未设置时默认 5 分钟(auth-manager 内置);
101
+ * 无已注册设备时每次 serve 都会重新生成 bootstrap,故重启即可恢复注册。
102
+ * 用于共享主机等 URL 可能泄漏的场景,缩短「先到先得」竞态窗口。 */
103
+ firstDeviceTtlMs?: number;
104
+ };
105
+ /** 用量与成本统计配置。 */
106
+ type UsageConfig = {
107
+ /** 月度成本预算(USD,按配置价目估算)。0 = 不限制。
108
+ * 项目口径:当前项目聚合成本超过预算时告警/暂停。
109
+ * 当前月成本超过预算时,设置页「用量」面板给出醒目告警。 */
110
+ monthlyBudgetUsd: number;
111
+ /**
112
+ * 全局月度成本预算(USD,所有项目 + 未归属调用聚合)。0 = 不限制。
113
+ * P1-3:与项目预算并存,任一超支即触发 budgetAction——
114
+ * 未归属项目/未配置项目预算的花费至少受全局预算兜底。
115
+ * 仅 global 作用域生效:项目作用域写入会被剥离并忽略(作用域收敛)。 */
116
+ globalMonthlyBudgetUsd?: number;
117
+ /**
118
+ * 月度 token 预算(input + output + cacheRead tokens 之和),0 = 不限制。
119
+ * P0:价格独立护栏——自建网关/未登记模型的 cost 恒 $0,金额护栏拦不住,
120
+ * token 护栏按 token 量兜底(缓存读取亦按用量计费,故纳入口径)。
121
+ * 动作由 tokenBudgetAction 决定(缺省回退 budgetAction,向后兼容)。 */
122
+ monthlyTokenBudget?: number;
123
+ /**
124
+ * 全局月度 token 预算(所有项目 + 未归属调用聚合),0 = 不限制。
125
+ * 与 monthlyTokenBudget 并存,任一超支即按 tokenBudgetAction 触发。
126
+ * 仅 global 作用域生效(与 globalMonthlyBudgetUsd 同口径)。 */
127
+ globalMonthlyTokenBudget?: number;
128
+ /**
129
+ * 超支动作(P3 成本护栏),作用于**金额**预算:
130
+ * - 'warn'(默认):仅告警(顶栏徽标/用量面板),agent 继续执行;
131
+ * - 'pause':新一轮 LLM 请求前发现当月成本超预算 → 暂停 run(等同权限超时
132
+ * 暂停机制),用户点「恢复」后本 run 不再因预算重复暂停(用户已知情);
133
+ * - 'abort':新一轮 LLM 请求前发现超预算 → 中止 run(硬封顶,无「恢复」按钮,
134
+ * 需重新发送消息/调高预算后重试),适合需要「撞线即硬停、不可在界面原地放行」的场景。
135
+ */
136
+ budgetAction?: 'warn' | 'pause' | 'abort';
137
+ /**
138
+ * token 预算的超支动作,独立于金额预算(P:token 是兜底口径,用户可能希望
139
+ * 「金额超支暂停、token 超支只告警」或反之)。缺省回退 budgetAction。
140
+ * 语义同 budgetAction:'pause'/'abort' 硬性暂停/中止,'warn' 仅徽标/面板告警。
141
+ */
142
+ tokenBudgetAction?: 'warn' | 'pause' | 'abort';
84
143
  };
85
144
  /** Global application configuration. */
86
145
  type Config = {
@@ -120,7 +179,7 @@ type Config = {
120
179
  agents: AgentsConfig;
121
180
  permission: PermissionConfig;
122
181
  update: UpdateConfig;
182
+ usage: UsageConfig;
123
183
  theme: 'light' | 'dark' | 'system';
124
- locale: string;
125
184
  };
126
- export type { AgentsConfig, CompactionConfig, Config, MCPServerConfig, PermissionConfig, SecurityConfig, ToolMetricsConfig, UpdateConfig, WebSearchConfig, };
185
+ export type { AgentsConfig, CompactionConfig, Config, MCPServerConfig, PermissionConfig, SecurityConfig, ToolMetricsConfig, UpdateConfig, UsageConfig, WebSearchConfig, };
@@ -30,10 +30,10 @@ type KanbanCard = {
30
30
  createdAt: string;
31
31
  updatedAt: string;
32
32
  };
33
- /** A kanban board (one per project). */
33
+ /** A kanban board (one per project). P2-5:回收站(软删除)看板 projectId 为 null。 */
34
34
  type KanbanBoard = {
35
35
  id: string;
36
- projectId: string;
36
+ projectId: string | null;
37
37
  columns: KanbanColumnDef[];
38
38
  labels: KanbanLabelDef[];
39
39
  createdAt: string;
@@ -51,6 +51,8 @@ declare const KANBAN_LABEL_COLORS: readonly string[];
51
51
  * implementation, mirroring the todoState pattern). */
52
52
  interface KanbanStore {
53
53
  getBoard(): Promise<KanbanBoardWithCards>;
54
+ /** 只读获取板(不懒创建):纯读路径(导出等)用,无板返回 null。 */
55
+ peekBoard(): Promise<KanbanBoardWithCards | null>;
54
56
  addCard(input: {
55
57
  title: string;
56
58
  description?: string | null;
@@ -70,6 +72,19 @@ interface KanbanStore {
70
72
  columns?: KanbanColumnDef[];
71
73
  labels?: KanbanLabelDef[];
72
74
  }): Promise<KanbanBoard>;
75
+ /** 导入/替换整板:列与标签配置 + 全部卡片原子替换(导入数据的落点)。 */
76
+ replaceBoard(input: {
77
+ columns: KanbanColumnDef[];
78
+ labels: KanbanLabelDef[];
79
+ cards: Array<{
80
+ title: string;
81
+ description?: string | null;
82
+ columnId: string;
83
+ priority: KanbanPriority;
84
+ position: number;
85
+ labels: string[];
86
+ }>;
87
+ }): Promise<KanbanBoardWithCards>;
73
88
  }
74
89
  export type { KanbanBoard, KanbanBoardWithCards, KanbanCard, KanbanColumnDef, KanbanLabelDef, KanbanPriority, KanbanStore, };
75
90
  export { DEFAULT_KANBAN_COLUMNS, KANBAN_LABEL_COLORS };
@@ -48,12 +48,35 @@ type SessionMetadata = {
48
48
  mainThreadId?: string;
49
49
  squashCount?: number;
50
50
  fileSnapshots?: string[];
51
+ /** 工作流运行会话对应的 /workflow 名称(创建时写入;中断恢复指引展示用)。 */
52
+ workflowName?: string;
51
53
  /** 本会话分段增量 LLM 调用记录,用于调用详情面板展示。 */
52
54
  segments?: LLMSegment[];
53
55
  /** 上次 agent run 状态;status='running' 且进程无活跃 run → 被中断。 */
54
56
  lastRun?: LastRun;
57
+ /** M3:中断 run 的半截轮次区间起点——最后一条含文本 user 消息的条目 id。
58
+ * 区间 (since, until] 内的条目属「未完成轮次」:构建上下文时剔除、时间线置灰。
59
+ * 与 unfinishedUntilEntryId 成对使用;条目被 /clear、压缩归档后区间自然失效(no-op)。 */
60
+ unfinishedSinceEntryId?: string;
61
+ /** M3:中断轮次区间终点——标记时数据库中最后一条条目的 id(含)。 */
62
+ unfinishedUntilEntryId?: string;
55
63
  /** 上次打开时间戳(ms),用于会话列表按最近打开排序。 */
56
64
  lastOpenedAt?: number;
65
+ /** P2:会话级授权模式覆盖('auto'/'default'),跨重启持久化。 */
66
+ permissionMode?: 'auto' | 'default';
67
+ /** 回收站条目首次被用户看到的时间戳(ms)。回收站保留期自此起算,而非删除时间——
68
+ * 避免「删除后长期不开服务,重启即被静默物理清除、从未见过倒计时」的墙钟缺陷。
69
+ * 只标记一次(touchTrashSeen 不重置),保证倒计时稳定可预期。 */
70
+ trashSeenAt?: number;
71
+ /** 回收站条目已到期、进入物理清除宽限期的时间戳(ms)。
72
+ * 到期先标记,宽限期(默认 7 天)内可在 UI 恢复;期满后由 purgeDeletedSessions 物理清除。 */
73
+ purgePendingAt?: number;
74
+ /**
75
+ * P2-3:最近一次预算超支暂停的原因(loop 暂停时写入)。热更新/重启后 run 重建,
76
+ * 内存 budgetPauseTriggered 标记丢失——新 run 启动时消费此字段还原标记,
77
+ * 避免用户已确认继续后又被同一超支原因二次暂停。消费即删除(单次语义)。
78
+ */
79
+ budgetPauseReason?: string;
57
80
  };
58
81
  /** A conversation session (may have a parent for branching). */
59
82
  type Session = {
@@ -31,6 +31,17 @@ function formatBoardSummary(board, errors = []) {
31
31
  if (cards.length === 0)
32
32
  lines.push(' (empty)');
33
33
  }
34
+ // P1 修复:历史幽灵卡片(列 id 不在当前列配置,旧版本脏数据)单独列出并附
35
+ // 完整 id——否则对 agent 完全不可见(且 Total 与列之和恒对不上),无法清理。
36
+ const colIds = new Set(board.columns.map((c) => c.id));
37
+ const orphans = board.cards.filter((c) => !colIds.has(c.columnId));
38
+ if (orphans.length > 0) {
39
+ lines.push(`## 未归属列 (${orphans.length})`);
40
+ for (const card of orphans) {
41
+ lines.push(` [${card.id}] "${card.title}"(引用已不存在的列 "${card.columnId}",` +
42
+ `可 delete 清理或 move 到现有列)`);
43
+ }
44
+ }
34
45
  const total = board.cards.length;
35
46
  lines.push(`\nTotal: ${total} card(s).`);
36
47
  if (total === 0)
@@ -84,12 +95,12 @@ export const kanbanTool = {
84
95
  name: 'kanban',
85
96
  description: `Manage the project's shared kanban task board. 5 operations:
86
97
  - view: list all columns and cards (read-only)
87
- - add: create a new task card (title required; optional description, columnId defaults to todo, priority defaults to medium, labels)
98
+ - add: create a new task card (title required; optional description, columnId defaults to the board's first column, priority defaults to medium, labels)
88
99
  - update: edit a card's title/description/priority/labels (id required)
89
100
  - move: move a card to a different column or reorder (id + columnId required; optional position)
90
101
  - delete: remove a card (id required)
91
102
 
92
- The board is shared across all sessions and agents in the same project — use it to coordinate work, track tasks visible to the user, and break down complex projects. Column ids are: todo, in_progress, in_review, done, cancelled (customizable by the user via the board config UI).`,
103
+ The board is shared across all sessions and agents in the same project — use it to coordinate work, track tasks visible to the user, and break down complex projects. Column ids are user-configurable: do NOT guess them — run \`view\` first to discover the current columns, then reuse those exact ids. Adding/moving to an unknown column id fails with the list of valid ids.`,
93
104
  parameters: kanbanParameters,
94
105
  permission: 'auto',
95
106
  execute: async (input, ctx) => {
@@ -34,10 +34,21 @@ import type { ToolRegistry } from './types.js';
34
34
  */
35
35
  export declare function createDefaultRegistry(config?: Config): ToolRegistry;
36
36
  /**
37
- * 解析本轮启用的工具名列表(P1-1 修复:config.tools.enabled/disabled 双入口统一生效)。
37
+ * 解析本轮启用的工具名列表(config.tools.enabled/disabled 双入口统一生效)。
38
38
  *
39
- * - explicit(前端显式选择)非 undefined → 以其为准,但过滤掉 disabled 与未注册名。
40
- * - 否则 enabled 非空 → enabled ∩ registered;enabled 空 → 全部 registered(空=全部)。
39
+ * 语义(P1-1 修复「空=全部」安全陷阱 + P2 交集收紧):
40
+ * - explicit(前端显式选择)非 undefined → 与 config.tools.enabled 取交集——
41
+ * enabled 含 '*' 或未配置时视为无上限(explicit 原样生效)。此前 explicit 完全
42
+ * 覆盖 enabled:config.tools.enabled=[] 的 fail-closed 语义可被请求级 tools
43
+ * 静默绕过,设置页「禁用全部」与实际行为矛盾。
44
+ * - 否则 config.tools.enabled:含 '*'(通配)→ 全部注册工具;
45
+ * 空数组 → 无工具(fail-closed);否则 → 显式名单。
41
46
  * - disabled 恒过滤(registry 已排除,此处兜底)。
42
47
  */
43
48
  export declare function resolveEnabledToolNames(registry: ToolRegistry, config: Pick<Config, 'tools'>, explicit?: string[]): string[];
49
+ /**
50
+ * 依赖「会话绑定项目」的工具:其运行时依赖(如 kanban 的 kanbanStore)由宿主按
51
+ * session.projectId 注入——无项目会话(CLI print 未注册 cwd 等)调用必失败,
52
+ * 只会浪费一轮工具调用。会话无 projectId 时从工具集中剔除。
53
+ */
54
+ export declare const PROJECT_BOUND_TOOLS: ReadonlySet<string>;