@tansr/sdk 0.2.0 → 0.3.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 +314 -103
  2. package/dist/index.js +1374 -430
  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";
@@ -7559,7 +7567,7 @@ async function* consumeModelStream(deps) {
7559
7567
  const stream = client.stream(request, callOptions);
7560
7568
  const collected = /* @__PURE__ */ new Map();
7561
7569
  const order = [];
7562
- const open = /* @__PURE__ */ new Set();
7570
+ const open4 = /* @__PURE__ */ new Set();
7563
7571
  const assembler = new ToolArgsAssembler();
7564
7572
  const toolCalls = [];
7565
7573
  let stopEvent;
@@ -7608,7 +7616,7 @@ async function* consumeModelStream(deps) {
7608
7616
  text: ev.block.t === "text" || ev.block.t === "thinking" ? ev.block.text : ""
7609
7617
  });
7610
7618
  order.push(ev.index);
7611
- open.add(ev.index);
7619
+ open4.add(ev.index);
7612
7620
  yield emit({ type: "msg.block.start", index: ev.index, blockType: ev.block.t });
7613
7621
  break;
7614
7622
  }
@@ -7633,7 +7641,7 @@ async function* consumeModelStream(deps) {
7633
7641
  assembler.append(ev.index, ev.json);
7634
7642
  break;
7635
7643
  case "block_stop": {
7636
- open.delete(ev.index);
7644
+ open4.delete(ev.index);
7637
7645
  yield emit({ type: "msg.block.end", index: ev.index });
7638
7646
  const c = collected.get(ev.index);
7639
7647
  if (c && c.base.t === "tool_call") {
@@ -7674,16 +7682,16 @@ async function* consumeModelStream(deps) {
7674
7682
  }
7675
7683
  }
7676
7684
  }
7677
- const completedTextBlocks = () => order.filter((index) => !open.has(index)).map((index) => collected.get(index)).filter(
7685
+ const completedTextBlocks = () => order.filter((index) => !open4.has(index)).map((index) => collected.get(index)).filter(
7678
7686
  (c) => c !== void 0 && c.base.t === "text" && c.text.trim().length > 0
7679
7687
  ).map((c) => finalizeBlock(c));
7680
7688
  if (abortedMidStream || signal.aborted) {
7681
- for (const index of open) yield emit({ type: "msg.retracted", index });
7689
+ for (const index of open4) yield emit({ type: "msg.retracted", index });
7682
7690
  disposeGenerator(stream);
7683
7691
  return { kind: "aborted" };
7684
7692
  }
7685
7693
  if (errorEvent) {
7686
- for (const index of open) yield emit({ type: "msg.retracted", index });
7694
+ for (const index of open4) yield emit({ type: "msg.retracted", index });
7687
7695
  disposeGenerator(stream);
7688
7696
  return {
7689
7697
  kind: "error",
@@ -7697,7 +7705,7 @@ async function* consumeModelStream(deps) {
7697
7705
  };
7698
7706
  }
7699
7707
  if (!stopEvent) {
7700
- for (const index of open) yield emit({ type: "msg.retracted", index });
7708
+ for (const index of open4) yield emit({ type: "msg.retracted", index });
7701
7709
  return {
7702
7710
  kind: "error",
7703
7711
  error: {
@@ -7709,7 +7717,7 @@ async function* consumeModelStream(deps) {
7709
7717
  };
7710
7718
  }
7711
7719
  if (stopEvent.stopReason === "unknown") {
7712
- for (const index of open) yield emit({ type: "msg.retracted", index });
7720
+ for (const index of open4) yield emit({ type: "msg.retracted", index });
7713
7721
  disposeGenerator(stream);
7714
7722
  return {
7715
7723
  kind: "error",
@@ -9839,16 +9847,16 @@ function unescapeSpecifier(spec) {
9839
9847
  function parseRule(raw) {
9840
9848
  const trimmed = raw.trim();
9841
9849
  if (trimmed === "") return null;
9842
- const open = findUnescapedParen(trimmed);
9843
- if (open === -1) {
9850
+ const open4 = findUnescapedParen(trimmed);
9851
+ if (open4 === -1) {
9844
9852
  if (TOOL_NAME_RE.test(trimmed)) return { raw: trimmed, toolName: trimmed };
9845
9853
  if (isMcpServerWildcard(trimmed)) return { raw: trimmed, toolName: trimmed };
9846
9854
  return null;
9847
9855
  }
9848
9856
  if (!trimmed.endsWith(")")) return null;
9849
- const toolName2 = trimmed.slice(0, open);
9857
+ const toolName2 = trimmed.slice(0, open4);
9850
9858
  if (!TOOL_NAME_RE.test(toolName2)) return null;
9851
- const spec = unescapeSpecifier(trimmed.slice(open + 1, -1)).trim();
9859
+ const spec = unescapeSpecifier(trimmed.slice(open4 + 1, -1)).trim();
9852
9860
  if (spec === "" || spec === "*") return { raw: trimmed, toolName: toolName2 };
9853
9861
  return { raw: trimmed, toolName: toolName2, specifier: spec };
9854
9862
  }
@@ -10092,9 +10100,9 @@ function stripBom(text) {
10092
10100
  return text.charCodeAt(0) === 65279 ? text.slice(1) : text;
10093
10101
  }
10094
10102
  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);
10103
+ const open4 = /^---[ \t]*\r?\n/.exec(text);
10104
+ if (open4 === null) return text;
10105
+ const rest = text.slice(open4[0].length);
10098
10106
  const close = /^---[ \t]*(?:\r?\n|$)/m.exec(rest);
10099
10107
  if (close === null) return text;
10100
10108
  return rest.slice(close.index + close[0].length);
@@ -10161,9 +10169,9 @@ var TYPE_KEY_RE = /^type[ \t]*:[ \t]*(.*)$/;
10161
10169
  function memoryEntryTypeOf(content, indexFile) {
10162
10170
  if (indexFile) return "unknown";
10163
10171
  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);
10172
+ const open4 = FRONTMATTER_OPEN_RE.exec(text);
10173
+ if (open4 === null) return "unknown";
10174
+ const rest = text.slice(open4[0].length);
10167
10175
  const close = FRONTMATTER_CLOSE_RE.exec(rest);
10168
10176
  if (close === null) return "unknown";
10169
10177
  for (const rawLine of rest.slice(0, close.index).split("\n")) {
@@ -10651,6 +10659,7 @@ var CLASSIFIER_BREAKER_TOTAL_LIMIT = 20;
10651
10659
  var CLASSIFIER_BREAKER_COOLDOWN_MS = 30 * 6e4;
10652
10660
 
10653
10661
  // ../kernel/src/journal/hash.ts
10662
+ import { createHash as createHash5 } from "node:crypto";
10654
10663
  function sortKeysDeep2(value) {
10655
10664
  if (Array.isArray(value)) return value.map(sortKeysDeep2);
10656
10665
  if (value !== null && typeof value === "object") {
@@ -10667,6 +10676,11 @@ function canonicalStringify(value) {
10667
10676
  const normalized = JSON.parse(JSON.stringify(value));
10668
10677
  return JSON.stringify(sortKeysDeep2(normalized));
10669
10678
  }
10679
+ function computeRecordHash(record) {
10680
+ const { integrity, ...rest } = record;
10681
+ const hashable = { ...rest, integrity: { previousHash: integrity.previousHash } };
10682
+ return createHash5("sha256").update(canonicalStringify(hashable), "utf8").digest("hex");
10683
+ }
10670
10684
 
10671
10685
  // ../kernel/src/permissions/broad-allow.ts
10672
10686
  var BROAD_EXECUTABLES = /* @__PURE__ */ new Set([
@@ -11146,13 +11160,13 @@ function canonicalCwd(cwd) {
11146
11160
  const resolved = resolveForPermission(cwd, ".");
11147
11161
  return resolved.kind === "ok" ? resolved.path : null;
11148
11162
  }
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("/");
11163
+ function canonicalPathEligible(cwdCanon, path23, policy, guard) {
11164
+ if (hasWindowsDeviceSegment(path23)) return false;
11165
+ if (path23 === cwdCanon) return !policy.strict;
11166
+ if (!path23.startsWith(`${cwdCanon}/`)) return false;
11167
+ if (guard.isProtectedContentPath(path23)) return false;
11168
+ if (matchProtectedPath(path23, null) !== null) return false;
11169
+ const segments = path23.slice(cwdCanon.length + 1).split("/");
11156
11170
  for (const [index, seg] of segments.entries()) {
11157
11171
  const hasGlob = GLOB_CHAR_RE.test(seg);
11158
11172
  if (hasGlob && seg.startsWith(".")) return false;
@@ -11336,9 +11350,9 @@ function verifyBundle(bundle, opts = {}) {
11336
11350
  // ../kernel/src/permissions/soften-radius.ts
11337
11351
  var PROTECTED_PATH_RULE_PREFIX = "protected-path(";
11338
11352
  var MCP_TOOL_PREFIX = "mcp__";
11339
- function pathInsideRoot(path20, root) {
11353
+ function pathInsideRoot(path23, root) {
11340
11354
  if (root === null) return false;
11341
- return path20 === root || path20.startsWith(root.endsWith("/") ? root : `${root}/`);
11355
+ return path23 === root || path23.startsWith(root.endsWith("/") ? root : `${root}/`);
11342
11356
  }
11343
11357
  function classifySoftenFace(input, base) {
11344
11358
  if (base.matchedRule?.startsWith(PROTECTED_PATH_RULE_PREFIX) === true) return "F6";
@@ -12642,8 +12656,8 @@ function fsErrorCode(err) {
12642
12656
  }
12643
12657
 
12644
12658
  // ../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" };
12659
+ function accessPhrases(access2) {
12660
+ return access2 === "read" ? { doing: "reading from", refused: "Reading is refused" } : { doing: "writing to", refused: "Writing is refused" };
12647
12661
  }
12648
12662
  async function findExistingAnchor(fs3, abs) {
12649
12663
  let cur = abs;
@@ -12670,8 +12684,8 @@ function insideCanonical(child, base, caseInsensitive) {
12670
12684
  const b = caseInsensitive ? base.toLowerCase() : base;
12671
12685
  return c === b || c.startsWith(b.endsWith("/") ? b : `${b}/`);
12672
12686
  }
12673
- function refusal(toolName2, declared, realTarget, code, detail, access) {
12674
- const phrase = accessPhrases(access);
12687
+ function refusal(toolName2, declared, realTarget, code, detail, access2) {
12688
+ const phrase = accessPhrases(access2);
12675
12689
  return {
12676
12690
  ok: false,
12677
12691
  code,
@@ -12681,8 +12695,8 @@ function refusal(toolName2, declared, realTarget, code, detail, access) {
12681
12695
  }
12682
12696
  async function checkRealTarget(toolName2, declaredAbs, options) {
12683
12697
  const caseInsensitive = options.caseInsensitivePaths ?? process.platform === "win32";
12684
- const access = options.access ?? "write";
12685
- const phrase = accessPhrases(access);
12698
+ const access2 = options.access ?? "write";
12699
+ const phrase = accessPhrases(access2);
12686
12700
  const fs3 = options.fs ?? defaultFs;
12687
12701
  let anchor;
12688
12702
  try {
@@ -12720,7 +12734,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12720
12734
  realTarget,
12721
12735
  "unverifiable",
12722
12736
  "The redirected target cannot be safely canonicalized for comparison.",
12723
- access
12737
+ access2
12724
12738
  );
12725
12739
  }
12726
12740
  const memoryDirs = options.memoryCarveOutDirs ?? [];
@@ -12766,7 +12780,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12766
12780
  realTarget,
12767
12781
  "memory_divergence",
12768
12782
  "Memory files must be plain files: no path component of a memory target may be a symlink or junction.",
12769
- access
12783
+ access2
12770
12784
  );
12771
12785
  }
12772
12786
  } else if (realInMemory) {
@@ -12776,7 +12790,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12776
12790
  realTarget,
12777
12791
  "memory_divergence",
12778
12792
  "The real target is inside the agent memory directory, which may only be addressed directly.",
12779
- access
12793
+ access2
12780
12794
  );
12781
12795
  }
12782
12796
  }
@@ -12798,7 +12812,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12798
12812
  realTarget,
12799
12813
  "escapes_workspace",
12800
12814
  `The real target is outside the workspace root "${options.cwd}".`,
12801
- access
12815
+ access2
12802
12816
  );
12803
12817
  }
12804
12818
  const homeC = canonicalHomedir(options.homedir ?? os.homedir());
@@ -12811,7 +12825,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12811
12825
  realTarget,
12812
12826
  "protected_divergence",
12813
12827
  `The real target is inside a protected area (${realLabel}).`,
12814
- access
12828
+ access2
12815
12829
  );
12816
12830
  }
12817
12831
  if (realC.kind === "ok" && matchesSecretFace(realC.path) && !(declaredC.kind === "ok" && matchesSecretFace(declaredC.path))) {
@@ -12821,7 +12835,7 @@ async function checkRealTarget(toolName2, declaredAbs, options) {
12821
12835
  realTarget,
12822
12836
  "secret_divergence",
12823
12837
  "The real target matches a protected secret-file pattern.",
12824
- access
12838
+ access2
12825
12839
  );
12826
12840
  }
12827
12841
  return { ok: true, realPath: realTarget, linkResolved: true };
@@ -13714,7 +13728,31 @@ var DispatchingToolExecutor = class {
13714
13728
  }
13715
13729
  };
13716
13730
 
13731
+ // ../kernel/src/journal/errors.ts
13732
+ var JournalError = class extends Error {
13733
+ code;
13734
+ constructor(code, message) {
13735
+ super(message);
13736
+ this.name = "JournalError";
13737
+ this.code = code;
13738
+ }
13739
+ };
13740
+ function isErrnoException(err) {
13741
+ return err instanceof Error && typeof err.code === "string";
13742
+ }
13743
+ var JournalCorruptionError = class extends JournalError {
13744
+ /** 损坏所在行号(1-based) */
13745
+ line;
13746
+ constructor(line, detail) {
13747
+ super("JOURNAL_CORRUPTED", `journal 第 ${line} 行数据损坏:${detail}`);
13748
+ this.name = "JournalCorruptionError";
13749
+ this.line = line;
13750
+ }
13751
+ };
13752
+
13717
13753
  // ../kernel/src/journal/paths.ts
13754
+ import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
13755
+ import path5 from "node:path";
13718
13756
  import { z as z14 } from "zod";
13719
13757
  var SessionMetaSchema = z14.object({
13720
13758
  schemaVersion: z14.literal(1),
@@ -13744,12 +13782,543 @@ var SessionMetaSchema = z14.object({
13744
13782
  */
13745
13783
  outputStyle: z14.string().optional()
13746
13784
  });
13785
+ var SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
13786
+ function assertValidSessionId(sessionId) {
13787
+ if (!SESSION_ID_PATTERN.test(sessionId)) {
13788
+ throw new JournalError(
13789
+ "INVALID_SESSION_ID",
13790
+ `非法 sessionId ${JSON.stringify(sessionId)}:仅允许字母数字与 . _ -,且须以字母数字开头`
13791
+ );
13792
+ }
13793
+ }
13794
+ function sessionDirPath(storageRoot, sessionId) {
13795
+ assertValidSessionId(sessionId);
13796
+ return path5.join(storageRoot, "sessions", sessionId);
13797
+ }
13798
+ function journalFilePath(sessionDir) {
13799
+ return path5.join(sessionDir, "journal.jsonl");
13800
+ }
13801
+ function metaFilePath(sessionDir) {
13802
+ return path5.join(sessionDir, "meta.json");
13803
+ }
13804
+ function lockFilePath(sessionDir) {
13805
+ return path5.join(sessionDir, "lock");
13806
+ }
13807
+ var durableTmpSeq = 0;
13808
+ async function writeFileDurable(filePath, content) {
13809
+ durableTmpSeq += 1;
13810
+ const tempPath = `${filePath}.tmp-${process.pid}-${durableTmpSeq.toString(36)}-${Date.now()}`;
13811
+ const handle = await open(tempPath, "w");
13812
+ try {
13813
+ if (typeof content === "string") {
13814
+ await handle.writeFile(content, "utf8");
13815
+ } else {
13816
+ await handle.writeFile(content);
13817
+ }
13818
+ await handle.sync();
13819
+ } finally {
13820
+ await handle.close();
13821
+ }
13822
+ try {
13823
+ await rename(tempPath, filePath);
13824
+ } catch (err) {
13825
+ await rm(tempPath, { force: true }).catch(() => void 0);
13826
+ throw err;
13827
+ }
13828
+ }
13829
+ async function readSessionMeta(sessionDir) {
13830
+ const raw = await readFile(metaFilePath(sessionDir), "utf8");
13831
+ let parsed;
13832
+ try {
13833
+ parsed = JSON.parse(raw);
13834
+ } catch {
13835
+ throw new JournalError("META_INVALID", `meta.json 不是合法 JSON:${metaFilePath(sessionDir)}`);
13836
+ }
13837
+ const result = SessionMetaSchema.safeParse(parsed);
13838
+ if (!result.success) {
13839
+ throw new JournalError("META_INVALID", `meta.json 不符合 SessionMeta schema:${result.error.message}`);
13840
+ }
13841
+ return result.data;
13842
+ }
13843
+ async function ensureSessionDir(options) {
13844
+ const sessionDir = sessionDirPath(options.storageRoot, options.sessionId);
13845
+ await mkdir(sessionDir, { recursive: true });
13846
+ try {
13847
+ const meta2 = await readSessionMeta(sessionDir);
13848
+ if (meta2.sessionId !== options.sessionId) {
13849
+ throw new JournalError(
13850
+ "META_MISMATCH",
13851
+ `meta.json 中 sessionId(${meta2.sessionId})与目录名(${options.sessionId})不一致`
13852
+ );
13853
+ }
13854
+ return { sessionDir, meta: meta2, created: false };
13855
+ } catch (err) {
13856
+ if (!isErrnoException(err) || err.code !== "ENOENT") throw err;
13857
+ }
13858
+ const meta = {
13859
+ schemaVersion: 1,
13860
+ sessionId: options.sessionId,
13861
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
13862
+ cwd: options.cwd
13863
+ };
13864
+ if (options.extra?.title !== void 0) meta.title = options.extra.title;
13865
+ if (options.extra?.firstUserMessage !== void 0) {
13866
+ meta.firstUserMessage = options.extra.firstUserMessage;
13867
+ }
13868
+ if (options.extra?.model !== void 0) meta.model = options.extra.model;
13869
+ if (options.extra?.provider !== void 0) meta.provider = options.extra.provider;
13870
+ if (options.extra?.outputStyle !== void 0) meta.outputStyle = options.extra.outputStyle;
13871
+ await writeFileDurable(metaFilePath(sessionDir), `${JSON.stringify(meta, null, 2)}
13872
+ `);
13873
+ return { sessionDir, meta, created: true };
13874
+ }
13747
13875
 
13748
13876
  // ../kernel/src/journal/lock.ts
13877
+ import { open as open2, rm as rm2, stat, readFile as readFile2 } from "node:fs/promises";
13878
+ import os2 from "node:os";
13749
13879
  var DEFAULT_STALE_MS = 30 * 60 * 1e3;
13880
+ function defaultIsPidAlive(pid) {
13881
+ try {
13882
+ process.kill(pid, 0);
13883
+ return true;
13884
+ } catch (err) {
13885
+ if (isErrnoException(err) && err.code === "EPERM") return true;
13886
+ return false;
13887
+ }
13888
+ }
13889
+ async function readLockPayload(lockPath) {
13890
+ try {
13891
+ const raw = await readFile2(lockPath, "utf8");
13892
+ const parsed = JSON.parse(raw);
13893
+ if (parsed !== null && typeof parsed === "object" && typeof parsed.pid === "number") {
13894
+ return parsed;
13895
+ }
13896
+ return null;
13897
+ } catch {
13898
+ return null;
13899
+ }
13900
+ }
13901
+ async function tryCreateLockFile(lockPath) {
13902
+ let handle;
13903
+ try {
13904
+ handle = await open2(lockPath, "wx");
13905
+ } catch (err) {
13906
+ if (isErrnoException(err) && err.code === "EEXIST") return false;
13907
+ throw err;
13908
+ }
13909
+ try {
13910
+ const payload = {
13911
+ pid: process.pid,
13912
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
13913
+ host: os2.hostname()
13914
+ };
13915
+ await handle.writeFile(`${JSON.stringify(payload)}
13916
+ `, "utf8");
13917
+ await handle.sync();
13918
+ } finally {
13919
+ await handle.close();
13920
+ }
13921
+ return true;
13922
+ }
13923
+ async function acquireSessionLock(sessionDir, options = {}) {
13924
+ const lockPath = lockFilePath(sessionDir);
13925
+ const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
13926
+ const isPidAlive = options.isPidAlive ?? defaultIsPidAlive;
13927
+ for (let attempt = 0; attempt < 2; attempt++) {
13928
+ if (await tryCreateLockFile(lockPath)) {
13929
+ let released = false;
13930
+ return {
13931
+ lockPath,
13932
+ async release() {
13933
+ if (released) return;
13934
+ released = true;
13935
+ await rm2(lockPath, { force: true });
13936
+ }
13937
+ };
13938
+ }
13939
+ const payload = await readLockPayload(lockPath);
13940
+ let mtimeMs = null;
13941
+ try {
13942
+ mtimeMs = (await stat(lockPath)).mtimeMs;
13943
+ } catch (err) {
13944
+ if (isErrnoException(err) && err.code === "ENOENT") {
13945
+ continue;
13946
+ }
13947
+ throw err;
13948
+ }
13949
+ const holderAlive = payload !== null && isPidAlive(payload.pid);
13950
+ const isStale = Date.now() - mtimeMs > staleMs;
13951
+ const canPreempt = payload === null ? isStale : !holderAlive || isStale;
13952
+ if (!canPreempt) {
13953
+ const holder = payload ? `pid=${payload.pid} host=${payload.host} since=${payload.startedAt}` : "未知持有者";
13954
+ throw new JournalError(
13955
+ "SESSION_LOCKED",
13956
+ `会话已被其他进程锁定(${holder}),锁文件:${lockPath}`
13957
+ );
13958
+ }
13959
+ await rm2(lockPath, { force: true });
13960
+ }
13961
+ throw new JournalError(
13962
+ "SESSION_LOCKED",
13963
+ `会话锁竞争激烈,清理陈旧锁后重试仍失败:${lockPath}`
13964
+ );
13965
+ }
13966
+
13967
+ // ../kernel/src/journal/reader.ts
13968
+ import { appendFile, readFile as readFile4, truncate } from "node:fs/promises";
13969
+
13970
+ // ../kernel/src/journal/attachments.ts
13971
+ import { createHash as createHash6 } from "node:crypto";
13972
+ import { access, mkdir as mkdir2, readFile as readFile3 } from "node:fs/promises";
13973
+ import path6 from "node:path";
13974
+ var ATTACHMENTS_DIR_NAME = "attachments";
13975
+ var IMAGE_MESSAGE_KINDS = /* @__PURE__ */ new Set([
13976
+ "user_prompt",
13977
+ "assistant_message",
13978
+ "tool_result"
13979
+ ]);
13980
+ var REF_PATTERN = /^sha256:([0-9a-f]{64})$/;
13981
+ function attachmentsDirPath(sessionDir) {
13982
+ return path6.join(sessionDir, ATTACHMENTS_DIR_NAME);
13983
+ }
13984
+ function attachmentFilePath(sessionDir, sha256Hex2) {
13985
+ return path6.join(attachmentsDirPath(sessionDir), sha256Hex2);
13986
+ }
13987
+ var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
13988
+ var isInlineImage = (b) => b["t"] === "image" && typeof b["data"] === "string" && typeof b["mime"] === "string" && b["$ref"] === void 0;
13989
+ var isExternalizedImage = (b) => b["t"] === "image" && typeof b["$ref"] === "string" && typeof b["mime"] === "string" && b["data"] === void 0;
13990
+ async function externalizeOne(block, sessionDir) {
13991
+ try {
13992
+ const bytes = Buffer.from(block["data"], "base64");
13993
+ const hex = createHash6("sha256").update(bytes).digest("hex");
13994
+ const file = attachmentFilePath(sessionDir, hex);
13995
+ let exists = true;
13996
+ try {
13997
+ await access(file);
13998
+ } catch {
13999
+ exists = false;
14000
+ }
14001
+ if (!exists) {
14002
+ await mkdir2(attachmentsDirPath(sessionDir), { recursive: true });
14003
+ await writeFileDurable(file, bytes);
14004
+ }
14005
+ const out = {
14006
+ t: "image",
14007
+ mime: block["mime"],
14008
+ $ref: `sha256:${hex}`,
14009
+ bytes: bytes.length
14010
+ };
14011
+ const dims = probeImageDimensions(bytes);
14012
+ if (dims !== null) {
14013
+ out["w"] = dims.width;
14014
+ out["h"] = dims.height;
14015
+ }
14016
+ return out;
14017
+ } catch {
14018
+ return block;
14019
+ }
14020
+ }
14021
+ async function rehydrateOne(block, sessionDir, notes) {
14022
+ const ref = block["$ref"];
14023
+ const placeholder = (noteReason, textReason) => {
14024
+ notes.push(`image 附件${noteReason},块已降级为占位文本(${ref})`);
14025
+ return { t: "text", text: `[image unavailable: attachment ${ref} ${textReason}]` };
14026
+ };
14027
+ const match = REF_PATTERN.exec(ref);
14028
+ if (match === null) return placeholder("引用非法", "has an invalid ref");
14029
+ const hex = match[1];
14030
+ let bytes;
14031
+ try {
14032
+ bytes = await readFile3(attachmentFilePath(sessionDir, hex));
14033
+ } catch {
14034
+ return placeholder("缺失", "is missing");
14035
+ }
14036
+ if (createHash6("sha256").update(bytes).digest("hex") !== hex) {
14037
+ return placeholder("摘要不符", "is corrupt (sha256 mismatch)");
14038
+ }
14039
+ return { t: "image", mime: block["mime"], data: bytes.toString("base64") };
14040
+ }
14041
+ async function mapImageBlocks(payload, match, mapper) {
14042
+ if (!isRecord(payload) || !Array.isArray(payload["blocks"])) return payload;
14043
+ let changed = false;
14044
+ const blocks = [];
14045
+ for (const raw of payload["blocks"]) {
14046
+ if (!isRecord(raw)) {
14047
+ blocks.push(raw);
14048
+ continue;
14049
+ }
14050
+ if (match(raw)) {
14051
+ const mapped = await mapper(raw);
14052
+ if (mapped !== raw) changed = true;
14053
+ blocks.push(mapped);
14054
+ continue;
14055
+ }
14056
+ if (raw["t"] === "tool_result" && Array.isArray(raw["content"])) {
14057
+ let itemChanged = false;
14058
+ const content = [];
14059
+ for (const item of raw["content"]) {
14060
+ if (!isRecord(item) || !match(item)) {
14061
+ content.push(item);
14062
+ continue;
14063
+ }
14064
+ const mapped = await mapper(item);
14065
+ if (mapped !== item) itemChanged = true;
14066
+ content.push(mapped);
14067
+ }
14068
+ if (!itemChanged) {
14069
+ blocks.push(raw);
14070
+ continue;
14071
+ }
14072
+ changed = true;
14073
+ blocks.push({ ...raw, content });
14074
+ continue;
14075
+ }
14076
+ blocks.push(raw);
14077
+ }
14078
+ return changed ? { ...payload, blocks } : payload;
14079
+ }
14080
+ async function externalizeImagePayload(payload, sessionDir) {
14081
+ return mapImageBlocks(payload, isInlineImage, (b) => externalizeOne(b, sessionDir));
14082
+ }
14083
+ async function rehydrateImagePayload(payload, sessionDir, notes) {
14084
+ return mapImageBlocks(payload, isExternalizedImage, (b) => rehydrateOne(b, sessionDir, notes));
14085
+ }
14086
+
14087
+ // ../kernel/src/journal/reader.ts
14088
+ var MAIN_BRANCH_ID = "main";
14089
+ var NEWLINE_BYTE = 10;
14090
+ async function readAll(sessionDir, options = {}) {
14091
+ const filePath = journalFilePath(sessionDir);
14092
+ let buf;
14093
+ try {
14094
+ buf = await readFile4(filePath);
14095
+ } catch (err) {
14096
+ if (isErrnoException(err) && err.code === "ENOENT") return { records: [], repairs: [] };
14097
+ throw err;
14098
+ }
14099
+ const records = [];
14100
+ const lineStartBytes = [];
14101
+ const repairs = [];
14102
+ let truncateTo = null;
14103
+ let missingFinalNewline = false;
14104
+ let pos = 0;
14105
+ let lineNo = 0;
14106
+ while (pos < buf.length) {
14107
+ lineNo++;
14108
+ const nl = buf.indexOf(NEWLINE_BYTE, pos);
14109
+ const hasNewline = nl !== -1;
14110
+ const end = hasNewline ? nl : buf.length;
14111
+ const isLastLine = !hasNewline || nl === buf.length - 1;
14112
+ const text = buf.subarray(pos, end).toString("utf8");
14113
+ let parsed;
14114
+ try {
14115
+ parsed = JSON.parse(text);
14116
+ } catch {
14117
+ if (isLastLine) {
14118
+ truncateTo = pos;
14119
+ repairs.push(`截断崩溃残留的不完整尾行(第 ${lineNo} 行,${buf.length - pos} 字节)`);
14120
+ break;
14121
+ }
14122
+ throw new JournalCorruptionError(lineNo, "JSON 解析失败且非最后一行,无法用崩溃截断解释");
14123
+ }
14124
+ const result = JournalRecordSchema.safeParse(parsed);
14125
+ if (!result.success) {
14126
+ throw new JournalCorruptionError(lineNo, `不符合 JournalRecord schema:${result.error.message}`);
14127
+ }
14128
+ records.push(result.data);
14129
+ lineStartBytes.push(pos);
14130
+ if (!hasNewline) missingFinalNewline = true;
14131
+ pos = end + 1;
14132
+ }
14133
+ while (records.length > 0 && records[records.length - 1].completeness === "partial") {
14134
+ const idx = records.length - 1;
14135
+ truncateTo = lineStartBytes[idx];
14136
+ repairs.push(`截断崩溃残留的 partial 尾记录(seq=${records[idx].seq})`);
14137
+ records.pop();
14138
+ lineStartBytes.pop();
14139
+ missingFinalNewline = false;
14140
+ }
14141
+ if (truncateTo !== null) {
14142
+ await truncate(filePath, truncateTo);
14143
+ } else if (missingFinalNewline) {
14144
+ await appendFile(filePath, "\n");
14145
+ repairs.push("补齐末行缺失的换行符");
14146
+ }
14147
+ let integrityBreakAt;
14148
+ let expectedPrev = null;
14149
+ for (let i = 0; i < records.length; i++) {
14150
+ const rec = records[i];
14151
+ if (rec.integrity.previousHash !== expectedPrev || computeRecordHash(rec) !== rec.integrity.hash) {
14152
+ integrityBreakAt = i;
14153
+ break;
14154
+ }
14155
+ expectedPrev = rec.integrity.hash;
14156
+ }
14157
+ if (options.rehydrateImages !== false) {
14158
+ for (let i = 0; i < records.length; i++) {
14159
+ const rec = records[i];
14160
+ if (!IMAGE_MESSAGE_KINDS.has(rec.kind)) continue;
14161
+ const notes = [];
14162
+ const payload = await rehydrateImagePayload(rec.payload, sessionDir, notes);
14163
+ if (payload !== rec.payload) records[i] = { ...rec, payload };
14164
+ for (const note of notes) repairs.push(`seq=${rec.seq}:${note}`);
14165
+ }
14166
+ }
14167
+ return integrityBreakAt === void 0 ? { records, repairs } : { records, integrityBreakAt, repairs };
14168
+ }
14169
+ var MESSAGE_KINDS = /* @__PURE__ */ new Set([
14170
+ "user_prompt",
14171
+ "assistant_message",
14172
+ "tool_result"
14173
+ ]);
14174
+ function rebuildState(records) {
14175
+ const messages = [];
14176
+ const warnings = [];
14177
+ for (const rec of records) {
14178
+ if (rec.branchId !== MAIN_BRANCH_ID) continue;
14179
+ if (!MESSAGE_KINDS.has(rec.kind)) continue;
14180
+ if (!rec.visibility.model) continue;
14181
+ const parsed = IRMessageSchema.safeParse(rec.payload);
14182
+ if (!parsed.success) {
14183
+ warnings.push(`seq=${rec.seq}(kind=${rec.kind})的 payload 不是合法 IRMessage,已跳过`);
14184
+ continue;
14185
+ }
14186
+ messages.push(parsed.data);
14187
+ }
14188
+ const last = records.length > 0 ? records[records.length - 1] : void 0;
14189
+ return { messages, lastSeq: last?.seq ?? -1, branchId: MAIN_BRANCH_ID, warnings };
14190
+ }
14191
+
14192
+ // ../kernel/src/journal/writer.ts
14193
+ import { open as open3 } from "node:fs/promises";
14194
+ var CRITICAL_KINDS = /* @__PURE__ */ new Set([
14195
+ "user_prompt",
14196
+ "assistant_message",
14197
+ "tool_result",
14198
+ "fork",
14199
+ "compact_boundary",
14200
+ "queue_enqueue",
14201
+ "queue_ack"
14202
+ ]);
14203
+ var JournalWriter = class _JournalWriter {
14204
+ #handle;
14205
+ /** 串行化队列尾:并发 append 按调用序依次执行 */
14206
+ #queue = Promise.resolve();
14207
+ #previousHash;
14208
+ #nextSeq;
14209
+ /** 已确认成功的文件字节长度,写失败时截断回滚的目标 */
14210
+ #byteLength;
14211
+ /** 会话目录(J15 附件外置的落点根) */
14212
+ #sessionDir;
14213
+ #closed = false;
14214
+ /** 回滚失败后置真:磁盘与内存链状态已不可信,拒绝继续写 */
14215
+ #broken = false;
14216
+ constructor(handle, previousHash, nextSeq, byteLength, sessionDir) {
14217
+ this.#handle = handle;
14218
+ this.#previousHash = previousHash;
14219
+ this.#nextSeq = nextSeq;
14220
+ this.#byteLength = byteLength;
14221
+ this.#sessionDir = sessionDir;
14222
+ }
14223
+ /**
14224
+ * 打开(或创建)会话 journal 并定位恢复点:经 readAll 读全量(顺带截断
14225
+ * 崩溃残留、保证行边界干净),取末条记录的 seq/hash 接续哈希链。
14226
+ * 恢复点读取用原始落盘形态(rehydrateImages:false)——只取 seq/hash,
14227
+ * 不为附件回填付费。前置条件:会话目录已存在(经 ensureSessionDir);
14228
+ * 会话锁由调用方持有。
14229
+ */
14230
+ static async open(sessionDir) {
14231
+ const { records } = await readAll(sessionDir, { rehydrateImages: false });
14232
+ const last = records.length > 0 ? records[records.length - 1] : void 0;
14233
+ const handle = await open3(journalFilePath(sessionDir), "a");
14234
+ try {
14235
+ const { size } = await handle.stat();
14236
+ return new _JournalWriter(handle, last?.integrity.hash ?? null, (last?.seq ?? -1) + 1, size, sessionDir);
14237
+ } catch (err) {
14238
+ await handle.close();
14239
+ throw err;
14240
+ }
14241
+ }
14242
+ /**
14243
+ * 追加一条记录(schema 校验后写);返回实际落盘的完整记录。
14244
+ * 并发调用按发起顺序串行落盘,seq 与文件行序一致。
14245
+ */
14246
+ append(input) {
14247
+ const task = this.#queue.then(() => this.#appendSerialized(input));
14248
+ this.#queue = task.then(
14249
+ () => void 0,
14250
+ () => void 0
14251
+ );
14252
+ return task;
14253
+ }
14254
+ async #appendSerialized(input) {
14255
+ if (this.#closed) {
14256
+ throw new JournalError("JOURNAL_CLOSED", "JournalWriter 已关闭,不能继续 append");
14257
+ }
14258
+ if (this.#broken) {
14259
+ throw new JournalError("JOURNAL_BROKEN", "此前写失败且截断回滚未成功,writer 已进入不可写状态");
14260
+ }
14261
+ let effective = input;
14262
+ if (IMAGE_MESSAGE_KINDS.has(input.kind)) {
14263
+ const payload = await externalizeImagePayload(input.payload, this.#sessionDir);
14264
+ if (payload !== input.payload) effective = { ...input, payload };
14265
+ }
14266
+ const candidate = {
14267
+ ...effective,
14268
+ schemaVersion: 1,
14269
+ seq: this.#nextSeq,
14270
+ completeness: "complete",
14271
+ integrity: { previousHash: this.#previousHash, hash: "" }
14272
+ };
14273
+ const parsed = JournalRecordSchema.safeParse(candidate);
14274
+ if (!parsed.success) {
14275
+ throw new JournalError("RECORD_INVALID", `记录不符合 JournalRecord schema:${parsed.error.message}`);
14276
+ }
14277
+ const record = parsed.data;
14278
+ record.integrity.hash = computeRecordHash(record);
14279
+ const line = Buffer.from(`${JSON.stringify(record)}
14280
+ `, "utf8");
14281
+ try {
14282
+ let written = 0;
14283
+ while (written < line.length) {
14284
+ const { bytesWritten } = await this.#handle.write(line, written, line.length - written);
14285
+ written += bytesWritten;
14286
+ }
14287
+ if (CRITICAL_KINDS.has(record.kind)) {
14288
+ try {
14289
+ await this.#handle.datasync();
14290
+ } catch {
14291
+ await this.#handle.sync();
14292
+ }
14293
+ }
14294
+ } catch (err) {
14295
+ try {
14296
+ await this.#handle.truncate(this.#byteLength);
14297
+ } catch {
14298
+ this.#broken = true;
14299
+ }
14300
+ throw err;
14301
+ }
14302
+ this.#byteLength += line.length;
14303
+ this.#previousHash = record.integrity.hash;
14304
+ this.#nextSeq = record.seq + 1;
14305
+ return record;
14306
+ }
14307
+ /** 下一条将分配的 seq(诊断用) */
14308
+ get nextSeq() {
14309
+ return this.#nextSeq;
14310
+ }
14311
+ /** 等待队列排空并关闭文件句柄;幂等 */
14312
+ async close() {
14313
+ if (this.#closed) return;
14314
+ this.#closed = true;
14315
+ await this.#queue;
14316
+ await this.#handle.close();
14317
+ }
14318
+ };
13750
14319
 
13751
14320
  // ../kernel/src/tools/search/path-utils.ts
13752
- import * as path5 from "node:path";
14321
+ import * as path7 from "node:path";
13753
14322
  function toPosixPath(p) {
13754
14323
  return p.replaceAll("\\", "/");
13755
14324
  }
@@ -13757,10 +14326,10 @@ function normalizeDriveLetter(p) {
13757
14326
  return /^[A-Za-z]:/.test(p) ? p.charAt(0).toLowerCase() + p.slice(1) : p;
13758
14327
  }
13759
14328
  function isAbsolutePath(p) {
13760
- return path5.win32.isAbsolute(p) || path5.posix.isAbsolute(p);
14329
+ return path7.win32.isAbsolute(p) || path7.posix.isAbsolute(p);
13761
14330
  }
13762
14331
  function canonicalize(p) {
13763
- return normalizeDriveLetter(toPosixPath(path5.resolve(p)));
14332
+ return normalizeDriveLetter(toPosixPath(path7.resolve(p)));
13764
14333
  }
13765
14334
  function resolvePathArg(p, cwd) {
13766
14335
  const target = p ?? cwd;
@@ -13813,11 +14382,11 @@ function expandBraces(pattern) {
13813
14382
  return out;
13814
14383
  }
13815
14384
  function expandOnce(pattern) {
13816
- const open = pattern.indexOf("{");
13817
- if (open === -1) return null;
14385
+ const open4 = pattern.indexOf("{");
14386
+ if (open4 === -1) return null;
13818
14387
  let depth = 0;
13819
14388
  let close = -1;
13820
- for (let i = open; i < pattern.length; i++) {
14389
+ for (let i = open4; i < pattern.length; i++) {
13821
14390
  const ch = pattern.charAt(i);
13822
14391
  if (ch === "{") depth += 1;
13823
14392
  else if (ch === "}") {
@@ -13829,8 +14398,8 @@ function expandOnce(pattern) {
13829
14398
  }
13830
14399
  }
13831
14400
  if (close === -1) return null;
13832
- const prefix = pattern.slice(0, open);
13833
- const body = pattern.slice(open + 1, close);
14401
+ const prefix = pattern.slice(0, open4);
14402
+ const body = pattern.slice(open4 + 1, close);
13834
14403
  const suffix = pattern.slice(close + 1);
13835
14404
  const alternatives = [];
13836
14405
  let level = 0;
@@ -14142,7 +14711,7 @@ var SESSION_EVENT_VOCABULARY = {
14142
14711
  var SESSION_EVENT_KINDS = Object.keys(SESSION_EVENT_VOCABULARY);
14143
14712
 
14144
14713
  // ../kernel/src/tools/files/read.ts
14145
- import path6 from "node:path";
14714
+ import path8 from "node:path";
14146
14715
  import { z as z15 } from "zod";
14147
14716
 
14148
14717
  // ../kernel/src/tools/files/encoding.ts
@@ -14224,19 +14793,19 @@ function truncateLine(line) {
14224
14793
  return `${line.slice(0, cut)}…[line truncated: ${line.length} chars total; use Grep to inspect the rest]`;
14225
14794
  }
14226
14795
  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();
14796
+ const dir = path8.dirname(filePath);
14797
+ const targetBase = path8.basename(filePath);
14798
+ const targetName = path8.parse(filePath).name.toLowerCase();
14230
14799
  try {
14231
14800
  const entries = await fs3.readdir(dir);
14232
14801
  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));
14802
+ (name) => name.toLowerCase() !== targetBase.toLowerCase() && path8.parse(name).name.toLowerCase() === targetName
14803
+ ).slice(0, 3).map((name) => path8.join(dir, name));
14235
14804
  } catch {
14236
14805
  return [];
14237
14806
  }
14238
14807
  }
14239
- async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
14808
+ async function executeImageRead(resolved, args, ctx, fs3, stat2, options) {
14240
14809
  let mode;
14241
14810
  try {
14242
14811
  mode = options.imageInput?.();
@@ -14255,9 +14824,9 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
14255
14824
  "offset/limit apply to text files only. Call Read again without offset/limit to read this image file."
14256
14825
  );
14257
14826
  }
14258
- if (stat.size > READ_IMAGE_RAW_MAX_BYTES) {
14827
+ if (stat2.size > READ_IMAGE_RAW_MAX_BYTES) {
14259
14828
  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.`
14829
+ `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
14830
  );
14262
14831
  }
14263
14832
  let buf;
@@ -14272,10 +14841,10 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
14272
14841
  const probed = probeImage(buf);
14273
14842
  if (probed === null) {
14274
14843
  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.`
14844
+ `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
14845
  );
14277
14846
  }
14278
- registerFileRead(ctx, normalizeFileKey(resolved), stat.mtimeMs);
14847
+ registerFileRead(ctx, normalizeFileKey(resolved), stat2.mtimeMs);
14279
14848
  const data = {
14280
14849
  path: resolved,
14281
14850
  mime: probed.mime,
@@ -14283,7 +14852,7 @@ async function executeImageRead(resolved, args, ctx, fs3, stat, options) {
14283
14852
  height: probed.height,
14284
14853
  bytes: buf.length
14285
14854
  };
14286
- const meta = `Image file: ${path6.basename(resolved)}
14855
+ const meta = `Image file: ${path8.basename(resolved)}
14287
14856
  Format: ${probed.mime}
14288
14857
  Dimensions: ${probed.width}x${probed.height} px
14289
14858
  Size: ${buf.length} bytes`;
@@ -14306,8 +14875,8 @@ function createReadTool(options = {}) {
14306
14875
  isConcurrencySafe: true,
14307
14876
  touchedPathsOf(args) {
14308
14877
  const parsed = ReadArgsSchema.safeParse(args);
14309
- if (!parsed.success || !path6.isAbsolute(parsed.data.file_path)) return [];
14310
- return [path6.resolve(parsed.data.file_path)];
14878
+ if (!parsed.success || !path8.isAbsolute(parsed.data.file_path)) return [];
14879
+ return [path8.resolve(parsed.data.file_path)];
14311
14880
  },
14312
14881
  async execute(args, ctx) {
14313
14882
  if (ctx.signal.aborted) {
@@ -14321,15 +14890,15 @@ function createReadTool(options = {}) {
14321
14890
  if (invalid) {
14322
14891
  return invalid;
14323
14892
  }
14324
- const resolved = path6.resolve(args.file_path);
14893
+ const resolved = path8.resolve(args.file_path);
14325
14894
  const fs3 = fsOf(ctx);
14326
14895
  const realTarget = await checkRealTarget("Read", resolved, { cwd: ctx.cwd, access: "read", fs: fs3 });
14327
14896
  if (!realTarget.ok) {
14328
14897
  return errorResult(realTarget.reason);
14329
14898
  }
14330
- let stat;
14899
+ let stat2;
14331
14900
  try {
14332
- stat = await fs3.stat(resolved);
14901
+ stat2 = await fs3.stat(resolved);
14333
14902
  } catch (err) {
14334
14903
  if (fsErrorCode(err) === "ENOENT") {
14335
14904
  const similar = await findSimilarFiles(fs3, resolved);
@@ -14338,11 +14907,11 @@ function createReadTool(options = {}) {
14338
14907
  }
14339
14908
  return errorResult(`Failed to read file: ${errorMessageOf(err)}`);
14340
14909
  }
14341
- if (stat.isDirectory()) {
14910
+ if (stat2.isDirectory()) {
14342
14911
  return errorResult(`Path is a directory, not a file: ${resolved}.`);
14343
14912
  }
14344
- if (isImageFileExtension(path6.extname(resolved))) {
14345
- return executeImageRead(resolved, args, ctx, fs3, stat, options);
14913
+ if (isImageFileExtension(path8.extname(resolved))) {
14914
+ return executeImageRead(resolved, args, ctx, fs3, stat2, options);
14346
14915
  }
14347
14916
  const fileKey = normalizeFileKey(resolved);
14348
14917
  const effectiveOffset = args.offset ?? 1;
@@ -14350,7 +14919,7 @@ function createReadTool(options = {}) {
14350
14919
  const dupWindow = takeDuplicateReadWindow(
14351
14920
  ctx,
14352
14921
  fileKey,
14353
- stat.mtimeMs,
14922
+ stat2.mtimeMs,
14354
14923
  effectiveOffset,
14355
14924
  effectiveLimit
14356
14925
  );
@@ -14367,9 +14936,9 @@ function createReadTool(options = {}) {
14367
14936
  data2
14368
14937
  );
14369
14938
  }
14370
- if (stat.size > READ_MAX_FULL_BYTES && args.offset === void 0 && args.limit === void 0) {
14939
+ if (stat2.size > READ_MAX_FULL_BYTES && args.offset === void 0 && args.limit === void 0) {
14371
14940
  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.`
14941
+ `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
14942
  );
14374
14943
  }
14375
14944
  let buf;
@@ -14388,7 +14957,7 @@ function createReadTool(options = {}) {
14388
14957
  );
14389
14958
  }
14390
14959
  if (decoded.text.length === 0) {
14391
- registerFileRead(ctx, fileKey, stat.mtimeMs, {
14960
+ registerFileRead(ctx, fileKey, stat2.mtimeMs, {
14392
14961
  offset: effectiveOffset,
14393
14962
  limit: effectiveLimit,
14394
14963
  totalLines: 0,
@@ -14402,7 +14971,7 @@ function createReadTool(options = {}) {
14402
14971
  const startLine = effectiveOffset;
14403
14972
  const maxLines = effectiveLimit;
14404
14973
  if (startLine > totalLines) {
14405
- registerFileRead(ctx, fileKey, stat.mtimeMs, {
14974
+ registerFileRead(ctx, fileKey, stat2.mtimeMs, {
14406
14975
  offset: effectiveOffset,
14407
14976
  limit: effectiveLimit,
14408
14977
  totalLines,
@@ -14421,7 +14990,7 @@ function createReadTool(options = {}) {
14421
14990
  text += `
14422
14991
  … (showing lines ${startLine}-${endLine} of ${totalLines}; use offset=${endLine + 1} to continue)`;
14423
14992
  }
14424
- registerFileRead(ctx, fileKey, stat.mtimeMs, {
14993
+ registerFileRead(ctx, fileKey, stat2.mtimeMs, {
14425
14994
  offset: effectiveOffset,
14426
14995
  limit: effectiveLimit,
14427
14996
  totalLines,
@@ -14435,7 +15004,7 @@ function createReadTool(options = {}) {
14435
15004
  var ReadTool = createReadTool();
14436
15005
 
14437
15006
  // ../kernel/src/tools/files/write.ts
14438
- import path7 from "node:path";
15007
+ import path9 from "node:path";
14439
15008
  import { z as z16 } from "zod";
14440
15009
  var WriteArgsSchema = z16.object({
14441
15010
  file_path: z16.string().describe("Absolute path to the file to write. Relative paths are rejected."),
@@ -14454,8 +15023,8 @@ var WriteTool = {
14454
15023
  isConcurrencySafe: false,
14455
15024
  mutatedPathsOf(args) {
14456
15025
  const parsed = WriteArgsSchema.safeParse(args);
14457
- if (!parsed.success || !path7.isAbsolute(parsed.data.file_path)) return [];
14458
- return [path7.resolve(parsed.data.file_path)];
15026
+ if (!parsed.success || !path9.isAbsolute(parsed.data.file_path)) return [];
15027
+ return [path9.resolve(parsed.data.file_path)];
14459
15028
  },
14460
15029
  async execute(args, ctx) {
14461
15030
  if (ctx.signal.aborted) {
@@ -14469,7 +15038,7 @@ var WriteTool = {
14469
15038
  if (invalid) {
14470
15039
  return invalid;
14471
15040
  }
14472
- const resolved = path7.resolve(args.file_path);
15041
+ const resolved = path9.resolve(args.file_path);
14473
15042
  const key2 = normalizeFileKey(resolved);
14474
15043
  const fs3 = fsOf(ctx);
14475
15044
  const realTarget = await checkRealTarget("Write", resolved, {
@@ -14511,7 +15080,7 @@ var WriteTool = {
14511
15080
  }
14512
15081
  } else {
14513
15082
  try {
14514
- await fs3.mkdir(path7.dirname(resolved), { recursive: true });
15083
+ await fs3.mkdir(path9.dirname(resolved), { recursive: true });
14515
15084
  } catch (err) {
14516
15085
  return errorResult(`Failed to create parent directories: ${errorMessageOf(err)}`);
14517
15086
  }
@@ -14567,7 +15136,7 @@ ${memoryNearLimitNote(health)}`, data);
14567
15136
  };
14568
15137
 
14569
15138
  // ../kernel/src/tools/files/edit.ts
14570
- import path8 from "node:path";
15139
+ import path10 from "node:path";
14571
15140
  import { z as z17 } from "zod";
14572
15141
  var EditArgsSchema = z17.object({
14573
15142
  file_path: z17.string().describe("Absolute path to the file to edit. Relative paths are rejected."),
@@ -14610,8 +15179,8 @@ var EditTool = {
14610
15179
  isConcurrencySafe: false,
14611
15180
  mutatedPathsOf(args) {
14612
15181
  const parsed = EditArgsSchema.safeParse(args);
14613
- if (!parsed.success || !path8.isAbsolute(parsed.data.file_path)) return [];
14614
- return [path8.resolve(parsed.data.file_path)];
15182
+ if (!parsed.success || !path10.isAbsolute(parsed.data.file_path)) return [];
15183
+ return [path10.resolve(parsed.data.file_path)];
14615
15184
  },
14616
15185
  async execute(args, ctx) {
14617
15186
  if (ctx.signal.aborted) {
@@ -14625,7 +15194,7 @@ var EditTool = {
14625
15194
  if (invalid) {
14626
15195
  return invalid;
14627
15196
  }
14628
- const resolved = path8.resolve(args.file_path);
15197
+ const resolved = path10.resolve(args.file_path);
14629
15198
  const key2 = normalizeFileKey(resolved);
14630
15199
  const fs3 = fsOf(ctx);
14631
15200
  const realTarget = await checkRealTarget("Edit", resolved, {
@@ -14636,19 +15205,19 @@ var EditTool = {
14636
15205
  if (!realTarget.ok) {
14637
15206
  return errorResult(realTarget.reason);
14638
15207
  }
14639
- let stat;
15208
+ let stat2;
14640
15209
  try {
14641
- stat = await fs3.stat(resolved);
15210
+ stat2 = await fs3.stat(resolved);
14642
15211
  } catch (err) {
14643
15212
  if (fsErrorCode(err) === "ENOENT") {
14644
15213
  return errorResult(`File does not exist: ${resolved}. Use the Write tool to create a new file.`);
14645
15214
  }
14646
15215
  return errorResult(`Failed to access file: ${errorMessageOf(err)}`);
14647
15216
  }
14648
- if (stat.isDirectory()) {
15217
+ if (stat2.isDirectory()) {
14649
15218
  return errorResult(`Path is a directory, not a file: ${resolved}.`);
14650
15219
  }
14651
- const guard = checkStaleWriteGuard(ctx, key2, stat.mtimeMs, "editing");
15220
+ const guard = checkStaleWriteGuard(ctx, key2, stat2.mtimeMs, "editing");
14652
15221
  if (guard) {
14653
15222
  return guard;
14654
15223
  }
@@ -14753,7 +15322,7 @@ ${memoryNearLimitNote(health)}`, data);
14753
15322
 
14754
15323
  // ../kernel/src/tools/search/glob-tool.ts
14755
15324
  import { z as z18 } from "zod";
14756
- import path9 from "node:path";
15325
+ import path11 from "node:path";
14757
15326
 
14758
15327
  // ../kernel/src/tools/search/walker.ts
14759
15328
  var DEFAULT_IGNORED_DIRS = /* @__PURE__ */ new Set([
@@ -14847,7 +15416,7 @@ var globTool = {
14847
15416
  return errorResult3(`path does not exist: ${root}`);
14848
15417
  }
14849
15418
  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 });
15419
+ const realRoot = await checkRealTarget("Glob", path11.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
14851
15420
  if (!realRoot.ok) return errorResult3(realRoot.reason);
14852
15421
  let matcher;
14853
15422
  try {
@@ -14886,7 +15455,7 @@ var globTool = {
14886
15455
 
14887
15456
  // ../kernel/src/tools/search/grep-tool.ts
14888
15457
  import { z as z19 } from "zod";
14889
- import path10 from "node:path";
15458
+ import path12 from "node:path";
14890
15459
 
14891
15460
  // ../kernel/src/tools/search/js-engine.ts
14892
15461
  var GREP_MAX_FILE_SIZE = 4 * 1024 * 1024;
@@ -15360,7 +15929,7 @@ var grepTool = {
15360
15929
  return finish(errorResult4(`path does not exist: ${root}`));
15361
15930
  }
15362
15931
  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 });
15932
+ const realRoot = await checkRealTarget("Grep", path12.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
15364
15933
  if (!realRoot.ok) return finish(errorResult4(realRoot.reason));
15365
15934
  try {
15366
15935
  new RegExp(args.pattern, args.case_insensitive ? "i" : "");
@@ -15445,7 +16014,7 @@ var grepTool = {
15445
16014
 
15446
16015
  // ../kernel/src/tools/search/list-tool.ts
15447
16016
  import { z as z20 } from "zod";
15448
- import path11 from "node:path";
16017
+ import path13 from "node:path";
15449
16018
  var LIST_MAX_ENTRIES = 500;
15450
16019
  var ListArgsSchema = z20.object({
15451
16020
  path: z20.string().min(1),
@@ -15475,7 +16044,7 @@ var listTool = {
15475
16044
  touchedPathsOf(args) {
15476
16045
  const parsed = ListArgsSchema.safeParse(args);
15477
16046
  if (!parsed.success || !isAbsolutePath(parsed.data.path)) return [];
15478
- return [path11.resolve(parsed.data.path)];
16047
+ return [path13.resolve(parsed.data.path)];
15479
16048
  },
15480
16049
  async execute(args, ctx) {
15481
16050
  if (ctx.signal.aborted) return errorResult5("aborted");
@@ -15489,7 +16058,7 @@ var listTool = {
15489
16058
  return errorResult5(`path does not exist: ${root}`);
15490
16059
  }
15491
16060
  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 });
16061
+ const realRoot = await checkRealTarget("List", path13.resolve(root), { cwd: ctx.cwd, access: "read", fs: fs3 });
15493
16062
  if (!realRoot.ok) return errorResult5(realRoot.reason);
15494
16063
  let ignoreMatchers = [];
15495
16064
  try {
@@ -16020,7 +16589,7 @@ ${tail}`;
16020
16589
 
16021
16590
  // ../kernel/src/tools/shell/output-file.ts
16022
16591
  import { createWriteStream, mkdirSync } from "node:fs";
16023
- import * as path12 from "node:path";
16592
+ import * as path14 from "node:path";
16024
16593
  var SHELL_OUTPUT_SPILL_THRESHOLD_CHARS = 5e4;
16025
16594
  var SHELL_OUTPUT_DIR_NAME = "shell-output";
16026
16595
  function sanitizeFileStem(stem) {
@@ -16029,7 +16598,7 @@ function sanitizeFileStem(stem) {
16029
16598
  }
16030
16599
  function shellOutputFilePath(sessionCwd, toolCallId) {
16031
16600
  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`);
16601
+ return path14.join(sessionCwd, ".tansr", SHELL_OUTPUT_DIR_NAME, `${stem}.log`);
16033
16602
  }
16034
16603
  var ShellOutputSink = class {
16035
16604
  filePath;
@@ -16065,7 +16634,7 @@ var ShellOutputSink = class {
16065
16634
  forceOpen() {
16066
16635
  if (this.#stream !== void 0 || this.#writeError !== void 0 || this.#closed) return;
16067
16636
  try {
16068
- mkdirSync(path12.dirname(this.filePath), { recursive: true });
16637
+ mkdirSync(path14.dirname(this.filePath), { recursive: true });
16069
16638
  const stream = createWriteStream(this.filePath, { encoding: "utf8" });
16070
16639
  stream.on("error", (err) => {
16071
16640
  this.#writeError = err.message;
@@ -17556,104 +18125,7 @@ function scrubSecrets(text, secrets) {
17556
18125
  }
17557
18126
 
17558
18127
  // ../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
18128
  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
18129
 
17658
18130
  // ../kernel/src/tools/web/search-tool.ts
17659
18131
  import { z as z27 } from "zod";
@@ -18591,7 +19063,7 @@ function applyToolOverrides(filtered, overrides) {
18591
19063
  }
18592
19064
 
18593
19065
  // ../kernel/src/agents/subagent-memory-fence.ts
18594
- import path13 from "node:path";
19066
+ import path15 from "node:path";
18595
19067
  function subagentMemoryFenceOf(ctx) {
18596
19068
  if (ctx === void 0 || ctx.dirs.length === 0) return void 0;
18597
19069
  return {
@@ -18606,7 +19078,7 @@ function writeTargetOf(args, cwd) {
18606
19078
  if (args === null || typeof args !== "object") return null;
18607
19079
  const raw = args.file_path;
18608
19080
  if (typeof raw !== "string" || raw.length === 0) return null;
18609
- return path13.isAbsolute(raw) ? path13.resolve(raw) : path13.resolve(cwd, raw);
19081
+ return path15.isAbsolute(raw) ? path15.resolve(raw) : path15.resolve(cwd, raw);
18610
19082
  }
18611
19083
  function fencedSubagentWriteTool(tool, fence) {
18612
19084
  return {
@@ -18914,10 +19386,10 @@ async function runSubagent(options) {
18914
19386
  }
18915
19387
 
18916
19388
  // ../kernel/src/tools/task/background.ts
18917
- import * as path14 from "node:path";
19389
+ import * as path16 from "node:path";
18918
19390
  var TASK_AGENT_OUTPUT_DIR_NAME = "agent-output";
18919
19391
  function taskAgentOutputFilePath(sessionCwd, agentId) {
18920
- return path14.join(sessionCwd, ".tansr", TASK_AGENT_OUTPUT_DIR_NAME, `${sanitizeFileStem(agentId)}.md`);
19392
+ return path16.join(sessionCwd, ".tansr", TASK_AGENT_OUTPUT_DIR_NAME, `${sanitizeFileStem(agentId)}.md`);
18921
19393
  }
18922
19394
  function taskSettlementStopReason(reason, aborted) {
18923
19395
  if (aborted) return "aborted";
@@ -20167,7 +20639,7 @@ import { spawnSync as spawnSync2 } from "node:child_process";
20167
20639
 
20168
20640
  // ../kernel/src/tools/mcp/win32-spawn.ts
20169
20641
  import fs2 from "node:fs";
20170
- import path15 from "node:path";
20642
+ import path17 from "node:path";
20171
20643
  var CMD_META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
20172
20644
  var SPAWNABLE_EXTS = [".COM", ".EXE", ".BAT", ".CMD"];
20173
20645
  function escapeCmdCommand(command) {
@@ -20206,7 +20678,7 @@ function defaultFileExists(filePath) {
20206
20678
  }
20207
20679
  }
20208
20680
  function resolveCommandFile(command, env, cwd, fileExists) {
20209
- const w = path15.win32;
20681
+ const w = path17.win32;
20210
20682
  const exts = spawnableExts(envLookup(env, "PATHEXT"));
20211
20683
  const bases = [];
20212
20684
  if (command.includes("/") || command.includes("\\")) {
@@ -20240,7 +20712,7 @@ function wrapWithComSpec(target, args, env) {
20240
20712
  function planStdioSpawn(command, args, options) {
20241
20713
  const platform = options.platform ?? process.platform;
20242
20714
  if (platform !== "win32") return { file: command, args };
20243
- const ext = path15.win32.extname(command).toUpperCase();
20715
+ const ext = path17.win32.extname(command).toUpperCase();
20244
20716
  if (ext === ".EXE" || ext === ".COM") return { file: command, args };
20245
20717
  if (ext === ".CMD" || ext === ".BAT") return wrapWithComSpec(command, args, options.env);
20246
20718
  if (ext !== "") return { file: command, args };
@@ -20248,7 +20720,7 @@ function planStdioSpawn(command, args, options) {
20248
20720
  const fileExists = options.fileExists ?? defaultFileExists;
20249
20721
  const resolved = resolveCommandFile(command, options.env, cwd, fileExists);
20250
20722
  if (resolved === void 0) return { file: command, args };
20251
- const resolvedExt = path15.win32.extname(resolved).toUpperCase();
20723
+ const resolvedExt = path17.win32.extname(resolved).toUpperCase();
20252
20724
  if (resolvedExt === ".CMD" || resolvedExt === ".BAT") {
20253
20725
  return wrapWithComSpec(resolved, args, options.env);
20254
20726
  }
@@ -20378,11 +20850,11 @@ function buildMcpCatalogSegment(servers) {
20378
20850
  if (text.length === 0) return null;
20379
20851
  return { text, cacheable: true, label: MCP_CATALOG_LABEL };
20380
20852
  }
20381
- function isRecord(value) {
20853
+ function isRecord2(value) {
20382
20854
  return typeof value === "object" && value !== null && !Array.isArray(value);
20383
20855
  }
20384
20856
  function describeSchemaType(prop) {
20385
- if (!isRecord(prop)) return "any";
20857
+ if (!isRecord2(prop)) return "any";
20386
20858
  if (Array.isArray(prop["enum"])) return "enum";
20387
20859
  const type = prop["type"];
20388
20860
  if (typeof type === "string") return type;
@@ -20393,7 +20865,7 @@ function describeSchemaType(prop) {
20393
20865
  return "any";
20394
20866
  }
20395
20867
  function summarizeMcpParams(inputSchema) {
20396
- const properties = isRecord(inputSchema["properties"]) ? inputSchema["properties"] : void 0;
20868
+ const properties = isRecord2(inputSchema["properties"]) ? inputSchema["properties"] : void 0;
20397
20869
  if (properties === void 0 || Object.keys(properties).length === 0) {
20398
20870
  return "(parameters unspecified)";
20399
20871
  }
@@ -20549,14 +21021,14 @@ var SseParser = class {
20549
21021
  var JSONRPC_VERSION = "2.0";
20550
21022
  var JSONRPC_METHOD_NOT_FOUND = -32601;
20551
21023
  var JSONRPC_INTERNAL_ERROR = -32603;
20552
- function isRecord2(value) {
21024
+ function isRecord3(value) {
20553
21025
  return typeof value === "object" && value !== null && !Array.isArray(value);
20554
21026
  }
20555
21027
  function isValidId(value) {
20556
21028
  return typeof value === "string" || typeof value === "number" && Number.isFinite(value);
20557
21029
  }
20558
21030
  function classifyJsonRpcValue(value) {
20559
- if (!isRecord2(value)) return { ok: false, error: "invalid_message" };
21031
+ if (!isRecord3(value)) return { ok: false, error: "invalid_message" };
20560
21032
  if (value["jsonrpc"] !== JSONRPC_VERSION) return { ok: false, error: "invalid_message" };
20561
21033
  const method = value["method"];
20562
21034
  if (typeof method === "string" && method.length > 0) {
@@ -20569,7 +21041,7 @@ function classifyJsonRpcValue(value) {
20569
21041
  if ("error" in value) {
20570
21042
  const err = value["error"];
20571
21043
  const idOk = value["id"] === null || isValidId(value["id"]);
20572
- if (!idOk || !isRecord2(err) || typeof err["code"] !== "number" || typeof err["message"] !== "string") {
21044
+ if (!idOk || !isRecord3(err) || typeof err["code"] !== "number" || typeof err["message"] !== "string") {
20573
21045
  return { ok: false, error: "invalid_message" };
20574
21046
  }
20575
21047
  return { ok: true, kind: "error", message: value };
@@ -20612,14 +21084,14 @@ function buildErrorResponse(id, code, message, data) {
20612
21084
  // ../kernel/src/tools/mcp/stream-connection.ts
20613
21085
  var DEFAULT_HANDSHAKE_TIMEOUT_MS = 1e4;
20614
21086
  var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
20615
- function isRecord3(value) {
21087
+ function isRecord4(value) {
20616
21088
  return typeof value === "object" && value !== null && !Array.isArray(value);
20617
21089
  }
20618
21090
  function createDefaultServerRequestHandler(serverName, onEvent) {
20619
21091
  return (method, params) => {
20620
21092
  if (method === "ping") return Promise.resolve({ result: {} });
20621
21093
  if (method === "elicitation/create") {
20622
- const p = isRecord3(params) ? params : {};
21094
+ const p = isRecord4(params) ? params : {};
20623
21095
  const mode = p["mode"] === "url" ? "url" : "form";
20624
21096
  const message = typeof p["message"] === "string" ? p["message"].slice(0, 200) : void 0;
20625
21097
  onEvent?.({
@@ -20868,7 +21340,7 @@ var StreamMcpConnection = class {
20868
21340
  }
20869
21341
  #handleServerNotification(notification) {
20870
21342
  if (notification.method === "notifications/message") {
20871
- const p = isRecord3(notification.params) ? notification.params : {};
21343
+ const p = isRecord4(notification.params) ? notification.params : {};
20872
21344
  const level = typeof p["level"] === "string" ? p["level"] : "info";
20873
21345
  const data = p["data"];
20874
21346
  const message = typeof data === "string" ? data : data !== void 0 ? JSON.stringify(data) : "";
@@ -20882,7 +21354,7 @@ var StreamMcpConnection = class {
20882
21354
  }
20883
21355
  };
20884
21356
  function validateInitializeResult(serverName, result) {
20885
- if (!isRecord3(result)) {
21357
+ if (!isRecord4(result)) {
20886
21358
  throw new McpError("handshake_failed", "MCP initialize returned a non-object result.", {
20887
21359
  server: serverName
20888
21360
  });
@@ -20907,8 +21379,8 @@ function validateInitializeResult(serverName, result) {
20907
21379
  );
20908
21380
  }
20909
21381
  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"] : {};
21382
+ const serverInfo = isRecord4(rawInfo) && typeof rawInfo["name"] === "string" && typeof rawInfo["version"] === "string" ? { name: rawInfo["name"], version: rawInfo["version"] } : void 0;
21383
+ const capabilities = isRecord4(result["capabilities"]) ? result["capabilities"] : {};
20912
21384
  const rawInstructions = result["instructions"];
20913
21385
  const instructions = typeof rawInstructions === "string" && rawInstructions.trim().length > 0 ? capMcpInstructions(rawInstructions).text : void 0;
20914
21386
  return {
@@ -21112,7 +21584,7 @@ ${tail}`, {
21112
21584
  }
21113
21585
 
21114
21586
  // ../kernel/src/tools/mcp/http-transport.ts
21115
- var defaultFetch3 = (url, init) => globalThis.fetch(url, init);
21587
+ var defaultFetch2 = (url, init) => globalThis.fetch(url, init);
21116
21588
  var SESSION_HEADER = "mcp-session-id";
21117
21589
  var PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
21118
21590
  var CANCEL_NOTIFY_TIMEOUT_MS = 3e3;
@@ -21139,7 +21611,7 @@ var HttpMcpConnection = class {
21139
21611
  this.serverName = options.serverName;
21140
21612
  this.#url = config.url;
21141
21613
  this.#configHeaders = config.headers ?? {};
21142
- this.#fetchImpl = options.fetchImpl ?? defaultFetch3;
21614
+ this.#fetchImpl = options.fetchImpl ?? defaultFetch2;
21143
21615
  this.#onEvent = options.onEvent;
21144
21616
  this.#defaultRequestTimeoutMs = options.defaultRequestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
21145
21617
  this.#serverRequestHandler = options.onServerRequest ?? createDefaultServerRequestHandler(options.serverName, options.onEvent);
@@ -21504,7 +21976,7 @@ async function connectHttpMcpServer(config, options) {
21504
21976
  }
21505
21977
 
21506
21978
  // ../kernel/src/tools/mcp/bridge.ts
21507
- import { createHash as createHash5 } from "node:crypto";
21979
+ import { createHash as createHash7 } from "node:crypto";
21508
21980
  import { z as z33 } from "zod";
21509
21981
  var MCP_TOOL_NAME_PREFIX = "mcp__";
21510
21982
  var MCP_REMOTE_TOOL_NAME_RE = /^[A-Za-z0-9_.-]{1,128}$/;
@@ -21512,7 +21984,7 @@ var MAX_LIST_PAGES = 64;
21512
21984
  function buildMcpToolName(serverName, toolName2) {
21513
21985
  const full = `${MCP_TOOL_NAME_PREFIX}${serverName}__${toolName2}`;
21514
21986
  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);
21987
+ const hash = createHash7("sha256").update(full, "utf8").digest("hex").slice(0, MCP_NAME_FOLD_HASH_CHARS);
21516
21988
  const prefix = `${MCP_TOOL_NAME_PREFIX}${serverName}__`;
21517
21989
  const tailBudget = MCP_BRIDGED_TOOL_NAME_MAX_CHARS - prefix.length;
21518
21990
  if (tailBudget >= MCP_NAME_FOLD_HASH_CHARS + 1) {
@@ -21522,7 +21994,7 @@ function buildMcpToolName(serverName, toolName2) {
21522
21994
  const head = full.slice(0, MCP_BRIDGED_TOOL_NAME_MAX_CHARS - MCP_NAME_FOLD_HASH_CHARS - 1);
21523
21995
  return `${head}_${hash}`;
21524
21996
  }
21525
- function isRecord4(value) {
21997
+ function isRecord5(value) {
21526
21998
  return typeof value === "object" && value !== null && !Array.isArray(value);
21527
21999
  }
21528
22000
  async function discoverMcpTools(connection, opts = {}) {
@@ -21536,9 +22008,9 @@ async function discoverMcpTools(connection, opts = {}) {
21536
22008
  cursor !== void 0 ? { cursor } : {},
21537
22009
  opts
21538
22010
  );
21539
- if (!isRecord4(result) || !Array.isArray(result["tools"])) break;
22011
+ if (!isRecord5(result) || !Array.isArray(result["tools"])) break;
21540
22012
  for (const raw of result["tools"]) {
21541
- if (!isRecord4(raw) || typeof raw["name"] !== "string") continue;
22013
+ if (!isRecord5(raw) || typeof raw["name"] !== "string") continue;
21542
22014
  const name = raw["name"];
21543
22015
  if (!MCP_REMOTE_TOOL_NAME_RE.test(name)) {
21544
22016
  invalidNames.push(name.slice(0, 160));
@@ -21549,7 +22021,7 @@ async function discoverMcpTools(connection, opts = {}) {
21549
22021
  tools.push({
21550
22022
  name,
21551
22023
  description: typeof raw["description"] === "string" ? raw["description"] : "",
21552
- inputSchema: isRecord4(raw["inputSchema"]) ? raw["inputSchema"] : { type: "object" }
22024
+ inputSchema: isRecord5(raw["inputSchema"]) ? raw["inputSchema"] : { type: "object" }
21553
22025
  });
21554
22026
  }
21555
22027
  const next = result["nextCursor"];
@@ -21572,7 +22044,7 @@ function buildLooseArgsSchema(inputSchema) {
21572
22044
  });
21573
22045
  }
21574
22046
  function mapCallResultToToolResult(serverName, toolName2, raw) {
21575
- if (!isRecord4(raw)) {
22047
+ if (!isRecord5(raw)) {
21576
22048
  return {
21577
22049
  content: [{ t: "text", text: "MCP server returned a malformed tools/call result." }],
21578
22050
  isError: true,
@@ -21586,11 +22058,11 @@ function mapCallResultToToolResult(serverName, toolName2, raw) {
21586
22058
  };
21587
22059
  }
21588
22060
  const isError = raw["isError"] === true;
21589
- const structuredContent = isRecord4(raw["structuredContent"]) ? raw["structuredContent"] : void 0;
22061
+ const structuredContent = isRecord5(raw["structuredContent"]) ? raw["structuredContent"] : void 0;
21590
22062
  const blocks = Array.isArray(raw["content"]) ? raw["content"] : [];
21591
22063
  const content = [];
21592
22064
  for (const block of blocks) {
21593
- if (!isRecord4(block)) continue;
22065
+ if (!isRecord5(block)) continue;
21594
22066
  const type = block["type"];
21595
22067
  if (type === "text" && typeof block["text"] === "string") {
21596
22068
  content.push({ t: "text", text: block["text"] });
@@ -21612,7 +22084,7 @@ function mapCallResultToToolResult(serverName, toolName2, raw) {
21612
22084
  content.push({ t: "text", text: `[resource link]${name} ${block["uri"]}${description}` });
21613
22085
  continue;
21614
22086
  }
21615
- if (type === "resource" && isRecord4(block["resource"])) {
22087
+ if (type === "resource" && isRecord5(block["resource"])) {
21616
22088
  const resource = block["resource"];
21617
22089
  const uri = typeof resource["uri"] === "string" ? resource["uri"] : "(unknown uri)";
21618
22090
  if (typeof resource["text"] === "string") {
@@ -21893,7 +22365,7 @@ function extractMcpServersSection(layersDescending, addDiagnostic) {
21893
22365
  }
21894
22366
 
21895
22367
  // ../kernel/src/tools/mcp/catalog-store.ts
21896
- import { createHash as createHash6 } from "node:crypto";
22368
+ import { createHash as createHash8 } from "node:crypto";
21897
22369
  var MCP_CATALOG_STORE_VERSION = 1;
21898
22370
  function stableStringify2(value) {
21899
22371
  if (value === null || typeof value !== "object") {
@@ -21907,10 +22379,10 @@ function stableStringify2(value) {
21907
22379
  return `{${parts.join(",")}}`;
21908
22380
  }
21909
22381
  function computeMcpServerConfigFingerprint(config) {
21910
- return createHash6("sha256").update(stableStringify2(config), "utf8").digest("hex");
22382
+ return createHash8("sha256").update(stableStringify2(config), "utf8").digest("hex");
21911
22383
  }
21912
22384
  function computeMcpToolDefinitionHash(tool) {
21913
- return createHash6("sha256").update(
22385
+ return createHash8("sha256").update(
21914
22386
  stableStringify2({
21915
22387
  name: tool.name,
21916
22388
  description: tool.description,
@@ -21988,14 +22460,14 @@ function resolveEnvRefs(record, baseEnv) {
21988
22460
  }
21989
22461
  return { resolved, missing };
21990
22462
  }
21991
- function isRecord5(value) {
22463
+ function isRecord6(value) {
21992
22464
  return typeof value === "object" && value !== null && !Array.isArray(value);
21993
22465
  }
21994
22466
  function createElicitationAwareHandler(serverName, onElicitation, onEvent) {
21995
22467
  const fallback = createDefaultServerRequestHandler(serverName, onEvent);
21996
22468
  return async (method, params) => {
21997
22469
  if (method !== "elicitation/create") return fallback(method, params);
21998
- const p = isRecord5(params) ? params : {};
22470
+ const p = isRecord6(params) ? params : {};
21999
22471
  const mode = p["mode"] === "url" ? "url" : "form";
22000
22472
  try {
22001
22473
  const response = await onElicitation({
@@ -22549,10 +23021,10 @@ async function initMcpLazyManager(toolset, servers, options = {}) {
22549
23021
  }
22550
23022
 
22551
23023
  // ../kernel/src/config/load.ts
22552
- import os2 from "node:os";
23024
+ import os3 from "node:os";
22553
23025
 
22554
23026
  // ../kernel/src/hooks/config.ts
22555
- import { createHash as createHash7 } from "node:crypto";
23027
+ import { createHash as createHash9 } from "node:crypto";
22556
23028
 
22557
23029
  // ../kernel/src/config/merge.ts
22558
23030
  var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
@@ -22582,12 +23054,12 @@ function deepMergeRawLayers(rawsAscending) {
22582
23054
  }
22583
23055
  return structuredClone(acc);
22584
23056
  }
22585
- function displayPath2(path20) {
22586
- return path20.map(String).join(".");
23057
+ function displayPath2(path23) {
23058
+ return path23.map(String).join(".");
22587
23059
  }
22588
- function getAtPath(root, path20) {
23060
+ function getAtPath(root, path23) {
22589
23061
  let cur = root;
22590
- for (const seg of path20) {
23062
+ for (const seg of path23) {
22591
23063
  if (typeof seg === "number") {
22592
23064
  if (!Array.isArray(cur) || seg < 0 || seg >= cur.length) return void 0;
22593
23065
  cur = cur[seg];
@@ -22598,11 +23070,11 @@ function getAtPath(root, path20) {
22598
23070
  }
22599
23071
  return cur;
22600
23072
  }
22601
- function hasPath(root, path20) {
22602
- if (path20.length === 0) return true;
23073
+ function hasPath(root, path23) {
23074
+ if (path23.length === 0) return true;
22603
23075
  let cur = root;
22604
- for (let i = 0; i < path20.length; i++) {
22605
- const seg = path20[i];
23076
+ for (let i = 0; i < path23.length; i++) {
23077
+ const seg = path23[i];
22606
23078
  if (typeof seg === "number") {
22607
23079
  if (!Array.isArray(cur) || seg < 0 || seg >= cur.length) return false;
22608
23080
  cur = cur[seg];
@@ -22613,25 +23085,25 @@ function hasPath(root, path20) {
22613
23085
  }
22614
23086
  return true;
22615
23087
  }
22616
- function sanitizeDropPath(root, path20) {
23088
+ function sanitizeDropPath(root, path23) {
22617
23089
  let cur = root;
22618
- for (let i = 0; i < path20.length; i++) {
22619
- const seg = path20[i];
23090
+ for (let i = 0; i < path23.length; i++) {
23091
+ const seg = path23[i];
22620
23092
  const next = getAtPath(cur, [seg]);
22621
- if (Array.isArray(next) && i < path20.length - 1) {
22622
- return path20.slice(0, i + 1);
23093
+ if (Array.isArray(next) && i < path23.length - 1) {
23094
+ return path23.slice(0, i + 1);
22623
23095
  }
22624
- if (typeof path20[i + 1] === "number") {
22625
- return path20.slice(0, i + 1);
23096
+ if (typeof path23[i + 1] === "number") {
23097
+ return path23.slice(0, i + 1);
22626
23098
  }
22627
23099
  cur = next;
22628
23100
  }
22629
- return path20;
23101
+ return path23;
22630
23102
  }
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];
23103
+ function deletePath(root, path23) {
23104
+ if (path23.length === 0) return;
23105
+ const parent = getAtPath(root, path23.slice(0, -1));
23106
+ const leaf = path23[path23.length - 1];
22635
23107
  if (isPlainObject2(parent) && typeof leaf === "string") {
22636
23108
  delete parent[leaf];
22637
23109
  }
@@ -22721,16 +23193,16 @@ function canonicalJson(value) {
22721
23193
  return JSON.stringify(value) ?? "null";
22722
23194
  }
22723
23195
  function configHash8(dedupeKey) {
22724
- return createHash7("sha256").update(dedupeKey, "utf8").digest("hex").slice(0, 8);
23196
+ return createHash9("sha256").update(dedupeKey, "utf8").digest("hex").slice(0, 8);
22725
23197
  }
22726
- function parseExecutor(value, layer, path20, file, addDiagnostic) {
23198
+ function parseExecutor(value, layer, path23, file, addDiagnostic) {
22727
23199
  const fileRef = file !== void 0 ? { file } : {};
22728
23200
  const invalid = (params) => {
22729
23201
  addDiagnostic({
22730
23202
  severity: "error",
22731
23203
  code: "hook_invalid_value",
22732
23204
  layer,
22733
- path: path20,
23205
+ path: path23,
22734
23206
  ...fileRef,
22735
23207
  params
22736
23208
  });
@@ -22778,7 +23250,7 @@ function parseExecutor(value, layer, path20, file, addDiagnostic) {
22778
23250
  severity: "error",
22779
23251
  code: "hook_url_invalid",
22780
23252
  layer,
22781
- path: `${path20}.url`,
23253
+ path: `${path23}.url`,
22782
23254
  ...fileRef,
22783
23255
  params: { reason: "unparsable" }
22784
23256
  });
@@ -22789,7 +23261,7 @@ function parseExecutor(value, layer, path20, file, addDiagnostic) {
22789
23261
  severity: "error",
22790
23262
  code: "hook_url_invalid",
22791
23263
  layer,
22792
- path: `${path20}.url`,
23264
+ path: `${path23}.url`,
22793
23265
  ...fileRef,
22794
23266
  params: { reason: "unsupported_protocol", protocol: parsed.protocol }
22795
23267
  });
@@ -23380,7 +23852,7 @@ function migrateLayerRaw(raw) {
23380
23852
 
23381
23853
  // ../kernel/src/config/paths.ts
23382
23854
  import { promises as fsPromises } from "node:fs";
23383
- import path16 from "node:path";
23855
+ import path18 from "node:path";
23384
23856
  var SETTINGS_FILE_NAME = "settings.json";
23385
23857
  var LOCAL_SETTINGS_FILE_NAME = "settings.local.json";
23386
23858
  var TANSR_DIR_NAME = ".tansr";
@@ -23407,27 +23879,27 @@ function normalizeForCompare(p, platform) {
23407
23879
  function defaultManagedPaths(platform, env) {
23408
23880
  if (platform === "win32") {
23409
23881
  const programData = readTrimmedEnv2(env, "PROGRAMDATA") ?? "C:\\ProgramData";
23410
- return [path16.join(programData, "tansr", MANAGED_SETTINGS_FILE_NAME)];
23882
+ return [path18.join(programData, "tansr", MANAGED_SETTINGS_FILE_NAME)];
23411
23883
  }
23412
- return [path16.posix.join("/etc", "tansr", MANAGED_SETTINGS_FILE_NAME)];
23884
+ return [path18.posix.join("/etc", "tansr", MANAGED_SETTINGS_FILE_NAME)];
23413
23885
  }
23414
23886
  async function discoverLayerFiles(options) {
23415
23887
  const { fs: fs3, env, platform } = options;
23416
23888
  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);
23889
+ const userDir = configDirOverride ?? path18.join(options.homedir, TANSR_DIR_NAME);
23890
+ const userFile = path18.join(userDir, SETTINGS_FILE_NAME);
23891
+ const homeKey = normalizeForCompare(path18.resolve(options.homedir), platform);
23420
23892
  let projectRoot;
23421
- const boundary = options.boundary === void 0 ? void 0 : normalizeForCompare(path16.resolve(options.boundary), platform);
23422
- let cursor = path16.resolve(options.cwd);
23893
+ const boundary = options.boundary === void 0 ? void 0 : normalizeForCompare(path18.resolve(options.boundary), platform);
23894
+ let cursor = path18.resolve(options.cwd);
23423
23895
  for (let depth = 0; depth < 256; depth++) {
23424
23896
  const homeAnchorInvisible = normalizeForCompare(cursor, platform) === homeKey;
23425
- if (!homeAnchorInvisible && await fs3.directoryExists(path16.join(cursor, TANSR_DIR_NAME))) {
23897
+ if (!homeAnchorInvisible && await fs3.directoryExists(path18.join(cursor, TANSR_DIR_NAME))) {
23426
23898
  projectRoot = cursor;
23427
23899
  break;
23428
23900
  }
23429
23901
  if (boundary !== void 0 && normalizeForCompare(cursor, platform) === boundary) break;
23430
- const parent = path16.dirname(cursor);
23902
+ const parent = path18.dirname(cursor);
23431
23903
  if (parent === cursor) break;
23432
23904
  cursor = parent;
23433
23905
  }
@@ -23436,8 +23908,8 @@ async function discoverLayerFiles(options) {
23436
23908
  managedCandidates,
23437
23909
  ...projectRoot !== void 0 ? {
23438
23910
  projectRoot,
23439
- projectSharedFile: path16.join(projectRoot, TANSR_DIR_NAME, SETTINGS_FILE_NAME),
23440
- projectLocalFile: path16.join(projectRoot, TANSR_DIR_NAME, LOCAL_SETTINGS_FILE_NAME)
23911
+ projectSharedFile: path18.join(projectRoot, TANSR_DIR_NAME, SETTINGS_FILE_NAME),
23912
+ projectLocalFile: path18.join(projectRoot, TANSR_DIR_NAME, LOCAL_SETTINGS_FILE_NAME)
23441
23913
  } : {},
23442
23914
  userFile
23443
23915
  };
@@ -23994,8 +24466,8 @@ function issueExpected(issue) {
23994
24466
  return issue.code;
23995
24467
  }
23996
24468
  }
23997
- function attributeLayer(layersDescending, path20) {
23998
- return layersDescending.find((entry) => hasPath(entry.raw, path20));
24469
+ function attributeLayer(layersDescending, path23) {
24470
+ return layersDescending.find((entry) => hasPath(entry.raw, path23));
23999
24471
  }
24000
24472
  function scanUnknownKeys(raw, parsed, pathSoFar, layersDescending, addDiagnostic) {
24001
24473
  if (!isPlainObject2(raw) || !isPlainObject2(parsed)) return;
@@ -24168,7 +24640,7 @@ async function loadConfig(options = {}) {
24168
24640
  const env = options.env ?? process.env;
24169
24641
  const discovered = await discoverLayerFiles({
24170
24642
  cwd: options.cwd ?? process.cwd(),
24171
- homedir: options.homedir ?? os2.homedir(),
24643
+ homedir: options.homedir ?? os3.homedir(),
24172
24644
  env,
24173
24645
  fs: fs3,
24174
24646
  platform: options.platform ?? process.platform,
@@ -24248,9 +24720,9 @@ var OPEN_FENCE_RE = /^---[ \t]*\r?\n/;
24248
24720
  var CLOSE_FENCE_RE = /^---[ \t]*$/;
24249
24721
  function splitFrontmatterBlock(rawText) {
24250
24722
  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);
24723
+ const open4 = OPEN_FENCE_RE.exec(text);
24724
+ if (open4 === null) return null;
24725
+ const rest = text.slice(open4[0].length);
24254
24726
  const lines = rest.split("\n");
24255
24727
  for (let i = 0; i < lines.length; i++) {
24256
24728
  const line = (lines[i] ?? "").replace(/\r$/, "");
@@ -25089,8 +25561,8 @@ var HookEngine = class {
25089
25561
 
25090
25562
  // ../kernel/src/skills/discover.ts
25091
25563
  import { promises as fsPromises2 } from "node:fs";
25092
- import os3 from "node:os";
25093
- import path17 from "node:path";
25564
+ import os4 from "node:os";
25565
+ import path19 from "node:path";
25094
25566
 
25095
25567
  // ../kernel/src/skills/types.ts
25096
25568
  var SKILLS_DIR_NAME = "skills";
@@ -25310,8 +25782,8 @@ async function scanFileSource(source, skillsDir, fs3, diagnostics) {
25310
25782
  const seen = /* @__PURE__ */ new Set();
25311
25783
  for (const dirName of [...subdirs].sort()) {
25312
25784
  if (dirName.startsWith(".") || dirName.startsWith("_")) continue;
25313
- const dir = path17.join(skillsDir, dirName);
25314
- const file = path17.join(dir, SKILL_FILE_NAME);
25785
+ const dir = path19.join(skillsDir, dirName);
25786
+ const file = path19.join(dir, SKILL_FILE_NAME);
25315
25787
  if (!SKILL_NAME_RE.test(dirName)) {
25316
25788
  diagnostics.push({ severity: "warning", code: "name_invalid", source, skill: dirName, file });
25317
25789
  continue;
@@ -25358,9 +25830,9 @@ async function discoverSkills(options = {}) {
25358
25830
  const fs3 = options.fs ?? defaultSkillsFileSystem();
25359
25831
  const env = options.env ?? process.env;
25360
25832
  const platform = options.platform ?? process.platform;
25361
- const homedir = options.homedir ?? os3.homedir();
25833
+ const homedir = options.homedir ?? os4.homedir();
25362
25834
  const cwd = options.cwd ?? process.cwd();
25363
- const explicitRoot = options.projectRoot === void 0 ? void 0 : path17.resolve(options.projectRoot);
25835
+ const explicitRoot = options.projectRoot === void 0 ? void 0 : path19.resolve(options.projectRoot);
25364
25836
  const discovered = await discoverLayerFiles({
25365
25837
  cwd: explicitRoot ?? cwd,
25366
25838
  homedir,
@@ -25371,13 +25843,13 @@ async function discoverSkills(options = {}) {
25371
25843
  ...explicitRoot !== void 0 ? { boundary: explicitRoot } : options.boundary !== void 0 ? { boundary: options.boundary } : {}
25372
25844
  });
25373
25845
  const builtinEntries = collectBuiltin(options.builtinSkills ?? [], diagnostics);
25374
- const userSkillsDir = path17.join(path17.dirname(discovered.userFile), SKILLS_DIR_NAME);
25846
+ const userSkillsDir = path19.join(path19.dirname(discovered.userFile), SKILLS_DIR_NAME);
25375
25847
  const userOutcome = await scanFileSource("user", userSkillsDir, fs3, diagnostics);
25376
25848
  const projectRoot = explicitRoot ?? discovered.projectRoot;
25377
25849
  let projectSkillsDir;
25378
25850
  let projectOutcome = { status: "missing" };
25379
25851
  if (projectRoot !== void 0) {
25380
- projectSkillsDir = path17.join(projectRoot, TANSR_DIR_NAME, SKILLS_DIR_NAME);
25852
+ projectSkillsDir = path19.join(projectRoot, TANSR_DIR_NAME, SKILLS_DIR_NAME);
25381
25853
  projectOutcome = await scanFileSource("project", projectSkillsDir, fs3, diagnostics);
25382
25854
  }
25383
25855
  const userEntries = userOutcome.status === "scanned" ? userOutcome.entries : [];
@@ -25451,7 +25923,7 @@ async function discoverSkills(options = {}) {
25451
25923
  }
25452
25924
 
25453
25925
  // ../kernel/src/skills/registry.ts
25454
- import path18 from "node:path";
25926
+ import path20 from "node:path";
25455
25927
  function errCode2(err) {
25456
25928
  if (err !== null && typeof err === "object" && "code" in err) {
25457
25929
  const code = err.code;
@@ -25514,7 +25986,7 @@ var SkillRegistry = class {
25514
25986
  /** 技能目录绝对路径(正文相对引用锚点;builtin 无) */
25515
25987
  static baseDirOf(entry) {
25516
25988
  if (entry.dir !== void 0) return entry.dir;
25517
- if (entry.file !== void 0) return path18.dirname(entry.file);
25989
+ if (entry.file !== void 0) return path20.dirname(entry.file);
25518
25990
  return void 0;
25519
25991
  }
25520
25992
  };
@@ -25718,9 +26190,9 @@ function defineSkill(options) {
25718
26190
  ];
25719
26191
  return { name: options.name, content: lines.join("\n") };
25720
26192
  }
25721
- var NO_DISCOVERY_ROOT = path19.resolve(path19.sep, ".tansr-sdk-no-discovery");
26193
+ var NO_DISCOVERY_ROOT = path21.resolve(path21.sep, ".tansr-sdk-no-discovery");
25722
26194
  function noDiscoveryFs(inner) {
25723
- const inVoid = (target) => path19.resolve(target).startsWith(NO_DISCOVERY_ROOT);
26195
+ const inVoid = (target) => path21.resolve(target).startsWith(NO_DISCOVERY_ROOT);
25724
26196
  const enoent = (target) => {
25725
26197
  const err = new Error(`ENOENT: no such file or directory ${target}`);
25726
26198
  err.code = "ENOENT";
@@ -25734,7 +26206,7 @@ function noDiscoveryFs(inner) {
25734
26206
  }
25735
26207
  async function assembleSdkSkills(options) {
25736
26208
  const fs3 = noDiscoveryFs(options.fs ?? defaultSkillsFileSystem());
25737
- const dirs = (options.dirs ?? []).map((dir) => path19.resolve(dir));
26209
+ const dirs = (options.dirs ?? []).map((dir) => path21.resolve(dir));
25738
26210
  for (const dir of dirs) {
25739
26211
  if (!await fs3.directoryExists(dir)) {
25740
26212
  throw new TansrSdkError(
@@ -27870,7 +28342,7 @@ var OpenAIResponsesAdapter = class {
27870
28342
  };
27871
28343
 
27872
28344
  // ../providers/src/retry/attempt-observer.ts
27873
- import { createHash as createHash8 } from "node:crypto";
28345
+ import { createHash as createHash10 } from "node:crypto";
27874
28346
  var PROVIDER_CALL_META_FIELD = "callMeta";
27875
28347
  function providerCallMetaOf(options) {
27876
28348
  if (options === void 0) return void 0;
@@ -27899,12 +28371,12 @@ function endpointKeyOf(baseUrl) {
27899
28371
  let normalized;
27900
28372
  try {
27901
28373
  const url = new URL(baseUrl);
27902
- const path20 = url.pathname.replace(/\/+$/, "");
27903
- normalized = `${url.protocol}//${url.host}${path20}`.toLowerCase();
28374
+ const path23 = url.pathname.replace(/\/+$/, "");
28375
+ normalized = `${url.protocol}//${url.host}${path23}`.toLowerCase();
27904
28376
  } catch {
27905
28377
  normalized = baseUrl.trim().replace(/\/+$/, "").toLowerCase();
27906
28378
  }
27907
- return createHash8("sha256").update(normalized, "utf8").digest("hex").slice(0, 16);
28379
+ return createHash10("sha256").update(normalized, "utf8").digest("hex").slice(0, 16);
27908
28380
  }
27909
28381
  function safeAttemptCall(fn) {
27910
28382
  if (fn === void 0) return;
@@ -28041,14 +28513,14 @@ function sendableTwpReasoningOff(features) {
28041
28513
  }
28042
28514
 
28043
28515
  // ../providers/src/twp/signing.ts
28044
- import { createHash as createHash9, createHmac, randomBytes } from "node:crypto";
28516
+ import { createHash as createHash11, createHmac, randomBytes } from "node:crypto";
28045
28517
  var TWP_SIGNING_ALGORITHM = "TWP1-HMAC-SHA256";
28046
28518
  var TWP_NONCE_LENGTH = 24;
28047
28519
  function deriveTwpSigningKey(secretKey) {
28048
- return createHash9("sha256").update(secretKey, "utf8").digest();
28520
+ return createHash11("sha256").update(secretKey, "utf8").digest();
28049
28521
  }
28050
28522
  function sha256Hex(data) {
28051
- return createHash9("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
28523
+ return createHash11("sha256").update(typeof data === "string" ? Buffer.from(data, "utf8") : data).digest("hex");
28052
28524
  }
28053
28525
  function generateTwpNonce(random = randomBytes) {
28054
28526
  return random(TWP_NONCE_LENGTH / 2).toString("hex");
@@ -28290,11 +28762,11 @@ function twpHttpStatusToIR(status, bodyText) {
28290
28762
  if (status >= 500) return { kind: "server", message, recoverable: true };
28291
28763
  return { kind: "invalid_request", message, recoverable: false };
28292
28764
  }
28293
- function isRecord6(value) {
28765
+ function isRecord7(value) {
28294
28766
  return typeof value === "object" && value !== null && !Array.isArray(value);
28295
28767
  }
28296
28768
  function twpNonStreamToEvents(raw, fallbackModel) {
28297
- if (!isRecord6(raw)) {
28769
+ if (!isRecord7(raw)) {
28298
28770
  return [
28299
28771
  {
28300
28772
  t: "error",
@@ -28312,7 +28784,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
28312
28784
  const blocks = Array.isArray(raw["blocks"]) ? raw["blocks"] : [];
28313
28785
  let index = 0;
28314
28786
  for (const rawBlock of blocks) {
28315
- if (!isRecord6(rawBlock)) continue;
28787
+ if (!isRecord7(rawBlock)) continue;
28316
28788
  if (rawBlock["t"] === "text" && typeof rawBlock["v"] === "string") {
28317
28789
  out.push({ t: "block_start", index, block: { t: "text", text: rawBlock["v"] } });
28318
28790
  out.push({ t: "block_stop", index });
@@ -28332,7 +28804,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
28332
28804
  index += 1;
28333
28805
  }
28334
28806
  }
28335
- const usage = isRecord6(raw["usage"]) ? twpUsageToIR(parseTwpUsage(raw["usage"])) : void 0;
28807
+ const usage = isRecord7(raw["usage"]) ? twpUsageToIR(parseTwpUsage(raw["usage"])) : void 0;
28336
28808
  out.push({
28337
28809
  t: "message_stop",
28338
28810
  stopReason: mapTwpStop(typeof raw["stop"] === "string" ? raw["stop"] : void 0),
@@ -28341,7 +28813,7 @@ function twpNonStreamToEvents(raw, fallbackModel) {
28341
28813
  return out;
28342
28814
  }
28343
28815
  function twpNonStreamUsageFrame(raw) {
28344
- if (!isRecord6(raw) || !isRecord6(raw["usage"])) return null;
28816
+ if (!isRecord7(raw) || !isRecord7(raw["usage"])) return null;
28345
28817
  return parseTwpUsage(raw["usage"]);
28346
28818
  }
28347
28819
 
@@ -29023,9 +29495,9 @@ var MissingApiKeyError = class extends ProviderRegistryError {
29023
29495
  var CyclicFallbackError = class extends ProviderRegistryError {
29024
29496
  /** 成环路径,末项为再次出现的别名,如 ['main','fast','main'] */
29025
29497
  path;
29026
- constructor(path20) {
29027
- super("cyclic_fallback", `Cyclic fallback chain: ${path20.join(" -> ")}`);
29028
- this.path = path20;
29498
+ constructor(path23) {
29499
+ super("cyclic_fallback", `Cyclic fallback chain: ${path23.join(" -> ")}`);
29500
+ this.path = path23;
29029
29501
  }
29030
29502
  };
29031
29503
 
@@ -29508,9 +29980,9 @@ function pushUnique(out, seen, resolved) {
29508
29980
  seen.add(dedupeKey);
29509
29981
  out.push(resolved);
29510
29982
  }
29511
- function expandInto(config, alias, path20, out, seen) {
29512
- if (path20.includes(alias)) throw new CyclicFallbackError([...path20, alias]);
29513
- const nextPath = [...path20, alias];
29983
+ function expandInto(config, alias, path23, out, seen) {
29984
+ if (path23.includes(alias)) throw new CyclicFallbackError([...path23, alias]);
29985
+ const nextPath = [...path23, alias];
29514
29986
  pushUnique(out, seen, resolveAliasOrRef(config, alias));
29515
29987
  for (const entry of config.fallbacks[alias] ?? []) {
29516
29988
  if (entry.includes("/")) {
@@ -30337,8 +30809,10 @@ var AppQuotaSchema = z41.object({
30337
30809
  import { z as z42 } from "zod";
30338
30810
  var IMAGEGEN_TOOL_MAX_N = 4;
30339
30811
  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)."
30812
+ // S-G1(媒体池化顺位取用):Optional——缺席即不发 model 位,网关按授权集顺位
30813
+ // 第一生效( bundle platformModels.imageGen 首行恒同);点名限集内(工具描述自列)。
30814
+ model: z42.string().min(1).optional().describe(
30815
+ "Image model name from the authorized set listed in this tool description. Omit to use the default (the first authorized model)."
30342
30816
  ),
30343
30817
  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
30818
  negativePrompt: z42.string().min(1).max(500).optional().describe("Negative prompt: content to keep out of the image."),
@@ -30368,23 +30842,34 @@ function hintOf(code) {
30368
30842
  }
30369
30843
  return "";
30370
30844
  }
30845
+ function authorizedModelsNote(models) {
30846
+ if (models === void 0) {
30847
+ return ' Omit "model" to use the app default, or ask the app developer for authorized model names.';
30848
+ }
30849
+ if (models.length === 0) {
30850
+ return " The platform has no image model configured for this app yet — tell the user instead of retrying or guessing model names.";
30851
+ }
30852
+ const listed = models.map((m) => m.displayName !== m.model ? `${m.model} (${m.displayName})` : m.model).join(", ");
30853
+ return ` Authorized image models (omit "model" to use the first as default): ${listed}.`;
30854
+ }
30371
30855
  function createImageGenTool(options) {
30372
30856
  const base = options.baseUrl.replace(/\/+$/, "");
30373
30857
  const fetchImpl = options.fetchImpl ?? fetch;
30374
30858
  const token = options.token;
30375
30859
  return {
30376
30860
  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?.",
30861
+ 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),
30862
+ shortDescription: "Generate images from a prompt via the tansr platform (billed per image). Args: model?, prompt, negativePrompt?, size?, n?, seed?.",
30379
30863
  inputSchema: ImageGenArgsSchema,
30380
30864
  isReadOnly: false,
30381
30865
  isConcurrencySafe: false,
30382
30866
  async execute(args, ctx) {
30867
+ const modelRef = args.model ?? "(default)";
30383
30868
  if (ctx.signal.aborted) {
30384
- return errorResult13("Tool execution was aborted.", args.model);
30869
+ return errorResult13("Tool execution was aborted.", modelRef);
30385
30870
  }
30386
30871
  const payload = {
30387
- model: args.model,
30872
+ ...args.model !== void 0 ? { model: args.model } : {},
30388
30873
  prompt: args.prompt,
30389
30874
  ...args.negativePrompt !== void 0 ? { negativePrompt: args.negativePrompt } : {},
30390
30875
  ...args.size !== void 0 ? { size: args.size } : {},
@@ -30404,9 +30889,9 @@ function createImageGenTool(options) {
30404
30889
  signal: ctx.signal
30405
30890
  });
30406
30891
  } catch (err) {
30407
- if (ctx.signal.aborted) return errorResult13("Tool execution was aborted.", args.model);
30892
+ if (ctx.signal.aborted) return errorResult13("Tool execution was aborted.", modelRef);
30408
30893
  const message = err instanceof Error ? err.message : String(err);
30409
- return errorResult13(`Image generation request failed to reach the platform: ${message}`, args.model);
30894
+ return errorResult13(`Image generation request failed to reach the platform: ${message}`, modelRef);
30410
30895
  }
30411
30896
  let raw = null;
30412
30897
  try {
@@ -30418,17 +30903,18 @@ function createImageGenTool(options) {
30418
30903
  const envelope = raw ?? {};
30419
30904
  const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
30420
30905
  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);
30906
+ return errorResult13(`Image generation failed (${code}): ${message}.${hintOf(code)}`, modelRef, code);
30422
30907
  }
30423
30908
  const body = raw ?? {};
30424
30909
  const images = Array.isArray(body.images) ? body.images.map((item) => typeof item.url === "string" ? { url: item.url } : null).filter((item) => item !== null) : [];
30425
30910
  const imageCount = typeof body.imageCount === "number" ? body.imageCount : images.length;
30426
30911
  if (images.length === 0) {
30427
- return errorResult13("Image generation returned no image URL (unexpected platform response).", args.model);
30912
+ return errorResult13("Image generation returned no image URL (unexpected platform response).", modelRef);
30428
30913
  }
30429
- const data = { model: args.model, images, imageCount };
30914
+ const resolvedModel = typeof body.model === "string" && body.model !== "" ? body.model : modelRef;
30915
+ const data = { model: resolvedModel, images, imageCount };
30430
30916
  const lines = [
30431
- `Generated ${imageCount} image(s) with model ${args.model} (billed per image).`,
30917
+ `Generated ${imageCount} image(s) with model ${resolvedModel} (billed per image).`,
30432
30918
  "Image URLs (valid ~24h; surface or persist promptly):",
30433
30919
  ...images.map((img, i) => `${i + 1}. ${img.url}`)
30434
30920
  ];
@@ -30441,8 +30927,10 @@ function createImageGenTool(options) {
30441
30927
  import { z as z43 } from "zod";
30442
30928
  var VIDEOGEN_TOOL_MAX_DURATION = 30;
30443
30929
  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)."
30930
+ // S-G1(媒体池化顺位取用):Optional——缺席即不发 model 位,网关按授权集顺位
30931
+ // 第一生效( bundle platformModels.videoGen 首行恒同);点名限集内(工具描述自列)。
30932
+ model: z43.string().min(1).optional().describe(
30933
+ "Video model name from the authorized set listed in this tool description. Omit to use the default (the first authorized model)."
30446
30934
  ),
30447
30935
  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
30936
  negativePrompt: z43.string().min(1).max(500).optional().describe("Negative prompt: content to keep out of the video."),
@@ -30480,23 +30968,34 @@ function hintOf2(code) {
30480
30968
  }
30481
30969
  return "";
30482
30970
  }
30971
+ function authorizedModelsNote2(models) {
30972
+ if (models === void 0) {
30973
+ return ' Omit "model" to use the app default, or ask the app developer for authorized model names.';
30974
+ }
30975
+ if (models.length === 0) {
30976
+ return " The platform has no video model configured for this app yet — tell the user instead of retrying or guessing model names.";
30977
+ }
30978
+ const listed = models.map((m) => m.displayName !== m.model ? `${m.model} (${m.displayName})` : m.model).join(", ");
30979
+ return ` Authorized video models (omit "model" to use the first as default): ${listed}.`;
30980
+ }
30483
30981
  function createVideoGenTool(options) {
30484
30982
  const base = options.baseUrl.replace(/\/+$/, "");
30485
30983
  const fetchImpl = options.fetchImpl ?? fetch;
30486
30984
  const token = options.token;
30487
30985
  return {
30488
30986
  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?.",
30987
+ 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),
30988
+ shortDescription: "Generate a video from a prompt via the tansr platform (billed per second). Args: model?, prompt, negativePrompt?, duration?, ratio?, seed?.",
30491
30989
  inputSchema: VideoGenArgsSchema,
30492
30990
  isReadOnly: false,
30493
30991
  isConcurrencySafe: false,
30494
30992
  async execute(args, ctx) {
30993
+ const modelRef = args.model ?? "(default)";
30495
30994
  if (ctx.signal.aborted) {
30496
- return errorResult14("Tool execution was aborted.", args.model);
30995
+ return errorResult14("Tool execution was aborted.", modelRef);
30497
30996
  }
30498
30997
  const payload = {
30499
- model: args.model,
30998
+ ...args.model !== void 0 ? { model: args.model } : {},
30500
30999
  prompt: args.prompt,
30501
31000
  ...args.negativePrompt !== void 0 ? { negativePrompt: args.negativePrompt } : {},
30502
31001
  ...args.duration !== void 0 ? { duration: args.duration } : {},
@@ -30516,9 +31015,9 @@ function createVideoGenTool(options) {
30516
31015
  signal: ctx.signal
30517
31016
  });
30518
31017
  } catch (err) {
30519
- if (ctx.signal.aborted) return errorResult14("Tool execution was aborted.", args.model);
31018
+ if (ctx.signal.aborted) return errorResult14("Tool execution was aborted.", modelRef);
30520
31019
  const message = err instanceof Error ? err.message : String(err);
30521
- return errorResult14(`Video generation request failed to reach the platform: ${message}`, args.model);
31020
+ return errorResult14(`Video generation request failed to reach the platform: ${message}`, modelRef);
30522
31021
  }
30523
31022
  let raw = null;
30524
31023
  try {
@@ -30530,18 +31029,19 @@ function createVideoGenTool(options) {
30530
31029
  const envelope = raw ?? {};
30531
31030
  const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
30532
31031
  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);
31032
+ return errorResult14(`Video generation failed (${code}): ${message}.${hintOf2(code)}`, modelRef, code);
30534
31033
  }
30535
31034
  const body = raw ?? {};
30536
31035
  const videos = Array.isArray(body.videos) ? body.videos.map((item) => typeof item.url === "string" ? { url: item.url } : null).filter((item) => item !== null) : [];
30537
31036
  if (videos.length === 0) {
30538
- return errorResult14("Video generation returned no video URL (unexpected platform response).", args.model);
31037
+ return errorResult14("Video generation returned no video URL (unexpected platform response).", modelRef);
30539
31038
  }
30540
31039
  const videoCount = typeof body.videoCount === "number" ? body.videoCount : videos.length;
30541
31040
  const billedSeconds = typeof body.billedSeconds === "number" ? body.billedSeconds : 0;
30542
- const data = { model: args.model, videos, videoCount, billedSeconds };
31041
+ const resolvedModel = typeof body.model === "string" && body.model !== "" ? body.model : modelRef;
31042
+ const data = { model: resolvedModel, videos, videoCount, billedSeconds };
30543
31043
  const lines = [
30544
- `Generated ${videoCount} video(s) with model ${args.model} (${billedSeconds}s billed, per-second pricing).`,
31044
+ `Generated ${videoCount} video(s) with model ${resolvedModel} (${billedSeconds}s billed, per-second pricing).`,
30545
31045
  "Video URLs (valid ~24h; surface or persist promptly):",
30546
31046
  ...videos.map((v, i) => `${i + 1}. ${v.url}`)
30547
31047
  ];
@@ -30550,24 +31050,8 @@ function createVideoGenTool(options) {
30550
31050
  };
30551
31051
  }
30552
31052
 
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
- }
31053
+ // src/platform/websearch-provider.ts
31054
+ var PLATFORM_SEARCH_PROVIDER_NAME = "tansr-platform";
30571
31055
  function hintOf3(code) {
30572
31056
  if (code === "forbidden") {
30573
31057
  return " The app platform capability webSearch is disabled; the app developer can enable it in console → app → capabilities.";
@@ -30589,25 +31073,13 @@ function hintOf3(code) {
30589
31073
  }
30590
31074
  return "";
30591
31075
  }
30592
- function createWebSearchTool2(options) {
31076
+ function createPlatformSearchProvider(options) {
30593
31077
  const base = options.baseUrl.replace(/\/+$/, "");
30594
31078
  const fetchImpl = options.fetchImpl ?? fetch;
30595
31079
  const token = options.token;
30596
31080
  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
- };
31081
+ name: PLATFORM_SEARCH_PROVIDER_NAME,
31082
+ async search(query2, context) {
30611
31083
  let response;
30612
31084
  try {
30613
31085
  response = await fetchImpl(`${base}/t1/websearch`, {
@@ -30617,13 +31089,15 @@ function createWebSearchTool2(options) {
30617
31089
  accept: "application/json",
30618
31090
  [HEADER_APP_TOKEN]: token
30619
31091
  },
30620
- body: JSON.stringify(payload),
30621
- signal: ctx.signal
31092
+ // wire 体 snake_case(CLI 内核 HTTP 后端字节同构的既有特例);
31093
+ // maxResults 已被 kernel 工具层夹紧(缺省 8 / 上限 20,与网关帽同值)。
31094
+ body: JSON.stringify({ query: query2.query, max_results: query2.maxResults }),
31095
+ signal: context.signal
30622
31096
  });
30623
31097
  } catch (err) {
30624
- if (ctx.signal.aborted) return errorResult15("Tool execution was aborted.", args.query);
31098
+ if (context.signal.aborted) throw err;
30625
31099
  const message = err instanceof Error ? err.message : String(err);
30626
- return errorResult15(`Web search request failed to reach the platform: ${message}`, args.query);
31100
+ throw new Error(`web search request failed to reach the tansr platform: ${message}`);
30627
31101
  }
30628
31102
  let raw = null;
30629
31103
  try {
@@ -30635,13 +31109,13 @@ function createWebSearchTool2(options) {
30635
31109
  const envelope = raw ?? {};
30636
31110
  const code = typeof envelope.error?.code === "string" ? envelope.error.code : `http_${response.status}`;
30637
31111
  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);
31112
+ throw new Error(`platform web search failed (${code}): ${message}.${hintOf3(code)}`);
30639
31113
  }
30640
31114
  const body = raw ?? {};
30641
31115
  if (!Array.isArray(body.results)) {
30642
- return errorResult15("Web search returned an unexpected platform response (no results array).", args.query);
31116
+ throw new Error("platform web search returned an unexpected response (no results array).");
30643
31117
  }
30644
- const results = body.results.map((item) => {
31118
+ return body.results.map((item) => {
30645
31119
  const row = item ?? {};
30646
31120
  if (typeof row.title !== "string" || typeof row.url !== "string") return null;
30647
31121
  return {
@@ -30650,19 +31124,6 @@ function createWebSearchTool2(options) {
30650
31124
  snippet: typeof row.snippet === "string" ? row.snippet : ""
30651
31125
  };
30652
31126
  }).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
31127
  }
30667
31128
  };
30668
31129
  }
@@ -30682,7 +31143,7 @@ var BUILTIN_ORDER = [
30682
31143
  "webSearch",
30683
31144
  "http"
30684
31145
  ];
30685
- var PLATFORM_NAMES = ["imageGen", "videoGen", "webSearch"];
31146
+ var PLATFORM_NAMES = ["imageGen", "videoGen"];
30686
31147
  function toToolDef(tool) {
30687
31148
  const converted = zodToJsonSchema(tool.inputSchema);
30688
31149
  const { $schema: _dropped, ...inputSchema } = converted;
@@ -30718,6 +31179,12 @@ function resolvePlatformSelection(selection, capabilities) {
30718
31179
  if (selection === void 0) return [];
30719
31180
  const requested = /* @__PURE__ */ new Set();
30720
31181
  for (const name of selection) {
31182
+ if (name === "webSearch") {
31183
+ throw new TansrSdkError(
31184
+ "invalid_options",
31185
+ "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)."
31186
+ );
31187
+ }
30721
31188
  if (!PLATFORM_NAMES.includes(name)) {
30722
31189
  throw new TansrSdkError(
30723
31190
  "invalid_options",
@@ -30785,7 +31252,7 @@ function buildBuiltinTool(name, materials) {
30785
31252
  case "webFetch":
30786
31253
  return createWebFetchTool();
30787
31254
  case "webSearch":
30788
- return createWebSearchTool({ provider: createHttpSearchProvider() });
31255
+ return createWebSearchTool({ provider: materials.searchProvider });
30789
31256
  case "http":
30790
31257
  return createHttpTool();
30791
31258
  }
@@ -30793,9 +31260,37 @@ function buildBuiltinTool(name, materials) {
30793
31260
  function buildSdkToolSet(options = {}) {
30794
31261
  const capabilities = options.capabilities ?? DEFAULT_APP_CAPABILITIES;
30795
31262
  const selection = options.tools ?? {};
30796
- const builtinNames = resolveBuiltinSelection(selection.builtin, capabilities);
31263
+ let builtinNames = resolveBuiltinSelection(selection.builtin, capabilities);
30797
31264
  const platformNames = resolvePlatformSelection(selection.platform, capabilities);
30798
31265
  validateCustomSelection(selection.custom, capabilities);
31266
+ let webSearchProvider = null;
31267
+ if (builtinNames.includes("webSearch")) {
31268
+ const pc = options.platformContext;
31269
+ const explicit = selection.builtin !== void 0;
31270
+ if (pc === void 0) {
31271
+ if (explicit) {
31272
+ throw new TansrSdkError(
31273
+ "invalid_options",
31274
+ "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."
31275
+ );
31276
+ }
31277
+ builtinNames = builtinNames.filter((name) => name !== "webSearch");
31278
+ } else if (!capabilities.platform.webSearch) {
31279
+ if (explicit) {
31280
+ throw new TansrSdkError(
31281
+ "capability_disabled",
31282
+ `App platform capability 'webSearch' is disabled; ${CONSOLE_HINT}, or remove 'webSearch' from tools.builtin.`
31283
+ );
31284
+ }
31285
+ builtinNames = builtinNames.filter((name) => name !== "webSearch");
31286
+ } else {
31287
+ webSearchProvider = createPlatformSearchProvider({
31288
+ baseUrl: pc.baseUrl,
31289
+ token: pc.token,
31290
+ ...pc.fetchImpl !== void 0 ? { fetchImpl: pc.fetchImpl } : {}
31291
+ });
31292
+ }
31293
+ }
30799
31294
  const platformTools = [];
30800
31295
  for (const name of platformNames) {
30801
31296
  const pc = options.platformContext;
@@ -30811,14 +31306,14 @@ function buildSdkToolSet(options = {}) {
30811
31306
  ...pc.fetchImpl !== void 0 ? { fetchImpl: pc.fetchImpl } : {}
30812
31307
  };
30813
31308
  platformTools.push(
30814
- name === "imageGen" ? createImageGenTool(materials) : name === "videoGen" ? createVideoGenTool(materials) : createWebSearchTool2(materials)
31309
+ name === "imageGen" ? createImageGenTool({ ...materials, ...pc.platformModels !== void 0 ? { models: pc.platformModels.imageGen } : {} }) : createVideoGenTool({ ...materials, ...pc.platformModels !== void 0 ? { models: pc.platformModels.videoGen } : {} })
30815
31310
  );
30816
31311
  }
30817
31312
  const store = options.store ?? new TodoStore();
30818
31313
  const channel = options.promptChannel ?? new UnavailableChannel();
30819
31314
  const custom = selection.custom ?? [];
30820
31315
  const tools = [
30821
- ...builtinNames.map((name) => buildBuiltinTool(name, { store, channel })),
31316
+ ...builtinNames.map((name) => buildBuiltinTool(name, { store, channel, searchProvider: webSearchProvider })),
30822
31317
  ...platformTools,
30823
31318
  ...custom
30824
31319
  ];
@@ -31001,11 +31496,11 @@ async function assembleTooling(options) {
31001
31496
  var TANSR_PROVIDER_ID = "tansr";
31002
31497
  var APP_TOKEN_PLACEHOLDER_ENV = "TANSR_SDK_APP_TOKEN_TIER";
31003
31498
  var APP_TOKEN_PLACEHOLDER_VALUE = "app-token-tier";
31004
- function isRecord7(value) {
31499
+ function isRecord8(value) {
31005
31500
  return typeof value === "object" && value !== null && !Array.isArray(value);
31006
31501
  }
31007
31502
  function parseModel(raw) {
31008
- if (!isRecord7(raw)) return null;
31503
+ if (!isRecord8(raw)) return null;
31009
31504
  if (typeof raw.handle !== "string" || raw.handle === "" || typeof raw.modelId !== "string" || raw.modelId === "" || typeof raw.protocol !== "string") {
31010
31505
  return null;
31011
31506
  }
@@ -31013,25 +31508,45 @@ function parseModel(raw) {
31013
31508
  handle: raw.handle,
31014
31509
  modelId: raw.modelId,
31015
31510
  displayName: typeof raw.displayName === "string" ? raw.displayName : raw.handle,
31511
+ // 模型呈现面二波(④)加法解析:缺键/坏形状 = null(旧 bundle 回落,恒不猜)
31512
+ manufacturer: typeof raw.manufacturer === "string" && raw.manufacturer !== "" ? raw.manufacturer : null,
31513
+ family: typeof raw.family === "string" && raw.family !== "" ? raw.family : null,
31016
31514
  protocol: raw.protocol,
31017
- capabilities: isRecord7(raw.capabilities) ? raw.capabilities : {},
31515
+ capabilities: isRecord8(raw.capabilities) ? raw.capabilities : {},
31018
31516
  contextWindow: typeof raw.contextWindow === "number" ? raw.contextWindow : null
31019
31517
  };
31020
31518
  }
31519
+ function parseMediaModels(raw) {
31520
+ if (!Array.isArray(raw)) return [];
31521
+ const out = [];
31522
+ for (const item of raw) {
31523
+ if (!isRecord8(item) || typeof item.model !== "string" || item.model === "") continue;
31524
+ out.push({
31525
+ model: item.model,
31526
+ displayName: typeof item.displayName === "string" && item.displayName !== "" ? item.displayName : item.model
31527
+ });
31528
+ }
31529
+ return out;
31530
+ }
31021
31531
  function parseAppBundle(raw) {
31022
- if (!isRecord7(raw)) return null;
31532
+ if (!isRecord8(raw)) return null;
31023
31533
  const models = Array.isArray(raw.models) ? raw.models.map(parseModel).filter((m) => m !== null) : [];
31024
31534
  const aliases = {};
31025
- if (isRecord7(raw.aliases)) {
31535
+ if (isRecord8(raw.aliases)) {
31026
31536
  for (const [key2, value] of Object.entries(raw.aliases)) {
31027
31537
  if (typeof value === "string" && value !== "") aliases[key2] = value;
31028
31538
  }
31029
31539
  }
31030
31540
  const parsedCaps = AppCapabilitiesSchema.safeParse(raw.capabilities);
31541
+ const mediaRaw = isRecord8(raw.platformModels) ? raw.platformModels : {};
31031
31542
  return {
31032
31543
  models,
31033
31544
  aliases,
31034
- capabilities: parsedCaps.success ? parsedCaps.data : DEFAULT_APP_CAPABILITIES
31545
+ capabilities: parsedCaps.success ? parsedCaps.data : DEFAULT_APP_CAPABILITIES,
31546
+ platformModels: {
31547
+ imageGen: parseMediaModels(mediaRaw.imageGen),
31548
+ videoGen: parseMediaModels(mediaRaw.videoGen)
31549
+ }
31035
31550
  };
31036
31551
  }
31037
31552
  function sanitizeModelCapabilities(capabilities, contextWindow) {
@@ -31172,10 +31687,13 @@ async function assemblePlatformModel(options) {
31172
31687
  const registry = new ProviderRegistry(appBundleRegistryConfig(bundle, base), {
31173
31688
  // 占位哑值:满足装配期 readApiKey 非空校验;真实鉴权恒由 fetch 包装注入
31174
31689
  env: { [APP_TOKEN_PLACEHOLDER_ENV]: APP_TOKEN_PLACEHOLDER_VALUE },
31175
- fetchImpl: createAppTokenFetch(options.token, options.fetchImpl)
31690
+ fetchImpl: createAppTokenFetch(options.token, options.fetchImpl),
31691
+ // 拍板⑤:平台会话 id 供给经既有 TwpClientOptions.sessionId 缝出线——
31692
+ // registry 为会话私有(每次装配新建),setModel/Task 子代理同缝同值
31693
+ ...options.sessionId !== void 0 ? { twp: { sessionId: options.sessionId } } : {}
31176
31694
  });
31177
31695
  const built = buildClientFromRegistry(registry, options.model ?? "main", options.onProviderSelected);
31178
- return { ...built, registry, capabilities: bundle.capabilities };
31696
+ return { ...built, registry, capabilities: bundle.capabilities, platformModels: bundle.platformModels };
31179
31697
  }
31180
31698
 
31181
31699
  // src/task.ts
@@ -31264,7 +31782,9 @@ async function* consumeManaged(options) {
31264
31782
  platformContext = {
31265
31783
  baseUrl: options.baseUrl,
31266
31784
  token: options.token,
31267
- ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
31785
+ ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
31786
+ // S-G1:bundle 授权媒体集随装配注入(媒体工具描述自列;集成方零手配)。
31787
+ platformModels: platform.platformModels
31268
31788
  };
31269
31789
  } else {
31270
31790
  const managed = await assembleManagedModel({
@@ -31362,6 +31882,60 @@ async function* consumeHandle(handle) {
31362
31882
 
31363
31883
  // src/session.ts
31364
31884
  import { randomUUID as randomUUID11 } from "node:crypto";
31885
+
31886
+ // src/sessions/pairing.ts
31887
+ var SYNTHETIC_TOOL_RESULT_TEXT = "Tool execution was interrupted before its result was persisted; this placeholder was synthesized while resuming the session.";
31888
+ function toolCallIds(message) {
31889
+ return message.blocks.flatMap((b) => b.t === "tool_call" ? [b.id] : []);
31890
+ }
31891
+ function toolResultIds(message) {
31892
+ return message.blocks.flatMap((b) => b.t === "tool_result" ? [b.callId] : []);
31893
+ }
31894
+ function repairHistoryPairing(messages) {
31895
+ const open4 = /* @__PURE__ */ new Set();
31896
+ let cleanEnd = 0;
31897
+ for (let i = 0; i < messages.length; i++) {
31898
+ const message = messages[i];
31899
+ if (message.role === "assistant") {
31900
+ for (const id of toolCallIds(message)) open4.add(id);
31901
+ } else {
31902
+ for (const callId of toolResultIds(message)) {
31903
+ if (!open4.has(callId)) {
31904
+ return {
31905
+ messages: messages.slice(0, cleanEnd).map((m) => structuredClone(m)),
31906
+ synthesizedResults: 0,
31907
+ droppedMessages: messages.length - cleanEnd
31908
+ };
31909
+ }
31910
+ open4.delete(callId);
31911
+ }
31912
+ }
31913
+ if (open4.size === 0) cleanEnd = i + 1;
31914
+ }
31915
+ if (open4.size === 0) {
31916
+ return {
31917
+ messages: messages.map((m) => structuredClone(m)),
31918
+ synthesizedResults: 0,
31919
+ droppedMessages: 0
31920
+ };
31921
+ }
31922
+ const synthetic = {
31923
+ role: "user",
31924
+ blocks: [...open4].map((callId) => ({
31925
+ t: "tool_result",
31926
+ callId,
31927
+ content: [{ t: "text", text: SYNTHETIC_TOOL_RESULT_TEXT }],
31928
+ isError: true
31929
+ }))
31930
+ };
31931
+ return {
31932
+ messages: [...messages.map((m) => structuredClone(m)), synthetic],
31933
+ synthesizedResults: open4.size,
31934
+ droppedMessages: 0
31935
+ };
31936
+ }
31937
+
31938
+ // src/session.ts
31365
31939
  function createEventQueue() {
31366
31940
  const buffered = [];
31367
31941
  let ended = false;
@@ -31637,7 +32211,21 @@ var AgentSession = class {
31637
32211
  };
31638
32212
  async function createSession(options = {}) {
31639
32213
  const cwd = options.cwd ?? process.cwd();
31640
- const sessionId = options.sessionId ?? randomUUID11();
32214
+ if (options.resume !== void 0) {
32215
+ if (options.initialMessages !== void 0) {
32216
+ throw new TansrSdkError(
32217
+ "invalid_options",
32218
+ 'createSession: "resume" and "initialMessages" are mutually exclusive; "resume" loads the history from the store ("initialMessages" is the low-level re-feed escape hatch).'
32219
+ );
32220
+ }
32221
+ if (options.sessionId !== void 0 && options.sessionId !== options.resume.sessionId) {
32222
+ throw new TansrSdkError(
32223
+ "invalid_options",
32224
+ 'createSession: "sessionId" conflicts with "resume.sessionId"; pass one of them (they must match).'
32225
+ );
32226
+ }
32227
+ }
32228
+ const sessionId = options.sessionId ?? options.resume?.sessionId ?? randomUUID11();
31641
32229
  const injected = options.client !== void 0;
31642
32230
  const tokenTier = options.token !== void 0;
31643
32231
  if (injected && typeof options.model === "string") {
@@ -31653,6 +32241,18 @@ async function createSession(options = {}) {
31653
32241
  );
31654
32242
  }
31655
32243
  validateTokenTierOptions("createSession", options);
32244
+ let initialMessages = options.initialMessages ?? [];
32245
+ if (options.resume !== void 0) {
32246
+ const record = await options.resume.store.get(options.resume.sessionId);
32247
+ if (record === null) {
32248
+ throw new TansrSdkError(
32249
+ "session_not_found",
32250
+ `createSession: session "${options.resume.sessionId}" was not found in the given store; list() the store or create a fresh session instead.`
32251
+ );
32252
+ }
32253
+ initialMessages = repairHistoryPairing(record.messages).messages;
32254
+ }
32255
+ const platformSessionId = tokenTier ? ulid() : void 0;
31656
32256
  let sessionRef = null;
31657
32257
  const emitBody = (body, source) => {
31658
32258
  sessionRef?.pushBody(body, source);
@@ -31675,6 +32275,9 @@ async function createSession(options = {}) {
31675
32275
  baseUrl: options.baseUrl,
31676
32276
  ...typeof options.model === "string" ? { model: options.model } : {},
31677
32277
  ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
32278
+ // 拍板⑤:本会话全部 exchange(含 setModel 热切换与 Task 子代理,同
32279
+ // registry 同缝)恒携同一枚平台会话 ULID——会话维归因/亲和自此在场
32280
+ ...platformSessionId !== void 0 ? { sessionId: () => platformSessionId } : {},
31678
32281
  onProviderSelected: (selection) => {
31679
32282
  const body = providerSwitchedBody(selection);
31680
32283
  if (body !== null) emitBody(body, "provider");
@@ -31691,7 +32294,9 @@ async function createSession(options = {}) {
31691
32294
  platformContext = {
31692
32295
  baseUrl: options.baseUrl,
31693
32296
  token: options.token,
31694
- ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {}
32297
+ ...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
32298
+ // S-G1:bundle 授权媒体集随装配注入(媒体工具描述自列;集成方零手配)。
32299
+ platformModels: assembled.platformModels
31695
32300
  };
31696
32301
  } else {
31697
32302
  const assembled = await assembleManagedModel({
@@ -31747,6 +32352,26 @@ async function createSession(options = {}) {
31747
32352
  }
31748
32353
  );
31749
32354
  }
32355
+ const store = options.store;
32356
+ if (store !== void 0) {
32357
+ await store.create({
32358
+ sessionId,
32359
+ ...platformSessionId !== void 0 ? { platformSessionId } : {}
32360
+ });
32361
+ }
32362
+ const userCommit = options.onHistoryCommit;
32363
+ const onHistoryCommit = store === void 0 ? userCommit : async (history, meta) => {
32364
+ let storeError;
32365
+ try {
32366
+ await store.commit(sessionId, history, meta);
32367
+ } catch (err) {
32368
+ storeError = err;
32369
+ }
32370
+ await userCommit?.(history, meta);
32371
+ if (storeError !== void 0) {
32372
+ throw storeError instanceof Error ? storeError : new Error(String(storeError));
32373
+ }
32374
+ };
31750
32375
  const session = new AgentSession({
31751
32376
  binding,
31752
32377
  executor: tooling.executor,
@@ -31757,9 +32382,9 @@ async function createSession(options = {}) {
31757
32382
  maxTokens,
31758
32383
  maxTurnsPerQuery: options.maxTurnsPerQuery ?? DEFAULT_MAX_TURNS,
31759
32384
  compaction: options.compaction === false ? void 0 : options.compaction ?? {},
31760
- initialMessages: options.initialMessages ?? [],
32385
+ initialMessages,
31761
32386
  registry,
31762
- onHistoryCommit: options.onHistoryCommit,
32387
+ onHistoryCommit,
31763
32388
  ...tooling.onEnded !== void 0 ? { onEnded: tooling.onEnded } : {}
31764
32389
  });
31765
32390
  sessionRef = session;
@@ -31770,6 +32395,272 @@ async function createSession(options = {}) {
31770
32395
  return session;
31771
32396
  }
31772
32397
 
32398
+ // src/sessions/file-store.ts
32399
+ import { randomUUID as randomUUID12 } from "node:crypto";
32400
+ import { mkdir as mkdir3, readFile as readFile5, readdir, rename as rename2, rm as rm3 } from "node:fs/promises";
32401
+ import path22 from "node:path";
32402
+ var MESSAGE_KINDS2 = /* @__PURE__ */ new Set([
32403
+ "user_prompt",
32404
+ "assistant_message",
32405
+ "tool_result"
32406
+ ]);
32407
+ function journalKindOf(message) {
32408
+ if (message.role === "assistant") return "assistant_message";
32409
+ return message.blocks.some((b) => b.t === "tool_result") ? "tool_result" : "user_prompt";
32410
+ }
32411
+ var TITLE_MAX_CHARS = 64;
32412
+ function sessionTitleOf(messages) {
32413
+ for (const message of messages) {
32414
+ if (message.role !== "user") continue;
32415
+ const text = message.blocks.flatMap((b) => b.t === "text" ? [b.text] : []).join(" ").replace(/\s+/g, " ").trim();
32416
+ if (text === "") continue;
32417
+ return text.length > TITLE_MAX_CHARS ? `${text.slice(0, TITLE_MAX_CHARS)}…` : text;
32418
+ }
32419
+ return void 0;
32420
+ }
32421
+ async function readRawMeta(sessionDir) {
32422
+ try {
32423
+ const parsed = JSON.parse(await readFile5(metaFilePath(sessionDir), "utf8"));
32424
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
32425
+ return parsed;
32426
+ } catch {
32427
+ return null;
32428
+ }
32429
+ }
32430
+ function toRecordMeta(raw) {
32431
+ const createdAt = typeof raw["createdAt"] === "string" ? raw["createdAt"] : "";
32432
+ const updatedAt = typeof raw["updatedAt"] === "string" ? raw["updatedAt"] : createdAt;
32433
+ const record = {
32434
+ sessionId: typeof raw["sessionId"] === "string" ? raw["sessionId"] : "",
32435
+ createdAt,
32436
+ updatedAt
32437
+ };
32438
+ if (typeof raw["title"] === "string") record.title = raw["title"];
32439
+ if (typeof raw["platformSessionId"] === "string" && raw["platformSessionId"] !== "") {
32440
+ record.platformSessionId = raw["platformSessionId"];
32441
+ }
32442
+ const extra = raw["meta"];
32443
+ if (typeof extra === "object" && extra !== null && !Array.isArray(extra)) {
32444
+ record.meta = extra;
32445
+ }
32446
+ return record;
32447
+ }
32448
+ function corrupted(sessionId, detail, cause) {
32449
+ return new TansrSdkError(
32450
+ "session_store_corrupted",
32451
+ `Session store for "${sessionId}" is corrupted: ${detail}. Refusing to resume from damaged history (delete the session directory to start over).`,
32452
+ cause !== void 0 ? { cause } : void 0
32453
+ );
32454
+ }
32455
+ function createFileSessionStore(options) {
32456
+ const storageRoot = options.dir;
32457
+ const tails = /* @__PURE__ */ new Map();
32458
+ function enqueue(sessionId, op) {
32459
+ const tail = tails.get(sessionId) ?? Promise.resolve();
32460
+ const task = tail.then(op);
32461
+ tails.set(
32462
+ sessionId,
32463
+ task.then(
32464
+ () => void 0,
32465
+ () => void 0
32466
+ )
32467
+ );
32468
+ return task;
32469
+ }
32470
+ async function patchRawMeta(sessionDir, mutate) {
32471
+ const raw = await readRawMeta(sessionDir);
32472
+ if (raw === null) return null;
32473
+ if (mutate(raw)) {
32474
+ await writeFileDurable(metaFilePath(sessionDir), `${JSON.stringify(raw, null, 2)}
32475
+ `);
32476
+ }
32477
+ return raw;
32478
+ }
32479
+ async function appendMessages(writer, sessionId, parentEventId, messages) {
32480
+ let parent = parentEventId;
32481
+ for (const message of messages) {
32482
+ const eventId = randomUUID12();
32483
+ await writer.append({
32484
+ eventId,
32485
+ sessionId,
32486
+ branchId: MAIN_BRANCH_ID,
32487
+ kind: journalKindOf(message),
32488
+ parentEventId: parent,
32489
+ payload: message,
32490
+ producer: "sdk",
32491
+ visibility: { ...Visibilities.normal }
32492
+ });
32493
+ parent = eventId;
32494
+ }
32495
+ }
32496
+ async function rotateVolume(sessionDir, sessionId, history) {
32497
+ const tempDir = path22.join(sessionDir, `.rotate-${process.pid}-${Date.now().toString(36)}`);
32498
+ await mkdir3(tempDir, { recursive: true });
32499
+ try {
32500
+ const writer = await JournalWriter.open(tempDir);
32501
+ try {
32502
+ await appendMessages(writer, sessionId, null, history);
32503
+ } finally {
32504
+ await writer.close();
32505
+ }
32506
+ const tempAttachments = attachmentsDirPath(tempDir);
32507
+ let names = [];
32508
+ try {
32509
+ names = await readdir(tempAttachments);
32510
+ } catch {
32511
+ names = [];
32512
+ }
32513
+ if (names.length > 0) {
32514
+ const target = attachmentsDirPath(sessionDir);
32515
+ await mkdir3(target, { recursive: true });
32516
+ for (const name of names) {
32517
+ await rename2(path22.join(tempAttachments, name), path22.join(target, name));
32518
+ }
32519
+ }
32520
+ await rename2(journalFilePath(tempDir), journalFilePath(sessionDir));
32521
+ } finally {
32522
+ await rm3(tempDir, { recursive: true, force: true }).catch(() => void 0);
32523
+ }
32524
+ }
32525
+ return {
32526
+ async create(init) {
32527
+ const sessionId = init.sessionId ?? randomUUID12();
32528
+ return enqueue(sessionId, async () => {
32529
+ const { sessionDir, created } = await ensureSessionDir({
32530
+ storageRoot,
32531
+ sessionId,
32532
+ cwd: process.cwd(),
32533
+ ...init.title !== void 0 ? { extra: { title: init.title } } : {}
32534
+ });
32535
+ const raw = await patchRawMeta(sessionDir, (m) => {
32536
+ let changed = false;
32537
+ if (created && typeof m["updatedAt"] !== "string") {
32538
+ m["updatedAt"] = typeof m["createdAt"] === "string" ? m["createdAt"] : (/* @__PURE__ */ new Date()).toISOString();
32539
+ changed = true;
32540
+ }
32541
+ if (init.platformSessionId !== void 0 && m["platformSessionId"] !== init.platformSessionId) {
32542
+ m["platformSessionId"] = init.platformSessionId;
32543
+ changed = true;
32544
+ }
32545
+ if (init.title !== void 0 && m["title"] !== init.title) {
32546
+ m["title"] = init.title;
32547
+ changed = true;
32548
+ }
32549
+ if (init.meta !== void 0 && created) {
32550
+ m["meta"] = init.meta;
32551
+ changed = true;
32552
+ }
32553
+ return changed;
32554
+ });
32555
+ if (raw === null) throw corrupted(sessionId, "meta.json unreadable right after creation");
32556
+ return toRecordMeta(raw);
32557
+ });
32558
+ },
32559
+ async get(sessionId) {
32560
+ return enqueue(sessionId, async () => {
32561
+ const sessionDir = sessionDirPath(storageRoot, sessionId);
32562
+ const raw = await readRawMeta(sessionDir);
32563
+ if (raw === null) return null;
32564
+ const lock = await acquireSessionLock(sessionDir);
32565
+ try {
32566
+ let result;
32567
+ try {
32568
+ result = await readAll(sessionDir);
32569
+ } catch (err) {
32570
+ throw corrupted(sessionId, err instanceof Error ? err.message : String(err), err);
32571
+ }
32572
+ if (result.integrityBreakAt !== void 0) {
32573
+ throw corrupted(
32574
+ sessionId,
32575
+ `journal hash chain broken at record index ${result.integrityBreakAt}`
32576
+ );
32577
+ }
32578
+ const state = rebuildState(result.records);
32579
+ if (state.warnings.length > 0) {
32580
+ throw corrupted(sessionId, state.warnings.join("; "));
32581
+ }
32582
+ return { meta: toRecordMeta(raw), messages: state.messages };
32583
+ } finally {
32584
+ await lock.release();
32585
+ }
32586
+ });
32587
+ },
32588
+ async list(filter) {
32589
+ const root = path22.join(storageRoot, "sessions");
32590
+ let entries;
32591
+ try {
32592
+ entries = await readdir(root, { withFileTypes: true });
32593
+ } catch {
32594
+ return [];
32595
+ }
32596
+ const records = [];
32597
+ for (const entry of entries) {
32598
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
32599
+ const raw = await readRawMeta(path22.join(root, entry.name));
32600
+ if (raw === null || typeof raw["sessionId"] !== "string") continue;
32601
+ records.push(toRecordMeta(raw));
32602
+ }
32603
+ records.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
32604
+ return filter?.limit !== void 0 ? records.slice(0, filter.limit) : records;
32605
+ },
32606
+ async commit(sessionId, history, meta) {
32607
+ const snapshot = history.map((m) => structuredClone(m));
32608
+ return enqueue(sessionId, async () => {
32609
+ const title = sessionTitleOf(snapshot);
32610
+ const { sessionDir } = await ensureSessionDir({
32611
+ storageRoot,
32612
+ sessionId,
32613
+ cwd: process.cwd(),
32614
+ ...title !== void 0 ? { extra: { title } } : {}
32615
+ });
32616
+ const lock = await acquireSessionLock(sessionDir);
32617
+ try {
32618
+ let result;
32619
+ try {
32620
+ result = await readAll(sessionDir, { rehydrateImages: false });
32621
+ } catch (err) {
32622
+ throw corrupted(sessionId, err instanceof Error ? err.message : String(err), err);
32623
+ }
32624
+ if (result.integrityBreakAt !== void 0) {
32625
+ throw corrupted(
32626
+ sessionId,
32627
+ `journal hash chain broken at record index ${result.integrityBreakAt}`
32628
+ );
32629
+ }
32630
+ const persisted = result.records.filter(
32631
+ (r) => r.branchId === MAIN_BRANCH_ID && MESSAGE_KINDS2.has(r.kind) && r.visibility.model
32632
+ ).length;
32633
+ if (meta.rewritten || snapshot.length < persisted) {
32634
+ await rotateVolume(sessionDir, sessionId, snapshot);
32635
+ } else {
32636
+ const delta = snapshot.slice(persisted);
32637
+ if (delta.length > 0) {
32638
+ const writer = await JournalWriter.open(sessionDir);
32639
+ try {
32640
+ const last = result.records[result.records.length - 1];
32641
+ await appendMessages(writer, sessionId, last?.eventId ?? null, delta);
32642
+ } finally {
32643
+ await writer.close();
32644
+ }
32645
+ }
32646
+ }
32647
+ await patchRawMeta(sessionDir, (m) => {
32648
+ m["updatedAt"] = (/* @__PURE__ */ new Date()).toISOString();
32649
+ return true;
32650
+ });
32651
+ } finally {
32652
+ await lock.release();
32653
+ }
32654
+ });
32655
+ },
32656
+ async delete(sessionId) {
32657
+ return enqueue(sessionId, async () => {
32658
+ await rm3(sessionDirPath(storageRoot, sessionId), { recursive: true, force: true });
32659
+ });
32660
+ }
32661
+ };
32662
+ }
32663
+
31773
32664
  // src/view/reducer.ts
31774
32665
  var OUTPUT_TAIL_MAX_CHARS = 4e3;
31775
32666
  function initialSessionViewState() {
@@ -32367,6 +33258,58 @@ function markSourceFailure(state, error) {
32367
33258
  });
32368
33259
  }
32369
33260
 
33261
+ // src/view/history.ts
33262
+ function viewStateFromHistory(messages) {
33263
+ const results = /* @__PURE__ */ new Map();
33264
+ for (const message of messages) {
33265
+ for (const block of message.blocks) {
33266
+ if (block.t === "tool_result") results.set(block.callId, block);
33267
+ }
33268
+ }
33269
+ const uiMessages = [];
33270
+ let nextSeq = 1;
33271
+ for (const message of messages) {
33272
+ const parts = [];
33273
+ for (const block of message.blocks) {
33274
+ switch (block.t) {
33275
+ case "text":
33276
+ if (block.text !== "") parts.push({ type: "text", text: block.text });
33277
+ break;
33278
+ case "thinking":
33279
+ if (block.text !== "") parts.push({ type: "thinking", text: block.text });
33280
+ break;
33281
+ case "tool_call": {
33282
+ const result = results.get(block.id);
33283
+ const resultText = result?.content.filter((item) => item.t === "text").map((item) => item.text ?? "").join("\n");
33284
+ const status = result === void 0 ? "aborted" : result.isError === true ? "failed" : "completed";
33285
+ const part = {
33286
+ type: "toolCall",
33287
+ id: block.id,
33288
+ name: block.name,
33289
+ // 键在场性对齐 Kotlin(JsonElement?):args 键在场(含显式 null)
33290
+ // 即透传,键缺席即不落
33291
+ ...block.args !== void 0 ? { args: block.args } : {},
33292
+ status,
33293
+ ...resultText !== void 0 && resultText !== "" ? { resultText } : {}
33294
+ };
33295
+ parts.push(part);
33296
+ break;
33297
+ }
33298
+ default:
33299
+ break;
33300
+ }
33301
+ }
33302
+ if (parts.length === 0) continue;
33303
+ uiMessages.push({ id: `msg-${nextSeq}`, role: message.role, parts });
33304
+ nextSeq += 1;
33305
+ }
33306
+ const initial = initialSessionViewState();
33307
+ return {
33308
+ view: { ...initial.view, messages: uiMessages },
33309
+ internal: { ...initial.internal, nextMessageSeq: nextSeq }
33310
+ };
33311
+ }
33312
+
32370
33313
  // src/view/pump.ts
32371
33314
  function resolveIterable(source) {
32372
33315
  if (Symbol.asyncIterator in source) return source;
@@ -32600,6 +33543,7 @@ export {
32600
33543
  ImageGenArgsSchema,
32601
33544
  McpHost,
32602
33545
  OUTPUT_TAIL_MAX_CHARS,
33546
+ PLATFORM_SEARCH_PROVIDER_NAME,
32603
33547
  QueueChannel,
32604
33548
  SequentialToolExecutor,
32605
33549
  TANSR_PROVIDER_ID,
@@ -32608,8 +33552,6 @@ export {
32608
33552
  UnavailableChannel,
32609
33553
  VIDEOGEN_TOOL_MAX_DURATION,
32610
33554
  VideoGenArgsSchema,
32611
- WEBSEARCH_TOOL_MAX_RESULTS,
32612
- WebSearchArgsSchema2 as WebSearchArgsSchema,
32613
33555
  accumulateUsage,
32614
33556
  appBundleRegistryConfig,
32615
33557
  appendUserMessage,
@@ -32623,14 +33565,15 @@ export {
32623
33565
  buildSdkToolSet,
32624
33566
  buildToolGuideSegment,
32625
33567
  createAppTokenFetch,
33568
+ createFileSessionStore,
32626
33569
  createImageGenTool,
32627
33570
  createMcpHost,
32628
33571
  createNarrator,
33572
+ createPlatformSearchProvider,
32629
33573
  createSdkPermissionGate,
32630
33574
  createSession,
32631
33575
  createSessionView,
32632
33576
  createVideoGenTool,
32633
- createWebSearchTool2 as createWebSearchTool,
32634
33577
  defineSkill,
32635
33578
  defineTool,
32636
33579
  initialSessionViewState,
@@ -32644,5 +33587,6 @@ export {
32644
33587
  resolvePlatformSelection,
32645
33588
  runAgent,
32646
33589
  subagentModelResolverOf,
32647
- toToolDef
33590
+ toToolDef,
33591
+ viewStateFromHistory
32648
33592
  };