@armadra/agent 0.7.3 → 0.7.5

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 (179) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/CHANGELOG.zh-CN.md +48 -0
  3. package/dist/agent/retry.d.ts +14 -5
  4. package/dist/agent/retry.js +30 -8
  5. package/dist/agent/session-cache-key.d.ts +8 -0
  6. package/dist/agent/session-cache-key.js +46 -0
  7. package/dist/agent/session-cache.d.ts +2 -3
  8. package/dist/agent/session-cache.js +19 -37
  9. package/dist/agent/session-classifier.d.ts +3 -2
  10. package/dist/agent/session-classifier.js +36 -10
  11. package/dist/agent/session-compaction.d.ts +24 -5
  12. package/dist/agent/session-compaction.js +54 -20
  13. package/dist/agent/session-core.d.ts +2 -0
  14. package/dist/agent/session-run.d.ts +12 -0
  15. package/dist/agent/session-run.js +29 -10
  16. package/dist/agent/session-subagent.d.ts +8 -1
  17. package/dist/agent/session-subagent.js +37 -14
  18. package/dist/agent/session-sync.d.ts +8 -2
  19. package/dist/agent/session-sync.js +7 -10
  20. package/dist/agent/session-tools.js +3 -2
  21. package/dist/agent/subagent-fork.d.ts +67 -0
  22. package/dist/agent/subagent-fork.js +87 -0
  23. package/dist/agent/subagent-registry.d.ts +2 -2
  24. package/dist/agent/subagent-registry.js +2 -2
  25. package/dist/agent/tool-availability.d.ts +8 -0
  26. package/dist/agent/tool-availability.js +8 -0
  27. package/dist/agent/tool-runner.js +13 -4
  28. package/dist/agent/worktree.d.ts +8 -0
  29. package/dist/agent/worktree.js +51 -4
  30. package/dist/agents/parse.js +6 -1
  31. package/dist/agents/task-record.js +3 -0
  32. package/dist/agents/types.d.ts +2 -0
  33. package/dist/ai/apis/anthropic-messages.js +10 -5
  34. package/dist/ai/apis/anthropic-request.d.ts +2 -1
  35. package/dist/ai/apis/anthropic-request.js +15 -4
  36. package/dist/ai/apis/cache-params.js +2 -1
  37. package/dist/ai/apis/google-generative-ai.js +4 -2
  38. package/dist/ai/apis/google-request.js +2 -1
  39. package/dist/ai/apis/max-tokens.d.ts +42 -0
  40. package/dist/ai/apis/max-tokens.js +121 -0
  41. package/dist/ai/apis/openai-completions.js +10 -5
  42. package/dist/ai/apis/openai-request.js +4 -2
  43. package/dist/ai/apis/openai-responses-request.js +3 -2
  44. package/dist/ai/apis/openai-responses.js +9 -5
  45. package/dist/ai/apis/shared.d.ts +3 -1
  46. package/dist/ai/apis/shared.js +21 -3
  47. package/dist/ai/cache/fingerprint.d.ts +4 -1
  48. package/dist/ai/cache/fingerprint.js +18 -1
  49. package/dist/ai/cache/miss.d.ts +2 -1
  50. package/dist/ai/cache/miss.js +9 -3
  51. package/dist/ai/cache/reporting.js +5 -1
  52. package/dist/ai/cache/types.d.ts +10 -2
  53. package/dist/ai/cache/warmer.d.ts +6 -1
  54. package/dist/ai/cache/warmer.js +7 -0
  55. package/dist/ai/context.d.ts +12 -4
  56. package/dist/ai/context.js +43 -24
  57. package/dist/ai/fake/fake-provider.d.ts +8 -1
  58. package/dist/ai/fake/fake-provider.js +19 -7
  59. package/dist/ai/fake/fake-script.d.ts +2 -0
  60. package/dist/ai/http.d.ts +17 -9
  61. package/dist/ai/http.js +33 -13
  62. package/dist/ai/image-intern.d.ts +29 -0
  63. package/dist/ai/image-intern.js +58 -0
  64. package/dist/ai/json-body.d.ts +63 -0
  65. package/dist/ai/json-body.js +286 -0
  66. package/dist/ai/overflow.js +5 -0
  67. package/dist/ai/providers/catalog-data.js +13 -13
  68. package/dist/ai/providers/catalog.d.ts +33 -0
  69. package/dist/ai/providers/catalog.js +105 -6
  70. package/dist/ai/providers/enrich.d.ts +13 -3
  71. package/dist/ai/providers/enrich.js +87 -31
  72. package/dist/ai/sse.d.ts +3 -2
  73. package/dist/ai/sse.js +3 -2
  74. package/dist/ai/types.d.ts +9 -2
  75. package/dist/bundle/ama.cjs +3637 -1973
  76. package/dist/cli/bootstrap.js +13 -5
  77. package/dist/cli/compose-agents.js +3 -0
  78. package/dist/cli/compose-extensions.js +2 -0
  79. package/dist/cli/compose-request.d.ts +20 -0
  80. package/dist/cli/compose-request.js +37 -0
  81. package/dist/cli/compose-session.js +2 -2
  82. package/dist/cli/deps.d.ts +1 -1
  83. package/dist/cli/subcommands/model-meta.d.ts +2 -1
  84. package/dist/cli/subcommands/model-meta.js +3 -2
  85. package/dist/codemode/host-side.d.ts +11 -4
  86. package/dist/codemode/host-side.js +25 -6
  87. package/dist/codemode/tool.d.ts +2 -0
  88. package/dist/codemode/tool.js +4 -0
  89. package/dist/config/json-schema.js +9 -2
  90. package/dist/config/key-docs.js +13 -4
  91. package/dist/config/schema-w5.js +9 -1
  92. package/dist/config/schema.d.ts +1 -1
  93. package/dist/config/schema.js +19 -3
  94. package/dist/config/settings-registry.js +6 -0
  95. package/dist/config/types-w5.d.ts +2 -0
  96. package/dist/config/types.d.ts +19 -1
  97. package/dist/config/types.js +4 -0
  98. package/dist/i18n/catalog.d.ts +13 -2
  99. package/dist/i18n/messages/cli.d.ts +2 -0
  100. package/dist/i18n/messages/cli.js +2 -0
  101. package/dist/i18n/messages/config-keys.d.ts +10 -0
  102. package/dist/i18n/messages/config-keys.js +12 -2
  103. package/dist/i18n/messages/config.d.ts +14 -0
  104. package/dist/i18n/messages/config.js +4 -0
  105. package/dist/i18n/messages/errors.d.ts +3 -2
  106. package/dist/i18n/messages/errors.js +15 -2
  107. package/dist/i18n/messages/report.d.ts +2 -0
  108. package/dist/i18n/messages/report.js +2 -0
  109. package/dist/i18n/messages/settings.d.ts +4 -0
  110. package/dist/i18n/messages/settings.js +6 -2
  111. package/dist/i18n/messages/subcommands.d.ts +2 -2
  112. package/dist/i18n/messages/subcommands.js +4 -2
  113. package/dist/modes/acp/acp-server.js +30 -18
  114. package/dist/modes/acp/acp-sessions.d.ts +22 -0
  115. package/dist/modes/acp/acp-sessions.js +68 -0
  116. package/dist/modes/context-report.js +3 -2
  117. package/dist/modes/print/json-event.d.ts +12 -1
  118. package/dist/modes/print/json-event.js +48 -1
  119. package/dist/modes/rpc/rpc-mode.d.ts +1 -0
  120. package/dist/modes/rpc/rpc-mode.js +9 -2
  121. package/dist/modes/session-report.d.ts +1 -1
  122. package/dist/modes/session-report.js +12 -6
  123. package/dist/rpc.d.ts +5 -2
  124. package/dist/session/line-reader.d.ts +28 -0
  125. package/dist/session/line-reader.js +82 -0
  126. package/dist/session/list.d.ts +14 -0
  127. package/dist/session/list.js +150 -0
  128. package/dist/session/manager.d.ts +23 -3
  129. package/dist/session/manager.js +91 -62
  130. package/dist/session/migrate.d.ts +2 -1
  131. package/dist/session/migrate.js +2 -1
  132. package/dist/session/offload.d.ts +51 -0
  133. package/dist/session/offload.js +195 -0
  134. package/dist/session/projection.d.ts +16 -3
  135. package/dist/session/projection.js +96 -29
  136. package/dist/session/scan.d.ts +3 -3
  137. package/dist/session/scan.js +9 -18
  138. package/dist/session/store.d.ts +15 -4
  139. package/dist/session/store.js +54 -38
  140. package/dist/session/types.d.ts +7 -2
  141. package/dist/tools/bash.d.ts +1 -1
  142. package/dist/tools/bash.js +5 -4
  143. package/dist/tools/glob.d.ts +2 -2
  144. package/dist/tools/glob.js +2 -2
  145. package/dist/tools/grep.js +4 -6
  146. package/dist/tools/image-file.js +2 -1
  147. package/dist/tools/read-lines.d.ts +71 -0
  148. package/dist/tools/read-lines.js +224 -0
  149. package/dist/tools/read.d.ts +6 -2
  150. package/dist/tools/read.js +47 -27
  151. package/dist/tools/task.d.ts +9 -0
  152. package/dist/tools/task.js +21 -8
  153. package/dist/tools/truncate.d.ts +7 -0
  154. package/dist/tools/truncate.js +11 -0
  155. package/dist/tools/types.d.ts +13 -0
  156. package/dist/trace/build-index.js +13 -1
  157. package/dist/tui/ansi.d.ts +2 -0
  158. package/dist/tui/ansi.js +7 -3
  159. package/dist/tui/components/editor-buffer.js +2 -2
  160. package/dist/tui/components/editor.js +5 -6
  161. package/dist/tui/keys.js +2 -2
  162. package/dist/tui/stdin-buffer.js +3 -3
  163. package/docs/acp.md +8 -1
  164. package/docs/agents.md +42 -6
  165. package/docs/codemode.md +4 -2
  166. package/docs/en/acp.md +3 -2
  167. package/docs/en/host-api.md +1 -1
  168. package/docs/en/permissions.md +2 -2
  169. package/docs/en/providers.md +54 -20
  170. package/docs/en/rpc.md +23 -7
  171. package/docs/en/sessions.md +4 -2
  172. package/docs/en/tui.md +2 -2
  173. package/docs/permissions.md +4 -1
  174. package/docs/providers.md +85 -22
  175. package/docs/rpc.md +22 -6
  176. package/docs/session-format.md +13 -2
  177. package/docs/sessions.md +3 -1
  178. package/docs/tui.md +1 -1
  179. package/package.json +2 -2
