@t2000/engine 1.3.0 → 1.5.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
@@ -61,6 +61,13 @@ var TxMutex = class {
61
61
  }
62
62
  }
63
63
  };
64
+ function withRetryStats(context) {
65
+ const retryStats = { attemptCount: 1 };
66
+ return {
67
+ context: { ...context, retryStats },
68
+ readAttemptCount: () => retryStats.attemptCount > 1 ? retryStats.attemptCount : void 0
69
+ };
70
+ }
64
71
  async function* runTools(pending, tools, context, txMutex) {
65
72
  const { reads, writes } = partitionToolCalls(pending, tools);
66
73
  if (reads.length > 0) {
@@ -68,24 +75,36 @@ async function* runTools(pending, tools, context, txMutex) {
68
75
  reads.map(async (call) => {
69
76
  const tool = findTool(tools, call.name);
70
77
  if (!tool) {
71
- return { call, result: { data: { error: `Unknown tool: ${call.name}` } }, isError: true };
78
+ return {
79
+ call,
80
+ result: { data: { error: `Unknown tool: ${call.name}` } },
81
+ isError: true,
82
+ attemptCount: void 0
83
+ };
72
84
  }
73
- const execResult = await executeSingleTool(tool, call, context);
85
+ const { context: toolCtx, readAttemptCount } = withRetryStats(context);
86
+ const execResult = await executeSingleTool(tool, call, toolCtx);
74
87
  if (!execResult.isError) {
75
88
  execResult.data = budgetToolResult(execResult.data, tool);
76
89
  }
77
- return { call, result: execResult, isError: execResult.isError };
90
+ return {
91
+ call,
92
+ result: execResult,
93
+ isError: execResult.isError,
94
+ attemptCount: readAttemptCount()
95
+ };
78
96
  })
79
97
  );
80
98
  for (const settled of readResults) {
81
99
  if (settled.status === "fulfilled") {
82
- const { call, result, isError } = settled.value;
100
+ const { call, result, isError, attemptCount } = settled.value;
83
101
  yield {
84
102
  type: "tool_result",
85
103
  toolName: call.name,
86
104
  toolUseId: call.id,
87
105
  result: result.data,
88
- isError
106
+ isError,
107
+ ...attemptCount !== void 0 ? { attemptCount } : {}
89
108
  };
90
109
  } else {
91
110
  const idx = readResults.indexOf(settled);
@@ -114,16 +133,19 @@ async function* runTools(pending, tools, context, txMutex) {
114
133
  }
115
134
  await txMutex.acquire();
116
135
  try {
117
- const result = await executeSingleTool(tool, call, context);
136
+ const { context: toolCtx, readAttemptCount } = withRetryStats(context);
137
+ const result = await executeSingleTool(tool, call, toolCtx);
118
138
  if (!result.isError) {
119
139
  result.data = budgetToolResult(result.data, tool);
120
140
  }
141
+ const attemptCount = readAttemptCount();
121
142
  yield {
122
143
  type: "tool_result",
123
144
  toolName: call.name,
124
145
  toolUseId: call.id,
125
146
  result: result.data,
126
- isError: result.isError
147
+ isError: result.isError,
148
+ ...attemptCount !== void 0 ? { attemptCount } : {}
127
149
  };
128
150
  } catch (err) {
129
151
  yield {
@@ -726,6 +748,7 @@ async function fetchBlockVisionWithRetry(url, init, opts = {}) {
726
748
  let lastError = null;
727
749
  let lastResponse = null;
728
750
  for (let attempt = 0; attempt < BV_RETRY_MAX_ATTEMPTS; attempt++) {
751
+ if (opts.retryStats) opts.retryStats.attemptCount = attempt + 1;
729
752
  if (attempt > 0) {
730
753
  let waitMs = BV_RETRY_BASE_DELAY_MS * Math.pow(BV_RETRY_BACKOFF_FACTOR, attempt - 1);
731
754
  const retryAfter = lastResponse?.headers.get("retry-after");
@@ -808,7 +831,7 @@ async function safeWalletStoreGet(store, address) {
808
831
  return null;
809
832
  }
810
833
  }
811
- async function fetchAddressPortfolio(address, apiKey, fallbackRpcUrl) {
834
+ async function fetchAddressPortfolio(address, apiKey, fallbackRpcUrl, opts = {}) {
812
835
  const store = getWalletCacheStore();
813
836
  const cachedEntry = await safeWalletStoreGet(store, address);
814
837
  if (cachedEntry) {
@@ -841,7 +864,7 @@ async function fetchAddressPortfolio(address, apiKey, fallbackRpcUrl) {
841
864
  }
842
865
  }
843
866
  if (apiKey && apiKey.trim().length > 0) {
844
- const blockvision = await fetchPortfolioFromBlockVision(address, apiKey);
867
+ const blockvision = await fetchPortfolioFromBlockVision(address, apiKey, opts.retryStats);
845
868
  if (blockvision) {
846
869
  await safeWalletStoreSet(
847
870
  store,
@@ -885,7 +908,7 @@ async function fetchAddressPortfolio(address, apiKey, fallbackRpcUrl) {
885
908
  portfolioInflight.set(address, promise);
886
909
  return promise;
887
910
  }
888
- async function fetchPortfolioFromBlockVision(address, apiKey) {
911
+ async function fetchPortfolioFromBlockVision(address, apiKey, retryStats) {
889
912
  const url = `${BLOCKVISION_BASE}/account/coins?account=${encodeURIComponent(address)}`;
890
913
  const signal = AbortSignal.timeout(PORTFOLIO_TIMEOUT_MS);
891
914
  let res;
@@ -896,7 +919,7 @@ async function fetchPortfolioFromBlockVision(address, apiKey) {
896
919
  headers: { "x-api-key": apiKey, accept: "application/json" },
897
920
  signal
898
921
  },
899
- { signal }
922
+ { signal, retryStats }
900
923
  );
901
924
  } catch (err) {
902
925
  console.warn("[blockvision-prices] portfolio fetch threw, degrading:", err);
@@ -982,7 +1005,7 @@ async function fetchPortfolioFromSuiRpc(address, apiKey, fallbackRpcUrl) {
982
1005
  source: "sui-rpc-degraded"
983
1006
  };
984
1007
  }
985
- async function fetchTokenPrices(coinTypes, apiKey) {
1008
+ async function fetchTokenPrices(coinTypes, apiKey, opts = {}) {
986
1009
  if (coinTypes.length === 0) return {};
987
1010
  const now = Date.now();
988
1011
  const cacheValid = priceMapCache !== null && now - priceMapCache.ts < CACHE_TTL_MS;
@@ -1006,7 +1029,7 @@ async function fetchTokenPrices(coinTypes, apiKey) {
1006
1029
  if (!apiKey || apiKey.trim().length === 0) {
1007
1030
  return result;
1008
1031
  }
1009
- const fetched = await fetchPricesFromBlockVision(stillMissing, apiKey);
1032
+ const fetched = await fetchPricesFromBlockVision(stillMissing, apiKey, opts.retryStats);
1010
1033
  Object.assign(result, fetched);
1011
1034
  const cacheUpdates = {};
1012
1035
  for (const [original, value] of Object.entries(fetched)) {
@@ -1016,7 +1039,7 @@ async function fetchTokenPrices(coinTypes, apiKey) {
1016
1039
  priceMapCache = { prices: merged, ts: cacheValid ? priceMapCache.ts : now };
1017
1040
  return result;
1018
1041
  }
1019
- async function fetchPricesFromBlockVision(coinTypes, apiKey) {
1042
+ async function fetchPricesFromBlockVision(coinTypes, apiKey, retryStats) {
1020
1043
  const out = {};
1021
1044
  const longToOriginal = /* @__PURE__ */ new Map();
1022
1045
  for (const original of coinTypes) {
@@ -1037,7 +1060,7 @@ async function fetchPricesFromBlockVision(coinTypes, apiKey) {
1037
1060
  headers: { "x-api-key": apiKey, accept: "application/json" },
1038
1061
  signal
1039
1062
  },
1040
- { signal }
1063
+ { signal, retryStats }
1041
1064
  );
1042
1065
  } catch (err) {
1043
1066
  console.warn("[blockvision-prices] price chunk threw, skipping:", err);
@@ -1123,7 +1146,7 @@ async function safeDefiStoreGet(store, address) {
1123
1146
  }
1124
1147
  }
1125
1148
  var warnedMissingApiKey = false;
1126
- async function fetchAddressDefiPortfolio(address, apiKey, priceHints = {}) {
1149
+ async function fetchAddressDefiPortfolio(address, apiKey, priceHints = {}, opts = {}) {
1127
1150
  if (!apiKey || apiKey.trim().length === 0) {
1128
1151
  if (!warnedMissingApiKey) {
1129
1152
  warnedMissingApiKey = true;
@@ -1164,7 +1187,7 @@ async function fetchAddressDefiPortfolio(address, apiKey, priceHints = {}) {
1164
1187
  const stickyBasis = recheck ?? cachedEntry;
1165
1188
  const fanoutAt = Date.now();
1166
1189
  const settled = await Promise.allSettled(
1167
- DEFI_PROTOCOLS.map((p) => fetchOneDefiProtocol(address, p, apiKey))
1190
+ DEFI_PROTOCOLS.map((p) => fetchOneDefiProtocol(address, p, apiKey, opts.retryStats))
1168
1191
  );
1169
1192
  const seen = /* @__PURE__ */ new Set();
1170
1193
  for (const s of settled) {
@@ -1259,7 +1282,7 @@ async function fetchAddressDefiPortfolio(address, apiKey, priceHints = {}) {
1259
1282
  defiInflight.set(address, inflight);
1260
1283
  return inflight;
1261
1284
  }
1262
- async function fetchOneDefiProtocol(address, protocol, apiKey) {
1285
+ async function fetchOneDefiProtocol(address, protocol, apiKey, retryStats) {
1263
1286
  const url = `${BLOCKVISION_BASE}/account/defiPortfolio?address=${encodeURIComponent(address)}&protocol=${protocol}`;
1264
1287
  const signal = AbortSignal.timeout(DEFI_PORTFOLIO_TIMEOUT_MS);
1265
1288
  let res;
@@ -1270,7 +1293,7 @@ async function fetchOneDefiProtocol(address, protocol, apiKey) {
1270
1293
  headers: { "x-api-key": apiKey, accept: "application/json" },
1271
1294
  signal
1272
1295
  },
1273
- { signal }
1296
+ { signal, retryStats }
1274
1297
  );
1275
1298
  } catch (err) {
1276
1299
  console.warn(`[defi] ${protocol} fetch threw:`, err);
@@ -1810,12 +1833,12 @@ async function applyVsuiPriceFallback(portfolio) {
1810
1833
  portfolio.totalUsd = portfolio.totalUsd - previousUsd + usdValue;
1811
1834
  }
1812
1835
  }
1813
- async function loadPortfolio(address, blockvisionApiKey, fallbackRpcUrl, cache) {
1836
+ async function loadPortfolio(address, blockvisionApiKey, fallbackRpcUrl, cache, retryStats) {
1814
1837
  if (cache) {
1815
1838
  const hit = cache.get(address);
1816
1839
  if (hit) return hit;
1817
1840
  }
1818
- const portfolio = await fetchAddressPortfolio(address, blockvisionApiKey, fallbackRpcUrl);
1841
+ const portfolio = await fetchAddressPortfolio(address, blockvisionApiKey, fallbackRpcUrl, { retryStats });
1819
1842
  if (cache) cache.set(address, portfolio);
1820
1843
  return portfolio;
1821
1844
  }
@@ -1874,7 +1897,8 @@ var balanceCheckTool = buildTool({
1874
1897
  address,
1875
1898
  context.blockvisionApiKey,
1876
1899
  context.suiRpcUrl,
1877
- context.portfolioCache
1900
+ context.portfolioCache,
1901
+ context.retryStats
1878
1902
  ).catch((err) => {
1879
1903
  console.warn("[balance_check] portfolio fetch failed, returning empty:", err);
1880
1904
  const fallback = {
@@ -1906,7 +1930,7 @@ var balanceCheckTool = buildTool({
1906
1930
  // as defi.totalUsd === 0 and `source: 'degraded'`, leaving the
1907
1931
  // rest of balance_check unaffected. The fetcher fills its own
1908
1932
  // prices via fetchTokenPrices for any coin types it discovers.
1909
- fetchAddressDefiPortfolio(address, context.blockvisionApiKey).catch((err) => {
1933
+ fetchAddressDefiPortfolio(address, context.blockvisionApiKey, {}, { retryStats: context.retryStats }).catch((err) => {
1910
1934
  console.warn("[balance_check] defi fetch failed:", err);
1911
1935
  const fallback = {
1912
1936
  totalUsd: 0,
@@ -2023,7 +2047,7 @@ var balanceCheckTool = buildTool({
2023
2047
  const fetchAddress = targetAddress ?? context.walletAddress;
2024
2048
  const [balance, defi] = await Promise.all([
2025
2049
  agent.balance(),
2026
- fetchAddressDefiPortfolio(fetchAddress, context.blockvisionApiKey).catch((err) => {
2050
+ fetchAddressDefiPortfolio(fetchAddress, context.blockvisionApiKey, {}, { retryStats: context.retryStats }).catch((err) => {
2027
2051
  console.warn("[balance_check] sdk-path defi fetch failed:", err);
2028
2052
  const fallback = {
2029
2053
  totalUsd: 0,
@@ -3986,7 +4010,8 @@ var portfolioAnalysisTool = buildTool({
3986
4010
  const fresh = await fetchAddressPortfolio(
3987
4011
  address,
3988
4012
  context.blockvisionApiKey,
3989
- context.suiRpcUrl
4013
+ context.suiRpcUrl,
4014
+ { retryStats: context.retryStats }
3990
4015
  );
3991
4016
  context.portfolioCache?.set(address, fresh);
3992
4017
  return fresh;
@@ -4036,7 +4061,7 @@ var portfolioAnalysisTool = buildTool({
4036
4061
  perProtocol: {},
4037
4062
  pricedAt: Date.now(),
4038
4063
  source: audricSnapshot.defiSource
4039
- }) : fetchAddressDefiPortfolio(address, context.blockvisionApiKey).catch(
4064
+ }) : fetchAddressDefiPortfolio(address, context.blockvisionApiKey, {}, { retryStats: context.retryStats }).catch(
4040
4065
  (err) => {
4041
4066
  console.warn("[portfolio_analysis] defi fetch failed:", err);
4042
4067
  return { totalUsd: 0, perProtocol: {}, pricedAt: Date.now(), source: "degraded" };
@@ -5104,6 +5129,107 @@ var resolveSuinsTool = buildTool({
5104
5129
  }
5105
5130
  }
5106
5131
  });
5132
+ var todoStatusSchema = z.enum(["pending", "in_progress", "completed"]);
5133
+ var todoItemSchema = z.object({
5134
+ id: z.string().min(1, "id must be a non-empty string").max(40, "id must be \u226440 chars (use a slug, not a sentence)"),
5135
+ label: z.string().min(1, "label must be a non-empty string").max(80, "label must be \u226480 chars (the whole point of this tool is concision)"),
5136
+ status: todoStatusSchema
5137
+ });
5138
+ var inputSchema6 = z.object({
5139
+ items: z.array(todoItemSchema).min(1, "items must contain at least 1 entry").max(8, "items must contain at most 8 entries (SPEC 8 ceiling)")
5140
+ });
5141
+ var updateTodoTool = buildTool({
5142
+ name: "update_todo",
5143
+ description: "Declare or replace your plan for the current turn as a structured todo list. Call this when the user's ask is multi-step (\u22653 tools, \u22652 reasoning hops) so the user can see what you're doing as you do it. Each call replaces the entire list \u2014 the tool is idempotent. \n\nRules: 1\u20138 items, each label \u226480 chars, exactly 1 item must be `in_progress`. Use stable `id`s across calls within the same turn so the UI can track item transitions (e.g. `id: 'check-balance'` first as `pending`, later as `completed`). \n\nDO NOT call this for single-step asks ('balance', 'rate') \u2014 it's wasted tokens. DO call it before kicking off long flows where the user benefits from seeing the plan unfold ('save my idle USDC' \u2192 check balance \u2192 check rates \u2192 compute split \u2192 propose). \n\nThis call doesn't count against your turn budget \u2014 re-narrating the plan as items move from pending \u2192 in_progress \u2192 completed is encouraged.",
5144
+ inputSchema: inputSchema6,
5145
+ jsonSchema: {
5146
+ type: "object",
5147
+ properties: {
5148
+ items: {
5149
+ type: "array",
5150
+ minItems: 1,
5151
+ maxItems: 8,
5152
+ items: {
5153
+ type: "object",
5154
+ properties: {
5155
+ id: {
5156
+ type: "string",
5157
+ description: 'Stable identifier across calls within the same turn (e.g. "check-balance"). \u226440 chars.'
5158
+ },
5159
+ label: {
5160
+ type: "string",
5161
+ description: 'What this step is doing, \u226480 chars. Concrete (e.g. "Check USDC rate") not abstract ("Gather data").'
5162
+ },
5163
+ status: {
5164
+ type: "string",
5165
+ enum: ["pending", "in_progress", "completed"],
5166
+ description: "Lifecycle state. Exactly one item must be `in_progress` per call."
5167
+ }
5168
+ },
5169
+ required: ["id", "label", "status"]
5170
+ }
5171
+ }
5172
+ },
5173
+ required: ["items"]
5174
+ },
5175
+ isReadOnly: true,
5176
+ // No I/O, just a pass-through that emits a side-channel event. Skip the
5177
+ // turn-read cache — every call is intentionally distinct (ids may match
5178
+ // but statuses change).
5179
+ cacheable: false,
5180
+ preflight: (input) => {
5181
+ const items = input.items ?? [];
5182
+ if (items.length === 0) {
5183
+ return { valid: false, error: "items must contain at least 1 entry" };
5184
+ }
5185
+ if (items.length > 8) {
5186
+ return { valid: false, error: `items must contain at most 8 entries, got ${items.length}` };
5187
+ }
5188
+ const seenIds = /* @__PURE__ */ new Set();
5189
+ let inProgressCount = 0;
5190
+ for (const item of items) {
5191
+ if (!item.id || item.id.trim().length === 0) {
5192
+ return { valid: false, error: "every item must have a non-empty id" };
5193
+ }
5194
+ if (item.id.length > 40) {
5195
+ return { valid: false, error: `item id "${item.id.slice(0, 30)}\u2026" exceeds 40 chars` };
5196
+ }
5197
+ if (seenIds.has(item.id)) {
5198
+ return { valid: false, error: `duplicate item id "${item.id}" \u2014 ids must be unique within a list` };
5199
+ }
5200
+ seenIds.add(item.id);
5201
+ if (!item.label || item.label.trim().length === 0) {
5202
+ return { valid: false, error: `item "${item.id}" has empty label` };
5203
+ }
5204
+ if (item.label.length > 80) {
5205
+ return { valid: false, error: `item "${item.id}" label exceeds 80 chars (got ${item.label.length})` };
5206
+ }
5207
+ if (item.status === "in_progress") {
5208
+ inProgressCount++;
5209
+ }
5210
+ }
5211
+ if (inProgressCount !== 1) {
5212
+ return {
5213
+ valid: false,
5214
+ error: `exactly 1 item must be in_progress, got ${inProgressCount}`
5215
+ };
5216
+ }
5217
+ return { valid: true };
5218
+ },
5219
+ async call(input) {
5220
+ return {
5221
+ // The `__todoUpdate` flag tells the engine's agent loop to emit a
5222
+ // `todo_update` side-channel event (mirrors the `__canvas` magic
5223
+ // flag pattern). The LLM still gets a normal `tool_result` keyed
5224
+ // to its `tool_use_id` so the Anthropic protocol stays satisfied.
5225
+ data: {
5226
+ __todoUpdate: true,
5227
+ items: input.items
5228
+ },
5229
+ displayText: `${input.items.length} step${input.items.length === 1 ? "" : "s"}: ${input.items.map((i) => `${i.status === "completed" ? "\u2713" : i.status === "in_progress" ? "\u2192" : "\xB7"} ${i.label}`).join(" / ")}`
5230
+ };
5231
+ }
5232
+ });
5107
5233
  var tokenPricesTool = buildTool({
5108
5234
  name: "token_prices",
5109
5235
  description: 'Get current USD prices for Sui tokens, with optional 24h change. Accepts full coin type strings (e.g. "0x2::sui::SUI"). Returns price per token and (when requested) 24h change percentage. Use for "what is X worth?" or "did Y move today?". For balance + portfolio rendering, prefer balance_check / portfolio_analysis instead \u2014 they bundle the same prices into the standard cards.',
@@ -5128,7 +5254,7 @@ var tokenPricesTool = buildTool({
5128
5254
  },
5129
5255
  isReadOnly: true,
5130
5256
  async call(input, context) {
5131
- const prices = await fetchTokenPrices(input.coinTypes, context.blockvisionApiKey);
5257
+ const prices = await fetchTokenPrices(input.coinTypes, context.blockvisionApiKey, { retryStats: context.retryStats });
5132
5258
  const results = input.coinTypes.map((coinType) => {
5133
5259
  const entry = prices[coinType];
5134
5260
  const symbol = coinType.split("::").pop() ?? coinType;
@@ -5350,6 +5476,29 @@ var CostTracker = class {
5350
5476
  }
5351
5477
  };
5352
5478
 
5479
+ // src/thinking-budget.ts
5480
+ var EFFORT_THINKING_BUDGET_CAPS = {
5481
+ // null = thinking force-disabled (LEAN tier — single-fact reads need
5482
+ // zero deliberation; a thinking block here adds ~300ms TTFVP for no
5483
+ // benefit — see SPEC 8 § "Decision 2: LEAN shape: zero thinking blocks")
5484
+ low: null,
5485
+ medium: 8e3,
5486
+ high: 16e3,
5487
+ max: 32e3
5488
+ };
5489
+ function clampThinkingForEffort(config, effort) {
5490
+ if (!config) return config;
5491
+ if (effort === void 0) return config;
5492
+ const cap = EFFORT_THINKING_BUDGET_CAPS[effort];
5493
+ if (cap === null) {
5494
+ return { type: "disabled" };
5495
+ }
5496
+ if (config.type === "enabled" && config.budgetTokens > cap) {
5497
+ return { ...config, budgetTokens: cap };
5498
+ }
5499
+ return config;
5500
+ }
5501
+
5353
5502
  // src/guards.ts
5354
5503
  function guardVerdictToAction(verdict) {
5355
5504
  if (verdict === "pass" || verdict === "hint") return "allow";
@@ -6374,12 +6523,14 @@ var EarlyToolDispatcher = class {
6374
6523
  call,
6375
6524
  tool,
6376
6525
  promise: Promise.resolve({ data: cached.result, isError: false }),
6377
- deduped: true
6526
+ deduped: true,
6527
+ readAttemptCount: () => void 0
6378
6528
  });
6379
6529
  return true;
6380
6530
  }
6381
6531
  }
6382
- const childContext = { ...this.context, signal: this.abortController.signal };
6532
+ const baseChildContext = { ...this.context, signal: this.abortController.signal };
6533
+ const { context: childContext, readAttemptCount } = withRetryStats(baseChildContext);
6383
6534
  const promise = executeTool(tool, call, childContext).then((result) => {
6384
6535
  if (!result.isError && this.turnReadCache) {
6385
6536
  const cacheKey = TurnReadCache.keyFor(call.name, call.input);
@@ -6390,7 +6541,7 @@ var EarlyToolDispatcher = class {
6390
6541
  }
6391
6542
  return result;
6392
6543
  });
6393
- this.entries.push({ call, tool, promise, deduped: false });
6544
+ this.entries.push({ call, tool, promise, deduped: false, readAttemptCount });
6394
6545
  return true;
6395
6546
  }
6396
6547
  /** True if any tools have been dispatched. */
@@ -6419,6 +6570,7 @@ var EarlyToolDispatcher = class {
6419
6570
  try {
6420
6571
  const result = await entry.promise;
6421
6572
  const budgeted = result.isError ? result.data : budgetToolResult(result.data, entry.tool);
6573
+ const attemptCount = entry.readAttemptCount();
6422
6574
  yield {
6423
6575
  type: "tool_result",
6424
6576
  toolName: entry.call.name,
@@ -6426,7 +6578,8 @@ var EarlyToolDispatcher = class {
6426
6578
  result: budgeted,
6427
6579
  isError: result.isError,
6428
6580
  wasEarlyDispatched: true,
6429
- ...entry.deduped ? { resultDeduped: true } : {}
6581
+ ...entry.deduped ? { resultDeduped: true } : {},
6582
+ ...attemptCount !== void 0 ? { attemptCount } : {}
6430
6583
  };
6431
6584
  } catch (err) {
6432
6585
  yield {
@@ -6560,8 +6713,17 @@ var QueryEngine = class {
6560
6713
  * `pending_action` event and the stream ends — no persistent connection needed.
6561
6714
  * The caller should save messages + pendingAction to the session store, then
6562
6715
  * call `resumeWithToolResult()` after the user approves/denies and executes.
6716
+ *
6717
+ * [SPEC 8 v0.5.1 B3.2] Optional `options.harnessShape` + `options.harnessRationale`
6718
+ * cause a one-shot `harness_shape` event to be yielded BEFORE the agent loop
6719
+ * begins. The engine itself doesn't classify — the host calls
6720
+ * `classifyEffort()` (host already does this for thinking-budget routing)
6721
+ * and maps via `harnessShapeForEffort()` before calling `submitMessage`.
6722
+ * Hosts that don't pass `harnessShape` won't see the event (existing
6723
+ * pre-SPEC-8 hosts continue to work; their `TurnMetrics.harnessShape`
6724
+ * defaults to `'legacy'`).
6563
6725
  */
6564
- async *submitMessage(prompt) {
6726
+ async *submitMessage(prompt, options) {
6565
6727
  if (this.costTracker.isOverBudget()) {
6566
6728
  yield { type: "error", error: new Error("Session budget exceeded") };
6567
6729
  return;
@@ -6573,6 +6735,13 @@ var QueryEngine = class {
6573
6735
  role: "user",
6574
6736
  content: [{ type: "text", text: prompt }]
6575
6737
  });
6738
+ if (options?.harnessShape) {
6739
+ yield {
6740
+ type: "harness_shape",
6741
+ shape: options.harnessShape,
6742
+ rationale: options.harnessRationale && options.harnessRationale.trim().length > 0 ? options.harnessRationale : `host-classified ${options.harnessShape}`
6743
+ };
6744
+ }
6576
6745
  this.turnPaused = false;
6577
6746
  try {
6578
6747
  yield* this.agentLoop(prompt, signal);
@@ -6699,11 +6868,19 @@ var QueryEngine = class {
6699
6868
  isError: true,
6700
6869
  data: {
6701
6870
  error: `Post-write refresh: invalid input for ${tool.name}`
6702
- }
6871
+ },
6872
+ attemptCount: void 0
6703
6873
  };
6704
6874
  }
6705
- const result = await tool.call(parsed.data, context);
6706
- return { tool, id, isError: false, data: result.data };
6875
+ const { context: toolCtx, readAttemptCount } = withRetryStats(context);
6876
+ const result = await tool.call(parsed.data, toolCtx);
6877
+ return {
6878
+ tool,
6879
+ id,
6880
+ isError: false,
6881
+ data: result.data,
6882
+ attemptCount: readAttemptCount()
6883
+ };
6707
6884
  } catch (err) {
6708
6885
  return {
6709
6886
  tool,
@@ -6711,7 +6888,8 @@ var QueryEngine = class {
6711
6888
  isError: true,
6712
6889
  data: {
6713
6890
  error: err instanceof Error ? err.message : "Post-write refresh failed"
6714
- }
6891
+ },
6892
+ attemptCount: void 0
6715
6893
  };
6716
6894
  }
6717
6895
  })
@@ -6743,7 +6921,8 @@ var QueryEngine = class {
6743
6921
  toolUseId: r.id,
6744
6922
  result: r.data,
6745
6923
  isError: r.isError,
6746
- wasPostWriteRefresh: true
6924
+ wasPostWriteRefresh: true,
6925
+ ...r.attemptCount !== void 0 ? { attemptCount: r.attemptCount } : {}
6747
6926
  };
6748
6927
  }
6749
6928
  }
@@ -6940,6 +7119,7 @@ ${recipeCtx}`;
6940
7119
  ];
6941
7120
  }
6942
7121
  }
7122
+ const cappedThinking = clampThinkingForEffort(this.thinking, this.outputConfig?.effort);
6943
7123
  const stream = this.provider.chat({
6944
7124
  messages: this.messages,
6945
7125
  systemPrompt: effectivePrompt,
@@ -6948,7 +7128,7 @@ ${recipeCtx}`;
6948
7128
  maxTokens: this.maxTokens,
6949
7129
  temperature: this.temperature,
6950
7130
  toolChoice: effectiveToolChoice,
6951
- thinking: this.thinking,
7131
+ thinking: cappedThinking,
6952
7132
  outputConfig: this.outputConfig,
6953
7133
  signal
6954
7134
  });
@@ -7029,6 +7209,13 @@ ${recipeCtx}`;
7029
7209
  toolUseId: finalEvent.toolUseId
7030
7210
  };
7031
7211
  }
7212
+ if (r && r.__todoUpdate === true && Array.isArray(r.items)) {
7213
+ yield {
7214
+ type: "todo_update",
7215
+ items: r.items,
7216
+ toolUseId: finalEvent.toolUseId
7217
+ };
7218
+ }
7032
7219
  }
7033
7220
  earlyResultBlocks.push({
7034
7221
  type: "tool_result",
@@ -7225,6 +7412,13 @@ ${recipeCtx}`;
7225
7412
  toolUseId: finalEvent.toolUseId
7226
7413
  };
7227
7414
  }
7415
+ if (r && r.__todoUpdate === true && Array.isArray(r.items)) {
7416
+ yield {
7417
+ type: "todo_update",
7418
+ items: r.items,
7419
+ toolUseId: finalEvent.toolUseId
7420
+ };
7421
+ }
7228
7422
  if (tool && !tool.isReadOnly && this.onAutoExecuted && this.permissionConfig && this.priceCache) {
7229
7423
  const operation = toolNameToOperation(toolEvent.toolName);
7230
7424
  if (operation && originalCall) {
@@ -7316,6 +7510,11 @@ ${recipeCtx}`;
7316
7510
  }
7317
7511
  this.messages.push({ role: "assistant", content: acc.assistantBlocks });
7318
7512
  this.messages.push({ role: "user", content: toolResultBlocks });
7513
+ const toolUseBlocks = acc.assistantBlocks.filter((b) => b.type === "tool_use");
7514
+ const allUpdateTodo = toolUseBlocks.length > 0 && toolUseBlocks.every((b) => b.name === "update_todo");
7515
+ if (allUpdateTodo) {
7516
+ turns--;
7517
+ }
7319
7518
  if (this.costTracker.isOverBudget()) {
7320
7519
  yield { type: "error", error: new Error("Session budget exceeded") };
7321
7520
  return;
@@ -7340,7 +7539,7 @@ ${recipeCtx}`;
7340
7539
  *handleProviderEvent(event, acc, dispatcher) {
7341
7540
  switch (event.type) {
7342
7541
  case "thinking_delta": {
7343
- yield { type: "thinking_delta", text: event.text };
7542
+ yield { type: "thinking_delta", text: event.text, blockIndex: event.blockIndex };
7344
7543
  break;
7345
7544
  }
7346
7545
  case "thinking_done": {
@@ -7349,7 +7548,14 @@ ${recipeCtx}`;
7349
7548
  thinking: event.thinking,
7350
7549
  signature: event.signature
7351
7550
  });
7352
- yield { type: "thinking_done", signature: event.signature };
7551
+ yield {
7552
+ type: "thinking_done",
7553
+ blockIndex: event.blockIndex,
7554
+ signature: event.signature,
7555
+ // [SPEC 8 v0.5.1] forward HowIEvaluated structured fields when
7556
+ // the provider parsed an <eval_summary> marker.
7557
+ ...event.summaryMode && event.evaluationItems ? { summaryMode: true, evaluationItems: event.evaluationItems } : {}
7558
+ };
7353
7559
  break;
7354
7560
  }
7355
7561
  case "redacted_thinking": {
@@ -7551,6 +7757,20 @@ function flagSuspiciousResult(toolName, result) {
7551
7757
  return null;
7552
7758
  }
7553
7759
 
7760
+ // src/types.ts
7761
+ function harnessShapeForEffort(effort) {
7762
+ switch (effort) {
7763
+ case "low":
7764
+ return "lean";
7765
+ case "medium":
7766
+ return "standard";
7767
+ case "high":
7768
+ return "rich";
7769
+ case "max":
7770
+ return "max";
7771
+ }
7772
+ }
7773
+
7554
7774
  // src/streaming.ts
7555
7775
  function serializeSSE(event) {
7556
7776
  const data = JSON.stringify(event);
@@ -7785,6 +8005,54 @@ function classifyEffort(model, userMessage, matchedRecipe, sessionWriteCount) {
7785
8005
  return "medium";
7786
8006
  }
7787
8007
 
8008
+ // src/eval-summary.ts
8009
+ var MARKER_REGEX = /<eval_summary>([\s\S]*?)<\/eval_summary>/g;
8010
+ var VALID_STATUSES = /* @__PURE__ */ new Set([
8011
+ "good",
8012
+ "warning",
8013
+ "critical",
8014
+ "info"
8015
+ ]);
8016
+ function parseEvalSummary(thinkingText) {
8017
+ if (!thinkingText.includes("<eval_summary>")) return null;
8018
+ const matches = [];
8019
+ for (const match of thinkingText.matchAll(MARKER_REGEX)) {
8020
+ matches.push(match[1] ?? "");
8021
+ }
8022
+ if (matches.length === 0) return null;
8023
+ const firstPayload = matches[0].trim();
8024
+ let parsed;
8025
+ try {
8026
+ parsed = JSON.parse(firstPayload);
8027
+ } catch {
8028
+ return null;
8029
+ }
8030
+ if (!parsed || typeof parsed !== "object") return null;
8031
+ const items = parsed.items;
8032
+ if (!Array.isArray(items)) return null;
8033
+ const evaluationItems = [];
8034
+ for (const item of items) {
8035
+ if (!item || typeof item !== "object") continue;
8036
+ const i = item;
8037
+ if (typeof i.label !== "string" || i.label.trim().length === 0) continue;
8038
+ if (typeof i.status !== "string" || !VALID_STATUSES.has(i.status)) continue;
8039
+ const out = {
8040
+ label: i.label,
8041
+ status: i.status
8042
+ };
8043
+ if (typeof i.note === "string" && i.note.length > 0) {
8044
+ out.note = i.note;
8045
+ }
8046
+ evaluationItems.push(out);
8047
+ }
8048
+ if (evaluationItems.length === 0) return null;
8049
+ return {
8050
+ summaryMode: true,
8051
+ evaluationItems,
8052
+ markerCount: matches.length
8053
+ };
8054
+ }
8055
+
7788
8056
  // src/prompt-cache.ts
7789
8057
  function buildCachedSystemPrompt(staticParts, dynamicPart) {
7790
8058
  const blocks = staticParts.map((text, i) => ({
@@ -8342,7 +8610,7 @@ var AnthropicProvider = class {
8342
8610
  } else if (delta.type === "thinking_delta") {
8343
8611
  const buf = thinkingBuffers.get(event.index);
8344
8612
  if (buf?.type === "thinking") buf.text += delta.thinking ?? "";
8345
- yield { type: "thinking_delta", text: delta.thinking ?? "" };
8613
+ yield { type: "thinking_delta", text: delta.thinking ?? "", blockIndex: event.index };
8346
8614
  } else if (delta.type === "signature_delta") {
8347
8615
  const buf = thinkingBuffers.get(event.index);
8348
8616
  if (buf?.type === "thinking") buf.signature = delta.signature ?? "";
@@ -8368,7 +8636,14 @@ var AnthropicProvider = class {
8368
8636
  }
8369
8637
  const thinkBuf = thinkingBuffers.get(event.index);
8370
8638
  if (thinkBuf?.type === "thinking") {
8371
- yield { type: "thinking_done", thinking: thinkBuf.text, signature: thinkBuf.signature };
8639
+ const summary = parseEvalSummary(thinkBuf.text);
8640
+ yield {
8641
+ type: "thinking_done",
8642
+ blockIndex: event.index,
8643
+ thinking: thinkBuf.text,
8644
+ signature: thinkBuf.signature,
8645
+ ...summary ? { summaryMode: true, evaluationItems: summary.evaluationItems } : {}
8646
+ };
8372
8647
  thinkingBuffers.delete(event.index);
8373
8648
  } else if (thinkBuf?.type === "redacted_thinking") {
8374
8649
  yield { type: "redacted_thinking", data: thinkBuf.data };
@@ -8582,6 +8857,6 @@ function sanitizeAnthropicMessages(messages) {
8582
8857
  return merged;
8583
8858
  }
8584
8859
 
8585
- 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, 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, 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, hasNaviMcp, healthCheckTool, loadRecipes, looksLikeSuiNs, microcompact, mppServicesTool, naviKey, normalizeAddressInput, 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, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };
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 };
8586
8861
  //# sourceMappingURL=index.js.map
8587
8862
  //# sourceMappingURL=index.js.map