@webskill/sdk 0.1.5 → 0.2.1

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.
@@ -174,7 +174,26 @@ var MemoryFS = class {
174
174
  for (const k of descendants) this.#entries.delete(k);
175
175
  this.#entries.delete(key);
176
176
  }
177
+ async rename(from, to) {
178
+ const src = this.#normalize(from);
179
+ const dst = this.#normalize(to);
180
+ const entry = this.#entries.get(src);
181
+ if (!entry) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${src}`);
182
+ const prefix = src + "/";
183
+ const descendants = [...this.#entries.entries()].filter(([k]) => k.startsWith(prefix));
184
+ this.#ensureParents(dst);
185
+ this.#entries.set(dst, entry);
186
+ for (const [k, v] of descendants) this.#entries.set(dst + k.slice(src.length), v);
187
+ for (const [k] of descendants) this.#entries.delete(k);
188
+ this.#entries.delete(src);
189
+ }
177
190
  };
191
+ /** 原子写文本:先写临时文件再 rename(进程崩溃不留下半写文件;lockfile 等账本场景) */
192
+ async function atomicWriteText(fs, path, content) {
193
+ const tmp = `${path}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
194
+ await fs.writeText(tmp, content);
195
+ await fs.rename(tmp, path);
196
+ }
178
197
  /** 统一分隔符为 `/`,去除 `.` 段与重复分隔符(不解析 `..`) */
179
198
  function normalizePath(path) {
180
199
  return path.replace(/\\/g, "/").split("/").filter((s) => s !== "" && s !== ".").join("/");
@@ -570,8 +589,7 @@ var SkillDiscovery = class {
570
589
  });
571
590
  continue;
572
591
  }
573
- for (const child of await this.#fs.list(root)) {
574
- if (child.type !== "directory") continue;
592
+ await Promise.all((await this.#fs.list(root)).filter((child) => child.type === "directory").map(async (child) => {
575
593
  const dirName = baseName(child.path);
576
594
  const skillRoot = `${root}/${dirName}`;
577
595
  const skillMdPath = `${skillRoot}/SKILL.md`;
@@ -596,7 +614,7 @@ var SkillDiscovery = class {
596
614
  const dependencies = metadata["dependencies"];
597
615
  adjacency.set(metadata.name, Array.isArray(dependencies) ? dependencies.filter((d) => typeof d === "string") : []);
598
616
  }
599
- }
617
+ }));
600
618
  }
601
619
  const cycles = checkDependencyCycles(adjacency);
602
620
  const claimedNames = /* @__PURE__ */ new Set();
@@ -725,4 +743,4 @@ const xmlRenderer = {
725
743
  };
726
744
 
727
745
  //#endregion
728
- export { validateSkills as A, parseSkillPackManifest as C, resolveArchiveLimits as D, renderCatalogJson as E, xmlRenderer as M, resolveInsideRoot as O, parseSkillMarkdown as S, renderAvailableSkillsXml as T, escapeXml as _, SKILL_NAME_MAX_LENGTH as a, jsonRenderer as b, SkillDiscovery as c, assertSafePathSegment as d, buildCatalog as f, computeDigest as g, checkSkillRules as h, SKILL_MANIFEST_FILE as i, verifyManifest as j, unzipWithLimits as k, SkillReader as l, checkDependencyCycles as m, MemoryFS as n, SKILL_NAME_PATTERN as o, buildManifest as p, SKILLS_LOCKFILE as r, SKILL_PACK_FILE as s, DEFAULT_ARCHIVE_LIMITS as t, WebSkillError as u, exportSkills as v, readResponseWithLimit as w, normalizePath as x, isValidSkillName as y };
746
+ export { unzipWithLimits as A, parseSkillMarkdown as C, renderCatalogJson as D, renderAvailableSkillsXml as E, verifyManifest as M, xmlRenderer as N, resolveArchiveLimits as O, normalizePath as S, readResponseWithLimit as T, computeDigest as _, SKILL_NAME_MAX_LENGTH as a, isValidSkillName as b, SkillDiscovery as c, assertSafePathSegment as d, atomicWriteText as f, checkSkillRules as g, checkDependencyCycles as h, SKILL_MANIFEST_FILE as i, validateSkills as j, resolveInsideRoot as k, SkillReader as l, buildManifest as m, MemoryFS as n, SKILL_NAME_PATTERN as o, buildCatalog as p, SKILLS_LOCKFILE as r, SKILL_PACK_FILE as s, DEFAULT_ARCHIVE_LIMITS as t, WebSkillError as u, escapeXml as v, parseSkillPackManifest as w, jsonRenderer as x, exportSkills as y };
@@ -1,4 +1,4 @@
1
- import { A as validateSkills, O as resolveInsideRoot, S as parseSkillMarkdown, T as renderAvailableSkillsXml, c as SkillDiscovery, d as assertSafePathSegment, f as buildCatalog, l as SkillReader, u as WebSkillError } from "./dist-fZFZaf43.js";
1
+ import { C as parseSkillMarkdown, E as renderAvailableSkillsXml, c as SkillDiscovery, d as assertSafePathSegment, j as validateSkills, k as resolveInsideRoot, l as SkillReader, p as buildCatalog, u as WebSkillError } from "./dist-D0saNPi_.js";
2
2
  import { t as MemoryArtifactStore } from "./memoryArtifactStore-C9lFVqPF-yFz6yJj0.js";
