@armadra/agent 0.7.4 → 0.7.6

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 (81) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CHANGELOG.zh-CN.md +32 -0
  3. package/dist/agent/session-classifier.d.ts +2 -0
  4. package/dist/agent/session-classifier.js +24 -3
  5. package/dist/agent/session-extensions.d.ts +1 -0
  6. package/dist/agent/session-extensions.js +3 -0
  7. package/dist/agent/session-subagent.js +9 -2
  8. package/dist/agent/subagent-background.d.ts +5 -1
  9. package/dist/agent/subagent-background.js +13 -1
  10. package/dist/agent/subagent-fork.d.ts +10 -5
  11. package/dist/agent/subagent-fork.js +22 -6
  12. package/dist/agent/subagent-registry.d.ts +1 -1
  13. package/dist/agent/subagent-registry.js +1 -1
  14. package/dist/agents/catalog.d.ts +1 -0
  15. package/dist/ai/apis/anthropic-request.js +6 -1
  16. package/dist/ai/apis/cache-params.js +2 -1
  17. package/dist/ai/apis/google-request.js +2 -1
  18. package/dist/ai/apis/max-tokens.d.ts +4 -0
  19. package/dist/ai/apis/max-tokens.js +12 -1
  20. package/dist/ai/apis/openai-request.js +3 -1
  21. package/dist/ai/apis/openai-responses-request.js +2 -1
  22. package/dist/ai/json-body.d.ts +33 -1
  23. package/dist/ai/json-body.js +96 -18
  24. package/dist/ai/providers/enrich.d.ts +6 -2
  25. package/dist/ai/providers/enrich.js +13 -2
  26. package/dist/ai/providers/max-tokens-cache.d.ts +22 -0
  27. package/dist/ai/providers/max-tokens-cache.js +95 -0
  28. package/dist/ai/providers/registry.js +3 -3
  29. package/dist/bundle/ama.cjs +982 -442
  30. package/dist/checkpoints/shadow-git.d.ts +5 -1
  31. package/dist/checkpoints/shadow-git.js +11 -2
  32. package/dist/cli/bootstrap.js +13 -5
  33. package/dist/cli/compose-providers.d.ts +1 -0
  34. package/dist/cli/compose-providers.js +3 -0
  35. package/dist/cli/compose-store.d.ts +6 -2
  36. package/dist/cli/compose-store.js +8 -4
  37. package/dist/cli/compose.js +2 -2
  38. package/dist/cli/deps.d.ts +1 -1
  39. package/dist/config/json-schema.js +1 -0
  40. package/dist/config/key-docs.js +1 -0
  41. package/dist/config/schema-w5.js +2 -0
  42. package/dist/config/settings-registry.js +1 -0
  43. package/dist/config/types-w5.d.ts +5 -0
  44. package/dist/i18n/catalog.d.ts +2 -0
  45. package/dist/i18n/messages/config-keys.d.ts +2 -0
  46. package/dist/i18n/messages/config-keys.js +2 -0
  47. package/dist/i18n/messages/config.d.ts +2 -0
  48. package/dist/i18n/messages/interactive.d.ts +2 -0
  49. package/dist/i18n/messages/interactive.js +2 -0
  50. package/dist/i18n/messages/session.js +2 -2
  51. package/dist/modes/acp/acp-server.js +17 -15
  52. package/dist/modes/acp/acp-sessions.d.ts +14 -1
  53. package/dist/modes/acp/acp-sessions.js +44 -1
  54. package/dist/modes/interactive/agent-ui.js +7 -0
  55. package/dist/session/manager.d.ts +18 -0
  56. package/dist/session/manager.js +75 -14
  57. package/dist/session/offload.d.ts +59 -0
  58. package/dist/session/offload.js +218 -0
  59. package/dist/session/store.d.ts +12 -4
  60. package/dist/session/store.js +18 -8
  61. package/dist/tools/background-jobs.d.ts +4 -4
  62. package/dist/tools/background-jobs.js +4 -4
  63. package/dist/tools/bash.d.ts +2 -1
  64. package/dist/tools/bash.js +4 -3
  65. package/dist/tools/read-lines.d.ts +39 -0
  66. package/dist/tools/read-lines.js +163 -86
  67. package/dist/tools/read.js +3 -3
  68. package/dist/tools/task-ctl.js +2 -1
  69. package/dist/tools/task.d.ts +9 -0
  70. package/dist/tools/task.js +27 -1
  71. package/docs/acp.md +6 -3
  72. package/docs/agents.md +14 -3
  73. package/docs/en/acp.md +2 -2
  74. package/docs/en/permissions.md +1 -1
  75. package/docs/en/providers.md +4 -4
  76. package/docs/en/sessions.md +2 -1
  77. package/docs/permissions.md +1 -1
  78. package/docs/providers.md +9 -5
  79. package/docs/session-format.md +11 -0
  80. package/docs/sessions.md +2 -1
  81. package/package.json +1 -1
@@ -41,7 +41,7 @@ var AMA_VERSION;
41
41
  var init_version = __esm({
42
42
  "src/version.ts"() {
43
43
  "use strict";
44
- AMA_VERSION = true ? "0.7.4" : readPackageVersion();
44
+ AMA_VERSION = true ? "0.7.6" : readPackageVersion();
45
45
  }
46
46
  });
47
47
 
