@cognipeer/agent-sdk 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,523 @@ 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
+ return JSON.stringify(value);
2153
+ } catch {
2154
+ return String(value);
2155
+ }
2156
+ }
2157
+ function summarizeObject(value) {
2158
+ if (value == null) return "null";
2159
+ if (typeof value === "string") {
2160
+ return value.length > 240 ? `${value.slice(0, 240)}...` : value;
2161
+ }
2162
+ if (Array.isArray(value)) {
2163
+ const preview = value.slice(0, 3).map((entry) => safeStringify(entry).slice(0, 120)).join(" | ");
2164
+ return `array(length=${value.length}) ${preview}`.trim();
2165
+ }
2166
+ if (typeof value === "object") {
2167
+ const keys = Object.keys(value);
2168
+ const preview = keys.slice(0, 6).map((key) => `${key}=${safeStringify(value[key]).slice(0, 80)}`).join("; ");
2169
+ return `object(keys=${keys.length}) ${preview}`.trim();
2170
+ }
2171
+ return safeStringify(value);
2172
+ }
2173
+ function classifyToolResponse(toolName, output, config) {
2174
+ const serialized = safeStringify(output);
2175
+ const tokenCount = countApproxTokens(serialized);
2176
+ const criticalTools = new Set(config.toolResponses.criticalTools);
2177
+ if (criticalTools.has(toolName)) return "critical";
2178
+ if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
2179
+ return "verbose";
2180
+ }
2181
+ if (serialized === "ok" || serialized === "true" || serialized === "null") {
2182
+ return "redundant";
2183
+ }
2184
+ return "informative";
2185
+ }
2186
+ function resolveToolResponsePolicy(toolName, output, config) {
2187
+ const serialized = safeStringify(output);
2188
+ const tokenCount = countApproxTokens(serialized);
2189
+ const classification = classifyToolResponse(toolName, output, config);
2190
+ const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2191
+ let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
2192
+ if (classification === "critical") {
2193
+ retentionPolicy = byTool || "keep_structured";
2194
+ } else if (classification === "verbose") {
2195
+ retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
2196
+ } else if (classification === "redundant") {
2197
+ retentionPolicy = byTool || "drop";
2198
+ }
2199
+ const summary = summarizeObject(output);
2200
+ if (retentionPolicy === "keep_full") {
2201
+ return { classification, retentionPolicy, content: serialized, rawOutput: output, summary, tokenCount };
2202
+ }
2203
+ if (retentionPolicy === "keep_structured") {
2204
+ return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
2205
+ }
2206
+ if (retentionPolicy === "summarize_archive") {
2207
+ return {
2208
+ classification,
2209
+ retentionPolicy,
2210
+ content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
2211
+ rawOutput: output,
2212
+ summary,
2213
+ tokenCount
2214
+ };
2215
+ }
2216
+ return {
2217
+ classification,
2218
+ retentionPolicy,
2219
+ content: `DROPPED_TOOL_RESPONSE: ${summary}`,
2220
+ rawOutput: output,
2221
+ summary,
2222
+ tokenCount
2223
+ };
2224
+ }
2225
+ function validateToolArgs(tool, args) {
2226
+ const schema = tool.schema;
2227
+ if (!schema || typeof schema.parse !== "function") {
2228
+ return { ok: true, value: args };
2229
+ }
2230
+ try {
2231
+ return { ok: true, value: schema.parse(args) };
2232
+ } catch (error) {
2233
+ if (error instanceof zod.ZodError) {
2234
+ const message = error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
2235
+ return { ok: false, message };
2236
+ }
2237
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
2238
+ }
2239
+ }
2240
+
2241
+ // src/nodes/tools.ts
2242
+ function normalizeMaxExecutionsPerRun(value) {
2243
+ if (value === null || value === void 0 || value === "") return null;
2244
+ const parsed = Number(value);
2245
+ if (!Number.isFinite(parsed)) return null;
2246
+ const normalized = Math.trunc(parsed);
2247
+ return normalized >= 0 ? normalized : null;
2248
+ }
2249
+ function countSuccessfulToolExecutions(toolHistory, toolName) {
2250
+ if (!Array.isArray(toolHistory)) return 0;
2251
+ return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
2252
+ }
1472
2253
  function createToolsNode(initialTools, opts) {
1473
- const baseToolByName = /* @__PURE__ */ new Map();
1474
- for (const t of initialTools) baseToolByName.set(t.name, t);
1475
2254
  return async (state) => {
1476
2255
  const runtime = state.agent || {
1477
2256
  name: opts?.name,
@@ -1479,15 +2258,17 @@ function createToolsNode(initialTools, opts) {
1479
2258
  tools: initialTools,
1480
2259
  limits: opts?.limits,
1481
2260
  systemPrompt: opts?.systemPrompt,
2261
+ todoListPrompt: opts?.todoListPrompt,
1482
2262
  useTodoList: opts?.useTodoList,
1483
2263
  outputSchema: opts?.outputSchema
1484
2264
  };
2265
+ const resolved = getResolvedSmartConfig(opts || {}, runtime);
1485
2266
  const activeTools = runtime.tools;
1486
2267
  const toolByName = /* @__PURE__ */ new Map();
1487
- for (const t of activeTools) toolByName.set(t.name, t);
2268
+ for (const tool of activeTools) toolByName.set(tool.name, tool);
1488
2269
  const limits = {
1489
- maxToolCalls: runtime.limits?.maxToolCalls ?? 10,
1490
- maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? 1)
2270
+ maxToolCalls: runtime.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10,
2271
+ maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? resolved.limits.maxParallelTools ?? 1)
1491
2272
  };
1492
2273
  const appended = [];
1493
2274
  const onEvent = state.ctx?.__onEvent;
@@ -1498,20 +2279,19 @@ function createToolsNode(initialTools, opts) {
1498
2279
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
1499
2280
  const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
1500
2281
  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;
2282
+ for (const tool of toolByName.values()) {
2283
+ const anyTool = tool;
2284
+ if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
2285
+ anyTool._stateRef.toolHistory = state.toolHistory;
2286
+ anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
2287
+ anyTool._stateRef.__onEvent = onEvent;
1507
2288
  }
1508
2289
  }
1509
2290
  const last = state.messages[state.messages.length - 1];
1510
2291
  let toolCount = state.toolCallCount || 0;
1511
- const toolCalls = Array.isArray(
1512
- last?.tool_calls
1513
- ) ? last.tool_calls : [];
2292
+ const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
1514
2293
  const toolHistory = state.toolHistory || [];
2294
+ const toolHistoryArchived = state.toolHistoryArchived || [];
1515
2295
  const remaining = Math.max(0, limits.maxToolCalls - toolCount);
1516
2296
  const planned = toolCalls.slice(0, remaining);
1517
2297
  const skipped = toolCalls.slice(remaining);
@@ -1520,6 +2300,17 @@ function createToolsNode(initialTools, opts) {
1520
2300
  if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
1521
2301
  return { cancelled: false, reason: void 0 };
1522
2302
  };
2303
+ const markToolFailure = (message, toolName, toolCallId) => {
2304
+ const ctx = { ...state.ctx || {} };
2305
+ ctx.__lastToolError = message;
2306
+ if (toolCallId) {
2307
+ ctx.__toolSchemaError = { toolName, toolCallId, message };
2308
+ }
2309
+ if (resolved.watchdog.autoReplanOnFailure) {
2310
+ ctx.__planNeedsReplan = true;
2311
+ }
2312
+ state.ctx = ctx;
2313
+ };
1523
2314
  const runOne = async (tc) => {
1524
2315
  const cancelState = isCancelled();
1525
2316
  if (cancelState.cancelled) {
@@ -1529,15 +2320,10 @@ function createToolsNode(initialTools, opts) {
1529
2320
  onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
1530
2321
  return { status: "error" };
1531
2322
  }
1532
- const t = toolByName.get(tc.name);
1533
- if (!t) {
2323
+ const tool = toolByName.get(tc.name);
2324
+ if (!tool) {
2325
+ appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1534
2326
  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
2327
  toolCount += 1;
1542
2328
  return { status: "error" };
1543
2329
  }
@@ -1545,22 +2331,67 @@ function createToolsNode(initialTools, opts) {
1545
2331
  if (typeof args === "string") {
1546
2332
  try {
1547
2333
  args = JSON.parse(args);
1548
- } catch (_) {
2334
+ } catch {
1549
2335
  }
1550
2336
  }
1551
2337
  const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
2338
+ const validatedArgs = validateToolArgs(tool, args);
2339
+ if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
2340
+ const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
2341
+ appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
2342
+ markToolFailure(errorMessage, tc.name, toolCallId);
2343
+ onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
2344
+ toolCount += 1;
2345
+ return { status: "error" };
2346
+ }
2347
+ if (validatedArgs.ok) {
2348
+ args = validatedArgs.value;
2349
+ }
2350
+ const toolName = tool.name || tc.name;
2351
+ const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
2352
+ if (maxExecutionsPerRun !== null) {
2353
+ const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
2354
+ if (currentExecutions >= maxExecutionsPerRun) {
2355
+ const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
2356
+ appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
2357
+ onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
2358
+ recordTraceEvent(traceSession, {
2359
+ type: "tool_call",
2360
+ label: `Tool Skipped - ${toolName}`,
2361
+ actor: { scope: "tool", name: toolName, role: "tool" },
2362
+ status: "skipped",
2363
+ toolExecutionId: tc.id,
2364
+ messageList: [
2365
+ {
2366
+ role: "assistant",
2367
+ name: toolName,
2368
+ content: limitMessage,
2369
+ tool_calls: [
2370
+ {
2371
+ id: tc.id,
2372
+ type: "function",
2373
+ function: { name: toolName, arguments: sanitizeTracePayload(args) }
2374
+ }
2375
+ ]
2376
+ }
2377
+ ]
2378
+ });
2379
+ toolCount += 1;
2380
+ return { status: "error" };
2381
+ }
2382
+ }
1552
2383
  let approvalEntry = pendingByCallId.get(toolCallId);
1553
- const needsApproval = Boolean(t.needsApproval);
2384
+ const needsApproval = Boolean(tool.needsApproval);
1554
2385
  if (needsApproval) {
1555
2386
  if (!approvalEntry) {
1556
2387
  approvalEntry = {
1557
2388
  id: nanoid.nanoid(),
1558
2389
  toolCallId,
1559
- toolName: t.name || tc.name,
2390
+ toolName: tool.name || tc.name,
1560
2391
  args,
1561
2392
  status: "pending",
1562
2393
  requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
1563
- metadata: t.approvalPrompt || t.approvalDefaults ? { prompt: t.approvalPrompt, defaults: t.approvalDefaults } : void 0
2394
+ metadata: tool.approvalPrompt || tool.approvalDefaults ? { prompt: tool.approvalPrompt, defaults: tool.approvalDefaults } : void 0
1564
2395
  };
1565
2396
  pendingApprovals.push(approvalEntry);
1566
2397
  pendingByCallId.set(toolCallId, approvalEntry);
@@ -1582,71 +2413,97 @@ function createToolsNode(initialTools, opts) {
1582
2413
  }
1583
2414
  if (approvalEntry.status === "rejected") {
1584
2415
  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 });
2416
+ appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
1592
2417
  approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
1593
2418
  approvalEntry.status = "executed";
1594
2419
  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
2420
  pendingByCallId.set(toolCallId, approvalEntry);
2421
+ 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" });
2422
+ onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
1597
2423
  toolCount += 1;
1598
2424
  return { status: "rejected", approval: approvalEntry };
1599
2425
  }
1600
- if (approvalEntry.status === "approved") {
1601
- if (approvalEntry.approvedArgs !== void 0) {
1602
- args = approvalEntry.approvedArgs;
1603
- }
2426
+ if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
2427
+ args = approvalEntry.approvedArgs;
1604
2428
  onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
1605
2429
  }
1606
2430
  }
1607
2431
  const start = Date.now();
2432
+ const sanitizedArgs = sanitizeTracePayload(args);
2433
+ const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1608
2434
  try {
1609
- onEvent?.({ type: "tool_call", phase: "start", name: t.name, id: tc.id, args });
2435
+ onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
1610
2436
  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;
2437
+ const anyTool = tool;
1615
2438
  const callOptions = { cancellationToken, signal: abortSignal };
2439
+ let output;
1616
2440
  if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
1617
2441
  else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
1618
2442
  else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
1619
2443
  else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
1620
2444
  else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
1621
2445
  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 });
2446
+ const durationMs = Date.now() - start;
2447
+ const executionId = nanoid.nanoid();
2448
+ if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
2449
+ toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
1626
2450
  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;
2451
+ state.agent = output.__handoff.runtime;
2452
+ onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
2453
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
1631
2454
  if (needsApproval && approvalEntry) {
1632
2455
  approvalEntry.status = "executed";
1633
2456
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
1634
- approvalEntry.executionId = executionId2;
2457
+ approvalEntry.executionId = executionId;
1635
2458
  pendingByCallId.set(toolCallId, approvalEntry);
1636
2459
  }
2460
+ toolCount += 1;
1637
2461
  return { status: "success", approval: approvalEntry };
1638
2462
  }
1639
- const content = typeof output === "string" ? output : JSON.stringify(output);
1640
2463
  if (output && typeof output === "object" && output.__finalStructuredOutput) {
1641
2464
  if (!state.ctx) state.ctx = {};
1642
2465
  state.ctx.__structuredOutputParsed = output.data;
1643
2466
  state.ctx.__finalizedDueToStructuredOutput = true;
1644
2467
  }
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 });
2468
+ const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
2469
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2470
+ const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
2471
+ toolHistory.push({
2472
+ executionId,
2473
+ toolName,
2474
+ args,
2475
+ output: storedOutput,
2476
+ rawOutput: output,
2477
+ timestamp,
2478
+ tool_call_id: tc.id,
2479
+ summarized: responsePolicy.retentionPolicy !== "keep_full",
2480
+ originalTokenCount: responsePolicy.tokenCount,
2481
+ classification: responsePolicy.classification,
2482
+ retentionPolicy: responsePolicy.retentionPolicy,
2483
+ summary: responsePolicy.summary,
2484
+ archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
2485
+ status: "success"
2486
+ });
2487
+ if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
2488
+ toolHistoryArchived.push({
2489
+ executionId,
2490
+ toolName,
2491
+ args,
2492
+ output,
2493
+ rawOutput: output,
2494
+ timestamp,
2495
+ tool_call_id: tc.id,
2496
+ summarized: true,
2497
+ originalTokenCount: responsePolicy.tokenCount,
2498
+ classification: responsePolicy.classification,
2499
+ retentionPolicy: responsePolicy.retentionPolicy,
2500
+ summary: responsePolicy.summary,
2501
+ archiveId: executionId,
2502
+ status: "success"
2503
+ });
2504
+ }
2505
+ appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2506
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
1650
2507
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
1651
2508
  if (needsApproval && approvalEntry) {
1652
2509
  approvalEntry.status = "executed";
@@ -1656,7 +2513,6 @@ function createToolsNode(initialTools, opts) {
1656
2513
  }
1657
2514
  const sanitizedOutput = sanitizeTracePayload(output);
1658
2515
  const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
1659
- const toolName = t.name || tc.name;
1660
2516
  const messageList = [
1661
2517
  {
1662
2518
  role: "assistant",
@@ -1676,7 +2532,7 @@ function createToolsNode(initialTools, opts) {
1676
2532
  {
1677
2533
  role: "tool",
1678
2534
  name: toolName,
1679
- content: sanitizedOutput ?? output ?? ""
2535
+ content: responsePolicy.content
1680
2536
  }
1681
2537
  ];
1682
2538
  recordTraceEvent(traceSession, {
@@ -1687,43 +2543,24 @@ function createToolsNode(initialTools, opts) {
1687
2543
  requestBytes: inputBytes,
1688
2544
  responseBytes: outputBytes,
1689
2545
  toolExecutionId: executionId,
1690
- messageList
2546
+ messageList,
2547
+ debug: {
2548
+ classification: responsePolicy.classification,
2549
+ retentionPolicy: responsePolicy.retentionPolicy,
2550
+ originalTokenCount: responsePolicy.tokenCount
2551
+ }
1691
2552
  });
1692
2553
  toolCount += 1;
1693
2554
  return { status: "success", approval: approvalEntry };
1694
- } catch (e) {
2555
+ } catch (error) {
1695
2556
  const durationMs = Date.now() - start;
1696
2557
  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
- ];
2558
+ const message = error?.message || String(error);
2559
+ toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
2560
+ appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2561
+ markToolFailure(message, toolName, toolCallId);
2562
+ onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
2563
+ onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
1727
2564
  recordTraceEvent(traceSession, {
1728
2565
  type: "tool_call",
1729
2566
  label: `Tool Error - ${toolName}`,
@@ -1732,8 +2569,26 @@ function createToolsNode(initialTools, opts) {
1732
2569
  durationMs,
1733
2570
  requestBytes: inputBytes,
1734
2571
  toolExecutionId: executionId,
1735
- error: { message: e?.message || String(e), stack: e?.stack },
1736
- messageList
2572
+ error: { message, stack: error?.stack },
2573
+ messageList: [
2574
+ {
2575
+ role: "assistant",
2576
+ name: toolName,
2577
+ content: "",
2578
+ tool_calls: [
2579
+ {
2580
+ id: tc.id || executionId,
2581
+ type: "function",
2582
+ function: { name: toolName, arguments: sanitizedArgs }
2583
+ }
2584
+ ]
2585
+ },
2586
+ {
2587
+ role: "tool",
2588
+ name: toolName,
2589
+ content: `Error executing tool: ${message}`
2590
+ }
2591
+ ]
1737
2592
  });
1738
2593
  toolCount += 1;
1739
2594
  return { status: "error", approval: approvalEntry };
@@ -1750,7 +2605,7 @@ function createToolsNode(initialTools, opts) {
1750
2605
  break;
1751
2606
  }
1752
2607
  const result = await runOne(tc);
1753
- if (result?.status === "awaiting_approval") {
2608
+ if (result.status === "awaiting_approval") {
1754
2609
  awaitingApproval = true;
1755
2610
  break;
1756
2611
  }
@@ -1758,37 +2613,28 @@ function createToolsNode(initialTools, opts) {
1758
2613
  for (const tc of skipped) {
1759
2614
  onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
1760
2615
  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
2616
  recordTraceEvent(traceSession, {
1779
2617
  type: "tool_call",
1780
2618
  label: `Tool Skipped - ${tc.name}`,
1781
2619
  actor: { scope: "tool", name: tc.name, role: "tool" },
1782
2620
  status: "skipped",
1783
2621
  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
2622
+ messageList: [
2623
+ {
2624
+ role: "assistant",
2625
+ name: tc.name,
2626
+ content: `Skipped tool due to tool-call limit: ${tc.name}`,
2627
+ tool_calls: [
2628
+ {
2629
+ id: tc.id,
2630
+ type: "function",
2631
+ function: { name: tc.name, arguments: sanitizedArgs }
2632
+ }
2633
+ ]
2634
+ }
2635
+ ]
1791
2636
  });
2637
+ 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
2638
  toolCount += 1;
1793
2639
  }
1794
2640
  if (!awaitingApproval && state.ctx?.__awaitingApproval) {
@@ -1803,6 +2649,7 @@ function createToolsNode(initialTools, opts) {
1803
2649
  messages: [...state.messages, ...appended],
1804
2650
  toolCallCount: toolCount,
1805
2651
  toolHistory,
2652
+ toolHistoryArchived,
1806
2653
  agent: state.agent,
1807
2654
  pendingApprovals
1808
2655
  };
@@ -2027,68 +2874,20 @@ function resolveToolApprovalState(state, resolution) {
2027
2874
  };
2028
2875
  }
2029
2876
 
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
- }
2877
+ // src/agent.ts
2878
+ function isSyntheticSummaryMessage(message) {
2879
+ if (!message) return false;
2880
+ if (message.role === "tool" && message.name === "summarize_context") {
2881
+ return true;
2072
2882
  }
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
- }
2883
+ if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
2884
+ return message.tool_calls.some((toolCall) => {
2885
+ const toolName = toolCall?.function?.name || toolCall?.name;
2886
+ return toolName === "summarize_context";
2887
+ });
2082
2888
  }
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;
2889
+ return false;
2089
2890
  }
2090
-
2091
- // src/agent.ts
2092
2891
  function createAgent(opts) {
2093
2892
  const resolver = createResolverNode();
2094
2893
  const agentCore = createAgentCoreNode(opts);
@@ -2131,6 +2930,7 @@ function createAgent(opts) {
2131
2930
  tools: toolsBase,
2132
2931
  guardrails: opts.guardrails,
2133
2932
  systemPrompt: void 0,
2933
+ todoListPrompt: opts.todoListPrompt,
2134
2934
  limits: opts.limits,
2135
2935
  useTodoList: void 0,
2136
2936
  outputSchema: opts.outputSchema,
@@ -2206,6 +3006,18 @@ function createAgent(opts) {
2206
3006
  };
2207
3007
  while (iterations < iterationLimit) {
2208
3008
  iterations++;
3009
+ const traceSession = state.ctx?.__traceSession;
3010
+ if (traceSession) {
3011
+ const iterEvent = recordTraceEvent(traceSession, {
3012
+ type: "agent_iteration",
3013
+ label: `Iteration ${iterations}`,
3014
+ actor: { scope: "agent", name: opts.name || "agent", role: "orchestrator" },
3015
+ parentSpanId: traceSession.rootSpanId
3016
+ });
3017
+ if (iterEvent) {
3018
+ traceSession.currentIterationSpanId = iterEvent.spanId;
3019
+ }
3020
+ }
2209
3021
  if (cancelIfRequested("loop")) break;
2210
3022
  const skippingAgent = resumeStage === "tools";
2211
3023
  if (!skippingAgent) {
@@ -2256,11 +3068,21 @@ function createAgent(opts) {
2256
3068
  maxTok = 5e4;
2257
3069
  }
2258
3070
  if (maxTok) {
2259
- const tokenCount = countMessagesTokens(state.messages || []);
3071
+ const tokCountMessages = (state.messages || []).filter((message) => {
3072
+ if (isSyntheticSummaryMessage(message)) return false;
3073
+ if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
3074
+ return true;
3075
+ });
3076
+ const tokenCount = countMessagesTokens(tokCountMessages);
2260
3077
  if (tokenCount > maxTok) {
2261
- const ctx = { ...state.ctx || {}, __needsSummarization: true };
2262
- state = { ...state, ctx };
2263
- break;
3078
+ const hasFreshSummary = (state.messages || []).some(
3079
+ (m) => m.role === "tool" && m.content === "SUMMARIZED"
3080
+ );
3081
+ if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
3082
+ const ctx = { ...state.ctx || {}, __needsSummarization: true };
3083
+ state = { ...state, ctx };
3084
+ break;
3085
+ }
2264
3086
  }
2265
3087
  }
2266
3088
  onProgress?.({ stage: "agent", message: "Invoking model" });
@@ -2498,7 +3320,11 @@ function createAgent(opts) {
2498
3320
  schema,
2499
3321
  func: async ({ input }) => {
2500
3322
  const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
2501
- return { content: res.content };
3323
+ return {
3324
+ content: res.content,
3325
+ output: res.output,
3326
+ summary: res.state?.summaries?.[res.state.summaries.length - 1]
3327
+ };
2502
3328
  }
2503
3329
  });
2504
3330
  },
@@ -2539,6 +3365,7 @@ function createAgentNode(opts) {
2539
3365
  model: opts.model,
2540
3366
  tools: opts.tools || [],
2541
3367
  systemPrompt: opts.systemPrompt,
3368
+ todoListPrompt: opts.todoListPrompt,
2542
3369
  limits: opts.limits,
2543
3370
  useTodoList: opts.useTodoList,
2544
3371
  outputSchema: opts.outputSchema
@@ -2556,7 +3383,8 @@ function createAgentNode(opts) {
2556
3383
  content: buildSystemPrompt(
2557
3384
  [runtime.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
2558
3385
  runtime.useTodoList === true,
2559
- runtime.name || "Agent"
3386
+ runtime.name || "Agent",
3387
+ runtime.todoListPrompt
2560
3388
  )
2561
3389
  };
2562
3390
  const messages = [systemMsg, ...state.messages];
@@ -2638,9 +3466,119 @@ function createAgentNode(opts) {
2638
3466
  };
2639
3467
  }
2640
3468
 
3469
+ // src/smart/contextPolicy.ts
3470
+ function messageText(message) {
3471
+ if (typeof message.content === "string") return message.content;
3472
+ if (Array.isArray(message.content)) {
3473
+ return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
3474
+ }
3475
+ return "";
3476
+ }
3477
+ function collectRecentTurns(messages, lastTurnsToKeep) {
3478
+ if (lastTurnsToKeep <= 0) return [];
3479
+ const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
3480
+ const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
3481
+ let userTurnCount = 0;
3482
+ const collected = [];
3483
+ for (let index = body.length - 1; index >= 0; index -= 1) {
3484
+ const message = body[index];
3485
+ collected.unshift(message);
3486
+ if (message.role === "user") {
3487
+ userTurnCount += 1;
3488
+ if (userTurnCount >= lastTurnsToKeep) break;
3489
+ }
3490
+ }
3491
+ return [...systemPrefix, ...collected];
3492
+ }
3493
+ function renderStructuredSummary(summary) {
3494
+ if (!summary) return "";
3495
+ const lines = ["Context summary:"];
3496
+ if (summary.stable_facts.length > 0) {
3497
+ lines.push("Stable facts:");
3498
+ lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
3499
+ }
3500
+ if (summary.active_goals.length > 0) {
3501
+ lines.push("Active goals:");
3502
+ lines.push(...summary.active_goals.map((goal) => `- ${goal}`));
3503
+ }
3504
+ if (summary.open_questions.length > 0) {
3505
+ lines.push("Open questions:");
3506
+ lines.push(...summary.open_questions.map((question) => `- ${question}`));
3507
+ }
3508
+ if (summary.discarded_obsolete.length > 0) {
3509
+ lines.push("Discarded obsolete:");
3510
+ lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
3511
+ }
3512
+ return lines.join("\n");
3513
+ }
3514
+ function renderMemoryBlock(facts) {
3515
+ if (!facts || facts.length === 0) return "";
3516
+ return [
3517
+ "Retrieved memory:",
3518
+ ...facts.map((fact) => `- ${fact.key}: ${fact.value}`)
3519
+ ].join("\n");
3520
+ }
3521
+ function clampToBudget(messages, maxContextTokens) {
3522
+ let working = [...messages];
3523
+ while (working.length > 2) {
3524
+ const tokenCount = countApproxTokens(working.map(messageText).join("\n"));
3525
+ if (tokenCount <= maxContextTokens) return working;
3526
+ const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
3527
+ if (firstNonSystem < 0) break;
3528
+ working.splice(firstNonSystem, 1);
3529
+ }
3530
+ return working;
3531
+ }
3532
+ function buildModelMessages(state, config) {
3533
+ const rawMessages = state.messages || [];
3534
+ if (config.context.policy === "raw") {
3535
+ return clampToBudget(rawMessages, config.limits.maxContextTokens);
3536
+ }
3537
+ const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
3538
+ const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
3539
+ const summaryText = renderStructuredSummary(latestSummary);
3540
+ const memoryText = renderMemoryBlock(state.memoryFacts);
3541
+ const systemMessage2 = recentMessages[0]?.role === "system" ? recentMessages[0] : void 0;
3542
+ const body = systemMessage2 ? recentMessages.slice(1) : recentMessages;
3543
+ const syntheticContextMessages = [];
3544
+ if (summaryText) {
3545
+ syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
3546
+ }
3547
+ if (memoryText) {
3548
+ syntheticContextMessages.push({ role: "system", name: "memory_context", content: memoryText });
3549
+ }
3550
+ const assembled = [
3551
+ ...systemMessage2 ? [systemMessage2] : [],
3552
+ ...syntheticContextMessages,
3553
+ ...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
3554
+ ];
3555
+ return clampToBudget(assembled, config.limits.maxContextTokens);
3556
+ }
3557
+ function estimateContextRotScore(state) {
3558
+ const summaryCount = state.summaryRecords?.length || 0;
3559
+ const archivedCount = state.toolHistoryArchived?.length || 0;
3560
+ const messageCount = state.messages?.length || 0;
3561
+ const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
3562
+ const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
3563
+ const denominator = Math.max(messageCount, 1);
3564
+ return Number((numerator / denominator).toFixed(3));
3565
+ }
3566
+
2641
3567
  // src/nodes/contextSummarize.ts
2642
3568
  var systemMessage = (content) => ({ role: "system", content });
2643
3569
  var humanMessage = (content) => ({ role: "user", content });
3570
+ function isSyntheticSummaryToolMessage(message) {
3571
+ return message.role === "tool" && message.name === "summarize_context";
3572
+ }
3573
+ function isSyntheticSummaryAssistantMessage(message) {
3574
+ if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
3575
+ return false;
3576
+ }
3577
+ return message.tool_calls.some((toolCall) => {
3578
+ const toolName = toolCall?.function?.name || toolCall?.name;
3579
+ return toolName === "summarize_context";
3580
+ });
3581
+ }
2644
3582
  function validateMessageSequence(messages) {
2645
3583
  const validated = [];
2646
3584
  const pendingToolCallIds = /* @__PURE__ */ new Set();
@@ -2666,33 +3604,137 @@ function validateMessageSequence(messages) {
2666
3604
  }
2667
3605
  return validated;
2668
3606
  }
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
- };
3607
+ function extractJsonObject(text) {
3608
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
3609
+ if (fenced?.[1]) return fenced[1].trim();
3610
+ const start = text.indexOf("{");
3611
+ const end = text.lastIndexOf("}");
3612
+ if (start >= 0 && end > start) {
3613
+ return text.slice(start, end + 1);
3614
+ }
3615
+ return null;
3616
+ }
3617
+ function normalizeStructuredSummary(input, fallbackText) {
3618
+ const summary = {
3619
+ stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
3620
+ key: String(fact.key || "unknown"),
3621
+ value: String(fact.value || ""),
3622
+ confidence: typeof fact.confidence === "number" ? fact.confidence : 0.7,
3623
+ source: fact.source ? String(fact.source) : void 0
3624
+ })) : [],
3625
+ active_goals: Array.isArray(input?.active_goals) ? input.active_goals.map((goal) => String(goal)) : [],
3626
+ open_questions: Array.isArray(input?.open_questions) ? input.open_questions.map((question) => String(question)) : [],
3627
+ discarded_obsolete: Array.isArray(input?.discarded_obsolete || input?.obsolete_discarded) ? (input.discarded_obsolete || input.obsolete_discarded).map((item) => String(item)) : [],
3628
+ rawSummary: typeof input?.rawSummary === "string" ? input.rawSummary : fallbackText
3629
+ };
3630
+ if (summary.stable_facts.length === 0 && fallbackText) {
3631
+ const importantLines = fallbackText.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 8);
3632
+ summary.stable_facts = importantLines.map((line, index) => ({
3633
+ key: `fact_${index + 1}`,
3634
+ value: line,
3635
+ confidence: 0.5
3636
+ }));
3637
+ }
3638
+ return summary;
3639
+ }
3640
+ function normalizeFactKeySegment(value) {
3641
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "item";
3642
+ }
3643
+ function extractCanonicalFacts(messages) {
3644
+ const facts = /* @__PURE__ */ new Map();
3645
+ for (const message of messages) {
3646
+ const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part?.text ?? part?.content ?? String(part ?? "")).join("\n") : "";
3647
+ if (!content) continue;
3648
+ const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
3649
+ for (const line of lines) {
3650
+ if (!/^[A-Z0-9_]+\|/.test(line) || !line.includes("=")) continue;
3651
+ const segments = line.split("|").map((segment) => segment.trim()).filter(Boolean);
3652
+ const label = segments[0];
3653
+ const entries = segments.slice(1).map((segment) => {
3654
+ const eqIndex = segment.indexOf("=");
3655
+ if (eqIndex <= 0) return null;
3656
+ return {
3657
+ key: segment.slice(0, eqIndex).trim(),
3658
+ value: segment.slice(eqIndex + 1).trim()
3659
+ };
3660
+ }).filter((entry) => Boolean(entry?.key));
3661
+ if (entries.length === 0) continue;
3662
+ const identifier = entries.find((entry) => ["code", "id", "project", "name", "key"].includes(entry.key.toLowerCase()))?.value;
3663
+ const keyPrefix = [normalizeFactKeySegment(label), identifier ? normalizeFactKeySegment(identifier) : void 0].filter(Boolean).join(".");
3664
+ for (const entry of entries) {
3665
+ const normalizedEntryKey = normalizeFactKeySegment(entry.key);
3666
+ const factKey = keyPrefix ? `${keyPrefix}.${normalizedEntryKey}` : `${normalizeFactKeySegment(label)}.${normalizedEntryKey}`;
3667
+ facts.set(factKey, {
3668
+ key: factKey,
3669
+ value: entry.value,
3670
+ confidence: 0.98,
3671
+ source: "canonical_tool_output"
3672
+ });
3673
+ }
3674
+ }
3675
+ }
3676
+ return [...facts.values()];
3677
+ }
3678
+ function mergeStableFacts(summary, additionalFacts) {
3679
+ if (additionalFacts.length === 0) return summary;
3680
+ const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
3681
+ for (const fact of additionalFacts) {
3682
+ const existing = merged.get(fact.key);
3683
+ if (!existing || (fact.confidence || 0) >= (existing.confidence || 0)) {
3684
+ merged.set(fact.key, fact);
3685
+ }
2679
3686
  }
2680
3687
  return {
2681
- enabled: opts.summarization !== false,
2682
- maxTokens: 5e4,
2683
- summaryPromptMaxTokens: 8e3,
2684
- promptTemplate: void 0
3688
+ ...summary,
3689
+ stable_facts: [...merged.values()]
2685
3690
  };
2686
3691
  }