3
3
 
4
4
  //#region ../runtime/dist/index.js
@@ -986,6 +986,41 @@ var EventBus = class {
986
986
  for (const listener of this.#listeners.get("*") ?? []) listener(frozen);
987
987
  }
988
988
  };
989
+ /**
990
+ * per-scope 串行化 MemoryStore 装饰器:同一 scope 的 get/set/delete/list
991
+ * 按 promise 链串行(并发 run 的 read-modify-write 不丢计数)。
992
+ */
993
+ var SerializingMemoryStore = class {
994
+ #inner;
995
+ #chains = /* @__PURE__ */ new Map();
996
+ constructor(inner) {
997
+ this.#inner = inner;
998
+ }
999
+ #serialize(scope, fn) {
1000
+ const next = (this.#chains.get(scope) ?? Promise.resolve()).then(fn, fn);
1001
+ this.#chains.set(scope, next.catch(() => void 0));
1002
+ return next;
1003
+ }
1004
+ get(scope, key) {
1005
+ return this.#serialize(scope, () => this.#inner.get(scope, key));
1006
+ }
1007
+ set(scope, key, value) {
1008
+ return this.#serialize(scope, () => this.#inner.set(scope, key, value));
1009
+ }
1010
+ delete(scope, key) {
1011
+ return this.#serialize(scope, () => this.#inner.delete(scope, key));
1012
+ }
1013
+ list(scope) {
1014
+ return this.#serialize(scope, () => this.#inner.list(scope));
1015
+ }
1016
+ clear(scope) {
1017
+ return this.#serialize(scope ?? "", () => this.#inner.clear(scope));
1018
+ }
1019
+ /** scope 级原子段:read-modify-write 全段在 promise 链内串行执行(fn 收到底层 store) */
1020
+ transaction(scope, fn) {
1021
+ return this.#serialize(scope, () => fn(this.#inner));
1022
+ }
1023
+ };
989
1024
  /** 结构化 trace 记录器;时钟与 id 工厂可注入(golden trace 用固定值保证确定性) */
990
1025
  var TraceRecorder = class {
991
1026
  #runId;
@@ -1061,8 +1096,6 @@ var AgentLoop = class {
1061
1096
  };
1062
1097
  }
1063
1098
  async run(input) {
1064
- this.#deps.llm;
1065
- this.#deps.artifactStore;
1066
1099
  const now = this.#deps.clock?.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
1067
1100
  const runId = input.runId ?? `run-${Math.random().toString(36).slice(2, 10)}`;
1068
1101
  const trace = new TraceRecorder(runId, this.#deps.clock);
@@ -1115,13 +1148,17 @@ var AgentLoop = class {
1115
1148
  return [];
1116
1149
  }
1117
1150
  }))).flat();
1118
- state.messages = [{
1119
- role: "system",
1120
- content: route.systemPrompt
1121
- }, {
1122
- role: "user",
1123
- content: input.userPrompt
1124
- }];
1151
+ state.messages = [
1152
+ {
1153
+ role: "system",
1154
+ content: route.systemPrompt
1155
+ },
1156
+ ...(input.history ?? []).map((m) => ({ ...m })),
1157
+ {
1158
+ role: "user",
1159
+ content: input.userPrompt
1160
+ }
1161
+ ];
1125
1162
  try {
1126
1163
  return await this.#turnLoop(state, 1, externalSpecs);
1127
1164
  } catch (e) {
@@ -1195,7 +1232,13 @@ var AgentLoop = class {
1195
1232
  hasToolCalls: Boolean(response.toolCalls?.length),
1196
1233
  contentLength: response.content?.length ?? 0
1197
1234
  } });
1198
- if (!response.toolCalls?.length) return finish("completed", "final-answer", response.content ?? "");
1235
+ if (!response.toolCalls?.length) {
1236
+ messages.push({
1237
+ role: "assistant",
1238
+ content: response.content ?? ""
1239
+ });
1240
+ return finish("completed", "final-answer", response.content ?? "");
1241
+ }
1199
1242
  await this.#lifecycle("execute", state, { turn });
