@cognipeer/agent-sdk 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6,6 +6,7 @@ var fs = require('fs');
6
6
  var path = require('path');
7
7
  var buffer = require('buffer');
8
8
  var nanoid = require('nanoid');
9
+ var crypto = require('crypto');
9
10
  var AjvModule = require('ajv');
10
11
 
11
12
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -13,6 +14,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
14
 
14
15
  var fs__default = /*#__PURE__*/_interopDefault(fs);
15
16
  var path__default = /*#__PURE__*/_interopDefault(path);
17
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
16
18
  var AjvModule__default = /*#__PURE__*/_interopDefault(AjvModule);
17
19
 
18
20
  // src/adapters/langchain.ts
@@ -224,7 +226,8 @@ function createTool({
224
226
  func,
225
227
  needsApproval,
226
228
  approvalPrompt,
227
- approvalDefaults
229
+ approvalDefaults,
230
+ maxExecutionsPerRun
228
231
  }) {
229
232
  const execute = async (input) => func(input);
230
233
  const toolRecord = {
@@ -246,25 +249,45 @@ function createTool({
246
249
  if (approvalDefaults !== void 0) {
247
250
  toolRecord.approvalDefaults = approvalDefaults;
248
251
  }
252
+ if (maxExecutionsPerRun !== void 0) {
253
+ toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
254
+ }
249
255
  toolRecord.__source = toolRecord.__source || "smart";
250
256
  toolRecord.__impl = func;
251
257
  return toolRecord;
252
258
  }
253
259
 
254
260
  // src/prompts.ts
255
- function buildSystemPrompt(extra, planning, name = "Agent") {
256
- const extraTrimmed = extra?.trim();
257
- const agentHeader = `Agent Name: ${name}`;
258
- const planningBlock = planning ? `<planning>
259
- PLANNING IS MANDATORY.
261
+ var DEFAULT_TODO_LIST_PROMPT = `Planning tools are available when they materially improve execution.
260
262
 
261
263
  Rules:
262
- 1) Your FIRST action in every task is a single call to the tool "manage_todo_list" with operation="write" and a full, ordered plan (1+ items). Even for trivial tasks, write a one-item plan.
263
- 2) After every non-planning tool call, you MUST immediately call "manage_todo_list" with operation="write" (or re-write) to update statuses and append one-line evidence on the affected item only.
264
- 3) Keep exactly ONE item "in-progress" at any time; all others are "not-started" or "completed".
265
- 4) Never reveal the plan text. Never summarize the plan in the assistant messages.
266
- 5) If you ever produce an assistant message without having written a plan in this session, STOP and first write the plan via "manage_todo_list".
267
- </planning>` : "";
264
+ 1) Use "manage_todo_list" for multi-step work, delegation, recovery after a failed attempt, or when the user explicitly asks for a plan.
265
+ 2) Do NOT create a plan for direct Q&A, simple recall from existing context, or a single straightforward tool lookup unless the user asks for one.
266
+ 3) If a task is multi-step and no valid plan exists yet, create one before substantial execution.
267
+ 4) Use operation="write" only to create or fully replace the entire plan. After a plan exists, default to operation="update" for progress, evidence, status, owner changes, blockers, or reprioritization.
268
+ 5) When using operation="update", send only the changed items. Do not resend unchanged plan items unless the whole plan structure is being rewritten.
269
+ 6) Include expectedVersion from the latest successful plan state whenever you update an existing plan. If you do not know the latest version, read the plan first.
270
+ 7) Keep exactly ONE item "in-progress" at a time.
271
+ 8) Update the plan whenever a step starts, completes, becomes blocked, fails, or the approach changes. Do not finish a multi-step task with stale plan state.
272
+ 9) If tool results materially change the task state, sync the plan before the final answer.
273
+ 10) Reuse prior tool results already present in the conversation before calling tools again.
274
+ 11) If an update fails because of a version mismatch, read the latest plan and retry instead of blindly rewriting it.
275
+ 12) Repeating full operation="write" calls for the same plan is a mistake unless the plan structure itself changed.
276
+ 13) Keep the plan internal unless the user explicitly asks to see it.`;
277
+ function buildPlanningBlock(todoListPrompt) {
278
+ const promptBody = todoListPrompt?.trim() || DEFAULT_TODO_LIST_PROMPT;
279
+ if (promptBody.startsWith("<planning>")) {
280
+ return promptBody;
281
+ }
282
+ return `<planning>
283
+ ${promptBody}
284
+ </planning>`;
285
+ }
286
+ function buildSystemPrompt(extra, planning, name = "Agent", todoListPrompt) {
287
+ const extraTrimmed = extra?.trim();
288
+ const agentHeader = `Agent Name: ${name}`;
289
+ const planningMode = typeof planning === "string" ? planning : planning ? "todo" : "off";
290
+ const planningBlock = planningMode !== "off" ? buildPlanningBlock(todoListPrompt) : "";
268
291
  return [
269
292
  agentHeader,
270
293
  "You are an advanced AI agent that is concise, accurate, and helpful.",
@@ -376,6 +399,9 @@ function cognipeerSink(first, second) {
376
399
  function httpSink(url, headers) {
377
400
  return { type: "http", url, headers };
378
401
  }
402
+ function otlpSink(endpoint, headers) {
403
+ return { type: "otlp", endpoint, headers };
404
+ }
379
405
  function resolveLogsBaseDir(customPath, ensureDirectory = true) {
380
406
  const root = process.cwd();
381
407
  const base = customPath && customPath.trim().length > 0 ? customPath : path__default.default.join(root, "logs");
@@ -575,6 +601,14 @@ function resolveSink(sink) {
575
601
  const headers = candidate.headers ? { ...candidate.headers } : void 0;
576
602
  return headers ? { type: "http", url, headers } : { type: "http", url };
577
603
  }
604
+ case "otlp": {
605
+ const endpoint = typeof candidate.endpoint === "string" ? candidate.endpoint.trim() : "";
606
+ if (!endpoint) {
607
+ throw new Error("otlp sink requires a non-empty endpoint");
608
+ }
609
+ const headers = candidate.headers ? { ...candidate.headers } : void 0;
610
+ return headers ? { type: "otlp", endpoint, headers } : { type: "otlp", endpoint };
611
+ }
578
612
  default: {
579
613
  if (typeof console !== "undefined" && typeof console.warn === "function") {
580
614
  console.warn("Unknown tracing sink type. Falling back to file sink.", candidate);
@@ -594,6 +628,8 @@ function snapshotSink(runtime) {
594
628
  return { type: "cognipeer", url: sink.url };
595
629
  case "http":
596
630
  return { type: "http", url: sink.url };
631
+ case "otlp":
632
+ return { type: "otlp", endpoint: sink.endpoint };
597
633
  default:
598
634
  return { type: "custom" };
599
635
  }
@@ -744,6 +780,202 @@ async function postStreamingSessionEnd(baseUrl, headers, payload) {
744
780
  throw err;
745
781
  }
746
782
  }
783
+ function isoToUnixNano(iso) {
784
+ const ms = new Date(iso).getTime();
785
+ return `${ms}000000`;
786
+ }
787
+ function toOtlpStatusCode(status) {
788
+ if (status === "error") return 2;
789
+ if (status === "success" || status === "completed") return 1;
790
+ return 0;
791
+ }
792
+ function toOtlpSpanKind(type) {
793
+ switch (type) {
794
+ case "ai_call":
795
+ return 3;
796
+ // CLIENT — outgoing LLM request
797
+ case "tool_call":
798
+ return 1;
799
+ // INTERNAL
800
+ case "agent_iteration":
801
+ return 1;
802
+ // INTERNAL
803
+ default:
804
+ return 1;
805
+ }
806
+ }
807
+ function stringAttr(key, val) {
808
+ return val != null ? { key, value: { stringValue: val } } : null;
809
+ }
810
+ function intAttr(key, val) {
811
+ return val != null && !Number.isNaN(val) ? { key, value: { intValue: String(val) } } : null;
812
+ }
813
+ function doubleAttr(key, val) {
814
+ return val != null && !Number.isNaN(val) ? { key, value: { doubleValue: val } } : null;
815
+ }
816
+ function eventToOtlpSpan(event) {
817
+ const startNano = isoToUnixNano(event.timestamp);
818
+ const endNano = event.durationMs != null ? `${new Date(new Date(event.timestamp).getTime() + event.durationMs).getTime()}000000` : startNano;
819
+ const attrs = [
820
+ stringAttr("cognipeer.event.type", event.type),
821
+ stringAttr("cognipeer.event.label", event.label),
822
+ intAttr("cognipeer.event.sequence", event.sequence),
823
+ stringAttr("cognipeer.actor.scope", event.actor?.scope),
824
+ stringAttr("cognipeer.actor.name", event.actor?.name),
825
+ stringAttr("cognipeer.actor.role", event.actor?.role),
826
+ stringAttr("cognipeer.model", event.model),
827
+ stringAttr("cognipeer.provider", event.provider),
828
+ intAttr("cognipeer.tokens.input", event.inputTokens),
829
+ intAttr("cognipeer.tokens.output", event.outputTokens),
830
+ intAttr("cognipeer.tokens.total", event.totalTokens),
831
+ intAttr("cognipeer.tokens.cached_input", event.cachedInputTokens),
832
+ intAttr("cognipeer.bytes.request", event.requestBytes),
833
+ intAttr("cognipeer.bytes.response", event.responseBytes),
834
+ stringAttr("cognipeer.tool.execution_id", event.toolExecutionId),
835
+ stringAttr("cognipeer.retry_of", event.retryOf),
836
+ stringAttr("cognipeer.event.id", event.id)
837
+ ].filter(Boolean);
838
+ if (event.data?.sections && event.data.sections.length > 0) {
839
+ try {
840
+ attrs.push({ key: "cognipeer.sections", value: { stringValue: JSON.stringify(event.data.sections) } });
841
+ } catch {
842
+ }
843
+ }
844
+ const span = {
845
+ traceId: event.traceId,
846
+ spanId: event.spanId,
847
+ parentSpanId: event.parentSpanId || "",
848
+ name: event.label || event.type,
849
+ kind: toOtlpSpanKind(event.type),
850
+ startTimeUnixNano: startNano,
851
+ endTimeUnixNano: endNano,
852
+ attributes: attrs,
853
+ status: {
854
+ code: toOtlpStatusCode(event.status),
855
+ ...event.status === "error" && event.error?.message ? { message: event.error.message } : {}
856
+ }
857
+ };
858
+ if (event.error) {
859
+ span.events = [{
860
+ timeUnixNano: startNano,
861
+ name: "exception",
862
+ attributes: [
863
+ { key: "exception.message", value: { stringValue: event.error.message } },
864
+ ...event.error.stack ? [{ key: "exception.stacktrace", value: { stringValue: event.error.stack } }] : []
865
+ ]
866
+ }];
867
+ }
868
+ return span;
869
+ }
870
+ function traceSessionToOtlp(session) {
871
+ const traceId = session.traceId || generateTraceId();
872
+ const rootSpanId = session.rootSpanId || generateSpanId();
873
+ const startNano = isoToUnixNano(session.startedAt);
874
+ const endNano = session.endedAt ? isoToUnixNano(session.endedAt) : startNano;
875
+ const resourceAttrs = [
876
+ { key: "service.name", value: { stringValue: session.agent?.name || "cognipeer-agent" } },
877
+ stringAttr("service.version", session.agent?.version),
878
+ stringAttr("cognipeer.session.id", session.sessionId),
879
+ stringAttr("cognipeer.thread.id", session.threadId),
880
+ stringAttr("cognipeer.agent.model", session.agent?.model),
881
+ stringAttr("cognipeer.agent.provider", session.agent?.provider)
882
+ ].filter(Boolean);
883
+ const rootAttrs = [
884
+ stringAttr("cognipeer.session.status", session.status),
885
+ doubleAttr("cognipeer.session.duration_ms", session.durationMs),
886
+ intAttr("cognipeer.session.total_input_tokens", session.summary.totalInputTokens),
887
+ intAttr("cognipeer.session.total_output_tokens", session.summary.totalOutputTokens),
888
+ intAttr("cognipeer.session.total_cached_input_tokens", session.summary.totalCachedInputTokens),
889
+ intAttr("cognipeer.session.total_bytes_in", session.summary.totalBytesIn),
890
+ intAttr("cognipeer.session.total_bytes_out", session.summary.totalBytesOut),
891
+ intAttr("cognipeer.session.event_count", session.events.length)
892
+ ].filter(Boolean);
893
+ if (session.summary.eventCounts) {
894
+ for (const [key, count] of Object.entries(session.summary.eventCounts)) {
895
+ rootAttrs.push({ key: `cognipeer.session.event_count.${key}`, value: { intValue: String(count) } });
896
+ }
897
+ }
898
+ const rootSpan = {
899
+ traceId,
900
+ spanId: rootSpanId,
901
+ parentSpanId: "",
902
+ name: `agent_session: ${session.agent?.name || "agent"}`,
903
+ kind: 1,
904
+ // INTERNAL
905
+ startTimeUnixNano: startNano,
906
+ endTimeUnixNano: endNano,
907
+ attributes: rootAttrs,
908
+ status: {
909
+ code: toOtlpStatusCode(session.status),
910
+ ...session.status === "error" && session.errors.length > 0 ? { message: session.errors.map((e) => e.message).join("; ") } : {}
911
+ }
912
+ };
913
+ if (session.errors.length > 0) {
914
+ rootSpan.events = session.errors.map((err) => ({
915
+ timeUnixNano: err.timestamp ? isoToUnixNano(err.timestamp) : startNano,
916
+ name: "exception",
917
+ attributes: [
918
+ { key: "exception.message", value: { stringValue: err.message } },
919
+ ...err.stack ? [{ key: "exception.stacktrace", value: { stringValue: err.stack } }] : [],
920
+ ...err.type ? [{ key: "exception.type", value: { stringValue: err.type } }] : []
921
+ ]
922
+ }));
923
+ }
924
+ const childSpans = session.events.map((event) => {
925
+ const span = eventToOtlpSpan({
926
+ ...event,
927
+ traceId: event.traceId || traceId,
928
+ spanId: event.spanId || generateSpanId(),
929
+ parentSpanId: event.parentSpanId || rootSpanId
930
+ });
931
+ return span;
932
+ });
933
+ return {
934
+ resourceSpans: [{
935
+ resource: { attributes: resourceAttrs },
936
+ scopeSpans: [{
937
+ scope: {
938
+ name: "cognipeer-agent-sdk",
939
+ version: "1.0.0"
940
+ },
941
+ spans: [rootSpan, ...childSpans]
942
+ }]
943
+ }]
944
+ };
945
+ }
946
+ async function postOtlpTraces(endpoint, headers, payload) {
947
+ const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
948
+ if (!fetchFn) {
949
+ throw new Error("OTLP sink requires fetch to be available in this runtime.");
950
+ }
951
+ const finalHeaders = {
952
+ "content-type": "application/json",
953
+ ...headers || {}
954
+ };
955
+ try {
956
+ const response = await fetchFn(endpoint, {
957
+ method: "POST",
958
+ headers: finalHeaders,
959
+ body: JSON.stringify(payload)
960
+ });
961
+ if (!response.ok) {
962
+ let responseText = "";
963
+ try {
964
+ responseText = await response.text();
965
+ } catch {
966
+ }
967
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
968
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
969
+ throw new Error(`${statusLine}${bodyPreview}`);
970
+ }
971
+ } catch (err) {
972
+ const errMsg = err instanceof Error ? err.message : String(err);
973
+ if (typeof console !== "undefined" && typeof console.error === "function") {
974
+ console.error("[Tracing] OTLP export failed:", errMsg);
975
+ }
976
+ throw err;
977
+ }
978
+ }
747
979
  function createEmptySummary() {
748
980
  return {
749
981
  totalDurationMs: 0,
@@ -761,6 +993,12 @@ function generateSessionId() {
761
993
  function generateEventId(sequence) {
762
994
  return `evt_${String(sequence).padStart(4, "0")}_${nanoid.nanoid(4)}`;
763
995
  }
996
+ function generateTraceId() {
997
+ return crypto__default.default.randomBytes(16).toString("hex");
998
+ }
999
+ function generateSpanId() {
1000
+ return crypto__default.default.randomBytes(8).toString("hex");
1001
+ }
764
1002
  function defaultEventLabel(type) {
765
1003
  switch (type) {
766
1004
  case "ai_call":
@@ -913,8 +1151,12 @@ function createTraceSession(opts) {
913
1151
  const cfg = withDefaults(opts.tracing);
914
1152
  if (!cfg.enabled) return void 0;
915
1153
  const sessionId = generateSessionId();
1154
+ const traceId = generateTraceId();
1155
+ const rootSpanId = generateSpanId();
916
1156
  const runtime = {
917
1157
  sessionId,
1158
+ traceId,
1159
+ rootSpanId,
918
1160
  threadId: opts.tracing?.threadId,
919
1161
  startedAt: Date.now(),
920
1162
  resolvedConfig: cfg,
@@ -1024,8 +1266,13 @@ function recordTraceEvent(session, event) {
1024
1266
  const resolvedModel = event.model ?? inferModelFromMessages(event.messageList);
1025
1267
  const resolvedProvider = event.provider ?? inferProviderFromMessages(event.messageList);
1026
1268
  const isAiCall = event.type === "ai_call";
1269
+ const spanId = generateSpanId();
1270
+ const parentSpanId = event.parentSpanId || session.currentIterationSpanId || session.rootSpanId;
1027
1271
  const record = {
1028
1272
  sessionId: session.sessionId,
1273
+ traceId: session.traceId,
1274
+ spanId,
1275
+ parentSpanId,
1029
1276
  id,
1030
1277
  type: event.type,
1031
1278
  label: eventLabel,
@@ -1132,6 +1379,8 @@ async function finalizeTraceSession(session, params = {}) {
1132
1379
  const initialStatus = params.status ? params.status : session.errors.length > 0 ? "error" : "success";
1133
1380
  const buildPayload = (status2) => ({
1134
1381
  sessionId: session.sessionId,
1382
+ traceId: session.traceId,
1383
+ rootSpanId: session.rootSpanId,
1135
1384
  threadId: session.threadId,
1136
1385
  startedAt: startedAtIso,
1137
1386
  endedAt: endedAtIso,
@@ -1147,7 +1396,21 @@ async function finalizeTraceSession(session, params = {}) {
1147
1396
  let sinkFailed = false;
1148
1397
  const sink = session.resolvedConfig.sink;
1149
1398
  const isStreamingMode = session.resolvedConfig.mode === "streaming";
1150
- if (sink.type === "cognipeer" || sink.type === "http") {
1399
+ if (sink.type === "otlp") {
1400
+ try {
1401
+ const otlpPayload = traceSessionToOtlp(payloadForSink);
1402
+ await postOtlpTraces(sink.endpoint, sink.headers, otlpPayload);
1403
+ } catch (err) {
1404
+ sinkFailed = true;
1405
+ const message = err instanceof Error ? err.message : String(err);
1406
+ session.errors.push({
1407
+ eventId: "sink",
1408
+ message,
1409
+ type: "sink",
1410
+ timestamp: endedAtIso
1411
+ });
1412
+ }
1413
+ } else if (sink.type === "cognipeer" || sink.type === "http") {
1151
1414
  try {
1152
1415
  const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
1153
1416
  if (isStreamingMode && session.sessionStarted) {
@@ -1174,8 +1437,9 @@ async function finalizeTraceSession(session, params = {}) {
1174
1437
  }
1175
1438
  }
1176
1439
  const hadNonSinkErrors = session.errors.some((error) => error.type && error.type !== "sink");
1440
+ const hadSinkErrors = session.errors.some((error) => error.type === "sink");
1177
1441
  let status = params.status ?? (hadNonSinkErrors ? "error" : "success");
1178
- if (status === "success" && sinkFailed) {
1442
+ if (status === "success" && (sinkFailed || hadSinkErrors)) {
1179
1443
  status = "partial";
1180
1444
  }
1181
1445
  let finalPayload = buildPayload(status);
@@ -1279,6 +1543,7 @@ function createAgentCoreNode(opts) {
1279
1543
  model: opts.model,
1280
1544
  tools: opts.tools || [],
1281
1545
  guardrails: opts.guardrails,
1546
+ todoListPrompt: opts.todoListPrompt,
1282
1547
  limits: opts.limits,
1283
1548
  outputSchema: opts.outputSchema,
1284
1549
  tracing: opts.tracing
@@ -1353,7 +1618,7 @@ function createAgentCoreNode(opts) {
1353
1618
  }
1354
1619
  }
1355
1620
  }
1356
- } catch (e) {
1621
+ } catch {
1357
1622
  }
1358
1623
  };
1359
1624
  const onEvent = state.ctx?.__onEvent;
@@ -1469,9 +1734,527 @@ function createAgentCoreNode(opts) {
1469
1734
  return { messages: messagesWithResponse, usage: state.usage };
1470
1735
  };
1471
1736
  }
1737
+
1738
+ // src/smart/runtimeConfig.ts
1739
+ var DEFAULT_CRITICAL_TOOLS = ["response", "manage_todo_list", "get_tool_response"];
1740
+ var DEFAULT_PROFILE_CONFIGS = {
1741
+ fast: {
1742
+ limits: { maxToolCalls: 4, maxParallelTools: 1, maxContextTokens: 12e3 },
1743
+ summarization: {
1744
+ enable: true,
1745
+ maxTokens: 12e3,
1746
+ summaryTriggerTokens: 9e3,
1747
+ summaryPromptMaxTokens: 5e3,
1748
+ summaryCompressionRatioTarget: 0.4,
1749
+ summaryMode: "incremental",
1750
+ promptTemplate: "",
1751
+ toolFreeCall: true,
1752
+ integrityCheck: true
1753
+ },
1754
+ context: {
1755
+ policy: "hybrid",
1756
+ lastTurnsToKeep: 6,
1757
+ toolResponsePolicy: "keep_structured",
1758
+ archiveLargeToolResponses: true,
1759
+ retrieveArchivedToolResponseOnDemand: true,
1760
+ budget: {
1761
+ systemReserveTokens: 1e3,
1762
+ goalsReserveTokens: 900,
1763
+ recentTurnsReserveTokens: 3200,
1764
+ toolResponseReserveTokens: 1800
1765
+ }
1766
+ },
1767
+ planning: {
1768
+ mode: "off",
1769
+ replanPolicy: "on_failure",
1770
+ everyNSteps: 0
1771
+ },
1772
+ memory: {
1773
+ provider: "inMemory",
1774
+ scope: "session",
1775
+ writePolicy: "auto_important",
1776
+ readPolicy: "recent_only"
1777
+ },
1778
+ delegation: {
1779
+ mode: "off",
1780
+ maxDelegationDepth: 1,
1781
+ maxChildCalls: 2,
1782
+ maxParallelChild: 1,
1783
+ childContextPolicy: "minimal",
1784
+ requireJsonOutputContract: true
1785
+ },
1786
+ toolResponses: {
1787
+ maxToolResponseChars: 2400,
1788
+ maxToolResponseTokens: 800,
1789
+ defaultPolicy: "keep_structured",
1790
+ largeResponsePolicy: "summarize_archive",
1791
+ toolResponseRetentionByTool: {},
1792
+ criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1793
+ schemaValidation: "strict",
1794
+ retryOnSchemaError: true,
1795
+ fallbackPolicy: "keep_structured"
1796
+ },
1797
+ watchdog: {
1798
+ enabled: true,
1799
+ tokenDriftThreshold: 1200,
1800
+ overToolingSpikeThreshold: 0.7,
1801
+ contextRotThreshold: 0.65,
1802
+ autoCompaction: true,
1803
+ autoReplanOnFailure: true
1804
+ }
1805
+ },
1806
+ balanced: {
1807
+ limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
1808
+ summarization: {
1809
+ enable: true,
1810
+ maxTokens: 24e3,
1811
+ summaryTriggerTokens: 17e3,
1812
+ summaryPromptMaxTokens: 7e3,
1813
+ summaryCompressionRatioTarget: 0.35,
1814
+ summaryMode: "incremental",
1815
+ promptTemplate: "",
1816
+ toolFreeCall: true,
1817
+ integrityCheck: true
1818
+ },
1819
+ context: {
1820
+ policy: "hybrid",
1821
+ lastTurnsToKeep: 8,
1822
+ toolResponsePolicy: "summarize_archive",
1823
+ archiveLargeToolResponses: true,
1824
+ retrieveArchivedToolResponseOnDemand: true,
1825
+ budget: {
1826
+ systemReserveTokens: 1200,
1827
+ goalsReserveTokens: 1200,
1828
+ recentTurnsReserveTokens: 5200,
1829
+ toolResponseReserveTokens: 2800
1830
+ }
1831
+ },
1832
+ planning: {
1833
+ mode: "off",
1834
+ replanPolicy: "on_failure",
1835
+ everyNSteps: 0
1836
+ },
1837
+ memory: {
1838
+ provider: "inMemory",
1839
+ scope: "session",
1840
+ writePolicy: "auto_important",
1841
+ readPolicy: "hybrid"
1842
+ },
1843
+ delegation: {
1844
+ mode: "role_based",
1845
+ maxDelegationDepth: 2,
1846
+ maxChildCalls: 4,
1847
+ maxParallelChild: 2,
1848
+ childContextPolicy: "scoped",
1849
+ requireJsonOutputContract: true
1850
+ },
1851
+ toolResponses: {
1852
+ maxToolResponseChars: 3600,
1853
+ maxToolResponseTokens: 1200,
1854
+ defaultPolicy: "keep_structured",
1855
+ largeResponsePolicy: "summarize_archive",
1856
+ toolResponseRetentionByTool: {},
1857
+ criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1858
+ schemaValidation: "strict",
1859
+ retryOnSchemaError: true,
1860
+ fallbackPolicy: "keep_structured"
1861
+ },
1862
+ watchdog: {
1863
+ enabled: true,
1864
+ tokenDriftThreshold: 1800,
1865
+ overToolingSpikeThreshold: 0.6,
1866
+ contextRotThreshold: 0.55,
1867
+ autoCompaction: true,
1868
+ autoReplanOnFailure: true
1869
+ }
1870
+ },
1871
+ deep: {
1872
+ limits: { maxToolCalls: 14, maxParallelTools: 3, maxContextTokens: 42e3 },
1873
+ summarization: {
1874
+ enable: true,
1875
+ maxTokens: 42e3,
1876
+ summaryTriggerTokens: 3e4,
1877
+ summaryPromptMaxTokens: 9e3,
1878
+ summaryCompressionRatioTarget: 0.3,
1879
+ summaryMode: "incremental",
1880
+ promptTemplate: "",
1881
+ toolFreeCall: true,
1882
+ integrityCheck: true
1883
+ },
1884
+ context: {
1885
+ policy: "hybrid",
1886
+ lastTurnsToKeep: 12,
1887
+ toolResponsePolicy: "summarize_archive",
1888
+ archiveLargeToolResponses: true,
1889
+ retrieveArchivedToolResponseOnDemand: true,
1890
+ budget: {
1891
+ systemReserveTokens: 1400,
1892
+ goalsReserveTokens: 1600,
1893
+ recentTurnsReserveTokens: 7e3,
1894
+ toolResponseReserveTokens: 3600
1895
+ }
1896
+ },
1897
+ planning: {
1898
+ mode: "off",
1899
+ replanPolicy: "on_failure",
1900
+ everyNSteps: 0
1901
+ },
1902
+ memory: {
1903
+ provider: "inMemory",
1904
+ scope: "workspace",
1905
+ writePolicy: "auto_important",
1906
+ readPolicy: "hybrid"
1907
+ },
1908
+ delegation: {
1909
+ mode: "role_based",
1910
+ maxDelegationDepth: 3,
1911
+ maxChildCalls: 6,
1912
+ maxParallelChild: 2,
1913
+ childContextPolicy: "scoped",
1914
+ requireJsonOutputContract: true
1915
+ },
1916
+ toolResponses: {
1917
+ maxToolResponseChars: 5400,
1918
+ maxToolResponseTokens: 1800,
1919
+ defaultPolicy: "keep_structured",
1920
+ largeResponsePolicy: "summarize_archive",
1921
+ toolResponseRetentionByTool: {},
1922
+ criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1923
+ schemaValidation: "strict",
1924
+ retryOnSchemaError: true,
1925
+ fallbackPolicy: "keep_structured"
1926
+ },
1927
+ watchdog: {
1928
+ enabled: true,
1929
+ tokenDriftThreshold: 2200,
1930
+ overToolingSpikeThreshold: 0.55,
1931
+ contextRotThreshold: 0.5,
1932
+ autoCompaction: true,
1933
+ autoReplanOnFailure: true
1934
+ }
1935
+ },
1936
+ research: {
1937
+ limits: { maxToolCalls: 20, maxParallelTools: 4, maxContextTokens: 56e3 },
1938
+ summarization: {
1939
+ enable: true,
1940
+ maxTokens: 56e3,
1941
+ summaryTriggerTokens: 42e3,
1942
+ summaryPromptMaxTokens: 1e4,
1943
+ summaryCompressionRatioTarget: 0.28,
1944
+ summaryMode: "incremental",
1945
+ promptTemplate: "",
1946
+ toolFreeCall: true,
1947
+ integrityCheck: true
1948
+ },
1949
+ context: {
1950
+ policy: "hybrid",
1951
+ lastTurnsToKeep: 20,
1952
+ toolResponsePolicy: "summarize_archive",
1953
+ archiveLargeToolResponses: true,
1954
+ retrieveArchivedToolResponseOnDemand: true,
1955
+ budget: {
1956
+ systemReserveTokens: 1600,
1957
+ goalsReserveTokens: 1800,
1958
+ recentTurnsReserveTokens: 9e3,
1959
+ toolResponseReserveTokens: 4800
1960
+ }
1961
+ },
1962
+ planning: {
1963
+ mode: "off",
1964
+ replanPolicy: "on_failure",
1965
+ everyNSteps: 0
1966
+ },
1967
+ memory: {
1968
+ provider: "inMemory",
1969
+ scope: "workspace",
1970
+ writePolicy: "auto_important",
1971
+ readPolicy: "hybrid"
1972
+ },
1973
+ delegation: {
1974
+ mode: "automatic",
1975
+ maxDelegationDepth: 4,
1976
+ maxChildCalls: 8,
1977
+ maxParallelChild: 3,
1978
+ childContextPolicy: "full",
1979
+ requireJsonOutputContract: true
1980
+ },
1981
+ toolResponses: {
1982
+ maxToolResponseChars: 7200,
1983
+ maxToolResponseTokens: 2200,
1984
+ defaultPolicy: "keep_structured",
1985
+ largeResponsePolicy: "summarize_archive",
1986
+ toolResponseRetentionByTool: {},
1987
+ criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1988
+ schemaValidation: "strict",
1989
+ retryOnSchemaError: true,
1990
+ fallbackPolicy: "keep_structured"
1991
+ },
1992
+ watchdog: {
1993
+ enabled: true,
1994
+ tokenDriftThreshold: 2600,
1995
+ overToolingSpikeThreshold: 0.5,
1996
+ contextRotThreshold: 0.45,
1997
+ autoCompaction: true,
1998
+ autoReplanOnFailure: true
1999
+ }
2000
+ }
2001
+ };
2002
+ function mergeLimits(base, override) {
2003
+ return {
2004
+ maxToolCalls: override?.maxToolCalls ?? base.maxToolCalls,
2005
+ maxParallelTools: override?.maxParallelTools ?? base.maxParallelTools,
2006
+ maxContextTokens: override?.maxContextTokens ?? base.maxContextTokens
2007
+ };
2008
+ }
2009
+ function resolveBaseProfile(opts, runtimeProfile) {
2010
+ if (opts.customProfile?.extends) return opts.customProfile.extends;
2011
+ if (runtimeProfile !== "custom") return runtimeProfile;
2012
+ return "balanced";
2013
+ }
2014
+ function normalizeSmartAgentOptions(opts) {
2015
+ const runtimeProfile = opts.runtimeProfile ?? (opts.customProfile ? "custom" : "balanced");
2016
+ const baseProfile = resolveBaseProfile(opts, runtimeProfile);
2017
+ const preset = DEFAULT_PROFILE_CONFIGS[baseProfile];
2018
+ const customProfile = opts.customProfile || {};
2019
+ const useTodoList = opts.useTodoList === true;
2020
+ const summarizationDisabled = opts.summarization === false;
2021
+ const summarizationOverride = typeof opts.summarization === "object" ? opts.summarization : {};
2022
+ const planningMode = opts.planning?.mode ?? customProfile.planning?.mode ?? (useTodoList ? "todo" : preset.planning.mode);
2023
+ const memoryStore = opts.memory?.store ?? customProfile.memory?.store;
2024
+ return {
2025
+ runtimeProfile,
2026
+ baseProfile,
2027
+ limits: mergeLimits(mergeLimits(preset.limits, customProfile.limits), opts.limits),
2028
+ summarization: {
2029
+ ...preset.summarization,
2030
+ ...customProfile.summarization || {},
2031
+ ...summarizationOverride,
2032
+ enable: summarizationDisabled ? false : summarizationOverride.enable ?? customProfile.summarization?.enable ?? preset.summarization.enable,
2033
+ maxTokens: summarizationOverride.maxTokens ?? opts.limits?.maxContextTokens ?? customProfile.summarization?.maxTokens ?? preset.summarization.maxTokens,
2034
+ summaryTriggerTokens: summarizationOverride.summaryTriggerTokens ?? summarizationOverride.maxTokens ?? opts.limits?.maxContextTokens ?? customProfile.summarization?.summaryTriggerTokens ?? customProfile.summarization?.maxTokens ?? preset.summarization.summaryTriggerTokens,
2035
+ promptTemplate: summarizationOverride.promptTemplate ?? customProfile.summarization?.promptTemplate ?? preset.summarization.promptTemplate
2036
+ },
2037
+ context: {
2038
+ ...preset.context,
2039
+ ...customProfile.context || {},
2040
+ ...opts.context || {},
2041
+ budget: {
2042
+ ...preset.context.budget,
2043
+ ...customProfile.context?.budget || {},
2044
+ ...opts.context?.budget || {}
2045
+ },
2046
+ toolResponsePolicy: opts.context?.toolResponsePolicy ?? customProfile.context?.toolResponsePolicy ?? preset.context.toolResponsePolicy
2047
+ },
2048
+ planning: {
2049
+ ...preset.planning,
2050
+ ...customProfile.planning || {},
2051
+ ...opts.planning || {},
2052
+ mode: planningMode
2053
+ },
2054
+ memory: {
2055
+ ...preset.memory,
2056
+ ...customProfile.memory || {},
2057
+ ...opts.memory || {},
2058
+ store: memoryStore
2059
+ },
2060
+ delegation: {
2061
+ ...preset.delegation,
2062
+ ...customProfile.delegation || {},
2063
+ ...opts.delegation || {}
2064
+ },
2065
+ toolResponses: {
2066
+ ...preset.toolResponses,
2067
+ ...customProfile.toolResponses || {},
2068
+ ...opts.toolResponses || {},
2069
+ toolResponseRetentionByTool: {
2070
+ ...preset.toolResponses.toolResponseRetentionByTool,
2071
+ ...customProfile.toolResponses?.toolResponseRetentionByTool || {},
2072
+ ...opts.toolResponses?.toolResponseRetentionByTool || {}
2073
+ },
2074
+ criticalTools: opts.toolResponses?.criticalTools ?? customProfile.toolResponses?.criticalTools ?? preset.toolResponses.criticalTools
2075
+ },
2076
+ watchdog: {
2077
+ ...preset.watchdog,
2078
+ ...customProfile.watchdog || {},
2079
+ ...opts.watchdog || {}
2080
+ }
2081
+ };
2082
+ }
2083
+ function getResolvedSmartConfig(opts, runtime) {
2084
+ return runtime?.smart || normalizeSmartAgentOptions(opts);
2085
+ }
2086
+
2087
+ // src/utils/utilTokens.ts
2088
+ function countApproxTokens(text) {
2089
+ if (!text) return 0;
2090
+ const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
2091
+ const asciiCount = text.length - nonAsciiCount;
2092
+ return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
2093
+ }
2094
+ function extractMessageText(m) {
2095
+ if (!m) return "";
2096
+ const parts = [];
2097
+ parts.push(m.role || "");
2098
+ if (m.name) parts.push(m.name);
2099
+ if (typeof m.content === "string") {
2100
+ parts.push(m.content);
2101
+ } else if (Array.isArray(m.content)) {
2102
+ for (const c of m.content) {
2103
+ if (typeof c === "string") parts.push(c);
2104
+ else if (c?.text) parts.push(c.text);
2105
+ else if (c?.content) parts.push(c.content);
2106
+ else if (c?.type === "tool_use" && c?.input) {
2107
+ parts.push(c.name || "");
2108
+ parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
2109
+ } else if (c != null && typeof c === "object") {
2110
+ parts.push(JSON.stringify(c));
2111
+ }
2112
+ }
2113
+ } else if (m.content != null && typeof m.content === "object") {
2114
+ parts.push(JSON.stringify(m.content));
2115
+ }
2116
+ if (Array.isArray(m.tool_calls)) {
2117
+ for (const tc of m.tool_calls) {
2118
+ if (tc?.function?.name) parts.push(tc.function.name);
2119
+ if (tc?.function?.arguments) {
2120
+ parts.push(
2121
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2122
+ );
2123
+ }
2124
+ if (tc?.name && !tc?.function?.name) parts.push(tc.name);
2125
+ if (tc?.args && !tc?.function?.arguments) {
2126
+ parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
2127
+ }
2128
+ }
2129
+ }
2130
+ if (Array.isArray(m.additional_kwargs?.tool_calls)) {
2131
+ for (const tc of m.additional_kwargs.tool_calls) {
2132
+ if (tc?.function?.name) parts.push(tc.function.name);
2133
+ if (tc?.function?.arguments) {
2134
+ parts.push(
2135
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2136
+ );
2137
+ }
2138
+ }
2139
+ }
2140
+ return parts.join("\n");
2141
+ }
2142
+ function countMessagesTokens(messages) {
2143
+ if (!Array.isArray(messages) || messages.length === 0) return 0;
2144
+ const allText = messages.map(extractMessageText).join("\n");
2145
+ return countApproxTokens(allText) + messages.length * 4;
2146
+ }
2147
+
2148
+ // src/smart/toolResponses.ts
2149
+ function safeStringify(value) {
2150
+ if (typeof value === "string") return value;
2151
+ try {
2152
+ const serialized = JSON.stringify(value);
2153
+ if (typeof serialized === "string") {
2154
+ return serialized;
2155
+ }
2156
+ } catch {
2157
+ }
2158
+ return String(value);
2159
+ }
2160
+ function summarizeObject(value) {
2161
+ if (value == null) return "null";
2162
+ if (typeof value === "string") {
2163
+ return value.length > 240 ? `${value.slice(0, 240)}...` : value;
2164
+ }
2165
+ if (Array.isArray(value)) {
2166
+ const preview = value.slice(0, 3).map((entry) => safeStringify(entry).slice(0, 120)).join(" | ");
2167
+ return `array(length=${value.length}) ${preview}`.trim();
2168
+ }
2169
+ if (typeof value === "object") {
2170
+ const keys = Object.keys(value);
2171
+ const preview = keys.slice(0, 6).map((key) => `${key}=${safeStringify(value[key]).slice(0, 80)}`).join("; ");
2172
+ return `object(keys=${keys.length}) ${preview}`.trim();
2173
+ }
2174
+ return safeStringify(value);
2175
+ }
2176
+ function classifyToolResponse(toolName, output, config) {
2177
+ const serialized = safeStringify(output);
2178
+ const tokenCount = countApproxTokens(serialized);
2179
+ const criticalTools = new Set(config.toolResponses.criticalTools);
2180
+ if (criticalTools.has(toolName)) return "critical";
2181
+ if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
2182
+ return "verbose";
2183
+ }
2184
+ if (serialized === "ok" || serialized === "true" || serialized === "null") {
2185
+ return "redundant";
2186
+ }
2187
+ return "informative";
2188
+ }
2189
+ function resolveToolResponsePolicy(toolName, output, config) {
2190
+ const serialized = safeStringify(output);
2191
+ const tokenCount = countApproxTokens(serialized);
2192
+ const classification = classifyToolResponse(toolName, output, config);
2193
+ const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2194
+ let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
2195
+ if (classification === "critical") {
2196
+ retentionPolicy = byTool || "keep_structured";
2197
+ } else if (classification === "verbose") {
2198
+ retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
2199
+ } else if (classification === "redundant") {
2200
+ retentionPolicy = byTool || "drop";
2201
+ }
2202
+ if (retentionPolicy === "keep_full") {
2203
+ const summary2 = "";
2204
+ return { classification, retentionPolicy, content: serialized, rawOutput: output, summary: summary2, tokenCount };
2205
+ }
2206
+ const summary = summarizeObject(output);
2207
+ if (retentionPolicy === "keep_structured") {
2208
+ return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
2209
+ }
2210
+ if (retentionPolicy === "summarize_archive") {
2211
+ return {
2212
+ classification,
2213
+ retentionPolicy,
2214
+ content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
2215
+ rawOutput: output,
2216
+ summary,
2217
+ tokenCount
2218
+ };
2219
+ }
2220
+ return {
2221
+ classification,
2222
+ retentionPolicy,
2223
+ content: `DROPPED_TOOL_RESPONSE: ${summary}`,
2224
+ rawOutput: output,
2225
+ summary,
2226
+ tokenCount
2227
+ };
2228
+ }
2229
+ function validateToolArgs(tool, args) {
2230
+ const schema = tool.schema;
2231
+ if (!schema || typeof schema.parse !== "function") {
2232
+ return { ok: true, value: args };
2233
+ }
2234
+ try {
2235
+ return { ok: true, value: schema.parse(args) };
2236
+ } catch (error) {
2237
+ if (error instanceof zod.ZodError) {
2238
+ const message = error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
2239
+ return { ok: false, message };
2240
+ }
2241
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
2242
+ }
2243
+ }
2244
+
2245
+ // src/nodes/tools.ts
2246
+ function normalizeMaxExecutionsPerRun(value) {
2247
+ if (value === null || value === void 0 || value === "") return null;
2248
+ const parsed = Number(value);
2249
+ if (!Number.isFinite(parsed)) return null;
2250
+ const normalized = Math.trunc(parsed);
2251
+ return normalized >= 0 ? normalized : null;
2252
+ }
2253
+ function countSuccessfulToolExecutions(toolHistory, toolName) {
2254
+ if (!Array.isArray(toolHistory)) return 0;
2255
+ return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
2256
+ }
1472
2257
  function createToolsNode(initialTools, opts) {
1473
- const baseToolByName = /* @__PURE__ */ new Map();
1474
- for (const t of initialTools) baseToolByName.set(t.name, t);
1475
2258
  return async (state) => {
1476
2259
  const runtime = state.agent || {
1477
2260
  name: opts?.name,
@@ -1479,15 +2262,17 @@ function createToolsNode(initialTools, opts) {
1479
2262
  tools: initialTools,
1480
2263
  limits: opts?.limits,
1481
2264
  systemPrompt: opts?.systemPrompt,
2265
+ todoListPrompt: opts?.todoListPrompt,
1482
2266
  useTodoList: opts?.useTodoList,
1483
2267
  outputSchema: opts?.outputSchema
1484
2268
  };
2269
+ const resolved = getResolvedSmartConfig(opts || {}, runtime);
1485
2270
  const activeTools = runtime.tools;
1486
2271
  const toolByName = /* @__PURE__ */ new Map();
1487
- for (const t of activeTools) toolByName.set(t.name, t);
2272
+ for (const tool of activeTools) toolByName.set(tool.name, tool);
1488
2273
  const limits = {
1489
- maxToolCalls: runtime.limits?.maxToolCalls ?? 10,
1490
- maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? 1)
2274
+ maxToolCalls: runtime.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10,
2275
+ maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? resolved.limits.maxParallelTools ?? 1)
1491
2276
  };
1492
2277
  const appended = [];
1493
2278
  const onEvent = state.ctx?.__onEvent;
@@ -1498,20 +2283,19 @@ function createToolsNode(initialTools, opts) {
1498
2283
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
1499
2284
  const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
1500
2285
  let awaitingApproval = false;
1501
- for (const t of toolByName.values()) {
1502
- const anyT = t;
1503
- if (anyT._stateRef && typeof anyT._stateRef === "object") {
1504
- anyT._stateRef.toolHistory = state.toolHistory;
1505
- anyT._stateRef.toolHistoryArchived = state.toolHistoryArchived;
1506
- anyT._stateRef.__onEvent = onEvent;
2286
+ for (const tool of toolByName.values()) {
2287
+ const anyTool = tool;
2288
+ if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
2289
+ anyTool._stateRef.toolHistory = state.toolHistory;
2290
+ anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
2291
+ anyTool._stateRef.__onEvent = onEvent;
1507
2292
  }
1508
2293
  }
1509
2294
  const last = state.messages[state.messages.length - 1];
1510
2295
  let toolCount = state.toolCallCount || 0;
1511
- const toolCalls = Array.isArray(
1512
- last?.tool_calls
1513
- ) ? last.tool_calls : [];
2296
+ const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
1514
2297
  const toolHistory = state.toolHistory || [];
2298
+ const toolHistoryArchived = state.toolHistoryArchived || [];
1515
2299
  const remaining = Math.max(0, limits.maxToolCalls - toolCount);
1516
2300
  const planned = toolCalls.slice(0, remaining);
1517
2301
  const skipped = toolCalls.slice(remaining);
@@ -1520,6 +2304,17 @@ function createToolsNode(initialTools, opts) {
1520
2304
  if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
1521
2305
  return { cancelled: false, reason: void 0 };
1522
2306
  };
2307
+ const markToolFailure = (message, toolName, toolCallId) => {
2308
+ const ctx = { ...state.ctx || {} };
2309
+ ctx.__lastToolError = message;
2310
+ if (toolCallId) {
2311
+ ctx.__toolSchemaError = { toolName, toolCallId, message };
2312
+ }
2313
+ if (resolved.watchdog.autoReplanOnFailure) {
2314
+ ctx.__planNeedsReplan = true;
2315
+ }
2316
+ state.ctx = ctx;
2317
+ };
1523
2318
  const runOne = async (tc) => {
1524
2319
  const cancelState = isCancelled();
1525
2320
  if (cancelState.cancelled) {
@@ -1529,15 +2324,10 @@ function createToolsNode(initialTools, opts) {
1529
2324
  onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
1530
2325
  return { status: "error" };
1531
2326
  }
1532
- const t = toolByName.get(tc.name);
1533
- if (!t) {
2327
+ const tool = toolByName.get(tc.name);
2328
+ if (!tool) {
2329
+ appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1534
2330
  onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
1535
- appended.push({
1536
- role: "tool",
1537
- content: `Tool not found: ${tc.name}`,
1538
- tool_call_id: tc.id || `${tc.name}_${appended.length}`,
1539
- name: tc.name
1540
- });
1541
2331
  toolCount += 1;
1542
2332
  return { status: "error" };
1543
2333
  }
@@ -1545,22 +2335,67 @@ function createToolsNode(initialTools, opts) {
1545
2335
  if (typeof args === "string") {
1546
2336
  try {
1547
2337
  args = JSON.parse(args);
1548
- } catch (_) {
2338
+ } catch {
1549
2339
  }
1550
2340
  }
1551
2341
  const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
2342
+ const validatedArgs = validateToolArgs(tool, args);
2343
+ if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
2344
+ const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
2345
+ appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
2346
+ markToolFailure(errorMessage, tc.name, toolCallId);
2347
+ onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
2348
+ toolCount += 1;
2349
+ return { status: "error" };
2350
+ }
2351
+ if (validatedArgs.ok) {
2352
+ args = validatedArgs.value;
2353
+ }
2354
+ const toolName = tool.name || tc.name;
2355
+ const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
2356
+ if (maxExecutionsPerRun !== null) {
2357
+ const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
2358
+ if (currentExecutions >= maxExecutionsPerRun) {
2359
+ const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
2360
+ appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
2361
+ onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
2362
+ recordTraceEvent(traceSession, {
2363
+ type: "tool_call",
2364
+ label: `Tool Skipped - ${toolName}`,
2365
+ actor: { scope: "tool", name: toolName, role: "tool" },
2366
+ status: "skipped",
2367
+ toolExecutionId: tc.id,
2368
+ messageList: [
2369
+ {
2370
+ role: "assistant",
2371
+ name: toolName,
2372
+ content: limitMessage,
2373
+ tool_calls: [
2374
+ {
2375
+ id: tc.id,
2376
+ type: "function",
2377
+ function: { name: toolName, arguments: sanitizeTracePayload(args) }
2378
+ }
2379
+ ]
2380
+ }
2381
+ ]
2382
+ });
2383
+ toolCount += 1;
2384
+ return { status: "error" };
2385
+ }
2386
+ }
1552
2387
  let approvalEntry = pendingByCallId.get(toolCallId);
1553
- const needsApproval = Boolean(t.needsApproval);
2388
+ const needsApproval = Boolean(tool.needsApproval);
1554
2389
  if (needsApproval) {
1555
2390
  if (!approvalEntry) {
1556
2391
  approvalEntry = {
1557
2392
  id: nanoid.nanoid(),
1558
2393
  toolCallId,
1559
- toolName: t.name || tc.name,
2394
+ toolName: tool.name || tc.name,
1560
2395
  args,
1561
2396
  status: "pending",
1562
2397
  requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
1563
- metadata: t.approvalPrompt || t.approvalDefaults ? { prompt: t.approvalPrompt, defaults: t.approvalDefaults } : void 0
2398
+ metadata: tool.approvalPrompt || tool.approvalDefaults ? { prompt: tool.approvalPrompt, defaults: tool.approvalDefaults } : void 0
1564
2399
  };
1565
2400
  pendingApprovals.push(approvalEntry);
1566
2401
  pendingByCallId.set(toolCallId, approvalEntry);
@@ -1582,71 +2417,97 @@ function createToolsNode(initialTools, opts) {
1582
2417
  }
1583
2418
  if (approvalEntry.status === "rejected") {
1584
2419
  const rejectionMessage = approvalEntry.comment || "Tool call rejected by reviewer.";
1585
- appended.push({
1586
- role: "tool",
1587
- content: `Tool call rejected: ${rejectionMessage}`,
1588
- tool_call_id: toolCallId,
1589
- name: tc.name
1590
- });
1591
- onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
2420
+ appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
1592
2421
  approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
1593
2422
  approvalEntry.status = "executed";
1594
2423
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
1595
- toolHistory.push({ executionId: nanoid.nanoid(), toolName: t.name, args, output: `Rejected: ${rejectionMessage}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
1596
2424
  pendingByCallId.set(toolCallId, approvalEntry);
2425
+ toolHistory.push({ executionId: nanoid.nanoid(), toolName: tool.name, args, output: `Rejected: ${rejectionMessage}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "rejected" });
2426
+ onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
1597
2427
  toolCount += 1;
1598
2428
  return { status: "rejected", approval: approvalEntry };
1599
2429
  }
1600
- if (approvalEntry.status === "approved") {
1601
- if (approvalEntry.approvedArgs !== void 0) {
1602
- args = approvalEntry.approvedArgs;
1603
- }
2430
+ if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
2431
+ args = approvalEntry.approvedArgs;
1604
2432
  onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
1605
2433
  }
1606
2434
  }
1607
2435
  const start = Date.now();
2436
+ const sanitizedArgs = sanitizeTracePayload(args);
2437
+ const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1608
2438
  try {
1609
- onEvent?.({ type: "tool_call", phase: "start", name: t.name, id: tc.id, args });
2439
+ onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
1610
2440
  onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
1611
- const sanitizedArgs = sanitizeTracePayload(args);
1612
- const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1613
- let output;
1614
- const anyTool = t;
2441
+ const anyTool = tool;
1615
2442
  const callOptions = { cancellationToken, signal: abortSignal };
2443
+ let output;
1616
2444
  if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
1617
2445
  else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
1618
2446
  else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
1619
2447
  else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
1620
2448
  else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
1621
2449
  else throw new Error("Tool is not invokable");
1622
- if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
1623
- const newRuntime = output.__handoff.runtime;
1624
- const executionId2 = nanoid.nanoid();
1625
- toolHistory.push({ executionId: executionId2, toolName: t.name, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
2450
+ const durationMs = Date.now() - start;
2451
+ const executionId = nanoid.nanoid();
2452
+ if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
2453
+ toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
1626
2454
  appended.push({ role: "tool", content: "ok", tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1627
- onEvent?.({ type: "handoff", from: runtime.name, to: newRuntime.name, toolName: t.name });
1628
- state.agent = newRuntime;
1629
- onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: "handoff", durationMs: Date.now() - start });
1630
- toolCount += 1;
2455
+ state.agent = output.__handoff.runtime;
2456
+ onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
2457
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
1631
2458
  if (needsApproval && approvalEntry) {
1632
2459
  approvalEntry.status = "executed";
1633
2460
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
1634
- approvalEntry.executionId = executionId2;
2461
+ approvalEntry.executionId = executionId;
1635
2462
  pendingByCallId.set(toolCallId, approvalEntry);
1636
2463
  }
2464
+ toolCount += 1;
1637
2465
  return { status: "success", approval: approvalEntry };
1638
2466
  }
1639
- const content = typeof output === "string" ? output : JSON.stringify(output);
1640
2467
  if (output && typeof output === "object" && output.__finalStructuredOutput) {
1641
2468
  if (!state.ctx) state.ctx = {};
1642
2469
  state.ctx.__structuredOutputParsed = output.data;
1643
2470
  state.ctx.__finalizedDueToStructuredOutput = true;
1644
2471
  }
1645
- const durationMs = Date.now() - start;
1646
- const executionId = nanoid.nanoid();
1647
- toolHistory.push({ executionId, toolName: t.name, args, output, rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
1648
- appended.push({ role: "tool", content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1649
- onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: output, durationMs });
2472
+ const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
2473
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2474
+ const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
2475
+ toolHistory.push({
2476
+ executionId,
2477
+ toolName,
2478
+ args,
2479
+ output: storedOutput,
2480
+ rawOutput: output,
2481
+ timestamp,
2482
+ tool_call_id: tc.id,
2483
+ summarized: responsePolicy.retentionPolicy !== "keep_full",
2484
+ originalTokenCount: responsePolicy.tokenCount,
2485
+ classification: responsePolicy.classification,
2486
+ retentionPolicy: responsePolicy.retentionPolicy,
2487
+ summary: responsePolicy.summary,
2488
+ archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
2489
+ status: "success"
2490
+ });
2491
+ if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
2492
+ toolHistoryArchived.push({
2493
+ executionId,
2494
+ toolName,
2495
+ args,
2496
+ output,
2497
+ rawOutput: output,
2498
+ timestamp,
2499
+ tool_call_id: tc.id,
2500
+ summarized: true,
2501
+ originalTokenCount: responsePolicy.tokenCount,
2502
+ classification: responsePolicy.classification,
2503
+ retentionPolicy: responsePolicy.retentionPolicy,
2504
+ summary: responsePolicy.summary,
2505
+ archiveId: executionId,
2506
+ status: "success"
2507
+ });
2508
+ }
2509
+ appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2510
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
1650
2511
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
1651
2512
  if (needsApproval && approvalEntry) {
1652
2513
  approvalEntry.status = "executed";
@@ -1656,7 +2517,6 @@ function createToolsNode(initialTools, opts) {
1656
2517
  }
1657
2518
  const sanitizedOutput = sanitizeTracePayload(output);
1658
2519
  const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
1659
- const toolName = t.name || tc.name;
1660
2520
  const messageList = [
1661
2521
  {
1662
2522
  role: "assistant",
@@ -1676,7 +2536,7 @@ function createToolsNode(initialTools, opts) {
1676
2536
  {
1677
2537
  role: "tool",
1678
2538
  name: toolName,
1679
- content: sanitizedOutput ?? output ?? ""
2539
+ content: responsePolicy.content
1680
2540
  }
1681
2541
  ];
1682
2542
  recordTraceEvent(traceSession, {
@@ -1687,43 +2547,24 @@ function createToolsNode(initialTools, opts) {
1687
2547
  requestBytes: inputBytes,
1688
2548
  responseBytes: outputBytes,
1689
2549
  toolExecutionId: executionId,
1690
- messageList
2550
+ messageList,
2551
+ debug: {
2552
+ classification: responsePolicy.classification,
2553
+ retentionPolicy: responsePolicy.retentionPolicy,
2554
+ originalTokenCount: responsePolicy.tokenCount
2555
+ }
1691
2556
  });
1692
2557
  toolCount += 1;
1693
2558
  return { status: "success", approval: approvalEntry };
1694
- } catch (e) {
2559
+ } catch (error) {
1695
2560
  const durationMs = Date.now() - start;
1696
2561
  const executionId = nanoid.nanoid();
1697
- toolHistory.push({ executionId, toolName: t.name, args, output: `Error executing tool: ${e?.message || String(e)}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
1698
- appended.push({ role: "tool", content: `Error executing tool: ${e?.message || String(e)}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1699
- onEvent?.({ type: "tool_call", phase: "error", name: t.name, id: tc.id, args, error: { message: e?.message || String(e) } });
1700
- onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: e?.message || String(e) } });
1701
- const sanitizedArgs = sanitizeTracePayload(args);
1702
- const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1703
- const toolName = t.name || tc.name;
1704
- const errorMessage = e?.message || String(e);
1705
- const messageList = [
1706
- {
1707
- role: "assistant",
1708
- name: toolName,
1709
- content: "",
1710
- tool_calls: [
1711
- {
1712
- id: tc.id || executionId,
1713
- type: "function",
1714
- function: {
1715
- name: toolName,
1716
- arguments: sanitizedArgs
1717
- }
1718
- }
1719
- ]
1720
- },
1721
- {
1722
- role: "tool",
1723
- name: toolName,
1724
- content: `Error executing tool: ${errorMessage}`
1725
- }
1726
- ];
2562
+ const message = error?.message || String(error);
2563
+ toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
2564
+ appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2565
+ markToolFailure(message, toolName, toolCallId);
2566
+ onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
2567
+ onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
1727
2568
  recordTraceEvent(traceSession, {
1728
2569
  type: "tool_call",
1729
2570
  label: `Tool Error - ${toolName}`,
@@ -1732,8 +2573,26 @@ function createToolsNode(initialTools, opts) {
1732
2573
  durationMs,
1733
2574
  requestBytes: inputBytes,
1734
2575
  toolExecutionId: executionId,
1735
- error: { message: e?.message || String(e), stack: e?.stack },
1736
- messageList
2576
+ error: { message, stack: error?.stack },
2577
+ messageList: [
2578
+ {
2579
+ role: "assistant",
2580
+ name: toolName,
2581
+ content: "",
2582
+ tool_calls: [
2583
+ {
2584
+ id: tc.id || executionId,
2585
+ type: "function",
2586
+ function: { name: toolName, arguments: sanitizedArgs }
2587
+ }
2588
+ ]
2589
+ },
2590
+ {
2591
+ role: "tool",
2592
+ name: toolName,
2593
+ content: `Error executing tool: ${message}`
2594
+ }
2595
+ ]
1737
2596
  });
1738
2597
  toolCount += 1;
1739
2598
  return { status: "error", approval: approvalEntry };
@@ -1750,7 +2609,7 @@ function createToolsNode(initialTools, opts) {
1750
2609
  break;
1751
2610
  }
1752
2611
  const result = await runOne(tc);
1753
- if (result?.status === "awaiting_approval") {
2612
+ if (result.status === "awaiting_approval") {
1754
2613
  awaitingApproval = true;
1755
2614
  break;
1756
2615
  }
@@ -1758,37 +2617,28 @@ function createToolsNode(initialTools, opts) {
1758
2617
  for (const tc of skipped) {
1759
2618
  onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
1760
2619
  const sanitizedArgs = sanitizeTracePayload(tc.args);
1761
- const messageList = [
1762
- {
1763
- role: "assistant",
1764
- name: tc.name,
1765
- content: `Skipped tool due to tool-call limit: ${tc.name}`,
1766
- tool_calls: [
1767
- {
1768
- id: tc.id,
1769
- type: "function",
1770
- function: {
1771
- name: tc.name,
1772
- arguments: sanitizedArgs
1773
- }
1774
- }
1775
- ]
1776
- }
1777
- ];
1778
2620
  recordTraceEvent(traceSession, {
1779
2621
  type: "tool_call",
1780
2622
  label: `Tool Skipped - ${tc.name}`,
1781
2623
  actor: { scope: "tool", name: tc.name, role: "tool" },
1782
2624
  status: "skipped",
1783
2625
  toolExecutionId: tc.id,
1784
- messageList
1785
- });
1786
- appended.push({
1787
- role: "tool",
1788
- content: `Skipped tool due to tool-call limit: ${tc.name}`,
1789
- tool_call_id: tc.id || `${tc.name}_${appended.length}`,
1790
- name: tc.name
2626
+ messageList: [
2627
+ {
2628
+ role: "assistant",
2629
+ name: tc.name,
2630
+ content: `Skipped tool due to tool-call limit: ${tc.name}`,
2631
+ tool_calls: [
2632
+ {
2633
+ id: tc.id,
2634
+ type: "function",
2635
+ function: { name: tc.name, arguments: sanitizedArgs }
2636
+ }
2637
+ ]
2638
+ }
2639
+ ]
1791
2640
  });
2641
+ appended.push({ role: "tool", content: `Skipped tool due to tool-call limit: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1792
2642
  toolCount += 1;
1793
2643
  }
1794
2644
  if (!awaitingApproval && state.ctx?.__awaitingApproval) {
@@ -1803,6 +2653,7 @@ function createToolsNode(initialTools, opts) {
1803
2653
  messages: [...state.messages, ...appended],
1804
2654
  toolCallCount: toolCount,
1805
2655
  toolHistory,
2656
+ toolHistoryArchived,
1806
2657
  agent: state.agent,
1807
2658
  pendingApprovals
1808
2659
  };
@@ -2027,68 +2878,20 @@ function resolveToolApprovalState(state, resolution) {
2027
2878
  };
2028
2879
  }
2029
2880
 
2030
- // src/utils/utilTokens.ts
2031
- function countApproxTokens(text) {
2032
- if (!text) return 0;
2033
- const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
2034
- const asciiCount = text.length - nonAsciiCount;
2035
- return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
2036
- }
2037
- function extractMessageText(m) {
2038
- if (!m) return "";
2039
- const parts = [];
2040
- parts.push(m.role || "");
2041
- if (m.name) parts.push(m.name);
2042
- if (typeof m.content === "string") {
2043
- parts.push(m.content);
2044
- } else if (Array.isArray(m.content)) {
2045
- for (const c of m.content) {
2046
- if (typeof c === "string") parts.push(c);
2047
- else if (c?.text) parts.push(c.text);
2048
- else if (c?.content) parts.push(c.content);
2049
- else if (c?.type === "tool_use" && c?.input) {
2050
- parts.push(c.name || "");
2051
- parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
2052
- } else if (c != null && typeof c === "object") {
2053
- parts.push(JSON.stringify(c));
2054
- }
2055
- }
2056
- } else if (m.content != null && typeof m.content === "object") {
2057
- parts.push(JSON.stringify(m.content));
2058
- }
2059
- if (Array.isArray(m.tool_calls)) {
2060
- for (const tc of m.tool_calls) {
2061
- if (tc?.function?.name) parts.push(tc.function.name);
2062
- if (tc?.function?.arguments) {
2063
- parts.push(
2064
- typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2065
- );
2066
- }
2067
- if (tc?.name && !tc?.function?.name) parts.push(tc.name);
2068
- if (tc?.args && !tc?.function?.arguments) {
2069
- parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
2070
- }
2071
- }
2881
+ // src/agent.ts
2882
+ function isSyntheticSummaryMessage(message) {
2883
+ if (!message) return false;
2884
+ if (message.role === "tool" && message.name === "summarize_context") {
2885
+ return true;
2072
2886
  }
2073
- if (Array.isArray(m.additional_kwargs?.tool_calls)) {
2074
- for (const tc of m.additional_kwargs.tool_calls) {
2075
- if (tc?.function?.name) parts.push(tc.function.name);
2076
- if (tc?.function?.arguments) {
2077
- parts.push(
2078
- typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2079
- );
2080
- }
2081
- }
2887
+ if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
2888
+ return message.tool_calls.some((toolCall) => {
2889
+ const toolName = toolCall?.function?.name || toolCall?.name;
2890
+ return toolName === "summarize_context";
2891
+ });
2082
2892
  }
2083
- return parts.join("\n");
2084
- }
2085
- function countMessagesTokens(messages) {
2086
- if (!Array.isArray(messages) || messages.length === 0) return 0;
2087
- const allText = messages.map(extractMessageText).join("\n");
2088
- return countApproxTokens(allText) + messages.length * 4;
2893
+ return false;
2089
2894
  }
2090
-
2091
- // src/agent.ts
2092
2895
  function createAgent(opts) {
2093
2896
  const resolver = createResolverNode();
2094
2897
  const agentCore = createAgentCoreNode(opts);
@@ -2131,6 +2934,7 @@ function createAgent(opts) {
2131
2934
  tools: toolsBase,
2132
2935
  guardrails: opts.guardrails,
2133
2936
  systemPrompt: void 0,
2937
+ todoListPrompt: opts.todoListPrompt,
2134
2938
  limits: opts.limits,
2135
2939
  useTodoList: void 0,
2136
2940
  outputSchema: opts.outputSchema,
@@ -2206,6 +3010,18 @@ function createAgent(opts) {
2206
3010
  };
2207
3011
  while (iterations < iterationLimit) {
2208
3012
  iterations++;
3013
+ const traceSession = state.ctx?.__traceSession;
3014
+ if (traceSession) {
3015
+ const iterEvent = recordTraceEvent(traceSession, {
3016
+ type: "agent_iteration",
3017
+ label: `Iteration ${iterations}`,
3018
+ actor: { scope: "agent", name: opts.name || "agent", role: "orchestrator" },
3019
+ parentSpanId: traceSession.rootSpanId
3020
+ });
3021
+ if (iterEvent) {
3022
+ traceSession.currentIterationSpanId = iterEvent.spanId;
3023
+ }
3024
+ }
2209
3025
  if (cancelIfRequested("loop")) break;
2210
3026
  const skippingAgent = resumeStage === "tools";
2211
3027
  if (!skippingAgent) {
@@ -2256,11 +3072,21 @@ function createAgent(opts) {
2256
3072
  maxTok = 5e4;
2257
3073
  }
2258
3074
  if (maxTok) {
2259
- const tokenCount = countMessagesTokens(state.messages || []);
3075
+ const tokCountMessages = (state.messages || []).filter((message) => {
3076
+ if (isSyntheticSummaryMessage(message)) return false;
3077
+ if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
3078
+ return true;
3079
+ });
3080
+ const tokenCount = countMessagesTokens(tokCountMessages);
2260
3081
  if (tokenCount > maxTok) {
2261
- const ctx = { ...state.ctx || {}, __needsSummarization: true };
2262
- state = { ...state, ctx };
2263
- break;
3082
+ const hasFreshSummary = (state.messages || []).some(
3083
+ (m) => m.role === "tool" && m.content === "SUMMARIZED"
3084
+ );
3085
+ if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
3086
+ const ctx = { ...state.ctx || {}, __needsSummarization: true };
3087
+ state = { ...state, ctx };
3088
+ break;
3089
+ }
2264
3090
  }
2265
3091
  }
2266
3092
  onProgress?.({ stage: "agent", message: "Invoking model" });
@@ -2498,7 +3324,11 @@ function createAgent(opts) {
2498
3324
  schema,
2499
3325
  func: async ({ input }) => {
2500
3326
  const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
2501
- return { content: res.content };
3327
+ return {
3328
+ content: res.content,
3329
+ output: res.output,
3330
+ summary: res.state?.summaries?.[res.state.summaries.length - 1]
3331
+ };
2502
3332
  }
2503
3333
  });
2504
3334
  },
@@ -2539,6 +3369,7 @@ function createAgentNode(opts) {
2539
3369
  model: opts.model,
2540
3370
  tools: opts.tools || [],
2541
3371
  systemPrompt: opts.systemPrompt,
3372
+ todoListPrompt: opts.todoListPrompt,
2542
3373
  limits: opts.limits,
2543
3374
  useTodoList: opts.useTodoList,
2544
3375
  outputSchema: opts.outputSchema
@@ -2556,7 +3387,8 @@ function createAgentNode(opts) {
2556
3387
  content: buildSystemPrompt(
2557
3388
  [runtime.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
2558
3389
  runtime.useTodoList === true,
2559
- runtime.name || "Agent"
3390
+ runtime.name || "Agent",
3391
+ runtime.todoListPrompt
2560
3392
  )
2561
3393
  };
2562
3394
  const messages = [systemMsg, ...state.messages];
@@ -2638,9 +3470,119 @@ function createAgentNode(opts) {
2638
3470
  };
2639
3471
  }
2640
3472
 
3473
+ // src/smart/contextPolicy.ts
3474
+ function messageText(message) {
3475
+ if (typeof message.content === "string") return message.content;
3476
+ if (Array.isArray(message.content)) {
3477
+ return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
3478
+ }
3479
+ return "";
3480
+ }
3481
+ function collectRecentTurns(messages, lastTurnsToKeep) {
3482
+ if (lastTurnsToKeep <= 0) return [];
3483
+ const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
3484
+ const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
3485
+ let userTurnCount = 0;
3486
+ const collected = [];
3487
+ for (let index = body.length - 1; index >= 0; index -= 1) {
3488
+ const message = body[index];
3489
+ collected.unshift(message);
3490
+ if (message.role === "user") {
3491
+ userTurnCount += 1;
3492
+ if (userTurnCount >= lastTurnsToKeep) break;
3493
+ }
3494
+ }
3495
+ return [...systemPrefix, ...collected];
3496
+ }
3497
+ function renderStructuredSummary(summary) {
3498
+ if (!summary) return "";
3499
+ const lines = ["Context summary:"];
3500
+ if (summary.stable_facts.length > 0) {
3501
+ lines.push("Stable facts:");
3502
+ lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
3503
+ }
3504
+ if (summary.active_goals.length > 0) {
3505
+ lines.push("Active goals:");
3506
+ lines.push(...summary.active_goals.map((goal) => `- ${goal}`));
3507
+ }
3508
+ if (summary.open_questions.length > 0) {
3509
+ lines.push("Open questions:");
3510
+ lines.push(...summary.open_questions.map((question) => `- ${question}`));
3511
+ }
3512
+ if (summary.discarded_obsolete.length > 0) {
3513
+ lines.push("Discarded obsolete:");
3514
+ lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
3515
+ }
3516
+ return lines.join("\n");
3517
+ }
3518
+ function renderMemoryBlock(facts) {
3519
+ if (!facts || facts.length === 0) return "";
3520
+ return [
3521
+ "Retrieved memory:",
3522
+ ...facts.map((fact) => `- ${fact.key}: ${fact.value}`)
3523
+ ].join("\n");
3524
+ }
3525
+ function clampToBudget(messages, maxContextTokens) {
3526
+ let working = [...messages];
3527
+ while (working.length > 2) {
3528
+ const tokenCount = countApproxTokens(working.map(messageText).join("\n"));
3529
+ if (tokenCount <= maxContextTokens) return working;
3530
+ const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
3531
+ if (firstNonSystem < 0) break;
3532
+ working.splice(firstNonSystem, 1);
3533
+ }
3534
+ return working;
3535
+ }
3536
+ function buildModelMessages(state, config) {
3537
+ const rawMessages = state.messages || [];
3538
+ if (config.context.policy === "raw") {
3539
+ return clampToBudget(rawMessages, config.limits.maxContextTokens);
3540
+ }
3541
+ const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
3542
+ const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
3543
+ const summaryText = renderStructuredSummary(latestSummary);
3544
+ const memoryText = renderMemoryBlock(state.memoryFacts);
3545
+ const systemMessage2 = recentMessages[0]?.role === "system" ? recentMessages[0] : void 0;
3546
+ const body = systemMessage2 ? recentMessages.slice(1) : recentMessages;
3547
+ const syntheticContextMessages = [];
3548
+ if (summaryText) {
3549
+ syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
3550
+ }
3551
+ if (memoryText) {
3552
+ syntheticContextMessages.push({ role: "system", name: "memory_context", content: memoryText });
3553
+ }
3554
+ const assembled = [
3555
+ ...systemMessage2 ? [systemMessage2] : [],
3556
+ ...syntheticContextMessages,
3557
+ ...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
3558
+ ];
3559
+ return clampToBudget(assembled, config.limits.maxContextTokens);
3560
+ }
3561
+ function estimateContextRotScore(state) {
3562
+ const summaryCount = state.summaryRecords?.length || 0;
3563
+ const archivedCount = state.toolHistoryArchived?.length || 0;
3564
+ const messageCount = state.messages?.length || 0;
3565
+ const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
3566
+ const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
3567
+ const denominator = Math.max(messageCount, 1);
3568
+ return Number((numerator / denominator).toFixed(3));
3569
+ }
3570
+
2641
3571
  // src/nodes/contextSummarize.ts
2642
3572
  var systemMessage = (content) => ({ role: "system", content });
2643
3573
  var humanMessage = (content) => ({ role: "user", content });
3574
+ function isSyntheticSummaryToolMessage(message) {
3575
+ return message.role === "tool" && message.name === "summarize_context";
3576
+ }
3577
+ function isSyntheticSummaryAssistantMessage(message) {
3578
+ if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
3579
+ return false;
3580
+ }
3581
+ return message.tool_calls.some((toolCall) => {
3582
+ const toolName = toolCall?.function?.name || toolCall?.name;
3583
+ return toolName === "summarize_context";
3584
+ });
3585
+ }
2644
3586
  function validateMessageSequence(messages) {
2645
3587
  const validated = [];
2646
3588
  const pendingToolCallIds = /* @__PURE__ */ new Set();
@@ -2666,33 +3608,137 @@ function validateMessageSequence(messages) {
2666
3608
  }
2667
3609
  return validated;
2668
3610
  }
2669
- function getSummarizationConfig(opts) {
2670
- if (typeof opts.summarization === "object") {
2671
- return {
2672
- enabled: opts.summarization.enable !== false,
2673
- maxTokens: opts.summarization.maxTokens ?? 5e4,
2674
- // Max tokens we can send to the model for summarization prompt itself
2675
- // This should be well under the model's context limit
2676
- summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3,
2677
- promptTemplate: opts.summarization.promptTemplate
2678
- };
3611
+ function extractJsonObject(text) {
3612
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
3613
+ if (fenced?.[1]) return fenced[1].trim();
3614
+ const start = text.indexOf("{");
3615
+ const end = text.lastIndexOf("}");
3616
+ if (start >= 0 && end > start) {
3617
+ return text.slice(start, end + 1);
3618
+ }
3619
+ return null;
3620
+ }
3621
+ function normalizeStructuredSummary(input, fallbackText) {
3622
+ const summary = {
3623
+ stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
3624
+ key: String(fact.key || "unknown"),
3625
+ value: String(fact.value || ""),
3626
+ confidence: typeof fact.confidence === "number" ? fact.confidence : 0.7,
3627
+ source: fact.source ? String(fact.source) : void 0
3628
+ })) : [],
3629
+ active_goals: Array.isArray(input?.active_goals) ? input.active_goals.map((goal) => String(goal)) : [],
3630
+ open_questions: Array.isArray(input?.open_questions) ? input.open_questions.map((question) => String(question)) : [],
3631
+ discarded_obsolete: Array.isArray(input?.discarded_obsolete || input?.obsolete_discarded) ? (input.discarded_obsolete || input.obsolete_discarded).map((item) => String(item)) : [],
3632
+ rawSummary: typeof input?.rawSummary === "string" ? input.rawSummary : fallbackText
3633
+ };
3634
+ if (summary.stable_facts.length === 0 && fallbackText) {
3635
+ const importantLines = fallbackText.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 8);
3636
+ summary.stable_facts = importantLines.map((line, index) => ({
3637
+ key: `fact_${index + 1}`,
3638
+ value: line,
3639
+ confidence: 0.5
3640
+ }));
3641
+ }
3642
+ return summary;
3643
+ }
3644
+ function normalizeFactKeySegment(value) {
3645
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "item";
3646
+ }
3647
+ function extractCanonicalFacts(messages) {
3648
+ const facts = /* @__PURE__ */ new Map();
3649
+ for (const message of messages) {
3650
+ const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part?.text ?? part?.content ?? String(part ?? "")).join("\n") : "";
3651
+ if (!content) continue;
3652
+ const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
3653
+ for (const line of lines) {
3654
+ if (!/^[A-Z0-9_]+\|/.test(line) || !line.includes("=")) continue;
3655
+ const segments = line.split("|").map((segment) => segment.trim()).filter(Boolean);
3656
+ const label = segments[0];
3657
+ const entries = segments.slice(1).map((segment) => {
3658
+ const eqIndex = segment.indexOf("=");
3659
+ if (eqIndex <= 0) return null;
3660
+ return {
3661
+ key: segment.slice(0, eqIndex).trim(),
3662
+ value: segment.slice(eqIndex + 1).trim()
3663
+ };
3664
+ }).filter((entry) => Boolean(entry?.key));
3665
+ if (entries.length === 0) continue;
3666
+ const identifier = entries.find((entry) => ["code", "id", "project", "name", "key"].includes(entry.key.toLowerCase()))?.value;
3667
+ const keyPrefix = [normalizeFactKeySegment(label), identifier ? normalizeFactKeySegment(identifier) : void 0].filter(Boolean).join(".");
3668
+ for (const entry of entries) {
3669
+ const normalizedEntryKey = normalizeFactKeySegment(entry.key);
3670
+ const factKey = keyPrefix ? `${keyPrefix}.${normalizedEntryKey}` : `${normalizeFactKeySegment(label)}.${normalizedEntryKey}`;
3671
+ facts.set(factKey, {
3672
+ key: factKey,
3673
+ value: entry.value,
3674
+ confidence: 0.98,
3675
+ source: "canonical_tool_output"
3676
+ });
3677
+ }
3678
+ }
3679
+ }
3680
+ return [...facts.values()];
3681
+ }
3682
+ function mergeStableFacts(summary, additionalFacts) {
3683
+ if (additionalFacts.length === 0) return summary;
3684
+ const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
3685
+ for (const fact of additionalFacts) {
3686
+ const existing = merged.get(fact.key);
3687
+ if (!existing || (fact.confidence || 0) >= (existing.confidence || 0)) {
3688
+ merged.set(fact.key, fact);
3689
+ }
2679
3690
  }
2680
3691
  return {
2681
- enabled: opts.summarization !== false,
2682
- maxTokens: 5e4,
2683
- summaryPromptMaxTokens: 8e3,
2684
- promptTemplate: void 0
3692
+ ...summary,
3693
+ stable_facts: [...merged.values()]
2685
3694
  };
2686
3695
  }
3696
+ function runIntegrityCheck(previous, current) {
3697
+ const notes = [];
3698
+ const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
3699
+ const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
3700
+ const obsoleteKeys = new Set(current.discarded_obsolete);
3701
+ const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key));
3702
+ const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
3703
+ if (criticalFactLoss) {
3704
+ notes.push("Missing previously retained facts; merged forward during integrity repair.");
3705
+ }
3706
+ if (obsoleteFactRevived) {
3707
+ notes.push("Obsolete facts reappeared; removed during integrity repair.");
3708
+ }
3709
+ return {
3710
+ passed: !criticalFactLoss && !obsoleteFactRevived,
3711
+ criticalFactLoss,
3712
+ obsoleteFactRevived,
3713
+ notes
3714
+ };
3715
+ }
3716
+ function repairStructuredSummary(previous, current, integrity) {
3717
+ if (!previous) return current;
3718
+ const nextFacts = [...current.stable_facts];
3719
+ if (integrity.criticalFactLoss) {
3720
+ const existingKeys = new Set(nextFacts.map((fact) => fact.key));
3721
+ for (const fact of previous.stable_facts) {
3722
+ if (!existingKeys.has(fact.key) && !current.discarded_obsolete.includes(fact.key)) {
3723
+ nextFacts.push(fact);
3724
+ }
3725
+ }
3726
+ }
3727
+ const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
3728
+ return { ...current, stable_facts: filteredFacts };
3729
+ }
2687
3730
  function createContextSummarizeNode(opts) {
2688
3731
  return async (state) => {
2689
- const config = getSummarizationConfig(opts);
2690
- if (!config.enabled) return {};
3732
+ const resolved = getResolvedSmartConfig(opts, state.agent);
3733
+ const config = resolved.summarization;
3734
+ if (!config.enable) return {};
2691
3735
  const model = opts.model;
2692
3736
  const messages = state.messages || [];
2693
3737
  const onEvent = state.ctx?.__onEvent;
2694
3738
  const traceSession = state.ctx?.__traceSession;
2695
- const compressableMessages = messages.filter((m) => m.role === "tool" && m.content !== "SUMMARIZED");
3739
+ const compressableMessages = messages.filter(
3740
+ (m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
3741
+ );
2696
3742
  if (compressableMessages.length === 0) {
2697
3743
  return {};
2698
3744
  }
@@ -2704,6 +3750,9 @@ function createContextSummarizeNode(opts) {
2704
3750
  let accumulatedTokens = 0;
2705
3751
  for (let i = messages.length - 1; i >= 0; i--) {
2706
3752
  const m = messages[i];
3753
+ if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
3754
+ continue;
3755
+ }
2707
3756
  let content = "";
2708
3757
  if (typeof m.content === "string") content = m.content;
2709
3758
  else if (Array.isArray(m.content)) {
@@ -2715,39 +3764,60 @@ function createContextSummarizeNode(opts) {
2715
3764
  }
2716
3765
  let prefix = `${m.role.toUpperCase()}`;
2717
3766
  if (m.name) prefix += ` (${m.name})`;
2718
- const messageText = `${prefix}: ${content}`;
2719
- const messageTokens = countApproxTokens(messageText);
3767
+ const messageText2 = `${prefix}: ${content}`;
3768
+ const messageTokens = countApproxTokens(messageText2);
2720
3769
  if (accumulatedTokens + messageTokens > availableTokensForConversation) {
2721
3770
  messageTexts.unshift("[Earlier conversation history omitted for brevity]");
2722
3771
  break;
2723
3772
  }
2724
- messageTexts.unshift(messageText);
3773
+ messageTexts.unshift(messageText2);
2725
3774
  accumulatedTokens += messageTokens;
2726
3775
  }
2727
3776
  const conversationText = messageTexts.join("\n\n");
3777
+ const canonicalFacts = extractCanonicalFacts(compressableMessages);
2728
3778
  const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
2729
- const defaultPrompt = `Please summarize the following conversation history and update any previous summary.
2730
- Focus on:
2731
- - User goals and intent.
2732
- - Key decisions made and actions taken.
2733
- - Important tool outputs and data retrieved.
2734
- - Current state of the task.
3779
+ const previousStructuredSummary = Array.isArray(state.summaryRecords) && state.summaryRecords.length > 0 ? state.summaryRecords[state.summaryRecords.length - 1] : void 0;
3780
+ const defaultPrompt = `Summarize the conversation into strict JSON.
3781
+
3782
+ Return exactly one JSON object with this schema:
3783
+ {
3784
+ "stable_facts": [{ "key": string, "value": string, "confidence": number }],
3785
+ "active_goals": [string],
3786
+ "open_questions": [string],
3787
+ "discarded_obsolete": [string],
3788
+ "rawSummary": string
3789
+ }
3790
+
3791
+ Rules:
3792
+ - Keep stable_facts only for facts that future turns must remember.
3793
+ - Put invalidated or superseded fact keys in discarded_obsolete.
3794
+ - Preserve active_goals still relevant to the user.
3795
+ - Preserve unresolved questions.
3796
+ - Do not include prose outside JSON.
3797
+ - Target compression ratio: ${config.summaryCompressionRatioTarget}.
3798
+ - Summary mode: ${config.summaryMode}.
2735
3799
 
2736
- Previous summary (if any):
3800
+ Previous summary text:
2737
3801
  ${previousSummary || "(none)"}
2738
3802
 
3803
+ Previous structured summary:
3804
+ ${previousStructuredSummary ? JSON.stringify(previousStructuredSummary) : "(none)"}
3805
+
2739
3806
  Conversation:
2740
3807
  ${conversationText}
2741
3808
 
2742
- Summary:`;
3809
+ Canonical facts extracted from tool outputs:
3810
+ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fact.value}`).join("\n") : "(none)"}`;
2743
3811
  const template = config.promptTemplate || defaultPrompt;
2744
3812
  const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
2745
3813
  const summaryPrompt = [
2746
- systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
3814
+ systemMessage("You are a helpful assistant that summarizes conversation history efficiently. Return strict JSON only."),
2747
3815
  humanMessage(promptBody)
2748
3816
  ];
2749
3817
  const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
2750
3818
  let summaryText = "Summary unavailable.";
3819
+ let structuredSummary;
3820
+ let integrity;
2751
3821
  let outputTokensEstimate = 0;
2752
3822
  let durationMs = 0;
2753
3823
  let inputTokensActual;
@@ -2756,10 +3826,23 @@ Summary:`;
2756
3826
  let totalTokensActual;
2757
3827
  const startTime = Date.now();
2758
3828
  try {
3829
+ if (!model || typeof model.invoke !== "function") {
3830
+ throw new Error("Summarization model is unavailable.");
3831
+ }
2759
3832
  const response = await model.invoke(summaryPrompt);
2760
3833
  durationMs = Date.now() - startTime;
2761
3834
  summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2762
3835
  outputTokensEstimate = countApproxTokens(summaryText);
3836
+ const jsonText = extractJsonObject(summaryText);
3837
+ const parsed = jsonText ? JSON.parse(jsonText) : void 0;
3838
+ structuredSummary = normalizeStructuredSummary(parsed, summaryText);
3839
+ structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
3840
+ integrity = config.integrityCheck ? runIntegrityCheck(previousStructuredSummary, structuredSummary) : { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: [] };
3841
+ if (integrity && !integrity.passed) {
3842
+ structuredSummary = repairStructuredSummary(previousStructuredSummary, structuredSummary, integrity);
3843
+ integrity = { ...integrity, passed: true };
3844
+ }
3845
+ summaryText = renderStructuredSummary(structuredSummary);
2763
3846
  const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2764
3847
  const normalized = normalizeUsage(rawUsage);
2765
3848
  if (normalized) {
@@ -2770,17 +3853,22 @@ Summary:`;
2770
3853
  }
2771
3854
  } catch (err) {
2772
3855
  durationMs = Date.now() - startTime;
2773
- console.error("[ContextSummarize] Failed to generate summary:", err);
3856
+ structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
3857
+ structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
3858
+ integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
3859
+ summaryText = renderStructuredSummary(structuredSummary);
2774
3860
  const errorEvent = {
2775
3861
  type: "summarization",
2776
- summary: "",
2777
- messagesCompressed: 0,
3862
+ summary: summaryText,
3863
+ messagesCompressed: compressableMessages.length,
2778
3864
  inputTokens: inputTokensEstimate,
2779
- outputTokens: 0,
3865
+ outputTokens: countApproxTokens(summaryText),
2780
3866
  durationMs,
2781
3867
  previousSummary: previousSummary || void 0,
2782
3868
  tokenCountBefore,
2783
- tokenCountAfter: tokenCountBefore
3869
+ tokenCountAfter: tokenCountBefore,
3870
+ structuredSummary,
3871
+ integrity
2784
3872
  };
2785
3873
  onEvent?.(errorEvent);
2786
3874
  recordTraceEvent(traceSession, {
@@ -2790,11 +3878,10 @@ Summary:`;
2790
3878
  status: "error",
2791
3879
  durationMs,
2792
3880
  inputTokens: inputTokensEstimate,
2793
- outputTokens: 0,
3881
+ outputTokens: countApproxTokens(summaryText),
2794
3882
  error: { message: err?.message || String(err), stack: err?.stack },
2795
3883
  messageList: sanitizeTracePayload(summaryPrompt)
2796
3884
  });
2797
- return {};
2798
3885
  }
2799
3886
  const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
2800
3887
  messages.forEach((m, idx) => {
@@ -2808,6 +3895,9 @@ Summary:`;
2808
3895
  });
2809
3896
  const newMessages = messages.map((m) => {
2810
3897
  if (m.role === "tool") {
3898
+ if (isSyntheticSummaryToolMessage(m)) {
3899
+ return m;
3900
+ }
2811
3901
  const toolCallId = m.tool_call_id;
2812
3902
  if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
2813
3903
  return null;
@@ -2845,6 +3935,7 @@ Summary:`;
2845
3935
  content: summaryText
2846
3936
  };
2847
3937
  const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
3938
+ const summaryRecords = Array.isArray(state.summaryRecords) ? [...state.summaryRecords, { ...structuredSummary || normalizeStructuredSummary(void 0, summaryText), integrity, createdAt: (/* @__PURE__ */ new Date()).toISOString() }] : [{ ...structuredSummary || normalizeStructuredSummary(void 0, summaryText), integrity, createdAt: (/* @__PURE__ */ new Date()).toISOString() }];
2848
3939
  const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
2849
3940
  const allTextAfter = finalMessages.map((m) => typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? "").join("") : "").join("\n");
2850
3941
  const tokenCountAfter = countApproxTokens(allTextAfter);
@@ -2860,8 +3951,10 @@ Summary:`;
2860
3951
  previousSummary: previousSummary || void 0,
2861
3952
  tokenCountBefore,
2862
3953
  tokenCountAfter,
2863
- archivedCount: compressableMessages.length
3954
+ archivedCount: compressableMessages.length,
2864
3955
  // deprecated field for backward compatibility
3956
+ structuredSummary,
3957
+ integrity
2865
3958
  };
2866
3959
  onEvent?.(summarizationEvent);
2867
3960
  recordTraceEvent(traceSession, {
@@ -2885,12 +3978,19 @@ Summary:`;
2885
3978
  tokensSaved: tokenCountBefore - tokenCountAfter,
2886
3979
  previousSummaryLength: previousSummary?.length || 0,
2887
3980
  usageFromModel: inputTokensActual !== void 0,
2888
- cachedInputTokens
3981
+ cachedInputTokens,
3982
+ integrity
2889
3983
  }
2890
3984
  });
2891
3985
  return {
2892
3986
  messages: finalMessages,
2893
- summaries
3987
+ summaries,
3988
+ summaryRecords,
3989
+ watchdog: {
3990
+ ...state.watchdog || {},
3991
+ compactions: (state.watchdog?.compactions || 0) + 1,
3992
+ lastAction: "summarized"
3993
+ }
2894
3994
  };
2895
3995
  };
2896
3996
  }
@@ -2913,38 +4013,200 @@ async function applyTokenLimits({
2913
4013
  }
2914
4014
  return { ...state, messages: newMessages };
2915
4015
  }
4016
+ var todoStatusSchema = zod.z.enum(["not-started", "in-progress", "completed", "blocked"]);
4017
+ var todoWriteItemSchema = zod.z.object({
4018
+ id: zod.z.number().int().min(1).describe("Sequential id starting from 1"),
4019
+ step: zod.z.string().min(1).optional(),
4020
+ title: zod.z.string().min(1).optional(),
4021
+ description: zod.z.string().min(1).optional(),
4022
+ owner: zod.z.string().min(1).optional(),
4023
+ exitCriteria: zod.z.string().min(1).optional(),
4024
+ status: todoStatusSchema,
4025
+ evidence: zod.z.string().max(300).optional()
4026
+ });
4027
+ var todoUpdateItemSchema = zod.z.object({
4028
+ id: zod.z.number().int().min(1).describe("Existing todo id to update"),
4029
+ step: zod.z.string().min(1).optional(),
4030
+ title: zod.z.string().min(1).optional(),
4031
+ description: zod.z.string().min(1).optional(),
4032
+ owner: zod.z.string().min(1).optional(),
4033
+ exitCriteria: zod.z.string().min(1).optional(),
4034
+ status: todoStatusSchema.optional(),
4035
+ evidence: zod.z.string().max(300).optional()
4036
+ });
4037
+ function normalizeTodoItem(item, existing) {
4038
+ const id = item.id ?? existing?.id;
4039
+ const step = item.step || item.title || item.description || existing?.step || existing?.title || existing?.description || `Step ${id}`;
4040
+ const title = item.title || item.step || existing?.title || existing?.step || `Step ${id}`;
4041
+ const description = item.description || item.step || item.title || existing?.description || existing?.step || existing?.title || `Step ${id}`;
4042
+ return {
4043
+ ...existing,
4044
+ ...item,
4045
+ id,
4046
+ step,
4047
+ title,
4048
+ description,
4049
+ owner: item.owner || existing?.owner || "agent",
4050
+ exitCriteria: item.exitCriteria || existing?.exitCriteria || description || `Complete step ${id}`,
4051
+ status: item.status || existing?.status,
4052
+ evidence: item.evidence ?? existing?.evidence
4053
+ };
4054
+ }
4055
+ function validatePlanInvariants(todoList, requireSequentialIds) {
4056
+ const ids = todoList.map((item) => item.id);
4057
+ const uniqueIds = new Set(ids);
4058
+ if (uniqueIds.size !== ids.length) {
4059
+ return { ok: false, error: "Todo ids must be unique." };
4060
+ }
4061
+ if (requireSequentialIds) {
4062
+ const expectedIds = Array.from({ length: todoList.length }, (_, index) => index + 1);
4063
+ const isSequential = ids.every((id, index) => id === expectedIds[index]);
4064
+ if (!isSequential) {
4065
+ return { ok: false, error: "Todo ids must be sequential starting from 1 for write operations." };
4066
+ }
4067
+ }
4068
+ const inProgressCount = todoList.filter((item) => item.status === "in-progress").length;
4069
+ if (inProgressCount > 1) {
4070
+ return { ok: false, error: "Only one todo item may be in-progress at a time." };
4071
+ }
4072
+ return { ok: true };
4073
+ }
4074
+ function calculateAdherenceScore(todoList) {
4075
+ const completedCount = todoList.filter((item) => item.status === "completed").length;
4076
+ return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
4077
+ }
2916
4078
  function createContextTools(stateRef, opts) {
2917
4079
  const tools = [];
2918
4080
  if (opts?.planningEnabled) {
2919
4081
  const manageTodo = createTool({
2920
4082
  name: "manage_todo_list",
2921
- description: "Manage a structured todo list to track progress and plan tasks throughout your coding session. Use this tool VERY frequently to ensure task visibility and proper planning.\n\nWhen to use this tool:\n- Complex multi-step work requiring planning and tracking\n- When user provides multiple tasks or requests (numbered/comma-separated)\n- After receiving new instructions that require multiple steps\n- BEFORE starting work on any todo (mark as in-progress)\n- IMMEDIATELY after completing each todo (mark completed individually)\n- When breaking down larger tasks into smaller actionable steps\n- To give users visibility into your progress and planning\n\nWhen NOT to use:\n- Single, trivial tasks that can be completed in one step\n- Purely conversational/informational requests\n- When just reading files or performing simple searches\n\nCRITICAL workflow:\n1. Plan tasks by writing todo list with specific, actionable items\n2. Complete the work for that specific todo\n3. Mark that todo as completed IMMEDIATELY\n4. Move to next todo and repeat\n\nTodo states:\n- not-started: Todo not yet begun\n- in-progress: Currently working (limit ONE at a time)\n- completed: Finished successfully\n\nIMPORTANT: Mark todos completed as soon as they are done. Do not batch completions.",
4083
+ description: "Manage a structured todo list to track progress and plan tasks throughout your coding session.\n\nOperations:\n- read: return the current plan\n- write: replace the full plan with a complete ordered todoList\n- update: patch existing todo items by id without resending the whole plan\n\nRules:\n- Use write only when creating or fully rewriting the plan\n- After a plan exists, prefer update for status, evidence, or owner changes\n- Update payloads should contain only the changed items\n- When using update, pass expectedVersion to avoid overwriting a newer plan\n- Keep ids unique; write operations must keep ids sequential starting from 1\n- Keep at most ONE item in-progress at a time\n- If update fails due to version mismatch, read the latest plan and retry",
2922
4084
  schema: zod.z.object({
2923
- operation: zod.z.enum(["write", "read"]),
2924
- todoList: zod.z.array(zod.z.object({
2925
- id: zod.z.number().int().min(1).describe("Sequential id starting from 1"),
2926
- title: zod.z.string().min(1),
2927
- description: zod.z.string().min(1),
2928
- status: zod.z.enum(["not-started", "in-progress", "completed"]),
2929
- evidence: zod.z.string().max(200).optional()
2930
- })).optional()
4085
+ operation: zod.z.enum(["write", "read", "update"]),
4086
+ expectedVersion: zod.z.number().int().min(0).optional(),
4087
+ todoList: zod.z.array(zod.z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
2931
4088
  }),
2932
- func: async ({ operation, todoList }) => {
4089
+ func: async ({ operation, expectedVersion, todoList }) => {
2933
4090
  const onEvent = manageTodo._stateRef?.__onEvent;
4091
+ const currentVersion = stateRef.planVersion || 0;
2934
4092
  if (operation === "read") {
2935
4093
  const list = stateRef.todoList || [];
2936
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list });
4094
+ onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
2937
4095
  return list;
2938
4096
  }
2939
- if (Array.isArray(todoList)) {
2940
- stateRef.todoList = todoList;
4097
+ if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
4098
+ return {
4099
+ status: "error",
4100
+ operation,
4101
+ error: "Plan version mismatch. Read the latest plan and retry.",
4102
+ version: currentVersion,
4103
+ adherenceScore: stateRef.adherenceScore || 0
4104
+ };
4105
+ }
4106
+ if (!Array.isArray(todoList)) {
4107
+ return {
4108
+ status: "error",
4109
+ operation,
4110
+ error: "todoList is required for write and update operations.",
4111
+ version: currentVersion,
4112
+ adherenceScore: stateRef.adherenceScore || 0
4113
+ };
2941
4114
  }
4115
+ if (operation === "write") {
4116
+ const parsed = zod.z.array(todoWriteItemSchema).safeParse(todoList);
4117
+ if (!parsed.success) {
4118
+ return {
4119
+ status: "error",
4120
+ operation,
4121
+ error: parsed.error.issues[0]?.message || "Invalid todoList for write.",
4122
+ version: currentVersion,
4123
+ adherenceScore: stateRef.adherenceScore || 0
4124
+ };
4125
+ }
4126
+ const normalizedList = parsed.data.map((item) => normalizeTodoItem(item));
4127
+ const validation = validatePlanInvariants(normalizedList, true);
4128
+ if (!validation.ok) {
4129
+ return {
4130
+ status: "error",
4131
+ operation,
4132
+ error: validation.error,
4133
+ version: currentVersion,
4134
+ adherenceScore: stateRef.adherenceScore || 0
4135
+ };
4136
+ }
4137
+ stateRef.todoList = normalizedList;
4138
+ } else if (operation === "update") {
4139
+ const parsed = zod.z.array(todoUpdateItemSchema).safeParse(todoList);
4140
+ if (!parsed.success) {
4141
+ return {
4142
+ status: "error",
4143
+ operation,
4144
+ error: parsed.error.issues[0]?.message || "Invalid todoList for update.",
4145
+ version: currentVersion,
4146
+ adherenceScore: stateRef.adherenceScore || 0
4147
+ };
4148
+ }
4149
+ const currentList = Array.isArray(stateRef.todoList) ? stateRef.todoList : [];
4150
+ if (currentList.length === 0) {
4151
+ return {
4152
+ status: "error",
4153
+ operation,
4154
+ error: "No existing plan to update. Use write first.",
4155
+ version: currentVersion,
4156
+ adherenceScore: stateRef.adherenceScore || 0
4157
+ };
4158
+ }
4159
+ const patchIds = /* @__PURE__ */ new Set();
4160
+ for (const item of parsed.data) {
4161
+ if (patchIds.has(item.id)) {
4162
+ return {
4163
+ status: "error",
4164
+ operation,
4165
+ error: "Update payload contains duplicate todo ids.",
4166
+ version: currentVersion,
4167
+ adherenceScore: stateRef.adherenceScore || 0
4168
+ };
4169
+ }
4170
+ patchIds.add(item.id);
4171
+ }
4172
+ const currentMap = new Map(currentList.map((item) => [item.id, item]));
4173
+ for (const item of parsed.data) {
4174
+ if (!currentMap.has(item.id)) {
4175
+ return {
4176
+ status: "error",
4177
+ operation,
4178
+ error: `Cannot update missing todo id ${item.id}. Use write to replace the plan.`,
4179
+ version: currentVersion,
4180
+ adherenceScore: stateRef.adherenceScore || 0
4181
+ };
4182
+ }
4183
+ }
4184
+ const mergedList = currentList.map((item) => {
4185
+ const patch = parsed.data.find((candidate) => candidate.id === item.id);
4186
+ return patch ? normalizeTodoItem(patch, item) : item;
4187
+ });
4188
+ const validation = validatePlanInvariants(mergedList, false);
4189
+ if (!validation.ok) {
4190
+ return {
4191
+ status: "error",
4192
+ operation,
4193
+ error: validation.error,
4194
+ version: currentVersion,
4195
+ adherenceScore: stateRef.adherenceScore || 0
4196
+ };
4197
+ }
4198
+ stateRef.todoList = mergedList;
4199
+ }
4200
+ stateRef.planVersion = currentVersion + 1;
4201
+ stateRef.adherenceScore = calculateAdherenceScore(stateRef.todoList || []);
2942
4202
  const payload = {
2943
4203
  status: "ok",
2944
4204
  operation,
2945
- count: Array.isArray(todoList) ? todoList.length : void 0
4205
+ count: Array.isArray(todoList) ? todoList.length : void 0,
4206
+ version: stateRef.planVersion || 1,
4207
+ adherenceScore: stateRef.adherenceScore || 0
2946
4208
  };
2947
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "write", todoList });
4209
+ onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
2948
4210
  return payload;
2949
4211
  }
2950
4212
  });
@@ -2953,12 +4215,13 @@ function createContextTools(stateRef, opts) {
2953
4215
  }
2954
4216
  const getTool = createTool({
2955
4217
  name: "get_tool_response",
2956
- description: "RETRIEVE tool execution response: Use this to access the full output of a tool execution that shows as 'SUMMARIZED' in the tool history.",
4218
+ description: "RETRIEVE tool execution response: Use this to access the full output of a tool execution that shows as 'SUMMARIZED' in the tool history. Accepts either the internal executionId or the provider tool call id shown in prior tool traces.",
2957
4219
  schema: zod.z.object({ executionId: zod.z.string().describe("Tool execution id") }),
2958
4220
  func: async ({ executionId }) => {
2959
- let execution = stateRef.toolHistory?.find((t) => t.executionId === executionId);
4221
+ const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
4222
+ let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));
2960
4223
  if (!execution) {
2961
- execution = stateRef.toolHistoryArchived?.find((t) => t.executionId === executionId);
4224
+ execution = stateRef.toolHistoryArchived?.find((t) => matchesExecution(t));
2962
4225
  }
2963
4226
  if (execution) {
2964
4227
  return execution.rawOutput || execution.output;
@@ -2989,19 +4252,33 @@ function createContextTools(stateRef, opts) {
2989
4252
  }
2990
4253
 
2991
4254
  // src/graph/decisions.ts
4255
+ function isSyntheticSummaryMessage2(message) {
4256
+ if (!message) return false;
4257
+ if (message.role === "tool" && message.name === "summarize_context") {
4258
+ return true;
4259
+ }
4260
+ if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
4261
+ return message.tool_calls.some((toolCall) => {
4262
+ const toolName = toolCall?.function?.name || toolCall?.name;
4263
+ return toolName === "summarize_context";
4264
+ });
4265
+ }
4266
+ return false;
4267
+ }
2992
4268
  function needsSummarization(state, opts, summarizationEnabled) {
2993
4269
  if (!summarizationEnabled) {
2994
4270
  return false;
2995
4271
  }
2996
- let maxTok;
2997
- if (typeof opts.summarization === "object" && typeof opts.summarization.maxTokens === "number") {
2998
- maxTok = opts.summarization.maxTokens;
4272
+ if (state.ctx?.__needsSummarization) {
4273
+ return true;
2999
4274
  }
3000
- if (maxTok === void 0) {
3001
- maxTok = 5e4;
4275
+ const resolved = getResolvedSmartConfig(opts, state.agent);
4276
+ if (!resolved.summarization.enable) {
4277
+ return false;
3002
4278
  }
4279
+ const maxTok = resolved.summarization.summaryTriggerTokens;
3003
4280
  try {
3004
- const allText = (state.messages || []).map((m) => {
4281
+ const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
3005
4282
  if (typeof m.content === "string") return m.content;
3006
4283
  if (Array.isArray(m.content)) {
3007
4284
  return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
@@ -3012,7 +4289,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
3012
4289
  return "";
3013
4290
  }).join("\n");
3014
4291
  const tokenCount = countApproxTokens(allText);
3015
- const needsSum = tokenCount > maxTok;
4292
+ const contextRotScore = state.watchdog?.contextRotScore || 0;
4293
+ const needsSum = tokenCount > maxTok || resolved.watchdog.enabled && resolved.watchdog.autoCompaction && contextRotScore >= resolved.watchdog.contextRotThreshold;
3016
4294
  if (process.env.DEBUG_SUMMARIZATION) {
3017
4295
  const msgCount = (state.messages || []).length;
3018
4296
  const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
@@ -3022,10 +4300,10 @@ function needsSummarization(state, opts, summarizationEnabled) {
3022
4300
  });
3023
4301
  console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
3024
4302
  toolDetails.forEach((d) => console.log(` ${d}`));
3025
- console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
4303
+ console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Context rot: ${contextRotScore}, Needs summarization: ${needsSum}`);
3026
4304
  }
3027
4305
  return needsSum;
3028
- } catch (e) {
4306
+ } catch {
3029
4307
  return false;
3030
4308
  }
3031
4309
  }
@@ -3043,33 +4321,205 @@ function toolsDecisionFactory(opts, summarizationEnabled) {
3043
4321
  };
3044
4322
  }
3045
4323
 
4324
+ // src/smart/memory.ts
4325
+ function normalizeFact(scope, fact) {
4326
+ return {
4327
+ ...fact,
4328
+ scope,
4329
+ obsolete: fact.obsolete ?? false,
4330
+ lastUpdatedAt: fact.lastUpdatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4331
+ confidence: Number.isFinite(fact.confidence) ? fact.confidence : 0.5
4332
+ };
4333
+ }
4334
+ var InMemoryMemoryStore = class {
4335
+ constructor() {
4336
+ this.buckets = /* @__PURE__ */ new Map();
4337
+ }
4338
+ async get(scope, options) {
4339
+ const includeObsolete = options?.includeObsolete === true;
4340
+ const all = [...this.buckets.get(scope) || []].filter((fact) => includeObsolete || !fact.obsolete).sort((left, right) => new Date(right.lastUpdatedAt || 0).getTime() - new Date(left.lastUpdatedAt || 0).getTime());
4341
+ return typeof options?.limit === "number" ? all.slice(0, options.limit) : all;
4342
+ }
4343
+ async upsert(scope, facts) {
4344
+ const existing = [...this.buckets.get(scope) || []];
4345
+ for (const fact of facts) {
4346
+ const normalized = normalizeFact(scope, fact);
4347
+ const sameKey = existing.filter((entry) => entry.key === normalized.key && !entry.obsolete);
4348
+ for (const entry of sameKey) {
4349
+ if (entry.value !== normalized.value) {
4350
+ entry.obsolete = true;
4351
+ entry.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
4352
+ }
4353
+ }
4354
+ const match = existing.find((entry) => entry.key === normalized.key && entry.value === normalized.value && !entry.obsolete);
4355
+ if (match) {
4356
+ match.confidence = Math.max(match.confidence, normalized.confidence);
4357
+ match.sourceTurn = normalized.sourceTurn;
4358
+ match.ttl = normalized.ttl;
4359
+ match.tags = normalized.tags;
4360
+ match.lastUpdatedAt = normalized.lastUpdatedAt;
4361
+ } else {
4362
+ existing.push(normalized);
4363
+ }
4364
+ }
4365
+ this.buckets.set(scope, existing);
4366
+ }
4367
+ async markObsolete(scope, keys) {
4368
+ const bucket = this.buckets.get(scope) || [];
4369
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4370
+ for (const fact of bucket) {
4371
+ if (keys.includes(fact.key)) {
4372
+ fact.obsolete = true;
4373
+ fact.lastUpdatedAt = now;
4374
+ }
4375
+ }
4376
+ this.buckets.set(scope, bucket);
4377
+ }
4378
+ async semanticSearch(scope, query, options) {
4379
+ const normalizedQuery = query.toLowerCase();
4380
+ const bucket = (this.buckets.get(scope) || []).filter((fact) => !fact.obsolete);
4381
+ const scored = bucket.map((fact) => {
4382
+ const haystack = `${fact.key} ${fact.value} ${(fact.tags || []).join(" ")}`.toLowerCase();
4383
+ const score = haystack.includes(normalizedQuery) ? 2 : normalizedQuery.split(/\s+/).filter(Boolean).reduce((acc, token) => acc + (haystack.includes(token) ? 1 : 0), 0);
4384
+ return { fact, score };
4385
+ }).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || right.fact.confidence - left.fact.confidence).map((entry) => entry.fact);
4386
+ return typeof options?.limit === "number" ? scored.slice(0, options.limit) : scored;
4387
+ }
4388
+ };
4389
+ function resolveMemoryStore(config) {
4390
+ if (config.memory.store) return config.memory.store;
4391
+ if (config.memory.provider === "inMemory") return new InMemoryMemoryStore();
4392
+ return void 0;
4393
+ }
4394
+ async function readMemoryFacts(store, config, query) {
4395
+ if (!store) return [];
4396
+ const scope = config.memory.scope;
4397
+ const limit = config.context.lastTurnsToKeep;
4398
+ const policy = config.memory.readPolicy;
4399
+ if (policy === "recent_only") {
4400
+ return store.get(scope, { limit });
4401
+ }
4402
+ if (policy === "semantic" && typeof store.semanticSearch === "function") {
4403
+ return store.semanticSearch(scope, query, { limit });
4404
+ }
4405
+ if (policy === "hybrid") {
4406
+ const recent = await store.get(scope, { limit: Math.max(3, Math.ceil(limit / 2)) });
4407
+ const semantic = typeof store.semanticSearch === "function" ? await store.semanticSearch(scope, query, { limit: Math.max(3, Math.ceil(limit / 2)) }) : [];
4408
+ const deduped = /* @__PURE__ */ new Map();
4409
+ for (const fact of [...semantic, ...recent]) {
4410
+ deduped.set(`${fact.key}:${fact.value}`, fact);
4411
+ }
4412
+ return [...deduped.values()].slice(0, limit);
4413
+ }
4414
+ return store.get(scope, { limit });
4415
+ }
4416
+ async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
4417
+ if (!store || !summary) return [];
4418
+ const facts = summary.stable_facts.map((fact) => ({
4419
+ key: fact.key,
4420
+ value: fact.value,
4421
+ sourceTurn,
4422
+ confidence: fact.confidence ?? 0.8,
4423
+ obsolete: false,
4424
+ tags: ["summary_fact"]
4425
+ }));
4426
+ if (summary.discarded_obsolete.length > 0) {
4427
+ await store.markObsolete(config.memory.scope, summary.discarded_obsolete);
4428
+ }
4429
+ if (facts.length > 0) {
4430
+ await store.upsert(config.memory.scope, facts);
4431
+ }
4432
+ return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
4433
+ }
4434
+ function renderMemoryFacts(facts) {
4435
+ if (facts.length === 0) return "";
4436
+ return [
4437
+ "Memory facts:",
4438
+ ...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
4439
+ ].join("\n");
4440
+ }
4441
+
3046
4442
  // src/smart/index.ts
3047
4443
  function createSmartAgent(opts) {
3048
- const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0 };
3049
- const planningEnabled = opts.useTodoList === true;
4444
+ const resolved = normalizeSmartAgentOptions(opts);
4445
+ const planningEnabled = resolved.planning.mode !== "off";
4446
+ const memoryStore = resolveMemoryStore(resolved);
4447
+ const runtimeOpts = {
4448
+ ...opts,
4449
+ runtimeProfile: resolved.runtimeProfile,
4450
+ limits: resolved.limits,
4451
+ summarization: resolved.summarization,
4452
+ context: resolved.context,
4453
+ planning: resolved.planning,
4454
+ delegation: resolved.delegation,
4455
+ memory: { ...resolved.memory, store: memoryStore },
4456
+ toolResponses: resolved.toolResponses,
4457
+ watchdog: resolved.watchdog,
4458
+ useTodoList: planningEnabled
4459
+ };
4460
+ const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
3050
4461
  const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
3051
4462
  const mergedTools = [...opts.tools ?? [], ...contextTools];
3052
- const base = createAgent({ ...opts, tools: mergedTools });
3053
- let summarizationEnabled = false;
3054
- if (typeof opts.summarization === "object") {
3055
- summarizationEnabled = opts.summarization.enable !== false;
3056
- } else {
3057
- summarizationEnabled = opts.summarization !== false;
3058
- }
3059
- const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
3060
- const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
3061
- const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
4463
+ const base = createAgent({ ...runtimeOpts, tools: mergedTools });
4464
+ base.__runtime.runtimeProfile = resolved.runtimeProfile;
4465
+ base.__runtime.smart = resolved;
4466
+ const summarizationEnabled = resolved.summarization.enable !== false;
4467
+ const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
4468
+ const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
4469
+ const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
3062
4470
  const structuredOutputHint = opts.outputSchema ? [
3063
4471
  "A structured output schema is active.",
3064
4472
  "Do NOT output the final JSON directly as an assistant message.",
3065
4473
  "When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
3066
4474
  "Call it exactly once then STOP producing further assistant messages."
3067
4475
  ].join("\n") : "";
4476
+ const runtimeHint = [
4477
+ resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
4478
+ `Context policy: ${resolved.context.policy}.`,
4479
+ `Planning mode: ${resolved.planning.mode}. Replan policy: ${resolved.planning.replanPolicy}.`,
4480
+ `Delegation mode: ${resolved.delegation.mode}. Child context policy: ${resolved.delegation.childContextPolicy}.`,
4481
+ `Tool response policy: ${resolved.context.toolResponsePolicy}.`
4482
+ ].join("\n");
4483
+ function latestUserPrompt(messages) {
4484
+ const latest = [...messages].reverse().find((message) => message.role === "user");
4485
+ if (!latest) return "";
4486
+ return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
4487
+ }
4488
+ async function syncMemory(state) {
4489
+ const query = latestUserPrompt(state.messages || []);
4490
+ const memoryFacts = await readMemoryFacts(memoryStore, resolved, query);
4491
+ return { ...state, memoryFacts };
4492
+ }
4493
+ async function persistLatestSummary(state) {
4494
+ if (!memoryStore) return state;
4495
+ if (resolved.memory.writePolicy === "manual") return state;
4496
+ const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
4497
+ if (!latestSummary) return state;
4498
+ if (resolved.memory.writePolicy === "auto_important" && latestSummary.stable_facts.length === 0) {
4499
+ return state;
4500
+ }
4501
+ const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
4502
+ return { ...state, memoryFacts };
4503
+ }
4504
+ function syncPlanState(state) {
4505
+ if (!Array.isArray(stateRef.todoList)) return state;
4506
+ return {
4507
+ ...state,
4508
+ plan: {
4509
+ version: stateRef.planVersion || state.planVersion || 1,
4510
+ steps: stateRef.todoList,
4511
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
4512
+ adherenceScore: stateRef.adherenceScore || 0
4513
+ },
4514
+ planVersion: stateRef.planVersion || state.planVersion || 1
4515
+ };
4516
+ }
3068
4517
  function systemMessage2() {
3069
4518
  const sys = buildSystemPrompt(
3070
- [opts.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
3071
- opts.useTodoList === true,
3072
- opts.name || "Agent"
4519
+ [opts.systemPrompt, runtimeHint, structuredOutputHint].filter(Boolean).join("\n"),
4520
+ planningEnabled,
4521
+ opts.name || "Agent",
4522
+ opts.todoListPrompt
3073
4523
  );
3074
4524
  return { role: "system", content: sys };
3075
4525
  }
@@ -3077,38 +4527,81 @@ function createSmartAgent(opts) {
3077
4527
  invoke: async (input, config) => {
3078
4528
  stateRef.toolHistory = input.toolHistory;
3079
4529
  stateRef.toolHistoryArchived = input.toolHistoryArchived;
4530
+ stateRef.todoList = input.plan?.steps;
4531
+ stateRef.planVersion = input.planVersion || input.plan?.version || 0;
4532
+ stateRef.adherenceScore = input.plan?.adherenceScore || 0;
3080
4533
  const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
3081
4534
  const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
3082
- let state = { ...input, messages: seedMessages };
4535
+ let state = await syncMemory({ ...input, messages: seedMessages });
3083
4536
  let lastResult = null;
3084
- const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
4537
+ let rawMessages = [...seedMessages];
4538
+ const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
3085
4539
  const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
4540
+ let previousTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
3086
4541
  for (let i = 0; i < iterationLimit; i++) {
4542
+ state = { ...state, messages: rawMessages };
3087
4543
  const next = summarizationEnabled ? decideBefore(state) : "agent";
3088
4544
  if (next === "contextSummarize" && summarizer) {
3089
4545
  const delta = await summarizer(state);
3090
4546
  if (!delta || Object.keys(delta).length === 0) ; else {
3091
- state = { ...state, ...delta };
4547
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4548
+ rawMessages = [...state.messages || rawMessages];
3092
4549
  continue;
3093
4550
  }
3094
4551
  }
3095
- const res = await base.invoke(state, config);
4552
+ const modelMessages = buildModelMessages(state, resolved);
4553
+ const res = await base.invoke({ ...state, messages: modelMessages }, config);
3096
4554
  lastResult = res;
3097
4555
  const currentSummaries = state.summaries;
4556
+ const currentSummaryRecords = state.summaryRecords;
4557
+ const currentPlan = state.plan;
4558
+ const appendedMessages = res.messages.slice(modelMessages.length);
4559
+ rawMessages = [...rawMessages, ...appendedMessages];
3098
4560
  state = res.state || { ...state, messages: res.messages };
4561
+ state = { ...state, messages: rawMessages };
3099
4562
  if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
3100
4563
  state = { ...state, summaries: currentSummaries };
3101
4564
  }
3102
- if (state.ctx?.__awaitingApproval || state.ctx?.__waitingForInput) break;
4565
+ if (currentSummaryRecords && currentSummaryRecords.length > 0 && (!state.summaryRecords || state.summaryRecords.length === 0)) {
4566
+ state = { ...state, summaryRecords: currentSummaryRecords };
4567
+ }
4568
+ if (currentPlan && !state.plan) {
4569
+ state = { ...state, plan: currentPlan };
4570
+ }
4571
+ state = syncPlanState(await syncMemory(state));
4572
+ stateRef.toolHistory = state.toolHistory;
4573
+ stateRef.toolHistoryArchived = state.toolHistoryArchived;
4574
+ const currentTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
4575
+ const tokenDrift = Math.max(0, currentTokenCount - previousTokenCount);
4576
+ const toolMessagesThisTurn = appendedMessages.filter((message) => message.role === "tool").length;
4577
+ const overToolingRate = appendedMessages.length === 0 ? 0 : Number((toolMessagesThisTurn / appendedMessages.length).toFixed(2));
4578
+ state = {
4579
+ ...state,
4580
+ watchdog: {
4581
+ tokenDrift,
4582
+ contextRotScore: estimateContextRotScore(state),
4583
+ overToolingRate,
4584
+ compactions: state.watchdog?.compactions || 0,
4585
+ lastAction: appendedMessages.length > 0 ? "agent_turn" : state.watchdog?.lastAction
4586
+ }
4587
+ };
4588
+ previousTokenCount = currentTokenCount;
4589
+ if (resolved.watchdog.enabled && resolved.watchdog.autoCompaction) {
4590
+ const shouldCompact = tokenDrift >= resolved.watchdog.tokenDriftThreshold || (state.watchdog?.overToolingRate || 0) >= resolved.watchdog.overToolingSpikeThreshold || (state.watchdog?.contextRotScore || 0) >= resolved.watchdog.contextRotThreshold;
4591
+ if (shouldCompact) {
4592
+ state = { ...state, ctx: { ...state.ctx || {}, __needsSummarization: true } };
4593
+ }
4594
+ }
3103
4595
  if (state.ctx?.__needsSummarization && summarizer) {
3104
4596
  const ctx = { ...state.ctx || {} };
3105
4597
  delete ctx.__needsSummarization;
3106
4598
  state = { ...state, ctx };
3107
4599
  const delta = await summarizer(state);
3108
4600
  if (delta && Object.keys(delta).length > 0) {
3109
- state = { ...state, ...delta };
4601
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4602
+ rawMessages = [...state.messages || rawMessages];
4603
+ continue;
3110
4604
  }
3111
- continue;
3112
4605
  }
3113
4606
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3114
4607
  if (summarizationEnabled) {
@@ -3116,7 +4609,8 @@ function createSmartAgent(opts) {
3116
4609
  if (after === "contextSummarize" && summarizer) {
3117
4610
  const delta = await summarizer(state);
3118
4611
  if (delta && Object.keys(delta).length > 0) {
3119
- state = { ...state, ...delta };
4612
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4613
+ rawMessages = [...state.messages || rawMessages];
3120
4614
  continue;
3121
4615
  }
3122
4616
  }
@@ -3129,10 +4623,12 @@ function createSmartAgent(opts) {
3129
4623
  }
3130
4624
  if (state.summaries && state.summaries.length > 0) {
3131
4625
  if (lastResult.state) {
3132
- lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
4626
+ lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries, summaryRecords: state.summaryRecords, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
3133
4627
  } else {
3134
- lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
4628
+ lastResult = { ...lastResult, state: { ...state, summaries: state.summaries, summaryRecords: state.summaryRecords, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
3135
4629
  }
4630
+ } else if (lastResult.state) {
4631
+ lastResult = { ...lastResult, state: { ...lastResult.state, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
3136
4632
  }
3137
4633
  return lastResult;
3138
4634
  },
@@ -3146,6 +4642,141 @@ function createSmartAgent(opts) {
3146
4642
  return instance;
3147
4643
  }
3148
4644
 
4645
+ // src/smart/eval.ts
4646
+ function normalizeProfileTarget(target) {
4647
+ if (typeof target === "string") {
4648
+ return {
4649
+ label: target,
4650
+ runtimeProfile: target,
4651
+ baseProfile: target === "custom" ? "balanced" : target
4652
+ };
4653
+ }
4654
+ return {
4655
+ label: target.label,
4656
+ runtimeProfile: target.runtimeProfile,
4657
+ baseProfile: target.baseProfile ?? (target.runtimeProfile === "custom" ? target.customProfile?.extends ?? "balanced" : target.runtimeProfile),
4658
+ customProfile: target.customProfile
4659
+ };
4660
+ }
4661
+ function normalizeText(value) {
4662
+ return (value || "").toLowerCase();
4663
+ }
4664
+ function ratio(numerator, denominator) {
4665
+ if (denominator <= 0) return 1;
4666
+ return Number((numerator / denominator).toFixed(3));
4667
+ }
4668
+ function sum(values) {
4669
+ return values.reduce((acc, value) => acc + value, 0);
4670
+ }
4671
+ function mean(values) {
4672
+ if (values.length === 0) return 0;
4673
+ return Number((sum(values) / values.length).toFixed(3));
4674
+ }
4675
+ function extractTotalTokens(usage) {
4676
+ if (!usage?.totals || typeof usage.totals !== "object") return 0;
4677
+ return Object.values(usage.totals).reduce((acc, entry) => acc + (Number(entry?.total) || 0), 0);
4678
+ }
4679
+ function caseResultFromRun(descriptor, testCase, content, events, state, latencyMs, totalTokens) {
4680
+ const normalizedContent = normalizeText(content);
4681
+ const matchedPhrases = (testCase.expectedPhrases || []).filter((phrase) => normalizedContent.includes(normalizeText(phrase)));
4682
+ const matchedFacts = (testCase.expectedFacts || []).filter((fact) => {
4683
+ const memoryMatch = state?.memoryFacts?.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value)));
4684
+ const summaryMatch = state?.summaryRecords?.some((record) => record.stable_facts.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value))));
4685
+ return Boolean(memoryMatch || summaryMatch || normalizedContent.includes(normalizeText(fact.value)));
4686
+ });
4687
+ const forbiddenMisses = (testCase.forbiddenPhrases || []).filter((phrase) => !normalizedContent.includes(normalizeText(phrase)));
4688
+ const toolCalls = events.filter((event) => event.type === "tool_call" && event.phase === "success");
4689
+ const expectedToolHits = (testCase.expectedToolNames || []).filter((toolName) => toolCalls.some((event) => event.name === toolName));
4690
+ const toolPrecision = ratio(expectedToolHits.length, Math.max(toolCalls.length, 1));
4691
+ const recallAccuracy = ratio(matchedPhrases.length + matchedFacts.length, (testCase.expectedPhrases?.length || 0) + (testCase.expectedFacts?.length || 0));
4692
+ const obsoleteDropAccuracy = ratio(forbiddenMisses.length, testCase.forbiddenPhrases?.length || 0);
4693
+ const trajectoryScore = ratio(expectedToolHits.length, testCase.expectedToolNames?.length || 0);
4694
+ const hadRecoverableError = events.some((event) => event.type === "tool_call" && event.phase === "error");
4695
+ const recoveryRate = hadRecoverableError ? Number((normalizedContent.length > 0 ? 1 : 0).toFixed(3)) : 1;
4696
+ const overToolingRate = toolCalls.length === 0 ? 0 : Number((Math.max(0, toolCalls.length - Math.max(testCase.expectedToolNames?.length || 0, 1)) / toolCalls.length).toFixed(3));
4697
+ const success = recallAccuracy >= 0.8 && obsoleteDropAccuracy >= 0.8;
4698
+ return {
4699
+ id: testCase.id,
4700
+ family: testCase.family,
4701
+ success,
4702
+ recallAccuracy,
4703
+ obsoleteDropAccuracy,
4704
+ trajectoryScore: Number(((trajectoryScore + toolPrecision) / 2).toFixed(3)),
4705
+ recoveryRate,
4706
+ overToolingRate,
4707
+ latencyMs,
4708
+ totalTokens,
4709
+ profile: descriptor.runtimeProfile,
4710
+ profileLabel: descriptor.label,
4711
+ baseProfile: descriptor.baseProfile,
4712
+ notes: success ? [] : [
4713
+ recallAccuracy < 0.8 ? "Recall below threshold." : "",
4714
+ obsoleteDropAccuracy < 0.8 ? "Obsolete filtering below threshold." : "",
4715
+ trajectoryScore < 0.5 ? "Tool trajectory diverged from expectation." : ""
4716
+ ].filter(Boolean)
4717
+ };
4718
+ }
4719
+ function aggregateMetrics(caseResults) {
4720
+ const taskSuccess = ratio(caseResults.filter((result) => result.success).length, caseResults.length);
4721
+ const recallAccuracy = mean(caseResults.map((result) => result.recallAccuracy));
4722
+ const obsoleteDropAccuracy = mean(caseResults.map((result) => result.obsoleteDropAccuracy));
4723
+ const trajectoryScore = mean(caseResults.map((result) => result.trajectoryScore));
4724
+ const recoveryRate = mean(caseResults.map((result) => result.recoveryRate));
4725
+ const overToolingRate = mean(caseResults.map((result) => result.overToolingRate));
4726
+ const latencyMs = mean(caseResults.map((result) => result.latencyMs));
4727
+ const totalTokens = sum(caseResults.map((result) => result.totalTokens || 0));
4728
+ const toolPrecision = trajectoryScore;
4729
+ const costScore = totalTokens <= 0 ? 1 : Number((1 / (1 + totalTokens / 1e4)).toFixed(3));
4730
+ const score = Number((0.45 * taskSuccess + 0.2 * trajectoryScore + 0.15 * toolPrecision + 0.1 * recoveryRate + 0.1 * costScore).toFixed(3));
4731
+ return {
4732
+ taskSuccess,
4733
+ recallAccuracy,
4734
+ obsoleteDropAccuracy,
4735
+ trajectoryScore,
4736
+ recoveryRate,
4737
+ overToolingRate,
4738
+ latencyMs,
4739
+ totalTokens,
4740
+ score
4741
+ };
4742
+ }
4743
+ async function runSmartAgentEvalHarness(params) {
4744
+ const results = [];
4745
+ for (const profileTarget of params.profiles) {
4746
+ const descriptor = normalizeProfileTarget(profileTarget);
4747
+ const caseResults = [];
4748
+ for (const testCase of params.cases) {
4749
+ const agent = params.createAgent(descriptor.runtimeProfile, descriptor);
4750
+ const events = [];
4751
+ const startedAt = Date.now();
4752
+ const result = await agent.invoke({
4753
+ messages: [{ role: "user", content: testCase.prompt }]
4754
+ }, {
4755
+ onEvent: (event) => {
4756
+ events.push(event);
4757
+ }
4758
+ });
4759
+ caseResults.push(caseResultFromRun(
4760
+ descriptor,
4761
+ testCase,
4762
+ result.content,
4763
+ events,
4764
+ result.state,
4765
+ Date.now() - startedAt,
4766
+ extractTotalTokens(result.metadata?.usage)
4767
+ ));
4768
+ }
4769
+ results.push({
4770
+ profile: descriptor.runtimeProfile,
4771
+ profileLabel: descriptor.label,
4772
+ baseProfile: descriptor.baseProfile,
4773
+ metrics: aggregateMetrics(caseResults),
4774
+ cases: caseResults
4775
+ });
4776
+ }
4777
+ return results;
4778
+ }
4779
+
3149
4780
  // src/utils/content.ts
3150
4781
  function contentToString(content) {
3151
4782
  try {
@@ -3423,7 +5054,9 @@ function createCodeGuardrail(options) {
3423
5054
  });
3424
5055
  }
3425
5056
 
5057
+ exports.DEFAULT_PROFILE_CONFIGS = DEFAULT_PROFILE_CONFIGS;
3426
5058
  exports.GuardrailPhase = GuardrailPhase;
5059
+ exports.InMemoryMemoryStore = InMemoryMemoryStore;
3427
5060
  exports.agentVerdictRule = agentVerdictRule;
3428
5061
  exports.applyTokenLimits = applyTokenLimits;
3429
5062
  exports.buildSystemPrompt = buildSystemPrompt;
@@ -3451,13 +5084,24 @@ exports.evaluateGuardrails = evaluateGuardrails;
3451
5084
  exports.fileSink = fileSink;
3452
5085
  exports.fromLangchainModel = fromLangchainModel;
3453
5086
  exports.fromLangchainTools = fromLangchainTools;
5087
+ exports.generateSpanId = generateSpanId;
5088
+ exports.generateTraceId = generateTraceId;
5089
+ exports.getResolvedSmartConfig = getResolvedSmartConfig;
3454
5090
  exports.httpSink = httpSink;
3455
5091
  exports.isSmartModel = isSmartModel;
3456
5092
  exports.jsonSchemaRule = jsonSchemaRule;
5093
+ exports.normalizeSmartAgentOptions = normalizeSmartAgentOptions;
5094
+ exports.otlpSink = otlpSink;
5095
+ exports.readMemoryFacts = readMemoryFacts;
3457
5096
  exports.regexRule = regexRule;
5097
+ exports.renderMemoryFacts = renderMemoryFacts;
5098
+ exports.resolveMemoryStore = resolveMemoryStore;
3458
5099
  exports.resolveToolApprovalState = resolveToolApprovalState;
3459
5100
  exports.restoreSnapshot = restoreSnapshot;
5101
+ exports.runSmartAgentEvalHarness = runSmartAgentEvalHarness;
3460
5102
  exports.startStreamingSession = startStreamingSession;
5103
+ exports.traceSessionToOtlp = traceSessionToOtlp;
3461
5104
  exports.withTools = withTools;
5105
+ exports.writeSummaryFactsToMemory = writeSummaryFactsToMemory;
3462
5106
  //# sourceMappingURL=index.cjs.map
3463
5107
  //# sourceMappingURL=index.cjs.map