3692
+ function runIntegrityCheck(previous, current) {
3693
+ const notes = [];
3694
+ const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
3695
+ const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
3696
+ const obsoleteKeys = new Set(current.discarded_obsolete);
3697
+ const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key));
3698
+ const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
3699
+ if (criticalFactLoss) {
3700
+ notes.push("Missing previously retained facts; merged forward during integrity repair.");
3701
+ }
3702
+ if (obsoleteFactRevived) {
3703
+ notes.push("Obsolete facts reappeared; removed during integrity repair.");
3704
+ }
3705
+ return {
3706
+ passed: !criticalFactLoss && !obsoleteFactRevived,
3707
+ criticalFactLoss,
3708
+ obsoleteFactRevived,
3709
+ notes
3710
+ };
3711
+ }
3712
+ function repairStructuredSummary(previous, current, integrity) {
3713
+ if (!previous) return current;
3714
+ const nextFacts = [...current.stable_facts];
3715
+ if (integrity.criticalFactLoss) {
3716
+ const existingKeys = new Set(nextFacts.map((fact) => fact.key));
3717
+ for (const fact of previous.stable_facts) {
3718
+ if (!existingKeys.has(fact.key) && !current.discarded_obsolete.includes(fact.key)) {
3719
+ nextFacts.push(fact);
3720
+ }
3721
+ }
3722
+ }
3723
+ const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
3724
+ return { ...current, stable_facts: filteredFacts };
3725
+ }
2687
3726
  function createContextSummarizeNode(opts) {
2688
3727
  return async (state) => {
2689
- const config = getSummarizationConfig(opts);
2690
- if (!config.enabled) return {};
3728
+ const resolved = getResolvedSmartConfig(opts, state.agent);
3729
+ const config = resolved.summarization;
3730
+ if (!config.enable) return {};
2691
3731
  const model = opts.model;
2692
3732
  const messages = state.messages || [];
2693
3733
  const onEvent = state.ctx?.__onEvent;
2694
3734
  const traceSession = state.ctx?.__traceSession;
2695
- const compressableMessages = messages.filter((m) => m.role === "tool" && m.content !== "SUMMARIZED");
3735
+ const compressableMessages = messages.filter(
3736
+ (m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
3737
+ );
2696
3738
  if (compressableMessages.length === 0) {
2697
3739
  return {};
2698
3740
  }
@@ -2704,6 +3746,9 @@ function createContextSummarizeNode(opts) {
2704
3746
  let accumulatedTokens = 0;
2705
3747
  for (let i = messages.length - 1; i >= 0; i--) {
2706
3748
  const m = messages[i];
3749
+ if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
3750
+ continue;
3751
+ }
2707
3752
  let content = "";
2708
3753
  if (typeof m.content === "string") content = m.content;
2709
3754
  else if (Array.isArray(m.content)) {
@@ -2715,39 +3760,60 @@ function createContextSummarizeNode(opts) {
2715
3760
  }
2716
3761
  let prefix = `${m.role.toUpperCase()}`;
2717
3762
  if (m.name) prefix += ` (${m.name})`;
2718
- const messageText = `${prefix}: ${content}`;
2719
- const messageTokens = countApproxTokens(messageText);
3763
+ const messageText2 = `${prefix}: ${content}`;
3764
+ const messageTokens = countApproxTokens(messageText2);
2720
3765
  if (accumulatedTokens + messageTokens > availableTokensForConversation) {
2721
3766
  messageTexts.unshift("[Earlier conversation history omitted for brevity]");
2722
3767
  break;
2723
3768
  }
2724
- messageTexts.unshift(messageText);
3769
+ messageTexts.unshift(messageText2);
2725
3770
  accumulatedTokens += messageTokens;
2726
3771
  }
2727
3772
  const conversationText = messageTexts.join("\n\n");
3773
+ const canonicalFacts = extractCanonicalFacts(compressableMessages);
2728
3774
  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.
3775
+ const previousStructuredSummary = Array.isArray(state.summaryRecords) && state.summaryRecords.length > 0 ? state.summaryRecords[state.summaryRecords.length - 1] : void 0;
3776
+ const defaultPrompt = `Summarize the conversation into strict JSON.
3777
+
3778
+ Return exactly one JSON object with this schema:
3779
+ {
3780
+ "stable_facts": [{ "key": string, "value": string, "confidence": number }],
3781
+ "active_goals": [string],
3782
+ "open_questions": [string],
3783
+ "discarded_obsolete": [string],
3784
+ "rawSummary": string
3785
+ }
3786
+
3787
+ Rules:
3788
+ - Keep stable_facts only for facts that future turns must remember.
3789
+ - Put invalidated or superseded fact keys in discarded_obsolete.
3790
+ - Preserve active_goals still relevant to the user.
3791
+ - Preserve unresolved questions.
3792
+ - Do not include prose outside JSON.
3793
+ - Target compression ratio: ${config.summaryCompressionRatioTarget}.
3794
+ - Summary mode: ${config.summaryMode}.
2735
3795
 
2736
- Previous summary (if any):
3796
+ Previous summary text:
2737
3797
  ${previousSummary || "(none)"}
2738
3798
 
3799
+ Previous structured summary:
3800
+ ${previousStructuredSummary ? JSON.stringify(previousStructuredSummary) : "(none)"}
3801
+
2739
3802
  Conversation:
2740
3803
  ${conversationText}
2741
3804
 
2742
- Summary:`;
3805
+ Canonical facts extracted from tool outputs:
3806
+ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fact.value}`).join("\n") : "(none)"}`;
2743
3807
  const template = config.promptTemplate || defaultPrompt;
2744
3808
  const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
2745
3809
  const summaryPrompt = [
2746
- systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
3810
+ systemMessage("You are a helpful assistant that summarizes conversation history efficiently. Return strict JSON only."),
2747
3811
  humanMessage(promptBody)
2748
3812
  ];
2749
3813
  const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
2750
3814
  let summaryText = "Summary unavailable.";
3815
+ let structuredSummary;
3816
+ let integrity;
2751
3817
  let outputTokensEstimate = 0;
2752
3818
  let durationMs = 0;
2753
3819
  let inputTokensActual;
@@ -2756,10 +3822,23 @@ Summary:`;
2756
3822
  let totalTokensActual;
2757
3823
  const startTime = Date.now();
2758
3824
  try {
3825
+ if (!model || typeof model.invoke !== "function") {
3826
+ throw new Error("Summarization model is unavailable.");
3827
+ }
2759
3828
  const response = await model.invoke(summaryPrompt);
2760
3829
  durationMs = Date.now() - startTime;
2761
3830
  summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2762
3831
  outputTokensEstimate = countApproxTokens(summaryText);
3832
+ const jsonText = extractJsonObject(summaryText);
3833
+ const parsed = jsonText ? JSON.parse(jsonText) : void 0;
3834
+ structuredSummary = normalizeStructuredSummary(parsed, summaryText);
3835
+ structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
3836
+ integrity = config.integrityCheck ? runIntegrityCheck(previousStructuredSummary, structuredSummary) : { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: [] };
3837
+ if (integrity && !integrity.passed) {
3838
+ structuredSummary = repairStructuredSummary(previousStructuredSummary, structuredSummary, integrity);
3839
+ integrity = { ...integrity, passed: true };
3840
+ }
3841
+ summaryText = renderStructuredSummary(structuredSummary);
2763
3842
  const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2764
3843
  const normalized = normalizeUsage(rawUsage);
2765
3844
  if (normalized) {
@@ -2770,17 +3849,22 @@ Summary:`;
2770
3849
  }
2771
3850
  } catch (err) {
2772
3851
  durationMs = Date.now() - startTime;
2773
- console.error("[ContextSummarize] Failed to generate summary:", err);
3852
+ structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
3853
+ structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
3854
+ integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
3855
+ summaryText = renderStructuredSummary(structuredSummary);
2774
3856
  const errorEvent = {
2775
3857
  type: "summarization",
2776
- summary: "",
2777
- messagesCompressed: 0,
3858
+ summary: summaryText,
3859
+ messagesCompressed: compressableMessages.length,
2778
3860
  inputTokens: inputTokensEstimate,
2779
- outputTokens: 0,
3861
+ outputTokens: countApproxTokens(summaryText),
2780
3862
  durationMs,
2781
3863
  previousSummary: previousSummary || void 0,
2782
3864
  tokenCountBefore,
2783
- tokenCountAfter: tokenCountBefore
3865
+ tokenCountAfter: tokenCountBefore,
3866
+ structuredSummary,
3867
+ integrity
2784
3868
  };
2785
3869
  onEvent?.(errorEvent);
2786
3870
  recordTraceEvent(traceSession, {
@@ -2790,11 +3874,10 @@ Summary:`;
2790
3874
  status: "error",
2791
3875
  durationMs,
2792
3876
  inputTokens: inputTokensEstimate,
2793
- outputTokens: 0,
3877
+ outputTokens: countApproxTokens(summaryText),
2794
3878
  error: { message: err?.message || String(err), stack: err?.stack },
2795
3879
  messageList: sanitizeTracePayload(summaryPrompt)
2796
3880
  });
2797
- return {};
2798
3881
  }
2799
3882
  const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
2800
3883
  messages.forEach((m, idx) => {
@@ -2808,6 +3891,9 @@ Summary:`;
2808
3891
  });
2809
3892
  const newMessages = messages.map((m) => {
2810
3893
  if (m.role === "tool") {
3894
+ if (isSyntheticSummaryToolMessage(m)) {
3895
+ return m;
3896
+ }
2811
3897
  const toolCallId = m.tool_call_id;
2812
3898
  if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
2813
3899
  return null;
@@ -2845,6 +3931,7 @@ Summary:`;
2845
3931
  content: summaryText
2846
3932
  };
2847
3933
  const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
3934
+ 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
3935
  const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
2849
3936
  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
3937
  const tokenCountAfter = countApproxTokens(allTextAfter);
@@ -2860,8 +3947,10 @@ Summary:`;
2860
3947
  previousSummary: previousSummary || void 0,
2861
3948
  tokenCountBefore,
2862
3949
  tokenCountAfter,
2863
- archivedCount: compressableMessages.length
3950
+ archivedCount: compressableMessages.length,
2864
3951
  // deprecated field for backward compatibility
3952
+ structuredSummary,
3953
+ integrity
2865
3954
  };
2866
3955
  onEvent?.(summarizationEvent);
2867
3956
  recordTraceEvent(traceSession, {
@@ -2885,12 +3974,19 @@ Summary:`;
2885
3974
  tokensSaved: tokenCountBefore - tokenCountAfter,
2886
3975
  previousSummaryLength: previousSummary?.length || 0,
2887
3976
  usageFromModel: inputTokensActual !== void 0,
2888
- cachedInputTokens
3977
+ cachedInputTokens,
3978
+ integrity
2889
3979
  }
2890
3980
  });
2891
3981
  return {
2892
3982
  messages: finalMessages,
2893
- summaries
3983
+ summaries,
3984
+ summaryRecords,
3985
+ watchdog: {
3986
+ ...state.watchdog || {},
3987
+ compactions: (state.watchdog?.compactions || 0) + 1,
3988
+ lastAction: "summarized"
3989
+ }
2894
3990
  };
2895
3991
  };
2896
3992
  }
@@ -2913,38 +4009,200 @@ async function applyTokenLimits({
2913
4009
  }
2914
4010
  return { ...state, messages: newMessages };
2915
4011
  }
4012
+ var todoStatusSchema = zod.z.enum(["not-started", "in-progress", "completed", "blocked"]);
4013
+ var todoWriteItemSchema = zod.z.object({
4014
+ id: zod.z.number().int().min(1).describe("Sequential id starting from 1"),
4015
+ step: zod.z.string().min(1).optional(),
4016
+ title: zod.z.string().min(1).optional(),
4017
+ description: zod.z.string().min(1).optional(),
4018
+ owner: zod.z.string().min(1).optional(),
4019
+ exitCriteria: zod.z.string().min(1).optional(),
4020
+ status: todoStatusSchema,
4021
+ evidence: zod.z.string().max(300).optional()
4022
+ });
4023
+ var todoUpdateItemSchema = zod.z.object({
4024
+ id: zod.z.number().int().min(1).describe("Existing todo id to update"),
4025
+ step: zod.z.string().min(1).optional(),
4026
+ title: zod.z.string().min(1).optional(),
4027
+ description: zod.z.string().min(1).optional(),
4028
+ owner: zod.z.string().min(1).optional(),
4029
+ exitCriteria: zod.z.string().min(1).optional(),
4030
+ status: todoStatusSchema.optional(),
4031
+ evidence: zod.z.string().max(300).optional()
4032
+ });
4033
+ function normalizeTodoItem(item, existing) {
4034
+ const id = item.id ?? existing?.id;
4035
+ const step = item.step || item.title || item.description || existing?.step || existing?.title || existing?.description || `Step ${id}`;
4036
+ const title = item.title || item.step || existing?.title || existing?.step || `Step ${id}`;
4037
+ const description = item.description || item.step || item.title || existing?.description || existing?.step || existing?.title || `Step ${id}`;
4038
+ return {
4039
+ ...existing,
4040
+ ...item,
4041
+ id,
4042
+ step,
4043
+ title,
4044
+ description,
4045
+ owner: item.owner || existing?.owner || "agent",
4046
+ exitCriteria: item.exitCriteria || existing?.exitCriteria || description || `Complete step ${id}`,
4047
+ status: item.status || existing?.status,
4048
+ evidence: item.evidence ?? existing?.evidence
4049
+ };
4050
+ }
4051
+ function validatePlanInvariants(todoList, requireSequentialIds) {
4052
+ const ids = todoList.map((item) => item.id);
4053
+ const uniqueIds = new Set(ids);
4054
+ if (uniqueIds.size !== ids.length) {
4055
+ return { ok: false, error: "Todo ids must be unique." };
4056
+ }
4057
+ if (requireSequentialIds) {
4058
+ const expectedIds = Array.from({ length: todoList.length }, (_, index) => index + 1);
4059
+ const isSequential = ids.every((id, index) => id === expectedIds[index]);
4060
+ if (!isSequential) {
4061
+ return { ok: false, error: "Todo ids must be sequential starting from 1 for write operations." };
4062
+ }
4063
+ }
4064
+ const inProgressCount = todoList.filter((item) => item.status === "in-progress").length;
4065
+ if (inProgressCount > 1) {
4066
+ return { ok: false, error: "Only one todo item may be in-progress at a time." };
4067
+ }
4068
+ return { ok: true };
4069
+ }
4070
+ function calculateAdherenceScore(todoList) {
4071
+ const completedCount = todoList.filter((item) => item.status === "completed").length;
4072
+ return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
4073
+ }
2916
4074
  function createContextTools(stateRef, opts) {
2917
4075
  const tools = [];
2918
4076
  if (opts?.planningEnabled) {
2919
4077
  const manageTodo = createTool({
2920
4078
  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.",
4079
+ 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
4080
  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()
4081
+ operation: zod.z.enum(["write", "read", "update"]),
4082
+ expectedVersion: zod.z.number().int().min(0).optional(),
4083
+ todoList: zod.z.array(zod.z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
2931
4084
  }),
2932
- func: async ({ operation, todoList }) => {
4085
+ func: async ({ operation, expectedVersion, todoList }) => {
2933
4086
  const onEvent = manageTodo._stateRef?.__onEvent;
4087
+ const currentVersion = stateRef.planVersion || 0;
2934
4088
  if (operation === "read") {
2935
4089
  const list = stateRef.todoList || [];
2936
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list });
4090
+ onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
2937
4091
  return list;
2938
4092
  }
2939
- if (Array.isArray(todoList)) {
2940
- stateRef.todoList = todoList;
4093
+ if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
4094
+ return {
4095
+ status: "error",
4096
+ operation,
4097
+ error: "Plan version mismatch. Read the latest plan and retry.",
4098
+ version: currentVersion,
4099
+ adherenceScore: stateRef.adherenceScore || 0
4100
+ };
4101
+ }
4102
+ if (!Array.isArray(todoList)) {
4103
+ return {
4104
+ status: "error",
4105
+ operation,
4106
+ error: "todoList is required for write and update operations.",
4107
+ version: currentVersion,
4108
+ adherenceScore: stateRef.adherenceScore || 0
4109
+ };
2941
4110
  }
4111
+ if (operation === "write") {
4112
+ const parsed = zod.z.array(todoWriteItemSchema).safeParse(todoList);
4113
+ if (!parsed.success) {
4114
+ return {
4115
+ status: "error",
4116
+ operation,
4117
+ error: parsed.error.issues[0]?.message || "Invalid todoList for write.",
4118
+ version: currentVersion,
4119
+ adherenceScore: stateRef.adherenceScore || 0
4120
+ };
4121
+ }
4122
+ const normalizedList = parsed.data.map((item) => normalizeTodoItem(item));
4123
+ const validation = validatePlanInvariants(normalizedList, true);
4124
+ if (!validation.ok) {
4125
+ return {
4126
+ status: "error",
4127
+ operation,
4128
+ error: validation.error,
4129
+ version: currentVersion,
4130
+ adherenceScore: stateRef.adherenceScore || 0
4131
+ };
4132
+ }
4133
+ stateRef.todoList = normalizedList;
4134
+ } else if (operation === "update") {
4135
+ const parsed = zod.z.array(todoUpdateItemSchema).safeParse(todoList);
4136
+ if (!parsed.success) {
4137
+ return {
4138
+ status: "error",
4139
+ operation,
4140
+ error: parsed.error.issues[0]?.message || "Invalid todoList for update.",
4141
+ version: currentVersion,
4142
+ adherenceScore: stateRef.adherenceScore || 0
4143
+ };
4144
+ }
4145
+ const currentList = Array.isArray(stateRef.todoList) ? stateRef.todoList : [];
4146
+ if (currentList.length === 0) {
4147
+ return {
4148
+ status: "error",
4149
+ operation,
4150
+ error: "No existing plan to update. Use write first.",
4151
+ version: currentVersion,
4152
+ adherenceScore: stateRef.adherenceScore || 0
4153
+ };
4154
+ }
4155
+ const patchIds = /* @__PURE__ */ new Set();
4156
+ for (const item of parsed.data) {
4157
+ if (patchIds.has(item.id)) {
4158
+ return {
4159
+ status: "error",
4160
+ operation,
4161
+ error: "Update payload contains duplicate todo ids.",
4162
+ version: currentVersion,
4163
+ adherenceScore: stateRef.adherenceScore || 0
4164
+ };
4165
+ }
4166
+ patchIds.add(item.id);
4167
+ }
4168
+ const currentMap = new Map(currentList.map((item) => [item.id, item]));
4169
+ for (const item of parsed.data) {
4170
+ if (!currentMap.has(item.id)) {
4171
+ return {
4172
+ status: "error",
4173
+ operation,
4174
+ error: `Cannot update missing todo id ${item.id}. Use write to replace the plan.`,
4175
+ version: currentVersion,
4176
+ adherenceScore: stateRef.adherenceScore || 0
4177
+ };
4178
+ }
4179
+ }
4180
+ const mergedList = currentList.map((item) => {
4181
+ const patch = parsed.data.find((candidate) => candidate.id === item.id);
4182
+ return patch ? normalizeTodoItem(patch, item) : item;
4183
+ });
4184
+ const validation = validatePlanInvariants(mergedList, false);
4185
+ if (!validation.ok) {
4186
+ return {
4187
+ status: "error",
4188
+ operation,
4189
+ error: validation.error,
4190
+ version: currentVersion,
4191
+ adherenceScore: stateRef.adherenceScore || 0
4192
+ };
4193
+ }
4194
+ stateRef.todoList = mergedList;
4195
+ }
4196
+ stateRef.planVersion = currentVersion + 1;
4197
+ stateRef.adherenceScore = calculateAdherenceScore(stateRef.todoList || []);
2942
4198
  const payload = {
2943
4199
  status: "ok",
2944
4200
  operation,
2945
- count: Array.isArray(todoList) ? todoList.length : void 0
4201
+ count: Array.isArray(todoList) ? todoList.length : void 0,
4202
+ version: stateRef.planVersion || 1,
4203
+ adherenceScore: stateRef.adherenceScore || 0
2946
4204
  };
2947
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "write", todoList });
4205
+ onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
2948
4206
  return payload;
2949
4207
  }
2950
4208
  });
@@ -2953,12 +4211,13 @@ function createContextTools(stateRef, opts) {
2953
4211
  }
2954
4212
  const getTool = createTool({
2955
4213
  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.",
4214
+ 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
4215
  schema: zod.z.object({ executionId: zod.z.string().describe("Tool execution id") }),
2958
4216
  func: async ({ executionId }) => {
2959
- let execution = stateRef.toolHistory?.find((t) => t.executionId === executionId);
4217
+ const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
4218
+ let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));
2960
4219
  if (!execution) {
2961
- execution = stateRef.toolHistoryArchived?.find((t) => t.executionId === executionId);
4220
+ execution = stateRef.toolHistoryArchived?.find((t) => matchesExecution(t));
2962
4221
  }
2963
4222
  if (execution) {
2964
4223
  return execution.rawOutput || execution.output;
@@ -2989,19 +4248,33 @@ function createContextTools(stateRef, opts) {
2989
4248
  }
2990
4249
 
2991
4250
  // src/graph/decisions.ts
4251
+ function isSyntheticSummaryMessage2(message) {
4252
+ if (!message) return false;
4253
+ if (message.role === "tool" && message.name === "summarize_context") {
4254
+ return true;
4255
+ }
4256
+ if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
4257
+ return message.tool_calls.some((toolCall) => {
4258
+ const toolName = toolCall?.function?.name || toolCall?.name;
4259
+ return toolName === "summarize_context";
4260
+ });
4261
+ }
4262
+ return false;
4263
+ }
2992
4264
  function needsSummarization(state, opts, summarizationEnabled) {
2993
4265
  if (!summarizationEnabled) {
2994
4266
  return false;
2995
4267
  }
2996
- let maxTok;
2997
- if (typeof opts.summarization === "object" && typeof opts.summarization.maxTokens === "number") {
2998
- maxTok = opts.summarization.maxTokens;
4268
+ if (state.ctx?.__needsSummarization) {
4269
+ return true;
2999
4270
  }
3000
- if (maxTok === void 0) {
3001
- maxTok = 5e4;
4271
+ const resolved = getResolvedSmartConfig(opts, state.agent);
4272
+ if (!resolved.summarization.enable) {
4273
+ return false;
3002
4274
  }
4275
+ const maxTok = resolved.summarization.summaryTriggerTokens;
3003
4276
  try {
3004
- const allText = (state.messages || []).map((m) => {
4277
+ const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
3005
4278
  if (typeof m.content === "string") return m.content;
3006
4279
  if (Array.isArray(m.content)) {
3007
4280
  return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
@@ -3012,7 +4285,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
3012
4285
  return "";
3013
4286
  }).join("\n");
3014
4287
  const tokenCount = countApproxTokens(allText);
3015
- const needsSum = tokenCount > maxTok;
4288
+ const contextRotScore = state.watchdog?.contextRotScore || 0;
4289
+ const needsSum = tokenCount > maxTok || resolved.watchdog.enabled && resolved.watchdog.autoCompaction && contextRotScore >= resolved.watchdog.contextRotThreshold;
3016
4290
  if (process.env.DEBUG_SUMMARIZATION) {
3017
4291
  const msgCount = (state.messages || []).length;
3018
4292
  const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
@@ -3022,10 +4296,10 @@ function needsSummarization(state, opts, summarizationEnabled) {
3022
4296
  });
3023
4297
  console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
3024
4298
  toolDetails.forEach((d) => console.log(` ${d}`));
3025
- console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
4299
+ console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Context rot: ${contextRotScore}, Needs summarization: ${needsSum}`);
3026
4300
  }
3027
4301
  return needsSum;
3028
- } catch (e) {
4302
+ } catch {
3029
4303
  return false;
3030
4304
  }
3031
4305
  }
@@ -3043,33 +4317,205 @@ function toolsDecisionFactory(opts, summarizationEnabled) {
3043
4317
  };
3044
4318
  }
3045
4319
 
4320
+ // src/smart/memory.ts
4321
+ function normalizeFact(scope, fact) {
4322
+ return {
4323
+ ...fact,
4324
+ scope,
4325
+ obsolete: fact.obsolete ?? false,
4326
+ lastUpdatedAt: fact.lastUpdatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4327
+ confidence: Number.isFinite(fact.confidence) ? fact.confidence : 0.5
4328
+ };
4329
+ }
4330
+ var InMemoryMemoryStore = class {
4331
+ constructor() {
4332
+ this.buckets = /* @__PURE__ */ new Map();
4333
+ }
4334
+ async get(scope, options) {
4335
+ const includeObsolete = options?.includeObsolete === true;
4336
+ 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());
4337
+ return typeof options?.limit === "number" ? all.slice(0, options.limit) : all;
4338
+ }
4339
+ async upsert(scope, facts) {
4340
+ const existing = [...this.buckets.get(scope) || []];
4341
+ for (const fact of facts) {
4342
+ const normalized = normalizeFact(scope, fact);
4343
+ const sameKey = existing.filter((entry) => entry.key === normalized.key && !entry.obsolete);
4344
+ for (const entry of sameKey) {
4345
+ if (entry.value !== normalized.value) {
4346
+ entry.obsolete = true;
4347
+ entry.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
4348
+ }
4349
+ }
4350
+ const match = existing.find((entry) => entry.key === normalized.key && entry.value === normalized.value && !entry.obsolete);
4351
+ if (match) {
4352
+ match.confidence = Math.max(match.confidence, normalized.confidence);
4353
+ match.sourceTurn = normalized.sourceTurn;
4354
+ match.ttl = normalized.ttl;
4355
+ match.tags = normalized.tags;
4356
+ match.lastUpdatedAt = normalized.lastUpdatedAt;
4357
+ } else {
4358
+ existing.push(normalized);
4359
+ }
4360
+ }
4361
+ this.buckets.set(scope, existing);
4362
+ }
4363
+ async markObsolete(scope, keys) {
4364
+ const bucket = this.buckets.get(scope) || [];
4365
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4366
+ for (const fact of bucket) {
4367
+ if (keys.includes(fact.key)) {
4368
+ fact.obsolete = true;
4369
+ fact.lastUpdatedAt = now;
4370
+ }
4371
+ }
4372
+ this.buckets.set(scope, bucket);
4373
+ }
4374
+ async semanticSearch(scope, query, options) {
4375
+ const normalizedQuery = query.toLowerCase();
4376
+ const bucket = (this.buckets.get(scope) || []).filter((fact) => !fact.obsolete);
4377
+ const scored = bucket.map((fact) => {
4378
+ const haystack = `${fact.key} ${fact.value} ${(fact.tags || []).join(" ")}`.toLowerCase();
4379
+ const score = haystack.includes(normalizedQuery) ? 2 : normalizedQuery.split(/\s+/).filter(Boolean).reduce((acc, token) => acc + (haystack.includes(token) ? 1 : 0), 0);
4380
+ return { fact, score };
4381
+ }).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || right.fact.confidence - left.fact.confidence).map((entry) => entry.fact);
4382
+ return typeof options?.limit === "number" ? scored.slice(0, options.limit) : scored;
4383
+ }
4384
+ };
4385
+ function resolveMemoryStore(config) {
4386
+ if (config.memory.store) return config.memory.store;
4387
+ if (config.memory.provider === "inMemory") return new InMemoryMemoryStore();
4388
+ return void 0;
4389
+ }
4390
+ async function readMemoryFacts(store, config, query) {
4391
+ if (!store) return [];
4392
+ const scope = config.memory.scope;
4393
+ const limit = config.context.lastTurnsToKeep;
4394
+ const policy = config.memory.readPolicy;
4395
+ if (policy === "recent_only") {
4396
+ return store.get(scope, { limit });
4397
+ }
4398
+ if (policy === "semantic" && typeof store.semanticSearch === "function") {
4399
+ return store.semanticSearch(scope, query, { limit });
4400
+ }
4401
+ if (policy === "hybrid") {
4402
+ const recent = await store.get(scope, { limit: Math.max(3, Math.ceil(limit / 2)) });
4403
+ const semantic = typeof store.semanticSearch === "function" ? await store.semanticSearch(scope, query, { limit: Math.max(3, Math.ceil(limit / 2)) }) : [];
4404
+ const deduped = /* @__PURE__ */ new Map();
4405
+ for (const fact of [...semantic, ...recent]) {
4406
+ deduped.set(`${fact.key}:${fact.value}`, fact);
4407
+ }
4408
+ return [...deduped.values()].slice(0, limit);
4409
+ }
4410
+ return store.get(scope, { limit });
4411
+ }
4412
+ async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
4413
+ if (!store || !summary) return [];
4414
+ const facts = summary.stable_facts.map((fact) => ({
4415
+ key: fact.key,
4416
+ value: fact.value,
4417
+ sourceTurn,
4418
+ confidence: fact.confidence ?? 0.8,
4419
+ obsolete: false,
4420
+ tags: ["summary_fact"]
4421
+ }));
4422
+ if (summary.discarded_obsolete.length > 0) {
4423
+ await store.markObsolete(config.memory.scope, summary.discarded_obsolete);
4424
+ }
4425
+ if (facts.length > 0) {
4426
+ await store.upsert(config.memory.scope, facts);
4427
+ }
4428
+ return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
4429
+ }
4430
+ function renderMemoryFacts(facts) {
4431
+ if (facts.length === 0) return "";
4432
+ return [
4433
+ "Memory facts:",
4434
+ ...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
4435
+ ].join("\n");
4436
+ }
4437
+
3046
4438
  // src/smart/index.ts
