@t2000/engine 1.7.0 → 1.9.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.
package/dist/index.js CHANGED
@@ -215,19 +215,19 @@ ${note}`;
215
215
 
216
216
  // src/tool-flags.ts
217
217
  var TOOL_FLAGS = {
218
- // Write tools — financial
219
- save_deposit: { mutating: true, requiresBalance: true },
220
- withdraw: { mutating: true, affectsHealth: true },
221
- send_transfer: { mutating: true, requiresBalance: true, irreversible: true },
222
- swap_execute: { mutating: true, requiresBalance: true },
223
- borrow: { mutating: true, affectsHealth: true },
224
- repay_debt: { mutating: true, requiresBalance: true },
225
- claim_rewards: { mutating: true },
226
- volo_stake: { mutating: true, requiresBalance: true },
227
- volo_unstake: { mutating: true },
228
- // Write tools — pay / services
218
+ // Write tools — financial (bundleable — SPEC 7 Layer 2)
219
+ save_deposit: { mutating: true, requiresBalance: true, bundleable: true },
220
+ withdraw: { mutating: true, affectsHealth: true, bundleable: true },
221
+ send_transfer: { mutating: true, requiresBalance: true, irreversible: true, bundleable: true },
222
+ swap_execute: { mutating: true, requiresBalance: true, bundleable: true },
223
+ borrow: { mutating: true, affectsHealth: true, bundleable: true },
224
+ repay_debt: { mutating: true, requiresBalance: true, bundleable: true },
225
+ claim_rewards: { mutating: true, bundleable: true },
226
+ volo_stake: { mutating: true, requiresBalance: true, bundleable: true },
227
+ volo_unstake: { mutating: true, bundleable: true },
228
+ // Write tools — pay / services (NOT bundleable — see ToolFlags.bundleable JSDoc)
229
229
  pay_api: { mutating: true, requiresBalance: true, costAware: true, producesArtifact: true, maxRetries: 1 },
230
- // Write tools — lightweight (no financial guards)
230
+ // Write tools — lightweight (no financial guards, NOT bundleable — Postgres only)
231
231
  save_contact: {},
232
232
  // [SIMPLIFICATION DAY 7] Removed flag entries for deleted tools:
233
233
  // create_schedule, cancel_schedule (DCA schedules retired)
@@ -248,6 +248,9 @@ function applyToolFlags(tools) {
248
248
  function getToolFlags(name) {
249
249
  return TOOL_FLAGS[name] ?? {};
250
250
  }
251
+ function isBundleableTool(name) {
252
+ return TOOL_FLAGS[name]?.bundleable === true;
253
+ }
251
254
 
252
255
  // src/navi-config.ts
253
256
  var NAVI_SERVER_NAME = "navi";
@@ -5476,6 +5479,51 @@ var CostTracker = class {
5476
5479
  }
5477
5480
  };
5478
5481
 
5482
+ // src/describe-action.ts
5483
+ function resolveTokenSymbol(nameOrType) {
5484
+ if (!nameOrType.includes("::")) return nameOrType;
5485
+ const parts = nameOrType.split("::");
5486
+ return parts[parts.length - 1];
5487
+ }
5488
+ function describeAction(tool, call) {
5489
+ const input = call.input;
5490
+ switch (tool.name) {
5491
+ case "save_deposit": {
5492
+ return `Save ${input.amount} USDC into lending`;
5493
+ }
5494
+ case "withdraw": {
5495
+ const wAsset = input.asset ?? "";
5496
+ return `Withdraw ${input.amount}${wAsset ? " " + wAsset : ""} from lending`;
5497
+ }
5498
+ case "send_transfer":
5499
+ return `Send $${input.amount} to ${input.to}`;
5500
+ case "borrow":
5501
+ return `Borrow $${input.amount} against collateral`;
5502
+ case "repay_debt":
5503
+ return `Repay $${input.amount} of outstanding debt`;
5504
+ case "claim_rewards":
5505
+ return "Claim all pending protocol rewards";
5506
+ case "pay_api": {
5507
+ const url = String(input.url ?? "");
5508
+ const cost = estimatePayApiCost(url);
5509
+ return `Pay for API call to ${url} (~$${cost})`;
5510
+ }
5511
+ case "swap_execute": {
5512
+ const from = resolveTokenSymbol(String(input.from ?? "?"));
5513
+ const to = resolveTokenSymbol(String(input.to ?? "?"));
5514
+ const amt = input.amount ?? "?";
5515
+ const slippagePct = (input.slippage ?? 0.01) * 100;
5516
+ return `Swap ${amt} ${from} for ${to} (${slippagePct}% max slippage)`;
5517
+ }
5518
+ case "volo_stake":
5519
+ return `Stake ${input.amount} SUI for vSUI`;
5520
+ case "volo_unstake":
5521
+ return `Unstake ${input.amount === "all" ? "all" : input.amount} vSUI`;
5522
+ default:
5523
+ return `Execute ${tool.name}`;
5524
+ }
5525
+ }
5526
+
5479
5527
  // src/thinking-budget.ts
5480
5528
  var EFFORT_THINKING_BUDGET_CAPS = {
5481
5529
  // null = thinking force-disabled (LEAN tier — single-fact reads need
@@ -6610,6 +6658,98 @@ async function executeTool(tool, call, context) {
6610
6658
  return { data: result.data, isError: false };
6611
6659
  }
6612
6660
 
6661
+ // src/tool-ttls.ts
6662
+ var TOOL_TTL_MS = {
6663
+ swap_quote: 3e4,
6664
+ rates_info: 9e4,
6665
+ balance_check: 12e4,
6666
+ portfolio_analysis: 12e4,
6667
+ savings_info: 12e4,
6668
+ health_check: 9e4
6669
+ };
6670
+ var DEFAULT_TOOL_TTL_MS = 6e4;
6671
+ function bundleShortestTtl(toolUseIds, toolNamesById) {
6672
+ if (toolUseIds.length === 0) return DEFAULT_TOOL_TTL_MS;
6673
+ let shortest = Number.POSITIVE_INFINITY;
6674
+ for (const id of toolUseIds) {
6675
+ const name = toolNamesById[id];
6676
+ const ttl = (name !== void 0 ? TOOL_TTL_MS[name] : void 0) ?? DEFAULT_TOOL_TTL_MS;
6677
+ if (ttl < shortest) shortest = ttl;
6678
+ }
6679
+ return Number.isFinite(shortest) ? shortest : DEFAULT_TOOL_TTL_MS;
6680
+ }
6681
+ var REGENERATABLE_READ_TOOLS = /* @__PURE__ */ new Set([
6682
+ "swap_quote",
6683
+ "rates_info",
6684
+ "balance_check",
6685
+ "portfolio_analysis",
6686
+ "savings_info",
6687
+ "health_check"
6688
+ ]);
6689
+
6690
+ // src/compose-bundle.ts
6691
+ function composeBundleFromToolResults(input) {
6692
+ if (input.pendingWrites.length < 2) {
6693
+ throw new Error(
6694
+ "composeBundleFromToolResults requires \u22652 pending writes; use the legacy single-write path for N=1."
6695
+ );
6696
+ }
6697
+ const steps = input.pendingWrites.map((call) => {
6698
+ const tool = findTool(input.tools, call.name);
6699
+ if (!tool) {
6700
+ throw new Error(`Unknown tool '${call.name}' in bundle composition`);
6701
+ }
6702
+ if (tool.flags?.bundleable !== true) {
6703
+ throw new Error(
6704
+ `Tool '${call.name}' is not bundleable. Set ToolFlags.bundleable=true in tool-flags.ts before including it in a bundle. See SPEC 7 \xA7 "Layer 2 \u2014 Bundleable tools (v1)".`
6705
+ );
6706
+ }
6707
+ const description = describeAction(tool, call);
6708
+ const modifiableFields = getModifiableFields(call.name);
6709
+ return {
6710
+ toolName: call.name,
6711
+ toolUseId: call.id,
6712
+ attemptId: randomUUID(),
6713
+ input: call.input,
6714
+ description,
6715
+ ...modifiableFields?.length ? { modifiableFields } : {}
6716
+ };
6717
+ });
6718
+ const regenerateToolUseIds = input.readResults.filter((r) => REGENERATABLE_READ_TOOLS.has(r.toolName)).map((r) => r.toolUseId);
6719
+ const canRegenerate = regenerateToolUseIds.length > 0;
6720
+ let quoteAge;
6721
+ if (regenerateToolUseIds.length > 0) {
6722
+ const stalest = Math.min(
6723
+ ...input.readResults.filter((r) => REGENERATABLE_READ_TOOLS.has(r.toolName)).map((r) => r.timestamp)
6724
+ );
6725
+ quoteAge = Math.max(0, Date.now() - stalest);
6726
+ }
6727
+ const allGuardInjections = [];
6728
+ if (input.guardInjectionsByCallId) {
6729
+ for (const call of input.pendingWrites) {
6730
+ const injections = input.guardInjectionsByCallId[call.id];
6731
+ if (injections?.length) allGuardInjections.push(...injections);
6732
+ }
6733
+ }
6734
+ const firstStep = steps[0];
6735
+ const action = {
6736
+ toolName: firstStep.toolName,
6737
+ toolUseId: firstStep.toolUseId,
6738
+ input: firstStep.input,
6739
+ description: firstStep.description,
6740
+ assistantContent: input.assistantContent,
6741
+ completedResults: input.completedResults,
6742
+ ...allGuardInjections.length ? { guardInjections: allGuardInjections } : {},
6743
+ turnIndex: input.turnIndex,
6744
+ attemptId: firstStep.attemptId,
6745
+ steps,
6746
+ canRegenerate,
6747
+ ...quoteAge !== void 0 ? { quoteAge } : {},
6748
+ ...regenerateToolUseIds.length > 0 ? { regenerateInput: { toolUseIds: regenerateToolUseIds } } : {}
6749
+ };
6750
+ return action;
6751
+ }
6752
+
6613
6753
  // src/engine.ts
6614
6754
  var DEFAULT_MAX_TURNS = 10;
6615
6755
  var DEFAULT_MAX_TOKENS = 4096;
@@ -6760,17 +6900,57 @@ var QueryEngine = class {
6760
6900
  async *resumeWithToolResult(action, response) {
6761
6901
  this.abortController = new AbortController();
6762
6902
  const signal = this.abortController.signal;
6763
- const writeResult = response.approved ? {
6764
- type: "tool_result",
6765
- toolUseId: action.toolUseId,
6766
- content: JSON.stringify(response.executionResult ?? { success: true }),
6767
- isError: false
6768
- } : {
6769
- type: "tool_result",
6770
- toolUseId: action.toolUseId,
6771
- content: JSON.stringify({ error: "User declined this action" }),
6772
- isError: true
6773
- };
6903
+ const isBundle = Array.isArray(action.steps) && action.steps.length > 0;
6904
+ const writeResultBlocks = [];
6905
+ if (isBundle) {
6906
+ const steps = action.steps;
6907
+ const stepResults = response.stepResults ?? [];
6908
+ const resultByToolUseId = new Map(stepResults.map((r) => [r.toolUseId, r]));
6909
+ for (const step of steps) {
6910
+ if (response.approved) {
6911
+ const stepResult = resultByToolUseId.get(step.toolUseId);
6912
+ if (stepResult) {
6913
+ writeResultBlocks.push({
6914
+ type: "tool_result",
6915
+ toolUseId: step.toolUseId,
6916
+ content: JSON.stringify(stepResult.result),
6917
+ isError: stepResult.isError
6918
+ });
6919
+ } else {
6920
+ writeResultBlocks.push({
6921
+ type: "tool_result",
6922
+ toolUseId: step.toolUseId,
6923
+ content: JSON.stringify({
6924
+ error: "Host omitted this step's execution result. Treating as failure \u2014 actual on-chain state is unknown. Re-check wallet via balance_check before re-attempting.",
6925
+ _hostBugMissingStepResult: true
6926
+ }),
6927
+ isError: true
6928
+ });
6929
+ }
6930
+ } else {
6931
+ writeResultBlocks.push({
6932
+ type: "tool_result",
6933
+ toolUseId: step.toolUseId,
6934
+ content: JSON.stringify({ error: "User declined this action" }),
6935
+ isError: true
6936
+ });
6937
+ }
6938
+ }
6939
+ } else {
6940
+ writeResultBlocks.push(
6941
+ response.approved ? {
6942
+ type: "tool_result",
6943
+ toolUseId: action.toolUseId,
6944
+ content: JSON.stringify(response.executionResult ?? { success: true }),
6945
+ isError: false
6946
+ } : {
6947
+ type: "tool_result",
6948
+ toolUseId: action.toolUseId,
6949
+ content: JSON.stringify({ error: "User declined this action" }),
6950
+ isError: true
6951
+ }
6952
+ );
6953
+ }
6774
6954
  if (action.assistantContent?.length) {
6775
6955
  this.messages.push({ role: "assistant", content: action.assistantContent });
6776
6956
  }
@@ -6781,16 +6961,47 @@ var QueryEngine = class {
6781
6961
  content: r.content,
6782
6962
  isError: r.isError
6783
6963
  })),
6784
- writeResult
6964
+ ...writeResultBlocks
6785
6965
  ];
6786
6966
  this.messages.push({ role: "user", content: allResults });
6787
- yield {
6788
- type: "tool_result",
6789
- toolName: action.toolName,
6790
- toolUseId: action.toolUseId,
6791
- result: response.approved ? response.executionResult ?? { success: true } : { error: "User declined this action" },
6792
- isError: !response.approved
6793
- };
6967
+ if (isBundle) {
6968
+ const steps = action.steps;
6969
+ const stepResults = response.stepResults ?? [];
6970
+ const resultByToolUseId = new Map(stepResults.map((r) => [r.toolUseId, r]));
6971
+ for (const step of steps) {
6972
+ const stepResult = resultByToolUseId.get(step.toolUseId);
6973
+ let eventResult;
6974
+ let eventIsError;
6975
+ if (!response.approved) {
6976
+ eventResult = { error: "User declined this action" };
6977
+ eventIsError = true;
6978
+ } else if (stepResult) {
6979
+ eventResult = stepResult.result;
6980
+ eventIsError = stepResult.isError;
6981
+ } else {
6982
+ eventResult = {
6983
+ error: "Host omitted this step's execution result.",
6984
+ _hostBugMissingStepResult: true
6985
+ };
6986
+ eventIsError = true;
6987
+ }
6988
+ yield {
6989
+ type: "tool_result",
6990
+ toolName: step.toolName,
6991
+ toolUseId: step.toolUseId,
6992
+ result: eventResult,
6993
+ isError: eventIsError
6994
+ };
6995
+ }
6996
+ } else {
6997
+ yield {
6998
+ type: "tool_result",
6999
+ toolName: action.toolName,
7000
+ toolUseId: action.toolUseId,
7001
+ result: response.approved ? response.executionResult ?? { success: true } : { error: "User declined this action" },
7002
+ isError: !response.approved
7003
+ };
7004
+ }
6794
7005
  if (!response.approved) {
6795
7006
  yield { type: "turn_complete", stopReason: "end_turn" };
6796
7007
  this.turnReadCache.clear();
@@ -6817,10 +7028,27 @@ var QueryEngine = class {
6817
7028
  * the fresh tool results and narrates from them.
6818
7029
  */
6819
7030
  async *runPostWriteRefresh(action, response, signal) {
6820
- const refreshList = this.postWriteRefresh?.[action.toolName];
6821
- if (!refreshList || refreshList.length === 0) return;
6822
- const exec = response.executionResult;
6823
- const writeFailed = exec != null && typeof exec === "object" && "success" in exec && exec.success === false;
7031
+ const isBundle = Array.isArray(action.steps) && action.steps.length > 0;
7032
+ const refreshSet = /* @__PURE__ */ new Set();
7033
+ if (isBundle) {
7034
+ for (const step of action.steps) {
7035
+ const stepRefresh = this.postWriteRefresh?.[step.toolName];
7036
+ if (stepRefresh) for (const t of stepRefresh) refreshSet.add(t);
7037
+ }
7038
+ } else {
7039
+ const singleRefresh = this.postWriteRefresh?.[action.toolName];
7040
+ if (singleRefresh) for (const t of singleRefresh) refreshSet.add(t);
7041
+ }
7042
+ if (refreshSet.size === 0) return;
7043
+ const refreshList = Array.from(refreshSet);
7044
+ const writeFailed = (() => {
7045
+ if (isBundle) {
7046
+ const stepResults = response.stepResults ?? [];
7047
+ return stepResults.some((r) => r.isError);
7048
+ }
7049
+ const exec = response.executionResult;
7050
+ return exec != null && typeof exec === "object" && "success" in exec && exec.success === false;
7051
+ })();
6824
7052
  if (writeFailed) return;
6825
7053
  const refreshTools = refreshList.map((name) => findTool(this.tools, name)).filter(
6826
7054
  (t) => t !== void 0 && t.isReadOnly && t.isConcurrencySafe
@@ -6932,6 +7160,16 @@ var QueryEngine = class {
6932
7160
  getMessages() {
6933
7161
  return this.messages;
6934
7162
  }
7163
+ /**
7164
+ * [SPEC 7 P2.4b] Read-only access to the engine's tool registry.
7165
+ * Exposed so out-of-band utilities like `regenerateBundle` can call
7166
+ * `composeBundleFromToolResults({ tools: engine.getTools(), ... })`
7167
+ * without forcing the host to hand-thread the tool array. Mirrors
7168
+ * `getMessages()` access pattern.
7169
+ */
7170
+ getTools() {
7171
+ return this.tools;
7172
+ }
6935
7173
  getMatchedRecipe() {
6936
7174
  return this.matchedRecipe;
6937
7175
  }
@@ -7067,6 +7305,7 @@ var QueryEngine = class {
7067
7305
  pendingToolCalls: []
7068
7306
  };
7069
7307
  const dispatcher = new EarlyToolDispatcher(this.tools, context, this.turnReadCache);
7308
+ const turnReadToolResults = [];
7070
7309
  try {
7071
7310
  const microcompacted = microcompact(this.messages, this.tools);
7072
7311
  this.messages = microcompacted;
@@ -7217,6 +7456,13 @@ ${recipeCtx}`;
7217
7456
  };
