@tansr/sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +389 -121
  2. package/dist/index.js +1563 -460
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ var TansrSdkError = class extends Error {
21
21
  };
22
22
 
23
23
  // src/define-tool.ts
24
- function specToZod(spec, path20) {
24
+ function specToZod(spec, path23) {
25
25
  let schema;
26
26
  switch (spec.type) {
27
27
  case "string":
@@ -34,24 +34,24 @@ function specToZod(spec, path20) {
34
34
  schema = z.boolean();
35
35
  break;
36
36
  case "array":
37
- schema = z.array(spec.items !== void 0 ? specToZod(spec.items, `${path20}.items`) : z.unknown());
37
+ schema = z.array(spec.items !== void 0 ? specToZod(spec.items, `${path23}.items`) : z.unknown());
38
38
  break;
39
39
  case "object":
40
- schema = spec.properties !== void 0 ? propertiesToZodObject(spec.properties, path20) : z.record(z.unknown());
40
+ schema = spec.properties !== void 0 ? propertiesToZodObject(spec.properties, path23) : z.record(z.unknown());
41
41
  break;
42
42
  default:
43
43
  throw new TansrSdkError(
44
44
  "invalid_options",
45
- `defineTool: parameter "${path20}" has unknown type "${String(spec.type)}"; use one of 'string' | 'number' | 'boolean' | 'array' | 'object'.`
45
+ `defineTool: parameter "${path23}" has unknown type "${String(spec.type)}"; use one of 'string' | 'number' | 'boolean' | 'array' | 'object'.`
46
46
  );
47
47
  }
48
48
  if (spec.description !== void 0) schema = schema.describe(spec.description);
49
49
  return schema;
50
50
  }
51
- function propertiesToZodObject(properties, path20) {
51
+ function propertiesToZodObject(properties, path23) {
52
52
  const shape = {};
53
53
  for (const [key2, spec] of Object.entries(properties)) {
54
- const field = specToZod(spec, path20 === "" ? key2 : `${path20}.${key2}`);
54
+ const field = specToZod(spec, path23 === "" ? key2 : `${path23}.${key2}`);
55
55
  shape[key2] = spec.optional === true ? field.optional() : field;
56
56
  }
57
57
  return z.object(shape);
@@ -133,25 +133,25 @@ function defineTool(options) {
133
133
  }
134
134
 
135
135
  // src/skills.ts
136
- import path19 from "node:path";
136
+ import path21 from "node:path";
137
137
 
138
138
  // ../kernel/src/world/default-fs.ts
139
139
  import fs from "node:fs/promises";
140
140
  var defaultFs = {
141
- stat(path20) {
142
- return fs.stat(path20);
141
+ stat(path23) {
142
+ return fs.stat(path23);
143
143
  },
144
- lstat(path20) {
145
- return fs.lstat(path20);
144
+ lstat(path23) {
145
+ return fs.lstat(path23);
146
146
  },
147
- realpath(path20) {
148
- return fs.realpath(path20);
147
+ realpath(path23) {
148
+ return fs.realpath(path23);
149
149
  },
150
- readFile(path20) {
151
- return fs.readFile(path20);
150
+ readFile(path23) {
151
+ return fs.readFile(path23);
152
152
  },
153
- async readFilePrefix(path20, bytes) {
154
- const handle = await fs.open(path20, "r");
153
+ async readFilePrefix(path23, bytes) {
154
+ const handle = await fs.open(path23, "r");
155
155
  try {
156
156
  const prefix = Buffer.alloc(bytes);
157
157
  const { bytesRead } = await handle.read(prefix, 0, bytes, 0);
@@ -160,17 +160,17 @@ var defaultFs = {
160
160
  await handle.close();
161
161
  }
162
162
  },
163
- async writeFile(path20, data) {
164
- await fs.writeFile(path20, data);
163
+ async writeFile(path23, data) {
164
+ await fs.writeFile(path23, data);
165
165
  },
166
- async appendFile(path20, data) {
167
- await fs.appendFile(path20, data);
166
+ async appendFile(path23, data) {
167
+ await fs.appendFile(path23, data);
168
168
  },
169
- async mkdir(path20, opts) {
170
- await fs.mkdir(path20, opts);
169
+ async mkdir(path23, opts) {
170
+ await fs.mkdir(path23, opts);
171
171
  },
172
- readdir(path20) {
173
- return fs.readdir(path20, { withFileTypes: true });
172
+ readdir(path23) {
173
+ return fs.readdir(path23, { withFileTypes: true });
174
174
  }
175
175
  };
176
176
 
@@ -1141,6 +1141,14 @@ var FileCheckpointSchema = z4.object({
1141
1141
  /** true = 文件存在但超采集上限,内容未存(回滚时如实跳过) */
1142
1142
  skipped: z4.boolean().optional()
1143
1143
  });
1144
+ var Visibilities = {
1145
+ /** 用户与助手正常消息:全可见 */
1146
+ normal: { model: true, defaultTranscript: true, expandedTranscript: true, sdk: true },
1147
+ /** 元信息(如 system-reminder):进模型但默认转录隐藏 */
1148
+ meta: { model: true, defaultTranscript: false, expandedTranscript: true, sdk: false },
1149
+ /** 内部记录:不进模型、仅展开转录可见 */
1150
+ internal: { model: false, defaultTranscript: false, expandedTranscript: true, sdk: false }
1151
+ };
1144
1152
 
1145
1153
  // ../protocol/src/config.ts
1146
1154
  import { z as z5 } from "zod";
@@ -1447,8 +1455,9 @@ var PermissionsSectionSchema = z5.object({
1447
1455
  * TANSR_CLASSIFIER_TIGHTEN_OUTSIDE_AUTO / TANSR_CLASSIFIER_BUDGET 保留为
1448
1456
  * 最高优先覆盖(逐键接管)。仅 user/managed 层被尊重(T-K0:分类器属
1449
1457
  * 用户/企业的信任与成本决策,项目层声明整段忽略+示警)。
1450
- * 刻意不进配置:TANSR_CLASSIFIER_ALLOW_UNQUALIFIED(资格越过)——
1451
- * break-glass 性质,进配置文件即可分发可持久,恒 env-only
1458
+ * 史注:TANSR_CLASSIFIER_ALLOW_UNQUALIFIED(资格越过)曾刻意不进配置
1459
+ * (break-glass 性质恒 env-only),已随 UAA P4-C2 整体退役(2026-08-30,
1460
+ * 任命即授照后无「未达资格」可越)。
1452
1461
  */
1453
1462
  classifier: z5.object({
1454
1463
  enabled: z5.boolean().optional(),
@@ -7559,7 +7568,7 @@ async function* consumeModelStream(deps) {
7559
7568
  const stream = client.stream(request, callOptions);
7560
7569
  const collected = /* @__PURE__ */ new Map();
7561
7570
  const order = [];
7562
- const open = /* @__PURE__ */ new Set();
7571
+ const open4 = /* @__PURE__ */ new Set();
7563
7572
  const assembler = new ToolArgsAssembler();
7564
7573
  const toolCalls = [];
7565
7574
  let stopEvent;
@@ -7608,7 +7617,7 @@ async function* consumeModelStream(deps) {
7608
7617
  text: ev.block.t === "text" || ev.block.t === "thinking" ? ev.block.text : ""
7609
7618
  });
7610
7619
  order.push(ev.index);
7611
- open.add(ev.index);
7620
+ open4.add(ev.index);
7612
7621
  yield emit({ type: "msg.block.start", index: ev.index, blockType: ev.block.t });
7613
7622
  break;
7614
7623
  }
@@ -7633,7 +7642,7 @@ async function* consumeModelStream(deps) {
7633
7642
  assembler.append(ev.index, ev.json);
7634
7643
  break;
7635
7644
  case "block_stop": {
7636
- open.delete(ev.index);
7645
+ open4.delete(ev.index);
7637
7646
  yield emit({ type: "msg.block.end", index: ev.index });
7638
7647
  const c = collected.get(ev.index);
7639
7648
  if (c && c.base.t === "tool_call") {
@@ -7674,16 +7683,16 @@ async function* consumeModelStream(deps) {
7674
7683
  }
7675
7684
  }
7676
7685
  }
7677
- const completedTextBlocks = () => order.filter((index) => !open.has(index)).map((index) => collected.get(index)).filter(
7686
+ const completedTextBlocks = () => order.filter((index) => !open4.has(index)).map((index) => collected.get(index)).filter(
7678
7687
  (c) => c !== void 0 && c.base.t === "text" && c.text.trim().length > 0
7679
7688
  ).map((c) => finalizeBlock(c));
7680
7689
  if (abortedMidStream || signal.aborted) {
7681
- for (const index of open) yield emit({ type: "msg.retracted", index });
7690
+ for (const index of open4) yield emit({ type: "msg.retracted", index });
7682
7691
  disposeGenerator(stream);
7683
7692
  return { kind: "aborted" };
7684
7693
  }
7685
7694
  if (errorEvent) {
7686
- for (const index of open) yield emit({ type: "msg.retracted", index });
7695
+ for (const index of open4) yield emit({ type: "msg.retracted", index });
7687
7696
  disposeGenerator(stream);
7688
7697
  return {
7689
7698
  kind: "error",
@@ -7697,7 +7706,7 @@ async function* consumeModelStream(deps) {
7697
7706
  };
7698
7707
  }
7699
7708
  if (!stopEvent) {
7700
- for (const index of open) yield emit({ type: "msg.retracted", index });
7709
+ for (const index of open4) yield emit({ type: "msg.retracted", index });
7701
7710
  return {
7702
7711
  kind: "error",
7703
7712
  error: {
@@ -7709,7 +7718,7 @@ async function* consumeModelStream(deps) {
7709
7718
  };
7710
7719
  }
7711
7720
  if (stopEvent.stopReason === "unknown") {
7712
- for (const index of open) yield emit({ type: "msg.retracted", index });
7721
+ for (const index of open4) yield emit({ type: "msg.retracted", index });
7713
7722
  disposeGenerator(stream);
7714
7723
  return {
7715
7724
  kind: "error",
@@ -9839,16 +9848,16 @@ function unescapeSpecifier(spec) {
9839
9848
  function parseRule(raw) {
9840
9849
  const trimmed = raw.trim();
9841
9850
  if (trimmed === "") return null;
9842
- const open = findUnescapedParen(trimmed);
9843
- if (open === -1) {
9851
+ const open4 = findUnescapedParen(trimmed);
9852
+ if (open4 === -1) {
9844
9853
  if (TOOL_NAME_RE.test(trimmed)) return { raw: trimmed, toolName: trimmed };
9845
9854
  if (isMcpServerWildcard(trimmed)) return { raw: trimmed, toolName: trimmed };
9846
9855
  return null;
9847
9856
  }
9848
9857
  if (!trimmed.endsWith(")")) return null;
9849
- const toolName2 = trimmed.slice(0, open);
9858
+ const toolName2 = trimmed.slice(0, open4);
9850
9859
  if (!TOOL_NAME_RE.test(toolName2)) return null;
9851
- const spec = unescapeSpecifier(trimmed.slice(open + 1, -1)).trim();
9860
+ const spec = unescapeSpecifier(trimmed.slice(open4 + 1, -1)).trim();
9852
9861
  if (spec === "" || spec === "*") return { raw: trimmed, toolName: toolName2 };
9853
9862
  return { raw: trimmed, toolName: toolName2, specifier: spec };
9854
9863
  }
@@ -10092,9 +10101,9 @@ function stripBom(text) {
10092
10101
  return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
10093
10102
  }
10094
10103
  function stripFrontmatter(text) {
10095
- const open = /^---[ \t]*\r?\n/.exec(text);
10096
- if (open === null) return text;
10097
- const rest = text.slice(open[0].length);
10104
+ const open4 = /^---[ \t]*\r?\n/.exec(text);
10105
+ if (open4 === null) return text;
10106
+ const rest = text.slice(open4[0].length);
10098
10107
  const close = /^---[ \t]*(?:\r?\n|$)/m.exec(rest);
10099
10108
  if (close === null) return text;
10100
10109
  return rest.slice(close.index + close[0].length);
@@ -10161,9 +10170,9 @@ var TYPE_KEY_RE = /^type[ \t]*:[ \t]*(.*)$/;
10161
10170
  function memoryEntryTypeOf(content, indexFile) {
10162
10171
  if (indexFile) return "unknown";
10163
10172
  const text = content.charCodeAt(0) === 65279 ? content.slice(1) : content;
10164
- const open = FRONTMATTER_OPEN_RE.exec(text);
10165
- if (open === null) return "unknown";
10166
- const rest = text.slice(open[0].length);
10173
+ const open4 = FRONTMATTER_OPEN_RE.exec(text);
10174
+ if (open4 === null) return "unknown";
10175
+ const rest = text.slice(open4[0].length);
10167
10176
  const close = FRONTMATTER_CLOSE_RE.exec(rest);
10168
10177
  if (close === null) return "unknown";
10169
10178
  for (const rawLine of rest.slice(0, close.index).split("\n")) {
@@ -10651,6 +10660,7 @@ var CLASSIFIER_BREAKER_TOTAL_LIMIT = 20;
10651
10660
  var CLASSIFIER_BREAKER_COOLDOWN_MS = 30 * 6e4;
10652
10661
 
10653
10662
  // ../kernel/src/journal/hash.ts
10663
+ import { createHash as createHash5 } from "node:crypto";
10654
10664
  function sortKeysDeep2(value) {
10655
10665
  if (Array.isArray(value)) return value.map(sortKeysDeep2);
10656
10666
  if (value !== null && typeof value === "object") {
@@ -10667,6 +10677,11 @@ function canonicalStringify(value) {
10667
10677
  const normalized = JSON.parse(JSON.stringify(value));
10668
10678
  return JSON.stringify(sortKeysDeep2(normalized));
10669
10679
  }
10680
+ function computeRecordHash(record) {
10681
+ const { integrity, ...rest } = record;
10682
+ const hashable = { ...rest, integrity: { previousHash: integrity.previousHash } };
10683
+ return createHash5("sha256").update(canonicalStringify(hashable), "utf8").digest("hex");
10684
+ }
10670
10685
 
10671
10686
  // ../kernel/src/permissions/broad-allow.ts
10672
10687
  var BROAD_EXECUTABLES = /* @__PURE__ */ new Set([
@@ -11146,13 +11161,13 @@ function canonicalCwd(cwd) {
11146
11161
  const resolved = resolveForPermission(cwd, ".");
11147
11162
  return resolved.kind === "ok" ? resolved.path : null;
11148
11163
  }
11149
- function canonicalPathEligible(cwdCanon, path20, policy, guard) {
11150
- if (hasWindowsDeviceSegment(path20)) return false;
11151
- if (path20 === cwdCanon) return !policy.strict;
11152
- if (!path20.startsWith(`${cwdCanon}/`)) return false;
11153
- if (guard.isProtectedContentPath(path20)) return false;
11154
- if (matchProtectedPath(path20, null) !== null) return false;
11155
- const segments = path20.slice(cwdCanon.length + 1).split("/");
11164
+ function canonicalPathEligible(cwdCanon, path23, policy, guard) {
11165
+ if (hasWindowsDeviceSegment(path23)) return false;
11166
+ if (path23 === cwdCanon) return !policy.strict;
11167
+ if (!path23.startsWith(`${cwdCanon}/`)) return false;
11168
+ if (guard.isProtectedContentPath(path23)) return false;
11169
+ if (matchProtectedPath(path23, null) !== null) return false;
11170
+ const segments = path23.slice(cwdCanon.length + 1).split("/");
11156
11171
  for (const [index, seg] of segments.entries()) {
11157
11172
  const hasGlob = GLOB_CHAR_RE.test(seg);
11158
11173
  if (hasGlob && seg.startsWith(".")) return false;
@@ -11336,9 +11351,9 @@ function verifyBundle(bundle, opts = {}) {
11336
11351
  // ../kernel/src/permissions/soften-radius.ts
11337
11352
  var PROTECTED_PATH_RULE_PREFIX = "protected-path(";
11338
11353
  var MCP_TOOL_PREFIX = "mcp__";
11339
- function pathInsideRoot(path20, root) {
11354
+ function pathInsideRoot(path23, root) {
11340
11355
  if (root === null) return false;
11341
- return path20 === root || path20.startsWith(root.endsWith("/") ? root : `${root}/`);
11356
+ return path23 === root || path23.startsWith(root.endsWith("/") ? root : `${root}/`);
11342
11357
  }
11343
11358
  function classifySoftenFace(input, base) {
11344
11359
  if (base.matchedRule?.startsWith(PROTECTED_PATH_RULE_PREFIX) === true) return "F6";
@@ -11970,14 +11985,14 @@ var PermissionEngine = class {
11970
11985
  /**
11971
11986
  * 生效资格档解析(T-K23c,16 §4.5):解析闭包在场即**现算**(装配层合成
11972
11987
  * min(entry, ceiling, override);闭包抛错 → 'none',词表外脏值由
11973
- * softenRadiusAllows 安全查表兜底——T-INV-B 一切异常朝严);闭包缺席时
11974
- * @deprecated 布尔位派生 `true→'high'/false|缺省→'none'`(W1 过渡
11975
- * 兼容,16 §4.5;收口见 T-K23g)。
11988
+ * softenRadiusAllows 安全查表兜底——T-INV-B 一切异常朝严);闭包缺席
11989
+ * 'none'(无任命即无软化面;@deprecated 布尔位 qualifiedForAllow
11990
+ * 已随 P4-C3 拆除,2026-08-30)。
11976
11991
  */
11977
11992
  resolveClassifierTier(config) {
11978
11993
  const resolve2 = config.resolveQualificationTier;
11979
11994
  if (resolve2 === void 0) {
11980
- return config.qualifiedForAllow === true ? "high" : "none";
11995
+ return "none";
11981
11996
  }
11982
11997
  try {
11983
11998
  return resolve2();
@@ -12642,8 +12657,8 @@ function fsErrorCode(err) {
12642
12657
  }
12643
12658
 
12644
12659
  // ../kernel/src/tools/files/real-target-guard.ts
12645
- function accessPhrases(access) {
12646
- return access === "read" ? { doing: "reading from", refused: "Reading is refused" } : { doing: "writing to", refused: "Writing is refused" };
12660
+ function accessPhrases(access2) {
12661
+ return access2 === "read" ? { doing: "reading from", refused: "Reading is refused" } : { doing: "writing to", refused: "Writing is refused" };
12647
12662
  }
12648
12663
  async function findExistingAnchor(fs3, abs) {
12649
12664
  let cur = abs;
@@ -12670,8 +12685,8 @@ function insideCanonical(child, base, caseInsensitive) {
12670
12685
  const b = caseInsensitive ? base.toLowerCase() : base;
12671
12686
  return c === b || c.startsWith(b.endsWith("/") ? b : `${b}/`);
12672
12687
  }
12673
- function refusal(toolName2, declared, realTarget, code, detail, access) {
12674
- const phrase = accessPhrases(access);
12688
+ function refusal(toolName2, declared, realTarget, code, detail, access2) {
12689
+ const phrase = accessPhrases(access2);
12675
12690
  return {
12676
12691
  ok: false,
12677
12692
  code,
@@ -12681,8 +12696,8 @@ function refusal(toolName2, declared, realTarget, code, detail, access) {
12681
12696
  }
12682
12697
  async function checkRealTarget(toolName2, declaredAbs, options) {
12683
12698
  const caseInsensitive = options.caseInsensitivePaths ?? process.platform === "win32";
12684
- const access = options.access ?? "write";
12685
- const phrase = accessPhrases(access);
12699
+ const access2 = options.access ?? "write";
12700
+ const phrase = accessPhrases(access2);
12686
12701
  const fs3 = options.fs ?? defaultFs;
12687
12702
  let anchor;
12688
12703
  try {
@@ -12720,7 +12735,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12720
12735
  realTarget,
12721
12736
  "unverifiable",
12722
12737
  "The redirected target cannot be safely canonicalized for comparison.",
12723
- access
12738
+ access2
12724
12739
  );
12725
12740
  }
12726
12741
  const memoryDirs = options.memoryCarveOutDirs ?? [];
@@ -12766,7 +12781,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12766
12781
  realTarget,
12767
12782
  "memory_divergence",
12768
12783
  "Memory files must be plain files: no path component of a memory target may be a symlink or junction.",
12769
- access
12784
+ access2
12770
12785
  );
12771
12786
  }
12772
12787
  } else if (realInMemory) {
@@ -12776,7 +12791,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12776
12791
  realTarget,
12777
12792
  "memory_divergence",
12778
12793
  "The real target is inside the agent memory directory, which may only be addressed directly.",
12779
- access
12794
+ access2
12780
12795
  );
12781
12796
  }
12782
12797
  }
@@ -12798,7 +12813,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12798
12813
  realTarget,
12799
12814
  "escapes_workspace",
12800
12815
  `The real target is outside the workspace root "${options.cwd}".`,
12801
- access
12816
+ access2
12802
12817
  );
12803
12818
  }
12804
12819
  const homeC = canonicalHomedir(options.homedir ?? os.homedir());
@@ -12811,7 +12826,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12811
12826
  realTarget,
12812
12827
  "protected_divergence",
12813
12828
  `The real target is inside a protected area (${realLabel}).`,
12814
- access
12829
+ access2
12815
12830
  );
12816
12831
  }
12817
12832
  if (realC.kind === "ok" && matchesSecretFace(realC.path) && !(declaredC.kind === "ok" && matchesSecretFace(declaredC.path))) {
@@ -12821,7 +12836,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12821
12836
  realTarget,
12822
12837
  "secret_divergence",
12823
12838
  "The real target matches a protected secret-file pattern.",
12824
- access
12839
+ access2
12825
12840
  );
12826
12841
  }
12827
12842
  return { ok: true, realPath: realTarget, linkResolved: true };
@@ -12950,6 +12965,47 @@ function buildDecisionRecord(params) {
12950
12965
  }
12951
12966
 
12952
12967
  // ../kernel/src/tools/dispatch/dispatching-tool-executor.ts
12968
+ function suggestToolName(attempted, registered) {
12969
+ const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
12970
+ const a = norm(attempted);
12971
+ if (a === "") return null;
12972
+ let prefixHit = null;
12973
+ let levHit = null;
12974
+ let levBest = 3;
12975
+ for (const name of registered) {
12976
+ const n = norm(name);
12977
+ if (n === a) return name;
12978
+ if (prefixHit === null && Math.min(n.length, a.length) >= 4 && (n.startsWith(a) || a.startsWith(n))) {
12979
+ prefixHit = name;
12980
+ }
12981
+ const d = boundedLevenshtein(a, n, 2);
12982
+ if (d !== null && d < levBest) {
12983
+ levBest = d;
12984
+ levHit = name;
12985
+ }
12986
+ }
12987
+ return prefixHit ?? levHit;
12988
+ }
12989
+ function boundedLevenshtein(a, b, cap) {
12990
+ if (Math.abs(a.length - b.length) > cap) return null;
12991
+ let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
12992
+ for (let i = 1; i <= a.length; i++) {
12993
+ const cur = [i, ...Array.from({ length: b.length }, () => 0)];
12994
+ let rowMin = i;
12995
+ for (let j = 1; j <= b.length; j++) {
12996
+ cur[j] = Math.min(
12997
+ prev[j] + 1,
12998
+ cur[j - 1] + 1,
12999
+ prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
13000
+ );
13001
+ rowMin = Math.min(rowMin, cur[j]);
13002
+ }
13003
+ if (rowMin > cap) return null;
13004
+ prev = cur;
13005
+ }
13006
+ const d = prev[b.length];
13007
+ return d <= cap ? d : null;
13008
+ }
12953
13009
  var DEFAULT_MAX_CONCURRENCY = 8;
12954
13010
  var DEFAULT_TIMEOUT_MS = 12e4;
12955
13011
  var unsafeNoGateConstruction = false;
@@ -13149,7 +13205,8 @@ var DispatchingToolExecutor = class {
13149
13205
  const tool = this.#registry.get(call.name);
13150
13206
  if (tool === void 0) {
13151
13207
  const registered = this.#registry.names();
13152
- const message = `Unknown tool "${call.name}". ` + (registered.length > 0 ? `Available tools: ${registered.join(", ")}.` : "No tools are registered.");
13208
+ const suggested = suggestToolName(call.name, registered);
13209
+ const message = `Unknown tool "${call.name}". ` + (suggested !== null ? `Did you mean "${suggested}"? ` : "") + (registered.length > 0 ? `Available tools: ${registered.join(", ")}.` : "No tools are registered.");
13153
13210
  yield {
13154
13211
  t: "event",
13155
13212
  body: { type: "tool.failed", toolCallId: call.id, errorType: "unknown_tool", message }
@@ -13714,7 +13771,31 @@ var DispatchingToolExecutor = class {
13714
13771
  }
13715
13772
  };
13716
13773
 
13774
+ // ../kernel/src/journal/errors.ts
13775
+ var JournalError = class extends Error {
13776
+ code;
13777
+ constructor(code, message) {
13778
+ super(message);
13779
+ this.name = "JournalError";
13780
+ this.code = code;
13781
+ }
13782
+ };
13783
+ function isErrnoException(err) {
13784
+ return err instanceof Error && typeof err.code === "string";
13785
+ }
13786
+ var JournalCorruptionError = class extends JournalError {
13787
+ /** 损坏所在行号(1-based) */
13788
+ line;
13789
+ constructor(line, detail) {
13790
+ super("JOURNAL_CORRUPTED", `journal 第 ${line} 行数据损坏:${detail}`);
13791
+ this.name = "JournalCorruptionError";
13792
+ this.line = line;
13793
+ }
13794
+ };
13795
+
13717
13796
  // ../kernel/src/journal/paths.ts
13797
+ import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
13798
+ import path5 from "node:path";
13718
13799
  import { z as z14 } from "zod";
13719
13800
  var SessionMetaSchema = z14.object({
13720
13801
  schemaVersion: z14.literal(1),
@@ -13744,12 +13825,543 @@ var SessionMetaSchema = z14.object({
13744
13825
  */
13745
13826
  outputStyle: z14.string().optional()
13746
13827
  });
13828
+ var SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
13829
+ function assertValidSessionId(sessionId) {
13830
+ if (!SESSION_ID_PATTERN.test(sessionId)) {
13831
+ throw new JournalError(
13832
+ "INVALID_SESSION_ID",
13833
+ `非法 sessionId ${JSON.stringify(sessionId)}:仅允许字母数字与 . _ -,且须以字母数字开头`
13834
+ );
13835
+ }
13836
+ }
13837
+ function sessionDirPath(storageRoot, sessionId) {
13838
+ assertValidSessionId(sessionId);
13839
+ return path5.join(storageRoot, "sessions", sessionId);
13840
+ }
13841
+ function journalFilePath(sessionDir) {
13842
+ return path5.join(sessionDir, "journal.jsonl");
13843
+ }
13844
+ function metaFilePath(sessionDir) {
13845
+ return path5.join(sessionDir, "meta.json");
13846
+ }
13847
+ function lockFilePath(sessionDir) {
13848
+ return path5.join(sessionDir, "lock");
13849
+ }
13850
+ var durableTmpSeq = 0;
13851
+ async function writeFileDurable(filePath, content) {
13852
+ durableTmpSeq += 1;
13853
+ const tempPath = `${filePath}.tmp-${process.pid}-${durableTmpSeq.toString(36)}-${Date.now()}`;
13854
+ const handle = await open(tempPath, "w");
13855
+ try {
13856
+ if (typeof content === "string") {
13857
+ await handle.writeFile(content, "utf8");
13858
+ } else {
13859
+ await handle.writeFile(content);
13860
+ }
13861
+ await handle.sync();
13862
+ } finally {
13863
+ await handle.close();
13864
+ }
13865
+ try {
13866
+ await rename(tempPath, filePath);
13867
+ } catch (err) {
13868
+ await rm(tempPath, { force: true }).catch(() => void 0);
13869
+ throw err;
13870
+ }
13871
+ }
13872
+ async function readSessionMeta(sessionDir) {
13873
+ const raw = await readFile(metaFilePath(sessionDir), "utf8");
13874
+ let parsed;
13875
+ try {
13876
+ parsed = JSON.parse(raw);
13877
+ } catch {
13878
+ throw new JournalError("META_INVALID", `meta.json 不是合法 JSON:${metaFilePath(sessionDir)}`);
13879
+ }
13880
+ const result = SessionMetaSchema.safeParse(parsed);
13881
+ if (!result.success) {
13882
+ throw new JournalError("META_INVALID", `meta.json 不符合 SessionMeta schema:${result.error.message}`);
13883
+ }
13884
+ return result.data;
13885
+ }
13886
+ async function ensureSessionDir(options) {
13887
+ const sessionDir = sessionDirPath(options.storageRoot, options.sessionId);
13888
+ await mkdir(sessionDir, { recursive: true });
13889
+ try {
13890
+ const meta2 = await readSessionMeta(sessionDir);
13891
+ if (meta2.sessionId !== options.sessionId) {
13892
+ throw new JournalError(
13893
+ "META_MISMATCH",
13894
+ `meta.json 中 sessionId(${meta2.sessionId})与目录名(${options.sessionId})不一致`
13895
+ );
13896
+ }
13897
+ return { sessionDir, meta: meta2, created: false };
13898
+ } catch (err) {
13899
+ if (!isErrnoException(err) || err.code !== "ENOENT") throw err;
13900
+ }
13901
+ const meta = {
13902
+ schemaVersion: 1,
13903
+ sessionId: options.sessionId,
13904
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
13905
+ cwd: options.cwd
13906
+ };
13907
+ if (options.extra?.title !== void 0) meta.title = options.extra.title;
13908
+ if (options.extra?.firstUserMessage !== void 0) {
13909
+ meta.firstUserMessage = options.extra.firstUserMessage;
13910
+ }
13911
+ if (options.extra?.model !== void 0) meta.model = options.extra.model;
13912
+ if (options.extra?.provider !== void 0) meta.provider = options.extra.provider;
13913
+ if (options.extra?.outputStyle !== void 0) meta.outputStyle = options.extra.outputStyle;
13914
+ await writeFileDurable(metaFilePath(sessionDir), `${JSON.stringify(meta, null, 2)}
13915
+ `);
13916
+ return { sessionDir, meta, created: true };
13917
+ }
13747
13918
 
13748
13919
  // ../kernel/src/journal/lock.ts
13920
+ import { open as open2, rm as rm2, stat, readFile as readFile2 } from "node:fs/promises";
13921
+ import os2 from "node:os";
13749
13922
  var DEFAULT_STALE_MS = 30 * 60 * 1e3;
13923
+ function defaultIsPidAlive(pid) {
13924
+ try {
13925
+ process.kill(pid, 0);
13926
+ return true;
13927
+ } catch (err) {
13928
+ if (isErrnoException(err) && err.code === "EPERM") return true;
13929
+ return false;
13930
+ }
13931
+ }
13932
+ async function readLockPayload(lockPath) {
13933
+ try {
13934
+ const raw = await readFile2(lockPath, "utf8");
13935
+ const parsed = JSON.parse(raw);
13936
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.pid === "number") {
13937
+ return parsed;
13938
+ }
13939
+ return null;
13940
+ } catch {
13941
+ return null;
13942
+ }
13943
+ }
13944
+ async function tryCreateLockFile(lockPath) {
13945
+ let handle;
13946
+ try {
13947
+ handle = await open2(lockPath, "wx");
13948
+ } catch (err) {
13949
+ if (isErrnoException(err) && err.code === "EEXIST") return false;
13950
+ throw err;
13951
+ }
13952
+ try {
13953
+ const payload = {
13954
+ pid: process.pid,
13955
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
13956
+ host: os2.hostname()
13957
+ };
13958
+ await handle.writeFile(`${JSON.stringify(payload)}
13959
+ `, "utf8");
13960
+ await handle.sync();
13961
+ } finally {
13962
+ await handle.close();
13963
+ }
13964
+ return true;
13965
+ }
13966
+ async function acquireSessionLock(sessionDir, options = {}) {
13967
+ const lockPath = lockFilePath(sessionDir);
13968
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
13969
+ const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
13970
+ for (let attempt = 0; attempt < 2; attempt++) {
13971
+ if (await tryCreateLockFile(lockPath)) {
13972
+ let released = false;
13973
+ return {
13974
+ lockPath,
13975
+ async release() {
13976
+ if (released) return;
13977
+ released = true;
13978
+ await rm2(lockPath, { force: true });
13979
+ }
13980
+ };
13981
+ }
13982
+ const payload = await readLockPayload(lockPath);
13983
+ let mtimeMs = null;
13984
+ try {
13985
+ mtimeMs = (await stat(lockPath)).mtimeMs;
13986
+ } catch (err) {
13987
+ if (isErrnoException(err) && err.code === "ENOENT") {
13988
+ continue;
13989
+ }
13990
+ throw err;
13991
+ }
13992
+ const holderAlive = payload !== null && isPidAlive(payload.pid);
13993
+ const isStale = Date.now() - mtimeMs > staleMs;
13994
+ const canPreempt = payload === null ? isStale : !holderAlive || isStale;
13995
+ if (!canPreempt) {
13996
+ const holder = payload ? `pid=${payload.pid} host=${payload.host} since=${payload.startedAt}` : "未知持有者";
13997
+ throw new JournalError(
13998
+ "SESSION_LOCKED",
13999
+ `会话已被其他进程锁定(${holder}),锁文件:${lockPath}`
14000
+ );
14001
+ }
14002
+ await rm2(lockPath, { force: true });
14003
+ }
14004
+ throw new JournalError(
14005
+ "SESSION_LOCKED",
14006
+ `会话锁竞争激烈,清理陈旧锁后重试仍失败:${lockPath}`
14007
+ );
14008
+ }
14009
+
14010
+ // ../kernel/src/journal/reader.ts
14011
+ import { appendFile, readFile as readFile4, truncate } from "node:fs/promises";
14012
+
14013
+ // ../kernel/src/journal/attachments.ts
14014
+ import { createHash as createHash6 } from "node:crypto";
14015
+ import { access, mkdir as mkdir2, readFile as readFile3 } from "node:fs/promises";
14016
+ import path6 from "node:path";
14017
+ var ATTACHMENTS_DIR_NAME = "attachments";
14018
+ var IMAGE_MESSAGE_KINDS = /* @__PURE__ */ new Set([
14019
+ "user_prompt",
14020
+ "assistant_message",
14021
+ "tool_result"
14022
+ ]);
14023
+ var REF_PATTERN = /^sha256:([0-9a-f]{64})$/;
14024
+ function attachmentsDirPath(sessionDir) {
14025
+ return path6.join(sessionDir, ATTACHMENTS_DIR_NAME);
14026
+ }
14027
+ function attachmentFilePath(sessionDir, sha256Hex2) {
14028
+ return path6.join(attachmentsDirPath(sessionDir), sha256Hex2);
14029
+ }
14030
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
14031
+ var isInlineImage = (b) => b["t"] === "image" && typeof b["data"] === "string" && typeof b["mime"] === "string" && b["$ref"] === void 0;
14032
+ var isExternalizedImage = (b) => b["t"] === "image" && typeof b["$ref"] === "string" && typeof b["mime"] === "string" && b["data"] === void 0;
14033
+ async function externalizeOne(block, sessionDir) {
14034
+ try {
14035
+ const bytes = Buffer.from(block["data"], "base64");
14036
+ const hex = createHash6("sha256").update(bytes).digest("hex");
14037
+ const file = attachmentFilePath(sessionDir, hex);
14038
+ let exists = true;
14039
+ try {
14040
+ await access(file);
14041
+ } catch {
14042
+ exists = false;
14043
+ }
14044
+ if (!exists) {
14045
+ await mkdir2(attachmentsDirPath(sessionDir), { recursive: true });
14046
+ await writeFileDurable(file, bytes);
14047
+ }
14048
+ const out = {
14049
+ t: "image",
14050
+ mime: block["mime"],
14051
+ $ref: `sha256:${hex}`,
14052
+ bytes: bytes.length
14053
+ };
14054
+ const dims = probeImageDimensions(bytes);
14055
+ if (dims !== null) {
14056
+ out["w"] = dims.width;
14057
+ out["h"] = dims.height;
14058
+ }
14059
+ return out;
14060
+ } catch {
14061
+ return block;
14062
+ }
14063
+ }
14064
+ async function rehydrateOne(block, sessionDir, notes) {
14065
+ const ref = block["$ref"];
14066
+ const placeholder = (noteReason, textReason) => {
14067
+ notes.push(`image 附件${noteReason},块已降级为占位文本(${ref})`);
14068
+ return { t: "text", text: `[image unavailable: attachment ${ref} ${textReason}]` };
14069
+ };
14070
+ const match = REF_PATTERN.exec(ref);
14071
+ if (match === null) return placeholder("引用非法", "has an invalid ref");
14072
+ const hex = match[1];
14073
+ let bytes;
14074
+ try {
14075
+ bytes = await readFile3(attachmentFilePath(sessionDir, hex));
14076
+ } catch {
14077
+ return placeholder("缺失", "is missing");
14078
+ }
14079
+ if (createHash6("sha256").update(bytes).digest("hex") !== hex) {
14080
+ return placeholder("摘要不符", "is corrupt (sha256 mismatch)");
14081
+ }
14082
+ return { t: "image", mime: block["mime"], data: bytes.toString("base64") };
14083
+ }
14084
+ async function mapImageBlocks(payload, match, mapper) {
14085
+ if (!isRecord(payload) || !Array.isArray(payload["blocks"])) return payload;
14086
+ let changed = false;
14087
+ const blocks = [];
14088
+ for (const raw of payload["blocks"]) {
14089
+ if (!isRecord(raw)) {
14090
+ blocks.push(raw);
14091
+ continue;
14092
+ }
14093
+ if (match(raw)) {
14094
+ const mapped = await mapper(raw);
14095
+ if (mapped !== raw) changed = true;
14096
+ blocks.push(mapped);
14097
+ continue;
14098
+ }
14099
+ if (raw["t"] === "tool_result" && Array.isArray(raw["content"])) {
14100
+ let itemChanged = false;
14101
+ const content = [];
14102
+ for (const item of raw["content"]) {
14103
+ if (!isRecord(item) || !match(item)) {
14104
+ content.push(item);
14105
+ continue;
14106
+ }
14107
+ const mapped = await mapper(item);
14108
+ if (mapped !== item) itemChanged = true;
14109
+ content.push(mapped);
14110
+ }
14111
+ if (!itemChanged) {
14112
+ blocks.push(raw);
14113
+ continue;
14114
+ }
14115
+ changed = true;
14116
+ blocks.push({ ...raw, content });
14117
+ continue;
14118
+ }
14119
+ blocks.push(raw);
14120
+ }
14121
+ return changed ? { ...payload, blocks } : payload;
14122
+ }
14123
+ async function externalizeImagePayload(payload, sessionDir) {
14124
+ return mapImageBlocks(payload, isInlineImage, (b) => externalizeOne(b, sessionDir));
14125
+ }
14126
+ async function rehydrateImagePayload(payload, sessionDir, notes) {
14127
+ return mapImageBlocks(payload, isExternalizedImage, (b) => rehydrateOne(b, sessionDir, notes));
14128
+ }
14129
+
14130
+ // ../kernel/src/journal/reader.ts
14131
+ var MAIN_BRANCH_ID = "main";
14132
+ var NEWLINE_BYTE = 10;
14133
+ async function readAll(sessionDir, options = {}) {
14134
+ const filePath = journalFilePath(sessionDir);
14135
+ let buf;
14136
+ try {
14137
+ buf = await readFile4(filePath);
14138
+ } catch (err) {
14139
+ if (isErrnoException(err) && err.code === "ENOENT") return { records: [], repairs: [] };
14140
+ throw err;
14141
+ }
14142
+ const records = [];
14143
+ const lineStartBytes = [];
14144
+ const repairs = [];
14145
+ let truncateTo = null;
14146
+ let missingFinalNewline = false;
14147
+ let pos = 0;
14148
+ let lineNo = 0;
14149
+ while (pos < buf.length) {
14150
+ lineNo++;
14151
+ const nl = buf.indexOf(NEWLINE_BYTE, pos);
14152
+ const hasNewline = nl !== -1;
14153
+ const end = hasNewline ? nl : buf.length;
14154
+ const isLastLine = !hasNewline || nl === buf.length - 1;
14155
+ const text = buf.subarray(pos, end).toString("utf8");
14156
+ let parsed;
14157
+ try {
14158
+ parsed = JSON.parse(text);
14159
+ } catch {
14160
+ if (isLastLine) {
14161
+ truncateTo = pos;
14162
+ repairs.push(`截断崩溃残留的不完整尾行(第 ${lineNo} 行,${buf.length - pos} 字节)`);
14163
+ break;
14164
+ }
14165
+ throw new JournalCorruptionError(lineNo, "JSON 解析失败且非最后一行,无法用崩溃截断解释");
14166
+ }
14167
+ const result = JournalRecordSchema.safeParse(parsed);
14168
+ if (!result.success) {
14169
+ throw new JournalCorruptionError(lineNo, `不符合 JournalRecord schema:${result.error.message}`);
14170
+ }
14171
+ records.push(result.data);
14172
+ lineStartBytes.push(pos);
14173
+ if (!hasNewline) missingFinalNewline = true;
14174
+ pos = end + 1;
14175
+ }
14176
+ while (records.length > 0 && records[records.length - 1].completeness === "partial") {
14177
+ const idx = records.length - 1;
14178
+ truncateTo = lineStartBytes[idx];
14179
+ repairs.push(`截断崩溃残留的 partial 尾记录(seq=${records[idx].seq})`);
14180
+ records.pop();
14181
+ lineStartBytes.pop();
14182
+ missingFinalNewline = false;
14183
+ }
14184
+ if (truncateTo !== null) {
14185
+ await truncate(filePath, truncateTo);
14186
+ } else if (missingFinalNewline) {
14187
+ await appendFile(filePath, "\n");
14188
+ repairs.push("补齐末行缺失的换行符");
14189
+ }
14190
+ let integrityBreakAt;
14191
+ let expectedPrev = null;
14192
+ for (let i = 0; i < records.length; i++) {
14193
+ const rec = records[i];
14194
+ if (rec.integrity.previousHash !== expectedPrev || computeRecordHash(rec) !== rec.integrity.hash) {
14195
+ integrityBreakAt = i;
14196
+ break;
14197
+ }
14198
+ expectedPrev = rec.integrity.hash;
14199
+ }
14200
+ if (options.rehydrateImages !== false) {
14201
+ for (let i = 0; i < records.length; i++) {
14202
+ const rec = records[i];
14203
+ if (!IMAGE_MESSAGE_KINDS.has(rec.kind)) continue;
14204
+ const notes = [];
14205
+ const payload = await rehydrateImagePayload(rec.payload, sessionDir, notes);
14206
+ if (payload !== rec.payload) records[i] = { ...rec, payload };
14207
+ for (const note of notes) repairs.push(`seq=${rec.seq}:${note}`);
14208
+ }
14209
+ }
14210
+ return integrityBreakAt === void 0 ? { records, repairs } : { records, integrityBreakAt, repairs };
14211
+ }
14212
+ var MESSAGE_KINDS = /* @__PURE__ */ new Set([
14213
+ "user_prompt",
14214
+ "assistant_message",
14215
+ "tool_result"
14216
+ ]);
14217
+ function rebuildState(records) {
14218
+ const messages = [];
14219
+ const warnings = [];
14220
+ for (const rec of records) {
14221
+ if (rec.branchId !== MAIN_BRANCH_ID) continue;
14222
+ if (!MESSAGE_KINDS.has(rec.kind)) continue;
14223
+ if (!rec.visibility.model) continue;
14224
+ const parsed = IRMessageSchema.safeParse(rec.payload);
14225
+ if (!parsed.success) {
14226
+ warnings.push(`seq=${rec.seq}(kind=${rec.kind})的 payload 不是合法 IRMessage,已跳过`);
14227
+ continue;
14228
+ }
14229
+ messages.push(parsed.data);
14230
+ }
14231
+ const last = records.length > 0 ? records[records.length - 1] : void 0;
14232
+ return { messages, lastSeq: last?.seq ?? -1, branchId: MAIN_BRANCH_ID, warnings };
14233
+ }
14234
+
14235
+ // ../kernel/src/journal/writer.ts
14236
+ import { open as open3 } from "node:fs/promises";
14237
+ var CRITICAL_KINDS = /* @__PURE__ */ new Set([
14238
+ "user_prompt",
14239
+ "assistant_message",
14240
+ "tool_result",
14241
+ "fork",
14242
+ "compact_boundary",
14243
+ "queue_enqueue",
14244
+ "queue_ack"
14245
+ ]);
14246
+ var JournalWriter = class _JournalWriter {
14247
+ #handle;
14248
+ /** 串行化队列尾:并发 append 按调用序依次执行 */
14249
+ #queue = Promise.resolve();
14250
+ #previousHash;
14251
+ #nextSeq;
14252
+ /** 已确认成功的文件字节长度,写失败时截断回滚的目标 */
14253
+ #byteLength;
14254
+ /** 会话目录(J15 附件外置的落点根) */
14255
+ #sessionDir;
14256
+ #closed = false;
14257
+ /** 回滚失败后置真:磁盘与内存链状态已不可信,拒绝继续写 */
14258
+ #broken = false;
14259
+ constructor(handle, previousHash, nextSeq, byteLength, sessionDir) {
14260
+ this.#handle = handle;
14261
+ this.#previousHash = previousHash;
14262
+ this.#nextSeq = nextSeq;
14263
+ this.#byteLength = byteLength;
14264
+ this.#sessionDir = sessionDir;
14265
+ }
14266
+ /**
14267
+ * 打开(或创建)会话 journal 并定位恢复点:经 readAll 读全量(顺带截断
14268
+ * 崩溃残留、保证行边界干净),取末条记录的 seq/hash 接续哈希链。
14269
+ * 恢复点读取用原始落盘形态(rehydrateImages:false)——只取 seq/hash,
14270
+ * 不为附件回填付费。前置条件:会话目录已存在(经 ensureSessionDir);
14271
+ * 会话锁由调用方持有。
14272
+ */
14273
+ static async open(sessionDir) {
14274
+ const { records } = await readAll(sessionDir, { rehydrateImages: false });
14275
+ const last = records.length > 0 ? records[records.length - 1] : void 0;
14276
+ const handle = await open3(journalFilePath(sessionDir), "a");
14277
+ try {
14278
+ const { size } = await handle.stat();
14279
+ return new _JournalWriter(handle, last?.integrity.hash ?? null, (last?.seq ?? -1) + 1, size, sessionDir);
14280
+ } catch (err) {
14281
+ await handle.close();
14282
+ throw err;
14283
+ }
14284
+ }
14285
+ /**
14286
+ * 追加一条记录(schema 校验后写);返回实际落盘的完整记录。
14287
+ * 并发调用按发起顺序串行落盘,seq 与文件行序一致。
14288
+ */
14289
+ append(input) {
14290
+ const task = this.#queue.then(() => this.#appendSerialized(input));
14291
+ this.#queue = task.then(
14292
+ () => void 0,
14293
+ () => void 0
14294
+ );
14295
+ return task;
14296
+ }
14297
+ async #appendSerialized(input) {
14298
+ if (this.#closed) {
14299
+ throw new JournalError("JOURNAL_CLOSED", "JournalWriter 已关闭,不能继续 append");
14300
+ }
14301
+ if (this.#broken) {
14302
+ throw new JournalError("JOURNAL_BROKEN", "此前写失败且截断回滚未成功,writer 已进入不可写状态");
14303
+ }
14304
+ let effective = input;
14305
+ if (IMAGE_MESSAGE_KINDS.has(input.kind)) {
14306
+ const payload = await externalizeImagePayload(input.payload, this.#sessionDir);
14307
+ if (payload !== input.payload) effective = { ...input, payload };
14308
+ }
14309
+ const candidate = {
14310
+ ...effective,
14311
+ schemaVersion: 1,
14312
+ seq: this.#nextSeq,
14313
+ completeness: "complete",
14314
+ integrity: { previousHash: this.#previousHash, hash: "" }
14315
+ };
14316
+ const parsed = JournalRecordSchema.safeParse(candidate);
14317
+ if (!parsed.success) {
14318
+ throw new JournalError("RECORD_INVALID", `记录不符合 JournalRecord schema:${parsed.error.message}`);
14319
+ }
14320
+ const record = parsed.data;
14321
+ record.integrity.hash = computeRecordHash(record);
14322
+ const line = Buffer.from(`${JSON.stringify(record)}
14323
+ `, "utf8");
14324
+ try {
14325
+ let written = 0;
14326
+ while (written < line.length) {
14327
+ const { bytesWritten } = await this.#handle.write(line, written, line.length - written);
14328
+ written += bytesWritten;
14329
+ }
14330
+ if (CRITICAL_KINDS.has(record.kind)) {
14331
+ try {
14332
+ await this.#handle.datasync();
14333
+ } catch {
14334
+ await this.#handle.sync();
14335
+ }
14336
+ }
14337
+ } catch (err) {
14338
+ try {
14339
+ await this.#handle.truncate(this.#byteLength);
14340
+ } catch {
14341
+ this.#broken = true;
14342
+ }
14343
+ throw err;
14344
+ }
14345
+ this.#byteLength += line.length;
14346
+ this.#previousHash = record.integrity.hash;
14347
+ this.#nextSeq = record.seq + 1;
14348
+ return record;
14349
+ }
14350
+ /** 下一条将分配的 seq(诊断用) */
14351
+ get nextSeq() {
14352
+ return this.#nextSeq;
14353
+ }
14354
+ /** 等待队列排空并关闭文件句柄;幂等 */
14355
+ async close() {
14356
+ if (this.#closed) return;
14357
+ this.#closed = true;
14358
+ await this.#queue;
14359
+ await this.#handle.close();
14360
+ }
14361
+ };
13750
14362
 
13751
14363
  // ../kernel/src/tools/search/path-utils.ts
13752
- import * as path5 from "node:path";
14364
+ import * as path7 from "node:path";
13753
14365
  function toPosixPath(p) {
13754
14366
  return p.replaceAll("\\", "/");
13755
14367
  }
@@ -13757,10 +14369,10 @@ function normalizeDriveLetter(p) {
13757
14369
  return /^[A-Za-z]:/.test(p) ? p.charAt(0).toLowerCase() + p.slice(1) : p;
13758
14370
  }
13759
14371
  function isAbsolutePath(p) {
13760
- return path5.win32.isAbsolute(p) || path5.posix.isAbsolute(p);
14372
+ return path7.win32.isAbsolute(p) || path7.posix.isAbsolute(p);
13761
14373
  }
13762
14374
  function canonicalize(p) {
13763
- return normalizeDriveLetter(toPosixPath(path5.resolve(p)));
14375
+ return normalizeDriveLetter(toPosixPath(path7.resolve(p)));
13764
14376
  }
13765
14377
  function resolvePathArg(p, cwd) {
13766
14378
  const target = p ?? cwd;
@@ -13813,11 +14425,11 @@ function expandBraces(pattern) {
13813
14425
  return out;
13814
14426
  }
13815
14427
  function expandOnce(pattern) {
13816
- const open = pattern.indexOf("{");
13817
- if (open === -1) return null;
14428
+ const open4 = pattern.indexOf("{");
14429
+ if (open4 === -1) return null;
13818
14430
  let depth = 0;
13819
14431
  let close = -1;
13820
- for (let i = open; i < pattern.length; i++) {
14432
+ for (let i = open4; i < pattern.length; i++) {
13821
14433
  const ch = pattern.charAt(i);
13822
14434
  if (ch === "{") depth += 1;
13823
14435
  else if (ch === "}") {
@@ -13829,8 +14441,8 @@ function expandOnce(pattern) {
13829
14441
  }
13830
14442
  }
13831
14443
  if (close === -1) return null;
13832
- const prefix = pattern.slice(0, open);
13833
- const body = pattern.slice(open + 1, close);
14444
+ const prefix = pattern.slice(0, open4);
14445
+ const body = pattern.slice(open4 + 1, close);
13834
14446
  const suffix = pattern.slice(close + 1);
13835
14447
  const alternatives = [];
13836
14448
  let level = 0;
@@ -14142,7 +14754,7 @@ var SESSION_EVENT_VOCABULARY = {
14142
14754
  var SESSION_EVENT_KINDS = Object.keys(SESSION_EVENT_VOCABULARY);
14143
14755
 
14144
14756
  // ../kernel/src/tools/files/read.ts
14145
- import path6 from "node:path";
14757
+ import path8 from "node:path";
14146
14758
  import { z as z15 } from "zod";
14147
14759
 
14148
14760
  // ../kernel/src/tools/files/encoding.ts
@@ -14224,19 +14836,19 @@ function truncateLine(line) {
14224
14836
  return `${line.slice(0, cut)}…[line truncated: ${line.length} chars total; use Grep to inspect the rest]`;
14225
14837
  }
14226
14838
  async function findSimilarFiles(fs3, filePath) {
14227
- const dir = path6.dirname(filePath);
14228
- const targetBase = path6.basename(filePath);
14229
- const targetName = path6.parse(filePath).name.toLowerCase();
14839
+ const dir = path8.dirname(filePath);
14840
+ const targetBase = path8.basename(filePath);
14841
+ const targetName = path8.parse(filePath).name.toLowerCase();
14230
14842
  try {
14231
14843
  const entries = await fs3.readdir(dir);
14232
14844
  return entries.filter((entry) => entry.isFile()).map((entry) => entry.name).filter(
14233
- (name) => name.toLowerCase() !== targetBase.toLowerCase() && path6.parse(name).name.toLowerCase() === targetName
14234
- ).slice(0, 3).map((name) => path6.join(dir, name));
14845
+ (name) => name.toLowerCase() !== targetBase.toLowerCase() && path8.parse(name).name.toLowerCase() === targetName
14846
+ ).slice(0, 3).map((name) => path8.join(dir, name));
14235
14847
  } catch {
14236
14848
  return [];
14237
14849
  }
14238
14850
  }
14239
- async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
14851
+ async function executeImageRead(resolved, args, ctx, fs3, stat2, options) {
14240
14852
  let mode;
14241
14853
  try {
14242
14854
  mode = options.imageInput?.();
@@ -14255,9 +14867,9 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
14255
14867
  "offset/limit apply to text files only. Call Read again without offset/limit to read this image file."
14256
14868
  );
14257
14869
  }
14258
- if (stat.size > READ_IMAGE_RAW_MAX_BYTES) {
14870
+ if (stat2.size > READ_IMAGE_RAW_MAX_BYTES) {
14259
14871
  return errorResult(
14260
- `Image file is ${stat.size} bytes, which exceeds the per-image limit of ${READ_IMAGE_RAW_MAX_BYTES} bytes (5 MiB base64-encoded, aligned with the platform cap). Downscale or compress the image, then retry.`
14872
+ `Image file is ${stat2.size} bytes, which exceeds the per-image limit of ${READ_IMAGE_RAW_MAX_BYTES} bytes (5 MiB base64-encoded, aligned with the platform cap). Downscale or compress the image, then retry.`
14261
14873
  );
14262
14874
  }
14263
14875
  let buf;
@@ -14272,10 +14884,10 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
14272
14884
  const probed = probeImage(buf);
14273
14885
  if (probed === null) {
14274
14886
  return errorResult(
14275
- `File has an image extension but its content is not a readable PNG/JPEG/GIF/WebP image (bad or truncated header): ${path6.basename(resolved)}. If it is actually a text file, rename it; otherwise re-export the image and retry.`
14887
+ `File has an image extension but its content is not a readable PNG/JPEG/GIF/WebP image (bad or truncated header): ${path8.basename(resolved)}. If it is actually a text file, rename it; otherwise re-export the image and retry.`
14276
14888
  );
14277
14889
  }
14278
- registerFileRead(ctx, normalizeFileKey(resolved), stat.mtimeMs);
14890
+ registerFileRead(ctx, normalizeFileKey(resolved), stat2.mtimeMs);
14279
14891
  const data = {
14280
14892
  path: resolved,
14281
14893
  mime: probed.mime,
@@ -14283,7 +14895,7 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
14283
14895
  height: probed.height,
14284
14896
  bytes: buf.length
14285
14897
  };
14286
- const meta = `Image file: ${path6.basename(resolved)}
14898
+ const meta = `Image file: ${path8.basename(resolved)}
14287
14899
  Format: ${probed.mime}
14288
14900
  Dimensions: ${probed.width}x${probed.height} px
14289
14901
  Size: ${buf.length} bytes`;
@@ -14306,8 +14918,8 @@ function createReadTool(options = {}) {
14306
14918
  isConcurrencySafe: true,
14307
14919
  touchedPathsOf(args) {
14308
14920
  const parsed = ReadArgsSchema.safeParse(args);
14309
- if (!parsed.success || !path6.isAbsolute(parsed.data.file_path)) return [];
14310
- return [path6.resolve(parsed.data.file_path)];
14921
+ if (!parsed.success || !path8.isAbsolute(parsed.data.file_path)) return [];
14922
+ return [path8.resolve(parsed.data.file_path)];
14311
14923
  },
14312
14924
  async execute(args, ctx) {
14313
14925
  if (ctx.signal.aborted) {
@@ -14321,15 +14933,15 @@ function createReadTool(options = {}) {
14321
14933
  if (invalid) {
14322
14934
  return invalid;
14323
14935
  }
14324
- const resolved = path6.resolve(args.file_path);
14936
+ const resolved = path8.resolve(args.file_path);
14325
14937
  const fs3 = fsOf(ctx);
14326
14938
  const realTarget = await checkRealTarget("Read", resolved, { cwd: ctx.cwd, access: "read", fs: fs3 });
14327
14939
  if (!realTarget.ok) {
14328
14940
  return errorResult(realTarget.reason);
14329
14941
  }
14330
- let stat;
14942
+ let stat2;
14331
14943
  try {
14332
- stat = await fs3.stat(resolved);
14944
+ stat2 = await fs3.stat(resolved);
14333
14945
  } catch (err) {
14334
14946
  if (fsErrorCode(err) === "ENOENT") {
14335
14947
  const similar = await findSimilarFiles(fs3, resolved);
@@ -14338,11 +14950,11 @@ function createReadTool(options = {}) {
14338
14950
  }
14339
14951
  return errorResult(`Failed to read file: ${errorMessageOf(err)}`);
14340
14952
  }
14341
- if (stat.isDirectory()) {
14953
+ if (stat2.isDirectory()) {
14342
14954
  return errorResult(`Path is a directory, not a file: ${resolved}.`);
14343
14955
  }
14344
- if (isImageFileExtension(path6.extname(resolved))) {
14345
- return executeImageRead(resolved, args, ctx, fs3, stat, options);
14956
+ if (isImageFileExtension(path8.extname(resolved))) {
14957
+ return executeImageRead(resolved, args, ctx, fs3, stat2, options);
14346
14958
  }
14347
14959
  const fileKey = normalizeFileKey(resolved);
14348
14960
  const effectiveOffset = args.offset ?? 1;
@@ -14350,7 +14962,7 @@ function createReadTool(options = {}) {
14350
14962
  const dupWindow = takeDuplicateReadWindow(
14351
14963
  ctx,
14352
14964
  fileKey,
14353
- stat.mtimeMs,
14965
+ stat2.mtimeMs,
14354
14966
  effectiveOffset,
14355
14967
  effectiveLimit
14356
14968
  );
@@ -14367,9 +14979,9 @@ function createReadTool(options = {}) {
14367
14979
  data2
14368
14980
  );
14369
14981
  }
14370
- if (stat.size > READ_MAX_FULL_BYTES && args.offset === void 0 && args.limit === void 0) {
14982
+ if (stat2.size > READ_MAX_FULL_BYTES && args.offset === void 0 && args.limit === void 0) {
14371
14983
  return errorResult(
14372
- `File is ${stat.size} bytes, which exceeds the ${READ_MAX_FULL_BYTES}-byte limit for reading the whole file at once (large files inflate context: this one would be roughly ${Math.ceil(stat.size / 4)}+ tokens). Use the offset and limit parameters to read it in pages, or use Grep to locate the relevant sections first.`
14984
+ `File is ${stat2.size} bytes, which exceeds the ${READ_MAX_FULL_BYTES}-byte limit for reading the whole file at once (large files inflate context: this one would be roughly ${Math.ceil(stat2.size / 4)}+ tokens). Use the offset and limit parameters to read it in pages, or use Grep to locate the relevant sections first.`
14373
14985
  );
14374
14986
  }
14375
14987
  let buf;
@@ -14388,7 +15000,7 @@ function createReadTool(options = {}) {
14388
15000
  );
14389
15001
  }
14390
15002
  if (decoded.text.length === 0) {
14391
- registerFileRead(ctx, fileKey, stat.mtimeMs, {
15003
+ registerFileRead(ctx, fileKey, stat2.mtimeMs, {
14392
15004
  offset: effectiveOffset,
14393
15005
  limit: effectiveLimit,
14394
15006
  totalLines: 0,
@@ -14402,7 +15014,7 @@ function createReadTool(options = {}) {
14402
15014
  const startLine = effectiveOffset;
14403
15015
  const maxLines = effectiveLimit;
14404
15016
  if (startLine > totalLines) {
14405
- registerFileRead(ctx, fileKey, stat.mtimeMs, {
15017
+ registerFileRead(ctx, fileKey, stat2.mtimeMs, {
14406
15018
  offset: effectiveOffset,
14407
15019
  limit: effectiveLimit,
14408
15020
  totalLines,
@@ -14421,7 +15033,7 @@ function createReadTool(options = {}) {
14421
15033
  text += `
14422
15034
  … (showing lines ${startLine}-${endLine} of ${totalLines}; use offset=${endLine + 1} to continue)`;
14423
15035
  }
14424
- registerFileRead(ctx, fileKey, stat.mtimeMs, {
15036
+ registerFileRead(ctx, fileKey, stat2.mtimeMs, {
14425
15037
  offset: effectiveOffset,
14426
15038
  limit: effectiveLimit,
14427
15039
  totalLines,
@@ -14435,7 +15047,7 @@ function createReadTool(options = {}) {
14435
15047
  var ReadTool = createReadTool();
14436
15048
 
14437
15049
  // ../kernel/src/tools/files/write.ts
14438
- import path7 from "node:path";
15050
+ import path9 from "node:path";
14439
15051
  import { z as z16 } from "zod";
14440
15052
  var WriteArgsSchema = z16.object({
14441
15053
  file_path: z16.string().describe("Absolute path to the file to write. Relative paths are rejected."),
@@ -14454,8 +15066,8 @@ var WriteTool = {
14454
15066
  isConcurrencySafe: false,
14455
15067
  mutatedPathsOf(args) {
14456
15068
  const parsed = WriteArgsSchema.safeParse(args);
14457
- if (!parsed.success || !path7.isAbsolute(parsed.data.file_path)) return [];
14458
- return [path7.resolve(parsed.data.file_path)];
15069
+ if (!parsed.success || !path9.isAbsolute(parsed.data.file_path)) return [];
15070
+ return [path9.resolve(parsed.data.file_path)];
14459
15071
  },
14460
15072
  async execute(args, ctx) {
14461
15073
  if (ctx.signal.aborted) {
@@ -14469,7 +15081,7 @@ var WriteTool = {
14469
15081
  if (invalid) {
14470
15082
  return invalid;
14471
15083
  }
14472
- const resolved = path7.resolve(args.file_path);
15084
+ const resolved = path9.resolve(args.file_path);
14473
15085
  const key2 = normalizeFileKey(resolved);
14474
15086
  const fs3 = fsOf(ctx);
14475
15087
  const realTarget = await checkRealTarget("Write", resolved, {
@@ -14511,7 +15123,7 @@ var WriteTool = {
14511
15123
  }
14512
15124
  } else {
14513
15125
  try {
14514
- await fs3.mkdir(path7.dirname(resolved), { recursive: true });
15126
+ await fs3.mkdir(path9.dirname(resolved), { recursive: true });
14515
15127
  } catch (err) {
14516
15128
  return errorResult(`Failed to create parent directories: ${errorMessageOf(err)}`);
14517
15129
  }
@@ -14567,7 +15179,7 @@ ${memoryNearLimitNote(health)}`, data);
14567
15179
  };
14568
15180
 
14569
15181
  // ../kernel/src/tools/files/edit.ts
14570
- import path8 from "node:path";
15182
+ import path10 from "node:path";
14571
15183
  import { z as z17 } from "zod";
14572
15184
  var EditArgsSchema = z17.object({
14573
15185
  file_path: z17.string().describe("Absolute path to the file to edit. Relative paths are rejected."),
@@ -14610,8 +15222,8 @@ var EditTool = {
14610
15222
  isConcurrencySafe: false,
14611
15223
  mutatedPathsOf(args) {
14612
15224
  const parsed = EditArgsSchema.safeParse(args);
14613
- if (!parsed.success || !path8.isAbsolute(parsed.data.file_path)) return [];
14614
- return [path8.resolve(parsed.data.file_path)];
15225
+ if (!parsed.success || !path10.isAbsolute(parsed.data.file_path)) return [];
15226
+ return [path10.resolve(parsed.data.file_path)];
14615
15227
  },
14616
15228
  async execute(args, ctx) {
14617
15229
  if (ctx.signal.aborted) {
@@ -14625,7 +15237,7 @@ var EditTool = {
14625
15237
  if (invalid) {
14626
15238
  return invalid;
14627
15239
  }
14628
- const resolved = path8.resolve(args.file_path);
15240
+ const resolved = path10.resolve(args.file_path);
14629
15241
  const key2 = normalizeFileKey(resolved);
14630
15242
  const fs3 = fsOf(ctx);
14631
15243
  const realTarget = await checkRealTarget("Edit", resolved, {
@@ -14636,19 +15248,19 @@ var EditTool = {
14636
15248
  if (!realTarget.ok) {
14637
15249
  return errorResult(realTarget.reason);
14638
15250
  }
14639
- let stat;
15251
+ let stat2;
14640
15252
  try {
14641
- stat = await fs3.stat(resolved);
15253
+ stat2 = await fs3.stat(resolved);
14642
15254
  } catch (err) {
14643
15255
  if (fsErrorCode(err) === "ENOENT") {
14644
15256
  return errorResult(`File does not exist: ${resolved}. Use the Write tool to create a new file.`);
14645
15257
  }
14646
15258
  return errorResult(`Failed to access file: ${errorMessageOf(err)}`);
14647
15259
  }
14648
- if (stat.isDirectory()) {
15260
+ if (stat2.isDirectory()) {
14649
15261
  return errorResult(`Path is a directory, not a file: ${resolved}.`);
14650
15262
  }
14651
- const guard = checkStaleWriteGuard(ctx, key2, stat.mtimeMs, "editing");
15263
+ const guard = checkStaleWriteGuard(ctx, key2, stat2.mtimeMs, "editing");
14652
15264
  if (guard) {
14653
15265
  return guard;
14654
15266
  }
@@ -14753,7 +15365,7 @@ ${memoryNearLimitNote(health)}`, data);
14753
15365
 
14754
15366
  // ../kernel/src/tools/search/glob-tool.ts
14755
15367
  import { z as z18 } from "zod";
14756
- import path9 from "node:path";
15368
+ import path11 from "node:path";
14757
15369
 
14758
15370
  // ../kernel/src/tools/search/walker.ts
14759
15371
  var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
@@ -14847,7 +15459,7 @@ var globTool = {
14847
15459
  return errorResult3(`path does not exist: ${root}`);
14848
15460
  }
14849
15461
  if (!rootStat.isDirectory()) return errorResult3(`path is not a directory: ${root}`);
14850
- const realRoot = await checkRealTarget("Glob", path9.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
15462
+ const realRoot = await checkRealTarget("Glob", path11.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
14851
15463
  if (!realRoot.ok) return errorResult3(realRoot.reason);
14852
15464
  let matcher;
14853
15465
  try {
@@ -14886,7 +15498,7 @@ var globTool = {
14886
15498
 
14887
15499
  // ../kernel/src/tools/search/grep-tool.ts
14888
15500
  import { z as z19 } from "zod";
14889
- import path10 from "node:path";
15501
+ import path12 from "node:path";
14890
15502
 
14891
15503
  // ../kernel/src/tools/search/js-engine.ts
14892
15504
  var GREP_MAX_FILE_SIZE = 4 * 1024 * 1024;
@@ -15360,7 +15972,7 @@ var grepTool = {
15360
15972
  return finish(errorResult4(`path does not exist: ${root}`));
15361
15973
  }
15362
15974
  if (!rootStat.isDirectory()) return finish(errorResult4(`path is not a directory: ${root}`));
15363
- const realRoot = await checkRealTarget("Grep", path10.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
15975
+ const realRoot = await checkRealTarget("Grep", path12.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
15364
15976
  if (!realRoot.ok) return finish(errorResult4(realRoot.reason));
15365
15977
  try {
15366
15978
  new RegExp(args.pattern, args.case_insensitive ? "i" : "");
@@ -15445,7 +16057,7 @@ var grepTool = {
15445
16057
 
15446
16058
  // ../kernel/src/tools/search/list-tool.ts
15447
16059
  import { z as z20 } from "zod";
15448
- import path11 from "node:path";
16060
+ import path13 from "node:path";
15449
16061
  var LIST_MAX_ENTRIES = 500;
15450
16062
  var ListArgsSchema = z20.object({
15451
16063
  path: z20.string().min(1),
@@ -15475,7 +16087,7 @@ var listTool = {
15475
16087
  touchedPathsOf(args) {
15476
16088
  const parsed = ListArgsSchema.safeParse(args);
15477
16089
  if (!parsed.success || !isAbsolutePath(parsed.data.path)) return [];
15478
- return [path11.resolve(parsed.data.path)];
16090
+ return [path13.resolve(parsed.data.path)];
15479
16091
  },
15480
16092
  async execute(args, ctx) {
15481
16093
  if (ctx.signal.aborted) return errorResult5("aborted");
@@ -15489,7 +16101,7 @@ var listTool = {
15489
16101
  return errorResult5(`path does not exist: ${root}`);
15490
16102
  }
15491
16103
  if (!rootStat.isDirectory()) return errorResult5(`path is not a directory: ${root}`);
15492
- const realRoot = await checkRealTarget("List", path11.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
16104
+ const realRoot = await checkRealTarget("List", path13.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
15493
16105
  if (!realRoot.ok) return errorResult5(realRoot.reason);
15494
16106
  let ignoreMatchers = [];
15495
16107
  try {
@@ -16020,7 +16632,7 @@ ${tail}`;
16020
16632
 
16021
16633
  // ../kernel/src/tools/shell/output-file.ts
16022
16634
  import { createWriteStream, mkdirSync } from "node:fs";
16023
- import * as path12 from "node:path";
16635
+ import * as path14 from "node:path";
16024
16636
  var SHELL_OUTPUT_SPILL_THRESHOLD_CHARS = 5e4;
16025
16637
  var SHELL_OUTPUT_DIR_NAME = "shell-output";
16026
16638
  function sanitizeFileStem(stem) {
@@ -16029,7 +16641,7 @@ function sanitizeFileStem(stem) {
16029
16641
  }
16030
16642
  function shellOutputFilePath(sessionCwd, toolCallId) {
16031
16643
  const stem = toolCallId !== void 0 && toolCallId.trim().length > 0 ? sanitizeFileStem(toolCallId) : `shell-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
16032
- return path12.join(sessionCwd, ".tansr", SHELL_OUTPUT_DIR_NAME, `${stem}.log`);
16644
+ return path14.join(sessionCwd, ".tansr", SHELL_OUTPUT_DIR_NAME, `${stem}.log`);
16033
16645
  }
16034
16646
  var ShellOutputSink = class {
16035
16647
  filePath;
@@ -16065,7 +16677,7 @@ var ShellOutputSink = class {
16065
16677
  forceOpen() {
16066
16678
  if (this.#stream !== void 0 || this.#writeError !== void 0 || this.#closed) return;
16067
16679
  try {
16068
- mkdirSync(path12.dirname(this.filePath), { recursive: true });
16680
+ mkdirSync(path14.dirname(this.filePath), { recursive: true });
16069
16681
  const stream = createWriteStream(this.filePath, { encoding: "utf8" });
16070
16682
  stream.on("error", (err) => {
16071
16683
  this.#writeError = err.message;
@@ -17556,120 +18168,23 @@ function scrubSecrets(text, secrets) {
17556
18168
  }
17557
18169
 
17558
18170
  // ../kernel/src/tools/web/http-search-provider.ts
17559
- var WEBSEARCH_ENDPOINT_VAR = "TANSR_WEBSEARCH_ENDPOINT";
17560
- var WEBSEARCH_API_KEY_VAR = "TANSR_WEBSEARCH_API_KEY";
17561
18171
  var WEBSEARCH_RESPONSE_MAX_BYTES = 512 * 1024;
17562
- var WEBSEARCH_PROVIDER_TIMEOUT_MS = 15e3;
17563
- var defaultFetch2 = (url, init) => globalThis.fetch(url, init);
17564
- function safeEndpointLabel(endpoint) {
17565
- try {
17566
- const u = new URL(endpoint);
17567
- return `${u.origin}${u.pathname}`;
17568
- } catch {
17569
- return "(invalid endpoint URL)";
17570
- }
17571
- }
17572
- function pickItems(payload) {
17573
- if (typeof payload !== "object" || payload === null) return [];
17574
- const results = payload.results;
17575
- if (!Array.isArray(results)) return [];
17576
- return results.filter(
17577
- (item) => typeof item === "object" && item !== null
17578
- );
17579
- }
17580
- function createHttpSearchProvider(options = {}) {
17581
- const env = options.env ?? process.env;
17582
- const endpoint = env[options.endpointVar ?? WEBSEARCH_ENDPOINT_VAR];
17583
- if (endpoint === void 0 || endpoint.trim() === "") return null;
17584
- const apiKey = env[options.apiKeyVar ?? WEBSEARCH_API_KEY_VAR];
17585
- const fetchImpl = options.fetchImpl ?? defaultFetch2;
17586
- const timeoutMs = options.timeoutMs ?? WEBSEARCH_PROVIDER_TIMEOUT_MS;
17587
- const endpointLabel = safeEndpointLabel(endpoint);
17588
- const scrub = (text) => scrubSecrets(text, [apiKey, endpoint]);
17589
- return {
17590
- name: options.name ?? "http",
17591
- async search(query2, context) {
17592
- const timeout = withTimeout(context.signal, timeoutMs);
17593
- try {
17594
- const headers = {
17595
- accept: "application/json",
17596
- "content-type": "application/json",
17597
- "user-agent": "tansr-websearch"
17598
- };
17599
- if (apiKey !== void 0 && apiKey !== "") {
17600
- headers["authorization"] = `Bearer ${apiKey}`;
17601
- }
17602
- const init = {
17603
- method: "POST",
17604
- signal: timeout.signal,
17605
- redirect: "manual",
17606
- headers,
17607
- body: JSON.stringify({ query: query2.query, max_results: query2.maxResults })
17608
- };
17609
- let response;
17610
- try {
17611
- response = await fetchImpl(endpoint, init);
17612
- } catch (err) {
17613
- if (timeout.signal.aborted || isAbortError(err)) {
17614
- throw new AbortError(timeout.signal.reason);
17615
- }
17616
- throw new Error(
17617
- scrub(
17618
- `search endpoint ${endpointLabel} is unreachable: ${err instanceof Error ? err.message : String(err)}`
17619
- )
17620
- );
17621
- }
17622
- const { bytes } = await readBodyWithLimit(
17623
- response,
17624
- WEBSEARCH_RESPONSE_MAX_BYTES,
17625
- timeout.signal
17626
- );
17627
- const bodyText = new TextDecoder("utf-8").decode(bytes);
17628
- if (response.status < 200 || response.status >= 300) {
17629
- const preview = scrub(bodyText.slice(0, 200));
17630
- throw new Error(
17631
- `search endpoint ${endpointLabel} returned HTTP ${response.status}${preview !== "" ? `: ${preview}` : ""}`
17632
- );
17633
- }
17634
- let payload;
17635
- try {
17636
- payload = JSON.parse(bodyText);
17637
- } catch {
17638
- throw new Error(`search endpoint ${endpointLabel} returned invalid JSON`);
17639
- }
17640
- const items = [];
17641
- for (const item of pickItems(payload)) {
17642
- if (typeof item.url !== "string" || item.url.trim() === "") continue;
17643
- const snippet = typeof item.snippet === "string" ? item.snippet : typeof item.content === "string" ? item.content : "";
17644
- items.push({
17645
- title: scrub(typeof item.title === "string" ? item.title : ""),
17646
- url: scrub(item.url),
17647
- snippet: scrub(snippet)
17648
- });
17649
- }
17650
- return items;
17651
- } finally {
17652
- timeout.release();
17653
- }
17654
- }
17655
- };
17656
- }
17657
-
17658
- // ../kernel/src/tools/web/search-tool.ts
17659
- import { z as z27 } from "zod";
17660
- var WEBSEARCH_DEFAULT_RESULTS = 8;
17661
- var WEBSEARCH_MAX_RESULTS = 20;
17662
- var WEBSEARCH_TIMEOUT_MS = 3e4;
17663
- var WEBSEARCH_MAX_TITLE_CHARS = 200;
17664
- var WEBSEARCH_MAX_SNIPPET_CHARS = 500;
17665
- var WebSearchArgsSchema = z27.object({
17666
- query: z27.string().refine((s) => s.trim().length > 0, { message: "query must be a non-empty string" }).describe("The search query."),
17667
- max_results: z27.number().int().min(1).max(WEBSEARCH_MAX_RESULTS).optional().describe(
17668
- `Maximum number of results to return (default ${WEBSEARCH_DEFAULT_RESULTS}, max ${WEBSEARCH_MAX_RESULTS}).`
17669
- )
17670
- });
17671
- function clip(text, maxChars) {
17672
- return text.length <= maxChars ? text : `${text.slice(0, maxChars)}…`;
18172
+
18173
+ // ../kernel/src/tools/web/search-tool.ts
18174
+ import { z as z27 } from "zod";
18175
+ var WEBSEARCH_DEFAULT_RESULTS = 8;
18176
+ var WEBSEARCH_MAX_RESULTS = 20;
18177
+ var WEBSEARCH_TIMEOUT_MS = 3e4;
18178
+ var WEBSEARCH_MAX_TITLE_CHARS = 200;
18179
+ var WEBSEARCH_MAX_SNIPPET_CHARS = 500;
18180
+ var WebSearchArgsSchema = z27.object({
18181
+ query: z27.string().refine((s) => s.trim().length > 0, { message: "query must be a non-empty string" }).describe("The search query."),
18182
+ max_results: z27.number().int().min(1).max(WEBSEARCH_MAX_RESULTS).optional().describe(
18183
+ `Maximum number of results to return (default ${WEBSEARCH_DEFAULT_RESULTS}, max ${WEBSEARCH_MAX_RESULTS}).`
18184
+ )
18185
+ });
18186
+ function clip(text, maxChars) {
18187
+ return text.length <= maxChars ? text : `${text.slice(0, maxChars)}…`;
17673
18188
  }
17674
18189
  function sanitizeItems(raw, cap) {
17675
18190
  const seen = /* @__PURE__ */ new Set();
@@ -18591,7 +19106,7 @@ function applyToolOverrides(filtered, overrides) {
18591
19106
  }
18592
19107
 
18593
19108
  // ../kernel/src/agents/subagent-memory-fence.ts
18594
- import path13 from "node:path";
19109
+ import path15 from "node:path";
18595
19110
  function subagentMemoryFenceOf(ctx) {
18596
19111
  if (ctx === void 0 || ctx.dirs.length === 0) return void 0;
18597
19112
  return {
@@ -18606,7 +19121,7 @@ function writeTargetOf(args, cwd) {
18606
19121
  if (args === null || typeof args !== "object") return null;
18607
19122
  const raw = args.file_path;
18608
19123
  if (typeof raw !== "string" || raw.length === 0) return null;
18609
- return path13.isAbsolute(raw) ? path13.resolve(raw) : path13.resolve(cwd, raw);
19124
+ return path15.isAbsolute(raw) ? path15.resolve(raw) : path15.resolve(cwd, raw);
18610
19125
  }
18611
19126
  function fencedSubagentWriteTool(tool, fence) {
18612
19127
  return {
@@ -18914,10 +19429,10 @@ async function runSubagent(options) {
18914
19429
  }
18915
19430
 
18916
19431
  // ../kernel/src/tools/task/background.ts
18917
- import * as path14 from "node:path";
19432
+ import * as path16 from "node:path";
18918
19433
  var TASK_AGENT_OUTPUT_DIR_NAME = "agent-output";
18919
19434
  function taskAgentOutputFilePath(sessionCwd, agentId) {
18920
- return path14.join(sessionCwd, ".tansr", TASK_AGENT_OUTPUT_DIR_NAME, `${sanitizeFileStem(agentId)}.md`);
19435
+ return path16.join(sessionCwd, ".tansr", TASK_AGENT_OUTPUT_DIR_NAME, `${sanitizeFileStem(agentId)}.md`);
18921
19436
  }
18922
19437
  function taskSettlementStopReason(reason, aborted) {
18923
19438
  if (aborted) return "aborted";
@@ -20167,7 +20682,7 @@ import { spawnSync as spawnSync2 } from "node:child_process";
20167
20682
 
20168
20683
  // ../kernel/src/tools/mcp/win32-spawn.ts
20169
20684
  import fs2 from "node:fs";
20170
- import path15 from "node:path";
20685
+ import path17 from "node:path";
20171
20686
  var CMD_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
20172
20687
  var SPAWNABLE_EXTS = [".COM", ".EXE", ".BAT", ".CMD"];
20173
20688
  function escapeCmdCommand(command) {
@@ -20206,7 +20721,7 @@ function defaultFileExists(filePath) {
20206
20721
  }
20207
20722
  }
20208
20723
  function resolveCommandFile(command, env, cwd, fileExists) {
20209
- const w = path15.win32;
20724
+ const w = path17.win32;
20210
20725
  const exts = spawnableExts(envLookup(env, "PATHEXT"));
20211
20726
  const bases = [];
20212
20727
  if (command.includes("/") || command.includes("\\")) {
@@ -20240,7 +20755,7 @@ function wrapWithComSpec(target, args, env) {
20240
20755
  function planStdioSpawn(command, args, options) {
20241
20756
  const platform = options.platform ?? process.platform;
20242
20757
  if (platform !== "win32") return { file: command, args };
20243
- const ext = path15.win32.extname(command).toUpperCase();
20758
+ const ext = path17.win32.extname(command).toUpperCase();
20244
20759
  if (ext === ".EXE" || ext === ".COM") return { file: command, args };
20245
20760
  if (ext === ".CMD" || ext === ".BAT") return wrapWithComSpec(command, args, options.env);
20246
20761
  if (ext !== "") return { file: command, args };
@@ -20248,7 +20763,7 @@ function planStdioSpawn(command, args, options) {
20248
20763
  const fileExists = options.fileExists ?? defaultFileExists;
20249
20764
  const resolved = resolveCommandFile(command, options.env, cwd, fileExists);
20250
20765
  if (resolved === void 0) return { file: command, args };
20251
- const resolvedExt = path15.win32.extname(resolved).toUpperCase();
20766
+ const resolvedExt = path17.win32.extname(resolved).toUpperCase();
20252
20767
  if (resolvedExt === ".CMD" || resolvedExt === ".BAT") {
20253
20768
  return wrapWithComSpec(resolved, args, options.env);
20254
20769
  }
@@ -20378,11 +20893,11 @@ function buildMcpCatalogSegment(servers) {
20378
20893
  if (text.length === 0) return null;
20379
20894
  return { text, cacheable: true, label: MCP_CATALOG_LABEL };
20380
20895
  }
20381
- function isRecord(value) {
20896
+ function isRecord2(value) {
20382
20897
  return typeof value === "object" && value !== null && !Array.isArray(value);
20383
20898
  }
20384
20899
  function describeSchemaType(prop) {
20385
- if (!isRecord(prop)) return "any";
20900
+ if (!isRecord2(prop)) return "any";
20386
20901
  if (Array.isArray(prop["enum"])) return "enum";
20387
20902
  const type = prop["type"];
20388
20903
  if (typeof type === "string") return type;
@@ -20393,7 +20908,7 @@ function describeSchemaType(prop) {
20393
20908
  return "any";
20394
20909
  }
20395
20910
  function summarizeMcpParams(inputSchema) {
20396
- const properties = isRecord(inputSchema["properties"]) ? inputSchema["properties"] : void 0;
20911
+ const properties = isRecord2(inputSchema["properties"]) ? inputSchema["properties"] : void 0;
20397
20912
  if (properties === void 0 || Object.keys(properties).length === 0) {
20398
20913
  return "(parameters unspecified)";
20399
20914
  }
@@ -20549,14 +21064,14 @@ var SseParser = class {
20549
21064
  var JSONRPC_VERSION = "2.0";
20550
21065
  var JSONRPC_METHOD_NOT_FOUND = -32601;
20551
21066
  var JSONRPC_INTERNAL_ERROR = -32603;
20552
- function isRecord2(value) {
21067
+ function isRecord3(value) {
20553
21068
  return typeof value === "object" && value !== null && !Array.isArray(value);
20554
21069
  }
20555
21070
  function isValidId(value) {
20556
21071
  return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
20557
21072
  }
20558
21073
  function classifyJsonRpcValue(value) {
20559
- if (!isRecord2(value)) return { ok: false, error: "invalid_message" };
21074
+ if (!isRecord3(value)) return { ok: false, error: "invalid_message" };
20560
21075
  if (value["jsonrpc"] !== JSONRPC_VERSION) return { ok: false, error: "invalid_message" };
20561
21076
  const method = value["method"];
20562
21077
  if (typeof method === "string" && method.length > 0) {
@@ -20569,7 +21084,7 @@ function classifyJsonRpcValue(value) {
20569
21084
  if ("error" in value) {
20570
21085
  const err = value["error"];
20571
21086
  const idOk = value["id"] === null || isValidId(value["id"]);
20572
- if (!idOk || !isRecord2(err) || typeof err["code"] !== "number" || typeof err["message"] !== "string") {
21087
+ if (!idOk || !isRecord3(err) || typeof err["code"] !== "number" || typeof err["message"] !== "string") {
20573
21088
  return { ok: false, error: "invalid_message" };
20574
21089
  }
20575
21090
  return { ok: true, kind: "error", message: value };
@@ -20612,14 +21127,14 @@ function buildErrorResponse(id, code, message, data) {
20612
21127
  // ../kernel/src/tools/mcp/stream-connection.ts
20613
21128
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
20614
21129
  var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
20615
- function isRecord3(value) {
21130
+ function isRecord4(value) {
20616
21131
  return typeof value === "object" && value !== null && !Array.isArray(value);
20617
21132
  }
20618
21133
  function createDefaultServerRequestHandler(serverName, onEvent) {
20619
21134
  return (method, params) => {
20620
21135
  if (method === "ping") return Promise.resolve({ result: {} });
20621
21136
  if (method === "elicitation/create") {
20622
- const p = isRecord3(params) ? params : {};
21137
+ const p = isRecord4(params) ? params : {};
20623
21138
  const mode = p["mode"] === "url" ? "url" : "form";
20624
21139
  const message = typeof p["message"] === "string" ? p["message"].slice(0, 200) : void 0;
20625
21140
  onEvent?.({
@@ -20868,7 +21383,7 @@ var StreamMcpConnection = class {
20868
21383
  }
20869
21384
  #handleServerNotification(notification) {
20870
21385
  if (notification.method === "notifications/message") {
20871
- const p = isRecord3(notification.params) ? notification.params : {};
21386
+ const p = isRecord4(notification.params) ? notification.params : {};
20872
21387
  const level = typeof p["level"] === "string" ? p["level"] : "info";
20873
21388
  const data = p["data"];
20874
21389
  const message = typeof data === "string" ? data : data !== void 0 ? JSON.stringify(data) : "";
@@ -20882,7 +21397,7 @@ var StreamMcpConnection = class {
20882
21397
  }
20883
21398
  };
20884
21399
  function validateInitializeResult(serverName, result) {
20885
- if (!isRecord3(result)) {
21400
+ if (!isRecord4(result)) {
20886
21401
  throw new McpError("handshake_failed", "MCP initialize returned a non-object result.", {
20887
21402
  server: serverName
20888
21403
  });
@@ -20907,8 +21422,8 @@ function validateInitializeResult(serverName, result) {
20907
21422
  );
20908
21423
  }
20909
21424
  const rawInfo = result["serverInfo"];
20910
- const serverInfo = isRecord3(rawInfo) && typeof rawInfo["name"] === "string" && typeof rawInfo["version"] === "string" ? { name: rawInfo["name"], version: rawInfo["version"] } : void 0;
20911
- const capabilities = isRecord3(result["capabilities"]) ? result["capabilities"] : {};
21425
+ const serverInfo = isRecord4(rawInfo) && typeof rawInfo["name"] === "string" && typeof rawInfo["version"] === "string" ? { name: rawInfo["name"], version: rawInfo["version"] } : void 0;
21426
+ const capabilities = isRecord4(result["capabilities"]) ? result["capabilities"] : {};
20912
21427
  const rawInstructions = result["instructions"];
20913
21428
  const instructions = typeof rawInstructions === "string" && rawInstructions.trim().length > 0 ? capMcpInstructions(rawInstructions).text : void 0;
20914
21429
  return {
@@ -21112,7 +21627,7 @@ ${tail}`, {
21112
21627
  }
21113
21628
 
21114
21629
  // ../kernel/src/tools/mcp/http-transport.ts
21115
- var defaultFetch3 = (url, init) => globalThis.fetch(url, init);
21630
+ var defaultFetch2 = (url, init) => globalThis.fetch(url, init);
21116
21631
  var SESSION_HEADER = "mcp-session-id";
21117
21632
  var PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
21118
21633
  var CANCEL_NOTIFY_TIMEOUT_MS = 3e3;
@@ -21139,7 +21654,7 @@ var HttpMcpConnection = class {
21139
21654
  this.serverName = options.serverName;
21140
21655
  this.#url = config.url;
21141
21656
  this.#configHeaders = config.headers ?? {};
21142
- this.#fetchImpl = options.fetchImpl ?? defaultFetch3;
21657
+ this.#fetchImpl = options.fetchImpl ?? defaultFetch2;
21143
21658
  this.#onEvent = options.onEvent;
21144
21659
  this.#defaultRequestTimeoutMs = options.defaultRequestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
21145
21660
  this.#serverRequestHandler = options.onServerRequest ?? createDefaultServerRequestHandler(options.serverName, options.onEvent);
@@ -21504,7 +22019,7 @@ async function connectHttpMcpServer(config, options) {
21504
22019
  }
21505
22020
 
21506
22021
  // ../kernel/src/tools/mcp/bridge.ts
21507
- import { createHash as createHash5 } from "node:crypto";
22022
+ import { createHash as createHash7 } from "node:crypto";
21508
22023
  import { z as z33 } from "zod";
21509
22024
  var MCP_TOOL_NAME_PREFIX = "mcp__";
21510
22025
  var MCP_REMOTE_TOOL_NAME_RE = /^[A-Za-z0-9_.-]{1,128}$/;
@@ -21512,7 +22027,7 @@ var MAX_LIST_PAGES = 64;
21512
22027
  function buildMcpToolName(serverName, toolName2) {
21513
22028
  const full = `${MCP_TOOL_NAME_PREFIX}${serverName}__${toolName2}`;
21514
22029
  if (full.length <= MCP_BRIDGED_TOOL_NAME_MAX_CHARS) return full;
21515
- const hash = createHash5("sha256").update(full, "utf8").digest("hex").slice(0, MCP_NAME_FOLD_HASH_CHARS);
22030
+ const hash = createHash7("sha256").update(full, "utf8").digest("hex").slice(0, MCP_NAME_FOLD_HASH_CHARS);
21516
22031
  const prefix = `${MCP_TOOL_NAME_PREFIX}${serverName}__`;
21517
22032
  const tailBudget = MCP_BRIDGED_TOOL_NAME_MAX_CHARS - prefix.length;
21518
22033
  if (tailBudget >= MCP_NAME_FOLD_HASH_CHARS + 1) {
@@ -21522,7 +22037,7 @@ function buildMcpToolName(serverName, toolName2) {
21522
22037
  const head = full.slice(0, MCP_BRIDGED_TOOL_NAME_MAX_CHARS - MCP_NAME_FOLD_HASH_CHARS - 1);
21523
22038
  return `${head}_${hash}`;
21524
22039
  }
21525
- function isRecord4(value) {
22040
+ function isRecord5(value) {
21526
22041
  return typeof value === "object" && value !== null && !Array.isArray(value);
21527
22042
  }
21528
22043
  async function discoverMcpTools(connection, opts = {}) {
@@ -21536,9 +22051,9 @@ async function discoverMcpTools(connection, opts = {}) {
21536
22051
  cursor !== void 0 ? { cursor } : {},
21537
22052
  opts
21538
22053
  );
21539
- if (!isRecord4(result) || !Array.isArray(result["tools"])) break;
22054
+ if (!isRecord5(result) || !Array.isArray(result["tools"])) break;
21540
22055
  for (const raw of result["tools"]) {
21541
- if (!isRecord4(raw) || typeof raw["name"] !== "string") continue;
22056
+ if (!isRecord5(raw) || typeof raw["name"] !== "string") continue;
21542
22057
  const name = raw["name"];
21543
22058
  if (!MCP_REMOTE_TOOL_NAME_RE.test(name)) {
21544
22059
  invalidNames.push(name.slice(0, 160));
@@ -21549,7 +22064,7 @@ async function discoverMcpTools(connection, opts = {}) {
21549
22064
  tools.push({
21550
22065
  name,
21551
22066
  description: typeof raw["description"] === "string" ? raw["description"] : "",
21552
- inputSchema: isRecord4(raw["inputSchema"]) ? raw["inputSchema"] : { type: "object" }
22067
+ inputSchema: isRecord5(raw["inputSchema"]) ? raw["inputSchema"] : { type: "object" }
21553
22068
  });
21554
22069
  }
21555
22070
  const next = result["nextCursor"];
@@ -21572,7 +22087,7 @@ function buildLooseArgsSchema(inputSchema) {
21572
22087
  });
21573
22088
  }
21574
22089
  function mapCallResultToToolResult(serverName, toolName2, raw) {
21575
- if (!isRecord4(raw)) {
22090
+ if (!isRecord5(raw)) {
21576
22091
  return {
21577
22092
  content: [{ t: "text", text: "MCP server returned a malformed tools/call result." }],
21578
22093
  isError: true,
@@ -21586,11 +22101,11 @@ function mapCallResultToToolResult(serverName, toolName2, raw) {
21586
22101
  };
21587
22102
  }
21588
22103
  const isError = raw["isError"] === true;
21589
- const structuredContent = isRecord4(raw["structuredContent"]) ? raw["structuredContent"] : void 0;
22104
+ const structuredContent = isRecord5(raw["structuredContent"]) ? raw["structuredContent"] : void 0;
21590
22105
  const blocks = Array.isArray(raw["content"]) ? raw["content"] : [];
21591
22106
  const content = [];
21592
22107
  for (const block of blocks) {
21593
- if (!isRecord4(block)) continue;
22108
+ if (!isRecord5(block)) continue;
21594
22109
  const type = block["type"];
21595
22110
  if (type === "text" && typeof block["text"] === "string") {
21596
22111
  content.push({ t: "text", text: block["text"] });
@@ -21612,7 +22127,7 @@ function mapCallResultToToolResult(serverName, toolName2, raw) {
21612
22127
  content.push({ t: "text", text: `[resource link]${name} ${block["uri"]}${description}` });
21613
22128
  continue;
21614
22129
  }
21615
- if (type === "resource" && isRecord4(block["resource"])) {
22130
+ if (type === "resource" && isRecord5(block["resource"])) {
21616
22131
  const resource = block["resource"];
21617
22132
  const uri = typeof resource["uri"] === "string" ? resource["uri"] : "(unknown uri)";
21618
22133
  if (typeof resource["text"] === "string") {
@@ -21893,7 +22408,7 @@ function extractMcpServersSection(layersDescending, addDiagnostic) {
21893
22408
  }
21894
22409
 
21895
22410
  // ../kernel/src/tools/mcp/catalog-store.ts
21896
- import { createHash as createHash6 } from "node:crypto";
22411
+ import { createHash as createHash8 } from "node:crypto";
21897
22412
  var MCP_CATALOG_STORE_VERSION = 1;
21898
22413
  function stableStringify2(value) {
21899
22414
  if (value === null || typeof value !== "object") {
@@ -21907,10 +22422,10 @@ function stableStringify2(value) {
21907
22422
  return `{${parts.join(",")}}`;
21908
22423
  }
21909
22424
  function computeMcpServerConfigFingerprint(config) {
21910
- return createHash6("sha256").update(stableStringify2(config), "utf8").digest("hex");
22425
+ return createHash8("sha256").update(stableStringify2(config), "utf8").digest("hex");
21911
22426
  }
21912
22427
  function computeMcpToolDefinitionHash(tool) {
21913
- return createHash6("sha256").update(
22428
+ return createHash8("sha256").update(
21914
22429
  stableStringify2({
21915
22430
  name: tool.name,
21916
22431
  description: tool.description,
@@ -21988,14 +22503,14 @@ function resolveEnvRefs(record, baseEnv) {
21988
22503
  }
21989
22504
  return { resolved, missing };
21990
22505
  }
21991
- function isRecord5(value) {
22506
+ function isRecord6(value) {
21992
22507
  return typeof value === "object" && value !== null && !Array.isArray(value);
21993
22508
  }
21994
22509
  function createElicitationAwareHandler(serverName, onElicitation, onEvent) {
21995
22510
  const fallback = createDefaultServerRequestHandler(serverName, onEvent);
21996
22511
  return async (method, params) => {
21997
22512
  if (method !== "elicitation/create") return fallback(method, params);
21998
- const p = isRecord5(params) ? params : {};
22513
+ const p = isRecord6(params) ? params : {};
21999
22514
  const mode = p["mode"] === "url" ? "url" : "form";
22000
22515
  try {
22001
22516
  const response = await onElicitation({
@@ -22549,10 +23064,10 @@ async function initMcpLazyManager(toolset, servers, options = {}) {
22549
23064
  }
22550
23065
 
22551
23066
  // ../kernel/src/config/load.ts
22552
- import os2 from "node:os";
23067
+ import os3 from "node:os";
22553
23068
 
22554
23069
  // ../kernel/src/hooks/config.ts
22555
- import { createHash as createHash7 } from "node:crypto";
23070
+ import { createHash as createHash9 } from "node:crypto";
22556
23071
 
22557
23072
  // ../kernel/src/config/merge.ts
22558
23073
  var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
@@ -22582,12 +23097,12 @@ function deepMergeRawLayers(rawsAscending) {
22582
23097
  }
22583
23098
  return structuredClone(acc);
22584
23099
  }
22585
- function displayPath2(path20) {
22586
- return path20.map(String).join(".");
23100
+ function displayPath2(path23) {
23101
+ return path23.map(String).join(".");
22587
23102
  }
22588
- function getAtPath(root, path20) {
23103
+ function getAtPath(root, path23) {
22589
23104
  let cur = root;
22590
- for (const seg of path20) {
23105
+ for (const seg of path23) {
22591
23106
  if (typeof seg === "number") {
22592
23107
  if (!Array.isArray(cur) || seg < 0 || seg >= cur.length) return void 0;
22593
23108
  cur = cur[seg];
@@ -22598,11 +23113,11 @@ function getAtPath(root, path20) {
22598
23113
  }
22599
23114
  return cur;
22600
23115
  }
22601
- function hasPath(root, path20) {
22602
- if (path20.length === 0) return true;
23116
+ function hasPath(root, path23) {
23117
+ if (path23.length === 0) return true;
22603
23118
  let cur = root;
22604
- for (let i = 0; i < path20.length; i++) {
22605
- const seg = path20[i];
23119
+ for (let i = 0; i < path23.length; i++) {
23120
+ const seg = path23[i];
22606
23121
  if (typeof seg === "number") {
22607
23122
  if (!Array.isArray(cur) || seg < 0 || seg >= cur.length) return false;
22608
23123
  cur = cur[seg];
@@ -22613,25 +23128,25 @@ function hasPath(root, path20) {
22613
23128
  }
22614
23129
  return true;
22615
23130
  }
22616
- function sanitizeDropPath(root, path20) {
23131
+ function sanitizeDropPath(root, path23) {
22617
23132
  let cur = root;
22618
- for (let i = 0; i < path20.length; i++) {
22619
- const seg = path20[i];
23133
+ for (let i = 0; i < path23.length; i++) {
23134
+ const seg = path23[i];
22620
23135
  const next = getAtPath(cur, [seg]);
22621
- if (Array.isArray(next) && i < path20.length - 1) {
22622
- return path20.slice(0, i + 1);
23136
+ if (Array.isArray(next) && i < path23.length - 1) {
23137
+ return path23.slice(0, i + 1);
22623
23138
  }
22624
- if (typeof path20[i + 1] === "number") {
22625
- return path20.slice(0, i + 1);
23139
+ if (typeof path23[i + 1] === "number") {
23140
+ return path23.slice(0, i + 1);
22626
23141
  }
22627
23142
  cur = next;
22628
23143
  }
22629
- return path20;
23144
+ return path23;
22630
23145
  }
22631
- function deletePath(root, path20) {
22632
- if (path20.length === 0) return;
22633
- const parent = getAtPath(root, path20.slice(0, -1));
22634
- const leaf = path20[path20.length - 1];
23146
+ function deletePath(root, path23) {
23147
+ if (path23.length === 0) return;
23148
+ const parent = getAtPath(root, path23.slice(0, -1));
23149
+ const leaf = path23[path23.length - 1];
22635
23150
  if (isPlainObject2(parent) && typeof leaf === "string") {
22636
23151
  delete parent[leaf];
22637
23152
  }
@@ -22721,16 +23236,16 @@ function canonicalJson(value) {
22721
23236
  return JSON.stringify(value) ?? "null";
22722
23237
  }
22723
23238
  function configHash8(dedupeKey) {
22724
- return createHash7("sha256").update(dedupeKey, "utf8").digest("hex").slice(0, 8);
23239
+ return createHash9("sha256").update(dedupeKey, "utf8").digest("hex").slice(0, 8);
22725
23240
  }
22726
- function parseExecutor(value, layer, path20, file, addDiagnostic) {
23241
+ function parseExecutor(value, layer, path23, file, addDiagnostic) {
22727
23242
  const fileRef = file !== void 0 ? { file } : {};
22728
23243
  const invalid = (params) => {
22729
23244
  addDiagnostic({
22730
23245
  severity: "error",
22731
23246
  code: "hook_invalid_value",
22732
23247
  layer,
22733
- path: path20,
23248
+ path: path23,
22734
23249
  ...fileRef,
22735
23250
  params
22736
23251
  });
@@ -22778,7 +23293,7 @@ function parseExecutor(value, layer, path20, file, addDiagnostic) {
22778
23293
  severity: "error",
22779
23294
  code: "hook_url_invalid",
22780
23295
  layer,
22781
- path: `${path20}.url`,
23296
+ path: `${path23}.url`,
22782
23297
  ...fileRef,
22783
23298
  params: { reason: "unparsable" }
22784
23299
  });
@@ -22789,7 +23304,7 @@ function parseExecutor(value, layer, path20, file, addDiagnostic) {
22789
23304
  severity: "error",
22790
23305
  code: "hook_url_invalid",
22791
23306
  layer,
22792
- path: `${path20}.url`,
23307
+ path: `${path23}.url`,
22793
23308
  ...fileRef,
22794
23309
  params: { reason: "unsupported_protocol", protocol: parsed.protocol }
22795
23310
  });
@@ -23380,7 +23895,7 @@ function migrateLayerRaw(raw) {
23380
23895
 
23381
23896
  // ../kernel/src/config/paths.ts
23382
23897
  import { promises as fsPromises } from "node:fs";
23383
- import path16 from "node:path";
23898
+ import path18 from "node:path";
23384
23899
  var SETTINGS_FILE_NAME = "settings.json";
23385
23900
  var LOCAL_SETTINGS_FILE_NAME = "settings.local.json";
23386
23901
  var TANSR_DIR_NAME = ".tansr";
@@ -23407,27 +23922,27 @@ function normalizeForCompare(p, platform) {
23407
23922
  function defaultManagedPaths(platform, env) {
23408
23923
  if (platform === "win32") {
23409
23924
  const programData = readTrimmedEnv2(env, "PROGRAMDATA") ?? "C:\\ProgramData";
23410
- return [path16.join(programData, "tansr", MANAGED_SETTINGS_FILE_NAME)];
23925
+ return [path18.join(programData, "tansr", MANAGED_SETTINGS_FILE_NAME)];
23411
23926
  }
23412
- return [path16.posix.join("/etc", "tansr", MANAGED_SETTINGS_FILE_NAME)];
23927
+ return [path18.posix.join("/etc", "tansr", MANAGED_SETTINGS_FILE_NAME)];
23413
23928
  }
23414
23929
  async function discoverLayerFiles(options) {
23415
23930
  const { fs: fs3, env, platform } = options;
23416
23931
  const configDirOverride = readTrimmedEnv2(env, "TANSR_CONFIG_DIR");
23417
- const userDir = configDirOverride ?? path16.join(options.homedir, TANSR_DIR_NAME);
23418
- const userFile = path16.join(userDir, SETTINGS_FILE_NAME);
23419
- const homeKey = normalizeForCompare(path16.resolve(options.homedir), platform);
23932
+ const userDir = configDirOverride ?? path18.join(options.homedir, TANSR_DIR_NAME);
23933
+ const userFile = path18.join(userDir, SETTINGS_FILE_NAME);
23934
+ const homeKey = normalizeForCompare(path18.resolve(options.homedir), platform);
23420
23935
  let projectRoot;
23421
- const boundary = options.boundary === void 0 ? void 0 : normalizeForCompare(path16.resolve(options.boundary), platform);
23422
- let cursor = path16.resolve(options.cwd);
23936
+ const boundary = options.boundary === void 0 ? void 0 : normalizeForCompare(path18.resolve(options.boundary), platform);
23937
+ let cursor = path18.resolve(options.cwd);
23423
23938
  for (let depth = 0; depth < 256; depth++) {
23424
23939
  const homeAnchorInvisible = normalizeForCompare(cursor, platform) === homeKey;
23425
- if (!homeAnchorInvisible && await fs3.directoryExists(path16.join(cursor, TANSR_DIR_NAME))) {
23940
+ if (!homeAnchorInvisible && await fs3.directoryExists(path18.join(cursor, TANSR_DIR_NAME))) {
23426
23941
  projectRoot = cursor;
23427
23942
  break;
23428
23943
  }
23429
23944
  if (boundary !== void 0 && normalizeForCompare(cursor, platform) === boundary) break;
23430
- const parent = path16.dirname(cursor);
23945
+ const parent = path18.dirname(cursor);
23431
23946
  if (parent === cursor) break;
23432
23947
  cursor = parent;
23433
23948
  }
@@ -23436,8 +23951,8 @@ async function discoverLayerFiles(options) {
23436
23951
  managedCandidates,
23437
23952
  ...projectRoot !== void 0 ? {
23438
23953
  projectRoot,
23439
- projectSharedFile: path16.join(projectRoot, TANSR_DIR_NAME, SETTINGS_FILE_NAME),
23440
- projectLocalFile: path16.join(projectRoot, TANSR_DIR_NAME, LOCAL_SETTINGS_FILE_NAME)
23954
+ projectSharedFile: path18.join(projectRoot, TANSR_DIR_NAME, SETTINGS_FILE_NAME),
23955
+ projectLocalFile: path18.join(projectRoot, TANSR_DIR_NAME, LOCAL_SETTINGS_FILE_NAME)
23441
23956
  } : {},
23442
23957
  userFile
23443
23958
  };
@@ -23994,8 +24509,8 @@ function issueExpected(issue) {
23994
24509
  return issue.code;
23995
24510
  }
23996
24511
  }
23997
- function attributeLayer(layersDescending, path20) {
23998
- return layersDescending.find((entry) => hasPath(entry.raw, path20));
24512
+ function attributeLayer(layersDescending, path23) {
24513
+ return layersDescending.find((entry) => hasPath(entry.raw, path23));
23999
24514
  }
24000
24515
  function scanUnknownKeys(raw, parsed, pathSoFar, layersDescending, addDiagnostic) {
24001
24516
  if (!isPlainObject2(raw) || !isPlainObject2(parsed)) return;
@@ -24168,7 +24683,7 @@ async function loadConfig(options = {}) {
24168
24683
  const env = options.env ?? process.env;
24169
24684
  const discovered = await discoverLayerFiles({
24170
24685
  cwd: options.cwd ?? process.cwd(),
24171
- homedir: options.homedir ?? os2.homedir(),
24686
+ homedir: options.homedir ?? os3.homedir(),
24172
24687
  env,
24173
24688
  fs: fs3,
24174
24689
  platform: options.platform ?? process.platform,
@@ -24248,9 +24763,9 @@ var OPEN_FENCE_RE = /^---[ \t]*\r?\n/;
24248
24763
  var CLOSE_FENCE_RE = /^---[ \t]*$/;
24249
24764
  function splitFrontmatterBlock(rawText) {
24250
24765
  const text = stripBom2(rawText);
24251
- const open = OPEN_FENCE_RE.exec(text);
24252
- if (open === null) return null;
24253
- const rest = text.slice(open[0].length);
24766
+ const open4 = OPEN_FENCE_RE.exec(text);
24767
+ if (open4 === null) return null;
24768
+ const rest = text.slice(open4[0].length);
24254
24769
  const lines = rest.split("\n");
24255
24770
  for (let i = 0; i < lines.length; i++) {
24256
24771
  const line = (lines[i] ?? "").replace(/\r$/, "");
@@ -25089,8 +25604,8 @@ var HookEngine = class {
25089
25604
 
25090
25605
  // ../kernel/src/skills/discover.ts
25091
25606
  import { promises as fsPromises2 } from "node:fs";
25092
- import os3 from "node:os";
25093
- import path17 from "node:path";
25607
+ import os4 from "node:os";
25608
+ import path19 from "node:path";
25094
25609
 
25095
25610
  // ../kernel/src/skills/types.ts
25096
25611
  var SKILLS_DIR_NAME = "skills";
@@ -25310,8 +25825,8 @@ async function scanFileSource(source, skillsDir, fs3, diagnostics) {
25310
25825
  const seen = /* @__PURE__ */ new Set();
25311
25826
  for (const dirName of [...subdirs].sort()) {
25312
25827
  if (dirName.startsWith(".") || dirName.startsWith("_")) continue;
25313
- const dir = path17.join(skillsDir, dirName);
25314
- const file = path17.join(dir, SKILL_FILE_NAME);
25828
+ const dir = path19.join(skillsDir, dirName);
25829
+ const file = path19.join(dir, SKILL_FILE_NAME);
25315
25830
  if (!SKILL_NAME_RE.test(dirName)) {
25316
25831
  diagnostics.push({ severity: "warning", code: "name_invalid", source, skill: dirName, file });
25317
25832
  continue;
@@ -25358,9 +25873,9 @@ async function discoverSkills(options = {}) {
25358
25873
  const fs3 = options.fs ?? defaultSkillsFileSystem();
25359
25874
  const env = options.env ?? process.env;
25360
25875
  const platform = options.platform ?? process.platform;
25361
- const homedir = options.homedir ?? os3.homedir();
25876
+ const homedir = options.homedir ?? os4.homedir();
25362
25877
  const cwd = options.cwd ?? process.cwd();
25363
- const explicitRoot = options.projectRoot === void 0 ? void 0 : path17.resolve(options.projectRoot);
25878
+ const explicitRoot = options.projectRoot === void 0 ? void 0 : path19.resolve(options.projectRoot);
25364
25879
  const discovered = await discoverLayerFiles({
25365
25880
  cwd: explicitRoot ?? cwd,
25366
25881
  homedir,
@@ -25371,13 +25886,13 @@ async function discoverSkills(options = {}) {
25371
25886
  ...explicitRoot !== void 0 ? { boundary: explicitRoot } : options.boundary !== void 0 ? { boundary: options.boundary } : {}
25372
25887
  });
25373
25888
  const builtinEntries = collectBuiltin(options.builtinSkills ?? [], diagnostics);
25374
- const userSkillsDir = path17.join(path17.dirname(discovered.userFile), SKILLS_DIR_NAME);
25889
+ const userSkillsDir = path19.join(path19.dirname(discovered.userFile), SKILLS_DIR_NAME);
25375
25890
  const userOutcome = await scanFileSource("user", userSkillsDir, fs3, diagnostics);
25376
25891
  const projectRoot = explicitRoot ?? discovered.projectRoot;
25377
25892
  let projectSkillsDir;
25378
25893
  let projectOutcome = { status: "missing" };
25379
25894
  if (projectRoot !== void 0) {
25380
- projectSkillsDir = path17.join(projectRoot, TANSR_DIR_NAME, SKILLS_DIR_NAME);
25895
+ projectSkillsDir = path19.join(projectRoot, TANSR_DIR_NAME, SKILLS_DIR_NAME);
25381
25896
  projectOutcome = await scanFileSource("project", projectSkillsDir, fs3, diagnostics);
25382
25897
  }
25383
25898
  const userEntries = userOutcome.status === "scanned" ? userOutcome.entries : [];
@@ -25451,7 +25966,7 @@ async function discoverSkills(options = {}) {
25451
25966
  }
25452
25967
 
25453
25968
  // ../kernel/src/skills/registry.ts
25454
- import path18 from "node:path";
25969
+ import path20 from "node:path";
25455
25970
  function errCode2(err) {
25456
25971
  if (err !== null && typeof err === "object" && "code" in err) {
25457
25972
  const code = err.code;
@@ -25514,7 +26029,7 @@ var SkillRegistry = class {
25514
26029
  /** 技能目录绝对路径(正文相对引用锚点;builtin 无) */
25515
26030
  static baseDirOf(entry) {
25516
26031
  if (entry.dir !== void 0) return entry.dir;
25517
- if (entry.file !== void 0) return path18.dirname(entry.file);
26032
+ if (entry.file !== void 0) return path20.dirname(entry.file);
25518
26033
  return void 0;
25519
26034
  }
25520
26035
  };
@@ -25718,9 +26233,9 @@ function defineSkill(options) {
25718
26233
  ];
25719
26234
  return { name: options.name, content: lines.join("\n") };
25720
26235
  }
25721
- var NO_DISCOVERY_ROOT = path19.resolve(path19.sep, ".tansr-sdk-no-discovery");
26236
+ var NO_DISCOVERY_ROOT = path21.resolve(path21.sep, ".tansr-sdk-no-discovery");
25722
26237
  function noDiscoveryFs(inner) {
25723
- const inVoid = (target) => path19.resolve(target).startsWith(NO_DISCOVERY_ROOT);
26238
+ const inVoid = (target) => path21.resolve(target).startsWith(NO_DISCOVERY_ROOT);
25724
26239
  const enoent = (target) => {
25725
26240
  const err = new Error(`ENOENT: no such file or directory ${target}`);
25726
26241
  err.code = "ENOENT";
@@ -25734,7 +26249,7 @@ function noDiscoveryFs(inner) {
25734
26249
  }
25735
26250
  async function assembleSdkSkills(options) {
25736
26251
  const fs3 = noDiscoveryFs(options.fs ?? defaultSkillsFileSystem());
25737
- const dirs = (options.dirs ?? []).map((dir) => path19.resolve(dir));
26252
+ const dirs = (options.dirs ?? []).map((dir) => path21.resolve(dir));
25738
26253
  for (const dir of dirs) {
25739
26254
  if (!await fs3.directoryExists(dir)) {
25740
26255
  throw new TansrSdkError(
@@ -27870,7 +28385,7 @@ var OpenAIResponsesAdapter = class {
27870
28385
  };
27871
28386
 
27872
28387
  // ../providers/src/retry/attempt-observer.ts
27873
- import { createHash as createHash8 } from "node:crypto";
28388
+ import { createHash as createHash10 } from "node:crypto";
27874
28389
  var PROVIDER_CALL_META_FIELD = "callMeta";
27875
28390
  function providerCallMetaOf(options) {
27876
28391
  if (options === void 0) return void 0;
@@ -27899,12 +28414,12 @@ function endpointKeyOf(baseUrl) {
27899
28414
  let normalized;
27900
28415
  try {
27901
28416
  const url = new URL(baseUrl);
27902
- const path20 = url.pathname.replace(/\/+$/, "");
27903
- normalized = `${url.protocol}//${url.host}${path20}`.toLowerCase();
28417
+ const path23 = url.pathname.replace(/\/+$/, "");
28418
+ normalized = `${url.protocol}//${url.host}${path23}`.toLowerCase();
27904
28419
  } catch {
27905
28420
  normalized = baseUrl.trim().replace(/\/+$/, "").toLowerCase();
27906
28421
  }
27907
- return createHash8("sha256").update(normalized, "utf8").digest("hex").slice(0, 16);
28422
+ return createHash10("sha256").update(normalized, "utf8").digest("hex").slice(0, 16);
27908
28423
  }
27909
28424
  function safeAttemptCall(fn) {
27910
28425
  if (fn === void 0) return;
@@ -28041,14 +28556,14 @@ function sendableTwpReasoningOff(features) {
28041
28556
  }
28042
28557
 
28043
28558
  // ../providers/src/twp/signing.ts
28044
- import { createHash as createHash9, createHmac, randomBytes } from "node:crypto";
28559
+ import { createHash as createHash11, createHmac, randomBytes } from "node:crypto";
28045
28560
  var TWP_SIGNING_ALGORITHM = "TWP1-HMAC-SHA256";
28046
28561
  var TWP_NONCE_LENGTH = 24;
28047
28562
  function deriveTwpSigningKey(secretKey) {
28048
- return createHash9("sha256").update(secretKey, "utf8").digest();
28563
+ return createHash11("sha256").update(secretKey, "utf8").digest();
28049
28564
  }
28050
28565
  function sha256Hex(data) {
28051
- return createHash9("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
28566
+ return createHash11("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
28052
28567
  }
28053
28568
  function generateTwpNonce(random = randomBytes) {
28054
28569
  return random(TWP_NONCE_LENGTH / 2).toString("hex");
@@ -28290,11 +28805,11 @@ function twpHttpStatusToIR(status, bodyText) {
28290
28805
  if (status >= 500) return { kind: "server", message, recoverable: true };
28291
28806
  return { kind: "invalid_request", message, recoverable: false };
28292
28807
  }
28293
- function isRecord6(value) {
28808
+ function isRecord7(value) {
28294
28809
  return typeof value === "object" && value !== null && !Array.isArray(value);
28295
28810
  }
28296
28811
  function twpNonStreamToEvents(raw, fallbackModel) {
28297
- if (!isRecord6(raw)) {
28812
+ if (!isRecord7(raw)) {
28298
28813
  return [
28299
28814
  {
28300
28815
  t: "error",
@@ -28312,7 +28827,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
28312
28827
  const blocks = Array.isArray(raw["blocks"]) ? raw["blocks"] : [];
28313
28828
  let index = 0;
28314
28829
  for (const rawBlock of blocks) {
28315
- if (!isRecord6(rawBlock)) continue;
28830
+ if (!isRecord7(rawBlock)) continue;
28316
28831
  if (rawBlock["t"] === "text" && typeof rawBlock["v"] === "string") {
28317
28832
  out.push({ t: "block_start", index, block: { t: "text", text: rawBlock["v"] } });
28318
28833
  out.push({ t: "block_stop", index });
@@ -28332,7 +28847,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
28332
28847
  index += 1;
28333
28848
  }
28334
28849
  }
28335
- const usage = isRecord6(raw["usage"]) ? twpUsageToIR(parseTwpUsage(raw["usage"])) : void 0;
28850
+ const usage = isRecord7(raw["usage"]) ? twpUsageToIR(parseTwpUsage(raw["usage"])) : void 0;
28336
28851
  out.push({
28337
28852
  t: "message_stop",
28338
28853
  stopReason: mapTwpStop(typeof raw["stop"] === "string" ? raw["stop"] : void 0),
@@ -28341,7 +28856,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
28341
28856
  return out;
28342
28857
  }
28343
28858
  function twpNonStreamUsageFrame(raw) {
28344
- if (!isRecord6(raw) || !isRecord6(raw["usage"])) return null;
28859
+ if (!isRecord7(raw) || !isRecord7(raw["usage"])) return null;
28345
28860
  return parseTwpUsage(raw["usage"]);
28346
28861
  }
28347
28862
 
@@ -29023,9 +29538,9 @@ var MissingApiKeyError = class extends ProviderRegistryError {
29023
29538
  var CyclicFallbackError = class extends ProviderRegistryError {
29024
29539
  /** 成环路径,末项为再次出现的别名,如 ['main','fast','main'] */
29025
29540
  path;
29026
- constructor(path20) {
29027
- super("cyclic_fallback", `Cyclic fallback chain: ${path20.join(" -> ")}`);
29028
- this.path = path20;
29541
+ constructor(path23) {
29542
+ super("cyclic_fallback", `Cyclic fallback chain: ${path23.join(" -> ")}`);
29543
+ this.path = path23;
29029
29544
  }
29030
29545
  };
29031
29546
 
@@ -29508,9 +30023,9 @@ function pushUnique(out, seen, resolved) {
29508
30023
  seen.add(dedupeKey);
29509
30024
  out.push(resolved);
29510
30025
  }
29511
- function expandInto(config, alias, path20, out, seen) {
29512
- if (path20.includes(alias)) throw new CyclicFallbackError([...path20, alias]);
29513
- const nextPath = [...path20, alias];
30026
+ function expandInto(config, alias, path23, out, seen) {
30027
+ if (path23.includes(alias)) throw new CyclicFallbackError([...path23, alias]);
30028
+ const nextPath = [...path23, alias];
29514
30029
  pushUnique(out, seen, resolveAliasOrRef(config, alias));
29515
30030
  for (const entry of config.fallbacks[alias] ?? []) {
29516
30031
  if (entry.includes("/")) {
@@ -30337,14 +30852,21 @@ var AppQuotaSchema = z41.object({
30337
30852
  import { z as z42 } from "zod";
30338
30853
  var IMAGEGEN_TOOL_MAX_N = 4;
30339
30854
  var ImageGenArgsSchema = z42.object({
30340
- model: z42.string().min(1).describe(
30341
- "Image model reference from the app platform config (ask the app developer / see app docs for available names)."
30855
+ // S-G1(媒体池化顺位取用):Optional——缺席即不发 model 位,网关按授权集顺位
30856
+ // 第一生效( bundle platformModels.imageGen 首行恒同);点名限集内(工具描述自列)。
30857
+ model: z42.string().min(1).optional().describe(
30858
+ "Image model name from the authorized set listed in this tool description. Omit to use the default (the first authorized model) when the user did not ask for a specific model. Never omit or swap this field as a workaround when the user named a model that is not in the set — tell the user and let them choose first."
30342
30859
  ),
30343
30860
  prompt: z42.string().refine((s) => s.trim().length > 0, { message: "prompt must be a non-empty string" }).describe("Positive prompt describing the desired image content, style and composition."),
30344
30861
  negativePrompt: z42.string().min(1).max(500).optional().describe("Negative prompt: content to keep out of the image."),
30345
30862
  size: z42.string().regex(/^\d{2,5}\*\d{2,5}$/, { message: "size must look like '1024*1024' (width*height)" }).optional().describe("Output resolution as 'width*height' (for example '1024*1024'; defaults to the model's default)."),
30346
30863
  n: z42.number().int().min(1).max(IMAGEGEN_TOOL_MAX_N).optional().describe(`Number of images to generate (default 1, max ${IMAGEGEN_TOOL_MAX_N}; each image is billed).`),
30347
- seed: z42.number().int().min(0).max(2147483647).optional().describe("Random seed for relatively stable output (defaults to a random seed upstream).")
30864
+ seed: z42.number().int().min(0).max(2147483647).optional().describe("Random seed for relatively stable output (defaults to a random seed upstream)."),
30865
+ // 媒体二批富输入位(2026-09-01;网关 TwpImagegenRequestSchema 同帽 1..14):
30866
+ // 受理域随模型(工具描述逐模型注明;不受理的模型在场即 bad_request)。
30867
+ imageUrls: z42.array(z42.string().min(1)).min(1).max(14).optional().describe(
30868
+ "Input images for image editing or reference-conditioned generation (public http(s) URLs or data:image/*;base64 URIs). Only pass this for models whose bracketed note in this description lists imageUrls support; other models reject it."
30869
+ )
30348
30870
  });
30349
30871
  function errorResult13(text, model, errorCode2) {
30350
30872
  const data = { model, images: [], imageCount: 0, ...errorCode2 !== void 0 ? { errorCode: errorCode2 } : {} };
@@ -30358,7 +30880,7 @@ function hintOf(code) {
30358
30880
  return " Image generation is not configured on this platform yet; tell the user instead of retrying.";
30359
30881
  }
30360
30882
  if (code === "model_not_authorized") {
30361
- return " Use an image model name that this app is authorized for.";
30883
+ return ` The requested model is not in the authorized image model set of this app. Do not silently retry with another model or without "model" — tell the user, show the authorized image models from this tool's description, and let the user choose.`;
30362
30884
  }
30363
30885
  if (code === "imagegen_quota_exceeded") {
30364
30886
  return " The daily image quota for this account is exhausted; retry after the daily reset (UTC+8).";
@@ -30368,28 +30890,59 @@ function hintOf(code) {
30368
30890
  }
30369
30891
  return "";
30370
30892
  }
30893
+ function imageModelNote(m) {
30894
+ const label = m.displayName !== m.model ? `${m.model} (${m.displayName})` : m.model;
30895
+ const c = m.constraints;
30896
+ if (c === void 0) return label;
30897
+ const notes = [];
30898
+ if (!c.promptRequired) {
30899
+ notes.push("task-action model, NOT usable via this tool — pick another model");
30900
+ }
30901
+ if (!c.size) notes.push("fixed output size — do not pass size");
30902
+ if (c.imageInput !== null) {
30903
+ const forms = c.imageInput.urlOk && c.imageInput.b64Ok ? "URL or base64 data URI" : c.imageInput.urlOk ? "URL only" : "base64 data URI only";
30904
+ const count = c.imageInput.min > 0 ? `requires ${c.imageInput.min === c.imageInput.max ? String(c.imageInput.min) : `${c.imageInput.min}-${c.imageInput.max}`} input image(s)` : `accepts up to ${c.imageInput.max} reference image(s)`;
30905
+ notes.push(`${count} via imageUrls (${forms})`);
30906
+ }
30907
+ if (!c.seed) notes.push("no seed");
30908
+ if (!c.negativePrompt) notes.push("no negativePrompt");
30909
+ if (c.maxImages === 1) notes.push("single image per call (n=1)");
30910
+ return notes.length > 0 ? `${label} [${notes.join("; ")}]` : label;
30911
+ }
30912
+ function authorizedModelsNote(models) {
30913
+ if (models === void 0) {
30914
+ return ' Omit "model" to use the app default, or ask the app developer for authorized model names.';
30915
+ }
30916
+ if (models.length === 0) {
30917
+ return " The platform has no image model configured for this app yet — tell the user instead of retrying or guessing model names.";
30918
+ }
30919
+ const listed = models.map((m) => `- ${imageModelNote(m)}`).join("\n");
30920
+ return ' Authorized image models — the first is the default when "model" is omitted; bracketed notes are hard per-model constraints enforced by the platform (violating them fails the call):\n' + listed + '\nIf the user names a model outside this list (including a video model asked to make images), do not silently omit "model" or substitute another model — tell the user, show this list, and let them choose.';
30921
+ }
30371
30922
  function createImageGenTool(options) {
30372
30923
  const base = options.baseUrl.replace(/\/+$/, "");
30373
30924
  const fetchImpl = options.fetchImpl ?? fetch;
30374
30925
  const token = options.token;
30375
30926
  return {
30376
30927
  name: "ImageGen",
30377
- description: "Generates images from a text prompt via the tansr platform (ring-3 hosted capability; the platform calls the image provider and bills the app per generated image). Returns image URLs that stay valid for roughly 24 hours — surface them to the user promptly. Requires the app to have the imageGen platform capability enabled.",
30378
- shortDescription: "Generate images from a prompt via the tansr platform (billed per image). Args: model, prompt, negativePrompt?, size?, n?, seed?.",
30928
+ description: "Generates images from a text prompt via the tansr platform (ring-3 hosted capability; the platform calls the image provider and bills the app per generated image). Returns image URLs that stay valid for roughly 24 hours — surface them to the user promptly. Requires the app to have the imageGen platform capability enabled." + authorizedModelsNote(options.models),
30929
+ shortDescription: "Generate images from a prompt via the tansr platform (billed per image). Args: model?, prompt, negativePrompt?, size?, n?, seed?, imageUrls?.",
30379
30930
  inputSchema: ImageGenArgsSchema,
30380
30931
  isReadOnly: false,
30381
30932
  isConcurrencySafe: false,
30382
30933
  async execute(args, ctx) {
30934
+ const modelRef = args.model ?? "(default)";
30383
30935
  if (ctx.signal.aborted) {
30384
- return errorResult13("Tool execution was aborted.", args.model);
30936
+ return errorResult13("Tool execution was aborted.", modelRef);
30385
30937
  }
30386
30938
  const payload = {
30387
- model: args.model,
30939
+ ...args.model !== void 0 ? { model: args.model } : {},
30388
30940
  prompt: args.prompt,
30389
30941
  ...args.negativePrompt !== void 0 ? { negativePrompt: args.negativePrompt } : {},
30390
30942
  ...args.size !== void 0 ? { size: args.size } : {},
30391
30943
  ...args.n !== void 0 ? { n: args.n } : {},
30392
- ...args.seed !== void 0 ? { seed: args.seed } : {}
30944
+ ...args.seed !== void 0 ? { seed: args.seed } : {},
30945
+ ...args.imageUrls !== void 0 ? { imageUrls: args.imageUrls } : {}
30393
30946
  };
30394
30947
  let response;
30395
30948
  try {
@@ -30404,9 +30957,9 @@ function createImageGenTool(options) {
30404
30957
  signal: ctx.signal
30405
30958
  });
30406
30959
  } catch (err) {
30407
- if (ctx.signal.aborted) return errorResult13("Tool execution was aborted.", args.model);
30960
+ if (ctx.signal.aborted) return errorResult13("Tool execution was aborted.", modelRef);
30408
30961
  const message = err instanceof Error ? err.message : String(err);
30409
- return errorResult13(`Image generation request failed to reach the platform: ${message}`, args.model);
30962
+ return errorResult13(`Image generation request failed to reach the platform: ${message}`, modelRef);
30410
30963
  }
30411
30964
  let raw = null;
30412
30965
  try {
@@ -30418,17 +30971,18 @@ function createImageGenTool(options) {
30418
30971
  const envelope = raw ?? {};
30419
30972
  const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
30420
30973
  const message = typeof envelope.error?.message === "string" ? envelope.error.message : "request rejected";
30421
- return errorResult13(`Image generation failed (${code}): ${message}.${hintOf(code)}`, args.model, code);
30974
+ return errorResult13(`Image generation failed (${code}): ${message}.${hintOf(code)}`, modelRef, code);
30422
30975
  }
30423
30976
  const body = raw ?? {};
30424
30977
  const images = Array.isArray(body.images) ? body.images.map((item) => typeof item.url === "string" ? { url: item.url } : null).filter((item) => item !== null) : [];
30425
30978
  const imageCount = typeof body.imageCount === "number" ? body.imageCount : images.length;
30426
30979
  if (images.length === 0) {
30427
- return errorResult13("Image generation returned no image URL (unexpected platform response).", args.model);
30980
+ return errorResult13("Image generation returned no image URL (unexpected platform response).", modelRef);
30428
30981
  }
30429
- const data = { model: args.model, images, imageCount };
30982
+ const resolvedModel = typeof body.model === "string" && body.model !== "" ? body.model : modelRef;
30983
+ const data = { model: resolvedModel, images, imageCount };
30430
30984
  const lines = [
30431
- `Generated ${imageCount} image(s) with model ${args.model} (billed per image).`,
30985
+ `Generated ${imageCount} image(s) with model ${resolvedModel} (billed per image).`,
30432
30986
  "Image URLs (valid ~24h; surface or persist promptly):",
30433
30987
  ...images.map((img, i) => `${i + 1}. ${img.url}`)
30434
30988
  ];
@@ -30441,16 +30995,24 @@ function createImageGenTool(options) {
30441
30995
  import { z as z43 } from "zod";
30442
30996
  var VIDEOGEN_TOOL_MAX_DURATION = 30;
30443
30997
  var VideoGenArgsSchema = z43.object({
30444
- model: z43.string().min(1).describe(
30445
- "Video model reference from the app platform config (ask the app developer / see app docs for available names)."
30998
+ // S-G1(媒体池化顺位取用):Optional——缺席即不发 model 位,网关按授权集顺位
30999
+ // 第一生效( bundle platformModels.videoGen 首行恒同);点名限集内(工具描述自列)。
31000
+ model: z43.string().min(1).optional().describe(
31001
+ "Video model name from the authorized set listed in this tool description. Omit to use the default (the first authorized model) when the user did not ask for a specific model. Never omit or swap this field as a workaround when the user named a model that is not in the set — tell the user and let them choose first."
30446
31002
  ),
30447
31003
  prompt: z43.string().refine((s) => s.trim().length > 0, { message: "prompt must be a non-empty string" }).describe("Positive prompt describing the desired video content, motion, style and camera work."),
30448
31004
  negativePrompt: z43.string().min(1).max(500).optional().describe("Negative prompt: content to keep out of the video."),
30449
31005
  duration: z43.number().int().min(2).max(VIDEOGEN_TOOL_MAX_DURATION).optional().describe(
30450
- `Video duration in seconds (default 5, max ${VIDEOGEN_TOOL_MAX_DURATION}; billing is per second — longer costs more).`
31006
+ "Video duration in seconds (billing is per second — longer costs more). Models accept only the durations listed in their bracketed note; omit to use the model default."
30451
31007
  ),
30452
31008
  ratio: z43.string().regex(/^(adaptive|\d{1,2}:\d{1,2})$/, { message: "ratio must look like '16:9' (or 'adaptive' where supported)" }).optional().describe("Aspect ratio such as '16:9', '9:16' or '1:1' (defaults to the model's default)."),
30453
- seed: z43.number().int().min(0).max(2147483647).optional().describe("Random seed for relatively stable output (defaults to a random seed upstream).")
31009
+ seed: z43.number().int().min(0).max(2147483647).optional().describe("Random seed for relatively stable output (defaults to a random seed upstream)."),
31010
+ // 媒体二批富输入位(2026-09-01;网关 TwpVideogenRequestSchema 同帽 1..2):
31011
+ // i2v 首帧(seedance 支持首帧+尾帧两张);受理域随模型(描述逐模型注明,
31012
+ // i2v 专用行缺席即拒、纯文生行在场即拒)。
31013
+ imageUrls: z43.array(z43.string().min(1)).min(1).max(2).optional().describe(
31014
+ 'First-frame image(s) for image-to-video generation (public http(s) URL or data:image/*;base64 URI). REQUIRED for models marked "image-to-video ONLY" in this description (generate or obtain an image first, e.g. via the ImageGen tool); rejected by text-to-video-only models.'
31015
+ )
30454
31016
  });
30455
31017
  function errorResult14(text, model, errorCode2) {
30456
31018
  const data = {
@@ -30470,7 +31032,7 @@ function hintOf2(code) {
30470
31032
  return " Video generation is not configured on this platform yet; tell the user instead of retrying.";
30471
31033
  }
30472
31034
  if (code === "model_not_authorized") {
30473
- return " Use a video model name that this app is authorized for.";
31035
+ return ` The requested model is not in the authorized video model set of this app. Do not silently retry with another model or without "model" — tell the user, show the authorized video models from this tool's description, and let the user choose.`;
30474
31036
  }
30475
31037
  if (code === "videogen_quota_exceeded") {
30476
31038
  return " The daily video-seconds quota for this account is exhausted; retry after the daily reset (UTC+8).";
@@ -30478,30 +31040,68 @@ function hintOf2(code) {
30478
31040
  if (code === "insufficient_balance") {
30479
31041
  return " The app account balance is insufficient; the app developer needs to top up.";
30480
31042
  }
31043
+ if (code === "upstream_error") {
31044
+ return " The video provider failed to execute the task. If imageUrls was passed, the provider may be unable to fetch that image host — retry with an image from a different image model or another public URL.";
31045
+ }
30481
31046
  return "";
30482
31047
  }
31048
+ function videoModelNote(m) {
31049
+ const label = m.displayName !== m.model ? `${m.model} (${m.displayName})` : m.model;
31050
+ const c = m.constraints;
31051
+ if (c === void 0) return label;
31052
+ const notes = [];
31053
+ const d = c.durations;
31054
+ notes.push(
31055
+ d.kind === "set" ? `duration ${d.values.join("|")}s ONLY (default ${d.defaultSec}s)` : `duration ${d.min}-${d.max}s (default ${d.defaultSec}s)`
31056
+ );
31057
+ if (c.imageInput === null) {
31058
+ notes.push("text-to-video only — rejects imageUrls");
31059
+ } else {
31060
+ const forms = c.imageInput.urlOk && c.imageInput.b64Ok ? "URL or base64 data URI" : c.imageInput.urlOk ? "URL only" : "base64 data URI only";
31061
+ notes.push(
31062
+ c.imageInput.required ? `image-to-video ONLY — first-frame image via imageUrls is REQUIRED (${forms})` : `optional first-frame image via imageUrls (${forms})`
31063
+ );
31064
+ }
31065
+ if (c.ratio === null) notes.push("no ratio");
31066
+ else if (c.ratio !== void 0) notes.push(`ratio ${c.ratio.join("|")}`);
31067
+ if (!c.seed) notes.push("no seed");
31068
+ if (!c.negativePrompt) notes.push("no negativePrompt");
31069
+ return `${label} [${notes.join("; ")}]`;
31070
+ }
31071
+ function authorizedModelsNote2(models) {
31072
+ if (models === void 0) {
31073
+ return ' Omit "model" to use the app default, or ask the app developer for authorized model names.';
31074
+ }
31075
+ if (models.length === 0) {
31076
+ return " The platform has no video model configured for this app yet — tell the user instead of retrying or guessing model names.";
31077
+ }
31078
+ const listed = models.map((m) => `- ${videoModelNote(m)}`).join("\n");
31079
+ return ' Authorized video models — the first is the default when "model" is omitted; bracketed notes are hard per-model constraints enforced by the platform (violating them fails the call):\n' + listed + '\nIf the user names a model outside this list (including an image model asked to make videos), do not silently omit "model" or substitute another model — tell the user, show this list, and let them choose.';
31080
+ }
30483
31081
  function createVideoGenTool(options) {
30484
31082
  const base = options.baseUrl.replace(/\/+$/, "");
30485
31083
  const fetchImpl = options.fetchImpl ?? fetch;
30486
31084
  const token = options.token;
30487
31085
  return {
30488
31086
  name: "VideoGen",
30489
- description: "Generates a short video from a text prompt via the tansr platform (ring-3 hosted capability; the platform calls the video provider and bills the app per second of generated video). Generation is a long-running task (often minutes). Returns a video URL that stays valid for roughly 24 hours — surface it to the user promptly. Requires the app to have the videoGen platform capability enabled.",
30490
- shortDescription: "Generate a video from a prompt via the tansr platform (billed per second). Args: model, prompt, negativePrompt?, duration?, ratio?, seed?.",
31087
+ description: "Generates a short video from a text prompt via the tansr platform (ring-3 hosted capability; the platform calls the video provider and bills the app per second of generated video). Generation is a long-running task (often minutes). Returns a video URL that stays valid for roughly 24 hours — surface it to the user promptly. Requires the app to have the videoGen platform capability enabled." + authorizedModelsNote2(options.models),
31088
+ shortDescription: "Generate a video from a prompt via the tansr platform (billed per second). Args: model?, prompt, negativePrompt?, duration?, ratio?, seed?, imageUrls?.",
30491
31089
  inputSchema: VideoGenArgsSchema,
30492
31090
  isReadOnly: false,
30493
31091
  isConcurrencySafe: false,
30494
31092
  async execute(args, ctx) {
31093
+ const modelRef = args.model ?? "(default)";
30495
31094
  if (ctx.signal.aborted) {
30496
- return errorResult14("Tool execution was aborted.", args.model);
31095
+ return errorResult14("Tool execution was aborted.", modelRef);
30497
31096
  }
30498
31097
  const payload = {
30499
- model: args.model,
31098
+ ...args.model !== void 0 ? { model: args.model } : {},
30500
31099
  prompt: args.prompt,
30501
31100
  ...args.negativePrompt !== void 0 ? { negativePrompt: args.negativePrompt } : {},
30502
31101
  ...args.duration !== void 0 ? { duration: args.duration } : {},
30503
31102
  ...args.ratio !== void 0 ? { ratio: args.ratio } : {},
30504
- ...args.seed !== void 0 ? { seed: args.seed } : {}
31103
+ ...args.seed !== void 0 ? { seed: args.seed } : {},
31104
+ ...args.imageUrls !== void 0 ? { imageUrls: args.imageUrls } : {}
30505
31105
  };
30506
31106
  let response;
30507
31107
  try {
@@ -30516,9 +31116,9 @@ function createVideoGenTool(options) {
30516
31116
  signal: ctx.signal
30517
31117
  });
30518
31118
  } catch (err) {
30519
- if (ctx.signal.aborted) return errorResult14("Tool execution was aborted.", args.model);
31119
+ if (ctx.signal.aborted) return errorResult14("Tool execution was aborted.", modelRef);
30520
31120
  const message = err instanceof Error ? err.message : String(err);
30521
- return errorResult14(`Video generation request failed to reach the platform: ${message}`, args.model);
31121
+ return errorResult14(`Video generation request failed to reach the platform: ${message}`, modelRef);
30522
31122
  }
30523
31123
  let raw = null;
30524
31124
  try {
@@ -30530,18 +31130,19 @@ function createVideoGenTool(options) {
30530
31130
  const envelope = raw ?? {};
30531
31131
  const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
30532
31132
  const message = typeof envelope.error?.message === "string" ? envelope.error.message : "request rejected";
30533
- return errorResult14(`Video generation failed (${code}): ${message}.${hintOf2(code)}`, args.model, code);
31133
+ return errorResult14(`Video generation failed (${code}): ${message}.${hintOf2(code)}`, modelRef, code);
30534
31134
  }
30535
31135
  const body = raw ?? {};
30536
31136
  const videos = Array.isArray(body.videos) ? body.videos.map((item) => typeof item.url === "string" ? { url: item.url } : null).filter((item) => item !== null) : [];
30537
31137
  if (videos.length === 0) {
30538
- return errorResult14("Video generation returned no video URL (unexpected platform response).", args.model);
31138
+ return errorResult14("Video generation returned no video URL (unexpected platform response).", modelRef);
30539
31139
  }
30540
31140
  const videoCount = typeof body.videoCount === "number" ? body.videoCount : videos.length;
30541
31141
  const billedSeconds = typeof body.billedSeconds === "number" ? body.billedSeconds : 0;
30542
- const data = { model: args.model, videos, videoCount, billedSeconds };
31142
+ const resolvedModel = typeof body.model === "string" && body.model !== "" ? body.model : modelRef;
31143
+ const data = { model: resolvedModel, videos, videoCount, billedSeconds };
30543
31144
  const lines = [
30544
- `Generated ${videoCount} video(s) with model ${args.model} (${billedSeconds}s billed, per-second pricing).`,
31145
+ `Generated ${videoCount} video(s) with model ${resolvedModel} (${billedSeconds}s billed, per-second pricing).`,
30545
31146
  "Video URLs (valid ~24h; surface or persist promptly):",
30546
31147
  ...videos.map((v, i) => `${i + 1}. ${v.url}`)
30547
31148
  ];
@@ -30550,24 +31151,8 @@ function createVideoGenTool(options) {
30550
31151
  };
30551
31152
  }
30552
31153
 
30553
- // src/platform/websearch-tool.ts
30554
- import { z as z44 } from "zod";
30555
- var WEBSEARCH_TOOL_MAX_RESULTS = 20;
30556
- var WebSearchArgsSchema2 = z44.object({
30557
- query: z44.string().max(1024).refine((s) => s.trim().length > 0, { message: "query must be a non-empty string" }).describe("Search query text (the platform never logs or stores it)."),
30558
- max_results: z44.number().int().min(1).max(WEBSEARCH_TOOL_MAX_RESULTS).optional().describe(
30559
- `Desired number of results (default 8, max ${WEBSEARCH_TOOL_MAX_RESULTS}; the provider may cap it lower). Billing is per call, not per result.`
30560
- )
30561
- });
30562
- function errorResult15(text, query2, errorCode2) {
30563
- const data = {
30564
- query: query2,
30565
- results: [],
30566
- resultCount: 0,
30567
- ...errorCode2 !== void 0 ? { errorCode: errorCode2 } : {}
30568
- };
30569
- return { content: [{ t: "text", text }], isError: true, data };
30570
- }
31154
+ // src/platform/websearch-provider.ts
31155
+ var PLATFORM_SEARCH_PROVIDER_NAME = "tansr-platform";
30571
31156
  function hintOf3(code) {
30572
31157
  if (code === "forbidden") {
30573
31158
  return " The app platform capability webSearch is disabled; the app developer can enable it in console → app → capabilities.";
@@ -30589,25 +31174,13 @@ function hintOf3(code) {
30589
31174
  }
30590
31175
  return "";
30591
31176
  }
30592
- function createWebSearchTool2(options) {
31177
+ function createPlatformSearchProvider(options) {
30593
31178
  const base = options.baseUrl.replace(/\/+$/, "");
30594
31179
  const fetchImpl = options.fetchImpl ?? fetch;
30595
31180
  const token = options.token;
30596
31181
  return {
30597
- name: "PlatformWebSearch",
30598
- description: "Searches the web via the tansr platform (ring-3 hosted capability; the platform calls a managed search provider pool and bills the app per call). Returns a numbered list of results with title, URL and snippet. Requires the app to have the webSearch platform capability enabled.",
30599
- shortDescription: "Search the web via the tansr platform (billed per call). Args: query, max_results?.",
30600
- inputSchema: WebSearchArgsSchema2,
30601
- isReadOnly: false,
30602
- isConcurrencySafe: false,
30603
- async execute(args, ctx) {
30604
- if (ctx.signal.aborted) {
30605
- return errorResult15("Tool execution was aborted.", args.query);
30606
- }
30607
- const payload = {
30608
- query: args.query,
30609
- ...args.max_results !== void 0 ? { max_results: args.max_results } : {}
30610
- };
31182
+ name: PLATFORM_SEARCH_PROVIDER_NAME,
31183
+ async search(query2, context) {
30611
31184
  let response;
30612
31185
  try {
30613
31186
  response = await fetchImpl(`${base}/t1/websearch`, {
@@ -30617,13 +31190,15 @@ function createWebSearchTool2(options) {
30617
31190
  accept: "application/json",
30618
31191
  [HEADER_APP_TOKEN]: token
30619
31192
  },
30620
- body: JSON.stringify(payload),
30621
- signal: ctx.signal
31193
+ // wire 体 snake_case(CLI 内核 HTTP 后端字节同构的既有特例);
31194
+ // maxResults 已被 kernel 工具层夹紧(缺省 8 / 上限 20,与网关帽同值)。
31195
+ body: JSON.stringify({ query: query2.query, max_results: query2.maxResults }),
31196
+ signal: context.signal
30622
31197
  });
30623
31198
  } catch (err) {
30624
- if (ctx.signal.aborted) return errorResult15("Tool execution was aborted.", args.query);
31199
+ if (context.signal.aborted) throw err;
30625
31200
  const message = err instanceof Error ? err.message : String(err);
30626
- return errorResult15(`Web search request failed to reach the platform: ${message}`, args.query);
31201
+ throw new Error(`web search request failed to reach the tansr platform: ${message}`);
30627
31202
  }
30628
31203
  let raw = null;
30629
31204
  try {
@@ -30635,13 +31210,13 @@ function createWebSearchTool2(options) {
30635
31210
  const envelope = raw ?? {};
30636
31211
  const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
30637
31212
  const message = typeof envelope.error?.message === "string" ? envelope.error.message : "request rejected";
30638
- return errorResult15(`Web search failed (${code}): ${message}.${hintOf3(code)}`, args.query, code);
31213
+ throw new Error(`platform web search failed (${code}): ${message}.${hintOf3(code)}`);
30639
31214
  }
30640
31215
  const body = raw ?? {};
30641
31216
  if (!Array.isArray(body.results)) {
30642
- return errorResult15("Web search returned an unexpected platform response (no results array).", args.query);
31217
+ throw new Error("platform web search returned an unexpected response (no results array).");
30643
31218
  }
30644
- const results = body.results.map((item) => {
31219
+ return body.results.map((item) => {
30645
31220
  const row = item ?? {};
30646
31221
  if (typeof row.title !== "string" || typeof row.url !== "string") return null;
30647
31222
  return {
@@ -30650,19 +31225,6 @@ function createWebSearchTool2(options) {
30650
31225
  snippet: typeof row.snippet === "string" ? row.snippet : ""
30651
31226
  };
30652
31227
  }).filter((item) => item !== null);
30653
- const data = { query: args.query, results, resultCount: results.length };
30654
- if (results.length === 0) {
30655
- return { content: [{ t: "text", text: "Web search returned no results for this query (the call was still billed)." }], data };
30656
- }
30657
- const lines = [
30658
- `Web search returned ${results.length} result(s) (billed per call):`,
30659
- ...results.map((item, i) => {
30660
- const block = [`${i + 1}. ${item.title}`, ` ${item.url}`];
30661
- if (item.snippet !== "") block.push(` ${item.snippet}`);
30662
- return block.join("\n");
30663
- })
30664
- ];
30665
- return { content: [{ t: "text", text: lines.join("\n") }], data };
30666
31228
  }
30667
31229
  };
30668
31230
  }
@@ -30682,7 +31244,7 @@ var BUILTIN_ORDER = [
30682
31244
  "webSearch",
30683
31245
  "http"
30684
31246
  ];
30685
- var PLATFORM_NAMES = ["imageGen", "videoGen", "webSearch"];
31247
+ var PLATFORM_NAMES = ["imageGen", "videoGen"];
30686
31248
  function toToolDef(tool) {
30687
31249
  const converted = zodToJsonSchema(tool.inputSchema);
30688
31250
  const { $schema: _dropped, ...inputSchema } = converted;
@@ -30718,6 +31280,12 @@ function resolvePlatformSelection(selection, capabilities) {
30718
31280
  if (selection === void 0) return [];
30719
31281
  const requested = /* @__PURE__ */ new Set();
30720
31282
  for (const name of selection) {
31283
+ if (name === "webSearch") {
31284
+ throw new TansrSdkError(
31285
+ "invalid_options",
31286
+ "Platform capability 'webSearch' moved: web search is now the builtin 'WebSearch' tool backed by the tansr platform channel (BYO endpoints are not supported in the SDK). Move 'webSearch' from tools.platform to tools.builtin (or omit tools to get it by default when the capability is enabled)."
31287
+ );
31288
+ }
30721
31289
  if (!PLATFORM_NAMES.includes(name)) {
30722
31290
  throw new TansrSdkError(
30723
31291
  "invalid_options",
@@ -30785,7 +31353,7 @@ function buildBuiltinTool(name, materials) {
30785
31353
  case "webFetch":
30786
31354
  return createWebFetchTool();
30787
31355
  case "webSearch":
30788
- return createWebSearchTool({ provider: createHttpSearchProvider() });
31356
+ return createWebSearchTool({ provider: materials.searchProvider });
30789
31357
  case "http":
30790
31358
  return createHttpTool();
30791
31359
  }
@@ -30793,9 +31361,37 @@ function buildBuiltinTool(name, materials) {
30793
31361
  function buildSdkToolSet(options = {}) {
30794
31362
  const capabilities = options.capabilities ?? DEFAULT_APP_CAPABILITIES;
30795
31363
  const selection = options.tools ?? {};
30796
- const builtinNames = resolveBuiltinSelection(selection.builtin, capabilities);
31364
+ let builtinNames = resolveBuiltinSelection(selection.builtin, capabilities);
30797
31365
  const platformNames = resolvePlatformSelection(selection.platform, capabilities);
30798
31366
  validateCustomSelection(selection.custom, capabilities);
31367
+ let webSearchProvider = null;
31368
+ if (builtinNames.includes("webSearch")) {
31369
+ const pc = options.platformContext;
31370
+ const explicit = selection.builtin !== void 0;
31371
+ if (pc === void 0) {
31372
+ if (explicit) {
31373
+ throw new TansrSdkError(
31374
+ "invalid_options",
31375
+ "tools.builtin ['webSearch'] requires the platform token tier: the SDK web search always rides the tansr platform channel (billed per call; local BYO endpoints are not supported). Create the session/query with { token, baseUrl }, or remove 'webSearch' from tools.builtin."
31376
+ );
31377
+ }
31378
+ builtinNames = builtinNames.filter((name) => name !== "webSearch");
31379
+ } else if (!capabilities.platform.webSearch) {
31380
+ if (explicit) {
31381
+ throw new TansrSdkError(
31382
+ "capability_disabled",
31383
+ `App platform capability 'webSearch' is disabled; ${CONSOLE_HINT}, or remove 'webSearch' from tools.builtin.`
31384
+ );
31385
+ }
31386
+ builtinNames = builtinNames.filter((name) => name !== "webSearch");
31387
+ } else {
31388
+ webSearchProvider = createPlatformSearchProvider({
31389
+ baseUrl: pc.baseUrl,
31390
+ token: pc.token,
31391
+ ...pc.fetchImpl !== void 0 ? { fetchImpl: pc.fetchImpl } : {}
31392
+ });
31393
+ }
31394
+ }
30799
31395
  const platformTools = [];
30800
31396
  for (const name of platformNames) {
30801
31397
  const pc = options.platformContext;
@@ -30811,14 +31407,14 @@ function buildSdkToolSet(options = {}) {
30811
31407
  ...pc.fetchImpl !== void 0 ? { fetchImpl: pc.fetchImpl } : {}
30812
31408
  };
30813
31409
  platformTools.push(
30814
- name === "imageGen" ? createImageGenTool(materials) : name === "videoGen" ? createVideoGenTool(materials) : createWebSearchTool2(materials)
31410
+ name === "imageGen" ? createImageGenTool({ ...materials, ...pc.platformModels !== void 0 ? { models: pc.platformModels.imageGen } : {} }) : createVideoGenTool({ ...materials, ...pc.platformModels !== void 0 ? { models: pc.platformModels.videoGen } : {} })
30815
31411
  );
30816
31412
  }
30817
31413
  const store = options.store ?? new TodoStore();
30818
31414
  const channel = options.promptChannel ?? new UnavailableChannel();
30819
31415
  const custom = selection.custom ?? [];
30820
31416
  const tools = [
30821
- ...builtinNames.map((name) => buildBuiltinTool(name, { store, channel })),
31417
+ ...builtinNames.map((name) => buildBuiltinTool(name, { store, channel, searchProvider: webSearchProvider })),
30822
31418
  ...platformTools,
30823
31419
  ...custom
30824
31420
  ];
@@ -31001,11 +31597,11 @@ async function assembleTooling(options) {
31001
31597
  var TANSR_PROVIDER_ID = "tansr";
31002
31598
  var APP_TOKEN_PLACEHOLDER_ENV = "TANSR_SDK_APP_TOKEN_TIER";
31003
31599
  var APP_TOKEN_PLACEHOLDER_VALUE = "app-token-tier";
31004
- function isRecord7(value) {
31600
+ function isRecord8(value) {
31005
31601
  return typeof value === "object" && value !== null && !Array.isArray(value);
31006
31602
  }
31007
31603
  function parseModel(raw) {
31008
- if (!isRecord7(raw)) return null;
31604
+ if (!isRecord8(raw)) return null;
31009
31605
  if (typeof raw.handle !== "string" || raw.handle === "" || typeof raw.modelId !== "string" || raw.modelId === "" || typeof raw.protocol !== "string") {
31010
31606
  return null;
31011
31607
  }
@@ -31013,25 +31609,103 @@ function parseModel(raw) {
31013
31609
  handle: raw.handle,
31014
31610
  modelId: raw.modelId,
31015
31611
  displayName: typeof raw.displayName === "string" ? raw.displayName : raw.handle,
31612
+ // 模型呈现面二波(④)加法解析:缺键/坏形状 = null(旧 bundle 回落,恒不猜)
31613
+ manufacturer: typeof raw.manufacturer === "string" && raw.manufacturer !== "" ? raw.manufacturer : null,
31614
+ family: typeof raw.family === "string" && raw.family !== "" ? raw.family : null,
31016
31615
  protocol: raw.protocol,
31017
- capabilities: isRecord7(raw.capabilities) ? raw.capabilities : {},
31616
+ capabilities: isRecord8(raw.capabilities) ? raw.capabilities : {},
31018
31617
  contextWindow: typeof raw.contextWindow === "number" ? raw.contextWindow : null
31019
31618
  };
31020
31619
  }
31620
+ function parseMediaImageInput(raw, flagKey) {
31621
+ if (raw === null) return null;
31622
+ if (!isRecord8(raw)) return false;
31623
+ const flag = raw[flagKey];
31624
+ if (flagKey === "min" ? typeof flag !== "number" : typeof flag !== "boolean") return false;
31625
+ if (typeof raw.max !== "number" || typeof raw.urlOk !== "boolean" || typeof raw.b64Ok !== "boolean") return false;
31626
+ return { flag, max: raw.max, urlOk: raw.urlOk, b64Ok: raw.b64Ok };
31627
+ }
31628
+ function parseImageConstraints(raw) {
31629
+ if (!isRecord8(raw)) return void 0;
31630
+ if (typeof raw.size !== "boolean" || typeof raw.seed !== "boolean" || typeof raw.negativePrompt !== "boolean" || typeof raw.maxImages !== "number" || typeof raw.promptRequired !== "boolean") {
31631
+ return void 0;
31632
+ }
31633
+ const input = parseMediaImageInput(raw.imageInput, "min");
31634
+ if (input === false) return void 0;
31635
+ return {
31636
+ size: raw.size,
31637
+ seed: raw.seed,
31638
+ negativePrompt: raw.negativePrompt,
31639
+ maxImages: raw.maxImages,
31640
+ imageInput: input === null ? null : { min: input.flag, max: input.max, urlOk: input.urlOk, b64Ok: input.b64Ok },
31641
+ promptRequired: raw.promptRequired,
31642
+ ...raw.actionRequired === true ? { actionRequired: true } : {}
31643
+ };
31644
+ }
31645
+ function parseVideoConstraints(raw) {
31646
+ if (!isRecord8(raw)) return void 0;
31647
+ const d = raw.durations;
31648
+ let durations = null;
31649
+ if (isRecord8(d) && typeof d.defaultSec === "number") {
31650
+ if (d.kind === "set" && Array.isArray(d.values) && d.values.every((v) => typeof v === "number")) {
31651
+ durations = { kind: "set", values: [...d.values], defaultSec: d.defaultSec };
31652
+ } else if (d.kind === "range" && typeof d.min === "number" && typeof d.max === "number") {
31653
+ durations = { kind: "range", min: d.min, max: d.max, defaultSec: d.defaultSec };
31654
+ }
31655
+ }
31656
+ if (durations === null) return void 0;
31657
+ if (typeof raw.negativePrompt !== "boolean" || typeof raw.seed !== "boolean" || typeof raw.audioInput !== "boolean") {
31658
+ return void 0;
31659
+ }
31660
+ const input = parseMediaImageInput(raw.imageInput, "required");
31661
+ if (input === false) return void 0;
31662
+ let ratio;
31663
+ if (raw.ratio === void 0) ratio = void 0;
31664
+ else if (raw.ratio === null) ratio = null;
31665
+ else if (Array.isArray(raw.ratio) && raw.ratio.every((r) => typeof r === "string")) ratio = [...raw.ratio];
31666
+ else return void 0;
31667
+ return {
31668
+ durations,
31669
+ ...ratio !== void 0 ? { ratio } : {},
31670
+ negativePrompt: raw.negativePrompt,
31671
+ seed: raw.seed,
31672
+ imageInput: input === null ? null : { required: input.flag, max: input.max, urlOk: input.urlOk, b64Ok: input.b64Ok },
31673
+ audioInput: raw.audioInput
31674
+ };
31675
+ }
31676
+ function parseMediaModels(raw, parseConstraints) {
31677
+ if (!Array.isArray(raw)) return [];
31678
+ const out = [];
31679
+ for (const item of raw) {
31680
+ if (!isRecord8(item) || typeof item.model !== "string" || item.model === "") continue;
31681
+ const constraints = item.constraints === void 0 ? void 0 : parseConstraints(item.constraints);
31682
+ out.push({
31683
+ model: item.model,
31684
+ displayName: typeof item.displayName === "string" && item.displayName !== "" ? item.displayName : item.model,
31685
+ ...constraints !== void 0 ? { constraints } : {}
31686
+ });
31687
+ }
31688
+ return out;
31689
+ }
31021
31690
  function parseAppBundle(raw) {
31022
- if (!isRecord7(raw)) return null;
31691
+ if (!isRecord8(raw)) return null;
31023
31692
  const models = Array.isArray(raw.models) ? raw.models.map(parseModel).filter((m) => m !== null) : [];
31024
31693
  const aliases = {};
31025
- if (isRecord7(raw.aliases)) {
31694
+ if (isRecord8(raw.aliases)) {
31026
31695
  for (const [key2, value] of Object.entries(raw.aliases)) {
31027
31696
  if (typeof value === "string" && value !== "") aliases[key2] = value;
31028
31697
  }
31029
31698
  }
31030
31699
  const parsedCaps = AppCapabilitiesSchema.safeParse(raw.capabilities);
31700
+ const mediaRaw = isRecord8(raw.platformModels) ? raw.platformModels : {};
31031
31701
  return {
31032
31702
  models,
31033
31703
  aliases,
31034
- capabilities: parsedCaps.success ? parsedCaps.data : DEFAULT_APP_CAPABILITIES
31704
+ capabilities: parsedCaps.success ? parsedCaps.data : DEFAULT_APP_CAPABILITIES,
31705
+ platformModels: {
31706
+ imageGen: parseMediaModels(mediaRaw.imageGen, parseImageConstraints),
31707
+ videoGen: parseMediaModels(mediaRaw.videoGen, parseVideoConstraints)
31708
+ }
31035
31709
  };
31036
31710
  }
31037
31711
  function sanitizeModelCapabilities(capabilities, contextWindow) {
@@ -31172,10 +31846,13 @@ async function assemblePlatformModel(options) {
31172
31846
  const registry = new ProviderRegistry(appBundleRegistryConfig(bundle, base), {
31173
31847
  // 占位哑值:满足装配期 readApiKey 非空校验;真实鉴权恒由 fetch 包装注入
31174
31848
  env: { [APP_TOKEN_PLACEHOLDER_ENV]: APP_TOKEN_PLACEHOLDER_VALUE },
31175
- fetchImpl: createAppTokenFetch(options.token, options.fetchImpl)
31849
+ fetchImpl: createAppTokenFetch(options.token, options.fetchImpl),
31850
+ // 拍板⑤:平台会话 id 供给经既有 TwpClientOptions.sessionId 缝出线——
31851
+ // registry 为会话私有(每次装配新建),setModel/Task 子代理同缝同值
31852
+ ...options.sessionId !== void 0 ? { twp: { sessionId: options.sessionId } } : {}
31176
31853
  });
31177
31854
  const built = buildClientFromRegistry(registry, options.model ?? "main", options.onProviderSelected);
31178
- return { ...built, registry, capabilities: bundle.capabilities };
31855
+ return { ...built, registry, capabilities: bundle.capabilities, platformModels: bundle.platformModels };
31179
31856
  }
31180
31857
 
31181
31858
  // src/task.ts
@@ -31264,7 +31941,9 @@ async function* consumeManaged(options) {
31264
31941
  platformContext = {
31265
31942
  baseUrl: options.baseUrl,
31266
31943
  token: options.token,
31267
- ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
31944
+ ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
31945
+ // S-G1:bundle 授权媒体集随装配注入(媒体工具描述自列;集成方零手配)。
31946
+ platformModels: platform.platformModels
31268
31947
  };
31269
31948
  } else {
31270
31949
  const managed = await assembleManagedModel({
@@ -31362,6 +32041,60 @@ async function* consumeHandle(handle) {
31362
32041
 
31363
32042
  // src/session.ts
31364
32043
  import { randomUUID as randomUUID11 } from "node:crypto";
32044
+
32045
+ // src/sessions/pairing.ts
32046
+ var SYNTHETIC_TOOL_RESULT_TEXT = "Tool execution was interrupted before its result was persisted; this placeholder was synthesized while resuming the session.";
32047
+ function toolCallIds(message) {
32048
+ return message.blocks.flatMap((b) => b.t === "tool_call" ? [b.id] : []);
32049
+ }
32050
+ function toolResultIds(message) {
32051
+ return message.blocks.flatMap((b) => b.t === "tool_result" ? [b.callId] : []);
32052
+ }
32053
+ function repairHistoryPairing(messages) {
32054
+ const open4 = /* @__PURE__ */ new Set();
32055
+ let cleanEnd = 0;
32056
+ for (let i = 0; i < messages.length; i++) {
32057
+ const message = messages[i];
32058
+ if (message.role === "assistant") {
32059
+ for (const id of toolCallIds(message)) open4.add(id);
32060
+ } else {
32061
+ for (const callId of toolResultIds(message)) {
32062
+ if (!open4.has(callId)) {
32063
+ return {
32064
+ messages: messages.slice(0, cleanEnd).map((m) => structuredClone(m)),
32065
+ synthesizedResults: 0,
32066
+ droppedMessages: messages.length - cleanEnd
32067
+ };
32068
+ }
32069
+ open4.delete(callId);
32070
+ }
32071
+ }
32072
+ if (open4.size === 0) cleanEnd = i + 1;
32073
+ }
32074
+ if (open4.size === 0) {
32075
+ return {
32076
+ messages: messages.map((m) => structuredClone(m)),
32077
+ synthesizedResults: 0,
32078
+ droppedMessages: 0
32079
+ };
32080
+ }
32081
+ const synthetic = {
32082
+ role: "user",
32083
+ blocks: [...open4].map((callId) => ({
32084
+ t: "tool_result",
32085
+ callId,
32086
+ content: [{ t: "text", text: SYNTHETIC_TOOL_RESULT_TEXT }],
32087
+ isError: true
32088
+ }))
32089
+ };
32090
+ return {
32091
+ messages: [...messages.map((m) => structuredClone(m)), synthetic],
32092
+ synthesizedResults: open4.size,
32093
+ droppedMessages: 0
32094
+ };
32095
+ }
32096
+
32097
+ // src/session.ts
31365
32098
  function createEventQueue() {
31366
32099
  const buffered = [];
31367
32100
  let ended = false;
@@ -31637,7 +32370,21 @@ var AgentSession = class {
31637
32370
  };
31638
32371
  async function createSession(options = {}) {
31639
32372
  const cwd = options.cwd ?? process.cwd();
31640
- const sessionId = options.sessionId ?? randomUUID11();
32373
+ if (options.resume !== void 0) {
32374
+ if (options.initialMessages !== void 0) {
32375
+ throw new TansrSdkError(
32376
+ "invalid_options",
32377
+ 'createSession: "resume" and "initialMessages" are mutually exclusive; "resume" loads the history from the store ("initialMessages" is the low-level re-feed escape hatch).'
32378
+ );
32379
+ }
32380
+ if (options.sessionId !== void 0 && options.sessionId !== options.resume.sessionId) {
32381
+ throw new TansrSdkError(
32382
+ "invalid_options",
32383
+ 'createSession: "sessionId" conflicts with "resume.sessionId"; pass one of them (they must match).'
32384
+ );
32385
+ }
32386
+ }
32387
+ const sessionId = options.sessionId ?? options.resume?.sessionId ?? randomUUID11();
31641
32388
  const injected = options.client !== void 0;
31642
32389
  const tokenTier = options.token !== void 0;
31643
32390
  if (injected && typeof options.model === "string") {
@@ -31653,6 +32400,18 @@ async function createSession(options = {}) {
31653
32400
  );
31654
32401
  }
31655
32402
  validateTokenTierOptions("createSession", options);
32403
+ let initialMessages = options.initialMessages ?? [];
32404
+ if (options.resume !== void 0) {
32405
+ const record = await options.resume.store.get(options.resume.sessionId);
32406
+ if (record === null) {
32407
+ throw new TansrSdkError(
32408
+ "session_not_found",
32409
+ `createSession: session "${options.resume.sessionId}" was not found in the given store; list() the store or create a fresh session instead.`
32410
+ );
32411
+ }
32412
+ initialMessages = repairHistoryPairing(record.messages).messages;
32413
+ }
32414
+ const platformSessionId = tokenTier ? ulid() : void 0;
31656
32415
  let sessionRef = null;
31657
32416
  const emitBody = (body, source) => {
31658
32417
  sessionRef?.pushBody(body, source);
@@ -31675,6 +32434,9 @@ async function createSession(options = {}) {
31675
32434
  baseUrl: options.baseUrl,
31676
32435
  ...typeof options.model === "string" ? { model: options.model } : {},
31677
32436
  ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
32437
+ // 拍板⑤:本会话全部 exchange(含 setModel 热切换与 Task 子代理,同
32438
+ // registry 同缝)恒携同一枚平台会话 ULID——会话维归因/亲和自此在场
32439
+ ...platformSessionId !== void 0 ? { sessionId: () => platformSessionId } : {},
31678
32440
  onProviderSelected: (selection) => {
31679
32441
  const body = providerSwitchedBody(selection);
31680
32442
  if (body !== null) emitBody(body, "provider");
@@ -31691,7 +32453,9 @@ async function createSession(options = {}) {
31691
32453
  platformContext = {
31692
32454
  baseUrl: options.baseUrl,
31693
32455
  token: options.token,
31694
- ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
32456
+ ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
32457
+ // S-G1:bundle 授权媒体集随装配注入(媒体工具描述自列;集成方零手配)。
32458
+ platformModels: assembled.platformModels
31695
32459
  };
31696
32460
  } else {
31697
32461
  const assembled = await assembleManagedModel({
@@ -31747,6 +32511,26 @@ async function createSession(options = {}) {
31747
32511
  }
31748
32512
  );
31749
32513
  }
32514
+ const store = options.store;
32515
+ if (store !== void 0) {
32516
+ await store.create({
32517
+ sessionId,
32518
+ ...platformSessionId !== void 0 ? { platformSessionId } : {}
32519
+ });
32520
+ }
32521
+ const userCommit = options.onHistoryCommit;
32522
+ const onHistoryCommit = store === void 0 ? userCommit : async (history, meta) => {
32523
+ let storeError;
32524
+ try {
32525
+ await store.commit(sessionId, history, meta);
32526
+ } catch (err) {
32527
+ storeError = err;
32528
+ }
32529
+ await userCommit?.(history, meta);
32530
+ if (storeError !== void 0) {
32531
+ throw storeError instanceof Error ? storeError : new Error(String(storeError));
32532
+ }
32533
+ };
31750
32534
  const session = new AgentSession({
31751
32535
  binding,
31752
32536
  executor: tooling.executor,
@@ -31757,9 +32541,9 @@ async function createSession(options = {}) {
31757
32541
  maxTokens,
31758
32542
  maxTurnsPerQuery: options.maxTurnsPerQuery ?? DEFAULT_MAX_TURNS,
31759
32543
  compaction: options.compaction === false ? void 0 : options.compaction ?? {},
31760
- initialMessages: options.initialMessages ?? [],
32544
+ initialMessages,
31761
32545
  registry,
31762
- onHistoryCommit: options.onHistoryCommit,
32546
+ onHistoryCommit,
31763
32547
  ...tooling.onEnded !== void 0 ? { onEnded: tooling.onEnded } : {}
31764
32548
  });
31765
32549
  sessionRef = session;
@@ -31770,6 +32554,272 @@ async function createSession(options = {}) {
31770
32554
  return session;
31771
32555
  }
31772
32556
 
32557
+ // src/sessions/file-store.ts
32558
+ import { randomUUID as randomUUID12 } from "node:crypto";
32559
+ import { mkdir as mkdir3, readFile as readFile5, readdir, rename as rename2, rm as rm3 } from "node:fs/promises";
32560
+ import path22 from "node:path";
32561
+ var MESSAGE_KINDS2 = /* @__PURE__ */ new Set([
32562
+ "user_prompt",
32563
+ "assistant_message",
32564
+ "tool_result"
32565
+ ]);
32566
+ function journalKindOf(message) {
32567
+ if (message.role === "assistant") return "assistant_message";
32568
+ return message.blocks.some((b) => b.t === "tool_result") ? "tool_result" : "user_prompt";
32569
+ }
32570
+ var TITLE_MAX_CHARS = 64;
32571
+ function sessionTitleOf(messages) {
32572
+ for (const message of messages) {
32573
+ if (message.role !== "user") continue;
32574
+ const text = message.blocks.flatMap((b) => b.t === "text" ? [b.text] : []).join(" ").replace(/\s+/g, " ").trim();
32575
+ if (text === "") continue;
32576
+ return text.length > TITLE_MAX_CHARS ? `${text.slice(0, TITLE_MAX_CHARS)}…` : text;
32577
+ }
32578
+ return void 0;
32579
+ }
32580
+ async function readRawMeta(sessionDir) {
32581
+ try {
32582
+ const parsed = JSON.parse(await readFile5(metaFilePath(sessionDir), "utf8"));
32583
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
32584
+ return parsed;
32585
+ } catch {
32586
+ return null;
32587
+ }
32588
+ }
32589
+ function toRecordMeta(raw) {
32590
+ const createdAt = typeof raw["createdAt"] === "string" ? raw["createdAt"] : "";
32591
+ const updatedAt = typeof raw["updatedAt"] === "string" ? raw["updatedAt"] : createdAt;
32592
+ const record = {
32593
+ sessionId: typeof raw["sessionId"] === "string" ? raw["sessionId"] : "",
32594
+ createdAt,
32595
+ updatedAt
32596
+ };
32597
+ if (typeof raw["title"] === "string") record.title = raw["title"];
32598
+ if (typeof raw["platformSessionId"] === "string" && raw["platformSessionId"] !== "") {
32599
+ record.platformSessionId = raw["platformSessionId"];
32600
+ }
32601
+ const extra = raw["meta"];
32602
+ if (typeof extra === "object" && extra !== null && !Array.isArray(extra)) {
32603
+ record.meta = extra;
32604
+ }
32605
+ return record;
32606
+ }
32607
+ function corrupted(sessionId, detail, cause) {
32608
+ return new TansrSdkError(
32609
+ "session_store_corrupted",
32610
+ `Session store for "${sessionId}" is corrupted: ${detail}. Refusing to resume from damaged history (delete the session directory to start over).`,
32611
+ cause !== void 0 ? { cause } : void 0
32612
+ );
32613
+ }
32614
+ function createFileSessionStore(options) {
32615
+ const storageRoot = options.dir;
32616
+ const tails = /* @__PURE__ */ new Map();
32617
+ function enqueue(sessionId, op) {
32618
+ const tail = tails.get(sessionId) ?? Promise.resolve();
32619
+ const task = tail.then(op);
32620
+ tails.set(
32621
+ sessionId,
32622
+ task.then(
32623
+ () => void 0,
32624
+ () => void 0
32625
+ )
32626
+ );
32627
+ return task;
32628
+ }
32629
+ async function patchRawMeta(sessionDir, mutate) {
32630
+ const raw = await readRawMeta(sessionDir);
32631
+ if (raw === null) return null;
32632
+ if (mutate(raw)) {
32633
+ await writeFileDurable(metaFilePath(sessionDir), `${JSON.stringify(raw, null, 2)}
32634
+ `);
32635
+ }
32636
+ return raw;
32637
+ }
32638
+ async function appendMessages(writer, sessionId, parentEventId, messages) {
32639
+ let parent = parentEventId;
32640
+ for (const message of messages) {
32641
+ const eventId = randomUUID12();
32642
+ await writer.append({
32643
+ eventId,
32644
+ sessionId,
32645
+ branchId: MAIN_BRANCH_ID,
32646
+ kind: journalKindOf(message),
32647
+ parentEventId: parent,
32648
+ payload: message,
32649
+ producer: "sdk",
32650
+ visibility: { ...Visibilities.normal }
32651
+ });
32652
+ parent = eventId;
32653
+ }
32654
+ }
32655
+ async function rotateVolume(sessionDir, sessionId, history) {
32656
+ const tempDir = path22.join(sessionDir, `.rotate-${process.pid}-${Date.now().toString(36)}`);
32657
+ await mkdir3(tempDir, { recursive: true });
32658
+ try {
32659
+ const writer = await JournalWriter.open(tempDir);
32660
+ try {
32661
+ await appendMessages(writer, sessionId, null, history);
32662
+ } finally {
32663
+ await writer.close();
32664
+ }
32665
+ const tempAttachments = attachmentsDirPath(tempDir);
32666
+ let names = [];
32667
+ try {
32668
+ names = await readdir(tempAttachments);
32669
+ } catch {
32670
+ names = [];
32671
+ }
32672
+ if (names.length > 0) {
32673
+ const target = attachmentsDirPath(sessionDir);
32674
+ await mkdir3(target, { recursive: true });
32675
+ for (const name of names) {
32676
+ await rename2(path22.join(tempAttachments, name), path22.join(target, name));
32677
+ }
32678
+ }
32679
+ await rename2(journalFilePath(tempDir), journalFilePath(sessionDir));
32680
+ } finally {
32681
+ await rm3(tempDir, { recursive: true, force: true }).catch(() => void 0);
32682
+ }
32683
+ }
32684
+ return {
32685
+ async create(init) {
32686
+ const sessionId = init.sessionId ?? randomUUID12();
32687
+ return enqueue(sessionId, async () => {
32688
+ const { sessionDir, created } = await ensureSessionDir({
32689
+ storageRoot,
32690
+ sessionId,
32691
+ cwd: process.cwd(),
32692
+ ...init.title !== void 0 ? { extra: { title: init.title } } : {}
32693
+ });
32694
+ const raw = await patchRawMeta(sessionDir, (m) => {
32695
+ let changed = false;
32696
+ if (created && typeof m["updatedAt"] !== "string") {
32697
+ m["updatedAt"] = typeof m["createdAt"] === "string" ? m["createdAt"] : (/* @__PURE__ */ new Date()).toISOString();
32698
+ changed = true;
32699
+ }
32700
+ if (init.platformSessionId !== void 0 && m["platformSessionId"] !== init.platformSessionId) {
32701
+ m["platformSessionId"] = init.platformSessionId;
32702
+ changed = true;
32703
+ }
32704
+ if (init.title !== void 0 && m["title"] !== init.title) {
32705
+ m["title"] = init.title;
32706
+ changed = true;
32707
+ }
32708
+ if (init.meta !== void 0 && created) {
32709
+ m["meta"] = init.meta;
32710
+ changed = true;
32711
+ }
32712
+ return changed;
32713
+ });
32714
+ if (raw === null) throw corrupted(sessionId, "meta.json unreadable right after creation");
32715
+ return toRecordMeta(raw);
32716
+ });
32717
+ },
32718
+ async get(sessionId) {
32719
+ return enqueue(sessionId, async () => {
32720
+ const sessionDir = sessionDirPath(storageRoot, sessionId);
32721
+ const raw = await readRawMeta(sessionDir);
32722
+ if (raw === null) return null;
32723
+ const lock = await acquireSessionLock(sessionDir);
32724
+ try {
32725
+ let result;
32726
+ try {
32727
+ result = await readAll(sessionDir);
32728
+ } catch (err) {
32729
+ throw corrupted(sessionId, err instanceof Error ? err.message : String(err), err);
32730
+ }
32731
+ if (result.integrityBreakAt !== void 0) {
32732
+ throw corrupted(
32733
+ sessionId,
32734
+ `journal hash chain broken at record index ${result.integrityBreakAt}`
32735
+ );
32736
+ }
32737
+ const state = rebuildState(result.records);
32738
+ if (state.warnings.length > 0) {
32739
+ throw corrupted(sessionId, state.warnings.join("; "));
32740
+ }
32741
+ return { meta: toRecordMeta(raw), messages: state.messages };
32742
+ } finally {
32743
+ await lock.release();
32744
+ }
32745
+ });
32746
+ },
32747
+ async list(filter) {
32748
+ const root = path22.join(storageRoot, "sessions");
32749
+ let entries;
32750
+ try {
32751
+ entries = await readdir(root, { withFileTypes: true });
32752
+ } catch {
32753
+ return [];
32754
+ }
32755
+ const records = [];
32756
+ for (const entry of entries) {
32757
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
32758
+ const raw = await readRawMeta(path22.join(root, entry.name));
32759
+ if (raw === null || typeof raw["sessionId"] !== "string") continue;
32760
+ records.push(toRecordMeta(raw));
32761
+ }
32762
+ records.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
32763
+ return filter?.limit !== void 0 ? records.slice(0, filter.limit) : records;
32764
+ },
32765
+ async commit(sessionId, history, meta) {
32766
+ const snapshot = history.map((m) => structuredClone(m));
32767
+ return enqueue(sessionId, async () => {
32768
+ const title = sessionTitleOf(snapshot);
32769
+ const { sessionDir } = await ensureSessionDir({
32770
+ storageRoot,
32771
+ sessionId,
32772
+ cwd: process.cwd(),
32773
+ ...title !== void 0 ? { extra: { title } } : {}
32774
+ });
32775
+ const lock = await acquireSessionLock(sessionDir);
32776
+ try {
32777
+ let result;
32778
+ try {
32779
+ result = await readAll(sessionDir, { rehydrateImages: false });
32780
+ } catch (err) {
32781
+ throw corrupted(sessionId, err instanceof Error ? err.message : String(err), err);
32782
+ }
32783
+ if (result.integrityBreakAt !== void 0) {
32784
+ throw corrupted(
32785
+ sessionId,
32786
+ `journal hash chain broken at record index ${result.integrityBreakAt}`
32787
+ );
32788
+ }
32789
+ const persisted = result.records.filter(
32790
+ (r) => r.branchId === MAIN_BRANCH_ID && MESSAGE_KINDS2.has(r.kind) && r.visibility.model
32791
+ ).length;
32792
+ if (meta.rewritten || snapshot.length < persisted) {
32793
+ await rotateVolume(sessionDir, sessionId, snapshot);
32794
+ } else {
32795
+ const delta = snapshot.slice(persisted);
32796
+ if (delta.length > 0) {
32797
+ const writer = await JournalWriter.open(sessionDir);
32798
+ try {
32799
+ const last = result.records[result.records.length - 1];
32800
+ await appendMessages(writer, sessionId, last?.eventId ?? null, delta);
32801
+ } finally {
32802
+ await writer.close();
32803
+ }
32804
+ }
32805
+ }
32806
+ await patchRawMeta(sessionDir, (m) => {
32807
+ m["updatedAt"] = (/* @__PURE__ */ new Date()).toISOString();
32808
+ return true;
32809
+ });
32810
+ } finally {
32811
+ await lock.release();
32812
+ }
32813
+ });
32814
+ },
32815
+ async delete(sessionId) {
32816
+ return enqueue(sessionId, async () => {
32817
+ await rm3(sessionDirPath(storageRoot, sessionId), { recursive: true, force: true });
32818
+ });
32819
+ }
32820
+ };
32821
+ }
32822
+
31773
32823
  // src/view/reducer.ts
31774
32824
  var OUTPUT_TAIL_MAX_CHARS = 4e3;
31775
32825
  function initialSessionViewState() {
@@ -32367,6 +33417,58 @@ function markSourceFailure(state, error) {
32367
33417
  });
32368
33418
  }
32369
33419
 
33420
+ // src/view/history.ts
33421
+ function viewStateFromHistory(messages) {
33422
+ const results = /* @__PURE__ */ new Map();
33423
+ for (const message of messages) {
33424
+ for (const block of message.blocks) {
33425
+ if (block.t === "tool_result") results.set(block.callId, block);
33426
+ }
33427
+ }
33428
+ const uiMessages = [];
33429
+ let nextSeq = 1;
33430
+ for (const message of messages) {
33431
+ const parts = [];
33432
+ for (const block of message.blocks) {
33433
+ switch (block.t) {
33434
+ case "text":
33435
+ if (block.text !== "") parts.push({ type: "text", text: block.text });
33436
+ break;
33437
+ case "thinking":
33438
+ if (block.text !== "") parts.push({ type: "thinking", text: block.text });
33439
+ break;
33440
+ case "tool_call": {
33441
+ const result = results.get(block.id);
33442
+ const resultText = result?.content.filter((item) => item.t === "text").map((item) => item.text ?? "").join("\n");
33443
+ const status = result === void 0 ? "aborted" : result.isError === true ? "failed" : "completed";
33444
+ const part = {
33445
+ type: "toolCall",
33446
+ id: block.id,
33447
+ name: block.name,
33448
+ // 键在场性对齐 Kotlin(JsonElement?):args 键在场(含显式 null)
33449
+ // 即透传,键缺席即不落
33450
+ ...block.args !== void 0 ? { args: block.args } : {},
33451
+ status,
33452
+ ...resultText !== void 0 && resultText !== "" ? { resultText } : {}
33453
+ };
33454
+ parts.push(part);
33455
+ break;
33456
+ }
33457
+ default:
33458
+ break;
33459
+ }
33460
+ }
33461
+ if (parts.length === 0) continue;
33462
+ uiMessages.push({ id: `msg-${nextSeq}`, role: message.role, parts });
33463
+ nextSeq += 1;
33464
+ }
33465
+ const initial = initialSessionViewState();
33466
+ return {
33467
+ view: { ...initial.view, messages: uiMessages },
33468
+ internal: { ...initial.internal, nextMessageSeq: nextSeq }
33469
+ };
33470
+ }
33471
+
32370
33472
  // src/view/pump.ts
32371
33473
  function resolveIterable(source) {
32372
33474
  if (Symbol.asyncIterator in source) return source;
@@ -32600,6 +33702,7 @@ export {
32600
33702
  ImageGenArgsSchema,
32601
33703
  McpHost,
32602
33704
  OUTPUT_TAIL_MAX_CHARS,
33705
+ PLATFORM_SEARCH_PROVIDER_NAME,
32603
33706
  QueueChannel,
32604
33707
  SequentialToolExecutor,
32605
33708
  TANSR_PROVIDER_ID,
@@ -32608,8 +33711,6 @@ export {
32608
33711
  UnavailableChannel,
32609
33712
  VIDEOGEN_TOOL_MAX_DURATION,
32610
33713
  VideoGenArgsSchema,
32611
- WEBSEARCH_TOOL_MAX_RESULTS,
32612
- WebSearchArgsSchema2 as WebSearchArgsSchema,
32613
33714
  accumulateUsage,
32614
33715
  appBundleRegistryConfig,
32615
33716
  appendUserMessage,
@@ -32623,14 +33724,15 @@ export {
32623
33724
  buildSdkToolSet,
32624
33725
  buildToolGuideSegment,
32625
33726
  createAppTokenFetch,
33727
+ createFileSessionStore,
32626
33728
  createImageGenTool,
32627
33729
  createMcpHost,
32628
33730
  createNarrator,
33731
+ createPlatformSearchProvider,
32629
33732
  createSdkPermissionGate,
32630
33733
  createSession,
32631
33734
  createSessionView,
32632
33735
  createVideoGenTool,
32633
- createWebSearchTool2 as createWebSearchTool,
32634
33736
  defineSkill,
32635
33737
  defineTool,
32636
33738
  initialSessionViewState,
@@ -32644,5 +33746,6 @@ export {
32644
33746
  resolvePlatformSelection,
32645
33747
  runAgent,
32646
33748
  subagentModelResolverOf,
32647
- toToolDef
33749
+ toToolDef,
33750
+ viewStateFromHistory
32648
33751
  };