3047
4439
  function createSmartAgent(opts) {
3048
- const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0 };
3049
- const planningEnabled = opts.useTodoList === true;
4440
+ const resolved = normalizeSmartAgentOptions(opts);
4441
+ const planningEnabled = resolved.planning.mode !== "off";
4442
+ const memoryStore = resolveMemoryStore(resolved);
4443
+ const runtimeOpts = {
4444
+ ...opts,
4445
+ runtimeProfile: resolved.runtimeProfile,
4446
+ limits: resolved.limits,
4447
+ summarization: resolved.summarization,
4448
+ context: resolved.context,
4449
+ planning: resolved.planning,
4450
+ delegation: resolved.delegation,
4451
+ memory: { ...resolved.memory, store: memoryStore },
4452
+ toolResponses: resolved.toolResponses,
4453
+ watchdog: resolved.watchdog,
4454
+ useTodoList: planningEnabled
4455
+ };
4456
+ const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
3050
4457
  const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
3051
4458
  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);
4459
+ const base = createAgent({ ...runtimeOpts, tools: mergedTools });
4460
+ base.__runtime.runtimeProfile = resolved.runtimeProfile;
4461
+ base.__runtime.smart = resolved;
4462
+ const summarizationEnabled = resolved.summarization.enable !== false;
4463
+ const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
4464
+ const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
4465
+ const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
3062
4466
  const structuredOutputHint = opts.outputSchema ? [
3063
4467
  "A structured output schema is active.",
3064
4468
  "Do NOT output the final JSON directly as an assistant message.",
3065
4469
  "When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
3066
4470
  "Call it exactly once then STOP producing further assistant messages."
3067
4471
  ].join("\n") : "";