1200
1243
  messages.push({
1201
1244
  role: "assistant",
@@ -1260,7 +1303,8 @@ var AgentLoop = class {
1260
1303
  run.trace = trace.list();
1261
1304
  return {
1262
1305
  output,
1263
- run
1306
+ run,
1307
+ messages: state.messages.map((m) => ({ ...m }))
1264
1308
  };
1265
1309
  }
1266
1310
  /** D3 快照写入(含 pendingInteraction 与过期时间;写失败降级 run.warning) */
@@ -1279,10 +1323,12 @@ var AgentLoop = class {
1279
1323
  activatedTools: [...state.activatedTools.values()],
1280
1324
  pendingInteraction: request,
1281
1325
  interactionExpiresAt: state.run.interruptExpiresAt,
1326
+ renderBlocks: [...state.renderBlocks],
1327
+ interactionSeq: state.interactionSeq,
1282
1328
  config: {
1283
- maxTurns: this.#config.maxTurns,
1284
- totalTimeoutMs: this.#config.totalTimeoutMs,
1285
- toolTimeoutMs: this.#config.toolTimeoutMs,
1329
+ maxTurns: state.maxTurns,
1330
+ totalTimeoutMs: state.totalTimeoutMs,
1331
+ toolTimeoutMs: state.toolTimeoutMs,
1286
1332
  ...this.#config.temperature !== void 0 ? { temperature: this.#config.temperature } : {}
1287
1333
  },
1288
1334
  snapshotAt: state.now()
@@ -1321,10 +1367,10 @@ var AgentLoop = class {
1321
1367
  activatedTools: new Map(snapshot.activatedTools.map((d) => [d.name, d])),
1322
1368
  toolTimeoutMs: snapshot.config.toolTimeoutMs,
1323
1369
  now,
1324
- interactionSeq: 0,
1370
+ interactionSeq: snapshot.interactionSeq ?? 0,
1325
1371
  messages: snapshot.messages.map((m) => ({ ...m })),
1326
1372
  turn: snapshot.turn,
1327
- renderBlocks: [],
1373
+ renderBlocks: (snapshot.renderBlocks ?? []).map((b) => ({ ...b })),
1328
1374
  startMs,
1329
1375
  pausedMs: 0,
1330
1376
  maxTurns: snapshot.config.maxTurns,
@@ -1494,7 +1540,7 @@ var AgentLoop = class {
1494
1540
  } });
1495
1541
  let response;
1496
1542
  try {
1497
- response = await this.#withInteractionTimeout(bridge.request(request), this.#policy.interactionTimeoutMs);
1543
+ response = await this.#withInteractionTimeout(bridge.request(request), this.#policy.interactionTimeoutMs, () => bridge.cancel?.(request.id));
1498
1544
  } catch (e) {
1499
1545
  run.status = "running";
1500
1546
  run.interruptExpiresAt = void 0;
@@ -1523,9 +1569,14 @@ var AgentLoop = class {
1523
1569
  await this.#appendParamHistory(state, request, response.value);
1524
1570
  return response.value;
1525
1571
  }
1526
- #withInteractionTimeout(promise, timeoutMs) {
1572
+ #withInteractionTimeout(promise, timeoutMs, onTimeout) {
1527
1573
  return new Promise((resolve, reject) => {
1528
- const timer = setTimeout(() => reject(new WebSkillError("RUN_INTERACTION_TIMEOUT", `Interaction timed out after ${timeoutMs}ms`)), timeoutMs);
1574
+ const timer = setTimeout(() => {
1575
+ try {
1576
+ onTimeout?.();
1577
+ } catch {}
1578
+ reject(new WebSkillError("RUN_INTERACTION_TIMEOUT", `Interaction timed out after ${timeoutMs}ms`));
1579
+ }, timeoutMs);
1529
1580
  promise.then((v) => {
1530
1581
  clearTimeout(timer);
1531
1582
  resolve(v);
@@ -1718,7 +1769,7 @@ var AgentLoop = class {
1718
1769
  const executor = this.#deps.executor;
1719
1770
  const loaded = [];
1720
1771
  if (executor) {
1721
- let scriptFiles = [];
1772
+ let scriptFiles;
1722
1773
  try {
1723
1774
  scriptFiles = (await this.#deps.fs.list(`${root}/scripts`)).filter((s) => s.type === "file").map((s) => baseName(s.path));
1724
1775
  } catch {
@@ -1766,8 +1817,12 @@ var AgentLoop = class {
1766
1817
  for (const ext of ["ts", "js"]) {
1767
1818
  const scriptPath = `${skillRoot}/scripts/${scriptName}.${ext}`;
1768
1819
  if (!await this.#deps.fs.exists(scriptPath)) continue;
1769
- const inferred = this.#deps.schemaInferer.inferSchemaFromSource(await this.#deps.fs.readText(scriptPath), { fileName: `${scriptName}.${ext}` });
1770
- if (inferred) def.inputSchema = inferred;
1820
+ try {
1821
+ const inferred = this.#deps.schemaInferer.inferSchemaFromSource(await this.#deps.fs.readText(scriptPath), { fileName: `${scriptName}.${ext}` });
1822
+ if (inferred) def.inputSchema = inferred;
1823
+ } catch (e) {
1824
+ state.trace.record("run.warning", { message: `Schema inference failed for ${scriptPath}: ${messageOf$1(e)}` });
1825
+ }
1771
1826
  return;
1772
1827
  }
1773
1828
  }
@@ -1869,6 +1924,22 @@ var AgentLoop = class {
1869
1924
  state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
1870
1925
  }
1871
1926
  }
1927
+ /** read-modify-write:有 transaction 实现在 scope 原子段内执行(并发不丢计数),否则顺序执行 */
1928
+ async #memoryMutate(scope, key, state, mutate) {
1929
+ const memory = this.#deps.memory;
1930
+ if (!memory) return;
1931
+ if (memory.transaction) {
1932
+ try {
1933
+ await memory.transaction(scope, async (inner) => {
1934
+ await inner.set(scope, key, mutate(await inner.get(scope, key)));
1935
+ });
1936
+ } catch (e) {
1937
+ state.trace.record("run.warning", { message: `Memory write failed: ${messageOf$1(e)}` });
1938
+ }
1939
+ return;
1940
+ }
1941
+ await this.#memorySet(scope, key, mutate(await this.#memoryGet(scope, key)), state);
1942
+ }
1872
1943
  async #writeActivationMemory(skillName, state) {
1873
1944
  if (!this.#deps.memory) return;
1874
1945
  const sessionScope = `session:${state.run.sessionId}`;
@@ -1876,33 +1947,38 @@ var AgentLoop = class {
1876
1947
  await this.#bumpSkillStat(skillName, "activations", state);
1877
1948
  if (this.#deps.longTerm?.enabled) {
1878
1949
  const userScope = `user:${this.#deps.longTerm.userId}`;
1879
- const usage = await this.#memoryGet(userScope, "skillUsage") ?? {};
1880
- usage[skillName] = (usage[skillName] ?? 0) + 1;
1881
- await this.#memorySet(userScope, "skillUsage", usage, state);
1950
+ await this.#memoryMutate(userScope, "skillUsage", state, (current) => {
1951
+ const usage = current ?? {};
1952
+ usage[skillName] = (usage[skillName] ?? 0) + 1;
1953
+ return usage;
1954
+ });
1882
1955
  }
1883
1956
  }
1884
1957
  async #bumpSkillStat(skillName, field, state) {
1885
1958
  if (!this.#deps.memory) return;
1886
- const scope = `skill:${skillName}`;
1887
- const stats = await this.#memoryGet(scope, "stats") ?? {
1888
- activations: 0,
1889
- successes: 0,
1890
- failures: 0
1891
- };
1892
- stats[field] = (stats[field] ?? 0) + 1;
1893
- await this.#memorySet(scope, "stats", stats, state);
1959
+ await this.#memoryMutate(`skill:${skillName}`, "stats", state, (current) => {
1960
+ const stats = current ?? {
1961
+ activations: 0,
1962
+ successes: 0,
1963
+ failures: 0
1964
+ };
1965
+ stats[field] = (stats[field] ?? 0) + 1;
1966
+ return stats;
1967
+ });
1894
1968
  }
1895
1969
  async #appendParamHistory(state, request, value) {
1896
1970
  if (!this.#deps.memory) return;
1897
1971
  const scope = `session:${state.run.sessionId}`;
1898
- const history = await this.#memoryGet(scope, "paramHistory") ?? [];
1899
- history.push({
1900
- ts: state.now(),
1901
- interactionId: request.id,
1902
- type: request.type,
1903
- value
1972
+ await this.#memoryMutate(scope, "paramHistory", state, (current) => {
1973
+ const history = current ?? [];
1974
+ history.push({
1975
+ ts: state.now(),
1976
+ interactionId: request.id,
1977
+ type: request.type,
1978
+ value
1979
+ });
1980
+ return history;
1904
1981
  });
1905
- await this.#memorySet(scope, "paramHistory", history, state);
1906
1982
  }
1907
1983
  };
1908
1984
  /**
@@ -1919,6 +1995,10 @@ const SNAPSHOT_SUFFIX = ".snapshot.json";
1919
1995
  /**
1920
1996
  * FileSystemProvider 后端的快照存储:<root>/<runId>.snapshot.json。
1921
1997
  * 坏 JSON → RUN_SNAPSHOT_INCOMPATIBLE 并自动清理坏文件;runId 过路径安全校验。
1998
+ * save/list 时顺带清理已过 interactionExpiresAt 的过期快照。
1999
+ *
2000
+ * 数据敏感性说明:快照含完整对话历史(用户输入、工具结果、可能的凭据片段),
2001
+ * 以明文 JSON 落盘于宿主提供的 fs;宿主应将其视为会话数据同等保护。
1922
2002
  * @experimental
1923
2003
  */
1924
2004
  var FsRunSnapshotStore = class {
@@ -1933,6 +2013,7 @@ var FsRunSnapshotStore = class {
1933
2013
  }
1934
2014
  async save(snapshot) {
1935
2015
  await this.#fs.writeText(this.#path(snapshot.runId), JSON.stringify(snapshot, null, 2));
2016
+ await this.#pruneExpired();
1936
2017
  }
1937
2018
  async load(runId) {
1938
2019
  const path = this.#path(runId);
@@ -1956,6 +2037,7 @@ var FsRunSnapshotStore = class {
1956
2037
  if (await this.#fs.exists(path)) await this.#fs.remove(path);
1957
2038
  }
1958
2039
  async list() {
2040
+ await this.#pruneExpired();
1959
2041
  if (!await this.#fs.exists(this.#root)) return [];
1960
2042
  const out = [];
1961
2043
  for (const entry of await this.#fs.list(this.#root)) {
@@ -1966,6 +2048,20 @@ var FsRunSnapshotStore = class {
1966
2048
  }
1967
2049
  return out.sort((a, b) => a.snapshotAt.localeCompare(b.snapshotAt));
1968
2050
  }
2051
+ /** 过期快照清理(save/list 时顺带;失败静默不阻断主流程) */
2052
+ async #pruneExpired() {
2053
+ try {
2054
+ if (!await this.#fs.exists(this.#root)) return;
2055
+ const now = Date.now();
2056
+ for (const entry of await this.#fs.list(this.#root)) {
2057
+ if (entry.type !== "file" || !entry.path.endsWith(SNAPSHOT_SUFFIX)) continue;
2058
+ try {
2059
+ const parsed = JSON.parse(await this.#fs.readText(entry.path));
2060
+ if (parsed.interactionExpiresAt !== void 0 && Date.parse(parsed.interactionExpiresAt) < now) await this.#fs.remove(entry.path);
2061
+ } catch {}
2062
+ }
2063
+ } catch {}
2064
+ }
1969
2065
  };
1970
2066
  /**
1971
2067
  * runtime 门面:组合 discovery / router / agent loop / lifecycle
@@ -1979,7 +2075,10 @@ var WebSkillRuntime = class {
1979
2075
  #events;
1980
2076
  #catalogCache;
1981
2077
  constructor(deps) {
1982
- this.#deps = deps;
2078
+ this.#deps = {
2079
+ ...deps,
2080
+ ...deps.memory ? { memory: new SerializingMemoryStore(deps.memory) } : {}
2081
+ };
1983
2082
  this.#discovery = new SkillDiscovery(deps.fs, deps.roots);
1984
2083
  this.#router = deps.router ?? new ProgressiveRouter();
1985
2084
  this.#events = deps.eventBus ?? new EventBus();
@@ -1991,6 +2090,10 @@ var WebSkillRuntime = class {
1991
2090
  get session() {
1992
2091
  return this.#session;
1993
2092
  }
2093
+ /** 当前装配的脚本执行器(治理默认执行器断言等只读场景) */
2094
+ get executor() {
2095
+ return this.#deps.executor;
2096
+ }
1994
2097
  /** 生命周期事件总线(只读观测) */
1995
2098
  get events() {
1996
2099
  return this.#events;
@@ -2002,6 +2105,27 @@ var WebSkillRuntime = class {
2002
2105
  invalidate() {
2003
2106
  this.#catalogCache = void 0;
2004
2107
  }
2108
+ /**
2109
+ * 多会话:session 对象的 run(prompt) 跨 run 延续消息历史(同一 session 对话上下文连续)。
2110
+ * 既有 runtime.run(prompt) 保持无状态单次语义不变。
2111
+ */
2112
+ createSession(options = {}) {
2113
+ const sessionId = options.sessionId ?? `session-${Math.random().toString(36).slice(2, 10)}`;
2114
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
2115
+ let history = [];
2116
+ return {
2117
+ id: sessionId,
2118
+ createdAt,
2119
+ run: async (prompt) => {
2120
+ const result = await this.run(prompt, {
2121
+ sessionId,
2122
+ history
2123
+ });
2124
+ history = result.messages.slice(1);
2125
+ return result;
2126
+ }
2127
+ };
2128
+ }
2005
2129
  async discover() {
2006
2130
  const result = await this.#discovery.discover();
2007
2131
  this.#catalogCache = {
@@ -2010,7 +2134,7 @@ var WebSkillRuntime = class {
2010
2134
  };
2011
2135
  return result;
2012
2136
  }
2013
- async run(userPrompt) {
2137
+ async run(userPrompt, options = {}) {
2014
2138
  if (!this.#catalogCache) await this.discover();
2015
2139
  const cache = this.#catalogCache;
2016
2140
  if (!cache) throw new Error("discover() did not populate the catalog cache");
@@ -2046,9 +2170,10 @@ var WebSkillRuntime = class {
2046
2170
  snapshotStore: this.#deps.snapshotStore,
2047
2171
  skillStateGuard: this.#deps.skillStateGuard
2048
2172
  }, this.#deps.config).run({
2049
- sessionId: this.#session.id,
2173
+ sessionId: options.sessionId ?? this.#session.id,
2050
2174
  userPrompt,
2051
- route
2175
+ route,
2176
+ ...options.history ? { history: options.history } : {}
2052
2177
  });
2053
2178
  for (const failure of providerFailures) result.run.trace.push({
2054
2179
  id: `evt-provider-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
@@ -2117,7 +2242,8 @@ var WebSkillRuntime = class {
2117
2242
  code: "RUN_INTERACTION_TIMEOUT"
2118
2243
  }
2119
2244
  }]
2120
- }
2245
+ },
2246
+ messages: []
2121
2247
  };
2122
2248
  }
2123
2249
  if (!this.#catalogCache) await this.discover();
@@ -2219,7 +2345,7 @@ function createWebSkillApi(deps) {
2219
2345
  }
2220
2346
  };
2221
2347
  }
2222
- /** install URL 分派:http(s)…​.zip → http;…​.git → git(无 git 能力的 manager 自行 TOOL_UNSUPPORTED);
2348
+ /** install URL 分派:http(s) .zip → http;.git → git(无 git 能力的 manager 自行 TOOL_UNSUPPORTED);
2223
2349
  * 其余 http(s) URL → INSTALL_FAILED(不静默当 local);非 URL → local */
2224
2350
  function sourceFromUrl(url) {
2225
2351
  const bare = url.split("?")[0] ?? url;
@@ -2507,16 +2633,21 @@ function networkUrlHost(url) {
2507
2633
  * once-per-run(默认)在 run 内记忆已批准能力,every-call 每次调用都问。
2508
2634
  * @stable
2509
2635
  */
2510
- var CapabilityApproval = class {
2636
+ var CapabilityApproval = class CapabilityApproval {
2511
2637
  #uiBridge;
2512
2638
  #scope;
2513
- /** runId → 已批准能力集合(once-per-run 记忆) */
2639
+ /** runId → 已批准能力集合(once-per-run 记忆;LRU 上限防长跑进程膨胀) */
2514
2640
  #approved = /* @__PURE__ */ new Map();
2515
2641
  #seq = 0;
2642
+ static #MAX_RUNS = 128;
2516
2643
  constructor(deps = {}) {
2517
2644
  this.#uiBridge = deps.uiBridge;
2518
2645
  this.#scope = deps.scope ?? "once-per-run";
2519
2646
  }
2647
+ /** run 终态清理授权记录(宿主在 run 结束后调用;同时有 LRU 上限兜底) */
2648
+ clearRun(runId) {
2649
+ this.#approved.delete(runId);
2650
+ }
2520
2651
  async authorize(input) {
2521
2652
  const { runId, capability, mode } = input;
2522
2653
  if (mode === false) return "disabled";
@@ -2537,10 +2668,14 @@ var CapabilityApproval = class {
2537
2668
  this.#approved.set(runId, set);
2538
2669
  }
2539
2670
  set.add(capability);
2671
+ if (this.#approved.size > CapabilityApproval.#MAX_RUNS) {
2672
+ const oldest = this.#approved.keys().next().value;
2673
+ if (oldest !== void 0) this.#approved.delete(oldest);
2674
+ }
2540
2675
  }
2541
2676
  return "allowed";
2542
2677
  }
2543
2678
  };
2544
2679
 
2545
2680
  //#endregion
2546
- export { mergeCatalogEntries as A, buildRenderResult as C, fromVercelResult as D, extractChartSpec as E, schemaToForm as F, toLlmToolSpec as I, toVercelToolSpecs as L, normalizeToolContent as M, parseBridgeRequest as N, fromVercelStreamPart as O, resolveToolName as P, bridgeError as S, createWebSkillApi as T, READ_SKILL_FILE_TOOL as _, AnthropicClient as a, TraceRecorder as b, FsArtifactStore as c, FullDisclosureRouter as d, GoogleGenAiClient as f, READ_SKILL_FILE_INPUT_SCHEMA as g, ProgressiveRouter as h, AgentLoop as i, networkUrlHost as j, isNetworkAllowed as k, FsMemoryStore as l, OpenAiCompatibleClient as m, ASK_USER_TOOL as n, CapabilityApproval as o, HookRunner as p, ASK_USER_TOOL_NAME as r, EventBus as s, ASK_USER_INPUT_SCHEMA as t, FsRunSnapshotStore as u, READ_SKILL_FILE_TOOL_NAME as v, createScriptContext as w, WebSkillRuntime as x, RUN_SNAPSHOT_SCHEMA_VERSION as y };
2681
+ export { isNetworkAllowed as A, bridgeError as C, extractChartSpec as D, createWebSkillApi as E, resolveToolName as F, schemaToForm as I, toLlmToolSpec as L, networkUrlHost as M, normalizeToolContent as N, fromVercelResult as O, parseBridgeRequest as P, toVercelToolSpecs as R, WebSkillRuntime as S, createScriptContext as T, READ_SKILL_FILE_TOOL as _, AnthropicClient as a, SerializingMemoryStore as b, FsArtifactStore as c, FullDisclosureRouter as d, GoogleGenAiClient as f, READ_SKILL_FILE_INPUT_SCHEMA as g, ProgressiveRouter as h, AgentLoop as i, mergeCatalogEntries as j, fromVercelStreamPart as k, FsMemoryStore as l, OpenAiCompatibleClient as m, ASK_USER_TOOL as n, CapabilityApproval as o, HookRunner as p, ASK_USER_TOOL_NAME as r, EventBus as s, ASK_USER_INPUT_SCHEMA as t, FsRunSnapshotStore as u, READ_SKILL_FILE_TOOL_NAME as v, buildRenderResult as w, TraceRecorder as x, RUN_SNAPSHOT_SCHEMA_VERSION as y };
@@ -0,0 +1,143 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ //#region ../node/dist/env--jJB-TSX.js
4
+ /** 解析 .env(简单 KEY=VALUE 行解析,不引依赖);文件缺失返回 undefined */
5
+ function readEnvVars(dotenvPath) {
6
+ let raw;
7
+ try {
8
+ raw = readFileSync(dotenvPath, "utf8");
9
+ } catch {
10
+ return;
11
+ }
12
+ const vars = /* @__PURE__ */ new Map();
13
+ for (const line of raw.split(/\r?\n/)) {
14
+ const trimmed = line.trim();
15
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
16
+ const eq = trimmed.indexOf("=");
17
+ if (eq <= 0) continue;
18
+ const key = trimmed.slice(0, eq).trim();
19
+ let value = trimmed.slice(eq + 1).trim();
20
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
21
+ vars.set(key, value);
22
+ }
23
+ return vars;
24
+ }
25
+ /**
26
+ * 读取 LLM_BASE_URL / LLM_API_KEY / LLM_MODEL(优先),
27
+ * 向后兼容 VITE_BASE_URL / VITE_API_KEY / VITE_MODEL_NAME;缺项返回 undefined 供测试 skip 判断。
28
+ */
29
+ function loadLlmConfigFromEnv(dotenvPath = ".env") {
30
+ const vars = readEnvVars(dotenvPath);
31
+ if (!vars) return void 0;
32
+ const baseUrl = vars.get("LLM_BASE_URL") ?? vars.get("VITE_BASE_URL");
33
+ const apiKey = vars.get("LLM_API_KEY") ?? vars.get("VITE_API_KEY");
34
+ const model = vars.get("LLM_MODEL") ?? vars.get("VITE_MODEL_NAME");
35
+ if (!baseUrl || !apiKey || !model) return void 0;
36
+ return {
37
+ baseUrl,
38
+ apiKey,
39
+ model
40
+ };
41
+ }
42
+ /** ANTHROPIC_API_KEY / ANTHROPIC_MODEL(可选 ANTHROPIC_BASE_URL);缺项返回 undefined */
43
+ function loadAnthropicConfigFromEnv(dotenvPath = ".env") {
44
+ const vars = readEnvVars(dotenvPath);
45
+ if (!vars) return void 0;
46
+ const apiKey = vars.get("ANTHROPIC_API_KEY");
47
+ const model = vars.get("ANTHROPIC_MODEL");
48
+ if (!apiKey || !model) return void 0;
49
+ const baseUrl = vars.get("ANTHROPIC_BASE_URL");
50
+ return {
51
+ apiKey,
52
+ model,
53
+ ...baseUrl ? { baseUrl } : {}
54
+ };
55
+ }
56
+ /** GOOGLE_API_KEY / GOOGLE_MODEL(可选 GOOGLE_BASE_URL);缺项返回 undefined */
57
+ function loadGoogleConfigFromEnv(dotenvPath = ".env") {
58
+ const vars = readEnvVars(dotenvPath);
59
+ if (!vars) return void 0;
60
+ const apiKey = vars.get("GOOGLE_API_KEY");
61
+ const model = vars.get("GOOGLE_MODEL");
62
+ if (!apiKey || !model) return void 0;
63
+ const baseUrl = vars.get("GOOGLE_BASE_URL");
64
+ return {
65
+ apiKey,
66
+ model,
67
+ ...baseUrl ? { baseUrl } : {}
68
+ };
69
+ }
70
+ let capabilitiesCache;
71
+ /**
72
+ * 探测 LLM 能力(结果模块级缓存):
73
+ * - available:GET /models 可达(与 checkAvailability 同语义)
74
+ * - nonStreaming:最小非流式 chat completion(max_tokens=1,短超时);
75
+ * HTTP 400/404 或明确不支持非流式的错误 → false
76
+ * - streaming 能力由 available 推断(SSE 解析在 SDK 侧实现)
77
+ * 环境变量 VITE_LLM_NON_STREAMING=0 可显式强制 nonStreaming=false(模拟流式-only 回归)。
78
+ */
79
+ function probeLlmCapabilities(config) {
80
+ capabilitiesCache ??= doProbeLlmCapabilities(config);
81
+ return capabilitiesCache;
82
+ }
83
+ async function doProbeLlmCapabilities(config) {
84
+ const baseUrl = config.baseUrl.replace(/\/+$/, "");
85
+ const headers = { authorization: `Bearer ${config.apiKey}` };
86
+ let available;
87
+ try {
88
+ available = (await fetch(`${baseUrl}/models`, {
89
+ headers,
90
+ signal: AbortSignal.timeout(1e4)
91
+ })).ok;
92
+ } catch {
93
+ available = false;
94
+ }
95
+ if (!available) return {
96
+ available: false,
97
+ nonStreaming: false,
98
+ reason: "endpoint unreachable or unauthorized"
99
+ };
100
+ if (process.env["VITE_LLM_NON_STREAMING"] === "0") return {
101
+ available: true,
102
+ nonStreaming: false,
103
+ reason: "non-streaming disabled via VITE_LLM_NON_STREAMING=0 (simulated)"
104
+ };
105
+ try {
106
+ const res = await fetch(`${baseUrl}/chat/completions`, {
107
+ method: "POST",
108
+ headers: {
109
+ ...headers,
110
+ "content-type": "application/json"
111
+ },
112
+ body: JSON.stringify({
113
+ model: config.model,
114
+ messages: [{
115
+ role: "user",
116
+ content: "OK"
117
+ }],
118
+ max_tokens: 1
119
+ }),
120
+ signal: AbortSignal.timeout(15e3)
121
+ });
122
+ const contentType = res.headers.get("content-type") ?? "";
123
+ if (res.ok && !contentType.includes("text/event-stream")) return {
124
+ available: true,
125
+ nonStreaming: true
126
+ };
127
+ const detail = (await res.text().catch(() => "")).slice(0, 200);
128
+ return {
129
+ available: true,
130
+ nonStreaming: false,
131
+ reason: `endpoint rejects non-streaming chat completions (HTTP ${res.status})${detail ? `: ${detail}` : ""}`
132
+ };
133
+ } catch (e) {
134
+ return {
135
+ available: true,
136
+ nonStreaming: false,
137
+ reason: `non-streaming probe failed: ${e instanceof Error ? e.message : String(e)}`
138
+ };
139
+ }
140
+ }
141
+
142
+ //#endregion
143
+ export { probeLlmCapabilities as i, loadGoogleConfigFromEnv as n, loadLlmConfigFromEnv as r, loadAnthropicConfigFromEnv as t };
@@ -0,0 +1,38 @@
1
+ //#region ../node/dist/env-BPUBZCwJ.d.ts
2
+ //#region src/env.d.ts
3
+ interface LlmEnvConfig {
4
+ baseUrl: string;
5
+ apiKey: string;
6
+ model: string;
7
+ }
8
+ /** per-provider env 配置(baseUrl 可选,客户端自带默认) */
9
+ interface ProviderEnvConfig {
10
+ apiKey: string;
11
+ model: string;
12
+ baseUrl?: string;
13
+ }
14
+ /**
15
+ * 读取 LLM_BASE_URL / LLM_API_KEY / LLM_MODEL(优先),
16
+ * 向后兼容 VITE_BASE_URL / VITE_API_KEY / VITE_MODEL_NAME;缺项返回 undefined 供测试 skip 判断。
17
+ */
18
+ declare function loadLlmConfigFromEnv(dotenvPath?: string): LlmEnvConfig | undefined;
19
+ /** ANTHROPIC_API_KEY / ANTHROPIC_MODEL(可选 ANTHROPIC_BASE_URL);缺项返回 undefined */
20
+ declare function loadAnthropicConfigFromEnv(dotenvPath?: string): ProviderEnvConfig | undefined;
21
+ /** GOOGLE_API_KEY / GOOGLE_MODEL(可选 GOOGLE_BASE_URL);缺项返回 undefined */
22
+ declare function loadGoogleConfigFromEnv(dotenvPath?: string): ProviderEnvConfig | undefined;
23
+ interface LlmCapabilities {
24
+ available: boolean;
25
+ nonStreaming: boolean;
26
+ reason?: string;
27
+ }
28
+ /**
29
+ * 探测 LLM 能力(结果模块级缓存):
30
+ * - available:GET /models 可达(与 checkAvailability 同语义)
31
+ * - nonStreaming:最小非流式 chat completion(max_tokens=1,短超时);
32
+ * HTTP 400/404 或明确不支持非流式的错误 → false
33
+ * - streaming 能力由 available 推断(SSE 解析在 SDK 侧实现)
34
+ * 环境变量 VITE_LLM_NON_STREAMING=0 可显式强制 nonStreaming=false(模拟流式-only 回归)。
35
+ */
36
+ declare function probeLlmCapabilities(config: LlmEnvConfig): Promise<LlmCapabilities>;
37
+ //#endregion
38
+ export { loadGoogleConfigFromEnv as a, loadAnthropicConfigFromEnv as i, LlmEnvConfig as n, loadLlmConfigFromEnv as o, ProviderEnvConfig as r, probeLlmCapabilities as s, LlmCapabilities as t };