7218
7457
  }
7219
7458
  }
7459
+ if (!finalEvent.isError && tool && tool.isReadOnly) {
7460
+ turnReadToolResults.push({
7461
+ toolUseId: finalEvent.toolUseId,
7462
+ toolName: finalEvent.toolName,
7463
+ timestamp: Date.now()
7464
+ });
7465
+ }
7220
7466
  earlyResultBlocks.push({
7221
7467
  type: "tool_result",
7222
7468
  toolUseId: finalEvent.toolUseId,
@@ -7245,7 +7491,7 @@ ${recipeCtx}`;
7245
7491
  }
7246
7492
  const approved = [];
7247
7493
  const toolResultBlocks = [...earlyResultBlocks];
7248
- let pendingWrite = null;
7494
+ const pendingWrites = [];
7249
7495
  for (const call of acc.pendingToolCalls) {
7250
7496
  const tool = findTool(this.tools, call.name);
7251
7497
  if (tool && tool.isReadOnly) {
@@ -7298,8 +7544,7 @@ ${recipeCtx}`;
7298
7544
  yield { type: "tool_start", toolName: call.name, toolUseId: call.id, input: call.input };
7299
7545
  continue;
7300
7546
  }
7301
- pendingWrite = { call, tool };
7302
- break;
7547
+ pendingWrites.push({ call, tool });
7303
7548
  }