4472
+ const runtimeHint = [
4473
+ resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
4474
+ `Context policy: ${resolved.context.policy}.`,
4475
+ `Planning mode: ${resolved.planning.mode}. Replan policy: ${resolved.planning.replanPolicy}.`,
4476
+ `Delegation mode: ${resolved.delegation.mode}. Child context policy: ${resolved.delegation.childContextPolicy}.`,
4477
+ `Tool response policy: ${resolved.context.toolResponsePolicy}.`
4478
+ ].join("\n");
4479
+ function latestUserPrompt(messages) {
4480
+ const latest = [...messages].reverse().find((message) => message.role === "user");
4481
+ if (!latest) return "";
4482
+ return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
4483
+ }
4484
+ async function syncMemory(state) {
4485
+ const query = latestUserPrompt(state.messages || []);
4486
+ const memoryFacts = await readMemoryFacts(memoryStore, resolved, query);
4487
+ return { ...state, memoryFacts };
4488
+ }
4489
+ async function persistLatestSummary(state) {
4490
+ if (!memoryStore) return state;
4491
+ if (resolved.memory.writePolicy === "manual") return state;
4492
+ const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
4493
+ if (!latestSummary) return state;
4494
+ if (resolved.memory.writePolicy === "auto_important" && latestSummary.stable_facts.length === 0) {
4495
+ return state;
4496
+ }
4497
+ const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
4498
+ return { ...state, memoryFacts };
4499
+ }
4500
+ function syncPlanState(state) {
4501
+ if (!Array.isArray(stateRef.todoList)) return state;
4502
+ return {
4503
+ ...state,
4504
+ plan: {
4505
+ version: stateRef.planVersion || state.planVersion || 1,
4506
+ steps: stateRef.todoList,
4507
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
4508
+ adherenceScore: stateRef.adherenceScore || 0
4509
+ },
4510
+ planVersion: stateRef.planVersion || state.planVersion || 1
4511
+ };
4512
+ }
3068
4513
  function systemMessage2() {
3069
4514
  const sys = buildSystemPrompt(
3070
- [opts.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
3071
- opts.useTodoList === true,
3072
- opts.name || "Agent"
4515
+ [opts.systemPrompt, runtimeHint, structuredOutputHint].filter(Boolean).join("\n"),
4516
+ planningEnabled,
4517
+ opts.name || "Agent",
4518
+ opts.todoListPrompt
3073
4519
  );
3074
4520
  return { role: "system", content: sys };
3075
4521
  }
@@ -3077,38 +4523,81 @@ function createSmartAgent(opts) {
3077
4523
  invoke: async (input, config) => {
3078
4524
  stateRef.toolHistory = input.toolHistory;
3079
4525
  stateRef.toolHistoryArchived = input.toolHistoryArchived;
4526
+ stateRef.todoList = input.plan?.steps;
4527
+ stateRef.planVersion = input.planVersion || input.plan?.version || 0;
4528
+ stateRef.adherenceScore = input.plan?.adherenceScore || 0;
3080
4529
  const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
3081
4530
  const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
3082
- let state = { ...input, messages: seedMessages };
4531
+ let state = await syncMemory({ ...input, messages: seedMessages });
3083
4532
  let lastResult = null;
3084
- const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
4533
+ let rawMessages = [...seedMessages];
4534
+ const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
3085
4535
  const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
4536
+ let previousTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
3086
4537
  for (let i = 0; i < iterationLimit; i++) {
4538
+ state = { ...state, messages: rawMessages };
3087
4539
  const next = summarizationEnabled ? decideBefore(state) : "agent";
3088
4540
  if (next === "contextSummarize" && summarizer) {
3089
4541
  const delta = await summarizer(state);
3090
4542
  if (!delta || Object.keys(delta).length === 0) ; else {
3091
- state = { ...state, ...delta };
4543
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4544
+ rawMessages = [...state.messages || rawMessages];
3092
4545
  continue;
3093
4546
  }
3094
4547
  }
3095
- const res = await base.invoke(state, config);
4548
+ const modelMessages = buildModelMessages(state, resolved);
4549
+ const res = await base.invoke({ ...state, messages: modelMessages }, config);
3096
4550
  lastResult = res;
3097
4551
  const currentSummaries = state.summaries;
4552
+ const currentSummaryRecords = state.summaryRecords;
4553
+ const currentPlan = state.plan;
4554
+ const appendedMessages = res.messages.slice(modelMessages.length);
4555
+ rawMessages = [...rawMessages, ...appendedMessages];
3098
4556
  state = res.state || { ...state, messages: res.messages };
4557
+ state = { ...state, messages: rawMessages };
3099
4558
  if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
3100
4559
  state = { ...state, summaries: currentSummaries };
3101
4560
  }
3102
- if (state.ctx?.__awaitingApproval || state.ctx?.__waitingForInput) break;
4561
+ if (currentSummaryRecords && currentSummaryRecords.length > 0 && (!state.summaryRecords || state.summaryRecords.length === 0)) {
4562
+ state = { ...state, summaryRecords: currentSummaryRecords };
4563
+ }
4564
+ if (currentPlan && !state.plan) {
4565
+ state = { ...state, plan: currentPlan };
4566
+ }
4567
+ state = syncPlanState(await syncMemory(state));
4568
+ stateRef.toolHistory = state.toolHistory;
4569
+ stateRef.toolHistoryArchived = state.toolHistoryArchived;
4570
+ const currentTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
4571
+ const tokenDrift = Math.max(0, currentTokenCount - previousTokenCount);
4572
+ const toolMessagesThisTurn = appendedMessages.filter((message) => message.role === "tool").length;
4573
+ const overToolingRate = appendedMessages.length === 0 ? 0 : Number((toolMessagesThisTurn / appendedMessages.length).toFixed(2));
4574
+ state = {
4575
+ ...state,
4576
+ watchdog: {
4577
+ tokenDrift,
4578
+ contextRotScore: estimateContextRotScore(state),
4579
+ overToolingRate,
4580
+ compactions: state.watchdog?.compactions || 0,
4581
+ lastAction: appendedMessages.length > 0 ? "agent_turn" : state.watchdog?.lastAction
4582
+ }
4583
+ };
4584
+ previousTokenCount = currentTokenCount;
4585
+ if (resolved.watchdog.enabled && resolved.watchdog.autoCompaction) {
4586
+ const shouldCompact = tokenDrift >= resolved.watchdog.tokenDriftThreshold || (state.watchdog?.overToolingRate || 0) >= resolved.watchdog.overToolingSpikeThreshold || (state.watchdog?.contextRotScore || 0) >= resolved.watchdog.contextRotThreshold;
4587
+ if (shouldCompact) {
4588
+ state = { ...state, ctx: { ...state.ctx || {}, __needsSummarization: true } };
4589
+ }
4590
+ }
3103
4591
  if (state.ctx?.__needsSummarization && summarizer) {
3104
4592
  const ctx = { ...state.ctx || {} };
3105
4593
  delete ctx.__needsSummarization;
3106
4594
  state = { ...state, ctx };
3107
4595
  const delta = await summarizer(state);
3108
4596
  if (delta && Object.keys(delta).length > 0) {
3109
- state = { ...state, ...delta };
4597
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4598
+ rawMessages = [...state.messages || rawMessages];
4599
+ continue;
3110
4600
  }
3111
- continue;
3112
4601
  }
3113
4602
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3114
4603
  if (summarizationEnabled) {
@@ -3116,7 +4605,8 @@ function createSmartAgent(opts) {
3116
4605
  if (after === "contextSummarize" && summarizer) {
3117
4606
  const delta = await summarizer(state);
3118
4607
  if (delta && Object.keys(delta).length > 0) {
3119
- state = { ...state, ...delta };
4608
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4609
+ rawMessages = [...state.messages || rawMessages];
3120
4610
  continue;
3121
4611
  }
3122
4612
  }
@@ -3129,10 +4619,12 @@ function createSmartAgent(opts) {
3129
4619
  }
3130
4620
  if (state.summaries && state.summaries.length > 0) {
3131
4621
  if (lastResult.state) {
3132
- lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
4622
+ 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
4623
  } else {
3134
- lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
4624
+ 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
4625
  }
4626
+ } else if (lastResult.state) {
4627
+ lastResult = { ...lastResult, state: { ...lastResult.state, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
3136
4628
  }
3137
4629
  return lastResult;
3138
4630
  },
@@ -3146,6 +4638,141 @@ function createSmartAgent(opts) {
3146
4638
  return instance;
3147
4639
  }
3148
4640
 
4641
+ // src/smart/eval.ts
4642
+ function normalizeProfileTarget(target) {
4643
+ if (typeof target === "string") {
4644
+ return {
4645
+ label: target,
4646
+ runtimeProfile: target,
4647
+ baseProfile: target === "custom" ? "balanced" : target
4648
+ };
4649
+ }
4650
+ return {
4651
+ label: target.label,
4652
+ runtimeProfile: target.runtimeProfile,
4653
+ baseProfile: target.baseProfile ?? (target.runtimeProfile === "custom" ? target.customProfile?.extends ?? "balanced" : target.runtimeProfile),
4654
+ customProfile: target.customProfile
4655
+ };
4656
+ }
4657
+ function normalizeText(value) {
4658
+ return (value || "").toLowerCase();
4659
+ }
4660
+ function ratio(numerator, denominator) {
4661
+ if (denominator <= 0) return 1;
4662
+ return Number((numerator / denominator).toFixed(3));
4663
+ }
4664
+ function sum(values) {
4665
+ return values.reduce((acc, value) => acc + value, 0);
4666
+ }
4667
+ function mean(values) {
4668
+ if (values.length === 0) return 0;
4669
+ return Number((sum(values) / values.length).toFixed(3));
4670
+ }
4671
+ function extractTotalTokens(usage) {
4672
+ if (!usage?.totals || typeof usage.totals !== "object") return 0;
4673
+ return Object.values(usage.totals).reduce((acc, entry) => acc + (Number(entry?.total) || 0), 0);
4674
+ }
4675
+ function caseResultFromRun(descriptor, testCase, content, events, state, latencyMs, totalTokens) {
4676
+ const normalizedContent = normalizeText(content);
4677
+ const matchedPhrases = (testCase.expectedPhrases || []).filter((phrase) => normalizedContent.includes(normalizeText(phrase)));
4678
+ const matchedFacts = (testCase.expectedFacts || []).filter((fact) => {
4679
+ const memoryMatch = state?.memoryFacts?.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value)));
4680
+ const summaryMatch = state?.summaryRecords?.some((record) => record.stable_facts.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value))));
4681
+ return Boolean(memoryMatch || summaryMatch || normalizedContent.includes(normalizeText(fact.value)));
4682
+ });
4683
+ const forbiddenMisses = (testCase.forbiddenPhrases || []).filter((phrase) => !normalizedContent.includes(normalizeText(phrase)));
4684
+ const toolCalls = events.filter((event) => event.type === "tool_call" && event.phase === "success");
4685
+ const expectedToolHits = (testCase.expectedToolNames || []).filter((toolName) => toolCalls.some((event) => event.name === toolName));
4686
+ const toolPrecision = ratio(expectedToolHits.length, Math.max(toolCalls.length, 1));
4687
+ const recallAccuracy = ratio(matchedPhrases.length + matchedFacts.length, (testCase.expectedPhrases?.length || 0) + (testCase.expectedFacts?.length || 0));
4688
+ const obsoleteDropAccuracy = ratio(forbiddenMisses.length, testCase.forbiddenPhrases?.length || 0);
4689
+ const trajectoryScore = ratio(expectedToolHits.length, testCase.expectedToolNames?.length || 0);
4690
+ const hadRecoverableError = events.some((event) => event.type === "tool_call" && event.phase === "error");
4691
+ const recoveryRate = hadRecoverableError ? Number((normalizedContent.length > 0 ? 1 : 0).toFixed(3)) : 1;
4692
+ const overToolingRate = toolCalls.length === 0 ? 0 : Number((Math.max(0, toolCalls.length - Math.max(testCase.expectedToolNames?.length || 0, 1)) / toolCalls.length).toFixed(3));
4693
+ const success = recallAccuracy >= 0.8 && obsoleteDropAccuracy >= 0.8;
4694
+ return {
4695
+ id: testCase.id,
4696
+ family: testCase.family,
4697
+ success,
4698
+ recallAccuracy,
4699
+ obsoleteDropAccuracy,
4700
+ trajectoryScore: Number(((trajectoryScore + toolPrecision) / 2).toFixed(3)),
4701
+ recoveryRate,
4702
+ overToolingRate,
4703
+ latencyMs,
4704
+ totalTokens,
4705
+ profile: descriptor.runtimeProfile,
4706
+ profileLabel: descriptor.label,
4707
+ baseProfile: descriptor.baseProfile,
4708
+ notes: success ? [] : [
4709
+ recallAccuracy < 0.8 ? "Recall below threshold." : "",
4710
+ obsoleteDropAccuracy < 0.8 ? "Obsolete filtering below threshold." : "",
4711
+ trajectoryScore < 0.5 ? "Tool trajectory diverged from expectation." : ""
4712
+ ].filter(Boolean)
4713
+ };
4714
+ }
4715
+ function aggregateMetrics(caseResults) {
4716
+ const taskSuccess = ratio(caseResults.filter((result) => result.success).length, caseResults.length);
4717
+ const recallAccuracy = mean(caseResults.map((result) => result.recallAccuracy));
4718
+ const obsoleteDropAccuracy = mean(caseResults.map((result) => result.obsoleteDropAccuracy));
4719
+ const trajectoryScore = mean(caseResults.map((result) => result.trajectoryScore));
4720
+ const recoveryRate = mean(caseResults.map((result) => result.recoveryRate));
4721
+ const overToolingRate = mean(caseResults.map((result) => result.overToolingRate));
4722
+ const latencyMs = mean(caseResults.map((result) => result.latencyMs));
4723
+ const totalTokens = sum(caseResults.map((result) => result.totalTokens || 0));
4724
+ const toolPrecision = trajectoryScore;
4725
+ const costScore = totalTokens <= 0 ? 1 : Number((1 / (1 + totalTokens / 1e4)).toFixed(3));
4726
+ const score = Number((0.45 * taskSuccess + 0.2 * trajectoryScore + 0.15 * toolPrecision + 0.1 * recoveryRate + 0.1 * costScore).toFixed(3));
4727
+ return {
4728
+ taskSuccess,
4729
+ recallAccuracy,
4730
+ obsoleteDropAccuracy,
4731
+ trajectoryScore,
4732
+ recoveryRate,
4733
+ overToolingRate,
4734
+ latencyMs,
4735
+ totalTokens,
4736
+ score
4737
+ };
4738
+ }
4739
+ async function runSmartAgentEvalHarness(params) {
4740
+ const results = [];
4741
+ for (const profileTarget of params.profiles) {
4742
+ const descriptor = normalizeProfileTarget(profileTarget);
4743
+ const caseResults = [];
4744
+ for (const testCase of params.cases) {
4745
+ const agent = params.createAgent(descriptor.runtimeProfile, descriptor);
4746
+ const events = [];
4747
+ const startedAt = Date.now();
4748
+ const result = await agent.invoke({
4749
+ messages: [{ role: "user", content: testCase.prompt }]
4750
+ }, {
4751
+ onEvent: (event) => {
4752
+ events.push(event);
4753
+ }
4754
+ });
4755
+ caseResults.push(caseResultFromRun(
4756
+ descriptor,
4757
+ testCase,
4758
+ result.content,
4759
+ events,
4760
+ result.state,
4761
+ Date.now() - startedAt,
4762
+ extractTotalTokens(result.metadata?.usage)
4763
+ ));
4764
+ }
4765
+ results.push({
4766
+ profile: descriptor.runtimeProfile,
4767
+ profileLabel: descriptor.label,
4768
+ baseProfile: descriptor.baseProfile,
4769
+ metrics: aggregateMetrics(caseResults),
4770
+ cases: caseResults
4771
+ });
4772
+ }
4773
+ return results;
4774
+ }
4775
+
3149
4776
  // src/utils/content.ts
