laohuang 0.9.7 → 0.9.8

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.
package/dist/bin.js CHANGED
@@ -7628,21 +7628,10 @@ function parseSessionItem(value, expectedSessionId) {
7628
7628
  if (message["role"] !== "system" || !Array.isArray(message["toolDefinitions"]))
7629
7629
  throw new SessionSchemaError("invalid tool definitions message");
7630
7630
  for (const raw of message["toolDefinitions"]) {
7631
- const definition = objectOf(raw, "loaded tool");
7632
- requireString(definition, "version");
7633
- const spec2 = objectOf(definition["spec"], "tool spec");
7634
- requireString(spec2, "name");
7635
- if (typeof spec2["description"] !== "string" || !Array.isArray(spec2["promptGuidelines"]) || spec2["promptGuidelines"].some((value2) => typeof value2 !== "string"))
7636
- throw new SessionSchemaError("invalid tool spec");
7637
- objectOf(spec2["parameters"], "tool parameters");
7638
- if (spec2["catalog"] !== void 0) {
7639
- const catalog = objectOf(spec2["catalog"], "tool catalog metadata");
7640
- for (const key of ["source", "originalName", "binding"])
7641
- requireString(catalog, key);
7642
- if (catalog["exposure"] !== "direct" && catalog["exposure"] !== "deferred")
7643
- throw new SessionSchemaError("invalid exposure");
7644
- }
7631
+ assertStoredTool(raw, true);
7645
7632
  }
7633
+ if (message["toolsRemoved"] !== void 0)
7634
+ assertToolReferences(message["toolsRemoved"]);
7646
7635
  } else {
7647
7636
  if (message["role"] !== "user")
7648
7637
  throw new SessionSchemaError("invalid catalog message role");
@@ -7654,6 +7643,9 @@ function parseSessionItem(value, expectedSessionId) {
7654
7643
  throw new SessionSchemaError("invalid tool version");
7655
7644
  }
7656
7645
  }
7646
+ if (entryType === "compaction" && entryPayload["toolState"] !== void 0) {
7647
+ assertToolSelectionSnapshot(entryPayload["toolState"]);
7648
+ }
7657
7649
  return input;
7658
7650
  }
7659
7651
  if (kind === "record") {
@@ -7682,6 +7674,54 @@ function parseSessionItem(value, expectedSessionId) {
7682
7674
  }
7683
7675
  throw new SessionSchemaError("invalid session item kind");
7684
7676
  }
7677
+ function assertStoredTool(value, activationOptional) {
7678
+ const definition = objectOf(value, "loaded tool");
7679
+ requireString(definition, "version");
7680
+ const activation = definition["activation"];
7681
+ if ((!activationOptional || activation !== void 0) && activation !== "baseline" && activation !== "search") {
7682
+ throw new SessionSchemaError("invalid tool activation");
7683
+ }
7684
+ const spec2 = objectOf(definition["spec"], "tool spec");
7685
+ requireString(spec2, "name");
7686
+ if (typeof spec2["description"] !== "string" || !Array.isArray(spec2["promptGuidelines"]) || spec2["promptGuidelines"].some((value2) => typeof value2 !== "string")) {
7687
+ throw new SessionSchemaError("invalid tool spec");
7688
+ }
7689
+ objectOf(spec2["parameters"], "tool parameters");
7690
+ if (spec2["catalog"] !== void 0) {
7691
+ const catalog = objectOf(spec2["catalog"], "tool catalog metadata");
7692
+ for (const key of ["source", "originalName", "binding"])
7693
+ requireString(catalog, key);
7694
+ if (catalog["exposure"] !== "direct" && catalog["exposure"] !== "deferred")
7695
+ throw new SessionSchemaError("invalid exposure");
7696
+ }
7697
+ }
7698
+ function assertToolReferences(value) {
7699
+ if (!Array.isArray(value))
7700
+ throw new SessionSchemaError("invalid removed tools");
7701
+ for (const raw of value) {
7702
+ const reference = objectOf(raw, "removed tool");
7703
+ requireString(reference, "name");
7704
+ if (reference["version"] !== void 0)
7705
+ requireString(reference, "version");
7706
+ }
7707
+ }
7708
+ function assertToolSelectionSnapshot(value) {
7709
+ const snapshot = objectOf(value, "tool state");
7710
+ const catalog = snapshot["catalog"];
7711
+ if (catalog !== null) {
7712
+ const state = objectOf(catalog, "tool state catalog");
7713
+ if (state["mode"] !== "full" && state["mode"] !== "deferred")
7714
+ throw new SessionSchemaError("invalid tool mode");
7715
+ for (const version of Object.values(objectOf(state["tools"], "tool versions"))) {
7716
+ if (typeof version !== "string")
7717
+ throw new SessionSchemaError("invalid tool version");
7718
+ }
7719
+ }
7720
+ if (!Array.isArray(snapshot["activeTools"]))
7721
+ throw new SessionSchemaError("invalid active tools");
7722
+ for (const tool of snapshot["activeTools"])
7723
+ assertStoredTool(tool, false);
7724
+ }
7685
7725
  function contextResetBoundary(entries) {
7686
7726
  return entries.reduce((boundary, entry) => entry.entryType === "context_reset" ? Math.max(boundary, entry.payload.resetThroughSeq) : boundary, 0);
7687
7727
  }
@@ -8666,7 +8706,7 @@ var ConversationHistory = class _ConversationHistory {
8666
8706
  payload: input
8667
8707
  });
8668
8708
  }