7304
7549
  const guardedApproved = [];
7305
7550
  if (this.guardConfig) {
@@ -7396,6 +7641,11 @@ ${recipeCtx}`;
7396
7641
  result: finalEvent.result,
7397
7642
  sourceToolUseId: finalEvent.toolUseId
7398
7643
  });
7644
+ turnReadToolResults.push({
7645
+ toolUseId: finalEvent.toolUseId,
7646
+ toolName: finalEvent.toolName,
7647
+ timestamp: Date.now()
7648
+ });
7399
7649
  } else {
7400
7650
  this.turnReadCache.clear();
7401
7651
  }
@@ -7447,46 +7697,101 @@ ${recipeCtx}`;
7447
7697
  }
7448
7698
  yield toolEvent;
7449
7699
  }
7450
- if (pendingWrite && this.guardConfig) {
7700
+ const guardPassedWrites = [];
7701
+ const guardInjectionsByCallId = {};
7702
+ let anyGuardBlocked = false;
7703
+ if (this.guardConfig && pendingWrites.length > 0) {
7451
7704
  const convCtx = extractConversationText(this.messages);
7452
- const check = runGuards(
7453
- pendingWrite.tool,
7454
- pendingWrite.call,
7455
- this.guardState,
7456
- this.guardConfig,
7457
- convCtx,
7458
- this.onGuardFired,
7459
- { contacts: this.contacts, walletAddress: this.walletAddress }
7460
- );
7461
- this.guardEvents.push(...check.events);
7462
- if (check.blocked) {
7463
- yield {
7464
- type: "tool_result",
7465
- toolName: pendingWrite.call.name,
7466
- toolUseId: pendingWrite.call.id,
7467
- result: { error: check.blockReason, _gate: check.blockGate },
7468
- isError: true
7469
- };
7470
- toolResultBlocks.push({
7471
- type: "tool_result",
7472
- toolUseId: pendingWrite.call.id,
7473
- content: JSON.stringify({ error: check.blockReason, _gate: check.blockGate }),
7474
- isError: true
7475
- });
7705
+ for (const write of pendingWrites) {
7706
+ const check = runGuards(
7707
+ write.tool,
7708
+ write.call,
7709
+ this.guardState,
7710
+ this.guardConfig,
7711
+ convCtx,
7712
+ this.onGuardFired,
7713
+ { contacts: this.contacts, walletAddress: this.walletAddress }
7714
+ );
7715
+ this.guardEvents.push(...check.events);
7716
+ if (check.blocked) {
7717
+ anyGuardBlocked = true;
7718
+ yield {
7719
+ type: "tool_result",
7720
+ toolName: write.call.name,
7721
+ toolUseId: write.call.id,
7722
+ result: { error: check.blockReason, _gate: check.blockGate },
7723
+ isError: true
7724
+ };
7725
+ toolResultBlocks.push({
7726
+ type: "tool_result",
7727
+ toolUseId: write.call.id,
7728
+ content: JSON.stringify({ error: check.blockReason, _gate: check.blockGate }),
7729
+ isError: true
7730
+ });
7731
+ continue;
7732
+ }
7733
+ if (check.injections.length > 0) {
7734
+ guardInjectionsByCallId[write.call.id] = check.injections;
7735
+ write.call._guardInjections = check.injections;
7736
+ }
7737
+ guardPassedWrites.push(write);
7738
+ }
7739
+ if (anyGuardBlocked) {
7476
7740
  this.messages.push({ role: "assistant", content: acc.assistantBlocks });
7477
7741
  this.messages.push({ role: "user", content: toolResultBlocks });
7478
7742
  continue;
7479
7743
  }
7480
- if (check.injections.length > 0) {
7481
- pendingWrite.call._guardInjections = check.injections;
7482
- }
7744
+ } else {
7745
+ guardPassedWrites.push(...pendingWrites);
7483
7746
  }
7484
- if (pendingWrite) {
7485
- const writeGuardInjections = pendingWrite.call._guardInjections;
7486
- const modifiableFields = getModifiableFields(pendingWrite.call.name);
7747
+ if (guardPassedWrites.length > 0) {
7748
+ const allBundleable = guardPassedWrites.length >= 2 && guardPassedWrites.every((w) => w.tool.flags?.bundleable === true);
7487
7749
  const turnIndex = this.messages.filter((m) => m.role === "assistant").length;
7488
- const attemptId = randomUUID();
7489
7750
  this.turnPaused = true;
7751
+ if (allBundleable) {
7752
+ const completedResults = toolResultBlocks.map((b) => ({
7753
+ toolUseId: b.toolUseId,
7754
+ content: b.content,
7755
+ isError: b.isError ?? false
7756
+ }));
7757
+ const bundleAction = composeBundleFromToolResults({
7758
+ pendingWrites: guardPassedWrites.map((w) => w.call),
7759
+ tools: this.tools,
7760
+ readResults: turnReadToolResults,
7761
+ assistantContent: acc.assistantBlocks,
7762
+ completedResults,
7763
+ guardInjectionsByCallId,
7764
+ turnIndex
7765
+ });
7766
+ yield { type: "pending_action", action: bundleAction };
7767
+ return;
7768
+ }
7769
+ const pendingWrite = guardPassedWrites[0];
7770
+ if (guardPassedWrites.length > 1) {
7771
+ for (let i = 1; i < guardPassedWrites.length; i++) {
7772
+ const dropped = guardPassedWrites[i];
7773
+ const errBody = JSON.stringify({
7774
+ error: "This write was emitted alongside another write that requires a separate confirmation. Re-emit it after the first write resolves.",
7775
+ _droppedDueToMixedBundleability: true
7776
+ });
7777
+ yield {
7778
+ type: "tool_result",
7779
+ toolName: dropped.call.name,
7780
+ toolUseId: dropped.call.id,
7781
+ result: { error: errBody },
7782
+ isError: true
7783
+ };
7784
+ toolResultBlocks.push({
7785
+ type: "tool_result",
7786
+ toolUseId: dropped.call.id,
7787
+ content: errBody,
7788
+ isError: true
7789
+ });
7790
+ }
7791
+ }
7792
+ const writeGuardInjections = guardInjectionsByCallId[pendingWrite.call.id];
7793
+ const modifiableFields = getModifiableFields(pendingWrite.call.name);
7794
+ const attemptId = randomUUID();
7490
7795
  yield {
7491
7796
  type: "pending_action",
7492
7797
  action: {
@@ -7697,49 +8002,6 @@ function validateHistory(messages) {
7697
8002
  }
7698
8003
  return merged;
7699
8004
  }
7700
- function resolveTokenSymbol(nameOrType) {
7701
- if (!nameOrType.includes("::")) return nameOrType;
7702
- const parts = nameOrType.split("::");
7703
- return parts[parts.length - 1];
7704
- }
7705
- function describeAction(tool, call) {
7706
- const input = call.input;
7707
- switch (tool.name) {
7708
- case "save_deposit": {
7709
- return `Save ${input.amount} USDC into lending`;
7710
- }
7711
- case "withdraw": {
7712
- const wAsset = input.asset ?? "";
7713
- return `Withdraw ${input.amount}${wAsset ? " " + wAsset : ""} from lending`;
7714
- }
7715
- case "send_transfer":
7716
- return `Send $${input.amount} to ${input.to}`;
7717
- case "borrow":
7718
- return `Borrow $${input.amount} against collateral`;
7719
- case "repay_debt":
7720
- return `Repay $${input.amount} of outstanding debt`;
7721
- case "claim_rewards":
7722
- return "Claim all pending protocol rewards";
7723
- case "pay_api": {
7724
- const url = String(input.url ?? "");
7725
- const cost = estimatePayApiCost(url);
7726
- return `Pay for API call to ${url} (~$${cost})`;
7727
- }
7728
- case "swap_execute": {
7729
- const from = resolveTokenSymbol(String(input.from ?? "?"));
7730
- const to = resolveTokenSymbol(String(input.to ?? "?"));
7731
- const amt = input.amount ?? "?";
7732
- const slippagePct = (input.slippage ?? 0.01) * 100;
7733
- return `Swap ${amt} ${from} for ${to} (${slippagePct}% max slippage)`;
7734
- }
7735
- case "volo_stake":
7736
- return `Stake ${input.amount} SUI for vSUI`;
7737
- case "volo_unstake":
7738
- return `Unstake ${input.amount === "all" ? "all" : input.amount} vSUI`;
7739
- default:
7740
- return `Execute ${tool.name}`;
7741
- }
7742
- }
7743
8005
  function flagSuspiciousResult(toolName, result) {
7744
8006
  if (!result || typeof result !== "object") return null;
7745
8007
  const r = result;
@@ -7770,6 +8032,158 @@ function harnessShapeForEffort(effort) {
7770
8032
  return "max";
7771
8033
  }
7772
8034
  }
8035
+ async function regenerateBundle(engine, action) {
8036
+ if (!action.steps || action.steps.length < 2) {
8037
+ return {
8038
+ success: false,
8039
+ reason: "pending_action_not_found",
8040
+ message: "Action is not a multi-step bundle"
8041
+ };
8042
+ }
8043
+ if (action.canRegenerate !== true) {
8044
+ return {
8045
+ success: false,
8046
+ reason: "cannot_regenerate",
8047
+ message: "Bundle has canRegenerate=false (no upstream reads contributed)"
8048
+ };
8049
+ }
8050
+ const regenIds = action.regenerateInput?.toolUseIds ?? [];
8051
+ if (regenIds.length === 0) {
8052
+ return {
8053
+ success: false,
8054
+ reason: "cannot_regenerate",
8055
+ message: "Bundle has no regenerateInput.toolUseIds"
8056
+ };
8057
+ }
8058
+ const messages = engine.getMessages();
8059
+ const tools = [...engine.getTools()];
8060
+ const originalReads = [];
8061
+ for (const id of regenIds) {
8062
+ let found = null;
8063
+ outer: for (const msg of messages) {
8064
+ if (msg.role !== "assistant") continue;
8065
+ for (const block of msg.content) {
8066
+ if (block.type === "tool_use" && block.id === id) {
8067
+ found = {
8068
+ name: block.name,
8069
+ input: block.input
8070
+ };
8071
+ break outer;
8072
+ }
8073
+ }
8074
+ }
8075
+ if (!found) {
8076
+ return {
8077
+ success: false,
8078
+ reason: "engine_error",
8079
+ message: `Original tool_use ${id} not found in session history`
8080
+ };
8081
+ }
8082
+ if (!REGENERATABLE_READ_TOOLS.has(found.name)) {
8083
+ continue;
8084
+ }
8085
+ originalReads.push({ toolName: found.name, input: found.input });
8086
+ }
8087
+ if (originalReads.length === 0) {
8088
+ return {
8089
+ success: false,
8090
+ reason: "cannot_regenerate",
8091
+ message: "No regeneratable read tool_use blocks found in session history"
8092
+ };
8093
+ }
8094
+ const timelineEvents = [];
8095
+ const newReads = [];
8096
+ for (const r of originalReads) {
8097
+ const newToolUseId = `regen_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
8098
+ timelineEvents.push({
8099
+ type: "tool_start",
8100
+ toolName: r.toolName,
8101
+ toolUseId: newToolUseId,
8102
+ input: r.input
8103
+ });
8104
+ const t0 = Date.now();
8105
+ let outcome;
8106
+ try {
8107
+ outcome = await engine.invokeReadTool(r.toolName, r.input);
8108
+ } catch (err) {
8109
+ outcome = {
8110
+ data: { error: err instanceof Error ? err.message : String(err) },
8111
+ isError: true
8112
+ };
8113
+ }
8114
+ const durationMs = Date.now() - t0;
8115
+ timelineEvents.push({
8116
+ type: "tool_result",
8117
+ toolName: r.toolName,
8118
+ toolUseId: newToolUseId,
8119
+ result: outcome.data,
8120
+ isError: outcome.isError,
8121
+ durationMs
8122
+ });
8123
+ if (outcome.isError) {
8124
+ return {
8125
+ success: false,
8126
+ reason: "engine_error",
8127
+ message: `Re-execution of ${r.toolName} failed`
8128
+ };
8129
+ }
8130
+ newReads.push({
8131
+ toolUseId: newToolUseId,
8132
+ toolName: r.toolName,
8133
+ input: r.input,
8134
+ result: outcome.data,
8135
+ isError: false,
8136
+ timestamp: t0
8137
+ });
8138
+ }
8139
+ const synthAssistantBlocks = newReads.map((r) => ({
8140
+ type: "tool_use",
8141
+ id: r.toolUseId,
8142
+ name: r.toolName,
8143
+ input: r.input
8144
+ }));
8145
+ const synthUserBlocks = newReads.map((r) => ({
8146
+ type: "tool_result",
8147
+ toolUseId: r.toolUseId,
8148
+ content: typeof r.result === "string" ? r.result : JSON.stringify(r.result),
8149
+ isError: r.isError
8150
+ }));
8151
+ const synthMessages = [
8152
+ { role: "assistant", content: synthAssistantBlocks },
8153
+ { role: "user", content: synthUserBlocks }
8154
+ ];
8155
+ engine.loadMessages([...messages, ...synthMessages]);
8156
+ const pendingWrites = action.steps.map((step) => ({
8157
+ id: step.toolUseId,
8158
+ name: step.toolName,
8159
+ input: step.input
8160
+ }));
8161
+ const readResults = newReads.map((r) => ({
8162
+ toolUseId: r.toolUseId,
8163
+ toolName: r.toolName,
8164
+ timestamp: r.timestamp
8165
+ }));
8166
+ const assistantContent = action.assistantContent ?? [];
8167
+ const completedResults = action.completedResults ?? [];
8168
+ let newPendingAction;
8169
+ try {
8170
+ newPendingAction = composeBundleFromToolResults({
8171
+ pendingWrites,
8172
+ tools,
8173
+ readResults,
8174
+ assistantContent,
8175
+ completedResults,
8176
+ turnIndex: action.turnIndex
8177
+ });
8178
+ } catch (err) {
8179
+ return {
8180
+ success: false,
8181
+ reason: "engine_error",
8182
+ message: err instanceof Error ? err.message : "Bundle rebuild failed"
8183
+ };
8184
+ }
8185
+ return { success: true, newPendingAction, timelineEvents };
8186
+ }
7773
8187
 
7774
8188
  // src/streaming.ts
7775
8189
  function serializeSSE(event) {
@@ -7870,7 +8284,8 @@ var StepSchema = z.object({
7870
8284
  flags: z.record(z.unknown()).optional(),
7871
8285
  on_error: OnErrorSchema.optional(),
7872
8286
  input_template: z.record(z.string()).optional(),
7873
- cost_per_unit: z.string().optional()
8287
+ cost_per_unit: z.string().optional(),
8288
+ bundle: z.boolean().optional()
7874
8289
  });
7875
8290
  var RecipeSchema = z.object({
7876
8291
  name: z.string().min(1),
@@ -7885,6 +8300,19 @@ var RecipeSchema = z.object({
7885
8300
  return new Set(names).size === names.length;
7886
8301
  },
7887
8302
  { message: "Step names must be unique within a recipe" }
8303
+ ).refine(
8304
+ (r) => {
8305
+ for (const step of r.steps) {
8306
+ if (step.bundle === true) {
8307
+ if (!step.tool) return false;
8308
+ if (!isBundleableTool(step.tool)) return false;
8309
+ }
8310
+ }
8311
+ return true;
8312
+ },
8313
+ {
8314
+ message: "Steps with bundle: true must reference a bundleable confirm-tier write tool. Allowed: save_deposit, withdraw, borrow, repay_debt, send_transfer, swap_execute, claim_rewards, volo_stake, volo_unstake. Forbidden: pay_api, save_contact, any read tool, any auto-tier write."
8315
+ }
7888
8316
  );
7889
8317
  function loadRecipes(yamlDir) {
7890
8318
  const files = readdirSync(yamlDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
@@ -8857,6 +9285,6 @@ function sanitizeAnthropicMessages(messages) {
8857
9285
  return merged;
8858
9286
  }
8859
9287
 
8860
- export { AnthropicProvider, BalanceTracker, CANVAS_TEMPLATES, ContextBudget, CostTracker, DEFAULT_GUARD_CONFIG, DEFAULT_LEASE_SEC, DEFAULT_PERMISSION_CONFIG, DEFAULT_POLL_BUDGET_MS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_SYSTEM_PROMPT, EFFORT_THINKING_BUDGET_CAPS, EarlyToolDispatcher, InMemoryDefiCacheStore, InMemoryFetchLock, InMemoryNaviCacheStore, InMemoryWalletCacheStore, InvalidAddressError, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_ADDR_TTL_SEC, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_RATES_TTL_SEC, NAVI_SERVER_NAME, NaviTools, PERMISSION_PRESETS, QueryEngine, READ_TOOLS, RecipeRegistry, RetryTracker, SUINS_NAME_REGEX, SUI_ADDRESS_REGEX, SUI_ADDRESS_STRICT_REGEX, SuinsNotRegisteredError, SuinsRpcError, TOOL_FLAGS, TOOL_MODIFIABLE_FIELDS, TxMutex, WRITE_TOOLS, _resetNaviCircuitBreaker, activitySummaryTool, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, applyToolFlags, awaitOrFetch, balanceCheckTool, borrowTool, budgetToolResult, buildCachedSystemPrompt, buildMcpTools, buildProactivenessInstructions, buildProfileContext, buildSelfEvaluationInstruction, buildStateContext, buildTool, claimRewardsTool, clampThinkingForEffort, classifyEffort, clearPortfolioCache, clearPortfolioCacheFor, clearPriceMapCache, compactMessages, createGuardRunnerState, engineToSSE, estimateTokens, explainTxTool, extractConversationText, extractMcpText, fetchAddressDefiPortfolio, fetchAddressPortfolio, fetchAudricHistory, fetchAudricPortfolio, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchTokenPrices, fetchWalletCoins, findTool, getAudricApiBase, getDefaultTools, getDefiCacheStore, getFetchLock, getMcpManager, getModifiableFields, getNaviCacheStore, getTelemetrySink, getToolFlags, getWalletAddress, getWalletCacheStore, guardArtifactPreview, guardStaleData, harnessShapeForEffort, hasNaviMcp, healthCheckTool, loadRecipes, looksLikeSuiNs, microcompact, mppServicesTool, naviKey, normalizeAddressInput, parseEvalSummary, parseMcpJson, parseRecipe, parseSSE, payApiTool, portfolioAnalysisTool, protocolDeepDiveTool, ratesInfoTool, registerEngineTools, renderCanvasTool, repayDebtTool, requireAgent, resetDefiCacheStore, resetFetchLock, resetNaviCacheStore, resetTelemetrySink, resetWalletCacheStore, resolveAddressToSuinsViaRpc, resolvePermissionTier, resolveSuinsTool, resolveSuinsViaRpc, resolveUsdValue, runGuards, runTools, saveContactTool, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, setDefiCacheStore, setFetchLock, setNaviCacheStore, setTelemetrySink, setWalletCacheStore, spendingAnalyticsTool, swapExecuteTool, swapQuoteTool, tokenPricesTool, toolNameToOperation, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, updateGuardStateAfterToolResult, updateTodoTool, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };
9288
+ export { AnthropicProvider, BalanceTracker, CANVAS_TEMPLATES, ContextBudget, CostTracker, DEFAULT_GUARD_CONFIG, DEFAULT_LEASE_SEC, DEFAULT_PERMISSION_CONFIG, DEFAULT_POLL_BUDGET_MS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_SYSTEM_PROMPT, DEFAULT_TOOL_TTL_MS, EFFORT_THINKING_BUDGET_CAPS, EarlyToolDispatcher, InMemoryDefiCacheStore, InMemoryFetchLock, InMemoryNaviCacheStore, InMemoryWalletCacheStore, InvalidAddressError, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_ADDR_TTL_SEC, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_RATES_TTL_SEC, NAVI_SERVER_NAME, NaviTools, PERMISSION_PRESETS, QueryEngine, READ_TOOLS, REGENERATABLE_READ_TOOLS, RecipeRegistry, RetryTracker, SUINS_NAME_REGEX, SUI_ADDRESS_REGEX, SUI_ADDRESS_STRICT_REGEX, SuinsNotRegisteredError, SuinsRpcError, TOOL_FLAGS, TOOL_MODIFIABLE_FIELDS, TOOL_TTL_MS, TxMutex, WRITE_TOOLS, _resetNaviCircuitBreaker, activitySummaryTool, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, applyToolFlags, awaitOrFetch, balanceCheckTool, borrowTool, budgetToolResult, buildCachedSystemPrompt, buildMcpTools, buildProactivenessInstructions, buildProfileContext, buildSelfEvaluationInstruction, buildStateContext, buildTool, bundleShortestTtl, claimRewardsTool, clampThinkingForEffort, classifyEffort, clearPortfolioCache, clearPortfolioCacheFor, clearPriceMapCache, compactMessages, createGuardRunnerState, engineToSSE, estimateTokens, explainTxTool, extractConversationText, extractMcpText, fetchAddressDefiPortfolio, fetchAddressPortfolio, fetchAudricHistory, fetchAudricPortfolio, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchTokenPrices, fetchWalletCoins, findTool, getAudricApiBase, getDefaultTools, getDefiCacheStore, getFetchLock, getMcpManager, getModifiableFields, getNaviCacheStore, getTelemetrySink, getToolFlags, getWalletAddress, getWalletCacheStore, guardArtifactPreview, guardStaleData, harnessShapeForEffort, hasNaviMcp, healthCheckTool, isBundleableTool, loadRecipes, looksLikeSuiNs, microcompact, mppServicesTool, naviKey, normalizeAddressInput, parseEvalSummary, parseMcpJson, parseRecipe, parseSSE, payApiTool, portfolioAnalysisTool, protocolDeepDiveTool, ratesInfoTool, regenerateBundle, registerEngineTools, renderCanvasTool, repayDebtTool, requireAgent, resetDefiCacheStore, resetFetchLock, resetNaviCacheStore, resetTelemetrySink, resetWalletCacheStore, resolveAddressToSuinsViaRpc, resolvePermissionTier, resolveSuinsTool, resolveSuinsViaRpc, resolveUsdValue, runGuards, runTools, saveContactTool, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, setDefiCacheStore, setFetchLock, setNaviCacheStore, setTelemetrySink, setWalletCacheStore, spendingAnalyticsTool, swapExecuteTool, swapQuoteTool, tokenPricesTool, toolNameToOperation, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, updateGuardStateAfterToolResult, updateTodoTool, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };
8861
9289
  //# sourceMappingURL=index.js.map
8862
9290
  //# sourceMappingURL=index.js.map