@@ -1969,7 +1969,8 @@ var init_interactive = __esm({
1969
1969
  backgroundExited: (jobId, code, command3) => `Background command ${jobId} exited (code ${code}): ${command3}`,
1970
1970
  backgroundStopped: (jobId, command3) => `Background command ${jobId} stopped: ${command3}`,
1971
1971
  compactionFailed: (error2) => `Compaction failed: ${error2}`,
1972
- compactionCancelled: "Compaction cancelled"
1972
+ compactionCancelled: "Compaction cancelled",
1973
+ imageReadBackFailed: (id) => `Session image for entry ${id} could not be read back (file changed); it stays empty`
1973
1974
  },
1974
1975
  statusLine: {
1975
1976
  usage: "Usage: /statusline [full|compact]",
@@ -2104,7 +2105,8 @@ var init_interactive = __esm({
2104
2105
  backgroundExited: (jobId, code, command3) => `后台命令 ${jobId} 已退出(码 ${code}):${command3}`,
2105
2106
  backgroundStopped: (jobId, command3) => `后台命令 ${jobId} 已停止:${command3}`,
2106
2107
  compactionFailed: (error2) => `压缩失败:${error2}`,
2107
- compactionCancelled: "压缩已取消"
2108
+ compactionCancelled: "压缩已取消",
2109
+ imageReadBackFailed: (id) => `会话文件已改变,条目 ${id} 的图片读不回,保留为空`
2108
2110
  },
2109
2111
  statusLine: {
2110
2112
  usage: "用法:/statusline [full|compact]",
@@ -3814,6 +3816,7 @@ var init_config_keys = __esm({
3814
3816
  "subagents.maxPending": "Queue limit; task fails beyond it",
3815
3817
  "subagents.retainSessions": "Finished sub-sessions kept in memory (least recently used first out); others reopen from their session file when continued",
3816
3818
  "subagents.defaultModel": "Default sub-session model provider/model[@channel]",
3819
+ "subagents.forkMaxContextRatio": "Fork sub-agents fall back to fresh when the parent's last request used more than this share of (window − compaction.reserveTokens); 0.05–0.95, default 0.5",
3817
3820
  "subagents.background": "Whether task runs in the background by default: auto = background in the TUI / RPC / ACP and foreground with -p; always / never are fixed. The call argument and the agent type's background: override it; user, project and host level",
3818
3821
  "subagents.autoBackgroundAfterMs": "Move a foreground task to the background after it has run this many milliseconds; 0 disables; user and project level",
3819
3822
  models: "Model aliases and the model picker list; user level only (effective from wave 5 W5-G)",
@@ -3942,6 +3945,7 @@ var init_config_keys = __esm({
3942
3945
  "subagents.maxPending": "排队上限,超出时 task 报错",
3943
3946
  "subagents.retainSessions": "内存中保留的已结束子会话数(最久未用的先释放),其余续聊时从会话文件重开",
3944
3947
  "subagents.defaultModel": "子会话缺省模型 provider/model[@channel]",
3948
+ "subagents.forkMaxContextRatio": "fork 子 Agent 的回落比例:父上一次请求超过(窗口 − compaction.reserveTokens)的这一比例时改为 fresh;0.05–0.95,缺省 0.5",
3945
3949
  "subagents.background": "task 缺省是否后台:auto = TUI / RPC / ACP 下后台、-p 下前台;always / never 固定。调用参数与类型定义的 background: 优先;用户、项目、宿主级都认",
3946
3950
  "subagents.autoBackgroundAfterMs": "前台任务运行超过该毫秒数自动转后台;0 关闭;用户、项目级都认",
3947
3951
  models: "模型别名与模型选择器清单;只认用户级(第五波 W5-G 起生效)",
@@ -4439,7 +4443,7 @@ var init_session = __esm({
4439
4443
  cwdIsHome: "the working directory is the home directory",
4440
4444
  gitNotFound: "git not found",
4441
4445
  tooManyFiles: (files, max) => `the working directory has ${plural(files, "file")} (limit ${max})`,
4442
- tooSlow: (seconds2, max) => `the snapshot took ${seconds2} s (limit ${max} s)`,
4446
+ tooSlow: (seconds2, max) => `snapshots took longer than ${max} s twice in a row (latest ${seconds2} s)`,
4443
4447
  gitFailed: (command3, code, detail) => `git ${command3} failed (${code})${detail === "" ? "" : `: ${detail}`}`,
4444
4448
  noShadowCommit: "the checkpoint has no shadow commit",
4445
4449
  shadowCommitMissing: (commit) => `shadow commit ${commit} does not exist`,
@@ -4571,7 +4575,7 @@ var init_session = __esm({
4571
4575
  cwdIsHome: "工作目录是家目录",
4572
4576
  gitNotFound: "找不到 git",
4573
4577
  tooManyFiles: (files, max) => `工作目录有 ${files} 个文件(上限 ${max})`,
4574
- tooSlow: (seconds2, max) => `快照用了 ${seconds2} 秒(上限 ${max} 秒)`,
4578
+ tooSlow: (seconds2, max) => `连续两次快照超过 ${max} 秒(最近一次 ${seconds2} 秒)`,
4575
4579
  gitFailed: (command3, code, detail) => `git ${command3} 失败(${code})${detail === "" ? "" : `:${detail}`}`,
4576
4580
  noShadowCommit: "检查点没有影子提交",
4577
4581
  shadowCommitMissing: (commit) => `影子提交 ${commit} 不存在`,
@@ -6776,7 +6780,8 @@ function validateConfigW5(c, config) {
6776
6780
  "defaultModel",
6777
6781
  "background",
6778
6782
  "autoBackgroundAfterMs",
6779
- "retainSessions"
6783
+ "retainSessions",
6784
+ "forkMaxContextRatio"
6780
6785
  ],
6781
6786
  (s2, p) => {
6782
6787
  c.number(s2, "maxConcurrent", p, 1, 64);
@@ -6785,6 +6790,7 @@ function validateConfigW5(c, config) {
6785
6790
  c.oneOf(s2, "background", p, SUBAGENT_BACKGROUND_MODES);
6786
6791
  c.number(s2, "autoBackgroundAfterMs", p, 0);
6787
6792
  c.number(s2, "retainSessions", p, 0);
6793
+ c.number(s2, "forkMaxContextRatio", p, 0.05, 0.95);
6788
6794
  }
6789
6795
  );
6790
6796
  checkSection(c, config, "models", ["aliases", "enabled"], (s2, p) => {
@@ -10284,8 +10290,9 @@ function readSessionLines(file, options = {}) {
10284
10290
  forEachLineSync(file, (buf, index2, last, offset) => {
10285
10291
  if (last) unterminated = true;
10286
10292
  if (isBlankLine(buf)) return;
10293
+ let line2;
10287
10294
  try {
10288
- lines2.push(JSON.parse(buf.toString("utf8")));
10295
+ line2 = JSON.parse(buf.toString("utf8"));
10289
10296
  } catch (error2) {
10290
10297
  if (last) {
10291
10298
  repairedTail = true;
@@ -10296,6 +10303,8 @@ function readSessionLines(file, options = {}) {
10296
10303
  cause: error2
10297
10304
  });
10298
10305
  }
10306
+ options.onLine?.(line2, { offset, length: buf.length });
10307
+ lines2.push(line2);
10299
10308
  });
10300
10309
  } catch (error2) {
10301
10310
  if (error2 instanceof AmaError) throw error2;
@@ -10304,19 +10313,20 @@ function readSessionLines(file, options = {}) {
10304
10313
  if (!repairedTail && unterminated && options.repair === true) {
10305
10314
  appendRaw(file, "\n");
10306
10315
  }
10307
- return { lines: lines2, repairedTail };
10316
+ if (options.onLine === void 0) return { lines: lines2, repairedTail };
10317
+ return { lines: lines2, repairedTail, bytes: (0, import_node_fs10.statSync)(file).size };
10308
10318
  }
10309
10319
  function appendRaw(file, text) {
10310
10320
  const fd = (0, import_node_fs10.openSync)(file, "a");
10311
10321
  try {
10312
- (0, import_node_fs10.writeSync)(fd, text);
10322
+ return (0, import_node_fs10.writeSync)(fd, text);
10313
10323
  } finally {
10314
10324
  (0, import_node_fs10.closeSync)(fd);
10315
10325
  }
10316
10326
  }
10317
10327
  function appendLines(file, lines2) {
10318
- if (lines2.length === 0) return;
10319
- appendRaw(file, lines2.map((line2) => JSON.stringify(line2)).join("\n") + "\n");
10328
+ if (lines2.length === 0) return 0;
10329
+ return appendRaw(file, lines2.map((line2) => JSON.stringify(line2)).join("\n") + "\n");
10320
10330
  }
10321
10331
  function writeNewSessionFile(file, lines2) {
10322
10332
  (0, import_node_fs10.mkdirSync)((0, import_node_path13.dirname)(file), { recursive: true, mode: 448 });
@@ -10324,8 +10334,10 @@ function writeNewSessionFile(file, lines2) {
10324
10334
  throw new AmaError("session_exists", `session file already exists: ${file}`);
10325
10335
  }
10326
10336
  const tmp = `${file}.${process.pid}.tmp`;
10327
- (0, import_node_fs10.writeFileSync)(tmp, lines2.map((line2) => JSON.stringify(line2)).join("\n") + "\n", { mode: 384 });
10337
+ const texts2 = lines2.map((line2) => JSON.stringify(line2));
10338
+ (0, import_node_fs10.writeFileSync)(tmp, texts2.join("\n") + "\n", { mode: 384 });
10328
10339
  (0, import_node_fs10.renameSync)(tmp, file);
10340
+ return texts2.map((text) => Buffer.byteLength(text));
10329
10341
  }
10330
10342
  function installExitHandler() {
10331
10343
  if (exitHandlerInstalled) return;
@@ -10788,12 +10800,26 @@ var init_suggest = __esm({
10788
10800
 
10789
10801
  // src/ai/json-body.ts
10790
10802
  function isPlain(value) {
10803
+ plainScans.count++;
10791
10804
  for (let i = 0; i < value.length; i += CHECK_CHUNK) {
10792
10805
  const chunk = value.slice(i, i + CHECK_CHUNK);
10793
10806
  if (JSON.stringify(chunk).length !== chunk.length + 2) return false;
10794
10807
  }
10795
10808
  return true;
10796
10809
  }
10810
+ function largeString(prefix, key, text) {
10811
+ if (text.length < LARGE_STRING_BYTES || !isShortPlain(prefix)) return prefix + text;
10812
+ return new LargeString(prefix, key, text);
10813
+ }
10814
+ function plainLargeBytes(value) {
10815
+ if (!isShortPlain(value.prefix)) return void 0;
10816
+ const known = plainBytes.get(value.key);
10817
+ if (known !== void 0 && known.length === value.text.length) return known.bytes;
10818
+ if (!isPlain(value.text)) return void 0;
10819
+ const bytes = Buffer.byteLength(value.text, "utf8");
10820
+ plainBytes.set(value.key, { length: value.text.length, bytes });
10821
+ return bytes;
10822
+ }
10797
10823
  function isContainer(value) {
10798
10824
  if (typeof value !== "object" || value === null) return false;
10799
10825
  if (typeof value.toJSON === "function") return false;
@@ -10803,6 +10829,7 @@ function isContainer(value) {
10803
10829
  }
10804
10830
  function collectLarge(value, marked, depth) {
10805
10831
  if (isLargeString(value)) return true;
10832
+ if (value instanceof LargeString) return value.text.length >= LARGE_STRING_BYTES;
10806
10833
  if (depth >= MAX_DEPTH || !isContainer(value)) return false;
10807
10834
  let found = false;
10808
10835
  if (Array.isArray(value)) {
@@ -10835,6 +10862,13 @@ function writeEntry(key, value, marked, out, prefix, depth) {
10835
10862
  }
10836
10863
  return true;
10837
10864
  }
10865
+ if (value instanceof LargeString && value.text.length >= LARGE_STRING_BYTES) {
10866
+ const bytes = plainLargeBytes(value);
10867
+ out.text(prefix);
10868
+ if (bytes !== void 0) out.large(value.text, value.prefix, bytes);
10869
+ else out.text(JSON.stringify(value.toJSON()));
10870
+ return true;
10871
+ }
10838
10872
  if (depth < MAX_DEPTH && isContainer(value) && marked.has(value)) {
10839
10873
  out.text(prefix);
10840
10874
  writeContainer(value, marked, out, depth);
@@ -10868,20 +10902,16 @@ function jsonParts(body) {
10868
10902
  const marked = /* @__PURE__ */ new Set();
10869
10903
  if (!collectLarge(body, marked, 0)) return void 0;
10870
10904
  const out = new Pieces();
10871
- if (isLargeString(body)) {
10872
- if (!isPlain(body)) return void 0;
10873
- out.large(body);
10874
- } else writeContainer(body, marked, out, 0);
10905
+ writeEntry("", body, marked, out, "", 0);
10875
10906
  const parts = out.finish();
10876
- let length = 0;
10877
- for (const part of parts) length += Buffer.byteLength(part, "utf8");
10878
- return { parts, length };
10907
+ return { parts, length: out.bytes };
10879
10908
  }
10880
10909
  function jsonFetchBody(body) {
10881
10910
  const split = jsonParts(body);
10882
10911
  if (split === void 0) return { body: JSON.stringify(body) };
10883
10912
  const { parts } = split;
10884
10913
  let next = 0;
10914
+ let offset = 0;
10885
10915
  const stream5 = new ReadableStream({
10886
10916
  pull(controller) {
10887
10917
  const part = parts[next];
@@ -10889,8 +10919,15 @@ function jsonFetchBody(body) {
10889
10919
  controller.close();
10890
10920
  return;
10891
10921
  }
10892
- parts[next++] = "";
10893
- controller.enqueue(Buffer.from(part, "utf8"));
10922
+ let end = Math.min(part.length, offset + STREAM_CHUNK_CHARS);
10923
+ const last = part.charCodeAt(end - 1);
10924
+ if (end < part.length && last >= 55296 && last <= 56319) end--;
10925
+ const piece = offset === 0 && end === part.length ? part : part.slice(offset, end);
10926
+ if (end === part.length) {
10927
+ parts[next++] = "";
10928
+ offset = 0;
10929
+ } else offset = end;
10930
+ controller.enqueue(Buffer.from(piece, "utf8"));
10894
10931
  },
10895
10932
  cancel() {
10896
10933
  parts.length = 0;
@@ -10898,31 +10935,55 @@ function jsonFetchBody(body) {
10898
10935
  });
10899
10936
  return { body: stream5, contentLength: split.length };
10900
10937
  }
10901
- var LARGE_STRING_BYTES, CHECK_CHUNK, MAX_DEPTH, isLargeString, Pieces;
10938
+ var LARGE_STRING_BYTES, CHECK_CHUNK, isShortPlain, plainScans, LargeString, plainBytes, MAX_DEPTH, isLargeString, Pieces, STREAM_CHUNK_CHARS;
10902
10939
  var init_json_body = __esm({
10903
10940
  "src/ai/json-body.ts"() {
10904
10941
  "use strict";
10905
10942
  LARGE_STRING_BYTES = 64 * 1024;
10906
10943
  CHECK_CHUNK = 16 * 1024;
10944
+ isShortPlain = (value) => JSON.stringify(value).length === value.length + 2;
10945
+ plainScans = { count: 0 };
10946
+ LargeString = class {
10947
+ constructor(prefix, key, text) {
10948
+ this.prefix = prefix;
10949
+ this.key = key;
10950
+ this.text = text;
10951
+ }
10952
+ prefix;
10953
+ key;
10954
+ text;
10955
+ toJSON() {
10956
+ return this.prefix + this.text;
10957
+ }
10958
+ };
10959
+ plainBytes = /* @__PURE__ */ new WeakMap();
10907
10960
  MAX_DEPTH = 64;
10908
10961
  isLargeString = (value) => typeof value === "string" && value.length >= LARGE_STRING_BYTES;
10909
10962
  Pieces = class {
10910
10963
  parts = [];
10964
+ /** 已推入 `parts` 的 UTF-8 字节数。 */
10965
+ bytes = 0;
10911
10966
  pending = "";
10912
10967
  text(chunk) {
10913
10968
  this.pending += chunk;
10914
10969
  }
10915
- /** 大字符串原样写入(引号进相邻的小片段)。 */
10916
- large(value) {
10917
- this.parts.push(`${this.pending}"`, value);
10970
+ /** 大字符串原样写入(引号与 `head` 进前一个小片段);`bytes` 已知时不再数一遍。 */
10971
+ large(value, head = "", bytes = Buffer.byteLength(value, "utf8")) {
10972
+ const before2 = `${this.pending}"${head}`;
10973
+ this.parts.push(before2, value);
10974
+ this.bytes += Buffer.byteLength(before2, "utf8") + bytes;
10918
10975
  this.pending = '"';
10919
10976
  }
10920
10977
  finish() {
10921
- if (this.pending.length > 0) this.parts.push(this.pending);
10978
+ if (this.pending.length > 0) {
10979
+ this.parts.push(this.pending);
10980
+ this.bytes += Buffer.byteLength(this.pending, "utf8");
10981
+ }
10922
10982
  this.pending = "";
10923
10983
  return this.parts;
10924
10984
  }
10925
10985
  };
10986
+ STREAM_CHUNK_CHARS = 256 * 1024;
10926
10987
  }
10927
10988
  });
10928
10989
 
@@ -12327,7 +12388,7 @@ function fillLimits(out, hit, sources) {
12327
12388
  sources.maxTokens = "catalog-alias";
12328
12389
  }
12329
12390
  }
12330
- function enrichEntry(entry, index2) {
12391
+ function enrichEntry(entry, index2, providerId) {
12331
12392
  const { modelsDev, channels: _channels, catalog: _catalog, ...rest } = entry;
12332
12393
  const sources = {};
12333
12394
  for (const field2 of ENRICHABLE_FIELDS) {
@@ -12335,10 +12396,14 @@ function enrichEntry(entry, index2) {
12335
12396
  }
12336
12397
  const metadata = { sources, looked: false };
12337
12398
  const out = { ...rest };
12399
+ const warnings = [];
12400
+ const ref = `${providerId ?? "?"}/${entry.id}`;
12338
12401
  const hit = catalogHit(entry);
12339
12402
  if (hit !== void 0) {
12340
12403
  metadata.catalog = hit.ref;
12341
12404
  inheritIntrinsic(out, hit, sources);
12405
+ } else if (typeof entry.catalog === "string") {
12406
+ warnings.push(`catalog "${entry.catalog}" for "${ref}" not found; ignored`);
12342
12407
  }
12343
12408
  const explicit = typeof modelsDev === "string" ? modelsDev : hit?.snapshotRef;
12344
12409
  const lookup = modelsDev !== false && (needsLookup(out) || modelsDev !== void 0);
@@ -12365,7 +12430,10 @@ function enrichEntry(entry, index2) {
12365
12430
  }
12366
12431
  }
12367
12432
  }
12433
+ if (typeof modelsDev === "string" && metadata.looked && metadata.match === void 0)
12434
+ warnings.push(`modelsDev "${modelsDev}" for "${ref}" not found`);
12368
12435
  if (hit !== void 0 && modelsDev !== false) fillLimits(out, hit, sources);
12436
+ if (warnings.length > 0) metadata.warnings = warnings;
12369
12437
  return { entry: out, metadata };
12370
12438
  }
12371
12439
  function catalogMetadata(model) {
@@ -16177,121 +16245,150 @@ var init_edit_fuzzy = __esm({
16177
16245
  });
16178
16246
 
16179
16247
  // src/tools/read-lines.ts
16180
- function readHead(abs, bytes) {
16181
- const fd = (0, import_node_fs22.openSync)(abs, "r");
16248
+ async function readHeadAsync(abs, bytes) {
16249
+ const handle = await (0, import_promises3.open)(abs, "r");
16182
16250
  try {
16183
16251
  const buf = Buffer.alloc(bytes);
16184
16252
  let got = 0;
16185
16253
  while (got < bytes) {
16186
- const n = (0, import_node_fs22.readSync)(fd, buf, got, bytes - got, null);
16187
- if (n === 0) break;
16188
- got += n;
16254
+ const { bytesRead } = await handle.read(buf, got, bytes - got, null);
16255
+ if (bytesRead === 0) break;
16256
+ got += bytesRead;
16189
16257
  }
16190
16258
  return buf.subarray(0, got);
16191
16259
  } finally {
16192
- (0, import_node_fs22.closeSync)(fd);
16260
+ await handle.close();
16193
16261
  }
16194
16262
  }
16195
- function readLineWindow(abs, offset, limit, maxBytes, chunkBytes = CHUNK_BYTES) {
16263
+ function scanner(offset, limit, maxBytes) {
16196
16264
  const first = offset - 1;
16197
- const stop = limit === void 0 ? Infinity : first + limit;
16198
- const lineCap = maxBytes + LINE_SLACK;
16199
- const lines2 = [];
16200
- let truncatedBy;
16201
- let collected = 0;
16202
- let index2 = 0;
16203
- let parts = [];
16204
- let partBytes = 0;
16205
- let pending = false;
16206
- let skipLF = false;
16207
- let bom = false;
16208
- const collecting = () => truncatedBy === void 0 && index2 >= first && index2 < stop;
16209
- const take = (buf, from, to) => {
16210
- if (to <= from || partBytes >= lineCap) return;
16211
- const end = Math.min(to, from + lineCap - partBytes);
16212
- parts.push(Buffer.from(buf.subarray(from, end)));
16213
- partBytes += end - from;
16214
- };
16215
- const finish2 = () => {
16216
- if (collecting()) {
16217
- const line2 = (parts.length === 1 ? parts[0] : Buffer.concat(parts)).toString("utf8");
16218
- lines2.push(line2);
16219
- collected += Buffer.byteLength(line2) + 1;
16220
- if (collected > maxBytes) truncatedBy = "bytes";
16221
- }
16222
- if (partBytes > 0) parts = [];
16223
- partBytes = 0;
16224
- pending = false;
16225
- index2++;
16226
- };
16265
+ return new LineScanner(first, limit === void 0 ? Infinity : first + limit, maxBytes);
16266
+ }
16267
+ async function readLineWindowAsync(abs, offset, limit, maxBytes, chunkBytes = CHUNK_BYTES) {
16268
+ const scan = scanner(offset, limit, maxBytes);
16227
16269
  const size = Math.max(3, chunkBytes);
16228
- const fd = (0, import_node_fs22.openSync)(abs, "r");
16270
+ const handle = await (0, import_promises3.open)(abs, "r");
16229
16271
  try {
16230
16272
  const buf = Buffer.alloc(size);
16231
16273
  let carry = 0;
16232
- let firstChunk = true;
16233
16274
  for (; ; ) {
16234
- const n = (0, import_node_fs22.readSync)(fd, buf, carry, size - carry, null);
16275
+ const { bytesRead: n } = await handle.read(buf, carry, size - carry, null);
16235
16276
  const end = carry + n;
16236
- carry = 0;
16237
- if (n === 0 && end === 0) break;
16238
- let pos = 0;
16239
- if (firstChunk) {
16240
- if (end < 3 && n > 0) {
16241
- carry = end;
16242
- continue;
16243
- }
16244
- firstChunk = false;
16245
- if (end >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
16246
- bom = true;
16247
- pos = 3;
16248
- }
16249
- }
16250
- if (skipLF && pos < end) {
16251
- if (buf[pos] === LF2) pos++;
16252
- skipLF = false;
16253
- }
16254
- let nextLF = buf.indexOf(LF2, pos);
16255
- let nextCR = buf.indexOf(CR2, pos);
16256
- while (pos < end) {
16257
- if (nextLF !== -1 && nextLF < pos) nextLF = buf.indexOf(LF2, pos);
16258
- if (nextCR !== -1 && nextCR < pos) nextCR = buf.indexOf(CR2, pos);
16259
- const lf = nextLF === -1 || nextLF >= end ? end : nextLF;
16260
- const cr = nextCR === -1 || nextCR >= end ? end : nextCR;
16261
- const at = Math.min(lf, cr);
16262
- if (at > pos) {
16263
- pending = true;
16264
- if (collecting()) take(buf, pos, at);
16265
- }
16266
- if (at === end) break;
16267
- finish2();
16268
- pos = at + 1;
16269
- if (at === cr) {
16270
- if (pos < end) {
16271
- if (buf[pos] === LF2) pos++;
16272
- } else {
16273
- skipLF = true;
16274
- }
16275
- }
16276
- }
16277
+ if (end === 0) break;
16278
+ carry = scan.feed(buf, end, n === 0);
16277
16279
  if (n === 0) break;
16278
16280
  }
16279
- if (pending) finish2();
16280
16281
  } finally {
16281
- (0, import_node_fs22.closeSync)(fd);
16282
+ await handle.close();
16282
16283
  }
16283
- return { lines: lines2, totalLines: index2, ...truncatedBy ? { truncatedBy } : {}, bom };
16284
+ return scan.finish();
16284
16285
  }
16285
- var import_node_fs22, STREAM_READ_THRESHOLD, CHUNK_BYTES, LINE_SLACK, LF2, CR2;
16286
+ var import_promises3, STREAM_READ_THRESHOLD, CHUNK_BYTES, LINE_SLACK, LF2, CR2, LineScanner;
16286
16287
  var init_read_lines = __esm({
16287
16288
  "src/tools/read-lines.ts"() {
16288
16289
  "use strict";
16289
- import_node_fs22 = require("node:fs");
16290
+ import_promises3 = require("node:fs/promises");
16290
16291
  STREAM_READ_THRESHOLD = 1024 * 1024;
16291
16292
  CHUNK_BYTES = 64 * 1024;
16292
16293
  LINE_SLACK = 64;
16293
16294
  LF2 = 10;
16294
16295
  CR2 = 13;
16296
+ LineScanner = class {
16297
+ constructor(first, stop, maxBytes) {
16298
+ this.first = first;
16299
+ this.stop = stop;
16300
+ this.maxBytes = maxBytes;
16301
+ this.lineCap = maxBytes + LINE_SLACK;
16302
+ }
16303
+ first;
16304
+ stop;
16305
+ maxBytes;
16306
+ lineCap;
16307
+ lines = [];
16308
+ truncatedBy;
16309
+ collected = 0;
16310
+ index = 0;
16311
+ // 已完成的行数
16312
+ parts = [];
16313
+ partBytes = 0;
16314
+ pending = false;
16315
+ // 最后一个终止符之后已有字节
16316
+ skipLF = false;
16317
+ // 上一块以 `\r` 结尾:下一块开头的 `\n` 属于同一终止符
16318
+ bom = false;
16319
+ firstChunk = true;
16320
+ /**
16321
+ * 处理 `buf[0, end)`;`eof` 表示这是最后一块。返回调用方下次读取前应保留在 `buf` 开头的字节数
16322
+ * (只在首块不足 3 字节、还判断不了 BOM 时非零)。
16323
+ */
16324
+ feed(buf, end, eof) {
16325
+ let pos = 0;
16326
+ if (this.firstChunk) {
16327
+ if (end < 3 && !eof) return end;
16328
+ this.firstChunk = false;
16329
+ if (end >= 3 && buf[0] === 239 && buf[1] === 187 && buf[2] === 191) {
16330
+ this.bom = true;
16331
+ pos = 3;
16332
+ }
16333
+ }
16334
+ if (this.skipLF && pos < end) {
16335
+ if (buf[pos] === LF2) pos++;
16336
+ this.skipLF = false;
16337
+ }
16338
+ let nextLF = buf.indexOf(LF2, pos);
16339
+ let nextCR = buf.indexOf(CR2, pos);
16340
+ while (pos < end) {
16341
+ if (nextLF !== -1 && nextLF < pos) nextLF = buf.indexOf(LF2, pos);
16342
+ if (nextCR !== -1 && nextCR < pos) nextCR = buf.indexOf(CR2, pos);
16343
+ const lf = nextLF === -1 || nextLF >= end ? end : nextLF;
16344
+ const cr = nextCR === -1 || nextCR >= end ? end : nextCR;
16345
+ const at = Math.min(lf, cr);
16346
+ if (at > pos) {
16347
+ this.pending = true;
16348
+ if (this.collecting()) this.take(buf, pos, at);
16349
+ }
16350
+ if (at === end) break;
16351
+ this.endLine();
16352
+ pos = at + 1;
16353
+ if (at === cr) {
16354
+ if (pos < end) {
16355
+ if (buf[pos] === LF2) pos++;
16356
+ } else {
16357
+ this.skipLF = true;
16358
+ }
16359
+ }
16360
+ }
16361
+ return 0;
16362
+ }
16363
+ /** 文件读完:收尾最后一行并给出结果。 */
16364
+ finish() {
16365
+ if (this.pending) this.endLine();
16366
+ const { lines: lines2, truncatedBy, bom } = this;
16367
+ return { lines: lines2, totalLines: this.index, ...truncatedBy ? { truncatedBy } : {}, bom };
16368
+ }
16369
+ collecting() {
16370
+ return this.truncatedBy === void 0 && this.index >= this.first && this.index < this.stop;
16371
+ }
16372
+ take(buf, from, to) {
16373
+ if (to <= from || this.partBytes >= this.lineCap) return;
16374
+ const end = Math.min(to, from + this.lineCap - this.partBytes);
16375
+ this.parts.push(Buffer.from(buf.subarray(from, end)));
16376
+ this.partBytes += end - from;
16377
+ }
16378
+ endLine() {
16379
+ if (this.collecting()) {
16380
+ const { parts } = this;
16381
+ const line2 = (parts.length === 1 ? parts[0] : Buffer.concat(parts)).toString("utf8");
16382
+ this.lines.push(line2);
16383
+ this.collected += Buffer.byteLength(line2) + 1;
16384
+ if (this.collected > this.maxBytes) this.truncatedBy = "bytes";
16385
+ }
16386
+ if (this.partBytes > 0) this.parts = [];
16387
+ this.partBytes = 0;
16388
+ this.pending = false;
16389
+ this.index++;
16390
+ }
16391
+ };
16295
16392
  }
16296
16393
  });
16297
16394
 
@@ -16363,7 +16460,7 @@ async function resizeImage(input2, target, deps = {}) {
16363
16460
  const mimeType = format === "jpeg" ? "image/jpeg" : "image/png";
16364
16461
  const long = target.size !== void 0 ? Math.max(target.size.width, target.size.height) : 0;
16365
16462
  const start = long > 0 ? Math.min(target.maxEdge, long) : target.maxEdge;
16366
- const dir = await (0, import_promises3.mkdtemp)((0, import_node_path25.join)(deps.tmpDir ?? (0, import_node_os5.tmpdir)(), "ama-resize-"));
16463
+ const dir = await (0, import_promises4.mkdtemp)((0, import_node_path25.join)(deps.tmpDir ?? (0, import_node_os5.tmpdir)(), "ama-resize-"));
16367
16464
  try {
16368
16465
  for (const tool of resizeTools(deps.platform ?? process.platform)) {
16369
16466
  let edge = start;
@@ -16374,7 +16471,7 @@ async function resizeImage(input2, target, deps = {}) {
16374
16471
  if (result.missing === true || result.code !== 0) break;
16375
16472
  let buf;
16376
16473
  try {
16377
- buf = await (0, import_promises3.readFile)(output);
16474
+ buf = await (0, import_promises4.readFile)(output);
16378
16475
  } catch {
16379
16476
  break;
16380
16477
  }
@@ -16390,15 +16487,15 @@ async function resizeImage(input2, target, deps = {}) {
16390
16487
  }
16391
16488
  return { ok: false, reason: "no_tool" };
16392
16489
  } finally {
16393
- await (0, import_promises3.rm)(dir, { recursive: true, force: true }).catch(() => void 0);
16490
+ await (0, import_promises4.rm)(dir, { recursive: true, force: true }).catch(() => void 0);
16394
16491
  }
16395
16492
  }
16396
- var import_node_child_process3, import_promises3, import_node_os5, import_node_path25, runCommand, MAX_ATTEMPTS, SHRINK, MIN_EDGE;
16493
+ var import_node_child_process3, import_promises4, import_node_os5, import_node_path25, runCommand, MAX_ATTEMPTS, SHRINK, MIN_EDGE;
16397
16494
  var init_image_resize = __esm({
16398
16495
  "src/tools/image-resize.ts"() {
16399
16496
  "use strict";
16400
16497
  import_node_child_process3 = require("node:child_process");
16401
- import_promises3 = require("node:fs/promises");
16498
+ import_promises4 = require("node:fs/promises");
16402
16499
  import_node_os5 = require("node:os");
16403
16500
  import_node_path25 = require("node:path");
16404
16501
  init_image_limits();
@@ -16570,7 +16667,7 @@ function fitError(path, fit, limit, bytes, resizeOff) {
16570
16667
  async function loadImageFile(path, options = {}) {
16571
16668
  let info2;
16572
16669
  try {
16573
- info2 = await (0, import_promises4.stat)(path);
16670
+ info2 = await (0, import_promises5.stat)(path);
16574
16671
  } catch {
16575
16672
  throw imageError(`Image not found: ${path}`, { reason: "missing", path });
16576
16673
  }
@@ -16586,7 +16683,7 @@ async function loadImageFile(path, options = {}) {
16586
16683
  sizeMb
16587
16684
  });
16588
16685
  }
16589
- const buf = await (0, import_promises4.readFile)(path);
16686
+ const buf = await (0, import_promises5.readFile)(path);
16590
16687
  const mimeType = sniffImageMime(buf) ?? imageMimeFromPath(path);
16591
16688
  if (mimeType === void 0)
16592
16689
  throw imageError(`Unsupported image (PNG / JPEG / GIF / WebP): ${path}`, {
@@ -16603,11 +16700,11 @@ async function loadImageFile(path, options = {}) {
16603
16700
  ...fit.size ? { size: fit.size } : {}
16604
16701
  };
16605
16702
  }
16606
- var import_promises4, import_node_path26, MAX_IMAGE_FILE_BYTES, IMAGE_EXTENSIONS, HINT_TEXT;
16703
+ var import_promises5, import_node_path26, MAX_IMAGE_FILE_BYTES, IMAGE_EXTENSIONS, HINT_TEXT;
16607
16704
  var init_image_file = __esm({
16608
16705
  "src/tools/image-file.ts"() {
16609
16706
  "use strict";
16610
- import_promises4 = require("node:fs/promises");
16707
+ import_promises5 = require("node:fs/promises");
16611
16708
  import_node_path26 = require("node:path");
16612
16709
  init_errors();
16613
16710
  init_image_intern();
@@ -16654,7 +16751,7 @@ function numberLines(lines2, firstLine3) {
16654
16751
  return lines2.map((line2, i) => `${String(firstLine3 + i).padStart(6)} ${line2}`).join("\n");
16655
16752
  }
16656
16753
  async function readImage(abs, shown2, ctx, options) {
16657
- const buf = await (0, import_promises5.readFile)(abs);
16754
+ const buf = await (0, import_promises6.readFile)(abs);
16658
16755
  const mimeType = sniffImageMime(buf) ?? imageMimeFromPath(abs) ?? "application/octet-stream";
16659
16756
  const size = imageSize(buf, mimeType);
16660
16757
  const dims = size ? `${size.width}x${size.height}, ` : "";
@@ -16685,7 +16782,7 @@ async function executeRead(input2, ctx, options = {}) {
16685
16782
  const shown2 = displayPath(abs, ctx.cwd);
16686
16783
  let info2;
16687
16784
  try {
16688
- info2 = await (0, import_promises5.stat)(abs);
16785
+ info2 = await (0, import_promises6.stat)(abs);
16689
16786
  } catch {
16690
16787
  return error(`File not found: ${shown2}`);
16691
16788
  }
@@ -16698,9 +16795,9 @@ async function executeRead(input2, ctx, options = {}) {
16698
16795
  let totalLines;
16699
16796
  let selected;
16700
16797
  if (info2.size > (options.streamThreshold ?? STREAM_READ_THRESHOLD)) {
16701
- if (isBinary(readHead(abs, SNIFF_BYTES))) return error(binaryMessage(shown2));
16798
+ if (isBinary(await readHeadAsync(abs, SNIFF_BYTES))) return error(binaryMessage(shown2));
16702
16799
  const want = Math.min(input2.limit ?? Infinity, DEFAULT_MAX_LINES + 1);
16703
- const win = readLineWindow(
16800
+ const win = await readLineWindowAsync(
16704
16801
  abs,
16705
16802
  badOffset ? 1 : offset,
16706
16803
  badOffset || badLimit ? 0 : want,
@@ -16709,7 +16806,7 @@ async function executeRead(input2, ctx, options = {}) {
16709
16806
  totalLines = win.totalLines;
16710
16807
  selected = win.lines;
16711
16808
  } else {
16712
- const buf = await (0, import_promises5.readFile)(abs);
16809
+ const buf = await (0, import_promises6.readFile)(abs);
16713
16810
  if (isBinary(buf)) return error(binaryMessage(shown2));
16714
16811
  const { text } = splitBom(buf.toString("utf8"));
16715
16812
  const normalized = normalizeToLF(text);
@@ -16770,11 +16867,11 @@ function createReadTool(options = {}) {
16770
16867
  execute: (input2, ctx) => executeRead(input2, ctx, options)
16771
16868
  };
16772
16869
  }
16773
- var import_promises5, SNIFF_BYTES, binaryMessage;
16870
+ var import_promises6, SNIFF_BYTES, binaryMessage;
16774
16871
  var init_read = __esm({
16775
16872
  "src/tools/read.ts"() {
16776
16873
  "use strict";
16777
- import_promises5 = require("node:fs/promises");
16874
+ import_promises6 = require("node:fs/promises");
16778
16875
  init_image_intern();
16779
16876
  init_paths2();
16780
16877
  init_truncate();
@@ -16943,7 +17040,7 @@ async function executeEdit(input2, ctx) {
16943
17040
  const shown2 = displayPath(abs, ctx.cwd);
16944
17041
  return withFileMutex(abs, async () => {
16945
17042
  try {
16946
- const info2 = await (0, import_promises6.stat)(abs);
17043
+ const info2 = await (0, import_promises7.stat)(abs);
16947
17044
  if (!info2.isFile()) return { content: `${shown2} is not a regular file`, isError: true };
16948
17045
  } catch {
16949
17046
  return { content: `File not found: ${shown2}`, isError: true };
@@ -16951,7 +17048,7 @@ async function executeEdit(input2, ctx) {
16951
17048
  if (!ctx.readFiles.has(abs)) {
16952
17049
  return { content: `Read ${shown2} with the read tool before editing it.`, isError: true };
16953
17050
  }
16954
- const raw = await (0, import_promises6.readFile)(abs, "utf8");
17051
+ const raw = await (0, import_promises7.readFile)(abs, "utf8");
16955
17052
  const { bom, text } = splitBom(raw);
16956
17053
  const ending = detectLineEnding(text);
16957
17054
  const content = normalizeToLF(text);
@@ -16964,7 +17061,7 @@ async function executeEdit(input2, ctx) {
16964
17061
  }
16965
17062
  const output = bom + restoreLineEndings(plan2.result, ending);
16966
17063
  await beforeWrite(ctx, abs);
16967
- await (0, import_promises6.writeFile)(abs, output, "utf8");
17064
+ await (0, import_promises7.writeFile)(abs, output, "utf8");
16968
17065
  ctx.checkpoint?.afterWrite(abs, output);
16969
17066
  const details = {
16970
17067
  path: abs,
@@ -17014,11 +17111,11 @@ function createEditTool() {
17014
17111
  execute: executeEdit
17015
17112
  };
17016
17113
  }
17017
- var import_promises6, EditError;
17114
+ var import_promises7, EditError;
17018
17115
  var init_edit = __esm({
17019
17116
  "src/tools/edit.ts"() {
17020
17117
  "use strict";
17021
- import_promises6 = require("node:fs/promises");
17118
+ import_promises7 = require("node:fs/promises");
17022
17119
  init_types8();
17023
17120
  init_paths2();
17024
17121
  init_file_mutex();
@@ -17031,7 +17128,7 @@ var init_edit = __esm({
17031
17128
  // src/tools/write.ts
17032
17129
  async function exists2(path) {
17033
17130
  try {
17034
- const info2 = await (0, import_promises7.stat)(path);
17131
+ const info2 = await (0, import_promises8.stat)(path);
17035
17132
  return info2.isDirectory() ? "dir" : "file";
17036
17133
  } catch {
17037
17134
  return false;
@@ -17072,12 +17169,12 @@ async function executeWrite(input2, ctx) {
17072
17169
  isError: true
17073
17170
  };
17074
17171
  }
17075
- original = await (0, import_promises7.readFile)(abs, "utf8");
17172
+ original = await (0, import_promises8.readFile)(abs, "utf8");
17076
17173
  output = conformToOriginal(original, input2.content);
17077
17174
  }
17078
17175
  await beforeWrite(ctx, abs);
17079
- await (0, import_promises7.mkdir)((0, import_node_path27.dirname)(abs), { recursive: true });
17080
- await (0, import_promises7.writeFile)(abs, output, "utf8");
17176
+ await (0, import_promises8.mkdir)((0, import_node_path27.dirname)(abs), { recursive: true });
17177
+ await (0, import_promises8.writeFile)(abs, output, "utf8");
17081
17178
  ctx.checkpoint?.afterWrite(abs, output);
17082
17179
  ctx.markRead(abs);
17083
17180
  const details = {
@@ -17113,11 +17210,11 @@ function createWriteTool() {
17113
17210
  execute: executeWrite
17114
17211
  };
17115
17212
  }
17116
- var import_promises7, import_node_path27;
17213
+ var import_promises8, import_node_path27;
17117
17214
  var init_write = __esm({
17118
17215
  "src/tools/write.ts"() {
17119
17216
  "use strict";
17120
- import_promises7 = require("node:fs/promises");
17217
+ import_promises8 = require("node:fs/promises");
17121
17218
  import_node_path27 = require("node:path");
17122
17219
  init_paths2();
17123
17220
  init_file_mutex();
@@ -17148,7 +17245,7 @@ function gitBashCandidates(env) {
17148
17245
  function resolveShell(deps = {}) {
17149
17246
  const platform = deps.platform ?? process.platform;
17150
17247
  const env = deps.env ?? process.env;
17151
- const exists5 = deps.exists ?? import_node_fs23.existsSync;
17248
+ const exists5 = deps.exists ?? import_node_fs22.existsSync;
17152
17249
  const custom = env["AMA_SHELL"]?.trim();
17153
17250
  if (custom) return { shell: custom, kind: shellKindOf(custom) };
17154
17251
  if (platform === "win32") {
@@ -17188,11 +17285,11 @@ function buildToolEnv(base4, info2) {
17188
17285
  set("AMA_DEPTH", String(info2.depth));
17189
17286
  return env;
17190
17287
  }
17191
- var import_node_fs23, POWERSHELL_ARGS, INJECTED_ENV_VARS;
17288
+ var import_node_fs22, POWERSHELL_ARGS, INJECTED_ENV_VARS;
17192
17289
  var init_shell = __esm({
17193
17290
  "src/tools/shell.ts"() {
17194
17291
  "use strict";
17195
- import_node_fs23 = require("node:fs");
17292
+ import_node_fs22 = require("node:fs");
17196
17293
  POWERSHELL_ARGS = ["-NoProfile", "-NonInteractive", "-Command"];
17197
17294
  INJECTED_ENV_VARS = [
17198
17295
  "AMA_SESSION_ID",
@@ -17307,11 +17404,11 @@ var init_process_tree = __esm({
17307
17404
  function sanitizeOutput(text) {
17308
17405
  return text.replace(/\r\n/g, "\n").replace(ANSI, "").replace(/\r/g, "\n").replace(CONTROL, "");
17309
17406
  }
17310
- var import_node_fs24, import_node_path28, import_node_string_decoder2, ANSI, CONTROL, OutputAccumulator;
17407
+ var import_node_fs23, import_node_path28, import_node_string_decoder2, ANSI, CONTROL, OutputAccumulator;
17311
17408
  var init_output_accumulator = __esm({
17312
17409
  "src/tools/output-accumulator.ts"() {
17313
17410
  "use strict";
17314
- import_node_fs24 = require("node:fs");
17411
+ import_node_fs23 = require("node:fs");
17315
17412
  import_node_path28 = require("node:path");
17316
17413
  import_node_string_decoder2 = require("node:string_decoder");
17317
17414
  init_truncate();
@@ -17353,7 +17450,7 @@ var init_output_accumulator = __esm({
17353
17450
  this.tailText = sliceTailBytes(this.tailText, this.maxBytes * 2);
17354
17451
  }
17355
17452
  if (this.spilledTo !== void 0) {
17356
- (0, import_node_fs24.appendFileSync)(this.spilledTo, text);
17453
+ (0, import_node_fs23.appendFileSync)(this.spilledTo, text);
17357
17454
  return;
17358
17455
  }
17359
17456
  this.chunks.push(text);
@@ -17362,8 +17459,8 @@ var init_output_accumulator = __esm({
17362
17459
  }
17363
17460
  spill() {
17364
17461
  const target = this.options.spillPath();
17365
- (0, import_node_fs24.mkdirSync)((0, import_node_path28.dirname)(target), { recursive: true });
17366
- (0, import_node_fs24.writeFileSync)(target, this.chunks.join(""));
17462
+ (0, import_node_fs23.mkdirSync)((0, import_node_path28.dirname)(target), { recursive: true });
17463
+ (0, import_node_fs23.writeFileSync)(target, this.chunks.join(""));
17367
17464
  this.spilledTo = target;
17368
17465
  this.chunks = [];
17369
17466
  this.memoryBytes = 0;
@@ -17421,11 +17518,11 @@ async function disposeSessionJobs(sessionId) {
17421
17518
  registry2.delete(sessionId);
17422
17519
  await jobs.disposeAll();
17423
17520
  }
17424
- var import_node_fs25, import_node_path29, BackgroundJobs, registry2;
17521
+ var import_node_fs24, import_node_path29, BackgroundJobs, registry2;
17425
17522
  var init_background_jobs = __esm({
17426
17523
  "src/tools/background-jobs.ts"() {
17427
17524
  "use strict";
17428
- import_node_fs25 = require("node:fs");
17525
+ import_node_fs24 = require("node:fs");
17429
17526
  import_node_path29 = require("node:path");
17430
17527
  init_process_tree();
17431
17528
  init_truncate();
@@ -17434,13 +17531,13 @@ var init_background_jobs = __esm({
17434
17531
  listeners = /* @__PURE__ */ new Set();
17435
17532
  seq = 0;
17436
17533
  start(command3, options) {
17437
- (0, import_node_fs25.mkdirSync)((0, import_node_path29.dirname)(options.outputPath), { recursive: true });
17438
- const fd = (0, import_node_fs25.openSync)(options.outputPath, "a");
17534
+ (0, import_node_fs24.mkdirSync)((0, import_node_path29.dirname)(options.outputPath), { recursive: true });
17535
+ const fd = (0, import_node_fs24.openSync)(options.outputPath, "a");
17439
17536
  let child;
17440
17537
  try {
17441
17538
  child = options.spawn(fd);
17442
17539
  } finally {
17443
- (0, import_node_fs25.closeSync)(fd);
17540
+ (0, import_node_fs24.closeSync)(fd);
17444
17541
  }
17445
17542
  const id = `bg${++this.seq}`;
17446
17543
  const job = {
@@ -17515,17 +17612,17 @@ var init_background_jobs = __esm({
17515
17612
  await entry.done;
17516
17613
  return entry.job;
17517
17614
  }
17518
- /** 输出文件尾部(2000 行 / 50 KB)。 */
17519
- output(id) {
17615
+ /** 输出文件尾部(缺省 2000 行 / 50 KB;调用方可传字节上限,#155)。 */
17616
+ output(id, options = {}) {
17520
17617
  const entry = this.entries.get(id);
17521
17618
  if (entry === void 0) return void 0;
17522
17619
  let text = "";
17523
17620
  try {
17524
- if ((0, import_node_fs25.statSync)(entry.job.outputPath).size > 0)
17525
- text = (0, import_node_fs25.readFileSync)(entry.job.outputPath, "utf8");
17621
+ if ((0, import_node_fs24.statSync)(entry.job.outputPath).size > 0)
17622
+ text = (0, import_node_fs24.readFileSync)(entry.job.outputPath, "utf8");
17526
17623
  } catch {
17527
17624
  }
17528
- return { ...truncateTail(text), job: entry.job };
17625
+ return { ...truncateTail(text, options), job: entry.job };
17529
17626
  }
17530
17627
  /** 取走已退出、尚未通知的任务(提醒通道用;wait 已看到退出的也算已知)。 */
17531
17628
  takeExited() {
@@ -17568,7 +17665,7 @@ var init_background_jobs = __esm({
17568
17665
  });
17569
17666
 
17570
17667
  // src/sandbox/wrap.ts
17571
- function resolveWritable(dirs, realpath4 = import_node_fs26.realpathSync) {
17668
+ function resolveWritable(dirs, realpath4 = import_node_fs25.realpathSync) {
17572
17669
  const out = [];
17573
17670
  for (const dir of dirs) {
17574
17671
  let real2;
@@ -17583,12 +17680,12 @@ function resolveWritable(dirs, realpath4 = import_node_fs26.realpathSync) {
17583
17680
  }
17584
17681
  function isDirectory(path) {
17585
17682
  try {
17586
- return (0, import_node_fs26.statSync)(path).isDirectory();
17683
+ return (0, import_node_fs25.statSync)(path).isDirectory();
17587
17684
  } catch {
17588
17685
  return false;
17589
17686
  }
17590
17687
  }
17591
- function resolveExtras(policy, missingOk, realpath4 = import_node_fs26.realpathSync, isDir4 = isDirectory) {
17688
+ function resolveExtras(policy, missingOk, realpath4 = import_node_fs25.realpathSync, isDir4 = isDirectory) {
17592
17689
  const readOnly = [];
17593
17690
  for (const path of policy.readOnly ?? []) {
17594
17691
  let real2;
@@ -17660,11 +17757,11 @@ function wrapCommand(status, command3, args, policy, realpath4, isDir4) {
17660
17757
  };
17661
17758
  }
17662
17759
  }
17663
- var import_node_fs26, import_node_path30;
17760
+ var import_node_fs25, import_node_path30;
17664
17761
  var init_wrap = __esm({
17665
17762
  "src/sandbox/wrap.ts"() {
17666
17763
  "use strict";
17667
- import_node_fs26 = require("node:fs");
17764
+ import_node_fs25 = require("node:fs");
17668
17765
  import_node_path30 = require("node:path");
17669
17766
  init_profile2();
17670
17767
  }
@@ -17827,7 +17924,7 @@ async function executeBash(input2, ctx, options = {}) {
17827
17924
  }
17828
17925
  const cwd = resolvePath(input2.cwd ?? ".", ctx.cwd);
17829
17926
  try {
17830
- if (!(await (0, import_promises8.stat)(cwd)).isDirectory())
17927
+ if (!(await (0, import_promises9.stat)(cwd)).isDirectory())
17831
17928
  return fail2(`Working directory is not a directory: ${cwd}`);
17832
17929
  } catch {
17833
17930
  return fail2(`Working directory does not exist: ${cwd}`);
@@ -18013,7 +18110,7 @@ async function jobAction(input2, ctx, options) {
18013
18110
  }
18014
18111
  if (action === "stop") {
18015
18112
  const job2 = await jobs.stop(id);
18016
- const out2 = jobs.output(id);
18113
+ const out2 = jobs.output(id, { maxBytes: toolOutputBytes(ctx.maxResultChars) });
18017
18114
  const text = out2?.content ?? "";
18018
18115
  return {
18019
18116
  content: `${text === "" ? "(no output)" : text}
@@ -18028,7 +18125,7 @@ ${jobStatusLine(job2)}`,
18028
18125
  return fail2(`timeoutMs must be between 1 and ${MAX_BASH_TIMEOUT_MS}`);
18029
18126
  await jobs.wait(id, waitMs, ctx.signal);
18030
18127
  }
18031
- const out = jobs.output(id);
18128
+ const out = jobs.output(id, { maxBytes: toolOutputBytes(ctx.maxResultChars) });
18032
18129
  if (out === void 0) return fail2(`Unknown background job ${id}`);
18033
18130
  const job = out.job;
18034
18131
  if (job.status !== "running") jobs.markSeen(id);
@@ -18077,12 +18174,12 @@ function createBashTool(options = {}) {
18077
18174
  execute: (input2, ctx) => executeBash(input2, ctx, options)
18078
18175
  };
18079
18176
  }
18080
- var import_node_child_process5, import_promises8, import_node_path32, DEFAULT_BASH_TIMEOUT_MS, MAX_BASH_TIMEOUT_MS, UPDATE_THROTTLE_MS, STDIO_DRAIN_MS, DEFAULT_JOB_WAIT_MS;
18177
+ var import_node_child_process5, import_promises9, import_node_path32, DEFAULT_BASH_TIMEOUT_MS, MAX_BASH_TIMEOUT_MS, UPDATE_THROTTLE_MS, STDIO_DRAIN_MS, DEFAULT_JOB_WAIT_MS;
18081
18178
  var init_bash2 = __esm({
18082
18179
  "src/tools/bash.ts"() {
18083
18180
  "use strict";
18084
18181
  import_node_child_process5 = require("node:child_process");
18085
- import_promises8 = require("node:fs/promises");
18182
+ import_promises9 = require("node:fs/promises");
18086
18183
  import_node_path32 = require("node:path");
18087
18184
  init_paths2();
18088
18185
  init_shell();
@@ -18109,9 +18206,9 @@ function compilePattern(input2) {
18109
18206
  }
18110
18207
  async function searchFile(abs, regex, firstOnly = false) {
18111
18208
  try {
18112
- const info2 = await (0, import_promises9.stat)(abs);
18209
+ const info2 = await (0, import_promises10.stat)(abs);
18113
18210
  if (!info2.isFile() || info2.size > GREP_MAX_FILE_BYTES) return void 0;
18114
- const buf = await (0, import_promises9.readFile)(abs);
18211
+ const buf = await (0, import_promises10.readFile)(abs);
18115
18212
  if (isBinary(buf)) return void 0;
18116
18213
  const lines2 = buf.toString("utf8").replace(/\r\n/g, "\n").split("\n");
18117
18214
  if (lines2[lines2.length - 1] === "") lines2.pop();
@@ -18172,7 +18269,7 @@ async function executeGrep(input2, ctx) {
18172
18269
  const root = resolvePath(input2.path ?? ".", ctx.cwd);
18173
18270
  let rootIsFile;
18174
18271
  try {
18175
- rootIsFile = (await (0, import_promises9.stat)(root)).isFile();
18272
+ rootIsFile = (await (0, import_promises10.stat)(root)).isFile();
18176
18273
  } catch {
18177
18274
  return { content: `Path not found: ${displayPath(root, ctx.cwd)}`, isError: true };
18178
18275
  }
@@ -18277,11 +18374,11 @@ function createGrepTool() {
18277
18374
  execute: executeGrep
18278
18375
  };
18279
18376
  }
18280
- var import_promises9, DEFAULT_GREP_LIMIT, GREP_MAX_FILE_BYTES, GREP_CONCURRENCY, MAX_CONTEXT;
18377
+ var import_promises10, DEFAULT_GREP_LIMIT, GREP_MAX_FILE_BYTES, GREP_CONCURRENCY, MAX_CONTEXT;
18281
18378
  var init_grep = __esm({
18282
18379
  "src/tools/grep.ts"() {
18283
18380
  "use strict";
18284
- import_promises9 = require("node:fs/promises");
18381
+ import_promises10 = require("node:fs/promises");
18285
18382
  init_paths2();
18286
18383
  init_ignore();
18287
18384
  init_glob();
@@ -18298,16 +18395,16 @@ var init_grep = __esm({
18298
18395
  async function describe(dir, name) {
18299
18396
  const abs = (0, import_node_path33.join)(dir, name);
18300
18397
  try {
18301
- const info2 = await (0, import_promises10.lstat)(abs);
18398
+ const info2 = await (0, import_promises11.lstat)(abs);
18302
18399
  if (info2.isSymbolicLink()) {
18303
18400
  let target = "?";
18304
18401
  try {
18305
- target = await (0, import_promises10.readlink)(abs);
18402
+ target = await (0, import_promises11.readlink)(abs);
18306
18403
  } catch {
18307
18404
  }
18308
18405
  let pointsToDir = false;
18309
18406
  try {
18310
- pointsToDir = (await (0, import_promises10.stat)(abs)).isDirectory();
18407
+ pointsToDir = (await (0, import_promises11.stat)(abs)).isDirectory();
18311
18408
  } catch {
18312
18409
  }
18313
18410
  return { name: pointsToDir ? `${name}/` : name, type: "symlink", target };
@@ -18333,9 +18430,9 @@ async function executeLs(input2, ctx) {
18333
18430
  const shown2 = displayPath(dir, ctx.cwd);
18334
18431
  let names;
18335
18432
  try {
18336
- if (!(await (0, import_promises10.stat)(dir)).isDirectory())
18433
+ if (!(await (0, import_promises11.stat)(dir)).isDirectory())
18337
18434
  return { content: `${shown2} is not a directory`, isError: true };
18338
- names = await (0, import_promises10.readdir)(dir);
18435
+ names = await (0, import_promises11.readdir)(dir);
18339
18436
  } catch {
18340
18437
  return { content: `Directory not found: ${shown2}`, isError: true };
18341
18438
  }
@@ -18370,11 +18467,11 @@ function createLsTool() {
18370
18467
  execute: executeLs
18371
18468
  };
18372
18469
  }
18373
- var import_promises10, import_node_path33, DEFAULT_LS_LIMIT;
18470
+ var import_promises11, import_node_path33, DEFAULT_LS_LIMIT;
18374
18471
  var init_ls = __esm({
18375
18472
  "src/tools/ls.ts"() {
18376
18473
  "use strict";
18377
- import_promises10 = require("node:fs/promises");
18474
+ import_promises11 = require("node:fs/promises");
18378
18475
  import_node_path33 = require("node:path");
18379
18476
  init_paths2();
18380
18477
  init_truncate();
@@ -18661,9 +18758,9 @@ ${tail}`, truncated: true };
18661
18758
  }
18662
18759
  function writeTaskOutput(dir, name, text) {
18663
18760
  try {
18664
- (0, import_node_fs27.mkdirSync)(dir, { recursive: true });
18761
+ (0, import_node_fs26.mkdirSync)(dir, { recursive: true });
18665
18762
  const file = (0, import_node_path34.join)(dir, name);
18666
- (0, import_node_fs27.writeFileSync)(file, text);
18763
+ (0, import_node_fs26.writeFileSync)(file, text);
18667
18764
  return file;
18668
18765
  } catch {
18669
18766
  return void 0;
@@ -18700,11 +18797,11 @@ function backgroundedText(reason, afterMs) {
18700
18797
  function waitDetachedText(taskId) {
18701
18798
  return `Task ${taskId} is still running and was moved to the background by the user; do not wait again — a <task-notification> arrives when it finishes.`;
18702
18799
  }
18703
- var import_node_fs27, import_node_path34, TASK_RESULT_LIMIT_BYTES, HEAD_SHARE, MAX_TURNS_NOTE, NOTIFY_HINT, TASK_NOTIFICATION_RULE;
18800
+ var import_node_fs26, import_node_path34, TASK_RESULT_LIMIT_BYTES, HEAD_SHARE, MAX_TURNS_NOTE, NOTIFY_HINT, TASK_NOTIFICATION_RULE;
18704
18801
  var init_result = __esm({
18705
18802
  "src/agents/result.ts"() {
18706
18803
  "use strict";
18707
- import_node_fs27 = require("node:fs");
18804
+ import_node_fs26 = require("node:fs");
18708
18805
  import_node_path34 = require("node:path");
18709
18806
  TASK_RESULT_LIMIT_BYTES = 50 * 1024;
18710
18807
  HEAD_SHARE = 0.7;
@@ -18715,6 +18812,16 @@ var init_result = __esm({
18715
18812
  });
18716
18813
 
18717
18814
  // src/tools/task.ts
18815
+ function taskLine(prompt) {
18816
+ const line2 = prompt.split("\n").find((text) => text.trim() !== "")?.trim() ?? "";
18817
+ return line2.length > TASK_LINE_MAX ? `${line2.slice(0, TASK_LINE_MAX - 1)}…` : line2;
18818
+ }
18819
+ function nestedTaskRejection(ctx, tool, fallback) {
18820
+ const mark = ctx.session.lastCustom(FORK_CUSTOM_TYPE);
18821
+ if (mark === void 0 || mark === null) return fallback;
18822
+ const line2 = typeof mark.taskLine === "string" ? mark.taskLine : "";
18823
+ return `${tool} rejected: you are a sub-agent forked from the main conversation and cannot start or manage sub-agents. Do not call task or task_ctl again; complete the sub-task in your <task> block yourself with your other tools, then end with your report.` + (line2 !== "" ? ` Your task begins: "${line2}"` : "");
18824
+ }
18718
18825
  function bindTaskAgents(tool, catalog) {
18719
18826
  if (tool !== void 0) catalogs.set(tool, catalog);
18720
18827
  }
@@ -18794,7 +18901,13 @@ function createTaskTool(_options = {}) {
18794
18901
  promptGuidelines: [TASK_NOTIFICATION_RULE],
18795
18902
  async execute(input2, ctx) {
18796
18903
  if (ctx.depth >= MAX_TASK_DEPTH || ctx.spawnSubagent === void 0) {
18797
- return fail3("Sub-agents are not available here (nested tasks are not allowed).");
18904
+ return fail3(
18905
+ nestedTaskRejection(
18906
+ ctx,
18907
+ "task",
18908
+ "Sub-agents are not available here (nested tasks are not allowed)."
18909
+ )
18910
+ );
18798
18911
  }
18799
18912
  const request = buildSubagentRequest(input2, ctx);
18800
18913
  if (typeof request === "string") return fail3(request);
@@ -18823,13 +18936,15 @@ function createTaskTool(_options = {}) {
18823
18936
  };
18824
18937
  return tool;
18825
18938
  }
18826
- var MAX_TASK_DEPTH, THINKING, TASK_CONTEXT_LEAD, FOREGROUND_DESCRIPTION, BACKGROUND_DESCRIPTION, TASK_AGENTS_HEADING, catalogs, backgrounds;
18939
+ var MAX_TASK_DEPTH, TASK_LINE_MAX, FORK_CUSTOM_TYPE, THINKING, TASK_CONTEXT_LEAD, FOREGROUND_DESCRIPTION, BACKGROUND_DESCRIPTION, TASK_AGENTS_HEADING, catalogs, backgrounds;
18827
18940
  var init_task = __esm({
18828
18941
  "src/tools/task.ts"() {
18829
18942
  "use strict";
18830
18943
  init_catalog3();
18831
18944
  init_result();
18832
18945
  MAX_TASK_DEPTH = 1;
18946
+ TASK_LINE_MAX = 120;
18947
+ FORK_CUSTOM_TYPE = "ama.fork";
18833
18948
  THINKING = [
18834
18949
  "off",
18835
18950
  "minimal",
@@ -18911,7 +19026,10 @@ function createTaskCtlTool() {
18911
19026
  executionMode: "parallel",
18912
19027
  annotations: { pollable: true },
18913
19028
  async execute(input2, ctx) {
18914
- if (ctx.depth >= 1) return fail4("task_ctl is not available inside a sub-agent.");
19029
+ if (ctx.depth >= 1)
19030
+ return fail4(
19031
+ nestedTaskRejection(ctx, "task_ctl", "task_ctl is not available inside a sub-agent.")
19032
+ );
18915
19033
  const control = taskControl(ctx.sessionId);
18916
19034
  if (input2.action === "list") {
18917
19035
  const tasks = control?.list() ?? ctx.tasks?.list() ?? [];
@@ -18968,6 +19086,7 @@ var init_task_ctl = __esm({
18968
19086
  "use strict";
18969
19087
  init_result();
18970
19088
  init_task_control();
19089
+ init_task();
18971
19090
  TASK_CTL_TOOL = "task_ctl";
18972
19091
  DEFAULT_WAIT_MS = 3e4;
18973
19092
  MAX_WAIT_MS = 6e5;
@@ -19303,9 +19422,9 @@ function truncateResult(call, result, options, nested = false) {
19303
19422
  let where = "full output not saved";
19304
19423
  if (options.outputDir !== void 0) {
19305
19424
  try {
19306
- (0, import_node_fs28.mkdirSync)(options.outputDir, { recursive: true });
19425
+ (0, import_node_fs27.mkdirSync)(options.outputDir, { recursive: true });
19307
19426
  const file = (0, import_node_path35.join)(options.outputDir, `${call.id.replace(/[^A-Za-z0-9_-]/g, "_")}.txt`);
19308
- (0, import_node_fs28.writeFileSync)(file, text, "utf8");
19427
+ (0, import_node_fs27.writeFileSync)(file, text, "utf8");
19309
19428
  where = `full output at ${file}`;
19310
19429
  } catch {
19311
19430
  }
@@ -19520,11 +19639,11 @@ async function runSingleToolCall(call, assistant, options, signal, nested) {
19520
19639
  await emitEnd(call, result, emit);
19521
19640
  return result;
19522
19641
  }
19523
- var import_node_fs28, import_node_path35, ABORTED_TOOL_TEXT, DEFAULT_MAX_TOOL_RESULT_CHARS, DEFAULT_ABORT_GRACE_MS, NESTED_MAX_RESULT_CHARS, autoDecisions, deniedCalls, batchSuffixSources;
19642
+ var import_node_fs27, import_node_path35, ABORTED_TOOL_TEXT, DEFAULT_MAX_TOOL_RESULT_CHARS, DEFAULT_ABORT_GRACE_MS, NESTED_MAX_RESULT_CHARS, autoDecisions, deniedCalls, batchSuffixSources;
19524
19643
  var init_tool_runner = __esm({
19525
19644
  "src/agent/tool-runner.ts"() {
19526
19645
  "use strict";
19527
- import_node_fs28 = require("node:fs");
19646
+ import_node_fs27 = require("node:fs");
19528
19647
  import_node_path35 = require("node:path");
19529
19648
  init_loop_guard();
19530
19649
  init_schema2();
@@ -20438,8 +20557,23 @@ var init_classifier = __esm({
20438
20557
  function textOf(message) {
20439
20558
  return message.content.filter((block) => block.type === "text").map((block) => block.type === "text" ? block.text : "").join("").trim();
20440
20559
  }
20560
+ function inferredSmall(core, session) {
20561
+ const hit = catalogByAlias(session.id);
20562
+ if (hit === void 0) return void 0;
20563
+ const origin = hit.ref.slice(0, hit.ref.indexOf("/"));
20564
+ const small = catalogSmall(origin);
20565
+ const target = `${origin}/${small}`;
20566
+ if (small === void 0 || hit.ref === target) return void 0;
20567
+ const listed2 = core.options.providers.get(session.provider)?.models ?? [];
20568
+ return listed2.find((m) => {
20569
+ if (m.id === small) return true;
20570
+ const entry = catalogByAlias(m.id);
20571
+ return entry?.ref === target && entry.tier === void 0;
20572
+ })?.id;
20573
+ }
20441
20574
  async function smallModel(core, session) {
20442
- const small = catalogSmall(session.provider);
20575
+ const own = catalogSmall(session.provider);
20576
+ const small = own === void 0 ? inferredSmall(core, session) : own;
20443
20577
  if (small === void 0 || small === session.id) return void 0;
20444
20578
  const lookup = core.options.providers.findModel(`${session.provider}/${small}`);
20445
20579
  if (!lookup.ok) return void 0;
@@ -21577,9 +21711,9 @@ function materialize(candidate, outputDir) {
21577
21711
  if (outputDir !== void 0) {
21578
21712
  fullTextPath = (0, import_node_path37.join)(outputDir, `${safeFileStem(message.toolCallId)}.txt`);
21579
21713
  try {
21580
- if (!(0, import_node_fs29.existsSync)(fullTextPath)) {
21581
- (0, import_node_fs29.mkdirSync)(outputDir, { recursive: true });
21582
- (0, import_node_fs29.writeFileSync)(fullTextPath, text, "utf8");
21714
+ if (!(0, import_node_fs28.existsSync)(fullTextPath)) {
21715
+ (0, import_node_fs28.mkdirSync)(outputDir, { recursive: true });
21716
+ (0, import_node_fs28.writeFileSync)(fullTextPath, text, "utf8");
21583
21717
  }
21584
21718
  } catch {
21585
21719
  fullTextPath = void 0;
@@ -21614,11 +21748,11 @@ function planPrune(items, options) {
21614
21748
  }
21615
21749
  return { items: plan2, savedTokens, availableTokens };
21616
21750
  }
21617
- var import_node_fs29, import_node_path37, PRUNE_TRIGGER_RATIO, PRUNE_TARGET_RATIO, PRUNE_KEEP_RESULTS, PRUNE_PROTECT_TOKENS, PRUNE_PROTECT_RATIO, PRUNE_CLEAR_AT_LEAST, PRUNE_CLEAR_AT_LEAST_RATIO, PRUNE_CLEAR_AT_LEAST_CAP_RATIO, PRUNE_MIN_TOKENS, REPLACEMENT_TOKENS;
21751
+ var import_node_fs28, import_node_path37, PRUNE_TRIGGER_RATIO, PRUNE_TARGET_RATIO, PRUNE_KEEP_RESULTS, PRUNE_PROTECT_TOKENS, PRUNE_PROTECT_RATIO, PRUNE_CLEAR_AT_LEAST, PRUNE_CLEAR_AT_LEAST_RATIO, PRUNE_CLEAR_AT_LEAST_CAP_RATIO, PRUNE_MIN_TOKENS, REPLACEMENT_TOKENS;
21618
21752
  var init_prune_tier = __esm({
21619
21753
  "src/compaction/prune-tier.ts"() {
21620
21754
  "use strict";
21621
- import_node_fs29 = require("node:fs");
21755
+ import_node_fs28 = require("node:fs");
21622
21756
  import_node_path37 = require("node:path");
21623
21757
  init_estimate();
21624
21758
  PRUNE_TRIGGER_RATIO = 0.7;
@@ -22075,9 +22209,9 @@ function isTaskHead(entry) {
22075
22209
  const data2 = entry.data;
22076
22210
  return data2?.context !== "fork";
22077
22211
  }
22078
- function readHead2(file) {
22212
+ function readHead(file) {
22079
22213
  try {
22080
- const [header, first] = (0, import_node_fs30.readFileSync)(file, "utf8").split("\n", 2);
22214
+ const [header, first] = (0, import_node_fs29.readFileSync)(file, "utf8").split("\n", 2);
22081
22215
  const parsed = JSON.parse(header ?? "{}");
22082
22216
  const entry = first ? JSON.parse(first) : void 0;
22083
22217
  return { ...parsed, task: isTaskHead(entry) };
@@ -22095,18 +22229,18 @@ function cacheKeyOf(manager) {
22095
22229
  let id = manager.id;
22096
22230
  let file = header.parentSession;
22097
22231
  for (let depth = 0; depth < 8 && file !== void 0; depth++) {
22098
- const next = readHead2(file);
22232
+ const next = readHead(file);
22099
22233
  if (next.id === void 0 || next.task) break;
22100
22234
  id = next.id;
22101
22235
  file = next.parentSession;
22102
22236
  }
22103
22237
  return id;
22104
22238
  }
22105
- var import_node_fs30, TASK_CUSTOM_TYPE;
22239
+ var import_node_fs29, TASK_CUSTOM_TYPE;
22106
22240
  var init_session_cache_key = __esm({
22107
22241
  "src/agent/session-cache-key.ts"() {
22108
22242
  "use strict";
22109
- import_node_fs30 = require("node:fs");
22243
+ import_node_fs29 = require("node:fs");
22110
22244
  TASK_CUSTOM_TYPE = "ama.task";
22111
22245
  }
22112
22246
  });
@@ -25068,7 +25202,7 @@ function listSessionItems(dir) {
25068
25202
  file,
25069
25203
  cwd: header.cwd,
25070
25204
  createdAt: header.timestamp,
25071
- modifiedAt: (0, import_node_fs31.statSync)(file).mtime.toISOString(),
25205
+ modifiedAt: (0, import_node_fs30.statSync)(file).mtime.toISOString(),
25072
25206
  messageCount
25073
25207
  };
25074
25208
  if (name !== void 0) item.name = name;
@@ -25080,11 +25214,11 @@ function listSessionItems(dir) {
25080
25214
  }
25081
25215
  return items;
25082
25216
  }
25083
- var import_node_fs31, SkipFile;
25217
+ var import_node_fs30, SkipFile;
25084
25218
  var init_list = __esm({
25085
25219
  "src/session/list.ts"() {
25086
25220
  "use strict";
25087
- import_node_fs31 = require("node:fs");
25221
+ import_node_fs30 = require("node:fs");
25088
25222
  init_line_reader();
25089
25223
  init_store();
25090
25224
  init_types6();
@@ -25093,6 +25227,179 @@ var init_list = __esm({
25093
25227
  }
25094
25228
  });
25095
25229
 
25230
+ // src/session/offload.ts
25231
+ function isImage(block) {
25232
+ return typeof block === "object" && block !== null && block.type === "image" && typeof block.data === "string";
25233
+ }
25234
+ function contentOf(entry) {
25235
+ if (entry.type !== "message") return void 0;
25236
+ const content = entry.message.content;
25237
+ return Array.isArray(content) ? content : void 0;
25238
+ }
25239
+ function hasImages(entry) {
25240
+ return contentOf(entry)?.some(isImage) === true;
25241
+ }
25242
+ function readLineAt(file, locator) {
25243
+ const fd = (0, import_node_fs31.openSync)(file, "r");
25244
+ try {
25245
+ const buf = Buffer.allocUnsafe(locator.length);
25246
+ let read2 = 0;
25247
+ while (read2 < locator.length) {
25248
+ const n = (0, import_node_fs31.readSync)(fd, buf, read2, locator.length - read2, locator.offset + read2);
25249
+ if (n === 0) throw new Error("unexpected end of file");
25250
+ read2 += n;
25251
+ }
25252
+ return JSON.parse(buf.toString("utf8"));
25253
+ } finally {
25254
+ (0, import_node_fs31.closeSync)(fd);
25255
+ }
25256
+ }
25257
+ function editTargets(file) {
25258
+ const targets = /* @__PURE__ */ new Set();
25259
+ try {
25260
+ forEachLineSync(file, (line2) => {
25261
+ if (lineTypeOf(line2)?.type !== "context_edit") return;
25262
+ try {
25263
+ const id = JSON.parse(line2.toString("utf8")).targetId;
25264
+ if (typeof id === "string") targets.add(id);
25265
+ } catch {
25266
+ }
25267
+ });
25268
+ } catch {
25269
+ }
25270
+ return targets;
25271
+ }
25272
+ var import_node_fs31, placeholder, PENDING_WARNINGS, ImageOffload;
25273
+ var init_offload = __esm({
25274
+ "src/session/offload.ts"() {
25275
+ "use strict";
25276
+ import_node_fs31 = require("node:fs");
25277
+ init_image_intern();
25278
+ init_line_reader();
25279
+ init_projection();
25280
+ placeholder = (block) => ({ ...block, data: "" });
25281
+ PENDING_WARNINGS = 16;
25282
+ ImageOffload = class {
25283
+ /** 含图 message 条目的行位置(只有已落盘的条目才有)。 */
25284
+ locators = /* @__PURE__ */ new Map();
25285
+ offloaded = /* @__PURE__ */ new Set();
25286
+ warn;
25287
+ pending = [];
25288
+ dropped = 0;
25289
+ constructor(warn2) {
25290
+ this.warn = warn2;
25291
+ }
25292
+ /** 接上(或换掉)告警通道;之前缓冲的告警按序冲出。 */
25293
+ setWarn(warn2) {
25294
+ this.warn = warn2;
25295
+ const pending = this.pending.splice(0);
25296
+ if (this.dropped > 0)
25297
+ pending.push(`and ${this.dropped} more session entries could not be read back`);
25298
+ this.dropped = 0;
25299
+ for (const message of pending) warn2(message);
25300
+ }
25301
+ /** 记下条目的行位置;不含图片的条目忽略。 */
25302
+ track(entry, locator) {
25303
+ if (hasImages(entry)) this.locators.set(entry.id, locator);
25304
+ }
25305
+ /** 读文件时逐行调用:记下行位置;`strip` 时就地换成占位(刚解析的对象,别处没有引用)。 */
25306
+ load(line2, locator, strip) {
25307
+ if (typeof line2.id !== "string" || !hasImages(line2)) return;
25308
+ this.locators.set(line2.id, locator);
25309
+ if (!strip) return;
25310
+ const content = contentOf(line2);
25311
+ content.forEach((block, i) => {
25312
+ if (isImage(block)) content[i] = placeholder(block);
25313
+ });
25314
+ this.offloaded.add(line2.id);
25315
+ }
25316
+ isOffloaded(id) {
25317
+ return this.offloaded.has(id);
25318
+ }
25319
+ /** 已卸载的条目数(测试与诊断用)。 */
25320
+ get size() {
25321
+ return this.offloaded.size;
25322
+ }
25323
+ /** 把条目里的图片换成占位;没有行位置(未落盘、不含图)或已卸载时返回 false。 */
25324
+ offload(entry) {
25325
+ if (entry.type !== "message" || this.offloaded.has(entry.id)) return false;
25326
+ const content = contentOf(entry);
25327
+ if (content === void 0 || !this.locators.has(entry.id)) return false;
25328
+ entry.message = {
25329
+ ...entry.message,
25330
+ content: content.map((block) => isImage(block) ? placeholder(block) : block)
25331
+ };
25332
+ this.offloaded.add(entry.id);
25333
+ return true;
25334
+ }
25335
+ /** 就地放回图片;读不回时返回 false(占位保留,之后不再卸载 / 回读这一条)。 */
25336
+ hydrate(file, entry) {
25337
+ if (!this.offloaded.has(entry.id)) return true;
25338
+ this.offloaded.delete(entry.id);
25339
+ const source = this.read(file, entry);
25340
+ const content = contentOf(entry);
25341
+ if (source === void 0 || content === void 0) return false;
25342
+ const originals = contentOf(source) ?? [];
25343
+ content.forEach((block, i) => {
25344
+ const original = originals[i];
25345
+ if (isImage(block) && block.data === "" && isImage(original)) {
25346
+ content[i] = internImage(original);
25347
+ }
25348
+ });
25349
+ return true;
25350
+ }
25351
+ /** 原文:已卸载的条目从文件读一份新副本(图片驻留),否则原样返回。读不回时返回占位版本。 */
25352
+ original(file, entry) {
25353
+ if (!this.offloaded.has(entry.id)) return entry;
25354
+ const copy = this.read(file, entry);
25355
+ if (copy === void 0) return entry;
25356
+ const content = contentOf(copy) ?? [];
25357
+ content.forEach((block, i) => {
25358
+ if (isImage(block)) content[i] = internImage(block);
25359
+ });
25360
+ return copy;
25361
+ }
25362
+ /** 按活动分支对齐:分支上被生效编辑改写的含图条目卸载,其余已卸载的回读。 */
25363
+ sync(file, branch, index2) {
25364
+ const edits = collectContextEdits(branch);
25365
+ const onBranch = /* @__PURE__ */ new Set();
25366
+ for (const entry of branch) if (edits.has(entry.id)) onBranch.add(entry.id);
25367
+ for (const id of [...this.offloaded]) {
25368
+ const entry = index2.get(id);
25369
+ if (entry !== void 0 && !onBranch.has(id)) this.hydrate(file, entry);
25370
+ }
25371
+ for (const id of onBranch) {
25372
+ const entry = index2.get(id);
25373
+ if (entry !== void 0) this.offload(entry);
25374
+ }
25375
+ }
25376
+ /** 从文件读回条目(新对象);不成功时告警、忘掉行位置并返回 undefined。 */
25377
+ read(file, entry) {
25378
+ const locator = this.locators.get(entry.id);
25379
+ try {
25380
+ if (locator === void 0) throw new Error("no line locator");
25381
+ const line2 = readLineAt(file, locator);
25382
+ const content = line2.id === entry.id ? contentOf(line2) : void 0;
25383
+ if (content === void 0 || content.length !== contentOf(entry)?.length) {
25384
+ throw new Error("line does not match the entry");
25385
+ }
25386
+ return line2;
25387
+ } catch (error2) {
25388
+ this.locators.delete(entry.id);
25389
+ this.offloaded.delete(entry.id);
25390
+ this.report(`cannot read session entry ${entry.id} back from ${file}: ${String(error2)}`);
25391
+ return void 0;
25392
+ }
25393
+ }
25394
+ report(message) {
25395
+ if (this.warn !== void 0) this.warn(message);
25396
+ else if (this.pending.length < PENDING_WARNINGS) this.pending.push(message);
25397
+ else this.dropped++;
25398
+ }
25399
+ };
25400
+ }
25401
+ });
25402
+
25096
25403
  // src/session/manager.ts
25097
25404
  function newEntryId(taken) {
25098
25405
  for (; ; ) {
@@ -25111,6 +25418,7 @@ var init_manager = __esm({
25111
25418
  init_version();
25112
25419
  init_list();
25113
25420
  init_migrate();
25421
+ init_offload();
25114
25422
  init_store();
25115
25423
  init_tree();
25116
25424
  init_types6();
@@ -25124,7 +25432,11 @@ var init_manager = __esm({
25124
25432
  storage;
25125
25433
  closed = false;
25126
25434
  now;
25127
- constructor(header, entries, storage, now, leafId) {
25435
+ images;
25436
+ warn;
25437
+ /** 已知的会话文件字节数(新行的行首偏移)。 */
25438
+ fileBytes = 0;
25439
+ constructor(header, entries, storage, now, leafId, warn2, images) {
25128
25440
  this._header = header;
25129
25441
  this.id = header.id;
25130
25442
  this.cwd = header.cwd;
@@ -25134,6 +25446,8 @@ var init_manager = __esm({
25134
25446
  this.leaf = leafId === void 0 || leafId !== null && !this.index.has(leafId) ? last : leafId;
25135
25447
  this.storage = storage;
25136
25448
  this.now = now;
25449
+ this.warn = warn2;
25450
+ this.images = images ?? new ImageOffload(warn2);
25137
25451
  }
25138
25452
  static newHeader(cwd, options, now) {
25139
25453
  const header = {
@@ -25154,7 +25468,14 @@ var init_manager = __esm({
25154
25468
  /** `dir` 是会话文件所在目录(通常是 `sessionDirForCwd(sessionsRoot, cwd)`)。 */
25155
25469
  static create(dir, cwd, options = {}) {
25156
25470
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
25157
- return new _SessionManager(this.newHeader(cwd, options, now()), [], { kind: "lazy", dir }, now);
25471
+ return new _SessionManager(
25472
+ this.newHeader(cwd, options, now()),
25473
+ [],
25474
+ { kind: "lazy", dir },
25475
+ now,
25476
+ void 0,
25477
+ options.warn
25478
+ );
25158
25479
  }
25159
25480
  static createForCwd(sessionsRoot, cwd, options = {}) {
25160
25481
  return this.create(sessionDirForCwd(sessionsRoot, cwd), cwd, options);
@@ -25162,16 +25483,29 @@ var init_manager = __esm({
25162
25483
  static open(file, options = {}) {
25163
25484
  const lock = acquireLock(file);
25164
25485
  try {
25165
- const { lines: lines2 } = readSessionLines(file, { repair: true });
25486
+ const images = new ImageOffload(options.warn);
25487
+ const targets = editTargets(file);
25488
+ const { lines: lines2, bytes } = readSessionLines(file, {
25489
+ repair: true,
25490
+ onLine: (line2, locator) => {
25491
+ const entry = line2;
25492
+ images.load(entry, locator, targets.has(entry.id));
25493
+ }
25494
+ });
25166
25495
  const { header, entries, leafId } = migrateSessionLines(lines2, file);
25167
25496
  internSessionImages(entries);
25168
- return new _SessionManager(
25497
+ const manager = new _SessionManager(
25169
25498
  header,
25170
25499
  entries,
25171
25500
  { kind: "file", file, lock },
25172
25501
  options.now ?? (() => /* @__PURE__ */ new Date()),
25173
- leafId
25502
+ leafId,
25503
+ options.warn,
25504
+ images
25174
25505
  );
25506
+ manager.fileBytes = bytes ?? 0;
25507
+ manager.syncImages();
25508
+ return manager;
25175
25509
  } catch (error2) {
25176
25510
  lock.release();
25177
25511
  throw error2;
@@ -25212,10 +25546,18 @@ var init_manager = __esm({
25212
25546
  parentId: this.leaf,
25213
25547
  timestamp: this.now().toISOString()
25214
25548
  };
25215
- if (this.storage.kind === "file") appendLines(this.storage.file, [entry]);
25549
+ if (this.storage.kind === "file") {
25550
+ const written = appendLines(this.storage.file, [entry]);
25551
+ this.images.track(entry, { offset: this.fileBytes, length: written - 1 });
25552
+ this.fileBytes += written;
25553
+ }
25216
25554
  this._entries.push(entry);
25217
25555
  this.index.set(entry.id, entry);
25218
25556
  this.leaf = entry.id;
25557
+ if (entry.type === "context_edit" && this.storage.kind === "file") {
25558
+ const target = this.index.get(entry.targetId);
25559
+ if (target !== void 0 && this.branch().includes(target)) this.images.offload(target);
25560
+ }
25219
25561
  return entry;
25220
25562
  }
25221
25563
  setLeaf(id) {
@@ -25223,7 +25565,29 @@ var init_manager = __esm({
25223
25565
  throw new AmaError("invalid_arguments", `no such session entry: ${id}`);
25224
25566
  }
25225
25567
  this.leaf = id;
25226
- if (this.storage.kind === "file") appendLines(this.storage.file, [this.leafLine()]);
25568
+ if (this.storage.kind === "file") {
25569
+ this.fileBytes += appendLines(this.storage.file, [this.leafLine()]);
25570
+ }
25571
+ this.syncImages();
25572
+ }
25573
+ /** 按当前活动分支卸载 / 回读图片(只对已落盘的会话)。 */
25574
+ syncImages() {
25575
+ if (this.storage.kind === "file")
25576
+ this.images.sync(this.storage.file, this.branch(), this.index);
25577
+ }
25578
+ /** 原文条目(已卸载的从文件回读一份副本)。 */
25579
+ original(entry) {
25580
+ if (this.storage.kind !== "file" || !this.images.isOffloaded(entry.id)) return entry;
25581
+ return this.images.original(this.storage.file, entry);
25582
+ }
25583
+ /** 接上(或换掉)告警出口(图片读不回等);`open()` 期间缓冲的告警随即按序冲出(#183)。 */
25584
+ setWarn(warn2) {
25585
+ this.warn = warn2;
25586
+ this.images.setWarn(warn2);
25587
+ }
25588
+ /** 已卸载图片的条目数(测试与诊断用)。 */
25589
+ offloadedCount() {
25590
+ return this.images.size;
25227
25591
  }
25228
25592
  leafLine() {
25229
25593
  return { type: "leaf", id: this.leaf, timestamp: this.now().toISOString() };
@@ -25232,10 +25596,9 @@ var init_manager = __esm({
25232
25596
  return pathToRoot(this.index, leafId === void 0 ? this.leaf : leafId);
25233
25597
  }
25234
25598
  getEntries(since) {
25235
- if (since === void 0) return { entries: [...this._entries], leafId: this.leaf };
25236
- const at = this._entries.findIndex((entry) => entry.id === since);
25599
+ const at = since === void 0 ? -1 : this._entries.findIndex((entry) => entry.id === since);
25237
25600
  return {
25238
- entries: at < 0 ? [...this._entries] : this._entries.slice(at + 1),
25601
+ entries: this._entries.slice(at + 1).map((entry) => this.original(entry)),
25239
25602
  leafId: this.leaf
25240
25603
  };
25241
25604
  }
@@ -25254,7 +25617,10 @@ var init_manager = __esm({
25254
25617
  if (!this.index.has(entryId)) {
25255
25618
  throw new AmaError("invalid_arguments", `no such session entry: ${entryId}`);
25256
25619
  }
25257
- const copied = this.branch(entryId).map((entry) => structuredClone(entry));
25620
+ const copied = this.branch(entryId).map((entry) => {
25621
+ const original = this.original(entry);
25622
+ return original === entry ? structuredClone(entry) : original;
25623
+ });
25258
25624
  if (forkOptions.head !== void 0) {
25259
25625
  const head = {
25260
25626
  ...forkOptions.head,
@@ -25273,7 +25639,7 @@ var init_manager = __esm({
25273
25639
  if (this.storage.kind === "memory") storage = { kind: "memory" };
25274
25640
  else if (this.storage.kind === "lazy") storage = { kind: "lazy", dir: this.storage.dir };
25275
25641
  else storage = { kind: "lazy", dir: (0, import_node_path41.dirname)(this.storage.file) };
25276
- const forked = new _SessionManager(header, copied, storage, this.now);
25642
+ const forked = new _SessionManager(header, copied, storage, this.now, void 0, this.warn);
25277
25643
  if (parentFile !== void 0) forked.flush();
25278
25644
  return forked;
25279
25645
  }
@@ -25287,11 +25653,18 @@ var init_manager = __esm({
25287
25653
  if (this.storage.kind === "file") return this.storage.file;
25288
25654
  const file = (0, import_node_path41.join)(this.storage.dir, sessionFileName(new Date(this._header.timestamp), this.id));
25289
25655
  const moved = this.leaf !== (this._entries.at(-1)?.id ?? null);
25290
- writeNewSessionFile(file, [
25656
+ const lengths = writeNewSessionFile(file, [
25291
25657
  this._header,
25292
25658
  ...this._entries,
25293
25659
  ...moved ? [this.leafLine()] : []
25294
25660
  ]);
25661
+ let offset = 0;
25662
+ lengths.forEach((length, i) => {
25663
+ const entry = i === 0 ? void 0 : this._entries[i - 1];
25664
+ if (entry !== void 0) this.images.track(entry, { offset, length });
25665
+ offset += length + 1;
25666
+ });
25667
+ this.fileBytes = offset;
25295
25668
  let lock;
25296
25669
  try {
25297
25670
  lock = acquireLock(file);
@@ -25299,6 +25672,7 @@ var init_manager = __esm({
25299
25672
  lock = void 0;
25300
25673
  }
25301
25674
  this.storage = { kind: "file", file, lock };
25675
+ this.syncImages();
25302
25676
  return file;
25303
25677
  }
25304
25678
  isPersisted() {
@@ -25323,6 +25697,9 @@ var init_manager = __esm({
25323
25697
  });
25324
25698
 
25325
25699
  // src/agent/subagent-fork.ts
25700
+ function forkRatio(ratio) {
25701
+ return ratio !== void 0 && Number.isFinite(ratio) && ratio > 0 && ratio < 1 ? ratio : FORK_MAX_CONTEXT_RATIO;
25702
+ }
25326
25703
  function requestedContext(request, agent) {
25327
25704
  return request.context ?? agent.context ?? "fresh";
25328
25705
  }
@@ -25334,7 +25711,7 @@ function forkPoint(branch, parentToolCallId) {
25334
25711
  );
25335
25712
  return index2 > 0 ? branch[index2 - 1]?.id : void 0;
25336
25713
  }
25337
- function forkPlan(parent, spec2, model, thinking) {
25714
+ function forkPlan(parent, spec2, model, thinking, ratio) {
25338
25715
  const base4 = parent.childBase();
25339
25716
  if (model.provider !== base4.model.provider || model.id !== base4.model.id)
25340
25717
  return { fallback: `model ${model.provider}/${model.id} differs from the parent` };
@@ -25344,7 +25721,7 @@ function forkPlan(parent, spec2, model, thinking) {
25344
25721
  if (last === void 0) return { fallback: "the parent has not sent a request yet" };
25345
25722
  const reserve = parent.options.compaction?.reserveTokens ?? DEFAULT_COMPACTION_SETTINGS.reserveTokens;
25346
25723
  const window2 = model.contextWindow;
25347
- const limit = window2 === void 0 ? Infinity : FORK_MAX_CONTEXT_RATIO * Math.max(0, window2 - reserve);
25724
+ const limit = window2 === void 0 ? Infinity : forkRatio(ratio) * Math.max(0, window2 - reserve);
25348
25725
  if (last.promptTokens > limit)
25349
25726
  return { fallback: `parent context ${last.promptTokens} tokens exceeds ${Math.floor(limit)}` };
25350
25727
  const forkedFrom = forkPoint(parent.manager.branch(), spec2.request.parentToolCallId);
@@ -25367,12 +25744,17 @@ function forkPlan(parent, spec2, model, thinking) {
25367
25744
  }
25368
25745
  }
25369
25746
  });
25747
+ manager.append({
25748
+ type: "custom",
25749
+ customType: FORK_CUSTOM_TYPE,
25750
+ data: { taskLine: taskLine(spec2.request.prompt) }
25751
+ });
25370
25752
  return { manager, forkedFrom };
25371
25753
  }
25372
25754
  function forkBrief(input2) {
25373
25755
  const lines2 = [
25374
25756
  "<task>",
25375
- "You are a sub-agent forked from the conversation above at this point. The main agent cannot see your work, only your final reply; do not delegate further. Requests above were for the main agent: do only this task. Instructions in this block take precedence over earlier plans or reminders above."
25757
+ "You are a sub-agent forked from the conversation above at this point. The main agent cannot see your work, only your final reply; do not delegate further. Requests above were for the main agent: do only this task. Instructions in this block take precedence over earlier plans or reminders above. Do not call task or task_ctl."
25376
25758
  ];
25377
25759
  if (input2.worktree !== void 0)
25378
25760
  lines2.push(
@@ -25399,6 +25781,7 @@ var FORK_MAX_CONTEXT_RATIO, TASK_CUSTOM_TYPE2;
25399
25781
  var init_subagent_fork = __esm({
25400
25782
  "src/agent/subagent-fork.ts"() {
25401
25783
  "use strict";
25784
+ init_task();
25402
25785
  init_session_compaction();
25403
25786
  FORK_MAX_CONTEXT_RATIO = 0.5;
25404
25787
  TASK_CUSTOM_TYPE2 = "ama.task";
@@ -26310,9 +26693,15 @@ function runningResult(record3, text, outputFile) {
26310
26693
  status: "running"
26311
26694
  };
26312
26695
  if (outputFile !== void 0) result.outputFile = outputFile;
26696
+ if (record3.info.context !== void 0) result.context = record3.info.context;
26313
26697
  return result;
26314
26698
  }
26315
- function startedResult(record3, outputFile) {
26699
+ async function startedResult(record3, outputFile, settle) {
26700
+ if (settle !== void 0)
26701
+ await Promise.race([
26702
+ settle.catch(() => void 0),
26703
+ new Promise((resolve41) => setImmediate(resolve41))
26704
+ ]);
26316
26705
  return runningResult(record3, startedText(record3.info.taskId, record3.agent.name), outputFile);
26317
26706
  }
26318
26707
  function backgroundedResult(record3, reason, afterMs, outputFile) {
@@ -26694,7 +27083,7 @@ var init_subagent_registry = __esm({
26694
27083
  const file = this.outputFileFor(record3.info.taskId);
26695
27084
  if (background) {
26696
27085
  void running.then((result) => this.notifier.notify(record3, result));
26697
- return Promise.resolve(startedResult(record3, file));
27086
+ return startedResult(record3, file, running);
26698
27087
  }
26699
27088
  const { waiter, promise } = foregroundWaiter(() => {
26700
27089
  clearTimeout(timer);
@@ -26999,7 +27388,8 @@ async function startAmaChild(parent, spec2, createChild, run7) {
26999
27388
  }
27000
27389
  const thinkingLevel = spec2.request.thinkingLevel ?? spec2.agent.thinking ?? base4.thinkingLevel ?? "off";
27001
27390
  const wanted = spec2.resumeFile === void 0 && requestedContext(spec2.request, spec2.agent) === "fork";
27002
- const plan2 = wanted ? forkPlan(parent, spec2, model, thinkingLevel) : void 0;
27391
+ const ratio = subagentRegistryFor(parent).env.modelConfig?.subagents?.forkMaxContextRatio;
27392
+ const plan2 = wanted ? forkPlan(parent, spec2, model, thinkingLevel, ratio) : void 0;
27003
27393
  if (plan2 !== void 0 && "fallback" in plan2)
27004
27394
  parent.log("info", `task ${spec2.taskId}: fork falls back to fresh (${plan2.fallback})`);
27005
27395
  const forked = plan2 !== void 0 && "manager" in plan2 ? plan2.manager : void 0;
@@ -27012,6 +27402,10 @@ async function startAmaChild(parent, spec2, createChild, run7) {
27012
27402
  const unavailable = inherited.filter(
27013
27403
  (name) => !allowed.includes(name) && !PARENT_ONLY_TOOLS.includes(name)
27014
27404
  );
27405
+ const briefUnavailable = [
27406
+ ...unavailable,
27407
+ ...PARENT_ONLY_TOOLS.filter((name) => inherited.includes(name))
27408
+ ];
27015
27409
  const options = {
27016
27410
  ...parent.options,
27017
27411
  ...base4,
@@ -27100,7 +27494,7 @@ async function startAmaChild(parent, spec2, createChild, run7) {
27100
27494
  const first = forked === void 0 ? run7.prompt : forkBrief({
27101
27495
  prompt: run7.prompt,
27102
27496
  role: spec2.agent.prompt,
27103
- unavailable,
27497
+ unavailable: briefUnavailable,
27104
27498
  ...spec2.isolated ? { worktree: { cwd: spec2.cwd, parentCwd: parent.cwd } } : {}
27105
27499
  });
27106
27500
  let current3 = runOnce(first, spec2.origin);
@@ -27328,6 +27722,7 @@ var init_session_extensions = __esm({
27328
27722
  if (extension !== void 0) list5.push(extension);
27329
27723
  }
27330
27724
  this.list = list5;
27725
+ core.manager.setWarn((message) => core.log("warn", message));
27331
27726
  }
27332
27727
  get size() {
27333
27728
  return this.list.length;
@@ -29168,16 +29563,16 @@ async function putBlob(dataDir, bytes, hash = hashBytes(bytes)) {
29168
29563
  const target = blobPath(dataDir, hash);
29169
29564
  if (await exists3(target)) {
29170
29565
  const now = /* @__PURE__ */ new Date();
29171
- await (0, import_promises11.utimes)(target, now, now).catch(() => void 0);
29566
+ await (0, import_promises12.utimes)(target, now, now).catch(() => void 0);
29172
29567
  return hash;
29173
29568
  }
29174
- await (0, import_promises11.mkdir)((0, import_node_path47.dirname)(target), { recursive: true, mode: 448 });
29569
+ await (0, import_promises12.mkdir)((0, import_node_path47.dirname)(target), { recursive: true, mode: 448 });
29175
29570
  const tmp = `${target}.tmp-${process.pid}-${tmpCounter++}`;
29176
29571
  try {
29177
- await (0, import_promises11.writeFile)(tmp, bytes, { mode: 384 });
29178
- await (0, import_promises11.rename)(tmp, target);
29572
+ await (0, import_promises12.writeFile)(tmp, bytes, { mode: 384 });
29573
+ await (0, import_promises12.rename)(tmp, target);
29179
29574
  } catch (error2) {
29180
- await (0, import_promises11.rm)(tmp, { force: true });
29575
+ await (0, import_promises12.rm)(tmp, { force: true });
29181
29576
  if (await exists3(target)) return hash;
29182
29577
  throw error2;
29183
29578
  }
@@ -29185,7 +29580,7 @@ async function putBlob(dataDir, bytes, hash = hashBytes(bytes)) {
29185
29580
  }
29186
29581
  async function readBlob(dataDir, hash) {
29187
29582
  try {
29188
- return await (0, import_promises11.readFile)(blobPath(dataDir, hash));
29583
+ return await (0, import_promises12.readFile)(blobPath(dataDir, hash));
29189
29584
  } catch (error2) {
29190
29585
  if (isNotFound(error2)) return void 0;
29191
29586
  throw error2;
@@ -29193,7 +29588,7 @@ async function readBlob(dataDir, hash) {
29193
29588
  }
29194
29589
  async function realParentOf(absolutePath) {
29195
29590
  try {
29196
- return await (0, import_promises11.realpath)((0, import_node_path47.dirname)(absolutePath));
29591
+ return await (0, import_promises12.realpath)((0, import_node_path47.dirname)(absolutePath));
29197
29592
  } catch (error2) {
29198
29593
  if (isNotFound(error2)) return void 0;
29199
29594
  throw error2;
@@ -29204,7 +29599,7 @@ async function recordFile(absolutePath, options) {
29204
29599
  const max = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
29205
29600
  let info2;
29206
29601
  try {
29207
- info2 = await (0, import_promises11.lstat)(absolutePath);
29602
+ info2 = await (0, import_promises12.lstat)(absolutePath);
29208
29603
  } catch (error2) {
29209
29604
  if (!isNotFound(error2)) throw error2;
29210
29605
  const realParentDir2 = await realParentOf(absolutePath);
@@ -29218,7 +29613,7 @@ async function recordFile(absolutePath, options) {
29218
29613
  const realParentDir = await realParentOf(absolutePath);
29219
29614
  const base4 = { mode, size: info2.size, ...realParentDir !== void 0 ? { realParentDir } : {} };
29220
29615
  if (info2.size > max) return { record: { blob: null, ...base4, skipped: "too_large" }, at };
29221
- const bytes = await (0, import_promises11.readFile)(absolutePath);
29616
+ const bytes = await (0, import_promises12.readFile)(absolutePath);
29222
29617
  if (bytes.length > max)
29223
29618
  return { record: { blob: null, ...base4, size: bytes.length, skipped: "too_large" }, at };
29224
29619
  const blob = await putBlob(options.dataDir, bytes);
@@ -29245,7 +29640,7 @@ async function listBlobs(dataDir, includeTemp = false) {
29245
29640
  const root = blobsDir(dataDir);
29246
29641
  let shards;
29247
29642
  try {
29248
- shards = await (0, import_promises11.readdir)(root);
29643
+ shards = await (0, import_promises12.readdir)(root);
29249
29644
  } catch (error2) {
29250
29645
  if (isNotFound(error2)) return [];
29251
29646
  throw error2;
@@ -29255,7 +29650,7 @@ async function listBlobs(dataDir, includeTemp = false) {
29255
29650
  if (!/^[0-9a-f]{2}$/.test(shard)) continue;
29256
29651
  let names;
29257
29652
  try {
29258
- names = await (0, import_promises11.readdir)((0, import_node_path47.join)(root, shard));
29653
+ names = await (0, import_promises12.readdir)((0, import_node_path47.join)(root, shard));
29259
29654
  } catch {
29260
29655
  continue;
29261
29656
  }
@@ -29264,7 +29659,7 @@ async function listBlobs(dataDir, includeTemp = false) {
29264
29659
  if (!isBlob && !(includeTemp && name.includes(".tmp-"))) continue;
29265
29660
  const path = (0, import_node_path47.join)(root, shard, name);
29266
29661
  try {
29267
- const info2 = await (0, import_promises11.stat)(path);
29662
+ const info2 = await (0, import_promises12.stat)(path);
29268
29663
  if (!info2.isFile()) continue;
29269
29664
  out.push({
29270
29665
  path,
@@ -29283,19 +29678,19 @@ function isNotFound(error2) {
29283
29678
  }
29284
29679
  async function exists3(path) {
29285
29680
  try {
29286
- await (0, import_promises11.stat)(path);
29681
+ await (0, import_promises12.stat)(path);
29287
29682
  return true;
29288
29683
  } catch {
29289
29684
  return false;
29290
29685
  }
29291
29686
  }
29292
- var import_node_crypto9, import_node_fs34, import_promises11, import_node_path47, FILE_HISTORY_DIR, BLOBS_DIR, DEFAULT_MAX_FILE_BYTES, SHA256_HEX, tmpCounter;
29687
+ var import_node_crypto9, import_node_fs34, import_promises12, import_node_path47, FILE_HISTORY_DIR, BLOBS_DIR, DEFAULT_MAX_FILE_BYTES, SHA256_HEX, tmpCounter;
29293
29688
  var init_blobs = __esm({
29294
29689
  "src/checkpoints/blobs.ts"() {
29295
29690
  "use strict";
29296
29691
  import_node_crypto9 = require("node:crypto");
29297
29692
  import_node_fs34 = require("node:fs");
29298
- import_promises11 = require("node:fs/promises");
29693
+ import_promises12 = require("node:fs/promises");
29299
29694
  import_node_path47 = require("node:path");
29300
29695
  FILE_HISTORY_DIR = "file-history";
29301
29696
  BLOBS_DIR = "blobs";
@@ -29311,7 +29706,7 @@ function rootsFile(dataDir) {
29311
29706
  }
29312
29707
  async function readSessionRoots(dataDir) {
29313
29708
  try {
29314
- const value = JSON.parse(await (0, import_promises12.readFile)(rootsFile(dataDir), "utf8"));
29709
+ const value = JSON.parse(await (0, import_promises13.readFile)(rootsFile(dataDir), "utf8"));
29315
29710
  const roots = value?.roots;
29316
29711
  return Array.isArray(roots) ? roots.filter((r) => typeof r === "string") : [];
29317
29712
  } catch {
@@ -29323,18 +29718,18 @@ async function registerSessionRoot(dataDir, sessionsRoot) {
29323
29718
  const roots = await readSessionRoots(dataDir);
29324
29719
  if (roots.includes(root)) return;
29325
29720
  const dir = fileHistoryDir(dataDir);
29326
- await (0, import_promises12.mkdir)(dir, { recursive: true, mode: 448 });
29721
+ await (0, import_promises13.mkdir)(dir, { recursive: true, mode: 448 });
29327
29722
  const tmp = `${rootsFile(dataDir)}.tmp-${process.pid}`;
29328
- await (0, import_promises12.writeFile)(tmp, `${JSON.stringify({ roots: [...roots, root] }, null, 2)}
29723
+ await (0, import_promises13.writeFile)(tmp, `${JSON.stringify({ roots: [...roots, root] }, null, 2)}
29329
29724
  `, {
29330
29725
  mode: 384
29331
29726
  });
29332
- await (0, import_promises12.rename)(tmp, rootsFile(dataDir));
29727
+ await (0, import_promises13.rename)(tmp, rootsFile(dataDir));
29333
29728
  }
29334
29729
  async function* sessionFiles(dir) {
29335
29730
  let entries;
29336
29731
  try {
29337
- entries = await (0, import_promises12.readdir)(dir, { withFileTypes: true });
29732
+ entries = await (0, import_promises13.readdir)(dir, { withFileTypes: true });
29338
29733
  } catch (error2) {
29339
29734
  if (isNotFound(error2)) return;
29340
29735
  throw error2;
@@ -29356,7 +29751,7 @@ async function collectBlobRefs(sessionRoots) {
29356
29751
  for await (const file of sessionFiles(abs)) {
29357
29752
  let text;
29358
29753
  try {
29359
- text = await (0, import_promises12.readFile)(file, "utf8");
29754
+ text = await (0, import_promises13.readFile)(file, "utf8");
29360
29755
  } catch (error2) {
29361
29756
  if (isNotFound(error2)) continue;
29362
29757
  throw error2;
@@ -29389,7 +29784,7 @@ async function gcBlobs(options) {
29389
29784
  if (old && unreferenced) {
29390
29785
  if (options.dryRun !== true) {
29391
29786
  try {
29392
- await (0, import_promises12.rm)(blob.path, { force: true });
29787
+ await (0, import_promises13.rm)(blob.path, { force: true });
29393
29788
  } catch {
29394
29789
  continue;
29395
29790
  }
@@ -29414,11 +29809,11 @@ function formatBytes2(bytes) {
29414
29809
  }
29415
29810
  return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unit]}`;
29416
29811
  }
29417
- var import_promises12, import_node_path48, GC_MIN_AGE_MS, ROOTS_FILE, HASH_IN_LINE;
29812
+ var import_promises13, import_node_path48, GC_MIN_AGE_MS, ROOTS_FILE, HASH_IN_LINE;
29418
29813
  var init_gc = __esm({
29419
29814
  "src/checkpoints/gc.ts"() {
29420
29815
  "use strict";
29421
- import_promises12 = require("node:fs/promises");
29816
+ import_promises13 = require("node:fs/promises");
29422
29817
  import_node_path48 = require("node:path");
29423
29818
  init_blobs();
29424
29819
  init_types10();
@@ -29547,7 +29942,7 @@ var init_replay = __esm({
29547
29942
  // src/checkpoints/git-head.ts
29548
29943
  async function readText2(path) {
29549
29944
  try {
29550
- return await (0, import_promises13.readFile)(path, "utf8");
29945
+ return await (0, import_promises14.readFile)(path, "utf8");
29551
29946
  } catch {
29552
29947
  return void 0;
29553
29948
  }
@@ -29557,7 +29952,7 @@ async function findGitDir(cwd) {
29557
29952
  for (; ; ) {
29558
29953
  const candidate = (0, import_node_path49.join)(dir, ".git");
29559
29954
  try {
29560
- const info2 = await (0, import_promises13.stat)(candidate);
29955
+ const info2 = await (0, import_promises14.stat)(candidate);
29561
29956
  if (info2.isDirectory()) return candidate;
29562
29957
  if (info2.isFile()) {
29563
29958
  const text = await readText2(candidate) ?? "";
@@ -29605,11 +30000,11 @@ async function readGitHead(cwd) {
29605
30000
  const branch = ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : void 0;
29606
30001
  return { head: sha, ...branch !== void 0 ? { branch } : {} };
29607
30002
  }
29608
- var import_promises13, import_node_path49, SHA;
30003
+ var import_promises14, import_node_path49, SHA;
29609
30004
  var init_git_head = __esm({
29610
30005
  "src/checkpoints/git-head.ts"() {
29611
30006
  "use strict";
29612
- import_promises13 = require("node:fs/promises");
30007
+ import_promises14 = require("node:fs/promises");
29613
30008
  import_node_path49 = require("node:path");
29614
30009
  SHA = /^[0-9a-f]{40}([0-9a-f]{24})?$/;
29615
30010
  }
@@ -29629,7 +30024,7 @@ function emptyCodeResult() {
29629
30024
  }
29630
30025
  async function lstatOrUndefined(path) {
29631
30026
  try {
29632
- return await (0, import_promises14.lstat)(path);
30027
+ return await (0, import_promises15.lstat)(path);
29633
30028
  } catch (error2) {
29634
30029
  if (isNotFound(error2)) return void 0;
29635
30030
  throw error2;
@@ -29718,7 +30113,7 @@ async function restoreFile(abs, record3, options) {
29718
30113
  await checkParent(abs, record3);
29719
30114
  const targetBytes = record3.blob === null ? void 0 : await (options.load ?? ((r) => readBlob(options.dataDir, r.blob)))(record3);
29720
30115
  if (record3.blob !== null && targetBytes === void 0) throw new Skip("backup_missing");
29721
- const currentBytes = info2 !== void 0 && info2.size <= options.maxDiff ? await (0, import_promises14.readFile)(abs) : void 0;
30116
+ const currentBytes = info2 !== void 0 && info2.size <= options.maxDiff ? await (0, import_promises15.readFile)(abs) : void 0;
29722
30117
  const counted = info2 !== void 0 && currentBytes === void 0 ? { insertions: 0, deletions: 0 } : countLineChanges(toText(currentBytes), toText(targetBytes));
29723
30118
  const outcome = { deleted: record3.blob === null, conflict, ...counted };
29724
30119
  if (options.dryRun === true) return outcome;
@@ -29742,17 +30137,17 @@ async function removeRegular(abs, before2) {
29742
30137
  if (now.nlink > 1) throw new Skip("hardlink");
29743
30138
  if (now.dev !== before2.dev || now.ino !== before2.ino)
29744
30139
  throw new Error(msg().session.checkpoints.replacedDuringRestore);
29745
- await (0, import_promises14.unlink)(abs);
30140
+ await (0, import_promises15.unlink)(abs);
29746
30141
  }
29747
30142
  async function writeBack(abs, bytes, record3, before2) {
29748
30143
  if (before2 === void 0) {
29749
30144
  const parent = (0, import_node_path50.dirname)(abs);
29750
- await (0, import_promises14.mkdir)(parent, { recursive: true });
29751
- if (record3.realParentDir !== void 0 && await (0, import_promises14.realpath)(parent) !== record3.realParentDir) {
30145
+ await (0, import_promises15.mkdir)(parent, { recursive: true });
30146
+ if (record3.realParentDir !== void 0 && await (0, import_promises15.realpath)(parent) !== record3.realParentDir) {
29752
30147
  throw new Skip("parent_moved");
29753
30148
  }
29754
30149
  const flags2 = import_node_fs35.constants.O_WRONLY | import_node_fs35.constants.O_CREAT | import_node_fs35.constants.O_EXCL | NOFOLLOW;
29755
- const handle2 = await (0, import_promises14.open)(abs, flags2, record3.mode ?? 420);
30150
+ const handle2 = await (0, import_promises15.open)(abs, flags2, record3.mode ?? 420);
29756
30151
  try {
29757
30152
  await handle2.writeFile(bytes);
29758
30153
  if (record3.mode !== void 0) await handle2.chmod(record3.mode);
@@ -29763,7 +30158,7 @@ async function writeBack(abs, bytes, record3, before2) {
29763
30158
  }
29764
30159
  let handle;
29765
30160
  try {
29766
- handle = await (0, import_promises14.open)(abs, import_node_fs35.constants.O_WRONLY | NOFOLLOW);
30161
+ handle = await (0, import_promises15.open)(abs, import_node_fs35.constants.O_WRONLY | NOFOLLOW);
29767
30162
  } catch (error2) {
29768
30163
  const code = error2.code;
29769
30164
  if (code === "ELOOP" || code === "EMLINK") throw new Skip("symlink");
@@ -29771,7 +30166,7 @@ async function writeBack(abs, bytes, record3, before2) {
29771
30166
  }
29772
30167
  try {
29773
30168
  const opened = await handle.stat();
29774
- const now = await (0, import_promises14.lstat)(abs);
30169
+ const now = await (0, import_promises15.lstat)(abs);
29775
30170
  if (!opened.isFile() || opened.dev !== before2.dev || opened.ino !== before2.ino || now.dev !== opened.dev || now.ino !== opened.ino) {
29776
30171
  throw new Error(msg().session.checkpoints.replacedDuringRestore);
29777
30172
  }
@@ -29789,12 +30184,12 @@ async function gitHintFor(cwd, target) {
29789
30184
  if (current3 === void 0 || current3.head === target.git.head) return void 0;
29790
30185
  return { recordedHead: target.git.head, currentHead: current3.head };
29791
30186
  }
29792
- var import_node_fs35, import_promises14, import_node_path50, RestoreSkip, Skip, IS_WINDOWS, NOFOLLOW;
30187
+ var import_node_fs35, import_promises15, import_node_path50, RestoreSkip, Skip, IS_WINDOWS, NOFOLLOW;
29793
30188
  var init_restore = __esm({
29794
30189
  "src/checkpoints/restore.ts"() {
29795
30190
  "use strict";
29796
30191
  import_node_fs35 = require("node:fs");
29797
- import_promises14 = require("node:fs/promises");
30192
+ import_promises15 = require("node:fs/promises");
29798
30193
  import_node_path50 = require("node:path");
29799
30194
  init_edit();
29800
30195
  init_blobs();
@@ -29845,7 +30240,7 @@ async function shadowUsage(dataDir) {
29845
30240
  const usage = { repos: 0, bytes: 0 };
29846
30241
  let names;
29847
30242
  try {
29848
- names = await (0, import_promises15.readdir)(shadowRoot(dataDir));
30243
+ names = await (0, import_promises16.readdir)(shadowRoot(dataDir));
29849
30244
  } catch {
29850
30245
  return usage;
29851
30246
  }
@@ -29861,7 +30256,7 @@ async function duBytes(dir) {
29861
30256
  let total = 0;
29862
30257
  let entries;
29863
30258
  try {
29864
- entries = await (0, import_promises15.readdir)(dir, { withFileTypes: true });
30259
+ entries = await (0, import_promises16.readdir)(dir, { withFileTypes: true });
29865
30260
  } catch {
29866
30261
  return 0;
29867
30262
  }
@@ -29870,7 +30265,7 @@ async function duBytes(dir) {
29870
30265
  if (entry.isDirectory()) total += await duBytes(full);
29871
30266
  else if (entry.isFile()) {
29872
30267
  try {
29873
- total += (await (0, import_promises15.stat)(full)).size;
30268
+ total += (await (0, import_promises16.stat)(full)).size;
29874
30269
  } catch {
29875
30270
  }
29876
30271
  }
@@ -29954,7 +30349,7 @@ function excludeLine(path) {
29954
30349
  async function removeStaleIndexes(dir) {
29955
30350
  let names;
29956
30351
  try {
29957
- names = await (0, import_promises15.readdir)(dir);
30352
+ names = await (0, import_promises16.readdir)(dir);
29958
30353
  } catch {
29959
30354
  return;
29960
30355
  }
@@ -29963,7 +30358,7 @@ async function removeStaleIndexes(dir) {
29963
30358
  if (match === null) continue;
29964
30359
  const pid = Number(match[1]);
29965
30360
  if (pid === process.pid || isAlive(pid)) continue;
29966
- await (0, import_promises15.rm)((0, import_node_path51.join)(dir, name), { force: true }).catch(() => void 0);
30361
+ await (0, import_promises16.rm)((0, import_node_path51.join)(dir, name), { force: true }).catch(() => void 0);
29967
30362
  }
29968
30363
  }
29969
30364
  function isAlive(pid) {
@@ -29976,7 +30371,7 @@ function isAlive(pid) {
29976
30371
  }
29977
30372
  async function exists4(path) {
29978
30373
  try {
29979
- await (0, import_promises15.stat)(path);
30374
+ await (0, import_promises16.stat)(path);
29980
30375
  return true;
29981
30376
  } catch {
29982
30377
  return false;
@@ -29984,18 +30379,18 @@ async function exists4(path) {
29984
30379
  }
29985
30380
  async function readSmall2(path) {
29986
30381
  try {
29987
- return (await (0, import_promises15.readFile)(path, "utf8")).trim();
30382
+ return (await (0, import_promises16.readFile)(path, "utf8")).trim();
29988
30383
  } catch {
29989
30384
  return void 0;
29990
30385
  }
29991
30386
  }
29992
- var import_node_child_process7, import_node_crypto10, import_promises15, import_node_os10, import_node_path51, SHADOW_DIR, SHADOW_MAX_FILES, SHADOW_MAX_SNAPSHOT_MS, GIT_TIMEOUT_MS, CONFIG_VERSION, IDENTITY, ATTRIBUTES, GitMissingError, MODE_ABSENT, MODE_SYMLINK, MODE_GITLINK, MODE_EXECUTABLE, queues2, ShadowRepo;
30387
+ var import_node_child_process7, import_node_crypto10, import_promises16, import_node_os10, import_node_path51, SHADOW_DIR, SHADOW_MAX_FILES, SHADOW_MAX_SNAPSHOT_MS, SHADOW_SLOW_STREAK, GIT_TIMEOUT_MS, CONFIG_VERSION, IDENTITY, ATTRIBUTES, GitMissingError, MODE_ABSENT, MODE_SYMLINK, MODE_GITLINK, MODE_EXECUTABLE, queues2, ShadowRepo;
29993
30388
  var init_shadow_git = __esm({
29994
30389
  "src/checkpoints/shadow-git.ts"() {
29995
30390
  "use strict";
29996
30391
  import_node_child_process7 = require("node:child_process");
29997
30392
  import_node_crypto10 = require("node:crypto");
29998
- import_promises15 = require("node:fs/promises");
30393
+ import_promises16 = require("node:fs/promises");
29999
30394
  import_node_os10 = require("node:os");
30000
30395
  import_node_path51 = require("node:path");
30001
30396
  init_blobs();
@@ -30003,6 +30398,7 @@ var init_shadow_git = __esm({
30003
30398
  SHADOW_DIR = "shadow";
30004
30399
  SHADOW_MAX_FILES = 2e4;
30005
30400
  SHADOW_MAX_SNAPSHOT_MS = 3e3;
30401
+ SHADOW_SLOW_STREAK = 2;
30006
30402
  GIT_TIMEOUT_MS = 6e4;
30007
30403
  CONFIG_VERSION = "1";
30008
30404
  IDENTITY = { name: "ama", email: "ama@localhost" };
@@ -30028,6 +30424,8 @@ var init_shadow_git = __esm({
30028
30424
  indexFile;
30029
30425
  initialized;
30030
30426
  counted = false;
30427
+ /** 连续超时的快照数(首次快照不计),到 `SHADOW_SLOW_STREAK` 才降级。 */
30428
+ slowStreak = 0;
30031
30429
  constructor(dataDir, cwd, options = {}) {
30032
30430
  this.cwd = (0, import_node_path51.resolve)(cwd);
30033
30431
  this.dir = shadowRepoDir(dataDir, this.cwd);
@@ -30079,9 +30477,9 @@ var init_shadow_git = __esm({
30079
30477
  }
30080
30478
  async doInit() {
30081
30479
  const hooks = (0, import_node_path51.join)(this.dir, "ama-hooks");
30082
- await (0, import_promises15.mkdir)(hooks, { recursive: true, mode: 448 });
30083
- await (0, import_promises15.writeFile)((0, import_node_path51.join)(this.dir, "ama-empty"), "", { flag: "a", mode: 384 });
30084
- await (0, import_promises15.writeFile)((0, import_node_path51.join)(this.dir, "ama-cwd"), `${this.cwd}
30480
+ await (0, import_promises16.mkdir)(hooks, { recursive: true, mode: 448 });
30481
+ await (0, import_promises16.writeFile)((0, import_node_path51.join)(this.dir, "ama-empty"), "", { flag: "a", mode: 384 });
30482
+ await (0, import_promises16.writeFile)((0, import_node_path51.join)(this.dir, "ama-cwd"), `${this.cwd}
30085
30483
  `, { mode: 384 });
30086
30484
  const version = await readSmall2((0, import_node_path51.join)(this.dir, "ama-config-version"));
30087
30485
  if (version !== CONFIG_VERSION) {
@@ -30112,9 +30510,9 @@ var init_shadow_git = __esm({
30112
30510
  ["user.useConfigOnly", "true"]
30113
30511
  ];
30114
30512
  for (const [key, value] of config) await this.run(["config", key, value]);
30115
- await (0, import_promises15.mkdir)((0, import_node_path51.join)(this.dir, "info"), { recursive: true });
30116
- await (0, import_promises15.writeFile)((0, import_node_path51.join)(this.dir, "info", "attributes"), ATTRIBUTES);
30117
- await (0, import_promises15.writeFile)((0, import_node_path51.join)(this.dir, "ama-config-version"), CONFIG_VERSION);
30513
+ await (0, import_promises16.mkdir)((0, import_node_path51.join)(this.dir, "info"), { recursive: true });
30514
+ await (0, import_promises16.writeFile)((0, import_node_path51.join)(this.dir, "info", "attributes"), ATTRIBUTES);
30515
+ await (0, import_promises16.writeFile)((0, import_node_path51.join)(this.dir, "ama-config-version"), CONFIG_VERSION);
30118
30516
  }
30119
30517
  await removeStaleIndexes(this.dir);
30120
30518
  }
@@ -30133,8 +30531,8 @@ var init_shadow_git = __esm({
30133
30531
  } catch (error2) {
30134
30532
  if (error2 instanceof GitMissingError) throw error2;
30135
30533
  }
30136
- await (0, import_promises15.mkdir)((0, import_node_path51.join)(this.dir, "info"), { recursive: true });
30137
- await (0, import_promises15.writeFile)((0, import_node_path51.join)(this.dir, "info", "exclude"), `${lines2.join("\n")}
30534
+ await (0, import_promises16.mkdir)((0, import_node_path51.join)(this.dir, "info"), { recursive: true });
30535
+ await (0, import_promises16.writeFile)((0, import_node_path51.join)(this.dir, "info", "exclude"), `${lines2.join("\n")}
30138
30536
  `);
30139
30537
  }
30140
30538
  /** 把工作区收进索引并写树,返回树 id(调用前已 writeExcludes)。 */
@@ -30147,6 +30545,7 @@ var init_shadow_git = __esm({
30147
30545
  return exclusive(this.dir, async () => {
30148
30546
  await this.init();
30149
30547
  const started = this.now();
30548
+ const first = !this.counted;
30150
30549
  await this.writeExcludes();
30151
30550
  if (!this.counted) {
30152
30551
  const out = await this.run([
@@ -30172,7 +30571,9 @@ var init_shadow_git = __esm({
30172
30571
  if (parent !== void 0 && await this.hasCommitUnlocked(parent)) args.push("-p", parent);
30173
30572
  const commit = await this.text(args);
30174
30573
  const elapsed = this.now() - started;
30175
- if (elapsed > this.maxSnapshotMs) {
30574
+ if (first) return { commit };
30575
+ this.slowStreak = elapsed > this.maxSnapshotMs ? this.slowStreak + 1 : 0;
30576
+ if (this.slowStreak >= SHADOW_SLOW_STREAK) {
30176
30577
  return {
30177
30578
  commit,
30178
30579
  degrade: {
@@ -30291,7 +30692,7 @@ async function restoreFromShadow(options) {
30291
30692
  changes.filter((c) => isRegularMode(c.oldMode)).map((c) => c.oldOid),
30292
30693
  maxBytes
30293
30694
  );
30294
- const realCwd = await (0, import_promises16.realpath)(options.cwd);
30695
+ const realCwd = await (0, import_promises17.realpath)(options.cwd);
30295
30696
  const latest = latestCheckpoint(options.state);
30296
30697
  const result = emptyCodeResult();
30297
30698
  const touched = [];
@@ -30342,7 +30743,7 @@ function isRegularMode(mode) {
30342
30743
  async function targetRecord(change, abs, realCwd, blobs) {
30343
30744
  const slash = change.path.lastIndexOf("/");
30344
30745
  const realParentDir = slash < 0 ? realCwd : (0, import_node_path52.join)(realCwd, ...change.path.slice(0, slash).split("/"));
30345
- const info2 = await (0, import_promises16.lstat)(abs).catch(() => void 0);
30746
+ const info2 = await (0, import_promises17.lstat)(abs).catch(() => void 0);
30346
30747
  if (change.newMode === MODE_ABSENT && info2 !== void 0) {
30347
30748
  return void 0;
30348
30749
  }
@@ -30375,11 +30776,11 @@ function merge(into, from) {
30375
30776
  into.insertions += from.insertions;
30376
30777
  into.deletions += from.deletions;
30377
30778
  }
30378
- var import_promises16, import_node_path52, IS_WINDOWS2;
30779
+ var import_promises17, import_node_path52, IS_WINDOWS2;
30379
30780
  var init_shadow_restore = __esm({
30380
30781
  "src/checkpoints/shadow-restore.ts"() {
30381
30782
  "use strict";
30382
- import_promises16 = require("node:fs/promises");
30783
+ import_promises17 = require("node:fs/promises");
30383
30784
  import_node_path52 = require("node:path");
30384
30785
  init_blobs();
30385
30786
  init_replay();
@@ -30394,11 +30795,11 @@ var init_shadow_restore = __esm({
30394
30795
  function describe2(error2) {
30395
30796
  return error2 instanceof Error ? error2.message : String(error2);
30396
30797
  }
30397
- var import_promises17, MTIME_GRANULARITY_MS, CheckpointTracker;
30798
+ var import_promises18, MTIME_GRANULARITY_MS, CheckpointTracker;
30398
30799
  var init_tracker = __esm({
30399
30800
  "src/checkpoints/tracker.ts"() {
30400
30801
  "use strict";
30401
- import_promises17 = require("node:fs/promises");
30802
+ import_promises18 = require("node:fs/promises");
30402
30803
  init_git_head();
30403
30804
  init_blobs();
30404
30805
  init_replay();
@@ -30510,7 +30911,7 @@ var init_tracker = __esm({
30510
30911
  const at = this.now();
30511
30912
  let info2;
30512
30913
  try {
30513
- info2 = await (0, import_promises17.lstat)(absolutePath);
30914
+ info2 = await (0, import_promises18.lstat)(absolutePath);
30514
30915
  } catch (error2) {
30515
30916
  if (!isNotFound(error2)) throw error2;
30516
30917
  if (prev !== void 0 && prev.record.blob === null && prev.record.skipped === void 0)
@@ -30541,7 +30942,7 @@ var init_tracker = __esm({
30541
30942
  return prev;
30542
30943
  return { record: { blob: null, ...base4, skipped: "too_large" }, at };
30543
30944
  }
30544
- const bytes = await (0, import_promises17.readFile)(absolutePath);
30945
+ const bytes = await (0, import_promises18.readFile)(absolutePath);
30545
30946
  const hash = hashBytes(bytes);
30546
30947
  if (p !== void 0 && p.blob === hash && p.mode === mode && p.realParentDir === realParentDir) {
30547
30948
  return { record: p, at };
@@ -30863,7 +31264,7 @@ function formatSkillInvocation(skill, body, args) {
30863
31264
 
30864
31265
  ${args}`;
30865
31266
  }
30866
- async function expandSkillCommand(text, skills, read2 = (p) => (0, import_promises18.readFile)(p, "utf8")) {
31267
+ async function expandSkillCommand(text, skills, read2 = (p) => (0, import_promises19.readFile)(p, "utf8")) {
30867
31268
  const cmd = parseSkillCommand(text);
30868
31269
  if (!cmd) return void 0;
30869
31270
  const skill = skills.find((s2) => s2.name === cmd.name);
@@ -30871,11 +31272,11 @@ async function expandSkillCommand(text, skills, read2 = (p) => (0, import_promis
30871
31272
  const body = skillBody(await read2(skill.location));
30872
31273
  return { kind: "expanded", text: formatSkillInvocation(skill, body, cmd.args), skill };
30873
31274
  }
30874
- var import_promises18;
31275
+ var import_promises19;
30875
31276
  var init_expand = __esm({
30876
31277
  "src/skills/expand.ts"() {
30877
31278
  "use strict";
30878
- import_promises18 = require("node:fs/promises");
31279
+ import_promises19 = require("node:fs/promises");
30879
31280
  init_frontmatter2();
30880
31281
  init_index_prompt();
30881
31282
  }
@@ -30901,8 +31302,8 @@ async function discoverPromptTemplates(sources, options) {
30901
31302
  for (const source of sources) {
30902
31303
  let names;
30903
31304
  try {
30904
- if (!(await (0, import_promises19.stat)(source.dir)).isDirectory()) continue;
30905
- names = await (0, import_promises19.readdir)(source.dir);
31305
+ if (!(await (0, import_promises20.stat)(source.dir)).isDirectory()) continue;
31306
+ names = await (0, import_promises20.readdir)(source.dir);
30906
31307
  } catch {
30907
31308
  continue;
30908
31309
  }
@@ -30924,8 +31325,8 @@ async function discoverPromptTemplates(sources, options) {
30924
31325
  }
30925
31326
  let text;
30926
31327
  try {
30927
- if (!(await (0, import_promises19.stat)(path)).isFile()) continue;
30928
- text = await (0, import_promises19.readFile)(path, "utf8");
31328
+ if (!(await (0, import_promises20.stat)(path)).isFile()) continue;
31329
+ text = await (0, import_promises20.readFile)(path, "utf8");
30929
31330
  } catch {
30930
31331
  continue;
30931
31332
  }
@@ -31012,7 +31413,7 @@ function parsePromptCommand(text) {
31012
31413
  const rawArgs = (m[2] ?? "").trim();
31013
31414
  return { name: m[1], args: parseCommandArgs(rawArgs), rawArgs };
31014
31415
  }
31015
- async function expandPromptCommand(text, templates, read2 = (p) => (0, import_promises19.readFile)(p, "utf8")) {
31416
+ async function expandPromptCommand(text, templates, read2 = (p) => (0, import_promises20.readFile)(p, "utf8")) {
31016
31417
  const cmd = parsePromptCommand(text);
31017
31418
  if (!cmd) return void 0;
31018
31419
  const template2 = templates.find((t) => t.name === cmd.name);
@@ -31020,11 +31421,11 @@ async function expandPromptCommand(text, templates, read2 = (p) => (0, import_pr
31020
31421
  const body = parseFrontmatter(await read2(template2.path)).body.replace(/^\n+/, "");
31021
31422
  return { text: expandTemplate(body, cmd.args), template: template2 };
31022
31423
  }
31023
- var import_promises19, import_node_path53, TEMPLATE_NAME_RE, PLACEHOLDER;
31424
+ var import_promises20, import_node_path53, TEMPLATE_NAME_RE, PLACEHOLDER;
31024
31425
  var init_templates = __esm({
31025
31426
  "src/skills/templates.ts"() {
31026
31427
  "use strict";
31027
- import_promises19 = require("node:fs/promises");
31428
+ import_promises20 = require("node:fs/promises");
31028
31429
  import_node_path53 = require("node:path");
31029
31430
  init_frontmatter2();
31030
31431
  TEMPLATE_NAME_RE = /^[a-z0-9][a-z0-9_-]*$/i;
@@ -31087,7 +31488,9 @@ function openSession(request, context) {
31087
31488
  return file === void 0 ? SessionManager.create(dir, cwd, { id: request.id }) : SessionManager.open(file);
31088
31489
  }
31089
31490
  case "fork": {
31090
- const source = SessionManager.open(requireFile(root, request.id, cwd));
31491
+ const source = SessionManager.open(requireFile(root, request.id, cwd), {
31492
+ warn: (message) => context.log?.("warn", message)
31493
+ });
31091
31494
  try {
31092
31495
  const leaf = source.leafId();
31093
31496
  if (leaf === null)
@@ -31103,9 +31506,9 @@ function listSessions(context) {
31103
31506
  const dirs = context.cwd === void 0 ? cwdDirs(context.sessionDir) : [sessionDirForCwd(context.sessionDir, context.cwd)];
31104
31507
  return dirs.flatMap((dir) => SessionManager.list(dir)).filter((item) => context.includeSubagents === true || item.subagent !== true).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt));
31105
31508
  }
31106
- function createSessionStore() {
31509
+ function createSessionStore(log) {
31107
31510
  return {
31108
- open: (request, context) => openSession(request, context),
31511
+ open: (request, context) => openSession(request, { ...context, log }),
31109
31512
  list: async (context) => listSessions(context),
31110
31513
  show: async (id, context) => {
31111
31514
  const file = requireFile(context.sessionDir, id);
@@ -31256,8 +31659,8 @@ function candidates(items) {
31256
31659
  }
31257
31660
  return out;
31258
31661
  }
31259
- function budgetReplacement(content, placeholder = IMAGE_OMITTED_FOR_BUDGET) {
31260
- return content.map((block) => block.type === "text" ? block.text : placeholder).filter((text) => text !== "").join("\n");
31662
+ function budgetReplacement(content, placeholder2 = IMAGE_OMITTED_FOR_BUDGET) {
31663
+ return content.map((block) => block.type === "text" ? block.text : placeholder2).filter((text) => text !== "").join("\n");
31261
31664
  }
31262
31665
  function base64ImageSize(block) {
31263
31666
  const cached = sizeCache.get(block);
@@ -31272,10 +31675,10 @@ function planImageBudget(items, budgetBytes, options = {}) {
31272
31675
  const all = candidates(items);
31273
31676
  const plan2 = [];
31274
31677
  const drop = (c, cause) => {
31275
- const placeholder = cause === "per_image" ? IMAGE_OMITTED_TOO_LARGE : IMAGE_OMITTED_FOR_BUDGET;
31678
+ const placeholder2 = cause === "per_image" ? IMAGE_OMITTED_TOO_LARGE : IMAGE_OMITTED_FOR_BUDGET;
31276
31679
  plan2.push({
31277
31680
  targetId: c.targetId,
31278
- replacement: budgetReplacement(c.content, placeholder),
31681
+ replacement: budgetReplacement(c.content, placeholder2),
31279
31682
  cause,
31280
31683
  images: c.images.length,
31281
31684
  bytes: c.bytes
@@ -31809,7 +32212,7 @@ var init_done_markers = __esm({
31809
32212
  function extractProposedPlan(text) {
31810
32213
  const lines2 = text.split(/\r?\n/);
31811
32214
  let fence2;
31812
- let open2;
32215
+ let open3;
31813
32216
  let last;
31814
32217
  for (const [i, line2] of lines2.entries()) {
31815
32218
  const marker = FENCE.exec(line2)?.[1];
@@ -31820,10 +32223,10 @@ function extractProposedPlan(text) {
31820
32223
  }
31821
32224
  if (fence2 !== void 0) continue;
31822
32225
  const trimmed = line2.trim();
31823
- if (trimmed === PLAN_OPEN_TAG) open2 = i;
31824
- else if (trimmed === PLAN_CLOSE_TAG && open2 !== void 0) {
31825
- last = { from: open2 + 1, to: i };
31826
- open2 = void 0;
32226
+ if (trimmed === PLAN_OPEN_TAG) open3 = i;
32227
+ else if (trimmed === PLAN_CLOSE_TAG && open3 !== void 0) {
32228
+ last = { from: open3 + 1, to: i };
32229
+ open3 = void 0;
31827
32230
  }
31828
32231
  }
31829
32232
  if (last === void 0) return void 0;
@@ -37196,7 +37599,7 @@ function permissionModes(currentModeId) {
37196
37599
  }))
37197
37600
  };
37198
37601
  }
37199
- function contentOf(content) {
37602
+ function contentOf2(content) {
37200
37603
  if (typeof content === "string") return content === "" ? [] : [{ type: "text", text: content }];
37201
37604
  const out = [];
37202
37605
  for (const block of content) {
@@ -37418,7 +37821,7 @@ var init_acp_events = __esm({
37418
37821
  for (const message of messages) {
37419
37822
  if (!("role" in message)) continue;
37420
37823
  if (message.role === "user") {
37421
- for (const block of contentOf(message.content))
37824
+ for (const block of contentOf2(message.content))
37422
37825
  this.emit({ sessionUpdate: "user_message_chunk", content: block });
37423
37826
  } else if (message.role === "assistant") {
37424
37827
  for (const block of message.content) {
@@ -37825,10 +38228,11 @@ function pageSessions(items, cursor, pageSize = LIST_PAGE_SIZE) {
37825
38228
  const last = all[start + pageSize - 1];
37826
38229
  return start + pageSize < all.length && last !== void 0 ? { sessions, nextCursor: encodeCursor(last.updatedAt, last.sessionId) } : { sessions };
37827
38230
  }
37828
- async function promptWhenIdle(session, job) {
38231
+ async function promptWhenIdle(session, job, onStart) {
37829
38232
  for (; ; ) {
37830
38233
  await session.waitForIdle();
37831
38234
  if (job.cancelRequested) return;
38235
+ onStart?.();
37832
38236
  try {
37833
38237
  await session.prompt(job.text, job.images.length > 0 ? { images: job.images } : {});
37834
38238
  return;
@@ -37837,10 +38241,36 @@ async function promptWhenIdle(session, job) {
37837
38241
  }
37838
38242
  }
37839
38243
  }
37840
- var LIST_PAGE_SIZE, TITLE_LIMIT, ZERO_USAGE2, PromptQueue;
38244
+ async function idleNow(session) {
38245
+ return await Promise.race([session.waitForIdle(), Promise.resolve(BUSY)]) !== BUSY;
38246
+ }
38247
+ async function quiesce(session, control, rounds = 50) {
38248
+ for (const task of control?.list() ?? [])
38249
+ if (task.status === "running") await control?.stop(task.taskId).catch(() => void 0);
38250
+ for (let i = 0; i < rounds; i++) {
38251
+ await session.abort();
38252
+ await new Promise((resolve41) => setImmediate(resolve41));
38253
+ if (await idleNow(session)) return true;
38254
+ }
38255
+ return false;
38256
+ }
38257
+ async function disposeQuiet(runtime, session, fallback, control, retries = 3) {
38258
+ for (let i = 0; ; i++) {
38259
+ try {
38260
+ await disposeSessionAlongside(runtime, session, fallback);
38261
+ break;
38262
+ } catch (error2) {
38263
+ if (i >= retries || !(error2 instanceof AmaError && error2.code === "busy")) throw error2;
38264
+ await quiesce(session, control);
38265
+ }
38266
+ }
38267
+ await session.abort();
38268
+ }
38269
+ var LIST_PAGE_SIZE, TITLE_LIMIT, ZERO_USAGE2, PromptQueue, BUSY;
37841
38270
  var init_acp_sessions = __esm({
37842
38271
  "src/modes/acp/acp-sessions.ts"() {
37843
38272
  "use strict";
38273
+ init_compose_session();
37844
38274
  init_types7();
37845
38275
  init_jsonrpc();
37846
38276
  init_errors();
@@ -37908,6 +38338,7 @@ var init_acp_sessions = __esm({
37908
38338
  });
37909
38339
  }
37910
38340
  };
38341
+ BUSY = /* @__PURE__ */ Symbol("busy");
37911
38342
  }
37912
38343
  });
37913
38344
 
@@ -37955,6 +38386,7 @@ var init_acp_server = __esm({
37955
38386
  init_compose_session();
37956
38387
  init_compose_store();
37957
38388
  init_session2();
38389
+ init_task_control();
37958
38390
  init_jsonrpc();
37959
38391
  init_types7();
37960
38392
  init_modes();
@@ -38052,13 +38484,14 @@ var init_acp_server = __esm({
38052
38484
  if (abort)
38053
38485
  await this.session().abort().catch(() => void 0);
38054
38486
  await this.queue.settled();
38055
- for (const session of this.sessions()) await session.waitForIdle().catch(() => void 0);
38487
+ for (const s2 of this.sessions())
38488
+ if (s2 instanceof AgentSessionImpl) await quiesce(s2, taskControl(s2.state.sessionId));
38056
38489
  const foreground = this.session();
38057
38490
  for (const entry of this.pool.values()) entry.unsubscribe();
38058
38491
  for (const session of this.sessions()) {
38059
38492
  if (session === foreground || !(session instanceof AgentSessionImpl)) continue;
38060
38493
  if (!(foreground instanceof AgentSessionImpl)) continue;
38061
- await disposeSessionAlongside(this.runtime, session, foreground).catch(() => void 0);
38494
+ await disposeQuiet(this.runtime, session, foreground).catch(() => void 0);
38062
38495
  }
38063
38496
  this.pool.clear();
38064
38497
  this.standby = void 0;
@@ -38194,12 +38627,9 @@ var init_acp_server = __esm({
38194
38627
  if (entry === void 0) return {};
38195
38628
  this.pool.delete(id);
38196
38629
  this.queue.cancelQueued(id);
38197
- const running = this.queue.running;
38198
- if (running?.sessionId === id) {
38199
- running.cancelRequested = true;
38200
- await entry.session.abort().catch(() => void 0);
38201
- await this.queue.settled(id);
38202
- }
38630
+ if (this.queue.running?.sessionId === id) this.queue.running.cancelRequested = true;
38631
+ await quiesce(entry.session, taskControl(id));
38632
+ await this.queue.settled(id);
38203
38633
  entry.unsubscribe();
38204
38634
  let fallback = this.session();
38205
38635
  if (fallback === entry.session) {
@@ -38211,7 +38641,7 @@ var init_acp_server = __esm({
38211
38641
  }
38212
38642
  }
38213
38643
  if (fallback instanceof AgentSessionImpl)
38214
- await disposeSessionAlongside(this.runtime, entry.session, fallback);
38644
+ await disposeQuiet(this.runtime, entry.session, fallback, taskControl(id));
38215
38645
  return {};
38216
38646
  }
38217
38647
  async prompt(params, signal) {
@@ -38250,15 +38680,15 @@ var init_acp_server = __esm({
38250
38680
  const { session } = entry;
38251
38681
  setForegroundSession(this.runtime, session);
38252
38682
  if (this.runtime.permission.mode !== entry.mode) session.setPermissionMode(entry.mode);
38253
- const before2 = session.getStats().tokens;
38683
+ let before2 = session.getStats().tokens;
38254
38684
  let failure2;
38255
38685
  try {
38256
- await promptWhenIdle(session, job);
38686
+ await promptWhenIdle(session, job, () => void (before2 = session.getStats().tokens));
38257
38687
  } catch (error2) {
38258
38688
  failure2 = error2;
38259
38689
  }
38260
- await this.peer.flush();
38261
38690
  const after = session.getStats().tokens;
38691
+ await this.peer.flush();
38262
38692
  const usage = {
38263
38693
  inputTokens: after.input - before2.input,
38264
38694
  outputTokens: after.output - before2.output,
@@ -38668,7 +39098,7 @@ async function bootstrap(argsIn, deps, io) {
38668
39098
  request = { kind: "resume", id };
38669
39099
  }
38670
39100
  const sessionRequest = request;
38671
- const sessionManager = await step(
39101
+ let sessionManager = await step(
38672
39102
  ExitCode.Session,
38673
39103
  m().steps.session,
38674
39104
  () => deps.sessions.open(sessionRequest, { sessionDir: paths.sessionDir, cwd: paths.cwd })
@@ -38893,6 +39323,8 @@ async function bootstrap(argsIn, deps, io) {
38893
39323
  uiBroker: () => uiBroker,
38894
39324
  onSessionReplaced: (next) => {
38895
39325
  session = next;
39326
+ sessionManager = next.manager ?? sessionManager;
39327
+ assembly.sessionManager = sessionManager;
38896
39328
  },
38897
39329
  sessionStartContext: () => sessionStartContext,
38898
39330
  unattended,
@@ -38922,7 +39354,7 @@ async function bootstrap(argsIn, deps, io) {
38922
39354
  await events.emit("session_shutdown", {});
38923
39355
  await hooks.run("SessionEnd", { reason }).catch(() => void 0);
38924
39356
  await disposeHost(host, (e) => warn2(m().hostDisposeFailed(String(e))));
38925
- await (session ?? active).dispose();
39357
+ await session?.dispose();
38926
39358
  })();
38927
39359
  return disposed2;
38928
39360
  };
@@ -38955,8 +39387,13 @@ async function bootstrap(argsIn, deps, io) {
38955
39387
  providers,
38956
39388
  model,
38957
39389
  thinkingLevel,
38958
- sessionManager,
38959
- session: active,
39390
+ // [#165] 跟随当前会话(ACP 关掉启动会话后它可被回收)
39391
+ get sessionManager() {
39392
+ return sessionManager;
39393
+ },
39394
+ get session() {
39395
+ return session;
39396
+ },
38960
39397
  hooks,
38961
39398
  host,
38962
39399
  permission,
@@ -43445,18 +43882,18 @@ async function confirmContinue(options = {}) {
43445
43882
  });
43446
43883
  return index2 === 0;
43447
43884
  }
43448
- const rl = (0, import_promises20.createInterface)({ input: input2, output: options.output ?? process.stderr });
43885
+ const rl = (0, import_promises21.createInterface)({ input: input2, output: options.output ?? process.stderr });
43449
43886
  try {
43450
43887
  return /^y(es)?$/i.test((await rl.question(m.textQuestion(question))).trim());
43451
43888
  } finally {
43452
43889
  rl.close();
43453
43890
  }
43454
43891
  }
43455
- var import_promises20, HIDE_CURSOR2, SHOW_CURSOR2;
43892
+ var import_promises21, HIDE_CURSOR2, SHOW_CURSOR2;
43456
43893
  var init_choice_prompt = __esm({
43457
43894
  "src/cli/choice-prompt.ts"() {
43458
43895
  "use strict";
43459
- import_promises20 = require("node:readline/promises");
43896
+ import_promises21 = require("node:readline/promises");
43460
43897
  init_tui2();
43461
43898
  init_i18n();
43462
43899
  HIDE_CURSOR2 = "\x1B[?25l";
@@ -43644,7 +44081,8 @@ var init_key_docs = __esm({
43644
44081
  maxPending: 16,
43645
44082
  background: "auto",
43646
44083
  autoBackgroundAfterMs: 0,
43647
- retainSessions: 4
44084
+ retainSessions: 4,
44085
+ forkMaxContextRatio: 0.5
43648
44086
  },
43649
44087
  reminders: { todo: true, fileChanges: true, contextPressure: true, budget: true },
43650
44088
  todo: { reminder: 10 }
@@ -43909,7 +44347,8 @@ function w5Sections() {
43909
44347
  defaultModel: str3(),
43910
44348
  background: oneOf(SUBAGENT_BACKGROUND_MODES),
43911
44349
  autoBackgroundAfterMs: num2(0),
43912
- retainSessions: num2(0)
44350
+ retainSessions: num2(0),
44351
+ forkMaxContextRatio: num2(0.05, 0.95)
43913
44352
  }),
43914
44353
  models: object({ aliases: object({ fast: str3(), strong: str3() }), enabled: strings2 }),
43915
44354
  fallbackModel: str3(),
@@ -44312,7 +44751,7 @@ ${error2.message}`)?.[0].toLowerCase();
44312
44751
  }
44313
44752
  function stripInto(value, removed) {
44314
44753
  if (Array.isArray(value)) return value.map((item) => stripInto(item, removed));
44315
- if (typeof value !== "object" || value === null) return value;
44754
+ if (typeof value !== "object" || value === null || value instanceof LargeString) return value;
44316
44755
  const out = {};
44317
44756
  for (const [key, inner] of Object.entries(value)) {
44318
44757
  if (key === "cache_control") removed.count++;
@@ -44351,6 +44790,7 @@ var init_cache_params = __esm({
44351
44790
  "src/ai/apis/cache-params.ts"() {
44352
44791
  "use strict";
44353
44792
  init_http();
44793
+ init_json_body();
44354
44794
  init_i18n();
44355
44795
  DEFAULT_BASE_URLS = {
44356
44796
  "anthropic-messages": "https://api.anthropic.com",
@@ -44433,7 +44873,11 @@ function convertBlocks(content) {
44433
44873
  } else {
44434
44874
  blocks.push({
44435
44875
  type: "image",
44436
- source: { type: "base64", media_type: block.mimeType, data: block.data }
44876
+ source: {
44877
+ type: "base64",
44878
+ media_type: block.mimeType,
44879
+ data: largeString("", block, block.data)
44880
+ }
44437
44881
  });
44438
44882
  }
44439
44883
  }
@@ -44607,6 +45051,7 @@ var init_anthropic_request = __esm({
44607
45051
  "src/ai/apis/anthropic-request.ts"() {
44608
45052
  "use strict";
44609
45053
  init_context();
45054
+ init_json_body();
44610
45055
  init_thinking();
44611
45056
  init_anthropic_compat();
44612
45057
  init_cache_params();
@@ -44656,7 +45101,7 @@ function validSignature(signature2) {
44656
45101
  return signature2 !== void 0 && signature2.length > 0 && signature2.length % 4 === 0 && BASE64.test(signature2);
44657
45102
  }
44658
45103
  function inlineData(block) {
44659
- return { inlineData: { mimeType: block.mimeType, data: block.data } };
45104
+ return { inlineData: { mimeType: block.mimeType, data: largeString("", block, block.data) } };
44660
45105
  }
44661
45106
  function convertUser2(message) {
44662
45107
  if (typeof message.content === "string") {
@@ -44827,6 +45272,7 @@ var init_google_request = __esm({
44827
45272
  "src/ai/apis/google-request.ts"() {
44828
45273
  "use strict";
44829
45274
  init_context();
45275
+ init_json_body();
44830
45276
  init_thinking();
44831
45277
  GOOGLE_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
44832
45278
  GOOGLE_COMPAT_KEYS = [
@@ -45274,7 +45720,8 @@ var init_chatgpt_backend = __esm({
45274
45720
 
45275
45721
  // src/ai/apis/openai-request.ts
45276
45722
  function imagePart(block) {
45277
- return { type: "image_url", image_url: { url: `data:${block.mimeType};base64,${block.data}` } };
45723
+ const url = largeString(`data:${block.mimeType};base64,`, block, block.data);
45724
+ return { type: "image_url", image_url: { url } };
45278
45725
  }
45279
45726
  function convertUser3(message) {
45280
45727
  if (typeof message.content === "string") {
@@ -45325,17 +45772,17 @@ function convertToolResults(model, results, compat) {
45325
45772
  for (const result of results) {
45326
45773
  const text = contentText2(result.content);
45327
45774
  const blocks = typeof result.content === "string" ? [] : result.content;
45328
- const hasImages = blocks.some((b) => b.type === "image");
45775
+ const hasImages2 = blocks.some((b) => b.type === "image");
45329
45776
  const message = {
45330
45777
  role: "tool",
45331
45778
  tool_call_id: normalizeCallId(model, result.toolCallId),
45332
45779
  content: sanitizeText(
45333
- text.length > 0 ? text : hasImages ? "(see attached image)" : "(no output)"
45780
+ text.length > 0 ? text : hasImages2 ? "(see attached image)" : "(no output)"
45334
45781
  )
45335
45782
  };
45336
45783
  if (compat.requiresToolResultName) message["name"] = result.toolName;
45337
45784
  messages.push(message);
45338
- if (hasImages && model.input.includes("image")) {
45785
+ if (hasImages2 && model.input.includes("image")) {
45339
45786
  for (const block of blocks) if (block.type === "image") images.push(imagePart(block));
45340
45787
  }
45341
45788
  }
@@ -45527,6 +45974,7 @@ var init_openai_request = __esm({
45527
45974
  init_thinking();
45528
45975
  init_anthropic_request();
45529
45976
  init_cache_params();
45977
+ init_json_body();
45530
45978
  init_openai_compat();
45531
45979
  REASONING_FIELDS = ["reasoning_content", "reasoning", "reasoning_text"];
45532
45980
  ASSISTANT_BRIDGE_TEXT = "Tool results received.";
@@ -45581,7 +46029,7 @@ function imagePart2(block) {
45581
46029
  return {
45582
46030
  type: "input_image",
45583
46031
  detail: "auto",
45584
- image_url: `data:${block.mimeType};base64,${block.data}`
46032
+ image_url: largeString(`data:${block.mimeType};base64,`, block, block.data)
45585
46033
  };
45586
46034
  }
45587
46035
  function convertUser4(message) {
@@ -45726,6 +46174,7 @@ var init_openai_responses_request = __esm({
45726
46174
  "src/ai/apis/openai-responses-request.ts"() {
45727
46175
  "use strict";
45728
46176
  init_context();
46177
+ init_json_body();
45729
46178
  init_thinking();
45730
46179
  init_cache_params();
45731
46180
  init_chatgpt_backend();
@@ -45886,6 +46335,10 @@ var init_sse = __esm({
45886
46335
  });
45887
46336
 
45888
46337
  // src/ai/apis/max-tokens.ts
46338
+ function onMaxTokensCap(listener) {
46339
+ capListeners.add(listener);
46340
+ return () => capListeners.delete(listener);
46341
+ }
45889
46342
  function clampMaxTokens(requested, window2, estimatedInput, fixed) {
45890
46343
  if (fixed || window2 === void 0 || !Number.isFinite(window2) || window2 <= 0) return requested;
45891
46344
  const room = Math.floor(window2 - estimatedInput - OUTPUT_HEADROOM_TOKENS);
@@ -45946,14 +46399,17 @@ async function postWithMaxTokensFallback(model, url, options, field2) {
45946
46399
  const sent = body[field2];
45947
46400
  if (rejection === void 0 || !("cap" in rejection) || typeof sent !== "number") throw error2;
45948
46401
  if (rejection.cap >= sent || !budgetFits(body, rejection.cap)) throw error2;
45949
- if (!rejection.transient) maxTokensCaps.set(key, rejection.cap);
46402
+ if (!rejection.transient) {
46403
+ maxTokensCaps.set(key, rejection.cap);
46404
+ for (const listener of capListeners) listener(key, rejection.cap);
46405
+ }
45950
46406
  return postWithCacheFallback(model, url, {
45951
46407
  ...options,
45952
46408
  body: withCap(body, field2, rejection.cap)
45953
46409
  });
45954
46410
  }
45955
46411
  }
45956
- var MIN_OUTPUT_TOKENS, OUTPUT_HEADROOM_TOKENS, maxTokensCaps, NUM, toInt, RANGE, LIMIT, CONTEXT_LIMIT;
46412
+ var MIN_OUTPUT_TOKENS, OUTPUT_HEADROOM_TOKENS, maxTokensCaps, capListeners, NUM, toInt, RANGE, LIMIT, CONTEXT_LIMIT;
45957
46413
  var init_max_tokens = __esm({
45958
46414
  "src/ai/apis/max-tokens.ts"() {
45959
46415
  "use strict";
@@ -45962,6 +46418,7 @@ var init_max_tokens = __esm({
45962
46418
  MIN_OUTPUT_TOKENS = 1024;
45963
46419
  OUTPUT_HEADROOM_TOKENS = 2048;
45964
46420
  maxTokensCaps = /* @__PURE__ */ new Map();
46421
+ capListeners = /* @__PURE__ */ new Set();
45965
46422
  NUM = "`?(\\d[\\d,_]*)`?";
45966
46423
  toInt = (text) => Number((text ?? "").replace(/[,_]/g, ""));
45967
46424
  RANGE = new RegExp(
@@ -47505,9 +47962,8 @@ var init_registry2 = __esm({
47505
47962
  (name) => this.warn(`model "${id}/${modelId}" channel "${name}" not found; ignored`)
47506
47963
  );
47507
47964
  for (const entry of config.models ?? []) {
47508
- const { entry: filled, metadata } = enrichEntry(entry, this.modelsDev);
47509
- if (typeof entry.modelsDev === "string" && metadata.looked && metadata.match === void 0)
47510
- this.warn(`modelsDev "${entry.modelsDev}" for "${id}/${entry.id}" not found`);
47965
+ const { entry: filled, metadata } = enrichEntry(entry, this.modelsDev, id);
47966
+ for (const warning of metadata.warnings ?? []) this.warn(warning);
47511
47967
  const model = withCustomDefaults(filled, id, entry.api ?? provider.api);
47512
47968
  if (provider.channels !== void 0 && (entry.channels !== void 0 || !provider.builtin))
47513
47969
  model.channels = channelsOf(entry.channels, entry.id);
@@ -47647,8 +48103,8 @@ var init_registry2 = __esm({
47647
48103
  const id = trimmed.slice(slash + 1);
47648
48104
  const model = provider.models.find((m) => m.id === id);
47649
48105
  if (model) return { ok: true, model, provider };
47650
- const open2 = this.relayed.has(provider.id) || this.openTables.has(provider.id);
47651
- const relayed = provider.models.length === 0 || open2;
48106
+ const open3 = this.relayed.has(provider.id) || this.openTables.has(provider.id);
48107
+ const relayed = provider.models.length === 0 || open3;
47652
48108
  if (synthesize && relayed && id.length > 0) {
47653
48109
  const known = provider.channels?.some((c) => c.name === channel) ? channel : void 0;
47654
48110
  const synthesized = this.synthesize(provider, id, known);
@@ -47860,6 +48316,7 @@ var init_settings_registry = __esm({
47860
48316
  row2("subagents.maxConcurrent", "agents", "nextSession", "deny"),
47861
48317
  row2("subagents.maxPending", "agents", "nextSession", "deny"),
47862
48318
  row2("subagents.retainSessions", "agents", "nextSession", "deny"),
48319
+ row2("subagents.forkMaxContextRatio", "agents", "nextSession", "deny"),
47863
48320
  // [W7-B2] 改 task 工具描述(缺省后台 / 前台两种文案),故 prefix
47864
48321
  row2("subagents.background", "agents", "restart", "any", { prefix: true }),
47865
48322
  row2("subagents.autoBackgroundAfterMs", "agents", "restart", "any"),
@@ -48253,7 +48710,7 @@ function attempts(platform, file, env) {
48253
48710
  }
48254
48711
  async function isPng(file) {
48255
48712
  try {
48256
- return sniffImageMime(await (0, import_promises21.readFile)(file)) === "image/png";
48713
+ return sniffImageMime(await (0, import_promises22.readFile)(file)) === "image/png";
48257
48714
  } catch {
48258
48715
  return false;
48259
48716
  }
@@ -48263,7 +48720,7 @@ async function readClipboardImage(dataDir, deps = {}) {
48263
48720
  const env = deps.env ?? process.env;
48264
48721
  const run7 = deps.run ?? runCommand;
48265
48722
  const dir = (0, import_node_path78.join)(dataDir, CLIPBOARD_DIR);
48266
- await (0, import_promises21.mkdir)(dir, { recursive: true });
48723
+ await (0, import_promises22.mkdir)(dir, { recursive: true });
48267
48724
  const stamp = new Date((deps.now ?? Date.now)()).toISOString().replace(/[:.]/g, "-");
48268
48725
  let file = (0, import_node_path78.join)(dir, `${stamp}.png`);
48269
48726
  for (let n = 1; (0, import_node_fs60.existsSync)(file); n++) file = (0, import_node_path78.join)(dir, `${stamp}-${n}.png`);
@@ -48276,19 +48733,19 @@ async function readClipboardImage(dataDir, deps = {}) {
48276
48733
  if (result.code !== 0) continue;
48277
48734
  if (attempt.via === "stdout") {
48278
48735
  if (sniffImageMime(result.stdout) !== "image/png") continue;
48279
- await (0, import_promises21.writeFile)(file, result.stdout);
48736
+ await (0, import_promises22.writeFile)(file, result.stdout);
48280
48737
  }
48281
48738
  if (await isPng(file)) return { ok: true, path: file, tool: attempt.tool };
48282
- await (0, import_promises21.rm)(file, { force: true });
48739
+ await (0, import_promises22.rm)(file, { force: true });
48283
48740
  }
48284
- await (0, import_promises21.rm)(file, { force: true });
48741
+ await (0, import_promises22.rm)(file, { force: true });
48285
48742
  return { ok: false, reason: anyTool ? "no_image" : "no_tool" };
48286
48743
  }
48287
48744
  async function gcClipboardImages(dataDir, options = {}) {
48288
48745
  const dir = (0, import_node_path78.join)(dataDir, CLIPBOARD_DIR);
48289
48746
  let names;
48290
48747
  try {
48291
- names = await (0, import_promises21.readdir)(dir);
48748
+ names = await (0, import_promises22.readdir)(dir);
48292
48749
  } catch {
48293
48750
  return [];
48294
48751
  }
@@ -48297,21 +48754,21 @@ async function gcClipboardImages(dataDir, options = {}) {
48297
48754
  for (const name of names) {
48298
48755
  const file = (0, import_node_path78.join)(dir, name);
48299
48756
  try {
48300
- const info2 = await (0, import_promises21.stat)(file);
48757
+ const info2 = await (0, import_promises22.stat)(file);
48301
48758
  if (!info2.isFile() || info2.mtimeMs >= cutoff) continue;
48302
- if (options.dryRun !== true) await (0, import_promises21.rm)(file, { force: true });
48759
+ if (options.dryRun !== true) await (0, import_promises22.rm)(file, { force: true });
48303
48760
  removed.push(file);
48304
48761
  } catch {
48305
48762
  }
48306
48763
  }
48307
48764
  return removed;
48308
48765
  }
48309
- var import_node_fs60, import_promises21, import_node_path78, CLIPBOARD_DIR, CLIPBOARD_MAX_AGE_DAYS, OSASCRIPT, POWERSHELL;
48766
+ var import_node_fs60, import_promises22, import_node_path78, CLIPBOARD_DIR, CLIPBOARD_MAX_AGE_DAYS, OSASCRIPT, POWERSHELL;
48310
48767
  var init_clipboard_image = __esm({
48311
48768
  "src/tools/clipboard-image.ts"() {
48312
48769
  "use strict";
48313
48770
  import_node_fs60 = require("node:fs");
48314
- import_promises21 = require("node:fs/promises");
48771
+ import_promises22 = require("node:fs/promises");
48315
48772
  import_node_path78 = require("node:path");
48316
48773
  init_image_file();
48317
48774
  init_image_resize();
@@ -49699,7 +50156,7 @@ function statusCost(stats) {
49699
50156
  function layoutRow(parts, width2, theme, style = {}) {
49700
50157
  const sep5 = style.sep ?? " · ";
49701
50158
  const dim = (text) => text === "" ? "" : theme.fg("dim", text);
49702
- const join66 = (list5) => {
50159
+ const join67 = (list5) => {
49703
50160
  let out = "";
49704
50161
  let size = 0;
49705
50162
  const add3 = (text, measured = visibleWidth(text)) => {
@@ -49731,7 +50188,7 @@ function layoutRow(parts, width2, theme, style = {}) {
49731
50188
  const left = zone("left");
49732
50189
  const right = zone("right");
49733
50190
  const between = left.length === 0 || right.length === 0 ? 0 : sep5.length;
49734
- return join66(left)[1] + between + join66(right)[1] <= width2;
50191
+ return join67(left)[1] + between + join67(right)[1] <= width2;
49735
50192
  };
49736
50193
  while (!fits()) {
49737
50194
  const droppable = kept.filter((p) => p.priority !== void 0);
@@ -49739,8 +50196,8 @@ function layoutRow(parts, width2, theme, style = {}) {
49739
50196
  const drop = droppable.reduce((worst, p) => p.priority > worst.priority ? p : worst);
49740
50197
  kept = kept.filter((p) => p !== drop);
49741
50198
  }
49742
- const l = join66(zone("left"))[0];
49743
- const r = join66(zone("right"))[0];
50199
+ const l = join67(zone("left"))[0];
50200
+ const r = join67(zone("right"))[0];
49744
50201
  const gap = width2 - visibleWidth(l) - visibleWidth(r);
49745
50202
  const line2 = r === "" ? l : l === "" ? " ".repeat(Math.max(0, gap)) + r : gap >= MIN_GAP ? l + " ".repeat(gap) + r : l + dim(sep5) + r;
49746
50203
  return truncateToWidth(line2, width2);
@@ -50456,8 +50913,8 @@ function barText(row3, width2, theme) {
50456
50913
  const color = colors[rank3];
50457
50914
  body += color === void 0 || plain ? ch2 : theme.fg(color, ch2);
50458
50915
  }
50459
- const [open2, close] = plain ? ["[", "]"] : ["▕", "▏"];
50460
- return theme.fg("border", open2) + body + theme.fg("border", close);
50916
+ const [open3, close] = plain ? ["[", "]"] : ["▕", "▏"];
50917
+ return theme.fg("border", open3) + body + theme.fg("border", close);
50461
50918
  }
50462
50919
  function labelPart(row3, ctx) {
50463
50920
  const g = ctx.theme.glyphs;
@@ -51115,11 +51572,11 @@ ${skill.body}`;
51115
51572
  }
51116
51573
  async function ensureFile(path, text) {
51117
51574
  try {
51118
- if (await (0, import_promises22.readFile)(path, "utf8") === text) return;
51575
+ if (await (0, import_promises23.readFile)(path, "utf8") === text) return;
51119
51576
  } catch {
51120
51577
  }
51121
- await (0, import_promises22.mkdir)((0, import_node_path86.dirname)(path), { recursive: true });
51122
- await (0, import_promises22.writeFile)(path, text, "utf8");
51578
+ await (0, import_promises23.mkdir)((0, import_node_path86.dirname)(path), { recursive: true });
51579
+ await (0, import_promises23.writeFile)(path, text, "utf8");
51123
51580
  }
51124
51581
  async function withBuiltinSkills(skills, dataDir, builtins = BUILTIN_SKILLS) {
51125
51582
  const out = [...skills];
@@ -51148,11 +51605,11 @@ async function withBuiltinSkills(skills, dataDir, builtins = BUILTIN_SKILLS) {
51148
51605
  }
51149
51606
  return { skills: out, warnings };
51150
51607
  }
51151
- var import_promises22, import_node_path86, BUILTIN_SKILLS_DIR, AMA_DOCS_BODY, BUILTIN_SKILLS;
51608
+ var import_promises23, import_node_path86, BUILTIN_SKILLS_DIR, AMA_DOCS_BODY, BUILTIN_SKILLS;
51152
51609
  var init_builtin3 = __esm({
51153
51610
  "src/skills/builtin.ts"() {
51154
51611
  "use strict";
51155
- import_promises22 = require("node:fs/promises");
51612
+ import_promises23 = require("node:fs/promises");
51156
51613
  import_node_path86 = require("node:path");
51157
51614
  BUILTIN_SKILLS_DIR = "builtin";
51158
51615
  AMA_DOCS_BODY = `# ama quick reference
@@ -51263,7 +51720,7 @@ function parseSkill(text, location, scope) {
51263
51720
  }
51264
51721
  async function isDir3(path) {
51265
51722
  try {
51266
- return (await (0, import_promises23.stat)(path)).isDirectory();
51723
+ return (await (0, import_promises24.stat)(path)).isDirectory();
51267
51724
  } catch {
51268
51725
  return false;
51269
51726
  }
@@ -51274,7 +51731,7 @@ async function findSkillFiles(dir, depth = 0) {
51274
51731
  if ((0, import_node_fs65.existsSync)(own)) return [own];
51275
51732
  let names;
51276
51733
  try {
51277
- names = await (0, import_promises23.readdir)(dir);
51734
+ names = await (0, import_promises24.readdir)(dir);
51278
51735
  } catch {
51279
51736
  return [];
51280
51737
  }
@@ -51302,7 +51759,7 @@ async function discoverSkills(sources, options) {
51302
51759
  for (const file of await findSkillFiles(source.dir)) {
51303
51760
  let text;
51304
51761
  try {
51305
- text = await (0, import_promises23.readFile)(file, "utf8");
51762
+ text = await (0, import_promises24.readFile)(file, "utf8");
51306
51763
  } catch (err) {
51307
51764
  result.warnings.push(`${file}: ${err.message}`);
51308
51765
  continue;
@@ -51323,12 +51780,12 @@ async function discoverSkills(sources, options) {
51323
51780
  }
51324
51781
  return result;
51325
51782
  }
51326
- var import_node_fs65, import_promises23, import_node_path87, SKILL_FILE, SKILL_NAME_RE, MAX_SKILL_NAME, MAX_SKILL_DESCRIPTION, MAX_DEPTH3, SKIP_DIRS;
51783
+ var import_node_fs65, import_promises24, import_node_path87, SKILL_FILE, SKILL_NAME_RE, MAX_SKILL_NAME, MAX_SKILL_DESCRIPTION, MAX_DEPTH3, SKIP_DIRS;
51327
51784
  var init_discover2 = __esm({
51328
51785
  "src/skills/discover.ts"() {
51329
51786
  "use strict";
51330
51787
  import_node_fs65 = require("node:fs");
51331
- import_promises23 = require("node:fs/promises");
51788
+ import_promises24 = require("node:fs/promises");
51332
51789
  import_node_path87 = require("node:path");
51333
51790
  init_frontmatter2();
51334
51791
  SKILL_FILE = "SKILL.md";
@@ -52172,6 +52629,81 @@ var init_codemode_notice = __esm({
52172
52629
  }
52173
52630
  });
52174
52631
 
52632
+ // src/ai/providers/max-tokens-cache.ts
52633
+ function maxTokensCachePath(dataDir) {
52634
+ return (0, import_node_path91.join)(dataDir, "models", "max-tokens-caps.json");
52635
+ }
52636
+ function readEntries(dataDir, now) {
52637
+ const out = /* @__PURE__ */ new Map();
52638
+ let parsed;
52639
+ try {
52640
+ parsed = JSON.parse((0, import_node_fs69.readFileSync)(maxTokensCachePath(dataDir), "utf8"));
52641
+ } catch {
52642
+ return out;
52643
+ }
52644
+ if (typeof parsed !== "object" || parsed === null) return out;
52645
+ const file = parsed;
52646
+ if (file.version !== MAX_TOKENS_CACHE_VERSION) return out;
52647
+ if (typeof file.caps !== "object" || file.caps === null) return out;
52648
+ for (const [key, value] of Object.entries(file.caps)) {
52649
+ if (typeof value !== "object" || value === null) continue;
52650
+ const { cap, learnedAt } = value;
52651
+ if (typeof cap !== "number" || !Number.isInteger(cap) || cap < 1) continue;
52652
+ if (typeof learnedAt !== "number" || !Number.isFinite(learnedAt)) continue;
52653
+ if (now - learnedAt > MAX_TOKENS_CACHE_TTL_MS) continue;
52654
+ out.set(key, { cap, learnedAt });
52655
+ }
52656
+ return out;
52657
+ }
52658
+ function loadMaxTokensCaps(dataDir, now = Date.now) {
52659
+ let loaded = 0;
52660
+ for (const [key, entry] of readEntries(dataDir, now())) {
52661
+ if (maxTokensCaps.has(key)) continue;
52662
+ maxTokensCaps.set(key, entry.cap);
52663
+ loaded++;
52664
+ }
52665
+ return loaded;
52666
+ }
52667
+ function recordMaxTokensCap(dataDir, key, cap, now = Date.now) {
52668
+ const at = now();
52669
+ const entries = readEntries(dataDir, at);
52670
+ entries.set(key, { cap, learnedAt: at });
52671
+ const path = maxTokensCachePath(dataDir);
52672
+ const tmp = `${path}.${process.pid}.tmp`;
52673
+ try {
52674
+ (0, import_node_fs69.mkdirSync)((0, import_node_path91.dirname)(path), { recursive: true, mode: 448 });
52675
+ const caps = Object.fromEntries([...entries].sort(([a], [b]) => a < b ? -1 : 1));
52676
+ (0, import_node_fs69.writeFileSync)(tmp, `${JSON.stringify({ version: MAX_TOKENS_CACHE_VERSION, caps }, null, 2)}
52677
+ `);
52678
+ (0, import_node_fs69.renameSync)(tmp, path);
52679
+ } catch {
52680
+ }
52681
+ }
52682
+ function attachMaxTokensCache(dataDir, now = Date.now) {
52683
+ const existing = attached2.get(dataDir);
52684
+ if (existing !== void 0) return existing;
52685
+ loadMaxTokensCaps(dataDir, now);
52686
+ const unsubscribe = onMaxTokensCap((key, cap) => recordMaxTokensCap(dataDir, key, cap, now));
52687
+ const detach = () => {
52688
+ unsubscribe();
52689
+ attached2.delete(dataDir);
52690
+ };
52691
+ attached2.set(dataDir, detach);
52692
+ return detach;
52693
+ }
52694
+ var import_node_fs69, import_node_path91, MAX_TOKENS_CACHE_VERSION, MAX_TOKENS_CACHE_TTL_MS, attached2;
52695
+ var init_max_tokens_cache = __esm({
52696
+ "src/ai/providers/max-tokens-cache.ts"() {
52697
+ "use strict";
52698
+ import_node_fs69 = require("node:fs");
52699
+ import_node_path91 = require("node:path");
52700
+ init_max_tokens();
52701
+ MAX_TOKENS_CACHE_VERSION = 1;
52702
+ MAX_TOKENS_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
52703
+ attached2 = /* @__PURE__ */ new Map();
52704
+ }
52705
+ });
52706
+
52175
52707
  // src/cli/compose-providers.ts
52176
52708
  function modelConfigOf(model, providerApi) {
52177
52709
  const { provider: _p, api, ...rest } = model;
@@ -52252,6 +52784,7 @@ async function buildProviderRegistry(input2, options = {}) {
52252
52784
  if (provider !== void 0) registry3 = make({ provider, apiKey: cli.apiKey });
52253
52785
  }
52254
52786
  if (dataDir !== void 0) {
52787
+ attachMaxTokensCache(dataDir);
52255
52788
  const flavor = readOAuthEntry(input2.authFile, CHATGPT_PROVIDER_ID)?.flavor;
52256
52789
  mergeDiscoveredModels(
52257
52790
  registry3,
@@ -52292,6 +52825,7 @@ var init_compose_providers = __esm({
52292
52825
  init_models_dev_cache();
52293
52826
  init_registry2();
52294
52827
  init_discovered_cache();
52828
+ init_max_tokens_cache();
52295
52829
  init_enrich();
52296
52830
  init_token_store();
52297
52831
  init_presets();
@@ -52303,7 +52837,7 @@ var init_compose_providers = __esm({
52303
52837
  // src/modes/image-input.ts
52304
52838
  function expand(path, cwd) {
52305
52839
  const home = path === "~" || path.startsWith("~/") ? (0, import_node_os14.homedir)() + path.slice(1) : path;
52306
- return (0, import_node_path91.resolve)(cwd, home);
52840
+ return (0, import_node_path92.resolve)(cwd, home);
52307
52841
  }
52308
52842
  function findImageRefs(text, cwd) {
52309
52843
  const out = [];
@@ -52315,7 +52849,7 @@ function findImageRefs(text, cwd) {
52315
52849
  token = token.replace(/\\ /g, " ").replace(/[,\uFF0C\u3002;\uFF1B)\uFF09]+$/, "");
52316
52850
  if (token === "" || imageMimeFromPath(token) === void 0) continue;
52317
52851
  const path = expand(token, cwd);
52318
- if (!explicit && !(0, import_node_fs69.existsSync)(path)) continue;
52852
+ if (!explicit && !(0, import_node_fs70.existsSync)(path)) continue;
52319
52853
  if (seen.has(path)) continue;
52320
52854
  seen.add(path);
52321
52855
  out.push({ path, explicit });
@@ -52374,13 +52908,13 @@ async function loadLocalized(path, options) {
52374
52908
  throw error2;
52375
52909
  }
52376
52910
  }
52377
- var import_node_fs69, import_node_os14, import_node_path91, TOKEN;
52911
+ var import_node_fs70, import_node_os14, import_node_path92, TOKEN;
52378
52912
  var init_image_input = __esm({
52379
52913
  "src/modes/image-input.ts"() {
52380
52914
  "use strict";
52381
- import_node_fs69 = require("node:fs");
52915
+ import_node_fs70 = require("node:fs");
52382
52916
  import_node_os14 = require("node:os");
52383
- import_node_path91 = require("node:path");
52917
+ import_node_path92 = require("node:path");
52384
52918
  init_channels();
52385
52919
  init_errors();
52386
52920
  init_image_limits();
@@ -53177,7 +53711,7 @@ function trustChoices() {
53177
53711
  }
53178
53712
  function isDirectory2(path) {
53179
53713
  try {
53180
- return (0, import_node_fs70.statSync)(path).isDirectory();
53714
+ return (0, import_node_fs71.statSync)(path).isDirectory();
53181
53715
  } catch {
53182
53716
  return false;
53183
53717
  }
@@ -53231,8 +53765,8 @@ function createStartupUi(options = {}) {
53231
53765
  placeholder: m.cwdPlaceholder,
53232
53766
  validate: (text) => {
53233
53767
  if (text === "") return m.cwdEmpty;
53234
- const abs = (0, import_node_path92.resolve)(text);
53235
- if (!(0, import_node_fs70.existsSync)(abs)) return m.cwdMissing(abs);
53768
+ const abs = (0, import_node_path93.resolve)(text);
53769
+ if (!(0, import_node_fs71.existsSync)(abs)) return m.cwdMissing(abs);
53236
53770
  if (!isDirectory2(abs)) return m.cwdNotDir(abs);
53237
53771
  return void 0;
53238
53772
  }
@@ -53240,13 +53774,13 @@ function createStartupUi(options = {}) {
53240
53774
  }
53241
53775
  };
53242
53776
  }
53243
- var import_node_fs70, import_node_path92;
53777
+ var import_node_fs71, import_node_path93;
53244
53778
  var init_startup_ui = __esm({
53245
53779
  "src/modes/interactive/startup-ui.ts"() {
53246
53780
  "use strict";
53247
53781
  init_i18n();
53248
- import_node_fs70 = require("node:fs");
53249
- import_node_path92 = require("node:path");
53782
+ import_node_fs71 = require("node:fs");
53783
+ import_node_path93 = require("node:path");
53250
53784
  init_tui2();
53251
53785
  init_model_items();
53252
53786
  init_model_items();
@@ -53384,7 +53918,7 @@ function oneLine7(text, max = 60) {
53384
53918
  const flat2 = text.replace(/\s+/g, " ").trim();
53385
53919
  return flat2.length > max ? `${flat2.slice(0, max - 1)}…` : flat2;
53386
53920
  }
53387
- function taskLine(task, now) {
53921
+ function taskLine2(task, now) {
53388
53922
  const m = msg().panels.tasks;
53389
53923
  const agent = task.runner !== "ama" && task.runner !== task.agent ? m.agentRunner(task.agent, task.runner) : task.agent;
53390
53924
  const parts = [agent, ...taskFacts(task, now)];
@@ -53398,7 +53932,7 @@ function describeTasks(sessionId, now) {
53398
53932
  if (tasks.length === 0) return m.none;
53399
53933
  return [
53400
53934
  m.heading(tasks.length),
53401
- ...tasks.map((task) => ` ${taskLine(task, now)}`),
53935
+ ...tasks.map((task) => ` ${taskLine2(task, now)}`),
53402
53936
  m.footer
53403
53937
  ].join("\n");
53404
53938
  }
@@ -53409,7 +53943,7 @@ function describeTaskOutput(sessionId, taskId, now) {
53409
53943
  const output = taskOutput(sessionId, taskId) ?? "";
53410
53944
  const file = task.outputFile !== void 0 ? `
53411
53945
  ${m.fullOutput(task.outputFile)}` : "";
53412
- return `${taskLine(task, now)}${file}
53946
+ return `${taskLine2(task, now)}${file}
53413
53947
  ${output.trim() === "" ? m.noOutput : output.trimEnd()}`;
53414
53948
  }
53415
53949
  function sourceText3(source) {
@@ -53663,7 +54197,7 @@ function startupInfo(runtime, home) {
53663
54197
  permissionMode: runtime.session.state.permissionMode,
53664
54198
  preset: runtime.config.tools?.preset ?? "default",
53665
54199
  codemode: effectiveCodemodeMode(runtime.config),
53666
- contextFiles: resources.contextFiles.map((f) => (0, import_node_path93.basename)(f.path)),
54200
+ contextFiles: resources.contextFiles.map((f) => (0, import_node_path94.basename)(f.path)),
53667
54201
  skills: resources.skills.filter((s2) => !isBuiltinSkill(s2)).length,
53668
54202
  prompts: resources.prompts.length,
53669
54203
  hooks: runtime.hooks.list().length,
@@ -53672,12 +54206,12 @@ function startupInfo(runtime, home) {
53672
54206
  if (runtime.host !== void 0) info2.host = runtime.host.adapter.id;
53673
54207
  return info2;
53674
54208
  }
53675
- var import_node_path93;
54209
+ var import_node_path94;
53676
54210
  var init_startup_screen = __esm({
53677
54211
  "src/cli/startup-screen.ts"() {
53678
54212
  "use strict";
53679
54213
  init_builtin3();
53680
- import_node_path93 = require("node:path");
54214
+ import_node_path94 = require("node:path");
53681
54215
  init_channels();
53682
54216
  init_presets2();
53683
54217
  init_i18n();
@@ -56420,29 +56954,29 @@ function runEditor(command3, file) {
56420
56954
  return result.status;
56421
56955
  }
56422
56956
  async function editExternally(text, name, deps) {
56423
- const dir = (0, import_node_fs71.mkdtempSync)((0, import_node_path94.join)((0, import_node_os15.tmpdir)(), "ama-edit-"));
56424
- const file = (0, import_node_path94.join)(dir, name);
56425
- (0, import_node_fs71.writeFileSync)(file, text);
56957
+ const dir = (0, import_node_fs72.mkdtempSync)((0, import_node_path95.join)((0, import_node_os15.tmpdir)(), "ama-edit-"));
56958
+ const file = (0, import_node_path95.join)(dir, name);
56959
+ (0, import_node_fs72.writeFileSync)(file, text);
56426
56960
  deps.suspend();
56427
56961
  try {
56428
56962
  const code = (deps.run ?? runEditor)(editorCommand(deps.env, deps.platform), file);
56429
56963
  if (code !== 0) return void 0;
56430
- return (0, import_node_fs71.readFileSync)(file, "utf8");
56964
+ return (0, import_node_fs72.readFileSync)(file, "utf8");
56431
56965
  } catch {
56432
56966
  return void 0;
56433
56967
  } finally {
56434
56968
  deps.resume();
56435
- (0, import_node_fs71.rmSync)(dir, { recursive: true, force: true });
56969
+ (0, import_node_fs72.rmSync)(dir, { recursive: true, force: true });
56436
56970
  }
56437
56971
  }
56438
- var import_node_child_process17, import_node_fs71, import_node_os15, import_node_path94;
56972
+ var import_node_child_process17, import_node_fs72, import_node_os15, import_node_path95;
56439
56973
  var init_external_editor = __esm({
56440
56974
  "src/modes/interactive/external-editor.ts"() {
56441
56975
  "use strict";
56442
56976
  import_node_child_process17 = require("node:child_process");
56443
- import_node_fs71 = require("node:fs");
56977
+ import_node_fs72 = require("node:fs");
56444
56978
  import_node_os15 = require("node:os");
56445
- import_node_path94 = require("node:path");
56979
+ import_node_path95 = require("node:path");
56446
56980
  }
56447
56981
  });
56448
56982
 
@@ -59406,7 +59940,7 @@ function agentCommandHooks(get) {
59406
59940
  agentView: (taskId) => get().openView(taskId)
59407
59941
  };
59408
59942
  }
59409
- var TASK_NOTICE, AgentUi;
59943
+ var TASK_NOTICE, SESSION_NOTICE, AgentUi;
59410
59944
  var init_agent_ui = __esm({
59411
59945
  "src/modes/interactive/agent-ui.ts"() {
59412
59946
  "use strict";
@@ -59426,6 +59960,7 @@ var init_agent_ui = __esm({
59426
59960
  init_task_background();
59427
59961
  init_tasks_report();
59428
59962
  TASK_NOTICE = /^\[task (t\d+)\] ([\s\S]*)$/;
59963
+ SESSION_NOTICE = /^cannot read session entry (\S+) back from /;
59429
59964
  AgentUi = class {
59430
59965
  constructor(deps) {
59431
59966
  this.deps = deps;
@@ -59536,6 +60071,11 @@ var init_agent_ui = __esm({
59536
60071
  this.deps.notice(level === "warn" ? "warn" : "info", `[${agent} · ${taskId}] ${match[2]}`);
59537
60072
  return;
59538
60073
  }
60074
+ const image = level === "warn" ? SESSION_NOTICE.exec(message) : null;
60075
+ if (image !== null) {
60076
+ this.deps.notice("warn", msg().interactive.events.imageReadBackFailed(image[1]));
60077
+ return;
60078
+ }
59539
60079
  original?.call(holder, level, message);
59540
60080
  };
59541
60081
  holder.log = routed;
@@ -59760,7 +60300,7 @@ var init_agent_ui = __esm({
59760
60300
  const now = this.deps.now();
59761
60301
  const picked = await this.deps.pick({
59762
60302
  title: msg().interactive.agentUi.tasksTitle,
59763
- items: [...tasks].reverse().map((task2) => ({ value: task2.taskId, label: taskLine(task2, now) })),
60303
+ items: [...tasks].reverse().map((task2) => ({ value: task2.taskId, label: taskLine2(task2, now) })),
59764
60304
  footer: msg().interactive.agentUi.tasksFooter("↑↓")
59765
60305
  });
59766
60306
  if (picked === void 0) return;
@@ -60456,9 +60996,9 @@ function parseLeftRight(text) {
60456
60996
  async function hasUpstream(cwd, branch) {
60457
60997
  const gitDir = await findGitDir(cwd);
60458
60998
  if (gitDir === void 0) return false;
60459
- const common = (await (0, import_promises24.readFile)((0, import_node_path95.join)(gitDir, "commondir"), "utf8").catch(() => void 0))?.trim();
60460
- const dir = common === void 0 ? gitDir : (0, import_node_path95.isAbsolute)(common) ? common : (0, import_node_path95.resolve)(gitDir, common);
60461
- const text = await (0, import_promises24.readFile)((0, import_node_path95.join)(dir, "config"), "utf8").catch(() => "");
60999
+ const common = (await (0, import_promises25.readFile)((0, import_node_path96.join)(gitDir, "commondir"), "utf8").catch(() => void 0))?.trim();
61000
+ const dir = common === void 0 ? gitDir : (0, import_node_path96.isAbsolute)(common) ? common : (0, import_node_path96.resolve)(gitDir, common);
61001
+ const text = await (0, import_promises25.readFile)((0, import_node_path96.join)(dir, "config"), "utf8").catch(() => "");
60462
61002
  let inBranch = false;
60463
61003
  for (const raw of text.split("\n")) {
60464
61004
  const line2 = raw.trim();
@@ -60471,7 +61011,7 @@ async function hasUpstream(cwd, branch) {
60471
61011
  }
60472
61012
  return false;
60473
61013
  }
60474
- async function readHead3(cwd) {
61014
+ async function readHead2(cwd) {
60475
61015
  const head = await readGitHead(cwd);
60476
61016
  if (head !== void 0) {
60477
61017
  return {
@@ -60481,17 +61021,17 @@ async function readHead3(cwd) {
60481
61021
  }
60482
61022
  const gitDir = await findGitDir(cwd);
60483
61023
  if (gitDir === void 0) return void 0;
60484
- const text = await (0, import_promises24.readFile)((0, import_node_path95.join)(gitDir, "HEAD"), "utf8").catch(() => void 0);
61024
+ const text = await (0, import_promises25.readFile)((0, import_node_path96.join)(gitDir, "HEAD"), "utf8").catch(() => void 0);
60485
61025
  const match = /^ref:\s*refs\/heads\/(\S+)\s*$/.exec(text ?? "");
60486
61026
  return match?.[1] !== void 0 ? { branch: match[1] } : void 0;
60487
61027
  }
60488
- var import_node_child_process18, import_promises24, import_node_path95, GIT_NUMSTAT_INTERVAL_MS, GIT_NUMSTAT_TIMEOUT_MS, sameInfo, GitInfoWatcher;
61028
+ var import_node_child_process18, import_promises25, import_node_path96, GIT_NUMSTAT_INTERVAL_MS, GIT_NUMSTAT_TIMEOUT_MS, sameInfo, GitInfoWatcher;
60489
61029
  var init_info = __esm({
60490
61030
  "src/git/info.ts"() {
60491
61031
  "use strict";
60492
61032
  import_node_child_process18 = require("node:child_process");
60493
- import_promises24 = require("node:fs/promises");
60494
- import_node_path95 = require("node:path");
61033
+ import_promises25 = require("node:fs/promises");
61034
+ import_node_path96 = require("node:path");
60495
61035
  init_git_head();
60496
61036
  GIT_NUMSTAT_INTERVAL_MS = 1e4;
60497
61037
  GIT_NUMSTAT_TIMEOUT_MS = 2e3;
@@ -60545,7 +61085,7 @@ var init_info = __esm({
60545
61085
  const run7 = async () => {
60546
61086
  do {
60547
61087
  this.headAgain = false;
60548
- const head = await readHead3(this.cwd).catch(() => void 0);
61088
+ const head = await readHead2(this.cwd).catch(() => void 0);
60549
61089
  this.update(head);
60550
61090
  } while (this.headAgain && !this.disposed);
60551
61091
  this.headReading = void 0;
@@ -60787,12 +61327,12 @@ function statusLineText(mode) {
60787
61327
  const m = msg().interactive.statusLine;
60788
61328
  return mode === "full" ? m.full : m.compact;
60789
61329
  }
60790
- var import_node_path96, WRITE_TOOLS3, QUOTA_TICK_MS, HintLine, StatusArea;
61330
+ var import_node_path97, WRITE_TOOLS3, QUOTA_TICK_MS, HintLine, StatusArea;
60791
61331
  var init_status_area = __esm({
60792
61332
  "src/modes/interactive/status-area.ts"() {
60793
61333
  "use strict";
60794
61334
  init_i18n();
60795
- import_node_path96 = require("node:path");
61335
+ import_node_path97 = require("node:path");
60796
61336
  init_capability();
60797
61337
  init_channels();
60798
61338
  init_info();
@@ -60982,7 +61522,7 @@ var init_status_area = __esm({
60982
61522
  gitView() {
60983
61523
  const cwd = this.gitCwd;
60984
61524
  if (cwd === void 0) return void 0;
60985
- return { dir: (0, import_node_path96.basename)(cwd), info: this.git?.current() };
61525
+ return { dir: (0, import_node_path97.basename)(cwd), info: this.git?.current() };
60986
61526
  }
60987
61527
  watchGit() {
60988
61528
  const cwd = this.deps.session().state.cwd;
@@ -62644,8 +63184,8 @@ var init_run_indicator = __esm({
62644
63184
  get busy() {
62645
63185
  return this.running || this.compacting;
62646
63186
  }
62647
- setApproval(open2) {
62648
- this.approval = open2;
63187
+ setApproval(open3) {
63188
+ this.approval = open3;
62649
63189
  this.sync();
62650
63190
  }
62651
63191
  /** 会话切换后清零。 */
@@ -63309,15 +63849,15 @@ function processTerminal() {
63309
63849
  return new ProcessTerminal();
63310
63850
  }
63311
63851
  function loadKeys(runtime, warn2) {
63312
- const parsed = loadKeybindingsFile((0, import_node_path97.join)(runtime.paths.configDir, KEYBINDINGS_FILE));
63852
+ const parsed = loadKeybindingsFile((0, import_node_path98.join)(runtime.paths.configDir, KEYBINDINGS_FILE));
63313
63853
  for (const w of parsed.warnings) warn2(msg().interactive.app.keybindingsWarning(w));
63314
63854
  return new Keybindings(parsed.overrides);
63315
63855
  }
63316
- var import_node_path97;
63856
+ var import_node_path98;
63317
63857
  var init_terminal_setup = __esm({
63318
63858
  "src/modes/interactive/terminal-setup.ts"() {
63319
63859
  "use strict";
63320
- import_node_path97 = require("node:path");
63860
+ import_node_path98 = require("node:path");
63321
63861
  init_paths();
63322
63862
  init_errors();
63323
63863
  init_i18n();
@@ -63383,7 +63923,7 @@ function runInteractiveMode(runtime, context, options = {}) {
63383
63923
  ...options.statusLine !== void 0 ? { layout: options.statusLine } : {}
63384
63924
  });
63385
63925
  const { hint, bar: status } = area;
63386
- const historyFile = options.historyFile === false ? void 0 : options.historyFile ?? (0, import_node_path98.join)(runtime.paths.dataDir, "history");
63926
+ const historyFile = options.historyFile === false ? void 0 : options.historyFile ?? (0, import_node_path99.join)(runtime.paths.dataDir, "history");
63387
63927
  const completion = new InteractiveCompletion({
63388
63928
  commands: () => ALL_COMMANDS,
63389
63929
  prompts: () => runtime.resources.prompts,
@@ -63747,12 +64287,12 @@ function runInteractiveMode(runtime, context, options = {}) {
63747
64287
  }
63748
64288
  });
63749
64289
  }
63750
- var import_node_path98, HINT_MS;
64290
+ var import_node_path99, HINT_MS;
63751
64291
  var init_interactive_mode = __esm({
63752
64292
  "src/modes/interactive/interactive-mode.ts"() {
63753
64293
  "use strict";
63754
64294
  init_image_input();
63755
- import_node_path98 = require("node:path");
64295
+ import_node_path99 = require("node:path");
63756
64296
  init_session2();
63757
64297
  init_compose_session();
63758
64298
  init_startup_screen();
@@ -63828,7 +64368,7 @@ function choose(answer, values) {
63828
64368
  }
63829
64369
  function isDirectory3(path) {
63830
64370
  try {
63831
- return (0, import_node_fs72.statSync)(path).isDirectory();
64371
+ return (0, import_node_fs73.statSync)(path).isDirectory();
63832
64372
  } catch {
63833
64373
  return false;
63834
64374
  }
@@ -63872,8 +64412,8 @@ function createTextStartupUi(io) {
63872
64412
  const m = msg().report.startup;
63873
64413
  const answer = (await ask([m.cwdMissing(missing)], m.cwdAnswer))?.trim();
63874
64414
  if (answer === void 0 || answer === "") return void 0;
63875
- const abs = (0, import_node_path99.resolve)(answer);
63876
- if (!(0, import_node_fs72.existsSync)(abs) || !isDirectory3(abs)) {
64415
+ const abs = (0, import_node_path100.resolve)(answer);
64416
+ if (!(0, import_node_fs73.existsSync)(abs) || !isDirectory3(abs)) {
63877
64417
  io.write(m.notDirectory(abs));
63878
64418
  return void 0;
63879
64419
  }
@@ -63881,12 +64421,12 @@ function createTextStartupUi(io) {
63881
64421
  }
63882
64422
  };
63883
64423
  }
63884
- var import_node_fs72, import_node_path99;
64424
+ var import_node_fs73, import_node_path100;
63885
64425
  var init_startup_ui_text = __esm({
63886
64426
  "src/modes/startup-ui-text.ts"() {
63887
64427
  "use strict";
63888
- import_node_fs72 = require("node:fs");
63889
- import_node_path99 = require("node:path");
64428
+ import_node_fs73 = require("node:fs");
64429
+ import_node_path100 = require("node:path");
63890
64430
  init_i18n();
63891
64431
  }
63892
64432
  });
@@ -63911,7 +64451,7 @@ function createTools(input2, options, state) {
63911
64451
  const osStatus = configureOsSandbox(input2.config.sandbox?.enabled);
63912
64452
  const bashSandbox = options.bashSandbox ?? resolveBashSandbox(input2.config.sandbox, {
63913
64453
  status: osStatus,
63914
- ...input2.paths === void 0 ? {} : { hidden: [(0, import_node_path100.join)(input2.paths.configDir, AUTH_FILE)] }
64454
+ ...input2.paths === void 0 ? {} : { hidden: [(0, import_node_path101.join)(input2.paths.configDir, AUTH_FILE)] }
63915
64455
  });
63916
64456
  state.bashSandbox = bashSandbox;
63917
64457
  const capability = options.sandboxCapability ?? sandboxCapabilityFor(input2.config);
@@ -64032,7 +64572,7 @@ function createRuntimeDeps(options = {}) {
64032
64572
  return registry3;
64033
64573
  }
64034
64574
  },
64035
- sessions: createSessionStore(),
64575
+ sessions: createSessionStore(options.log ?? stderrLog()),
64036
64576
  tools: { create: (input2) => createTools(input2, options, state) },
64037
64577
  permissions: {
64038
64578
  create: (input2) => new PermissionPipeline({
@@ -64101,7 +64641,7 @@ function createRuntimeDeps(options = {}) {
64101
64641
  if (ui !== void 0) deps.ui = ui;
64102
64642
  return deps;
64103
64643
  }
64104
- var import_node_path100, DEFAULT_TOOL_FACTORIES, DEFAULT_MODES;
64644
+ var import_node_path101, DEFAULT_TOOL_FACTORIES, DEFAULT_MODES;
64105
64645
  var init_compose2 = __esm({
64106
64646
  "src/cli/compose.ts"() {
64107
64647
  "use strict";
@@ -64116,7 +64656,7 @@ var init_compose2 = __esm({
64116
64656
  init_tool();
64117
64657
  init_detect();
64118
64658
  init_bash();
64119
- import_node_path100 = require("node:path");
64659
+ import_node_path101 = require("node:path");
64120
64660
  init_paths();
64121
64661
  init_presets2();
64122
64662
  init_registry();
@@ -64331,8 +64871,8 @@ var init_memory3 = __esm({
64331
64871
  });
64332
64872
 
64333
64873
  // src/cli/main.ts
64334
- var import_node_fs73 = require("node:fs");
64335
- var import_node_path101 = require("node:path");
64874
+ var import_node_fs74 = require("node:fs");
64875
+ var import_node_path102 = require("node:path");
64336
64876
  var import_node_url3 = require("node:url");
64337
64877
  init_version();
64338
64878
  init_args();
@@ -69698,9 +70238,9 @@ async function runSessionsTrace(argv, io, deps = {}) {
69698
70238
  });
69699
70239
  }
69700
70240
  const outFlag = split.file ?? values.get("output");
69701
- const open2 = flags2.has("open");
70241
+ const open3 = flags2.has("open");
69702
70242
  let outPath = outFlag === void 0 ? void 0 : (0, import_node_path84.resolve)(io.cwd, expandHome(outFlag, { env: io.env }));
69703
- if (outPath === void 0 && open2)
70243
+ if (outPath === void 0 && open3)
69704
70244
  outPath = (0, import_node_path84.join)((0, import_node_os12.tmpdir)(), `ama-trace-${read2.header.id}.${wantsJson ? "json" : "html"}`);
69705
70245
  if (outPath === void 0) {
69706
70246
  io.stdout(text);
@@ -69708,7 +70248,7 @@ async function runSessionsTrace(argv, io, deps = {}) {
69708
70248
  }
69709
70249
  (0, import_node_fs64.writeFileSync)(outPath, text, { mode: 384 });
69710
70250
  io.stderr(m.wrote(outPath));
69711
- if (open2) {
70251
+ if (open3) {
69712
70252
  try {
69713
70253
  await (deps.open ?? spawnOpen)(openCommand(deps.platform ?? process.platform, outPath));
69714
70254
  } catch (error2) {
@@ -69907,7 +70447,7 @@ function readStdinDefault() {
69907
70447
  }
69908
70448
  function stdinKindDefault() {
69909
70449
  try {
69910
- const stat16 = (0, import_node_fs73.fstatSync)(0);
70450
+ const stat16 = (0, import_node_fs74.fstatSync)(0);
69911
70451
  if (stat16.isFile()) return "file";
69912
70452
  if (stat16.isFIFO()) return "fifo";
69913
70453
  if (stat16.isSocket()) return "socket";
@@ -69967,7 +70507,7 @@ function defaultIo() {
69967
70507
  }
69968
70508
  function peekUserLanguage(env) {
69969
70509
  try {
69970
- const raw = (0, import_node_fs73.readFileSync)((0, import_node_path101.join)(resolveConfigDir({ env }), "config.json"), "utf8");
70510
+ const raw = (0, import_node_fs74.readFileSync)((0, import_node_path102.join)(resolveConfigDir({ env }), "config.json"), "utf8");
69971
70511
  const value = JSON.parse(raw).ui?.language;
69972
70512
  return value === "auto" || value === "zh" || value === "en" ? value : void 0;
69973
70513
  } catch {
@@ -70055,7 +70595,7 @@ function isDirectRun() {
70055
70595
  const entry = process.argv[1];
70056
70596
  if (entry === void 0) return false;
70057
70597
  try {
70058
- return (0, import_node_fs73.realpathSync)(entry) === (0, import_node_fs73.realpathSync)((0, import_node_url3.fileURLToPath)(__ama_import_meta_url));
70598
+ return (0, import_node_fs74.realpathSync)(entry) === (0, import_node_fs74.realpathSync)((0, import_node_url3.fileURLToPath)(__ama_import_meta_url));
70059
70599
  } catch {
70060
70600
  return false;
70061
70601
  }