8669
- appendToolDefinitions(input) {
8709
+ appendToolState(input) {
8670
8710
  return this.append({ entryType: "tool_definitions", payload: input });
8671
8711
  }
8672
8712
  appendToolCatalog(input) {
@@ -9054,7 +9094,9 @@ function conversationTail(entries, activeCompaction) {
9054
9094
  return entries.filter((entry) => entry.seq >= boundary && entry.entryType !== "system_context" && entry.entryType !== "project_instructions" && entry.entryType !== "compaction");
9055
9095
  }
9056
9096
  function modelMessageForEntry(entry, currentProvider, currentModel) {
9057
- if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9097
+ if (entry.entryType === "tool_definitions")
9098
+ return null;
9099
+ if (entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9058
9100
  return entry.payload.message;
9059
9101
  if (entry.entryType === "user_message" || entry.entryType === "reminder") {
9060
9102
  return entry.payload.message;
@@ -9151,8 +9193,6 @@ function serializeConversation(entries) {
9151
9193
  }
9152
9194
  } else if (entry.entryType === "tool_result") {
9153
9195
  lines2.push(`[tool-result ${entry.payload.message.toolCallId} ${entry.payload.message.toolName}] ${entry.payload.message.content}`);
9154
- } else if (entry.entryType === "tool_definitions") {
9155
- lines2.push(`[loaded tool definitions] ${(entry.payload.message.toolDefinitions ?? []).map((tool) => tool.spec.name).join(", ")}`);
9156
9196
  } else if (entry.entryType === "user_message" || entry.entryType === "skill_context" || entry.entryType === "reminder" || entry.entryType === "project_instructions") {
9157
9197
  lines2.push(`[${entry.payload.message.role}] ${entry.payload.message.content}`);
9158
9198
  }
@@ -9167,12 +9207,9 @@ function serializeConversation(entries) {
9167
9207
  }
9168
9208
  function semanticUnits(entries) {
9169
9209
  const units = [];
9170
- const groupedDefinitions = /* @__PURE__ */ new Set();
9171
9210
  for (let index = 0; index < entries.length; index += 1) {
9172
9211
  const entry = entries[index];
9173
- if (groupedDefinitions.has(entry.id))
9174
- continue;
9175
- if (entry.entryType === "context_reset" || entry.entryType === "image_offload" || entry.entryType === "system_context" || entry.entryType === "project_instructions" || entry.entryType === "compaction") {
9212
+ if (entry.entryType === "context_reset" || entry.entryType === "image_offload" || entry.entryType === "tool_definitions" || entry.entryType === "system_context" || entry.entryType === "project_instructions" || entry.entryType === "compaction") {
9176
9213
  continue;
9177
9214
  }
9178
9215
  if (entry.entryType === "assistant_message") {
@@ -9186,12 +9223,6 @@ function semanticUnits(entries) {
9186
9223
  }
9187
9224
  grouped.push(entries[resultIndex]);
9188
9225
  }
9189
- const lastResultIndex = entries.findIndex((candidate) => candidate.id === grouped.at(-1).id);
9190
- const definitions = entries[lastResultIndex + 1];
9191
- if (definitions?.entryType === "tool_definitions") {
9192
- grouped.push(definitions);
9193
- groupedDefinitions.add(definitions.id);
9194
- }
9195
9226
  units.push({ entries: grouped, messages: grouped.map(messageForEntry) });
9196
9227
  continue;
9197
9228
  }
@@ -9204,7 +9235,7 @@ function semanticUnits(entries) {
9204
9235
  return units;
9205
9236
  }
9206
9237
  function messageForEntry(entry) {
9207
- if (entry.entryType === "tool_definitions" || entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9238
+ if (entry.entryType === "tool_catalog" || entry.entryType === "skill_context")
9208
9239
  return entry.payload.message;
9209
9240
  if (entry.entryType === "assistant_message") {
9210
9241
  return entry.payload.message;
@@ -9275,9 +9306,7 @@ var ContextGovernor = class {
9275
9306
  const plan = selectCompactionPlan({
9276
9307
  protectedFromSeq: input.protectedFromSeq,
9277
9308
  entries: projectedEntries,
9278
- // A search preflight reserves the pending call/result/definition unit.
9279
- // Keep the latest completed unit and summarize older units to make room.
9280
- retainTokens: input.reserveTokens === void 0 ? calculated.retainTokens : 1,
9309
+ retainTokens: calculated.retainTokens,
9281
9310
  estimator: this.#estimator
9282
9311
  });
9283
9312
  if (plan.summarizedEntries.length === 0) {
@@ -9308,7 +9337,8 @@ var ContextGovernor = class {
9308
9337
  summaryOutputTokens: summary.outputTokens,
9309
9338
  provider: input.currentProvider,
9310
9339
  model: input.currentModel,
9311
- trigger: input.trigger
9340
+ trigger: input.trigger,
9341
+ ...input.toolState === void 0 ? {} : { toolState: input.toolState }
9312
9342
  };
9313
9343
  return { entry: this.#appendCompaction(payload) };
9314
9344
  }
@@ -9340,7 +9370,7 @@ var ContextGovernor = class {
9340
9370
  systemFingerprint: fingerprintContextPart(built.messages.find((message) => message.role === "system") ?? null),
9341
9371
  projectInstructionsFingerprint: fingerprintContextPart(built.messages.filter((message, index) => index > 0 && message.role === "user")),
9342
9372
  toolsFingerprint: fingerprintContextPart(tools)
9343
- }).totalTokens + (input.reserveTokens ?? 0);
9373
+ }).totalTokens;
9344
9374
  }
9345
9375
  };
9346
9376
  function serializeCompactionInput(previousSummary, entries) {
@@ -9360,6 +9390,52 @@ function calculateModelBudget(input) {
9360
9390
  return { hardInputLimit, safetyTokens, autoTrigger, retainTokens };
9361
9391
  }
9362
9392
 
9393
+ // ../../packages/session/session-context/dist/tool-state.js
9394
+ function projectToolSelectionState(entries) {
9395
+ const sorted = [...entries].sort((left, right) => left.seq - right.seq);
9396
+ const latestCompaction2 = [...sorted].reverse().find((entry) => entry.entryType === "compaction");
9397
+ const checkpoint = latestCompaction2?.entryType === "compaction" && latestCompaction2.payload.toolState !== void 0 ? latestCompaction2 : void 0;
9398
+ let catalog = checkpoint?.entryType === "compaction" ? copyCatalog(checkpoint.payload.toolState.catalog) : null;
9399
+ const active = /* @__PURE__ */ new Map();
9400
+ if (checkpoint?.entryType === "compaction") {
9401
+ for (const tool of checkpoint.payload.toolState.activeTools) {
9402
+ active.set(tool.spec.name, copyActiveTool(tool));
9403
+ }
9404
+ }
9405
+ const afterSeq = checkpoint?.seq ?? 0;
9406
+ for (const entry of sorted) {
9407
+ if (entry.seq <= afterSeq)
9408
+ continue;
9409
+ if (entry.entryType === "tool_catalog") {
9410
+ catalog = copyCatalog(entry.payload.message.toolCatalog ?? null);
9411
+ continue;
9412
+ }
9413
+ if (entry.entryType !== "tool_definitions")
9414
+ continue;
9415
+ for (const removed of entry.payload.message.toolsRemoved ?? []) {
9416
+ const current = active.get(removed.name);
9417
+ if (current !== void 0 && (removed.version === void 0 || current.version === removed.version)) {
9418
+ active.delete(removed.name);
9419
+ }
9420
+ }
9421
+ for (const tool of entry.payload.message.toolDefinitions ?? []) {
9422
+ const activeTool = normalizeActiveTool(tool);
9423
+ active.set(activeTool.spec.name, activeTool);
9424
+ }
9425
+ }
9426
+ return { catalog, activeTools: [...active.values()] };
9427
+ }
9428
+ function normalizeActiveTool(tool) {
9429
+ const activation = "activation" in tool && (tool.activation === "baseline" || tool.activation === "search") ? tool.activation : "search";
9430
+ return { version: tool.version, spec: tool.spec, activation };
9431
+ }
9432
+ function copyCatalog(catalog) {
9433
+ return catalog === null ? null : { mode: catalog.mode, tools: { ...catalog.tools } };
9434
+ }
9435
+ function copyActiveTool(tool) {
9436
+ return { version: tool.version, spec: tool.spec, activation: tool.activation };
9437
+ }
9438
+
9363
9439
  // ../../packages/llm/llm-pi-ai/dist/adapter.js
9364
9440
  import { clampThinkingLevel, ModelsError } from "@earendil-works/pi-ai";
9365
9441
  import { builtinModels } from "@earendil-works/pi-ai/providers/all";
@@ -18458,6 +18534,33 @@ function errorMessage3(error) {
18458
18534
  return error instanceof Error ? error.message : String(error);
18459
18535
  }
18460
18536
 
18537
+ // ../../packages/llm/llm-pi-ai/dist/tool-validation.js
18538
+ import { validateToolArguments } from "@earendil-works/pi-ai";
18539
+ var validatePiToolArguments = (spec2, call, args) => {
18540
+ const tool = {
18541
+ name: spec2.name,
18542
+ description: spec2.description,
18543
+ parameters: spec2.parameters
18544
+ };
18545
+ const toolCall = {
18546
+ type: "toolCall",
18547
+ id: call.id,
18548
+ name: call.name,
18549
+ arguments: args
18550
+ };
18551
+ let validated;
18552
+ try {
18553
+ validated = validateToolArguments(tool, toolCall);
18554
+ } catch (error) {
18555
+ const message = error instanceof Error ? error.message : String(error);
18556
+ throw new Error(message.split("\n\nReceived arguments:\n")[0]);
18557
+ }
18558
+ if (typeof validated !== "object" || validated === null || Array.isArray(validated)) {
18559
+ throw new Error("Validated tool arguments must be a JSON object");
18560
+ }
18561
+ return validated;
18562
+ };
18563
+
18461
18564
  // ../../packages/storage/local-config/dist/config.js
18462
18565
  import { randomUUID as randomUUID3 } from "node:crypto";
18463
18566
  import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync } from "node:fs";
@@ -28837,8 +28940,13 @@ var StdTerminalDriver = class {
28837
28940
  var ToolRuntime = class {
28838
28941
  tools;
28839
28942
  createExecutionContext;
28840
- constructor(tools, options = {}) {
28943
+ validateArguments;
28944
+ constructor(tools, options) {
28945
+ if (typeof options?.validateToolArguments !== "function") {
28946
+ throw new Error("Tool argument validator is required");
28947
+ }
28841
28948
  this.tools = tools;
28949
+ this.validateArguments = options.validateToolArguments;
28842
28950
  this.createExecutionContext = options.createExecutionContext ?? ((_, cancelToken) => ({
28843
28951
  signal: cancelToken?.signal,
28844
28952
  isCancelled: () => isCancelled(cancelToken),
@@ -28849,6 +28957,7 @@ var ToolRuntime = class {
28849
28957
  }
28850
28958
  async execute(request) {
28851
28959
  const registry = request.registry ?? this.tools;
28960
+ const definitions = new Map(registry.definitions.map((tool) => [tool.name, tool]));
28852
28961
  const results = new Array(request.toolCalls.length).fill(void 0);
28853
28962
  const prepared = [];
28854
28963
  for (let offset = 0; offset < request.toolCalls.length; offset += 1) {
@@ -28856,7 +28965,7 @@ var ToolRuntime = class {
28856
28965
  if (toolCall === void 0) {
28857
28966
  continue;
28858
28967
  }
28859
- let args;
28968
+ let args = { _raw: toolCall.arguments };
28860
28969
  let result;
28861
28970
  try {
28862
28971
  const decoded = JSON.parse(toolCall.arguments);
@@ -28864,8 +28973,11 @@ var ToolRuntime = class {
28864
28973
  throw new Error("Tool arguments must be a JSON object");
28865
28974
  }
28866
28975
  args = decoded;
28976
+ const tool = definitions.get(toolCall.name);
28977
+ if (tool === void 0)
28978
+ throw new Error(`Unknown tool: ${toolCall.name}`);
28979
+ args = this.validateArguments(tool, toolCall, args);
28867
28980
  } catch (error) {
28868
- args = { _raw: toolCall.arguments };
28869
28981
  result = { ok: false, error: errorMessage7(error) };
28870
28982
  }
28871
28983
  const event = {
@@ -29038,23 +29150,92 @@ var TOOL_SEARCH_THRESHOLD = 20;
29038
29150
  var ToolSelection = class {
29039
29151
  indexKey = "";
29040
29152
  index;
29041
- prepare(registry, records, canLoad = () => true, retainedRecords) {
29153
+ catalog = null;
29154
+ active = /* @__PURE__ */ new Map();
29155
+ apply(update) {
29156
+ if (update.catalog !== void 0)
29157
+ this.catalog = copyCatalog2(update.catalog);
29158
+ for (const removed of update.toolsRemoved) {
29159
+ const current = this.active.get(removed.name);
29160
+ if (current !== void 0 && (removed.version === void 0 || current.version === removed.version)) {
29161
+ this.active.delete(removed.name);
29162
+ }
29163
+ }
29164
+ for (const tool of update.toolsAdded)
29165
+ this.active.set(tool.spec.name, copyActiveTool2(tool));
29166
+ }
29167
+ restore(snapshot) {
29168
+ this.catalog = snapshot.catalog === null ? null : copyCatalog2(snapshot.catalog);
29169
+ this.active.clear();
29170
+ for (const tool of snapshot.activeTools)
29171
+ this.active.set(tool.spec.name, copyActiveTool2(tool));
29172
+ }
29173
+ snapshot() {
29174
+ return {
29175
+ catalog: this.catalog === null ? null : copyCatalog2(this.catalog),
29176
+ activeTools: [...this.active.values()].map(copyActiveTool2)
29177
+ };
29178
+ }
29179
+ prepare(registry) {
29042
29180
  const all = registry.definitions.filter((spec2) => spec2.name !== SEARCH_TOOL_NAME);
29043
29181
  const deferred = all.filter((spec2) => spec2.catalog?.exposure === "deferred");
29044
29182
  const mode = all.length >= TOOL_SEARCH_THRESHOLD ? "deferred" : "full";
29045
- const versions = new Map(deferred.map((spec2) => [spec2.name, toolVersion(spec2)]));
29046
- const catalog = { mode, tools: mode === "deferred" ? Object.fromEntries(versions) : {} };
29047
- let previous;
29048
- const loaded = loadedVersions(records, versions);
29049
- for (const record of records) {
29050
- if (record.toolCatalog) {
29051
- previous = record.toolCatalog;
29183
+ const versions = new Map(all.map((spec2) => [spec2.name, toolVersion(spec2)]));
29184
+ const catalog = {
29185
+ mode,
29186
+ tools: mode === "deferred" ? Object.fromEntries(deferred.map((spec2) => [spec2.name, versions.get(spec2.name)])) : {}
29187
+ };
29188
+ const current = new Map(all.map((spec2) => [spec2.name, spec2]));
29189
+ const desired = /* @__PURE__ */ new Map();
29190
+ const toolsAdded = [];
29191
+ const toolsRemoved = [];
29192
+ const missing = [];
29193
+ for (const [name, active] of this.active) {
29194
+ const spec2 = current.get(name);
29195
+ if (spec2 === void 0) {
29196
+ toolsRemoved.push({ name, version: active.version });
29197
+ missing.push(name);
29198
+ continue;
29199
+ }
29200
+ const baseline = mode === "full" || spec2.catalog?.exposure !== "deferred";
29201
+ const version = versions.get(name);
29202
+ if (version !== active.version) {
29203
+ toolsRemoved.push({ name, version: active.version });
29204
+ if (baseline || active.activation === "search") {
29205
+ const updated = {
29206
+ spec: spec2,
29207
+ version,
29208
+ activation: spec2.catalog?.exposure === "deferred" && active.activation === "search" ? "search" : "baseline"
29209
+ };
29210
+ desired.set(name, updated);
29211
+ toolsAdded.push(updated);
29212
+ }
29213
+ continue;
29214
+ }
29215
+ if (baseline || active.activation === "search") {
29216
+ desired.set(name, { spec: spec2, version, activation: active.activation });
29217
+ } else {
29218
+ toolsRemoved.push({ name, version: active.version });
29052
29219
  }
29053
29220
  }
29054
- const changed = canonicalToolJson(previous ?? { mode: "full", tools: {} }) !== canonicalToolJson(catalog);
29221
+ for (const spec2 of all) {
29222
+ const baseline = mode === "full" || spec2.catalog?.exposure !== "deferred";
29223
+ if (!baseline || desired.has(spec2.name))
29224
+ continue;
29225
+ const added = { spec: spec2, version: versions.get(spec2.name), activation: "baseline" };
29226
+ desired.set(spec2.name, added);
29227
+ toolsAdded.push(added);
29228
+ }
29229
+ const catalogChanged = canonicalToolJson(this.catalog ?? { mode: "full", tools: {} }) !== canonicalToolJson(catalog);
29230
+ const update = {
29231
+ ...catalogChanged ? { catalog } : {},
29232
+ toolsAdded,
29233
+ toolsRemoved
29234
+ };
29235
+ const loaded = new Map([...desired].map(([name, tool]) => [name, tool.version]));
29055
29236
  let announcement;
29056
- if (changed) {
29057
- const before = previous?.mode === "deferred" && mode === "deferred" ? previous.tools : {};
29237
+ if (catalogChanged) {
29238
+ const before = this.catalog?.mode === "deferred" && mode === "deferred" ? this.catalog.tools : {};
29058
29239
  const added = Object.keys(catalog.tools).filter((name) => before[name] !== catalog.tools[name]);
29059
29240
  const removed = Object.keys(before).filter((name) => before[name] !== catalog.tools[name]);
29060
29241
  const sections = ["<system-reminder>", mode === "deferred" ? "Use tool_search to search and load tool definitions before calling deferred tools." : "All currently available tool definitions are now directly available."];
@@ -29073,8 +29254,10 @@ var ToolSelection = class {
29073
29254
  }
29074
29255
  }
29075
29256
  const pending = /* @__PURE__ */ new Map();
29076
- let resultBytes = 0;
29077
- const definitions = mode === "full" ? all : [...all.filter((spec2) => spec2.catalog?.exposure !== "deferred" || loaded.has(spec2.name)), SEARCH_TOOL_SPEC];
29257
+ const definitions = [
29258
+ ...all.filter((spec2) => desired.has(spec2.name)),
29259
+ ...mode === "deferred" ? [SEARCH_TOOL_SPEC] : []
29260
+ ];
29078
29261
  const visible = new Set(definitions.map((spec2) => spec2.name));
29079
29262
  const index = this.index;
29080
29263
  const view = {
@@ -29098,55 +29281,29 @@ var ToolSelection = class {
29098
29281
  name: name2,
29099
29282
  description: spec2.description.length > 1024 ? `${spec2.description.slice(0, 1024)}...[truncated]` : spec2.description,
29100
29283
  source: spec2.catalog?.source,
29101
- status: "not_loaded_budget"
29284
+ status: "loaded"
29102
29285
  };
29103
29286
  });
29104
- resultBytes += Buffer.byteLength(JSON.stringify({ ok: true, matches }), "utf8");
29105
- const previouslyPending = new Set(pending.keys());
29106
- for (let pass = 0; pass < (retainedRecords ? 2 : 1); pass++) {
29107
- const retained = retainedRecords ? loadedVersions(retainedRecords(), versions) : loaded;
29108
- for (const match of matches) {
29109
- const { name: name2 } = match;
29110
- if (pending.has(name2)) {
29111
- match.status = previouslyPending.has(name2) ? "already_loaded" : "loaded";
29112
- continue;
29113
- }
29114
- if (retained.has(name2)) {
29115
- match.status = "already_loaded";
29116
- continue;
29117
- }
29118
- if (pass > 0 && match.status === "not_loaded_budget")
29119
- continue;
29120
- const spec2 = deferred.find((tool2) => tool2.name === name2);
29121
- const tool = { spec: spec2, version: versions.get(name2) };
29122
- if (await canLoad([...pending.values(), tool], resultBytes)) {
29123
- pending.set(name2, tool);
29124
- match.status = "loaded";
29125
- } else
29126
- match.status = "not_loaded_budget";
29287
+ for (const match of matches) {
29288
+ const { name: name2 } = match;
29289
+ if (pending.has(name2) || loaded.has(name2)) {
29290
+ match.status = "already_loaded";
29291
+ continue;
29127
29292
  }
29293
+ const spec2 = deferred.find((tool) => tool.name === name2);
29294
+ pending.set(name2, { spec: spec2, version: versions.get(name2), activation: "search" });
29128
29295
  }
29129
29296
  return { ok: true, matches, ...matches.length ? {} : { content: "No matching tools. Try an exact name, source, or different keywords." } };
29130
29297
  }
29131
29298
  };
29132
- return { view, announcement, mode, pending: () => [...pending.values()] };
29299
+ return { view, announcement, mode, update, missing, pending: () => [...pending.values()] };
29133
29300
  }
29134
29301
  };
29135
- function loadedVersions(records, versions) {
29136
- const loaded = /* @__PURE__ */ new Map();
29137
- for (const record of records) {
29138
- if (record.toolCatalog?.mode === "deferred") {
29139
- for (const [name, version] of loaded)
29140
- if (record.toolCatalog.tools[name] !== version)
29141
- loaded.delete(name);
29142
- }
29143
- for (const tool of record.toolDefinitions ?? [])
29144
- loaded.set(tool.spec.name, tool.version);
29145
- }
29146
- for (const [name, version] of loaded)
29147
- if (versions.get(name) !== version)
29148
- loaded.delete(name);
29149
- return loaded;
29302
+ function copyCatalog2(catalog) {
29303
+ return { mode: catalog.mode, tools: { ...catalog.tools } };
29304
+ }
29305
+ function copyActiveTool2(tool) {
29306
+ return { version: tool.version, spec: tool.spec, activation: tool.activation };
29150
29307
  }
29151
29308
 
29152
29309
  // ../../packages/core/tools/dist/index.js
@@ -32583,44 +32740,22 @@ var AgentStepRunner = class {
32583
32740
  this.options.onRequestId(requestId);
32584
32741
  const registry = this.options.getTools();
32585
32742
  const selection = this.options.selection ?? new ToolSelection();
32586
- let remainingTokens = Number.POSITIVE_INFINITY;
32587
- let preflightCompacted = false;
32588
- const select = (messages) => selection.prepare(registry, messages.map((message) => message.role === "system" || message.role === "user" ? message : {}), async (pending2, resultBytes) => {
32589
- const snapshot = history.snapshot();
32590
- const assistant = snapshot.at(-1);
32591
- const reserve = Math.ceil((resultBytes + Buffer.byteLength(JSON.stringify(assistant ?? {}), "utf8")) / 4);
32592
- const cost2 = reserve + pending2.reduce((sum, { spec: { name, description, parameters } }) => sum + Math.ceil(Buffer.byteLength(JSON.stringify({ name, description, parameters }), "utf8") / 4) + 8, 0);
32593
- if (cost2 <= remainingTokens)
32594
- return true;
32595
- if (preflightCompacted || !this.options.contextGovernor)
32596
- return false;
32597
- preflightCompacted = true;
32598
- if (assistant?.role !== "assistant")
32599
- return false;
32600
- try {
32601
- const projectTools = (retained) => selection.prepare(registry, [...retained.map((message) => message.role === "system" || message.role === "user" ? message : {}), { toolDefinitions: pending2 }]).view.definitions;
32602
- const compacted = await this.options.contextGovernor.prepare({
32603
- messages: snapshot.slice(0, -1),
32604
- tools: projectTools(snapshot),
32605
- projectTools,
32606
- provider: this.options.provider,
32607
- model: this.options.model,
32608
- pendingToolCall: true,
32609
- reserveTokens: reserve + 1024
32610
- });
32611
- history.retain([...compacted.messages, assistant]);
32612
- remainingTokens = compacted.hardInputLimit === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, compacted.hardInputLimit - (compacted.contextTokens ?? 0)) + cost2;
32613
- return cost2 <= remainingTokens;
32614
- } catch (error) {
32615
- if (error instanceof Error && error.name === "ContextBudgetError")
32616
- return false;
32617
- throw error;
32618
- }
32619
- }, () => history.snapshot().map((message) => message.role === "system" || message.role === "user" ? message : {}));
32743
+ const select = () => selection.prepare(registry);
32744
+ const synchronizeSelection = () => {
32745
+ const planned = select();
32746
+ if (planned.update.toolsAdded.length > 0 || planned.update.toolsRemoved.length > 0) {
32747
+ history.commitToolState(planned.update);
32748
+ }
32749
+ if (planned.announcement) {
32750
+ history.commitToolCatalog({ role: "user", ...planned.announcement });
32751
+ }
32752
+ selection.apply(planned.update);
32753
+ if (planned.missing.length > 0)
32754
+ this.options.onToolStateWarning?.(planned.missing);
32755
+ return select();
32756
+ };
32620
32757
  const prepareRequest = async () => {
32621
- let selected2 = select(history.snapshot());
32622
- if (selected2.announcement)
32623
- history.commitToolContext({ role: "user", ...selected2.announcement });
32758
+ let selected2 = synchronizeSelection();
32624
32759
  let prepared2 = { messages: history.snapshot() };
32625
32760
  for (let attempt = 0; attempt < 3; attempt++) {
32626
32761
  prepared2 = await this.options.contextGovernor?.prepare({
@@ -32628,23 +32763,23 @@ var AgentStepRunner = class {
32628
32763
  tools: selected2.view.definitions,
32629
32764
  provider: this.options.provider,
32630
32765
  model: this.options.model,
32631
- projectTools: (messages) => select(messages).view.definitions
32766
+ projectTools: () => select().view.definitions
32632
32767
  }) ?? { messages: history.snapshot() };
32633
32768
  history.retain(prepared2.messages);
32634
- selected2 = select(prepared2.messages);
32769
+ selected2 = select();
32635
32770
  const resources = await this.options.resources?.prepareContext(prepared2.messages, cancelToken?.signal) ?? [];
32636
32771
  if (!selected2.announcement && resources.length === 0)
32637
32772
  break;
32638
32773
  if (attempt === 2)
32639
32774
  throw this.options.createError("Resource catalog cannot fit the retained context.");
32640
- if (selected2.announcement)
32641
- history.commitToolContext({ role: "user", ...selected2.announcement });
32775
+ if (selected2.announcement || selected2.update.toolsAdded.length > 0 || selected2.update.toolsRemoved.length > 0) {
32776
+ selected2 = synchronizeSelection();
32777
+ }
32642
32778
  for (const message of resources) {
32643
32779
  history.commitSkillContext(message);
32644
32780
  this.options.resources?.committed([message]);
32645
32781
  }
32646
32782
  }
32647
- remainingTokens = prepared2.hardInputLimit === void 0 ? Number.POSITIVE_INFINITY : Math.max(0, prepared2.hardInputLimit - (prepared2.contextTokens ?? 0) - 1024);
32648
32783
  return { selected: selected2, prepared: prepared2 };
32649
32784
  };
32650
32785
  let { selected, prepared } = await prepareRequest();
@@ -32716,12 +32851,10 @@ var AgentStepRunner = class {
32716
32851
  tools: definitions,
32717
32852
  provider: this.options.provider,
32718
32853
  model: this.options.model,
32719
- projectTools: (messages) => select(messages).view.definitions
32854
+ projectTools: () => select().view.definitions
32720
32855
  });
32721
32856
  this.raiseIfCancelled();
32722
32857
  history.retain(recovered.messages);
32723
- remainingTokens = Number.POSITIVE_INFINITY;
32724
- preflightCompacted = false;
32725
32858
  ({ selected, prepared } = await prepareRequest());
32726
32859
  this.raiseIfCancelled();
32727
32860
  tools = selected.view;
@@ -32805,13 +32938,16 @@ Authentication failed for ${this.options.provider}. Run /login ${this.options.pr
32805
32938
  onToolResult: (event) => this.completeToolEvent(event, modelRound)
32806
32939
  })).results;
32807
32940
  const repeated = repeatToolPolicy.record(toolCalls, toolResults);
32941
+ const current = new Map(this.options.getTools().definitions.map((spec2) => [spec2.name, toolVersion(spec2)]));
32942
+ const pending = selected.pending().filter((tool) => current.get(tool.spec.name) === tool.version);
32943
+ if (pending.length) {
32944
+ const update = { toolsAdded: pending, toolsRemoved: [] };
32945
+ history.commitToolState(update);
32946
+ selection.apply(update);
32947
+ }
32808
32948
  history.commitToolResults(toolCalls, toolResults);
32809
32949
  this.options.resources?.committed(history.snapshot().slice(-toolResults.length));
32810
32950
  this.raiseIfCancelled();
32811
- const current = new Map(this.options.getTools().definitions.map((spec2) => [spec2.name, toolVersion(spec2)]));
32812
- const pending = selected.pending().filter((tool) => current.get(tool.spec.name) === tool.version);
32813
- if (pending.length)
32814
- history.commitToolContext({ role: "system", content: "", toolDefinitions: pending });
32815
32951
  const touchedPaths = [];
32816
32952
  for (const toolResult of toolResults) {
32817
32953
  const touched = touchedPathOf(toolResult);
@@ -33048,12 +33184,20 @@ var HistoryCommitter = class {
33048
33184
  this.conversationHistory?.appendSkillContext?.({ message });
33049
33185
  this.messages.push(message);
33050
33186
  }
33051
- commitToolContext(message) {
33187
+ commitToolState(update) {
33052
33188
  this.raiseIfCancelled();
33053
- if (message.role === "system")
33054
- this.conversationHistory?.appendToolDefinitions?.({ message });
33055
- else
33056
- this.conversationHistory?.appendToolCatalog?.({ message });
33189
+ this.conversationHistory?.appendToolState?.({
33190
+ message: {
33191
+ role: "system",
33192
+ content: "",
33193
+ toolDefinitions: update.toolsAdded,
33194
+ toolsRemoved: update.toolsRemoved
33195
+ }
33196
+ });
33197
+ }
33198
+ commitToolCatalog(message) {
33199
+ this.raiseIfCancelled();
33200
+ this.conversationHistory?.appendToolCatalog?.({ message });
33057
33201
  this.messages.push(message);
33058
33202
  }
33059
33203
  /** Adopt the governor's retained context without rewriting the append-only journal. */
@@ -33122,7 +33266,7 @@ var CodingAgent = class {
33122
33266
  modelRuntime;
33123
33267
  reasoningEffort;
33124
33268
  toolRuntime;
33125
- selection = new ToolSelection();
33269
+ selection;
33126
33270
  tools;
33127
33271
  repeatToolReminderThresholds;
33128
33272
  toolExecution;
@@ -33143,9 +33287,11 @@ var CodingAgent = class {
33143
33287
  contextGovernor;
33144
33288
  prepareTools;
33145
33289
  resources;
33290
+ onToolStateWarning;
33146
33291
  constructor(options) {
33147
33292
  this.repeatToolReminderThresholds = normalizeReminderThresholds(options.repeatToolReminderThresholds ?? [3, 5, 8]);
33148
33293
  this.model = options.model;
33294
+ this.selection = options.toolSelection ?? new ToolSelection();
33149
33295
  this.tools = options.tools;
33150
33296
  this.onToolEvent = options.onToolEvent ?? null;
33151
33297
  this.onAgentEvent = options.onAgentEvent ?? null;
@@ -33159,9 +33305,11 @@ var CodingAgent = class {
33159
33305
  this.contextGovernor = options.contextGovernor ?? null;
33160
33306
  this.prepareTools = options.prepareTools;
33161
33307
  this.resources = options.resources;
33308
+ this.onToolStateWarning = options.onToolStateWarning;
33162
33309
  this.modelRuntime = new ModelRuntime(this.adapter);
33163
33310
  this.toolExecution = options.toolExecution ?? "parallel";
33164
33311
  this.toolRuntime = new ToolRuntime(this.tools, {
33312
+ validateToolArguments: options.validateToolArguments,
33165
33313
  createExecutionContext: (toolCallId, cancelToken) => makeToolContext(this.activeContext, toolCallId, cancelToken)
33166
33314
  });
33167
33315
  this.instructionRoot = options.projectRoot == null ? null : realpathOrSelf(options.projectRoot);
@@ -33244,6 +33392,7 @@ var CodingAgent = class {
33244
33392
  onToolEvent: (name, args, result) => {
33245
33393
  this.onToolEvent?.(name, args, result);
33246
33394
  },
33395
+ onToolStateWarning: this.onToolStateWarning,
33247
33396
  createError: (message, cause) => new AgentError(message, { cause }),
33248
33397
  createCancelled: (message, cause) => new AgentCancelled(message, { cause })
33249
33398
  });
@@ -33554,7 +33703,7 @@ function createSessionRuntime(options) {
33554
33703
  let releaseAttachments;
33555
33704
  const activeConversationHistory = {
33556
33705
  appendSkillContext: (input) => activeHistory(options.sessionController).appendSkillContext(input),
33557
- appendToolDefinitions: (input) => activeHistory(options.sessionController).appendToolDefinitions(input),
33706
+ appendToolState: (input) => activeHistory(options.sessionController).appendToolState(input),
33558
33707
  appendToolCatalog: (input) => activeHistory(options.sessionController).appendToolCatalog(input),
33559
33708
  appendUser: (input) => activeHistory(options.sessionController).appendUser(input),
33560
33709
  appendAssistant: (input) => activeHistory(options.sessionController).appendAssistant(input),
@@ -33585,6 +33734,7 @@ function createSessionRuntime(options) {
33585
33734
  };
33586
33735
  }
33587
33736
  });
33737
+ const toolSelection = new ToolSelection();
33588
33738
  const agent = new CodingAgent({
33589
33739
  resources: {
33590
33740
  prepareInput: async (input, signal, inputs) => {
@@ -33612,7 +33762,10 @@ function createSessionRuntime(options) {
33612
33762
  modelAdapter: adapter,
33613
33763
  model: route.model,
33614
33764
  tools: options.tools,
33765
+ validateToolArguments: validatePiToolArguments,
33615
33766
  prepareTools: options.prepareTools,
33767
+ toolSelection,
33768
+ onToolStateWarning: (names) => options.presentation?.toolStateWarning?.(names),
33616
33769
  cliName: "laohuang",
33617
33770
  cliVersion: options.version,
33618
33771
  provider: route.provider,
@@ -33621,21 +33774,20 @@ function createSessionRuntime(options) {
33621
33774
  startupCwd: options.startupCwd,
33622
33775
  conversationHistory: activeConversationHistory,
33623
33776
  contextGovernor: {
33624
- prepare: async ({ tools, projectTools: projectTools2, reserveTokens, pendingToolCall }) => {
33777
+ prepare: async ({ tools, projectTools: projectTools2 }) => {
33625
33778
  const history = options.sessionController.history;
33626
33779
  if (history === null) {
33627
33780
  return { messages: [], contextTokens: 0, contextWindow: 0, hardInputLimit: 0 };
33628
33781
  }
33629
33782
  const entries = history.entries();
33630
- const pendingAssistant = pendingToolCall ? [...entries].reverse().find((entry) => entry.entryType === "assistant_message") : void 0;
33631
33783
  const prepared = await createGovernor(history).prepare({
33632
33784
  protectedFromSeq: attachmentTurnStart,
33633
- entries: entries.filter((entry) => entry !== pendingAssistant),
33785
+ entries,
33634
33786
  currentProvider: route.provider,
33635
33787
  currentModel: route.model,
33636
33788
  tools,
33637
33789
  projectTools: projectTools2,
33638
- reserveTokens,
33790
+ toolState: toolSelection.snapshot(),
33639
33791
  budget: modelBudget(selectedModel),
33640
33792
  policy: defaultContextPolicy()
33641
33793
  });
@@ -33655,6 +33807,7 @@ function createSessionRuntime(options) {
33655
33807
  currentModel: route.model,
33656
33808
  tools,
33657
33809
  projectTools: projectTools2,
33810
+ toolState: toolSelection.snapshot(),
33658
33811
  budget: modelBudget(selectedModel),
33659
33812
  policy: defaultContextPolicy()
33660
33813
  });
@@ -33680,10 +33833,7 @@ function createSessionRuntime(options) {
33680
33833
  eventBus: session.eventBus,
33681
33834
  journal: () => options.sessionController.currentJournal
33682
33835
  });
33683
- const projectTools = (messages) => new ToolSelection().prepare(
33684
- options.tools,
33685
- messages.map((message) => message.role === "system" || message.role === "user" ? message : {})
33686
- ).view.definitions;
33836
+ const projectTools = (_messages) => toolSelection.prepare(options.tools).view.definitions;
33687
33837
  const refreshContextUsage = () => {
33688
33838
  const notify = options.presentation?.contextUsageChanged;
33689
33839
  if (notify === void 0) {
@@ -33709,6 +33859,7 @@ function createSessionRuntime(options) {
33709
33859
  options.skills?.selectSession(sessionId);
33710
33860
  options.presentation?.sessionChanged?.(sessionId);
33711
33861
  let entries = history.entries();
33862
+ toolSelection.restore(projectToolSelectionState(entries));
33712
33863
  if (entries.length === 0) {
33713
33864
  const system = agent.messages.find(
33714
33865
  (message) => message.role === "system"
@@ -33738,6 +33889,7 @@ function createSessionRuntime(options) {
33738
33889
  currentModel: route.model,
33739
33890
  tools: options.tools.definitions,
33740
33891
  projectTools,
33892
+ toolState: toolSelection.snapshot(),
33741
33893
  budget: modelBudget(selectedModel),
33742
33894
  policy: defaultContextPolicy(),
33743
33895
  trigger: "manual"
@@ -40159,17 +40311,22 @@ Submit an empty line to retry, or enter a replacement.`);
40159
40311
  projectRoot: instructionRoot,
40160
40312
  startupCwd: process.cwd(),
40161
40313
  version: VERSION,
40162
- presentation: terminalUi === null ? void 0 : {
40163
- sessionChanged: (sessionId) => terminalUi?.setSessionId(sessionId),
40164
- historyChanged: (entries) => {
40165
- const transcript = projectTranscript(entries);
40166
- if (transcript.length > 0) {
40167
- terminalUi?.replaceTranscript(transcript);
40168
- } else if (runtimeInitialized) {
40169
- resetEmptySessionTranscript(terminalUi);
40170
- }
40171
- },
40172
- contextUsageChanged: ({ contextTokens, contextWindow }) => terminalUi?.setContextUsage(contextTokens, contextWindow)
40314
+ presentation: {
40315
+ toolStateWarning: (names) => runtimeForCleanup?.publishNotice(
40316
+ `Previously active tools are no longer available and were removed: ${names.join(", ")}`
40317
+ ),
40318
+ ...terminalUi === null ? {} : {
40319
+ sessionChanged: (sessionId) => terminalUi?.setSessionId(sessionId),
40320
+ historyChanged: (entries) => {
40321
+ const transcript = projectTranscript(entries);
40322
+ if (transcript.length > 0) {
40323
+ terminalUi?.replaceTranscript(transcript);
40324
+ } else if (runtimeInitialized) {
40325
+ resetEmptySessionTranscript(terminalUi);
40326
+ }
40327
+ },
40328
+ contextUsageChanged: ({ contextTokens, contextWindow }) => terminalUi?.setContextUsage(contextTokens, contextWindow)
40329
+ }
40173
40330
  },
40174
40331
  commandDispatcher: (command) => commandDispatcher?.(command) ?? { status: "not_found", command }
40175
40332
  });