@@ -76,7 +76,8 @@ export async function bootstrap(argsIn, deps, io) {
76
76
  request = { kind: "resume", id };
77
77
  }
78
78
  const sessionRequest = request;
79
- const sessionManager = await step(ExitCode.Session, m().steps.session, () => deps.sessions.open(sessionRequest, { sessionDir: paths.sessionDir, cwd: paths.cwd }));
79
+ // [#165] 会话替换后跟随当前会话(闭包只经它取,启动会话的 manager 不被常驻)
80
+ let sessionManager = await step(ExitCode.Session, m().steps.session, () => deps.sessions.open(sessionRequest, { sessionDir: paths.sessionDir, cwd: paths.cwd }));
80
81
  let sessionCwd = sessionManager.cwd;
81
82
  if (!existsSync(sessionCwd)) {
82
83
  const replacement = interactive ? await deps.ui?.askCwd?.(sessionCwd) : undefined;
@@ -281,6 +282,8 @@ export async function bootstrap(argsIn, deps, io) {
281
282
  uiBroker: () => uiBroker,
282
283
  onSessionReplaced: (next) => {
283
284
  session = next;
285
+ sessionManager = next.manager ?? sessionManager;
286
+ assembly.sessionManager = sessionManager;
284
287
  },
285
288
  sessionStartContext: () => sessionStartContext,
286
289
  unattended,
@@ -302,7 +305,7 @@ export async function bootstrap(argsIn, deps, io) {
302
305
  if (Object.keys(overrides).length > 0)
303
306
  assembly.overrides = overrides;
304
307
  session = await step(ExitCode.RuntimeError, m().steps.assembly, () => deps.session.create(assembly));
305
- const active = session;
308
+ const active = session; // 只同步使用:闭包捕获它会让启动会话常驻(#165)
306
309
  let disposed;
307
310
  shutdown = (reason) => {
308
311
  disposed ??= (async () => {
@@ -310,7 +313,7 @@ export async function bootstrap(argsIn, deps, io) {
310
313
  await hooks.run("SessionEnd", { reason }).catch(() => undefined);
311
314
  await disposeHost(host, (e) => warn(m().hostDisposeFailed(String(e))));
312
315
  // 会话被替换过时 dispose 当前那个(旧会话由替换方负责)
313
- await (session ?? active).dispose();
316
+ await session?.dispose();
314
317
  })();
315
318
  return disposed;
316
319
  };
@@ -339,8 +342,13 @@ export async function bootstrap(argsIn, deps, io) {
339
342
  providers,
340
343
  model,
341
344
  thinkingLevel,
342
- sessionManager,
343
- session: active,
345
+ // [#165] 跟随当前会话(ACP 关掉启动会话后它可被回收)
346
+ get sessionManager() {
347
+ return sessionManager;
348
+ },
349
+ get session() {
350
+ return session;
351
+ },
344
352
  hooks,
345
353
  host,
346
354
  permission,
@@ -56,6 +56,9 @@ export function subagentEnvironment(deps, catalog) {
56
56
  const pending = config.subagents?.maxPending;
57
57
  if (pending !== undefined)
58
58
  env.maxPending = pending;
59
+ const retain = config.subagents?.retainSessions; // [M-F] 子会话句柄 LRU 上限
60
+ if (retain !== undefined)
61
+ env.retain = retain;
59
62
  // [W7-B1] 缺省后台与自动转后台(配置键 W7-B2 登记于 config/types-w5.ts)
60
63
  const sub = config.subagents;
61
64
  env.background = resolveTaskBackground(sub?.background, deps.assembly.unattended === true);
@@ -25,6 +25,7 @@ import { createLimitsExtension } from "../agent/limits.js";
25
25
  import { createRemindersExtension, remindersSettings } from "../agent/reminders.js";
26
26
  import { planExtensionFor } from "../plan/compose.js";
27
27
  import { createSubagentsFactory } from "./compose-agents.js";
28
+ import { requestTimeoutsFactory } from "./compose-request.js";
28
29
  import { createExternalStatsExtension } from "../drivers/store.js";
29
30
  export function composeExtensions(deps) {
30
31
  return [
@@ -42,5 +43,6 @@ export function composeExtensions(deps) {
42
43
  // [W5-E] 外部 Agent 记账 → SessionStats.external(只主会话)
43
44
  ({ core }) => core.depth > 0 ? undefined : createExternalStatsExtension(() => core.manager.branch()),
44
45
  createSubagentsFactory(deps), // [W5-G] 子 Agent 任务注册表
46
+ requestTimeoutsFactory(deps), // [ME-C] request.streamIdleTimeoutMs → 每次请求的流中空闲上限
45
47
  ];
46
48
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 模型请求的流中空闲超时(docs/model-efficiency-plan.md D18)。[ME-C]
3
+ *
4
+ * 等响应头仍由 `request.idleTimeoutMs` / `AMA_IDLE_TIMEOUT_MS`(compose-session.ts `idleTimeoutFrom`)
5
+ * 决定;流开始后两块数据之间的上限读 `AMA_STREAM_IDLE_TIMEOUT_MS` > config
6
+ * `request.streamIdleTimeoutMs`,都没有时协议层取缺省 180 000(ai/http.ts)。
7
+ *
8
+ * `session.ts` 不加行:值经会话扩展的 `wrapStream` 写进每次请求的 `StreamOptions.streamIdleTimeoutMs`
9
+ * (已显式给出的不覆盖);工厂对主会话与 task 子会话都装(子会话沿用父的扩展表)。
10
+ */
11
+ import type { SessionExtensionFactory } from "../agent/session-extensions.js";
12
+ import type { AmaConfig } from "../config/types.js";
13
+ import type { ComposeExtensionDeps } from "./compose-extensions.js";
14
+ export declare const STREAM_IDLE_TIMEOUT_ENV = "AMA_STREAM_IDLE_TIMEOUT_MS";
15
+ /** 流中空闲超时:环境变量 > config;都没有 → undefined。0 关闭;非法的环境变量值忽略并 warning。 */
16
+ export declare function streamIdleTimeoutFrom(config: Pick<AmaConfig, "request">, env?: NodeJS.ProcessEnv, warn?: (message: string) => void): number | undefined;
17
+ /** 给每次请求补上 `streamIdleTimeoutMs`(调用方已给出的不覆盖)。 */
18
+ export declare function streamIdleExtension(ms: number): SessionExtensionFactory;
19
+ /** compose-extensions.ts 表里的一行:配置或环境变量给了值才装。 */
20
+ export declare function requestTimeoutsFactory(deps: ComposeExtensionDeps): SessionExtensionFactory;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 模型请求的流中空闲超时(docs/model-efficiency-plan.md D18)。[ME-C]
3
+ *
4
+ * 等响应头仍由 `request.idleTimeoutMs` / `AMA_IDLE_TIMEOUT_MS`(compose-session.ts `idleTimeoutFrom`)
5
+ * 决定;流开始后两块数据之间的上限读 `AMA_STREAM_IDLE_TIMEOUT_MS` > config
6
+ * `request.streamIdleTimeoutMs`,都没有时协议层取缺省 180 000(ai/http.ts)。
7
+ *
8
+ * `session.ts` 不加行:值经会话扩展的 `wrapStream` 写进每次请求的 `StreamOptions.streamIdleTimeoutMs`
9
+ * (已显式给出的不覆盖);工厂对主会话与 task 子会话都装(子会话沿用父的扩展表)。
10
+ */
11
+ import { msg } from "../i18n/index.js";
12
+ export const STREAM_IDLE_TIMEOUT_ENV = "AMA_STREAM_IDLE_TIMEOUT_MS";
13
+ /** 流中空闲超时:环境变量 > config;都没有 → undefined。0 关闭;非法的环境变量值忽略并 warning。 */
14
+ export function streamIdleTimeoutFrom(config, env = process.env, warn = () => undefined) {
15
+ const raw = env[STREAM_IDLE_TIMEOUT_ENV];
16
+ if (raw !== undefined && raw.trim() !== "") {
17
+ const value = Number(raw);
18
+ if (Number.isFinite(value) && value >= 0)
19
+ return value;
20
+ warn(msg().cli.composeSession.invalidStreamIdleTimeout(raw));
21
+ }
22
+ return config.request?.streamIdleTimeoutMs;
23
+ }
24
+ /** 给每次请求补上 `streamIdleTimeoutMs`(调用方已给出的不覆盖)。 */
25
+ export function streamIdleExtension(ms) {
26
+ return () => ({
27
+ id: "request-timeouts",
28
+ wrapStream: (stream) => (model, context, options) => stream(model, context, options.streamIdleTimeoutMs === undefined
29
+ ? { ...options, streamIdleTimeoutMs: ms }
30
+ : options),
31
+ });
32
+ }
33
+ /** compose-extensions.ts 表里的一行:配置或环境变量给了值才装。 */
34
+ export function requestTimeoutsFactory(deps) {
35
+ const ms = streamIdleTimeoutFrom(deps.assembly.config, deps.env, (message) => deps.log("warn", message));
36
+ return ms === undefined ? () => undefined : streamIdleExtension(ms);
37
+ }
@@ -411,8 +411,8 @@ export async function createSessionAlongside(target, request, options = {}) {
411
411
  const { assembly } = record;
412
412
  const sessionDir = assembly.paths.sessionDir;
413
413
  const fresh = (id) => assembly.overrides?.noSession === true
414
- ? SessionManager.inMemory(current.cwd)
415
- : SessionManager.create(sessionDirForCwd(sessionDir, current.cwd), current.cwd, id !== undefined ? { id } : {});
414
+ ? SessionManager.inMemory(record.current.cwd)
415
+ : SessionManager.create(sessionDirForCwd(sessionDir, record.current.cwd), record.current.cwd, id !== undefined ? { id } : {});
416
416
  let manager;
417
417
  if (request.kind === "new")
418
418
  manager = fresh();
@@ -107,7 +107,7 @@ export interface SessionAssembly {
107
107
  /**
108
108
  * 模式层把当前会话换成另一个(`/new`、`/resume`、`/fork`、RPC `switch_session`)后调用:
109
109
  * bootstrap 让 HostApi.session.*、宿主 sendUser、Hook 公共字段与退出时的 dispose 跟随 `next`。
110
- * 旧会话由调用方自行 dispose;`Runtime.session` 仍指初始会话。
110
+ * 旧会话由调用方自行 dispose;`Runtime.session` / `sessionManager` 也随之指向 `next`(#165)。
111
111
  */
112
112
  onSessionReplaced(next: AgentSession): void;
113
113
  /** SessionStart Hook 的 additionalContext(系统提示 hooks 节);首次装配系统提示时读取。 */
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * 模型元数据的一行描述(`ama models list`、`ama config show`、`ama providers`):上下文、输出、思考、
3
- * 图片、渠道,以及每个字段来自哪里(config / 目录 / models.dev / 缺省)与 models.dev 匹配。
3
+ * 图片、渠道,以及每个字段来自哪里(config / 目录 / 按 id 继承的目录 / models.dev / 缺省)、继承的
4
+ * 目录条目与 models.dev 匹配。
4
5
  */
5
6
  import { type ModelMetadata } from "../../ai/providers/enrich.js";
6
7
  import type { Model, ProviderRegistryApi } from "../../ai/types.js";
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * 模型元数据的一行描述(`ama models list`、`ama config show`、`ama providers`):上下文、输出、思考、
3
- * 图片、渠道,以及每个字段来自哪里(config / 目录 / models.dev / 缺省)与 models.dev 匹配。
3
+ * 图片、渠道,以及每个字段来自哪里(config / 目录 / 按 id 继承的目录 / models.dev / 缺省)、继承的
4
+ * 目录条目与 models.dev 匹配。
4
5
  */
5
6
  import { sourceText } from "../../ai/providers/enrich.js";
6
7
  import { matchLabel } from "../../ai/providers/models-dev.js";
@@ -52,5 +53,5 @@ export function sourcesLine(metadata) {
52
53
  };
53
54
  const parts = Object.keys(label).map((field) => `${label[field]} ${sourceText(metadata.sources[field])}`);
54
55
  const match = metadata.looked ? matchLabel(metadata.match) : undefined;
55
- return t.sources(parts, match, metadata.toolCall === false);
56
+ return t.sources(parts, match, metadata.toolCall === false, metadata.catalog);
56
57
  }
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * codemode 父进程侧:起子进程、转发工具调用、超时杀树(设计 §5.5 沙箱第 1–4 条)。[B10]
3
3
  *
4
- * - 命令行:`<node> --permission --allow-fs-read=<入口> --disallow-code-generation-from-strings
5
- * <入口> --ama-codemode-sandbox`;Node 22.0–22.12 用 `--experimental-permission`。不授予文件写、
4
+ * - 命令行:`<node> --max-old-space-size=<MB> --permission --allow-fs-read=<入口>
5
+ * --disallow-code-generation-from-strings <入口> --ama-codemode-sandbox`;堆上限取 `codemode.maxHeapMb`
6
+ * (缺省 256,0 不加),超出时子进程 OOM 退出,映射为脚本错误(docs/memory-plan.md D10);Node 22.0–22.12 用 `--experimental-permission`。不授予文件写、
6
7
  * 子进程、worker、addon、inspector 权限;以空环境启动(拿不到密钥与会话路径);嵌入 Electron
7
8
  * 时设 `ELECTRON_RUN_AS_NODE=1` 运行同一可执行文件。
8
9
  * - 入口:bundle 里是 ama.cjs 同目录的 `ama-sandbox.cjs`(构建脚本定义 `__AMA_SANDBOX_ENTRY__`);
@@ -25,10 +26,12 @@ import { type ScriptOptions, type StoreSnapshot, type ToolDecl } from "./protoco
25
26
  export declare const MAX_COLLECTED_OUTPUT_CHARS: number;
26
27
  /** 外层 abort 后等子进程自行结束的宽限。 */
27
28
  export declare const ABORT_GRACE_MS = 500;
29
+ /** 子进程 V8 老生代堆上限(MB)缺省值;`codemode.maxHeapMb` 覆盖,0 不设上限。 */
30
+ export declare const DEFAULT_CODEMODE_HEAP_MB = 256;
28
31
  /** 子进程入口的绝对路径(realpath);找不到 → undefined。 */
29
32
  export declare function resolveSandboxEntry(moduleUrl?: string): string | undefined;
30
- /** 子进程参数(不含可执行文件)。 */
31
- export declare function sandboxArgs(entry: string, capability?: Pick<SandboxCapability, "permissionFlag">): string[];
33
+ /** 子进程参数(不含可执行文件)。V8 选项必须在 `--permission` 与脚本之前。 */
34
+ export declare function sandboxArgs(entry: string, capability?: Pick<SandboxCapability, "permissionFlag">, heapMb?: number): string[];
32
35
  /** codemode 子进程的 OS 沙箱策略:拒绝网络,任何位置都不可写。 */
33
36
  export declare const CODEMODE_OS_POLICY: OsSandboxPolicy;
34
37
  /** 完整命令行:`<node> <sandboxArgs>`,有 OS 沙箱时再经它包装。 */
@@ -53,7 +56,11 @@ export interface SandboxRunRequest {
53
56
  os?: Pick<OsSandboxStatus, "kind" | "path">;
54
57
  /** true:必须经 OS 沙箱启动(Node 22 / 24 的 strict 依赖它),包装不了直接失败。 */
55
58
  requireOsSandbox?: boolean;
59
+ /** 子进程堆上限(MB),缺省 DEFAULT_CODEMODE_HEAP_MB;0 不设上限。 */
60
+ maxHeapMb?: number;
56
61
  }
62
+ /** 子进程因堆上限 OOM 退出时给模型的文案(固定英文)。 */
63
+ export declare function heapLimitError(heapMb: number): string;
57
64
  export interface SandboxRunResult {
58
65
  ok: boolean;
59
66
  error?: string;
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * codemode 父进程侧:起子进程、转发工具调用、超时杀树(设计 §5.5 沙箱第 1–4 条)。[B10]
3
3
  *
4
- * - 命令行:`<node> --permission --allow-fs-read=<入口> --disallow-code-generation-from-strings
5
- * <入口> --ama-codemode-sandbox`;Node 22.0–22.12 用 `--experimental-permission`。不授予文件写、
4
+ * - 命令行:`<node> --max-old-space-size=<MB> --permission --allow-fs-read=<入口>
5
+ * --disallow-code-generation-from-strings <入口> --ama-codemode-sandbox`;堆上限取 `codemode.maxHeapMb`
6
+ * (缺省 256,0 不加),超出时子进程 OOM 退出,映射为脚本错误(docs/memory-plan.md D10);Node 22.0–22.12 用 `--experimental-permission`。不授予文件写、
6
7
  * 子进程、worker、addon、inspector 权限;以空环境启动(拿不到密钥与会话路径);嵌入 Electron
7
8
  * 时设 `ELECTRON_RUN_AS_NODE=1` 运行同一可执行文件。
8
9
  * - 入口:bundle 里是 ama.cjs 同目录的 `ama-sandbox.cjs`(构建脚本定义 `__AMA_SANDBOX_ENTRY__`);
@@ -29,6 +30,8 @@ import { LineSplitter, SANDBOX_MAIN_FLAG, decodeChildLine, encodeLine, } from ".
29
30
  export const MAX_COLLECTED_OUTPUT_CHARS = 16 * 1024 * 1024;
30
31
  /** 外层 abort 后等子进程自行结束的宽限。 */
31
32
  export const ABORT_GRACE_MS = 500;
33
+ /** 子进程 V8 老生代堆上限(MB)缺省值;`codemode.maxHeapMb` 覆盖,0 不设上限。 */
34
+ export const DEFAULT_CODEMODE_HEAP_MB = 256;
32
35
  /** 子进程入口的绝对路径(realpath);找不到 → undefined。 */
33
36
  export function resolveSandboxEntry(moduleUrl = import.meta.url) {
34
37
  const here = dirname(fileURLToPath(moduleUrl));
@@ -44,9 +47,11 @@ export function resolveSandboxEntry(moduleUrl = import.meta.url) {
44
47
  }
45
48
  return undefined;
46
49
  }
47
- /** 子进程参数(不含可执行文件)。 */
48
- export function sandboxArgs(entry, capability = detectSandboxCapability()) {
50
+ /** 子进程参数(不含可执行文件)。V8 选项必须在 `--permission` 与脚本之前。 */
51
+ export function sandboxArgs(entry, capability = detectSandboxCapability(), heapMb = DEFAULT_CODEMODE_HEAP_MB) {
52
+ const heap = Math.floor(heapMb);
49
53
  return [
54
+ ...(heap > 0 ? [`--max-old-space-size=${heap}`] : []),
50
55
  capability.permissionFlag,
51
56
  `--allow-fs-read=${entry}`,
52
57
  "--disallow-code-generation-from-strings",
@@ -75,9 +80,14 @@ export function sandboxEnv(versions = process.versions, platform = process.platf
75
80
  }
76
81
  return env;
77
82
  }
83
+ /** 子进程因堆上限 OOM 退出时给模型的文案(固定英文)。 */
84
+ export function heapLimitError(heapMb) {
85
+ return `Script exceeded the codemode memory limit (${heapMb} MB); process the data in smaller pieces`;
86
+ }
78
87
  export async function runSandbox(request) {
79
88
  const started = Date.now();
80
89
  const entry = request.entry ?? resolveSandboxEntry();
90
+ const heapMb = Math.floor(request.maxHeapMb ?? DEFAULT_CODEMODE_HEAP_MB);
81
91
  const base = {
82
92
  outputs: [],
83
93
  timedOut: false,
@@ -96,7 +106,7 @@ export async function runSandbox(request) {
96
106
  if (request.signal.aborted) {
97
107
  return { ...base, ok: false, error: "Script aborted", aborted: true, elapsedMs: 0 };
98
108
  }
99
- const command = sandboxCommand(request.nodePath ?? process.execPath, sandboxArgs(entry, request.capability), request.os ?? osSandboxStatus());
109
+ const command = sandboxCommand(request.nodePath ?? process.execPath, sandboxArgs(entry, request.capability, heapMb), request.os ?? osSandboxStatus());
100
110
  if (request.requireOsSandbox === true && !command.networkDenied) {
101
111
  return {
102
112
  ...base,
@@ -121,6 +131,7 @@ export async function runSandbox(request) {
121
131
  result.pid = pid;
122
132
  let collected = 0;
123
133
  let stderr = "";
134
+ let outOfMemory = false;
124
135
  let settled = false;
125
136
  let finish;
126
137
  const finished = new Promise((resolve) => (finish = resolve));
@@ -166,7 +177,11 @@ export async function runSandbox(request) {
166
177
  child.stdin.on("error", () => { });
167
178
  child.stderr.setEncoding("utf8");
168
179
  child.stderr.on("data", (chunk) => {
169
- stderr = (stderr + chunk).slice(-4000);
180
+ stderr = stderr + chunk;
181
+ // 原生栈可能把 OOM 那行挤出 4000 字符的尾部:在截断前判定
182
+ if (heapMb > 0 && /heap out of memory/i.test(stderr))
183
+ outOfMemory = true;
184
+ stderr = stderr.slice(-4000);
170
185
  });
171
186
  const splitter = new LineSplitter();
172
187
  const onLine = (line) => {
@@ -247,6 +262,10 @@ export async function runSandbox(request) {
247
262
  onLine(line);
248
263
  if (pid !== undefined && !isWindows)
249
264
  untrackProcessGroup(pid);
265
+ if (outOfMemory) {
266
+ settle({ ok: false, error: heapLimitError(heapMb) });
267
+ return;
268
+ }
250
269
  const detail = stderr.trim().split("\n").slice(-3).join(" | ");
251
270
  settle({
252
271
  ok: false,
@@ -48,6 +48,8 @@ export interface CodemodeToolOptions {
48
48
  /** 子进程入口(测试 / 嵌入方覆盖)。 */
49
49
  entry?: string;
50
50
  nodePath?: string;
51
+ /** 子进程堆上限(MB,`codemode.maxHeapMb`);缺省 256,0 不设上限。 */
52
+ maxHeapMb?: number;
51
53
  }
52
54
  export interface CodemodeDetails {
53
55
  ok: boolean;
@@ -222,6 +222,7 @@ export function createCodemodeTool(options) {
222
222
  requireOsSandbox: strictNeedsOsSandbox(capability),
223
223
  ...(options.entry !== undefined ? { entry: options.entry } : {}),
224
224
  ...(options.nodePath !== undefined ? { nodePath: options.nodePath } : {}),
225
+ ...(options.maxHeapMb !== undefined ? { maxHeapMb: options.maxHeapMb } : {}),
225
226
  callTool: async (name, args, signal) => {
226
227
  if (!callable.has(name))
227
228
  throw new Error(`Tool ${name} is not available in codemode`);
@@ -316,6 +317,9 @@ export function codemodeToolFactory(overrides = {}) {
316
317
  const budget = ctx.config.codemode?.inlineBudget;
317
318
  if (budget !== undefined)
318
319
  options.inlineBudget = budget;
320
+ const heapMb = ctx.config.codemode?.maxHeapMb;
321
+ if (heapMb !== undefined)
322
+ options.maxHeapMb = heapMb;
319
323
  const maxChars = ctx.config.tools?.maxToolResultChars;
320
324
  if (maxChars !== undefined)
321
325
  options.maxResultChars = maxChars;
@@ -11,7 +11,7 @@ import { PERMISSION_MODES_STRICT_FIRST } from "../permissions/types.js";
11
11
  import { msg } from "../i18n/index.js";
12
12
  import { defaultFor, isDynamicDefault, keyDoc } from "./key-docs.js";
13
13
  import { HOOK_TIMEOUT_MAX_MS, THINKING_LEVELS } from "./schema.js";
14
- import { CACHE_RETENTIONS, CHANNEL_NAME_PATTERN, CHECKPOINT_MODES, SANDBOX_ENABLED_MODES, SANDBOX_NETWORK_MODES, CODEMODE_MODES, IMAGE_RESIZE_MODES, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, STATUS_LINE_MODES, TOOLS_PRESET_INPUTS, LANGUAGE_SETTINGS, AGENT_BAR_MODES, CHATGPT_FLAVORS, MEMORY_SCOPES, MEMORY_SUBAGENT_MODES, } from "./types.js";
14
+ import { CACHE_RETENTIONS, CHANNEL_NAME_PATTERN, CHECKPOINT_MODES, SANDBOX_ENABLED_MODES, SANDBOX_NETWORK_MODES, CODEMODE_MODES, IMAGE_RESIZE_MODES, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, STATUS_LINE_MODES, TOOLS_PRESET_INPUTS, LANGUAGE_SETTINGS, AGENT_BAR_MODES, CHATGPT_FLAVORS, MEMORY_SCOPES, MEMORY_SUBAGENT_MODES, MIN_CONTEXT_BUDGET, } from "./types.js";
15
15
  import { AGENT_ID_PATTERN } from "./schema-w5.js";
16
16
  import { AGENTS_RESERVED_KEYS, SUBAGENT_BACKGROUND_MODES } from "./types-w5.js";
17
17
  export const CONFIG_SCHEMA_FILE = "config.schema.json";
@@ -89,6 +89,10 @@ function providerSchema() {
89
89
  description: m.modelsDev,
90
90
  anyOf: [{ type: "string", pattern: "^[^/]+/.+$" }, { const: false }],
91
91
  },
92
+ catalog: {
93
+ description: m.modelCatalog,
94
+ anyOf: [{ type: "string", pattern: "^[^/]+/.+$" }, { const: false }],
95
+ },
92
96
  headers: stringRecord,
93
97
  compat,
94
98
  promptCache: object({ short: num(), long: num(), minTokens: num() }),
@@ -156,6 +160,7 @@ function w5Sections() {
156
160
  defaultModel: str(),
157
161
  background: oneOf(SUBAGENT_BACKGROUND_MODES),
158
162
  autoBackgroundAfterMs: num(0),
163
+ retainSessions: num(0),
159
164
  }),
160
165
  models: object({ aliases: object({ fast: str(), strong: str() }), enabled: strings }),
161
166
  fallbackModel: str(),
@@ -244,6 +249,7 @@ function buildBaseSchema() {
244
249
  enabled: bool(),
245
250
  reserveTokens: num(0),
246
251
  keepRecentTokens: num(0),
252
+ contextBudget: num(MIN_CONTEXT_BUDGET),
247
253
  prune: object({
248
254
  keepResults: num(0),
249
255
  clearAtLeast: { anyOf: [{ const: "auto" }, { type: "number", minimum: 0 }] },
@@ -267,6 +273,7 @@ function buildBaseSchema() {
267
273
  mode: oneOf(CODEMODE_MODES),
268
274
  inlineBudget: num(0),
269
275
  requireStrict: bool(),
276
+ maxHeapMb: num(0),
270
277
  }),
271
278
  hooks: object({ timeoutMs: num(1, HOOK_TIMEOUT_MAX_MS) }),
272
279
  ui: object({
@@ -295,7 +302,7 @@ function buildBaseSchema() {
295
302
  missNotices: bool(),
296
303
  warmSubagents: bool(),
297
304
  }),
298
- request: object({ idleTimeoutMs: num(0) }),
305
+ request: object({ idleTimeoutMs: num(0), streamIdleTimeoutMs: num(0) }),
299
306
  checkpoints: object({
300
307
  mode: oneOf(CHECKPOINT_MODES),
301
308
  maxFileBytes: num(0),
@@ -14,15 +14,18 @@ import { DEFAULT_CODEX_CLIENT_VERSION } from "../auth/chatgpt/presets.js";
14
14
  import { messagesFor, msg } from "../i18n/index.js";
15
15
  import { DEFAULT_INLINE_BUDGET } from "../codemode/declarations.js";
16
16
  import { DEFAULT_CONFIG, mergeConfig } from "./merge.js";
17
- import { DEFAULT_CACHE_CONFIG, DEFAULT_CHECKPOINTS_CONFIG } from "./types.js";
17
+ import { DEFAULT_CACHE_CONFIG, DEFAULT_CHECKPOINTS_CONFIG, DEFAULT_STREAM_IDLE_TIMEOUT_MS, } from "./types.js";
18
18
  /** 代码里生效、但 DEFAULT_CONFIG 不写的缺省(写进去会改变合并结果或 `config.codemode` 的有无)。 */
19
19
  const IMPLICIT_DEFAULTS = {
20
20
  providers: {},
21
21
  permission: { builtinDeny: true, autoSafeCommands: [] },
22
22
  tools: { default: [] },
23
- codemode: { inlineBudget: DEFAULT_INLINE_BUDGET, requireStrict: false },
23
+ codemode: { inlineBudget: DEFAULT_INLINE_BUDGET, requireStrict: false, maxHeapMb: 256 },
24
24
  cache: { ...DEFAULT_CACHE_CONFIG },
25
- request: { idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS },
25
+ request: {
26
+ idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
27
+ streamIdleTimeoutMs: DEFAULT_STREAM_IDLE_TIMEOUT_MS,
28
+ },
26
29
  ui: {
27
30
  compact: false,
28
31
  logo: "auto",
@@ -56,7 +59,13 @@ const IMPLICIT_DEFAULTS = {
56
59
  images: { resize: "auto" },
57
60
  plan: { bash: "readonly", unattended: "stop" },
58
61
  agents: { maxConcurrent: 3, dirs: [] },
59
- subagents: { maxConcurrent: 4, maxPending: 16, background: "auto", autoBackgroundAfterMs: 0 },
62
+ subagents: {
63
+ maxConcurrent: 4,
64
+ maxPending: 16,
65
+ background: "auto",
66
+ autoBackgroundAfterMs: 0,
67
+ retainSessions: 4,
68
+ },
60
69
  reminders: { todo: true, fileChanges: true, contextPressure: true, budget: true },
61
70
  todo: { reminder: 10 },
62
71
  };
@@ -83,12 +83,20 @@ export function validateConfigW5(c, config) {
83
83
  c.oneOf(s, "thinkingLevel", p, THINKING_LEVELS);
84
84
  });
85
85
  checkAgents(c, config);
86
- checkSection(c, config, "subagents", ["maxConcurrent", "maxPending", "defaultModel", "background", "autoBackgroundAfterMs"], (s, p) => {
86
+ checkSection(c, config, "subagents", [
87
+ "maxConcurrent",
88
+ "maxPending",
89
+ "defaultModel",
90
+ "background",
91
+ "autoBackgroundAfterMs",
92
+ "retainSessions",
93
+ ], (s, p) => {
87
94
  c.number(s, "maxConcurrent", p, 1, 64);
88
95
  c.number(s, "maxPending", p, 0, 1024);
89
96
  c.string(s, "defaultModel", p);
90
97
  c.oneOf(s, "background", p, SUBAGENT_BACKGROUND_MODES);
91
98
  c.number(s, "autoBackgroundAfterMs", p, 0);
99
+ c.number(s, "retainSessions", p, 0);
92
100
  });
93
101
  checkSection(c, config, "models", ["aliases", "enabled"], (s, p) => {
94
102
  c.stringArray(s, "enabled", p);
@@ -5,7 +5,7 @@
5
5
  * 校验结果是诊断列表:`severity: "error"` 拒绝该文件(启动退出码 3),`"warning"` 只提示
6
6
  * (未知字段等)。`path` 是 JSON 指针风格的字段路径(`permission.mode`、`hooks.PreToolUse[0]`)。
7
7
  */
8
- import type { AmaConfig, AuthFile, ProfileFile, TrustFile } from "./types.js";
8
+ import { type AmaConfig, type AuthFile, type ProfileFile, type TrustFile } from "./types.js";
9
9
  import { type Diagnostic } from "./checker.js";
10
10
  import type { HookConfig } from "../hooks/types.js";
11
11
  export type { AmaConfig, AuthFile, ProfileFile, TrustFile, TrustEntry, ProviderConfig, PermissionConfig, CompactionConfig, RetryConfig, ToolsConfig, ToolsPreset, ToolsPresetInput, CodemodeConfig, CodemodeMode, HooksSettings, UiConfig, SkillsConfig, CacheConfig, CheckpointsConfig, ModelConfig, ModelOverride, ChannelConfig, } from "./types.js";
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { isAbsolute } from "node:path";
9
9
  import { msg } from "../i18n/index.js";
10
+ import { MIN_CONTEXT_BUDGET, } from "./types.js";
10
11
  import { Checker, THINKING_LEVELS, checkSection, join, } from "./checker.js";
11
12
  import { W5_COMPACTION_KEYS, W5_CONFIG_KEYS, W5_UI_KEYS, checkCompactionW5, checkUiW5, validateConfigW5, } from "./schema-w5.js";
12
13
  import { W6_CONFIG_KEYS, W6_UI_KEYS, checkOAuthEntry, checkProfileMemory, checkUiW6, validateConfigW6, } from "./schema-w6.js";
@@ -93,6 +94,12 @@ function checkModels(c, value, path, channels) {
93
94
  (typeof modelsDev !== "string" || !/^[^/]+\/.+$/.test(modelsDev))) {
94
95
  c.error(join(p, "modelsDev"), msg().config.schema.modelsDev);
95
96
  }
97
+ const catalog = item["catalog"];
98
+ if (catalog !== undefined &&
99
+ catalog !== false &&
100
+ (typeof catalog !== "string" || !/^[^/]+\/.+$/.test(catalog))) {
101
+ c.error(join(p, "catalog"), msg().config.schema.catalog);
102
+ }
96
103
  c.stringArray(item, "channels", p);
97
104
  const used = item["channels"];
98
105
  if (Array.isArray(used)) {
@@ -221,11 +228,18 @@ export function validateConfig(value) {
221
228
  c.error(join(p, "builtinDeny"), msg().config.schema.builtinDeny);
222
229
  }
223
230
  });
224
- const compactionKeys = ["enabled", "reserveTokens", "keepRecentTokens", ...W5_COMPACTION_KEYS];
231
+ const compactionKeys = [
232
+ "enabled",
233
+ "reserveTokens",
234
+ "keepRecentTokens",
235
+ "contextBudget",
236
+ ...W5_COMPACTION_KEYS,
237
+ ];
225
238
  checkSection(c, value, "compaction", compactionKeys, (s, p) => {
226
239
  c.boolean(s, "enabled", p);
227
240
  c.number(s, "reserveTokens", p);
228
241
  c.number(s, "keepRecentTokens", p);
242
+ c.number(s, "contextBudget", p, MIN_CONTEXT_BUDGET);
229
243
  checkCompactionW5(c, s, p);
230
244
  });
231
245
  checkSection(c, value, "retry", ["enabled", "maxRetries", "baseDelayMs", "maxDelayMs"], (s, p) => {
@@ -241,10 +255,11 @@ export function validateConfig(value) {
241
255
  c.number(s, "bashTimeoutMs", p, 1);
242
256
  c.stringArray(s, "disabled", p);
243
257
  });
244
- checkSection(c, value, "codemode", ["mode", "inlineBudget", "requireStrict"], (s, p) => {
258
+ checkSection(c, value, "codemode", ["mode", "inlineBudget", "requireStrict", "maxHeapMb"], (s, p) => {
245
259
  c.oneOf(s, "mode", p, CODEMODE_MODES);
246
260
  c.number(s, "inlineBudget", p, 0);
247
261
  c.boolean(s, "requireStrict", p);
262
+ c.number(s, "maxHeapMb", p, 0);
248
263
  });
249
264
  checkSection(c, value, "hooks", ["timeoutMs"], (s, p) => {
250
265
  c.number(s, "timeoutMs", p, 1, HOOK_TIMEOUT_MAX_MS);
@@ -281,8 +296,9 @@ export function validateConfig(value) {
281
296
  checkUiW6(c, s, p);
282
297
  });
283
298
  checkSection(c, value, "skills", ["dirs"], (s, p) => c.stringArray(s, "dirs", p));
284
- checkSection(c, value, "request", ["idleTimeoutMs"], (s, p) => {
299
+ checkSection(c, value, "request", ["idleTimeoutMs", "streamIdleTimeoutMs"], (s, p) => {
285
300
  c.number(s, "idleTimeoutMs", p, 0);
301
+ c.number(s, "streamIdleTimeoutMs", p, 0);
286
302
  });
287
303
  checkSection(c, value, "checkpoints", ["mode", "maxFileBytes", "keep"], (s, p) => {
288
304
  c.oneOf(s, "mode", p, CHECKPOINT_MODES);
@@ -57,6 +57,7 @@ const ROWS = [
57
57
  row("codemode.mode", "tools", "restart", "tighten", { prefix: true }),
58
58
  row("codemode.inlineBudget", "tools", "restart", "deny", { prefix: true }),
59
59
  row("codemode.requireStrict", "tools", "restart", "deny"),
60
+ row("codemode.maxHeapMb", "tools", "restart", "deny"),
60
61
  row("tools.maxToolResultChars", "tools", "nextSession", "deny"),
61
62
  row("tools.bashTimeoutMs", "tools", "restart", "deny"),
62
63
  row("images.resize", "tools", "restart", "deny"),
@@ -64,6 +65,7 @@ const ROWS = [
64
65
  row("compaction.enabled", "context", "now", "any"),
65
66
  row("compaction.reserveTokens", "context", "nextSession", "any"),
66
67
  row("compaction.keepRecentTokens", "context", "nextSession", "any"),
68
+ row("compaction.contextBudget", "context", "nextSession", "any", { kind: "optionalNumber" }),
67
69
  row("compaction.prune.keepResults", "context", "nextSession", "deny"),
68
70
  row("compaction.prune.clearAtLeast", "context", "nextSession", "deny"),
69
71
  row("cache.warming", "context", "now", "deny", { envOverride: "AMA_CACHE_WARMING" }),
@@ -74,6 +76,9 @@ const ROWS = [
74
76
  row("request.idleTimeoutMs", "context", "nextSession", "deny", {
75
77
  envOverride: "AMA_IDLE_TIMEOUT_MS",
76
78
  }),
79
+ row("request.streamIdleTimeoutMs", "context", "nextSession", "deny", {
80
+ envOverride: "AMA_STREAM_IDLE_TIMEOUT_MS",
81
+ }),
77
82
  row("retry.enabled", "context", "now", "deny"),
78
83
  row("retry.maxRetries", "context", "nextSession", "deny"),
79
84
  row("retry.baseDelayMs", "context", "nextSession", "deny"),
@@ -95,6 +100,7 @@ const ROWS = [
95
100
  row("agents.sessionBudgetUsd", "agents", "nextSession", "deny", { kind: "optionalNumber" }),
96
101
  row("subagents.maxConcurrent", "agents", "nextSession", "deny"),
97
102
  row("subagents.maxPending", "agents", "nextSession", "deny"),
103
+ row("subagents.retainSessions", "agents", "nextSession", "deny"),
98
104
  // [W7-B2] 改 task 工具描述(缺省后台 / 前台两种文案),故 prefix
99
105
  row("subagents.background", "agents", "restart", "any", { prefix: true }),
100
106
  row("subagents.autoBackgroundAfterMs", "agents", "restart", "any"),
@@ -77,6 +77,8 @@ export interface SubagentsConfig {
77
77
  background?: SubagentBackgroundMode;
78
78
  /** [W7-B2] 前台任务运行超过该毫秒数自动转后台;缺省 0 关闭。用户 / 项目级都认。 */
79
79
  autoBackgroundAfterMs?: number;
80
+ /** [M-F] 已结束子会话保留在内存的句柄数(LRU),缺省 4;超出的续聊时从会话文件重开。只认用户级。 */
81
+ retainSessions?: number;
80
82
  }
81
83
  /** `models.enabled` 的一项:`provider/model[@channel]` 或 `provider/*`。 */
82
84
  export declare const MODELS_ENABLED_REF: RegExp;
@@ -31,6 +31,11 @@ export type ModelConfig = Partial<Omit<Model, "id" | "provider" | "channel" | "c
31
31
  channels?: string[];
32
32
  /** models.dev 条目 `provider/model`(显式匹配);false 关闭 models.dev 补全。 */
33
33
  modelsDev?: string | false;
34
+ /**
35
+ * [ME-C0] 从内置目录继承模型固有属性:缺省按 id 别名自动匹配;`"provider/id"` 显式指定;false 关闭
36
+ * (docs/model-efficiency-plan.md D10,实现归 ME-D)。
37
+ */
38
+ catalog?: string | false;
34
39
  };
35
40
  /** 只改元数据的覆盖项(含模型级 `api` 与 `channels`)。 */
36
41
  export type ModelOverride = ModelConfig;
@@ -83,6 +88,8 @@ export interface CompactionConfig {
83
88
  enabled?: boolean;
84
89
  reserveTokens?: number;
85
90
  keepRecentTokens?: number;
91
+ /** [ME-C0] 软窗口(≥ 32768):档一 / 档二 / context_pressure 的窗口取 min(模型窗口, 它);缺省 = 模型窗口。 */
92
+ contextBudget?: number;
86
93
  /** [W5-C0] 档一裁剪参数(W5-H1);只认用户级。 */
87
94
  prune?: PruneConfig;
88
95
  /** [W5-C0] 不被档一裁剪的工具名(W5-H1);只认用户级。 */
@@ -129,6 +136,8 @@ export interface CodemodeConfig {
129
136
  inlineBudget?: number;
130
137
  /** true:运行时 Node 的权限模型不隔离网络(Node 22 / 24)时直接禁用 codemode。 */
131
138
  requireStrict?: boolean;
139
+ /** [M-F] 脚本子进程的 V8 堆上限(MB,`--max-old-space-size`),缺省 256,0 不加;只认用户级。 */
140
+ maxHeapMb?: number;
132
141
  }
133
142
  export interface HooksSettings {
134
143
  timeoutMs?: number;
@@ -183,9 +192,18 @@ export declare const DEFAULT_CACHE_CONFIG: Readonly<Required<CacheConfig>>;
183
192
  * 模型请求(W4-C)。只认用户级 / profile;环境变量 `AMA_IDLE_TIMEOUT_MS` 覆盖 `idleTimeoutMs`。
184
193
  */
185
194
  export interface RequestConfig {
186
- /** 等响应头与流中两块数据之间的最长间隔(毫秒),收到任何字节即重新计时;缺省 300 000,0 关闭。 */
195
+ /** 等响应头的最长时间(毫秒);缺省 300 000,0 关闭。流中的间隔见 `streamIdleTimeoutMs`。 */
187
196
  idleTimeoutMs?: number;
197
+ /**
198
+ * [ME-C0] 流中两块数据之间的最长间隔(毫秒);缺省 180 000,0 关闭;环境变量
199
+ * `AMA_STREAM_IDLE_TIMEOUT_MS` 覆盖(cli/compose-request.ts)。
200
+ */
201
+ streamIdleTimeoutMs?: number;
188
202
  }
203
+ /** [ME-C0] `compaction.contextBudget` 的下限。 */
204
+ export declare const MIN_CONTEXT_BUDGET = 32768;
205
+ /** [ME-C0] `request.streamIdleTimeoutMs` 的缺省(毫秒)。 */
206
+ export declare const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 180000;
189
207
  export declare const CHECKPOINT_MODES: readonly CheckpointMode[];
190
208
  /**
191
209
  * 检查点(docs/rewind-plan.md §5)。环境变量 `AMA_CHECKPOINTS` 覆盖 `mode`;项目级只接受