3150
4777
  function contentToString(content) {
3151
4778
  try {
@@ -3423,7 +5050,9 @@ function createCodeGuardrail(options) {
3423
5050
  });
3424
5051
  }
3425
5052
 
5053
+ exports.DEFAULT_PROFILE_CONFIGS = DEFAULT_PROFILE_CONFIGS;
3426
5054
  exports.GuardrailPhase = GuardrailPhase;
5055
+ exports.InMemoryMemoryStore = InMemoryMemoryStore;
3427
5056
  exports.agentVerdictRule = agentVerdictRule;
3428
5057
  exports.applyTokenLimits = applyTokenLimits;
3429
5058
  exports.buildSystemPrompt = buildSystemPrompt;
@@ -3451,13 +5080,24 @@ exports.evaluateGuardrails = evaluateGuardrails;
3451
5080
  exports.fileSink = fileSink;
3452
5081
  exports.fromLangchainModel = fromLangchainModel;
3453
5082
  exports.fromLangchainTools = fromLangchainTools;
5083
+ exports.generateSpanId = generateSpanId;
5084
+ exports.generateTraceId = generateTraceId;
5085
+ exports.getResolvedSmartConfig = getResolvedSmartConfig;
3454
5086
  exports.httpSink = httpSink;
3455
5087
  exports.isSmartModel = isSmartModel;
3456
5088
  exports.jsonSchemaRule = jsonSchemaRule;
5089
+ exports.normalizeSmartAgentOptions = normalizeSmartAgentOptions;
5090
+ exports.otlpSink = otlpSink;
5091
+ exports.readMemoryFacts = readMemoryFacts;
3457
5092
  exports.regexRule = regexRule;
5093
+ exports.renderMemoryFacts = renderMemoryFacts;
5094
+ exports.resolveMemoryStore = resolveMemoryStore;
3458
5095
  exports.resolveToolApprovalState = resolveToolApprovalState;
3459
5096
  exports.restoreSnapshot = restoreSnapshot;
5097
+ exports.runSmartAgentEvalHarness = runSmartAgentEvalHarness;
3460
5098
  exports.startStreamingSession = startStreamingSession;
5099
+ exports.traceSessionToOtlp = traceSessionToOtlp;
3461
5100
  exports.withTools = withTools;
5101
+ exports.writeSummaryFactsToMemory = writeSummaryFactsToMemory;
3462
5102
  //# sourceMappingURL=index.cjs.map
3463
5103
  //# sourceMappingURL=index.cjs.map