@cognipeer/agent-sdk 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,9 +1,10 @@
1
- import { z } from 'zod';
1
+ import { z, ZodError } from 'zod';
2
2
  import { createRequire } from 'module';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { Buffer } from 'buffer';
6
6
  import { nanoid } from 'nanoid';
7
+ import crypto from 'crypto';
7
8
  import AjvModule from 'ajv';
8
9
 
9
10
  // src/adapters/langchain.ts
@@ -215,7 +216,8 @@ function createTool({
215
216
  func,
216
217
  needsApproval,
217
218
  approvalPrompt,
218
- approvalDefaults
219
+ approvalDefaults,
220
+ maxExecutionsPerRun
219
221
  }) {
220
222
  const execute = async (input) => func(input);
221
223
  const toolRecord = {
@@ -237,25 +239,45 @@ function createTool({
237
239
  if (approvalDefaults !== void 0) {
238
240
  toolRecord.approvalDefaults = approvalDefaults;
239
241
  }
242
+ if (maxExecutionsPerRun !== void 0) {
243
+ toolRecord.maxExecutionsPerRun = maxExecutionsPerRun;
244
+ }
240
245
  toolRecord.__source = toolRecord.__source || "smart";
241
246
  toolRecord.__impl = func;
242
247
  return toolRecord;
243
248
  }
244
249
 
245
250
  // src/prompts.ts
246
- function buildSystemPrompt(extra, planning, name = "Agent") {
247
- const extraTrimmed = extra?.trim();
248
- const agentHeader = `Agent Name: ${name}`;
249
- const planningBlock = planning ? `<planning>
250
- PLANNING IS MANDATORY.
251
+ var DEFAULT_TODO_LIST_PROMPT = `Planning tools are available when they materially improve execution.
251
252
 
252
253
  Rules:
253
- 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.
254
- 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.
255
- 3) Keep exactly ONE item "in-progress" at any time; all others are "not-started" or "completed".
256
- 4) Never reveal the plan text. Never summarize the plan in the assistant messages.
257
- 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".
258
- </planning>` : "";
254
+ 1) Use "manage_todo_list" for multi-step work, delegation, recovery after a failed attempt, or when the user explicitly asks for a plan.
255
+ 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.
256
+ 3) If a task is multi-step and no valid plan exists yet, create one before substantial execution.
257
+ 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.
258
+ 5) When using operation="update", send only the changed items. Do not resend unchanged plan items unless the whole plan structure is being rewritten.
259
+ 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.
260
+ 7) Keep exactly ONE item "in-progress" at a time.
261
+ 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.
262
+ 9) If tool results materially change the task state, sync the plan before the final answer.
263
+ 10) Reuse prior tool results already present in the conversation before calling tools again.
264
+ 11) If an update fails because of a version mismatch, read the latest plan and retry instead of blindly rewriting it.
265
+ 12) Repeating full operation="write" calls for the same plan is a mistake unless the plan structure itself changed.
266
+ 13) Keep the plan internal unless the user explicitly asks to see it.`;
267
+ function buildPlanningBlock(todoListPrompt) {
268
+ const promptBody = todoListPrompt?.trim() || DEFAULT_TODO_LIST_PROMPT;
269
+ if (promptBody.startsWith("<planning>")) {
270
+ return promptBody;
271
+ }
272
+ return `<planning>
273
+ ${promptBody}
274
+ </planning>`;
275
+ }
276
+ function buildSystemPrompt(extra, planning, name = "Agent", todoListPrompt) {
277
+ const extraTrimmed = extra?.trim();
278
+ const agentHeader = `Agent Name: ${name}`;
279
+ const planningMode = typeof planning === "string" ? planning : planning ? "todo" : "off";
280
+ const planningBlock = planningMode !== "off" ? buildPlanningBlock(todoListPrompt) : "";
259
281
  return [
260
282
  agentHeader,
261
283
  "You are an advanced AI agent that is concise, accurate, and helpful.",
@@ -367,6 +389,9 @@ function cognipeerSink(first, second) {
367
389
  function httpSink(url, headers) {
368
390
  return { type: "http", url, headers };
369
391
  }
392
+ function otlpSink(endpoint, headers) {
393
+ return { type: "otlp", endpoint, headers };
394
+ }
370
395
  function resolveLogsBaseDir(customPath, ensureDirectory = true) {
371
396
  const root = process.cwd();
372
397
  const base = customPath && customPath.trim().length > 0 ? customPath : path.join(root, "logs");
@@ -566,6 +591,14 @@ function resolveSink(sink) {
566
591
  const headers = candidate.headers ? { ...candidate.headers } : void 0;
567
592
  return headers ? { type: "http", url, headers } : { type: "http", url };
568
593
  }
594
+ case "otlp": {
595
+ const endpoint = typeof candidate.endpoint === "string" ? candidate.endpoint.trim() : "";
596
+ if (!endpoint) {
597
+ throw new Error("otlp sink requires a non-empty endpoint");
598
+ }
599
+ const headers = candidate.headers ? { ...candidate.headers } : void 0;
600
+ return headers ? { type: "otlp", endpoint, headers } : { type: "otlp", endpoint };
601
+ }
569
602
  default: {
570
603
  if (typeof console !== "undefined" && typeof console.warn === "function") {
571
604
  console.warn("Unknown tracing sink type. Falling back to file sink.", candidate);
@@ -585,6 +618,8 @@ function snapshotSink(runtime) {
585
618
  return { type: "cognipeer", url: sink.url };
586
619
  case "http":
587
620
  return { type: "http", url: sink.url };
621
+ case "otlp":
622
+ return { type: "otlp", endpoint: sink.endpoint };
588
623
  default:
589
624
  return { type: "custom" };
590
625
  }
@@ -735,6 +770,202 @@ async function postStreamingSessionEnd(baseUrl, headers, payload) {
735
770
  throw err;
736
771
  }
737
772
  }
773
+ function isoToUnixNano(iso) {
774
+ const ms = new Date(iso).getTime();
775
+ return `${ms}000000`;
776
+ }
777
+ function toOtlpStatusCode(status) {
778
+ if (status === "error") return 2;
779
+ if (status === "success" || status === "completed") return 1;
780
+ return 0;
781
+ }
782
+ function toOtlpSpanKind(type) {
783
+ switch (type) {
784
+ case "ai_call":
785
+ return 3;
786
+ // CLIENT — outgoing LLM request
787
+ case "tool_call":
788
+ return 1;
789
+ // INTERNAL
790
+ case "agent_iteration":
791
+ return 1;
792
+ // INTERNAL
793
+ default:
794
+ return 1;
795
+ }
796
+ }
797
+ function stringAttr(key, val) {
798
+ return val != null ? { key, value: { stringValue: val } } : null;
799
+ }
800
+ function intAttr(key, val) {
801
+ return val != null && !Number.isNaN(val) ? { key, value: { intValue: String(val) } } : null;
802
+ }
803
+ function doubleAttr(key, val) {
804
+ return val != null && !Number.isNaN(val) ? { key, value: { doubleValue: val } } : null;
805
+ }
806
+ function eventToOtlpSpan(event) {
807
+ const startNano = isoToUnixNano(event.timestamp);
808
+ const endNano = event.durationMs != null ? `${new Date(new Date(event.timestamp).getTime() + event.durationMs).getTime()}000000` : startNano;
809
+ const attrs = [
810
+ stringAttr("cognipeer.event.type", event.type),
811
+ stringAttr("cognipeer.event.label", event.label),
812
+ intAttr("cognipeer.event.sequence", event.sequence),
813
+ stringAttr("cognipeer.actor.scope", event.actor?.scope),
814
+ stringAttr("cognipeer.actor.name", event.actor?.name),
815
+ stringAttr("cognipeer.actor.role", event.actor?.role),
816
+ stringAttr("cognipeer.model", event.model),
817
+ stringAttr("cognipeer.provider", event.provider),
818
+ intAttr("cognipeer.tokens.input", event.inputTokens),
819
+ intAttr("cognipeer.tokens.output", event.outputTokens),
820
+ intAttr("cognipeer.tokens.total", event.totalTokens),
821
+ intAttr("cognipeer.tokens.cached_input", event.cachedInputTokens),
822
+ intAttr("cognipeer.bytes.request", event.requestBytes),
823
+ intAttr("cognipeer.bytes.response", event.responseBytes),
824
+ stringAttr("cognipeer.tool.execution_id", event.toolExecutionId),
825
+ stringAttr("cognipeer.retry_of", event.retryOf),
826
+ stringAttr("cognipeer.event.id", event.id)
827
+ ].filter(Boolean);
828
+ if (event.data?.sections && event.data.sections.length > 0) {
829
+ try {
830
+ attrs.push({ key: "cognipeer.sections", value: { stringValue: JSON.stringify(event.data.sections) } });
831
+ } catch {
832
+ }
833
+ }
834
+ const span = {
835
+ traceId: event.traceId,
836
+ spanId: event.spanId,
837
+ parentSpanId: event.parentSpanId || "",
838
+ name: event.label || event.type,
839
+ kind: toOtlpSpanKind(event.type),
840
+ startTimeUnixNano: startNano,
841
+ endTimeUnixNano: endNano,
842
+ attributes: attrs,
843
+ status: {
844
+ code: toOtlpStatusCode(event.status),
845
+ ...event.status === "error" && event.error?.message ? { message: event.error.message } : {}
846
+ }
847
+ };
848
+ if (event.error) {
849
+ span.events = [{
850
+ timeUnixNano: startNano,
851
+ name: "exception",
852
+ attributes: [
853
+ { key: "exception.message", value: { stringValue: event.error.message } },
854
+ ...event.error.stack ? [{ key: "exception.stacktrace", value: { stringValue: event.error.stack } }] : []
855
+ ]
856
+ }];
857
+ }
858
+ return span;
859
+ }
860
+ function traceSessionToOtlp(session) {
861
+ const traceId = session.traceId || generateTraceId();
862
+ const rootSpanId = session.rootSpanId || generateSpanId();
863
+ const startNano = isoToUnixNano(session.startedAt);
864
+ const endNano = session.endedAt ? isoToUnixNano(session.endedAt) : startNano;
865
+ const resourceAttrs = [
866
+ { key: "service.name", value: { stringValue: session.agent?.name || "cognipeer-agent" } },
867
+ stringAttr("service.version", session.agent?.version),
868
+ stringAttr("cognipeer.session.id", session.sessionId),
869
+ stringAttr("cognipeer.thread.id", session.threadId),
870
+ stringAttr("cognipeer.agent.model", session.agent?.model),
871
+ stringAttr("cognipeer.agent.provider", session.agent?.provider)
872
+ ].filter(Boolean);
873
+ const rootAttrs = [
874
+ stringAttr("cognipeer.session.status", session.status),
875
+ doubleAttr("cognipeer.session.duration_ms", session.durationMs),
876
+ intAttr("cognipeer.session.total_input_tokens", session.summary.totalInputTokens),
877
+ intAttr("cognipeer.session.total_output_tokens", session.summary.totalOutputTokens),
878
+ intAttr("cognipeer.session.total_cached_input_tokens", session.summary.totalCachedInputTokens),
879
+ intAttr("cognipeer.session.total_bytes_in", session.summary.totalBytesIn),
880
+ intAttr("cognipeer.session.total_bytes_out", session.summary.totalBytesOut),
881
+ intAttr("cognipeer.session.event_count", session.events.length)
882
+ ].filter(Boolean);
883
+ if (session.summary.eventCounts) {
884
+ for (const [key, count] of Object.entries(session.summary.eventCounts)) {
885
+ rootAttrs.push({ key: `cognipeer.session.event_count.${key}`, value: { intValue: String(count) } });
886
+ }
887
+ }
888
+ const rootSpan = {
889
+ traceId,
890
+ spanId: rootSpanId,
891
+ parentSpanId: "",
892
+ name: `agent_session: ${session.agent?.name || "agent"}`,
893
+ kind: 1,
894
+ // INTERNAL
895
+ startTimeUnixNano: startNano,
896
+ endTimeUnixNano: endNano,
897
+ attributes: rootAttrs,
898
+ status: {
899
+ code: toOtlpStatusCode(session.status),
900
+ ...session.status === "error" && session.errors.length > 0 ? { message: session.errors.map((e) => e.message).join("; ") } : {}
901
+ }
902
+ };
903
+ if (session.errors.length > 0) {
904
+ rootSpan.events = session.errors.map((err) => ({
905
+ timeUnixNano: err.timestamp ? isoToUnixNano(err.timestamp) : startNano,
906
+ name: "exception",
907
+ attributes: [
908
+ { key: "exception.message", value: { stringValue: err.message } },
909
+ ...err.stack ? [{ key: "exception.stacktrace", value: { stringValue: err.stack } }] : [],
910
+ ...err.type ? [{ key: "exception.type", value: { stringValue: err.type } }] : []
911
+ ]
912
+ }));
913
+ }
914
+ const childSpans = session.events.map((event) => {
915
+ const span = eventToOtlpSpan({
916
+ ...event,
917
+ traceId: event.traceId || traceId,
918
+ spanId: event.spanId || generateSpanId(),
919
+ parentSpanId: event.parentSpanId || rootSpanId
920
+ });
921
+ return span;
922
+ });
923
+ return {
924
+ resourceSpans: [{
925
+ resource: { attributes: resourceAttrs },
926
+ scopeSpans: [{
927
+ scope: {
928
+ name: "cognipeer-agent-sdk",
929
+ version: "1.0.0"
930
+ },
931
+ spans: [rootSpan, ...childSpans]
932
+ }]
933
+ }]
934
+ };
935
+ }
936
+ async function postOtlpTraces(endpoint, headers, payload) {
937
+ const fetchFn = typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0;
938
+ if (!fetchFn) {
939
+ throw new Error("OTLP sink requires fetch to be available in this runtime.");
940
+ }
941
+ const finalHeaders = {
942
+ "content-type": "application/json",
943
+ ...headers || {}
944
+ };
945
+ try {
946
+ const response = await fetchFn(endpoint, {
947
+ method: "POST",
948
+ headers: finalHeaders,
949
+ body: JSON.stringify(payload)
950
+ });
951
+ if (!response.ok) {
952
+ let responseText = "";
953
+ try {
954
+ responseText = await response.text();
955
+ } catch {
956
+ }
957
+ const statusLine = `HTTP ${response.status}${response.statusText ? ` ${response.statusText}` : ""}`;
958
+ const bodyPreview = responseText ? ` - ${responseText.slice(0, 200)}` : "";
959
+ throw new Error(`${statusLine}${bodyPreview}`);
960
+ }
961
+ } catch (err) {
962
+ const errMsg = err instanceof Error ? err.message : String(err);
963
+ if (typeof console !== "undefined" && typeof console.error === "function") {
964
+ console.error("[Tracing] OTLP export failed:", errMsg);
965
+ }
966
+ throw err;
967
+ }
968
+ }
738
969
  function createEmptySummary() {
739
970
  return {
740
971
  totalDurationMs: 0,
@@ -752,6 +983,12 @@ function generateSessionId() {
752
983
  function generateEventId(sequence) {
753
984
  return `evt_${String(sequence).padStart(4, "0")}_${nanoid(4)}`;
754
985
  }
986
+ function generateTraceId() {
987
+ return crypto.randomBytes(16).toString("hex");
988
+ }
989
+ function generateSpanId() {
990
+ return crypto.randomBytes(8).toString("hex");
991
+ }
755
992
  function defaultEventLabel(type) {
756
993
  switch (type) {
757
994
  case "ai_call":
@@ -904,8 +1141,12 @@ function createTraceSession(opts) {
904
1141
  const cfg = withDefaults(opts.tracing);
905
1142
  if (!cfg.enabled) return void 0;
906
1143
  const sessionId = generateSessionId();
1144
+ const traceId = generateTraceId();
1145
+ const rootSpanId = generateSpanId();
907
1146
  const runtime = {
908
1147
  sessionId,
1148
+ traceId,
1149
+ rootSpanId,
909
1150
  threadId: opts.tracing?.threadId,
910
1151
  startedAt: Date.now(),
911
1152
  resolvedConfig: cfg,
@@ -1015,8 +1256,13 @@ function recordTraceEvent(session, event) {
1015
1256
  const resolvedModel = event.model ?? inferModelFromMessages(event.messageList);
1016
1257
  const resolvedProvider = event.provider ?? inferProviderFromMessages(event.messageList);
1017
1258
  const isAiCall = event.type === "ai_call";
1259
+ const spanId = generateSpanId();
1260
+ const parentSpanId = event.parentSpanId || session.currentIterationSpanId || session.rootSpanId;
1018
1261
  const record = {
1019
1262
  sessionId: session.sessionId,
1263
+ traceId: session.traceId,
1264
+ spanId,
1265
+ parentSpanId,
1020
1266
  id,
1021
1267
  type: event.type,
1022
1268
  label: eventLabel,
@@ -1123,6 +1369,8 @@ async function finalizeTraceSession(session, params = {}) {
1123
1369
  const initialStatus = params.status ? params.status : session.errors.length > 0 ? "error" : "success";
1124
1370
  const buildPayload = (status2) => ({
1125
1371
  sessionId: session.sessionId,
1372
+ traceId: session.traceId,
1373
+ rootSpanId: session.rootSpanId,
1126
1374
  threadId: session.threadId,
1127
1375
  startedAt: startedAtIso,
1128
1376
  endedAt: endedAtIso,
@@ -1138,7 +1386,21 @@ async function finalizeTraceSession(session, params = {}) {
1138
1386
  let sinkFailed = false;
1139
1387
  const sink = session.resolvedConfig.sink;
1140
1388
  const isStreamingMode = session.resolvedConfig.mode === "streaming";
1141
- if (sink.type === "cognipeer" || sink.type === "http") {
1389
+ if (sink.type === "otlp") {
1390
+ try {
1391
+ const otlpPayload = traceSessionToOtlp(payloadForSink);
1392
+ await postOtlpTraces(sink.endpoint, sink.headers, otlpPayload);
1393
+ } catch (err) {
1394
+ sinkFailed = true;
1395
+ const message = err instanceof Error ? err.message : String(err);
1396
+ session.errors.push({
1397
+ eventId: "sink",
1398
+ message,
1399
+ type: "sink",
1400
+ timestamp: endedAtIso
1401
+ });
1402
+ }
1403
+ } else if (sink.type === "cognipeer" || sink.type === "http") {
1142
1404
  try {
1143
1405
  const headers = sink.type === "cognipeer" ? { Authorization: `Bearer ${sink.apiKey}` } : sink.headers;
1144
1406
  if (isStreamingMode && session.sessionStarted) {
@@ -1165,8 +1427,9 @@ async function finalizeTraceSession(session, params = {}) {
1165
1427
  }
1166
1428
  }
1167
1429
  const hadNonSinkErrors = session.errors.some((error) => error.type && error.type !== "sink");
1430
+ const hadSinkErrors = session.errors.some((error) => error.type === "sink");
1168
1431
  let status = params.status ?? (hadNonSinkErrors ? "error" : "success");
1169
- if (status === "success" && sinkFailed) {
1432
+ if (status === "success" && (sinkFailed || hadSinkErrors)) {
1170
1433
  status = "partial";
1171
1434
  }
1172
1435
  let finalPayload = buildPayload(status);
@@ -1270,6 +1533,7 @@ function createAgentCoreNode(opts) {
1270
1533
  model: opts.model,
1271
1534
  tools: opts.tools || [],
1272
1535
  guardrails: opts.guardrails,
1536
+ todoListPrompt: opts.todoListPrompt,
1273
1537
  limits: opts.limits,
1274
1538
  outputSchema: opts.outputSchema,
1275
1539
  tracing: opts.tracing
@@ -1344,7 +1608,7 @@ function createAgentCoreNode(opts) {
1344
1608
  }
1345
1609
  }
1346
1610
  }
1347
- } catch (e) {
1611
+ } catch {
1348
1612
  }
1349
1613
  };
1350
1614
  const onEvent = state.ctx?.__onEvent;
@@ -1460,9 +1724,527 @@ function createAgentCoreNode(opts) {
1460
1724
  return { messages: messagesWithResponse, usage: state.usage };
1461
1725
  };
1462
1726
  }
1727
+
1728
+ // src/smart/runtimeConfig.ts
1729
+ var DEFAULT_CRITICAL_TOOLS = ["response", "manage_todo_list", "get_tool_response"];
1730
+ var DEFAULT_PROFILE_CONFIGS = {
1731
+ fast: {
1732
+ limits: { maxToolCalls: 4, maxParallelTools: 1, maxContextTokens: 12e3 },
1733
+ summarization: {
1734
+ enable: true,
1735
+ maxTokens: 12e3,
1736
+ summaryTriggerTokens: 9e3,
1737
+ summaryPromptMaxTokens: 5e3,
1738
+ summaryCompressionRatioTarget: 0.4,
1739
+ summaryMode: "incremental",
1740
+ promptTemplate: "",
1741
+ toolFreeCall: true,
1742
+ integrityCheck: true
1743
+ },
1744
+ context: {
1745
+ policy: "hybrid",
1746
+ lastTurnsToKeep: 6,
1747
+ toolResponsePolicy: "keep_structured",
1748
+ archiveLargeToolResponses: true,
1749
+ retrieveArchivedToolResponseOnDemand: true,
1750
+ budget: {
1751
+ systemReserveTokens: 1e3,
1752
+ goalsReserveTokens: 900,
1753
+ recentTurnsReserveTokens: 3200,
1754
+ toolResponseReserveTokens: 1800
1755
+ }
1756
+ },
1757
+ planning: {
1758
+ mode: "off",
1759
+ replanPolicy: "on_failure",
1760
+ everyNSteps: 0
1761
+ },
1762
+ memory: {
1763
+ provider: "inMemory",
1764
+ scope: "session",
1765
+ writePolicy: "auto_important",
1766
+ readPolicy: "recent_only"
1767
+ },
1768
+ delegation: {
1769
+ mode: "off",
1770
+ maxDelegationDepth: 1,
1771
+ maxChildCalls: 2,
1772
+ maxParallelChild: 1,
1773
+ childContextPolicy: "minimal",
1774
+ requireJsonOutputContract: true
1775
+ },
1776
+ toolResponses: {
1777
+ maxToolResponseChars: 2400,
1778
+ maxToolResponseTokens: 800,
1779
+ defaultPolicy: "keep_structured",
1780
+ largeResponsePolicy: "summarize_archive",
1781
+ toolResponseRetentionByTool: {},
1782
+ criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1783
+ schemaValidation: "strict",
1784
+ retryOnSchemaError: true,
1785
+ fallbackPolicy: "keep_structured"
1786
+ },
1787
+ watchdog: {
1788
+ enabled: true,
1789
+ tokenDriftThreshold: 1200,
1790
+ overToolingSpikeThreshold: 0.7,
1791
+ contextRotThreshold: 0.65,
1792
+ autoCompaction: true,
1793
+ autoReplanOnFailure: true
1794
+ }
1795
+ },
1796
+ balanced: {
1797
+ limits: { maxToolCalls: 8, maxParallelTools: 2, maxContextTokens: 24e3 },
1798
+ summarization: {
1799
+ enable: true,
1800
+ maxTokens: 24e3,
1801
+ summaryTriggerTokens: 17e3,
1802
+ summaryPromptMaxTokens: 7e3,
1803
+ summaryCompressionRatioTarget: 0.35,
1804
+ summaryMode: "incremental",
1805
+ promptTemplate: "",
1806
+ toolFreeCall: true,
1807
+ integrityCheck: true
1808
+ },
1809
+ context: {
1810
+ policy: "hybrid",
1811
+ lastTurnsToKeep: 8,
1812
+ toolResponsePolicy: "summarize_archive",
1813
+ archiveLargeToolResponses: true,
1814
+ retrieveArchivedToolResponseOnDemand: true,
1815
+ budget: {
1816
+ systemReserveTokens: 1200,
1817
+ goalsReserveTokens: 1200,
1818
+ recentTurnsReserveTokens: 5200,
1819
+ toolResponseReserveTokens: 2800
1820
+ }
1821
+ },
1822
+ planning: {
1823
+ mode: "off",
1824
+ replanPolicy: "on_failure",
1825
+ everyNSteps: 0
1826
+ },
1827
+ memory: {
1828
+ provider: "inMemory",
1829
+ scope: "session",
1830
+ writePolicy: "auto_important",
1831
+ readPolicy: "hybrid"
1832
+ },
1833
+ delegation: {
1834
+ mode: "role_based",
1835
+ maxDelegationDepth: 2,
1836
+ maxChildCalls: 4,
1837
+ maxParallelChild: 2,
1838
+ childContextPolicy: "scoped",
1839
+ requireJsonOutputContract: true
1840
+ },
1841
+ toolResponses: {
1842
+ maxToolResponseChars: 3600,
1843
+ maxToolResponseTokens: 1200,
1844
+ defaultPolicy: "keep_structured",
1845
+ largeResponsePolicy: "summarize_archive",
1846
+ toolResponseRetentionByTool: {},
1847
+ criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1848
+ schemaValidation: "strict",
1849
+ retryOnSchemaError: true,
1850
+ fallbackPolicy: "keep_structured"
1851
+ },
1852
+ watchdog: {
1853
+ enabled: true,
1854
+ tokenDriftThreshold: 1800,
1855
+ overToolingSpikeThreshold: 0.6,
1856
+ contextRotThreshold: 0.55,
1857
+ autoCompaction: true,
1858
+ autoReplanOnFailure: true
1859
+ }
1860
+ },
1861
+ deep: {
1862
+ limits: { maxToolCalls: 14, maxParallelTools: 3, maxContextTokens: 42e3 },
1863
+ summarization: {
1864
+ enable: true,
1865
+ maxTokens: 42e3,
1866
+ summaryTriggerTokens: 3e4,
1867
+ summaryPromptMaxTokens: 9e3,
1868
+ summaryCompressionRatioTarget: 0.3,
1869
+ summaryMode: "incremental",
1870
+ promptTemplate: "",
1871
+ toolFreeCall: true,
1872
+ integrityCheck: true
1873
+ },
1874
+ context: {
1875
+ policy: "hybrid",
1876
+ lastTurnsToKeep: 12,
1877
+ toolResponsePolicy: "summarize_archive",
1878
+ archiveLargeToolResponses: true,
1879
+ retrieveArchivedToolResponseOnDemand: true,
1880
+ budget: {
1881
+ systemReserveTokens: 1400,
1882
+ goalsReserveTokens: 1600,
1883
+ recentTurnsReserveTokens: 7e3,
1884
+ toolResponseReserveTokens: 3600
1885
+ }
1886
+ },
1887
+ planning: {
1888
+ mode: "off",
1889
+ replanPolicy: "on_failure",
1890
+ everyNSteps: 0
1891
+ },
1892
+ memory: {
1893
+ provider: "inMemory",
1894
+ scope: "workspace",
1895
+ writePolicy: "auto_important",
1896
+ readPolicy: "hybrid"
1897
+ },
1898
+ delegation: {
1899
+ mode: "role_based",
1900
+ maxDelegationDepth: 3,
1901
+ maxChildCalls: 6,
1902
+ maxParallelChild: 2,
1903
+ childContextPolicy: "scoped",
1904
+ requireJsonOutputContract: true
1905
+ },
1906
+ toolResponses: {
1907
+ maxToolResponseChars: 5400,
1908
+ maxToolResponseTokens: 1800,
1909
+ defaultPolicy: "keep_structured",
1910
+ largeResponsePolicy: "summarize_archive",
1911
+ toolResponseRetentionByTool: {},
1912
+ criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1913
+ schemaValidation: "strict",
1914
+ retryOnSchemaError: true,
1915
+ fallbackPolicy: "keep_structured"
1916
+ },
1917
+ watchdog: {
1918
+ enabled: true,
1919
+ tokenDriftThreshold: 2200,
1920
+ overToolingSpikeThreshold: 0.55,
1921
+ contextRotThreshold: 0.5,
1922
+ autoCompaction: true,
1923
+ autoReplanOnFailure: true
1924
+ }
1925
+ },
1926
+ research: {
1927
+ limits: { maxToolCalls: 20, maxParallelTools: 4, maxContextTokens: 56e3 },
1928
+ summarization: {
1929
+ enable: true,
1930
+ maxTokens: 56e3,
1931
+ summaryTriggerTokens: 42e3,
1932
+ summaryPromptMaxTokens: 1e4,
1933
+ summaryCompressionRatioTarget: 0.28,
1934
+ summaryMode: "incremental",
1935
+ promptTemplate: "",
1936
+ toolFreeCall: true,
1937
+ integrityCheck: true
1938
+ },
1939
+ context: {
1940
+ policy: "hybrid",
1941
+ lastTurnsToKeep: 20,
1942
+ toolResponsePolicy: "summarize_archive",
1943
+ archiveLargeToolResponses: true,
1944
+ retrieveArchivedToolResponseOnDemand: true,
1945
+ budget: {
1946
+ systemReserveTokens: 1600,
1947
+ goalsReserveTokens: 1800,
1948
+ recentTurnsReserveTokens: 9e3,
1949
+ toolResponseReserveTokens: 4800
1950
+ }
1951
+ },
1952
+ planning: {
1953
+ mode: "off",
1954
+ replanPolicy: "on_failure",
1955
+ everyNSteps: 0
1956
+ },
1957
+ memory: {
1958
+ provider: "inMemory",
1959
+ scope: "workspace",
1960
+ writePolicy: "auto_important",
1961
+ readPolicy: "hybrid"
1962
+ },
1963
+ delegation: {
1964
+ mode: "automatic",
1965
+ maxDelegationDepth: 4,
1966
+ maxChildCalls: 8,
1967
+ maxParallelChild: 3,
1968
+ childContextPolicy: "full",
1969
+ requireJsonOutputContract: true
1970
+ },
1971
+ toolResponses: {
1972
+ maxToolResponseChars: 7200,
1973
+ maxToolResponseTokens: 2200,
1974
+ defaultPolicy: "keep_structured",
1975
+ largeResponsePolicy: "summarize_archive",
1976
+ toolResponseRetentionByTool: {},
1977
+ criticalTools: [...DEFAULT_CRITICAL_TOOLS],
1978
+ schemaValidation: "strict",
1979
+ retryOnSchemaError: true,
1980
+ fallbackPolicy: "keep_structured"
1981
+ },
1982
+ watchdog: {
1983
+ enabled: true,
1984
+ tokenDriftThreshold: 2600,
1985
+ overToolingSpikeThreshold: 0.5,
1986
+ contextRotThreshold: 0.45,
1987
+ autoCompaction: true,
1988
+ autoReplanOnFailure: true
1989
+ }
1990
+ }
1991
+ };
1992
+ function mergeLimits(base, override) {
1993
+ return {
1994
+ maxToolCalls: override?.maxToolCalls ?? base.maxToolCalls,
1995
+ maxParallelTools: override?.maxParallelTools ?? base.maxParallelTools,
1996
+ maxContextTokens: override?.maxContextTokens ?? base.maxContextTokens
1997
+ };
1998
+ }
1999
+ function resolveBaseProfile(opts, runtimeProfile) {
2000
+ if (opts.customProfile?.extends) return opts.customProfile.extends;
2001
+ if (runtimeProfile !== "custom") return runtimeProfile;
2002
+ return "balanced";
2003
+ }
2004
+ function normalizeSmartAgentOptions(opts) {
2005
+ const runtimeProfile = opts.runtimeProfile ?? (opts.customProfile ? "custom" : "balanced");
2006
+ const baseProfile = resolveBaseProfile(opts, runtimeProfile);
2007
+ const preset = DEFAULT_PROFILE_CONFIGS[baseProfile];
2008
+ const customProfile = opts.customProfile || {};
2009
+ const useTodoList = opts.useTodoList === true;
2010
+ const summarizationDisabled = opts.summarization === false;
2011
+ const summarizationOverride = typeof opts.summarization === "object" ? opts.summarization : {};
2012
+ const planningMode = opts.planning?.mode ?? customProfile.planning?.mode ?? (useTodoList ? "todo" : preset.planning.mode);
2013
+ const memoryStore = opts.memory?.store ?? customProfile.memory?.store;
2014
+ return {
2015
+ runtimeProfile,
2016
+ baseProfile,
2017
+ limits: mergeLimits(mergeLimits(preset.limits, customProfile.limits), opts.limits),
2018
+ summarization: {
2019
+ ...preset.summarization,
2020
+ ...customProfile.summarization || {},
2021
+ ...summarizationOverride,
2022
+ enable: summarizationDisabled ? false : summarizationOverride.enable ?? customProfile.summarization?.enable ?? preset.summarization.enable,
2023
+ maxTokens: summarizationOverride.maxTokens ?? opts.limits?.maxContextTokens ?? customProfile.summarization?.maxTokens ?? preset.summarization.maxTokens,
2024
+ summaryTriggerTokens: summarizationOverride.summaryTriggerTokens ?? summarizationOverride.maxTokens ?? opts.limits?.maxContextTokens ?? customProfile.summarization?.summaryTriggerTokens ?? customProfile.summarization?.maxTokens ?? preset.summarization.summaryTriggerTokens,
2025
+ promptTemplate: summarizationOverride.promptTemplate ?? customProfile.summarization?.promptTemplate ?? preset.summarization.promptTemplate
2026
+ },
2027
+ context: {
2028
+ ...preset.context,
2029
+ ...customProfile.context || {},
2030
+ ...opts.context || {},
2031
+ budget: {
2032
+ ...preset.context.budget,
2033
+ ...customProfile.context?.budget || {},
2034
+ ...opts.context?.budget || {}
2035
+ },
2036
+ toolResponsePolicy: opts.context?.toolResponsePolicy ?? customProfile.context?.toolResponsePolicy ?? preset.context.toolResponsePolicy
2037
+ },
2038
+ planning: {
2039
+ ...preset.planning,
2040
+ ...customProfile.planning || {},
2041
+ ...opts.planning || {},
2042
+ mode: planningMode
2043
+ },
2044
+ memory: {
2045
+ ...preset.memory,
2046
+ ...customProfile.memory || {},
2047
+ ...opts.memory || {},
2048
+ store: memoryStore
2049
+ },
2050
+ delegation: {
2051
+ ...preset.delegation,
2052
+ ...customProfile.delegation || {},
2053
+ ...opts.delegation || {}
2054
+ },
2055
+ toolResponses: {
2056
+ ...preset.toolResponses,
2057
+ ...customProfile.toolResponses || {},
2058
+ ...opts.toolResponses || {},
2059
+ toolResponseRetentionByTool: {
2060
+ ...preset.toolResponses.toolResponseRetentionByTool,
2061
+ ...customProfile.toolResponses?.toolResponseRetentionByTool || {},
2062
+ ...opts.toolResponses?.toolResponseRetentionByTool || {}
2063
+ },
2064
+ criticalTools: opts.toolResponses?.criticalTools ?? customProfile.toolResponses?.criticalTools ?? preset.toolResponses.criticalTools
2065
+ },
2066
+ watchdog: {
2067
+ ...preset.watchdog,
2068
+ ...customProfile.watchdog || {},
2069
+ ...opts.watchdog || {}
2070
+ }
2071
+ };
2072
+ }
2073
+ function getResolvedSmartConfig(opts, runtime) {
2074
+ return runtime?.smart || normalizeSmartAgentOptions(opts);
2075
+ }
2076
+
2077
+ // src/utils/utilTokens.ts
2078
+ function countApproxTokens(text) {
2079
+ if (!text) return 0;
2080
+ const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
2081
+ const asciiCount = text.length - nonAsciiCount;
2082
+ return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
2083
+ }
2084
+ function extractMessageText(m) {
2085
+ if (!m) return "";
2086
+ const parts = [];
2087
+ parts.push(m.role || "");
2088
+ if (m.name) parts.push(m.name);
2089
+ if (typeof m.content === "string") {
2090
+ parts.push(m.content);
2091
+ } else if (Array.isArray(m.content)) {
2092
+ for (const c of m.content) {
2093
+ if (typeof c === "string") parts.push(c);
2094
+ else if (c?.text) parts.push(c.text);
2095
+ else if (c?.content) parts.push(c.content);
2096
+ else if (c?.type === "tool_use" && c?.input) {
2097
+ parts.push(c.name || "");
2098
+ parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
2099
+ } else if (c != null && typeof c === "object") {
2100
+ parts.push(JSON.stringify(c));
2101
+ }
2102
+ }
2103
+ } else if (m.content != null && typeof m.content === "object") {
2104
+ parts.push(JSON.stringify(m.content));
2105
+ }
2106
+ if (Array.isArray(m.tool_calls)) {
2107
+ for (const tc of m.tool_calls) {
2108
+ if (tc?.function?.name) parts.push(tc.function.name);
2109
+ if (tc?.function?.arguments) {
2110
+ parts.push(
2111
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2112
+ );
2113
+ }
2114
+ if (tc?.name && !tc?.function?.name) parts.push(tc.name);
2115
+ if (tc?.args && !tc?.function?.arguments) {
2116
+ parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
2117
+ }
2118
+ }
2119
+ }
2120
+ if (Array.isArray(m.additional_kwargs?.tool_calls)) {
2121
+ for (const tc of m.additional_kwargs.tool_calls) {
2122
+ if (tc?.function?.name) parts.push(tc.function.name);
2123
+ if (tc?.function?.arguments) {
2124
+ parts.push(
2125
+ typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2126
+ );
2127
+ }
2128
+ }
2129
+ }
2130
+ return parts.join("\n");
2131
+ }
2132
+ function countMessagesTokens(messages) {
2133
+ if (!Array.isArray(messages) || messages.length === 0) return 0;
2134
+ const allText = messages.map(extractMessageText).join("\n");
2135
+ return countApproxTokens(allText) + messages.length * 4;
2136
+ }
2137
+
2138
+ // src/smart/toolResponses.ts
2139
+ function safeStringify(value) {
2140
+ if (typeof value === "string") return value;
2141
+ try {
2142
+ const serialized = JSON.stringify(value);
2143
+ if (typeof serialized === "string") {
2144
+ return serialized;
2145
+ }
2146
+ } catch {
2147
+ }
2148
+ return String(value);
2149
+ }
2150
+ function summarizeObject(value) {
2151
+ if (value == null) return "null";
2152
+ if (typeof value === "string") {
2153
+ return value.length > 240 ? `${value.slice(0, 240)}...` : value;
2154
+ }
2155
+ if (Array.isArray(value)) {
2156
+ const preview = value.slice(0, 3).map((entry) => safeStringify(entry).slice(0, 120)).join(" | ");
2157
+ return `array(length=${value.length}) ${preview}`.trim();
2158
+ }
2159
+ if (typeof value === "object") {
2160
+ const keys = Object.keys(value);
2161
+ const preview = keys.slice(0, 6).map((key) => `${key}=${safeStringify(value[key]).slice(0, 80)}`).join("; ");
2162
+ return `object(keys=${keys.length}) ${preview}`.trim();
2163
+ }
2164
+ return safeStringify(value);
2165
+ }
2166
+ function classifyToolResponse(toolName, output, config) {
2167
+ const serialized = safeStringify(output);
2168
+ const tokenCount = countApproxTokens(serialized);
2169
+ const criticalTools = new Set(config.toolResponses.criticalTools);
2170
+ if (criticalTools.has(toolName)) return "critical";
2171
+ if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
2172
+ return "verbose";
2173
+ }
2174
+ if (serialized === "ok" || serialized === "true" || serialized === "null") {
2175
+ return "redundant";
2176
+ }
2177
+ return "informative";
2178
+ }
2179
+ function resolveToolResponsePolicy(toolName, output, config) {
2180
+ const serialized = safeStringify(output);
2181
+ const tokenCount = countApproxTokens(serialized);
2182
+ const classification = classifyToolResponse(toolName, output, config);
2183
+ const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2184
+ let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
2185
+ if (classification === "critical") {
2186
+ retentionPolicy = byTool || "keep_structured";
2187
+ } else if (classification === "verbose") {
2188
+ retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
2189
+ } else if (classification === "redundant") {
2190
+ retentionPolicy = byTool || "drop";
2191
+ }
2192
+ if (retentionPolicy === "keep_full") {
2193
+ const summary2 = "";
2194
+ return { classification, retentionPolicy, content: serialized, rawOutput: output, summary: summary2, tokenCount };
2195
+ }
2196
+ const summary = summarizeObject(output);
2197
+ if (retentionPolicy === "keep_structured") {
2198
+ return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
2199
+ }
2200
+ if (retentionPolicy === "summarize_archive") {
2201
+ return {
2202
+ classification,
2203
+ retentionPolicy,
2204
+ content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
2205
+ rawOutput: output,
2206
+ summary,
2207
+ tokenCount
2208
+ };
2209
+ }
2210
+ return {
2211
+ classification,
2212
+ retentionPolicy,
2213
+ content: `DROPPED_TOOL_RESPONSE: ${summary}`,
2214
+ rawOutput: output,
2215
+ summary,
2216
+ tokenCount
2217
+ };
2218
+ }
2219
+ function validateToolArgs(tool, args) {
2220
+ const schema = tool.schema;
2221
+ if (!schema || typeof schema.parse !== "function") {
2222
+ return { ok: true, value: args };
2223
+ }
2224
+ try {
2225
+ return { ok: true, value: schema.parse(args) };
2226
+ } catch (error) {
2227
+ if (error instanceof ZodError) {
2228
+ const message = error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
2229
+ return { ok: false, message };
2230
+ }
2231
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
2232
+ }
2233
+ }
2234
+
2235
+ // src/nodes/tools.ts
2236
+ function normalizeMaxExecutionsPerRun(value) {
2237
+ if (value === null || value === void 0 || value === "") return null;
2238
+ const parsed = Number(value);
2239
+ if (!Number.isFinite(parsed)) return null;
2240
+ const normalized = Math.trunc(parsed);
2241
+ return normalized >= 0 ? normalized : null;
2242
+ }
2243
+ function countSuccessfulToolExecutions(toolHistory, toolName) {
2244
+ if (!Array.isArray(toolHistory)) return 0;
2245
+ return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
2246
+ }
1463
2247
  function createToolsNode(initialTools, opts) {
1464
- const baseToolByName = /* @__PURE__ */ new Map();
1465
- for (const t of initialTools) baseToolByName.set(t.name, t);
1466
2248
  return async (state) => {
1467
2249
  const runtime = state.agent || {
1468
2250
  name: opts?.name,
@@ -1470,15 +2252,17 @@ function createToolsNode(initialTools, opts) {
1470
2252
  tools: initialTools,
1471
2253
  limits: opts?.limits,
1472
2254
  systemPrompt: opts?.systemPrompt,
2255
+ todoListPrompt: opts?.todoListPrompt,
1473
2256
  useTodoList: opts?.useTodoList,
1474
2257
  outputSchema: opts?.outputSchema
1475
2258
  };
2259
+ const resolved = getResolvedSmartConfig(opts || {}, runtime);
1476
2260
  const activeTools = runtime.tools;
1477
2261
  const toolByName = /* @__PURE__ */ new Map();
1478
- for (const t of activeTools) toolByName.set(t.name, t);
2262
+ for (const tool of activeTools) toolByName.set(tool.name, tool);
1479
2263
  const limits = {
1480
- maxToolCalls: runtime.limits?.maxToolCalls ?? 10,
1481
- maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? 1)
2264
+ maxToolCalls: runtime.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10,
2265
+ maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? resolved.limits.maxParallelTools ?? 1)
1482
2266
  };
1483
2267
  const appended = [];
1484
2268
  const onEvent = state.ctx?.__onEvent;
@@ -1489,20 +2273,19 @@ function createToolsNode(initialTools, opts) {
1489
2273
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
1490
2274
  const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
1491
2275
  let awaitingApproval = false;
1492
- for (const t of toolByName.values()) {
1493
- const anyT = t;
1494
- if (anyT._stateRef && typeof anyT._stateRef === "object") {
1495
- anyT._stateRef.toolHistory = state.toolHistory;
1496
- anyT._stateRef.toolHistoryArchived = state.toolHistoryArchived;
1497
- anyT._stateRef.__onEvent = onEvent;
2276
+ for (const tool of toolByName.values()) {
2277
+ const anyTool = tool;
2278
+ if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
2279
+ anyTool._stateRef.toolHistory = state.toolHistory;
2280
+ anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
2281
+ anyTool._stateRef.__onEvent = onEvent;
1498
2282
  }
1499
2283
  }
1500
2284
  const last = state.messages[state.messages.length - 1];
1501
2285
  let toolCount = state.toolCallCount || 0;
1502
- const toolCalls = Array.isArray(
1503
- last?.tool_calls
1504
- ) ? last.tool_calls : [];
2286
+ const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
1505
2287
  const toolHistory = state.toolHistory || [];
2288
+ const toolHistoryArchived = state.toolHistoryArchived || [];
1506
2289
  const remaining = Math.max(0, limits.maxToolCalls - toolCount);
1507
2290
  const planned = toolCalls.slice(0, remaining);
1508
2291
  const skipped = toolCalls.slice(remaining);
@@ -1511,6 +2294,17 @@ function createToolsNode(initialTools, opts) {
1511
2294
  if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
1512
2295
  return { cancelled: false, reason: void 0 };
1513
2296
  };
2297
+ const markToolFailure = (message, toolName, toolCallId) => {
2298
+ const ctx = { ...state.ctx || {} };
2299
+ ctx.__lastToolError = message;
2300
+ if (toolCallId) {
2301
+ ctx.__toolSchemaError = { toolName, toolCallId, message };
2302
+ }
2303
+ if (resolved.watchdog.autoReplanOnFailure) {
2304
+ ctx.__planNeedsReplan = true;
2305
+ }
2306
+ state.ctx = ctx;
2307
+ };
1514
2308
  const runOne = async (tc) => {
1515
2309
  const cancelState = isCancelled();
1516
2310
  if (cancelState.cancelled) {
@@ -1520,15 +2314,10 @@ function createToolsNode(initialTools, opts) {
1520
2314
  onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
1521
2315
  return { status: "error" };
1522
2316
  }
1523
- const t = toolByName.get(tc.name);
1524
- if (!t) {
2317
+ const tool = toolByName.get(tc.name);
2318
+ if (!tool) {
2319
+ appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1525
2320
  onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args: tc.args, error: { message: "Tool not found" } });
1526
- appended.push({
1527
- role: "tool",
1528
- content: `Tool not found: ${tc.name}`,
1529
- tool_call_id: tc.id || `${tc.name}_${appended.length}`,
1530
- name: tc.name
1531
- });
1532
2321
  toolCount += 1;
1533
2322
  return { status: "error" };
1534
2323
  }
@@ -1536,22 +2325,67 @@ function createToolsNode(initialTools, opts) {
1536
2325
  if (typeof args === "string") {
1537
2326
  try {
1538
2327
  args = JSON.parse(args);
1539
- } catch (_) {
2328
+ } catch {
1540
2329
  }
1541
2330
  }
1542
2331
  const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
2332
+ const validatedArgs = validateToolArgs(tool, args);
2333
+ if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
2334
+ const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
2335
+ appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
2336
+ markToolFailure(errorMessage, tc.name, toolCallId);
2337
+ onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
2338
+ toolCount += 1;
2339
+ return { status: "error" };
2340
+ }
2341
+ if (validatedArgs.ok) {
2342
+ args = validatedArgs.value;
2343
+ }
2344
+ const toolName = tool.name || tc.name;
2345
+ const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
2346
+ if (maxExecutionsPerRun !== null) {
2347
+ const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
2348
+ if (currentExecutions >= maxExecutionsPerRun) {
2349
+ const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
2350
+ appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
2351
+ onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
2352
+ recordTraceEvent(traceSession, {
2353
+ type: "tool_call",
2354
+ label: `Tool Skipped - ${toolName}`,
2355
+ actor: { scope: "tool", name: toolName, role: "tool" },
2356
+ status: "skipped",
2357
+ toolExecutionId: tc.id,
2358
+ messageList: [
2359
+ {
2360
+ role: "assistant",
2361
+ name: toolName,
2362
+ content: limitMessage,
2363
+ tool_calls: [
2364
+ {
2365
+ id: tc.id,
2366
+ type: "function",
2367
+ function: { name: toolName, arguments: sanitizeTracePayload(args) }
2368
+ }
2369
+ ]
2370
+ }
2371
+ ]
2372
+ });
2373
+ toolCount += 1;
2374
+ return { status: "error" };
2375
+ }
2376
+ }
1543
2377
  let approvalEntry = pendingByCallId.get(toolCallId);
1544
- const needsApproval = Boolean(t.needsApproval);
2378
+ const needsApproval = Boolean(tool.needsApproval);
1545
2379
  if (needsApproval) {
1546
2380
  if (!approvalEntry) {
1547
2381
  approvalEntry = {
1548
2382
  id: nanoid(),
1549
2383
  toolCallId,
1550
- toolName: t.name || tc.name,
2384
+ toolName: tool.name || tc.name,
1551
2385
  args,
1552
2386
  status: "pending",
1553
2387
  requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
1554
- metadata: t.approvalPrompt || t.approvalDefaults ? { prompt: t.approvalPrompt, defaults: t.approvalDefaults } : void 0
2388
+ metadata: tool.approvalPrompt || tool.approvalDefaults ? { prompt: tool.approvalPrompt, defaults: tool.approvalDefaults } : void 0
1555
2389
  };
1556
2390
  pendingApprovals.push(approvalEntry);
1557
2391
  pendingByCallId.set(toolCallId, approvalEntry);
@@ -1573,71 +2407,97 @@ function createToolsNode(initialTools, opts) {
1573
2407
  }
1574
2408
  if (approvalEntry.status === "rejected") {
1575
2409
  const rejectionMessage = approvalEntry.comment || "Tool call rejected by reviewer.";
1576
- appended.push({
1577
- role: "tool",
1578
- content: `Tool call rejected: ${rejectionMessage}`,
1579
- tool_call_id: toolCallId,
1580
- name: tc.name
1581
- });
1582
- onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
2410
+ appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
1583
2411
  approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
1584
2412
  approvalEntry.status = "executed";
1585
2413
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
1586
- toolHistory.push({ executionId: nanoid(), toolName: t.name, args, output: `Rejected: ${rejectionMessage}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
1587
2414
  pendingByCallId.set(toolCallId, approvalEntry);
2415
+ toolHistory.push({ executionId: nanoid(), toolName: tool.name, args, output: `Rejected: ${rejectionMessage}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "rejected" });
2416
+ onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
1588
2417
  toolCount += 1;
1589
2418
  return { status: "rejected", approval: approvalEntry };
1590
2419
  }
1591
- if (approvalEntry.status === "approved") {
1592
- if (approvalEntry.approvedArgs !== void 0) {
1593
- args = approvalEntry.approvedArgs;
1594
- }
2420
+ if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
2421
+ args = approvalEntry.approvedArgs;
1595
2422
  onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
1596
2423
  }
1597
2424
  }
1598
2425
  const start = Date.now();
2426
+ const sanitizedArgs = sanitizeTracePayload(args);
2427
+ const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1599
2428
  try {
1600
- onEvent?.({ type: "tool_call", phase: "start", name: t.name, id: tc.id, args });
2429
+ onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
1601
2430
  onProgress?.({ stage: "tools", message: `Running tool ${tc.name}` });
1602
- const sanitizedArgs = sanitizeTracePayload(args);
1603
- const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1604
- let output;
1605
- const anyTool = t;
2431
+ const anyTool = tool;
1606
2432
  const callOptions = { cancellationToken, signal: abortSignal };
2433
+ let output;
1607
2434
  if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
1608
2435
  else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
1609
2436
  else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
1610
2437
  else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
1611
2438
  else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
1612
2439
  else throw new Error("Tool is not invokable");
1613
- if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
1614
- const newRuntime = output.__handoff.runtime;
1615
- const executionId2 = nanoid();
1616
- toolHistory.push({ executionId: executionId2, toolName: t.name, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
2440
+ const durationMs = Date.now() - start;
2441
+ const executionId = nanoid();
2442
+ if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
2443
+ toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
1617
2444
  appended.push({ role: "tool", content: "ok", tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1618
- onEvent?.({ type: "handoff", from: runtime.name, to: newRuntime.name, toolName: t.name });
1619
- state.agent = newRuntime;
1620
- onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: "handoff", durationMs: Date.now() - start });
1621
- toolCount += 1;
2445
+ state.agent = output.__handoff.runtime;
2446
+ onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
2447
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
1622
2448
  if (needsApproval && approvalEntry) {
1623
2449
  approvalEntry.status = "executed";
1624
2450
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
1625
- approvalEntry.executionId = executionId2;
2451
+ approvalEntry.executionId = executionId;
1626
2452
  pendingByCallId.set(toolCallId, approvalEntry);
1627
2453
  }
2454
+ toolCount += 1;
1628
2455
  return { status: "success", approval: approvalEntry };
1629
2456
  }
1630
- const content = typeof output === "string" ? output : JSON.stringify(output);
1631
2457
  if (output && typeof output === "object" && output.__finalStructuredOutput) {
1632
2458
  if (!state.ctx) state.ctx = {};
1633
2459
  state.ctx.__structuredOutputParsed = output.data;
1634
2460
  state.ctx.__finalizedDueToStructuredOutput = true;
1635
2461
  }
1636
- const durationMs = Date.now() - start;
1637
- const executionId = nanoid();
1638
- toolHistory.push({ executionId, toolName: t.name, args, output, rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id });
1639
- appended.push({ role: "tool", content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1640
- onEvent?.({ type: "tool_call", phase: "success", name: t.name, id: tc.id, args, result: output, durationMs });
2462
+ const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
2463
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2464
+ const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
2465
+ toolHistory.push({
2466
+ executionId,
2467
+ toolName,
2468
+ args,
2469
+ output: storedOutput,
2470
+ rawOutput: output,
2471
+ timestamp,
2472
+ tool_call_id: tc.id,
2473
+ summarized: responsePolicy.retentionPolicy !== "keep_full",
2474
+ originalTokenCount: responsePolicy.tokenCount,
2475
+ classification: responsePolicy.classification,
2476
+ retentionPolicy: responsePolicy.retentionPolicy,
2477
+ summary: responsePolicy.summary,
2478
+ archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
2479
+ status: "success"
2480
+ });
2481
+ if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
2482
+ toolHistoryArchived.push({
2483
+ executionId,
2484
+ toolName,
2485
+ args,
2486
+ output,
2487
+ rawOutput: output,
2488
+ timestamp,
2489
+ tool_call_id: tc.id,
2490
+ summarized: true,
2491
+ originalTokenCount: responsePolicy.tokenCount,
2492
+ classification: responsePolicy.classification,
2493
+ retentionPolicy: responsePolicy.retentionPolicy,
2494
+ summary: responsePolicy.summary,
2495
+ archiveId: executionId,
2496
+ status: "success"
2497
+ });
2498
+ }
2499
+ appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2500
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
1641
2501
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
1642
2502
  if (needsApproval && approvalEntry) {
1643
2503
  approvalEntry.status = "executed";
@@ -1647,7 +2507,6 @@ function createToolsNode(initialTools, opts) {
1647
2507
  }
1648
2508
  const sanitizedOutput = sanitizeTracePayload(output);
1649
2509
  const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
1650
- const toolName = t.name || tc.name;
1651
2510
  const messageList = [
1652
2511
  {
1653
2512
  role: "assistant",
@@ -1667,7 +2526,7 @@ function createToolsNode(initialTools, opts) {
1667
2526
  {
1668
2527
  role: "tool",
1669
2528
  name: toolName,
1670
- content: sanitizedOutput ?? output ?? ""
2529
+ content: responsePolicy.content
1671
2530
  }
1672
2531
  ];
1673
2532
  recordTraceEvent(traceSession, {
@@ -1678,43 +2537,24 @@ function createToolsNode(initialTools, opts) {
1678
2537
  requestBytes: inputBytes,
1679
2538
  responseBytes: outputBytes,
1680
2539
  toolExecutionId: executionId,
1681
- messageList
2540
+ messageList,
2541
+ debug: {
2542
+ classification: responsePolicy.classification,
2543
+ retentionPolicy: responsePolicy.retentionPolicy,
2544
+ originalTokenCount: responsePolicy.tokenCount
2545
+ }
1682
2546
  });
1683
2547
  toolCount += 1;
1684
2548
  return { status: "success", approval: approvalEntry };
1685
- } catch (e) {
2549
+ } catch (error) {
1686
2550
  const durationMs = Date.now() - start;
1687
2551
  const executionId = nanoid();
1688
- 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 });
1689
- appended.push({ role: "tool", content: `Error executing tool: ${e?.message || String(e)}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1690
- onEvent?.({ type: "tool_call", phase: "error", name: t.name, id: tc.id, args, error: { message: e?.message || String(e) } });
1691
- onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: e?.message || String(e) } });
1692
- const sanitizedArgs = sanitizeTracePayload(args);
1693
- const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1694
- const toolName = t.name || tc.name;
1695
- const errorMessage = e?.message || String(e);
1696
- const messageList = [
1697
- {
1698
- role: "assistant",
1699
- name: toolName,
1700
- content: "",
1701
- tool_calls: [
1702
- {
1703
- id: tc.id || executionId,
1704
- type: "function",
1705
- function: {
1706
- name: toolName,
1707
- arguments: sanitizedArgs
1708
- }
1709
- }
1710
- ]
1711
- },
1712
- {
1713
- role: "tool",
1714
- name: toolName,
1715
- content: `Error executing tool: ${errorMessage}`
1716
- }
1717
- ];
2552
+ const message = error?.message || String(error);
2553
+ toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
2554
+ appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2555
+ markToolFailure(message, toolName, toolCallId);
2556
+ onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
2557
+ onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
1718
2558
  recordTraceEvent(traceSession, {
1719
2559
  type: "tool_call",
1720
2560
  label: `Tool Error - ${toolName}`,
@@ -1723,8 +2563,26 @@ function createToolsNode(initialTools, opts) {
1723
2563
  durationMs,
1724
2564
  requestBytes: inputBytes,
1725
2565
  toolExecutionId: executionId,
1726
- error: { message: e?.message || String(e), stack: e?.stack },
1727
- messageList
2566
+ error: { message, stack: error?.stack },
2567
+ messageList: [
2568
+ {
2569
+ role: "assistant",
2570
+ name: toolName,
2571
+ content: "",
2572
+ tool_calls: [
2573
+ {
2574
+ id: tc.id || executionId,
2575
+ type: "function",
2576
+ function: { name: toolName, arguments: sanitizedArgs }
2577
+ }
2578
+ ]
2579
+ },
2580
+ {
2581
+ role: "tool",
2582
+ name: toolName,
2583
+ content: `Error executing tool: ${message}`
2584
+ }
2585
+ ]
1728
2586
  });
1729
2587
  toolCount += 1;
1730
2588
  return { status: "error", approval: approvalEntry };
@@ -1741,7 +2599,7 @@ function createToolsNode(initialTools, opts) {
1741
2599
  break;
1742
2600
  }
1743
2601
  const result = await runOne(tc);
1744
- if (result?.status === "awaiting_approval") {
2602
+ if (result.status === "awaiting_approval") {
1745
2603
  awaitingApproval = true;
1746
2604
  break;
1747
2605
  }
@@ -1749,37 +2607,28 @@ function createToolsNode(initialTools, opts) {
1749
2607
  for (const tc of skipped) {
1750
2608
  onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
1751
2609
  const sanitizedArgs = sanitizeTracePayload(tc.args);
1752
- const messageList = [
1753
- {
1754
- role: "assistant",
1755
- name: tc.name,
1756
- content: `Skipped tool due to tool-call limit: ${tc.name}`,
1757
- tool_calls: [
1758
- {
1759
- id: tc.id,
1760
- type: "function",
1761
- function: {
1762
- name: tc.name,
1763
- arguments: sanitizedArgs
1764
- }
1765
- }
1766
- ]
1767
- }
1768
- ];
1769
2610
  recordTraceEvent(traceSession, {
1770
2611
  type: "tool_call",
1771
2612
  label: `Tool Skipped - ${tc.name}`,
1772
2613
  actor: { scope: "tool", name: tc.name, role: "tool" },
1773
2614
  status: "skipped",
1774
2615
  toolExecutionId: tc.id,
1775
- messageList
1776
- });
1777
- appended.push({
1778
- role: "tool",
1779
- content: `Skipped tool due to tool-call limit: ${tc.name}`,
1780
- tool_call_id: tc.id || `${tc.name}_${appended.length}`,
1781
- name: tc.name
2616
+ messageList: [
2617
+ {
2618
+ role: "assistant",
2619
+ name: tc.name,
2620
+ content: `Skipped tool due to tool-call limit: ${tc.name}`,
2621
+ tool_calls: [
2622
+ {
2623
+ id: tc.id,
2624
+ type: "function",
2625
+ function: { name: tc.name, arguments: sanitizedArgs }
2626
+ }
2627
+ ]
2628
+ }
2629
+ ]
1782
2630
  });
2631
+ 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 });
1783
2632
  toolCount += 1;
1784
2633
  }
1785
2634
  if (!awaitingApproval && state.ctx?.__awaitingApproval) {
@@ -1794,6 +2643,7 @@ function createToolsNode(initialTools, opts) {
1794
2643
  messages: [...state.messages, ...appended],
1795
2644
  toolCallCount: toolCount,
1796
2645
  toolHistory,
2646
+ toolHistoryArchived,
1797
2647
  agent: state.agent,
1798
2648
  pendingApprovals
1799
2649
  };
@@ -2018,68 +2868,20 @@ function resolveToolApprovalState(state, resolution) {
2018
2868
  };
2019
2869
  }
2020
2870
 
2021
- // src/utils/utilTokens.ts
2022
- function countApproxTokens(text) {
2023
- if (!text) return 0;
2024
- const nonAsciiCount = (text.match(/[^\x00-\x7F]/g) || []).length;
2025
- const asciiCount = text.length - nonAsciiCount;
2026
- return Math.ceil(asciiCount / 4 + nonAsciiCount / 1.5);
2027
- }
2028
- function extractMessageText(m) {
2029
- if (!m) return "";
2030
- const parts = [];
2031
- parts.push(m.role || "");
2032
- if (m.name) parts.push(m.name);
2033
- if (typeof m.content === "string") {
2034
- parts.push(m.content);
2035
- } else if (Array.isArray(m.content)) {
2036
- for (const c of m.content) {
2037
- if (typeof c === "string") parts.push(c);
2038
- else if (c?.text) parts.push(c.text);
2039
- else if (c?.content) parts.push(c.content);
2040
- else if (c?.type === "tool_use" && c?.input) {
2041
- parts.push(c.name || "");
2042
- parts.push(typeof c.input === "string" ? c.input : JSON.stringify(c.input));
2043
- } else if (c != null && typeof c === "object") {
2044
- parts.push(JSON.stringify(c));
2045
- }
2046
- }
2047
- } else if (m.content != null && typeof m.content === "object") {
2048
- parts.push(JSON.stringify(m.content));
2049
- }
2050
- if (Array.isArray(m.tool_calls)) {
2051
- for (const tc of m.tool_calls) {
2052
- if (tc?.function?.name) parts.push(tc.function.name);
2053
- if (tc?.function?.arguments) {
2054
- parts.push(
2055
- typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2056
- );
2057
- }
2058
- if (tc?.name && !tc?.function?.name) parts.push(tc.name);
2059
- if (tc?.args && !tc?.function?.arguments) {
2060
- parts.push(typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args));
2061
- }
2062
- }
2871
+ // src/agent.ts
2872
+ function isSyntheticSummaryMessage(message) {
2873
+ if (!message) return false;
2874
+ if (message.role === "tool" && message.name === "summarize_context") {
2875
+ return true;
2063
2876
  }
2064
- if (Array.isArray(m.additional_kwargs?.tool_calls)) {
2065
- for (const tc of m.additional_kwargs.tool_calls) {
2066
- if (tc?.function?.name) parts.push(tc.function.name);
2067
- if (tc?.function?.arguments) {
2068
- parts.push(
2069
- typeof tc.function.arguments === "string" ? tc.function.arguments : JSON.stringify(tc.function.arguments)
2070
- );
2071
- }
2072
- }
2877
+ if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
2878
+ return message.tool_calls.some((toolCall) => {
2879
+ const toolName = toolCall?.function?.name || toolCall?.name;
2880
+ return toolName === "summarize_context";
2881
+ });
2073
2882
  }
2074
- return parts.join("\n");
2075
- }
2076
- function countMessagesTokens(messages) {
2077
- if (!Array.isArray(messages) || messages.length === 0) return 0;
2078
- const allText = messages.map(extractMessageText).join("\n");
2079
- return countApproxTokens(allText) + messages.length * 4;
2883
+ return false;
2080
2884
  }
2081
-
2082
- // src/agent.ts
2083
2885
  function createAgent(opts) {
2084
2886
  const resolver = createResolverNode();
2085
2887
  const agentCore = createAgentCoreNode(opts);
@@ -2122,6 +2924,7 @@ function createAgent(opts) {
2122
2924
  tools: toolsBase,
2123
2925
  guardrails: opts.guardrails,
2124
2926
  systemPrompt: void 0,
2927
+ todoListPrompt: opts.todoListPrompt,
2125
2928
  limits: opts.limits,
2126
2929
  useTodoList: void 0,
2127
2930
  outputSchema: opts.outputSchema,
@@ -2197,6 +3000,18 @@ function createAgent(opts) {
2197
3000
  };
2198
3001
  while (iterations < iterationLimit) {
2199
3002
  iterations++;
3003
+ const traceSession = state.ctx?.__traceSession;
3004
+ if (traceSession) {
3005
+ const iterEvent = recordTraceEvent(traceSession, {
3006
+ type: "agent_iteration",
3007
+ label: `Iteration ${iterations}`,
3008
+ actor: { scope: "agent", name: opts.name || "agent", role: "orchestrator" },
3009
+ parentSpanId: traceSession.rootSpanId
3010
+ });
3011
+ if (iterEvent) {
3012
+ traceSession.currentIterationSpanId = iterEvent.spanId;
3013
+ }
3014
+ }
2200
3015
  if (cancelIfRequested("loop")) break;
2201
3016
  const skippingAgent = resumeStage === "tools";
2202
3017
  if (!skippingAgent) {
@@ -2247,11 +3062,21 @@ function createAgent(opts) {
2247
3062
  maxTok = 5e4;
2248
3063
  }
2249
3064
  if (maxTok) {
2250
- const tokenCount = countMessagesTokens(state.messages || []);
3065
+ const tokCountMessages = (state.messages || []).filter((message) => {
3066
+ if (isSyntheticSummaryMessage(message)) return false;
3067
+ if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
3068
+ return true;
3069
+ });
3070
+ const tokenCount = countMessagesTokens(tokCountMessages);
2251
3071
  if (tokenCount > maxTok) {
2252
- const ctx = { ...state.ctx || {}, __needsSummarization: true };
2253
- state = { ...state, ctx };
2254
- break;
3072
+ const hasFreshSummary = (state.messages || []).some(
3073
+ (m) => m.role === "tool" && m.content === "SUMMARIZED"
3074
+ );
3075
+ if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
3076
+ const ctx = { ...state.ctx || {}, __needsSummarization: true };
3077
+ state = { ...state, ctx };
3078
+ break;
3079
+ }
2255
3080
  }
2256
3081
  }
2257
3082
  onProgress?.({ stage: "agent", message: "Invoking model" });
@@ -2489,7 +3314,11 @@ function createAgent(opts) {
2489
3314
  schema,
2490
3315
  func: async ({ input }) => {
2491
3316
  const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
2492
- return { content: res.content };
3317
+ return {
3318
+ content: res.content,
3319
+ output: res.output,
3320
+ summary: res.state?.summaries?.[res.state.summaries.length - 1]
3321
+ };
2493
3322
  }
2494
3323
  });
2495
3324
  },
@@ -2530,6 +3359,7 @@ function createAgentNode(opts) {
2530
3359
  model: opts.model,
2531
3360
  tools: opts.tools || [],
2532
3361
  systemPrompt: opts.systemPrompt,
3362
+ todoListPrompt: opts.todoListPrompt,
2533
3363
  limits: opts.limits,
2534
3364
  useTodoList: opts.useTodoList,
2535
3365
  outputSchema: opts.outputSchema
@@ -2547,7 +3377,8 @@ function createAgentNode(opts) {
2547
3377
  content: buildSystemPrompt(
2548
3378
  [runtime.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
2549
3379
  runtime.useTodoList === true,
2550
- runtime.name || "Agent"
3380
+ runtime.name || "Agent",
3381
+ runtime.todoListPrompt
2551
3382
  )
2552
3383
  };
2553
3384
  const messages = [systemMsg, ...state.messages];
@@ -2629,9 +3460,119 @@ function createAgentNode(opts) {
2629
3460
  };
2630
3461
  }
2631
3462
 
3463
+ // src/smart/contextPolicy.ts
3464
+ function messageText(message) {
3465
+ if (typeof message.content === "string") return message.content;
3466
+ if (Array.isArray(message.content)) {
3467
+ return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
3468
+ }
3469
+ return "";
3470
+ }
3471
+ function collectRecentTurns(messages, lastTurnsToKeep) {
3472
+ if (lastTurnsToKeep <= 0) return [];
3473
+ const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
3474
+ const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
3475
+ let userTurnCount = 0;
3476
+ const collected = [];
3477
+ for (let index = body.length - 1; index >= 0; index -= 1) {
3478
+ const message = body[index];
3479
+ collected.unshift(message);
3480
+ if (message.role === "user") {
3481
+ userTurnCount += 1;
3482
+ if (userTurnCount >= lastTurnsToKeep) break;
3483
+ }
3484
+ }
3485
+ return [...systemPrefix, ...collected];
3486
+ }
3487
+ function renderStructuredSummary(summary) {
3488
+ if (!summary) return "";
3489
+ const lines = ["Context summary:"];
3490
+ if (summary.stable_facts.length > 0) {
3491
+ lines.push("Stable facts:");
3492
+ lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
3493
+ }
3494
+ if (summary.active_goals.length > 0) {
3495
+ lines.push("Active goals:");
3496
+ lines.push(...summary.active_goals.map((goal) => `- ${goal}`));
3497
+ }
3498
+ if (summary.open_questions.length > 0) {
3499
+ lines.push("Open questions:");
3500
+ lines.push(...summary.open_questions.map((question) => `- ${question}`));
3501
+ }
3502
+ if (summary.discarded_obsolete.length > 0) {
3503
+ lines.push("Discarded obsolete:");
3504
+ lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
3505
+ }
3506
+ return lines.join("\n");
3507
+ }
3508
+ function renderMemoryBlock(facts) {
3509
+ if (!facts || facts.length === 0) return "";
3510
+ return [
3511
+ "Retrieved memory:",
3512
+ ...facts.map((fact) => `- ${fact.key}: ${fact.value}`)
3513
+ ].join("\n");
3514
+ }
3515
+ function clampToBudget(messages, maxContextTokens) {
3516
+ let working = [...messages];
3517
+ while (working.length > 2) {
3518
+ const tokenCount = countApproxTokens(working.map(messageText).join("\n"));
3519
+ if (tokenCount <= maxContextTokens) return working;
3520
+ const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
3521
+ if (firstNonSystem < 0) break;
3522
+ working.splice(firstNonSystem, 1);
3523
+ }
3524
+ return working;
3525
+ }
3526
+ function buildModelMessages(state, config) {
3527
+ const rawMessages = state.messages || [];
3528
+ if (config.context.policy === "raw") {
3529
+ return clampToBudget(rawMessages, config.limits.maxContextTokens);
3530
+ }
3531
+ const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
3532
+ const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
3533
+ const summaryText = renderStructuredSummary(latestSummary);
3534
+ const memoryText = renderMemoryBlock(state.memoryFacts);
3535
+ const systemMessage2 = recentMessages[0]?.role === "system" ? recentMessages[0] : void 0;
3536
+ const body = systemMessage2 ? recentMessages.slice(1) : recentMessages;
3537
+ const syntheticContextMessages = [];
3538
+ if (summaryText) {
3539
+ syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
3540
+ }
3541
+ if (memoryText) {
3542
+ syntheticContextMessages.push({ role: "system", name: "memory_context", content: memoryText });
3543
+ }
3544
+ const assembled = [
3545
+ ...systemMessage2 ? [systemMessage2] : [],
3546
+ ...syntheticContextMessages,
3547
+ ...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
3548
+ ];
3549
+ return clampToBudget(assembled, config.limits.maxContextTokens);
3550
+ }
3551
+ function estimateContextRotScore(state) {
3552
+ const summaryCount = state.summaryRecords?.length || 0;
3553
+ const archivedCount = state.toolHistoryArchived?.length || 0;
3554
+ const messageCount = state.messages?.length || 0;
3555
+ const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
3556
+ const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
3557
+ const denominator = Math.max(messageCount, 1);
3558
+ return Number((numerator / denominator).toFixed(3));
3559
+ }
3560
+
2632
3561
  // src/nodes/contextSummarize.ts
2633
3562
  var systemMessage = (content) => ({ role: "system", content });
2634
3563
  var humanMessage = (content) => ({ role: "user", content });
3564
+ function isSyntheticSummaryToolMessage(message) {
3565
+ return message.role === "tool" && message.name === "summarize_context";
3566
+ }
3567
+ function isSyntheticSummaryAssistantMessage(message) {
3568
+ if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
3569
+ return false;
3570
+ }
3571
+ return message.tool_calls.some((toolCall) => {
3572
+ const toolName = toolCall?.function?.name || toolCall?.name;
3573
+ return toolName === "summarize_context";
3574
+ });
3575
+ }
2635
3576
  function validateMessageSequence(messages) {
2636
3577
  const validated = [];
2637
3578
  const pendingToolCallIds = /* @__PURE__ */ new Set();
@@ -2657,33 +3598,137 @@ function validateMessageSequence(messages) {
2657
3598
  }
2658
3599
  return validated;
2659
3600
  }
2660
- function getSummarizationConfig(opts) {
2661
- if (typeof opts.summarization === "object") {
2662
- return {
2663
- enabled: opts.summarization.enable !== false,
2664
- maxTokens: opts.summarization.maxTokens ?? 5e4,
2665
- // Max tokens we can send to the model for summarization prompt itself
2666
- // This should be well under the model's context limit
2667
- summaryPromptMaxTokens: opts.summarization.summaryPromptMaxTokens ?? 8e3,
2668
- promptTemplate: opts.summarization.promptTemplate
2669
- };
3601
+ function extractJsonObject(text) {
3602
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
3603
+ if (fenced?.[1]) return fenced[1].trim();
3604
+ const start = text.indexOf("{");
3605
+ const end = text.lastIndexOf("}");
3606
+ if (start >= 0 && end > start) {
3607
+ return text.slice(start, end + 1);
3608
+ }
3609
+ return null;
3610
+ }
3611
+ function normalizeStructuredSummary(input, fallbackText) {
3612
+ const summary = {
3613
+ stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
3614
+ key: String(fact.key || "unknown"),
3615
+ value: String(fact.value || ""),
3616
+ confidence: typeof fact.confidence === "number" ? fact.confidence : 0.7,
3617
+ source: fact.source ? String(fact.source) : void 0
3618
+ })) : [],
3619
+ active_goals: Array.isArray(input?.active_goals) ? input.active_goals.map((goal) => String(goal)) : [],
3620
+ open_questions: Array.isArray(input?.open_questions) ? input.open_questions.map((question) => String(question)) : [],
3621
+ discarded_obsolete: Array.isArray(input?.discarded_obsolete || input?.obsolete_discarded) ? (input.discarded_obsolete || input.obsolete_discarded).map((item) => String(item)) : [],
3622
+ rawSummary: typeof input?.rawSummary === "string" ? input.rawSummary : fallbackText
3623
+ };
3624
+ if (summary.stable_facts.length === 0 && fallbackText) {
3625
+ const importantLines = fallbackText.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 8);
3626
+ summary.stable_facts = importantLines.map((line, index) => ({
3627
+ key: `fact_${index + 1}`,
3628
+ value: line,
3629
+ confidence: 0.5
3630
+ }));
3631
+ }
3632
+ return summary;
3633
+ }
3634
+ function normalizeFactKeySegment(value) {
3635
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "item";
3636
+ }
3637
+ function extractCanonicalFacts(messages) {
3638
+ const facts = /* @__PURE__ */ new Map();
3639
+ for (const message of messages) {
3640
+ const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part?.text ?? part?.content ?? String(part ?? "")).join("\n") : "";
3641
+ if (!content) continue;
3642
+ const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
3643
+ for (const line of lines) {
3644
+ if (!/^[A-Z0-9_]+\|/.test(line) || !line.includes("=")) continue;
3645
+ const segments = line.split("|").map((segment) => segment.trim()).filter(Boolean);
3646
+ const label = segments[0];
3647
+ const entries = segments.slice(1).map((segment) => {
3648
+ const eqIndex = segment.indexOf("=");
3649
+ if (eqIndex <= 0) return null;
3650
+ return {
3651
+ key: segment.slice(0, eqIndex).trim(),
3652
+ value: segment.slice(eqIndex + 1).trim()
3653
+ };
3654
+ }).filter((entry) => Boolean(entry?.key));
3655
+ if (entries.length === 0) continue;
3656
+ const identifier = entries.find((entry) => ["code", "id", "project", "name", "key"].includes(entry.key.toLowerCase()))?.value;
3657
+ const keyPrefix = [normalizeFactKeySegment(label), identifier ? normalizeFactKeySegment(identifier) : void 0].filter(Boolean).join(".");
3658
+ for (const entry of entries) {
3659
+ const normalizedEntryKey = normalizeFactKeySegment(entry.key);
3660
+ const factKey = keyPrefix ? `${keyPrefix}.${normalizedEntryKey}` : `${normalizeFactKeySegment(label)}.${normalizedEntryKey}`;
3661
+ facts.set(factKey, {
3662
+ key: factKey,
3663
+ value: entry.value,
3664
+ confidence: 0.98,
3665
+ source: "canonical_tool_output"
3666
+ });
3667
+ }
3668
+ }
3669
+ }
3670
+ return [...facts.values()];
3671
+ }
3672
+ function mergeStableFacts(summary, additionalFacts) {
3673
+ if (additionalFacts.length === 0) return summary;
3674
+ const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
3675
+ for (const fact of additionalFacts) {
3676
+ const existing = merged.get(fact.key);
3677
+ if (!existing || (fact.confidence || 0) >= (existing.confidence || 0)) {
3678
+ merged.set(fact.key, fact);
3679
+ }
2670
3680
  }
2671
3681
  return {
2672
- enabled: opts.summarization !== false,
2673
- maxTokens: 5e4,
2674
- summaryPromptMaxTokens: 8e3,
2675
- promptTemplate: void 0
3682
+ ...summary,
3683
+ stable_facts: [...merged.values()]
2676
3684
  };
2677
3685
  }
3686
+ function runIntegrityCheck(previous, current) {
3687
+ const notes = [];
3688
+ const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
3689
+ const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
3690
+ const obsoleteKeys = new Set(current.discarded_obsolete);
3691
+ const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key));
3692
+ const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
3693
+ if (criticalFactLoss) {
3694
+ notes.push("Missing previously retained facts; merged forward during integrity repair.");
3695
+ }
3696
+ if (obsoleteFactRevived) {
3697
+ notes.push("Obsolete facts reappeared; removed during integrity repair.");
3698
+ }
3699
+ return {
3700
+ passed: !criticalFactLoss && !obsoleteFactRevived,
3701
+ criticalFactLoss,
3702
+ obsoleteFactRevived,
3703
+ notes
3704
+ };
3705
+ }
3706
+ function repairStructuredSummary(previous, current, integrity) {
3707
+ if (!previous) return current;
3708
+ const nextFacts = [...current.stable_facts];
3709
+ if (integrity.criticalFactLoss) {
3710
+ const existingKeys = new Set(nextFacts.map((fact) => fact.key));
3711
+ for (const fact of previous.stable_facts) {
3712
+ if (!existingKeys.has(fact.key) && !current.discarded_obsolete.includes(fact.key)) {
3713
+ nextFacts.push(fact);
3714
+ }
3715
+ }
3716
+ }
3717
+ const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
3718
+ return { ...current, stable_facts: filteredFacts };
3719
+ }
2678
3720
  function createContextSummarizeNode(opts) {
2679
3721
  return async (state) => {
2680
- const config = getSummarizationConfig(opts);
2681
- if (!config.enabled) return {};
3722
+ const resolved = getResolvedSmartConfig(opts, state.agent);
3723
+ const config = resolved.summarization;
3724
+ if (!config.enable) return {};
2682
3725
  const model = opts.model;
2683
3726
  const messages = state.messages || [];
2684
3727
  const onEvent = state.ctx?.__onEvent;
2685
3728
  const traceSession = state.ctx?.__traceSession;
2686
- const compressableMessages = messages.filter((m) => m.role === "tool" && m.content !== "SUMMARIZED");
3729
+ const compressableMessages = messages.filter(
3730
+ (m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
3731
+ );
2687
3732
  if (compressableMessages.length === 0) {
2688
3733
  return {};
2689
3734
  }
@@ -2695,6 +3740,9 @@ function createContextSummarizeNode(opts) {
2695
3740
  let accumulatedTokens = 0;
2696
3741
  for (let i = messages.length - 1; i >= 0; i--) {
2697
3742
  const m = messages[i];
3743
+ if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
3744
+ continue;
3745
+ }
2698
3746
  let content = "";
2699
3747
  if (typeof m.content === "string") content = m.content;
2700
3748
  else if (Array.isArray(m.content)) {
@@ -2706,39 +3754,60 @@ function createContextSummarizeNode(opts) {
2706
3754
  }
2707
3755
  let prefix = `${m.role.toUpperCase()}`;
2708
3756
  if (m.name) prefix += ` (${m.name})`;
2709
- const messageText = `${prefix}: ${content}`;
2710
- const messageTokens = countApproxTokens(messageText);
3757
+ const messageText2 = `${prefix}: ${content}`;
3758
+ const messageTokens = countApproxTokens(messageText2);
2711
3759
  if (accumulatedTokens + messageTokens > availableTokensForConversation) {
2712
3760
  messageTexts.unshift("[Earlier conversation history omitted for brevity]");
2713
3761
  break;
2714
3762
  }
2715
- messageTexts.unshift(messageText);
3763
+ messageTexts.unshift(messageText2);
2716
3764
  accumulatedTokens += messageTokens;
2717
3765
  }
2718
3766
  const conversationText = messageTexts.join("\n\n");
3767
+ const canonicalFacts = extractCanonicalFacts(compressableMessages);
2719
3768
  const previousSummary = Array.isArray(state.summaries) && state.summaries.length > 0 ? state.summaries[state.summaries.length - 1] : "";
2720
- const defaultPrompt = `Please summarize the following conversation history and update any previous summary.
2721
- Focus on:
2722
- - User goals and intent.
2723
- - Key decisions made and actions taken.
2724
- - Important tool outputs and data retrieved.
2725
- - Current state of the task.
3769
+ const previousStructuredSummary = Array.isArray(state.summaryRecords) && state.summaryRecords.length > 0 ? state.summaryRecords[state.summaryRecords.length - 1] : void 0;
3770
+ const defaultPrompt = `Summarize the conversation into strict JSON.
3771
+
3772
+ Return exactly one JSON object with this schema:
3773
+ {
3774
+ "stable_facts": [{ "key": string, "value": string, "confidence": number }],
3775
+ "active_goals": [string],
3776
+ "open_questions": [string],
3777
+ "discarded_obsolete": [string],
3778
+ "rawSummary": string
3779
+ }
3780
+
3781
+ Rules:
3782
+ - Keep stable_facts only for facts that future turns must remember.
3783
+ - Put invalidated or superseded fact keys in discarded_obsolete.
3784
+ - Preserve active_goals still relevant to the user.
3785
+ - Preserve unresolved questions.
3786
+ - Do not include prose outside JSON.
3787
+ - Target compression ratio: ${config.summaryCompressionRatioTarget}.
3788
+ - Summary mode: ${config.summaryMode}.
2726
3789
 
2727
- Previous summary (if any):
3790
+ Previous summary text:
2728
3791
  ${previousSummary || "(none)"}
2729
3792
 
3793
+ Previous structured summary:
3794
+ ${previousStructuredSummary ? JSON.stringify(previousStructuredSummary) : "(none)"}
3795
+
2730
3796
  Conversation:
2731
3797
  ${conversationText}
2732
3798
 
2733
- Summary:`;
3799
+ Canonical facts extracted from tool outputs:
3800
+ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fact.value}`).join("\n") : "(none)"}`;
2734
3801
  const template = config.promptTemplate || defaultPrompt;
2735
3802
  const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
2736
3803
  const summaryPrompt = [
2737
- systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
3804
+ systemMessage("You are a helpful assistant that summarizes conversation history efficiently. Return strict JSON only."),
2738
3805
  humanMessage(promptBody)
2739
3806
  ];
2740
3807
  const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
2741
3808
  let summaryText = "Summary unavailable.";
3809
+ let structuredSummary;
3810
+ let integrity;
2742
3811
  let outputTokensEstimate = 0;
2743
3812
  let durationMs = 0;
2744
3813
  let inputTokensActual;
@@ -2747,10 +3816,23 @@ Summary:`;
2747
3816
  let totalTokensActual;
2748
3817
  const startTime = Date.now();
2749
3818
  try {
3819
+ if (!model || typeof model.invoke !== "function") {
3820
+ throw new Error("Summarization model is unavailable.");
3821
+ }
2750
3822
  const response = await model.invoke(summaryPrompt);
2751
3823
  durationMs = Date.now() - startTime;
2752
3824
  summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2753
3825
  outputTokensEstimate = countApproxTokens(summaryText);
3826
+ const jsonText = extractJsonObject(summaryText);
3827
+ const parsed = jsonText ? JSON.parse(jsonText) : void 0;
3828
+ structuredSummary = normalizeStructuredSummary(parsed, summaryText);
3829
+ structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
3830
+ integrity = config.integrityCheck ? runIntegrityCheck(previousStructuredSummary, structuredSummary) : { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: [] };
3831
+ if (integrity && !integrity.passed) {
3832
+ structuredSummary = repairStructuredSummary(previousStructuredSummary, structuredSummary, integrity);
3833
+ integrity = { ...integrity, passed: true };
3834
+ }
3835
+ summaryText = renderStructuredSummary(structuredSummary);
2754
3836
  const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2755
3837
  const normalized = normalizeUsage(rawUsage);
2756
3838
  if (normalized) {
@@ -2761,17 +3843,22 @@ Summary:`;
2761
3843
  }
2762
3844
  } catch (err) {
2763
3845
  durationMs = Date.now() - startTime;
2764
- console.error("[ContextSummarize] Failed to generate summary:", err);
3846
+ structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
3847
+ structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
3848
+ integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
3849
+ summaryText = renderStructuredSummary(structuredSummary);
2765
3850
  const errorEvent = {
2766
3851
  type: "summarization",
2767
- summary: "",
2768
- messagesCompressed: 0,
3852
+ summary: summaryText,
3853
+ messagesCompressed: compressableMessages.length,
2769
3854
  inputTokens: inputTokensEstimate,
2770
- outputTokens: 0,
3855
+ outputTokens: countApproxTokens(summaryText),
2771
3856
  durationMs,
2772
3857
  previousSummary: previousSummary || void 0,
2773
3858
  tokenCountBefore,
2774
- tokenCountAfter: tokenCountBefore
3859
+ tokenCountAfter: tokenCountBefore,
3860
+ structuredSummary,
3861
+ integrity
2775
3862
  };
2776
3863
  onEvent?.(errorEvent);
2777
3864
  recordTraceEvent(traceSession, {
@@ -2781,11 +3868,10 @@ Summary:`;
2781
3868
  status: "error",
2782
3869
  durationMs,
2783
3870
  inputTokens: inputTokensEstimate,
2784
- outputTokens: 0,
3871
+ outputTokens: countApproxTokens(summaryText),
2785
3872
  error: { message: err?.message || String(err), stack: err?.stack },
2786
3873
  messageList: sanitizeTracePayload(summaryPrompt)
2787
3874
  });
2788
- return {};
2789
3875
  }
2790
3876
  const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
2791
3877
  messages.forEach((m, idx) => {
@@ -2799,6 +3885,9 @@ Summary:`;
2799
3885
  });
2800
3886
  const newMessages = messages.map((m) => {
2801
3887
  if (m.role === "tool") {
3888
+ if (isSyntheticSummaryToolMessage(m)) {
3889
+ return m;
3890
+ }
2802
3891
  const toolCallId = m.tool_call_id;
2803
3892
  if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
2804
3893
  return null;
@@ -2836,6 +3925,7 @@ Summary:`;
2836
3925
  content: summaryText
2837
3926
  };
2838
3927
  const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
3928
+ 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() }];
2839
3929
  const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
2840
3930
  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");
2841
3931
  const tokenCountAfter = countApproxTokens(allTextAfter);
@@ -2851,8 +3941,10 @@ Summary:`;
2851
3941
  previousSummary: previousSummary || void 0,
2852
3942
  tokenCountBefore,
2853
3943
  tokenCountAfter,
2854
- archivedCount: compressableMessages.length
3944
+ archivedCount: compressableMessages.length,
2855
3945
  // deprecated field for backward compatibility
3946
+ structuredSummary,
3947
+ integrity
2856
3948
  };
2857
3949
  onEvent?.(summarizationEvent);
2858
3950
  recordTraceEvent(traceSession, {
@@ -2876,12 +3968,19 @@ Summary:`;
2876
3968
  tokensSaved: tokenCountBefore - tokenCountAfter,
2877
3969
  previousSummaryLength: previousSummary?.length || 0,
2878
3970
  usageFromModel: inputTokensActual !== void 0,
2879
- cachedInputTokens
3971
+ cachedInputTokens,
3972
+ integrity
2880
3973
  }
2881
3974
  });
2882
3975
  return {
2883
3976
  messages: finalMessages,
2884
- summaries
3977
+ summaries,
3978
+ summaryRecords,
3979
+ watchdog: {
3980
+ ...state.watchdog || {},
3981
+ compactions: (state.watchdog?.compactions || 0) + 1,
3982
+ lastAction: "summarized"
3983
+ }
2885
3984
  };
2886
3985
  };
2887
3986
  }
@@ -2904,38 +4003,200 @@ async function applyTokenLimits({
2904
4003
  }
2905
4004
  return { ...state, messages: newMessages };
2906
4005
  }
4006
+ var todoStatusSchema = z.enum(["not-started", "in-progress", "completed", "blocked"]);
4007
+ var todoWriteItemSchema = z.object({
4008
+ id: z.number().int().min(1).describe("Sequential id starting from 1"),
4009
+ step: z.string().min(1).optional(),
4010
+ title: z.string().min(1).optional(),
4011
+ description: z.string().min(1).optional(),
4012
+ owner: z.string().min(1).optional(),
4013
+ exitCriteria: z.string().min(1).optional(),
4014
+ status: todoStatusSchema,
4015
+ evidence: z.string().max(300).optional()
4016
+ });
4017
+ var todoUpdateItemSchema = z.object({
4018
+ id: z.number().int().min(1).describe("Existing todo id to update"),
4019
+ step: z.string().min(1).optional(),
4020
+ title: z.string().min(1).optional(),
4021
+ description: z.string().min(1).optional(),
4022
+ owner: z.string().min(1).optional(),
4023
+ exitCriteria: z.string().min(1).optional(),
4024
+ status: todoStatusSchema.optional(),
4025
+ evidence: z.string().max(300).optional()
4026
+ });
4027
+ function normalizeTodoItem(item, existing) {
4028
+ const id = item.id ?? existing?.id;
4029
+ const step = item.step || item.title || item.description || existing?.step || existing?.title || existing?.description || `Step ${id}`;
4030
+ const title = item.title || item.step || existing?.title || existing?.step || `Step ${id}`;
4031
+ const description = item.description || item.step || item.title || existing?.description || existing?.step || existing?.title || `Step ${id}`;
4032
+ return {
4033
+ ...existing,
4034
+ ...item,
4035
+ id,
4036
+ step,
4037
+ title,
4038
+ description,
4039
+ owner: item.owner || existing?.owner || "agent",
4040
+ exitCriteria: item.exitCriteria || existing?.exitCriteria || description || `Complete step ${id}`,
4041
+ status: item.status || existing?.status,
4042
+ evidence: item.evidence ?? existing?.evidence
4043
+ };
4044
+ }
4045
+ function validatePlanInvariants(todoList, requireSequentialIds) {
4046
+ const ids = todoList.map((item) => item.id);
4047
+ const uniqueIds = new Set(ids);
4048
+ if (uniqueIds.size !== ids.length) {
4049
+ return { ok: false, error: "Todo ids must be unique." };
4050
+ }
4051
+ if (requireSequentialIds) {
4052
+ const expectedIds = Array.from({ length: todoList.length }, (_, index) => index + 1);
4053
+ const isSequential = ids.every((id, index) => id === expectedIds[index]);
4054
+ if (!isSequential) {
4055
+ return { ok: false, error: "Todo ids must be sequential starting from 1 for write operations." };
4056
+ }
4057
+ }
4058
+ const inProgressCount = todoList.filter((item) => item.status === "in-progress").length;
4059
+ if (inProgressCount > 1) {
4060
+ return { ok: false, error: "Only one todo item may be in-progress at a time." };
4061
+ }
4062
+ return { ok: true };
4063
+ }
4064
+ function calculateAdherenceScore(todoList) {
4065
+ const completedCount = todoList.filter((item) => item.status === "completed").length;
4066
+ return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
4067
+ }
2907
4068
  function createContextTools(stateRef, opts) {
2908
4069
  const tools = [];
2909
4070
  if (opts?.planningEnabled) {
2910
4071
  const manageTodo = createTool({
2911
4072
  name: "manage_todo_list",
2912
- 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.",
4073
+ 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",
2913
4074
  schema: z.object({
2914
- operation: z.enum(["write", "read"]),
2915
- todoList: z.array(z.object({
2916
- id: z.number().int().min(1).describe("Sequential id starting from 1"),
2917
- title: z.string().min(1),
2918
- description: z.string().min(1),
2919
- status: z.enum(["not-started", "in-progress", "completed"]),
2920
- evidence: z.string().max(200).optional()
2921
- })).optional()
4075
+ operation: z.enum(["write", "read", "update"]),
4076
+ expectedVersion: z.number().int().min(0).optional(),
4077
+ todoList: z.array(z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
2922
4078
  }),
2923
- func: async ({ operation, todoList }) => {
4079
+ func: async ({ operation, expectedVersion, todoList }) => {
2924
4080
  const onEvent = manageTodo._stateRef?.__onEvent;
4081
+ const currentVersion = stateRef.planVersion || 0;
2925
4082
  if (operation === "read") {
2926
4083
  const list = stateRef.todoList || [];
2927
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list });
4084
+ onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
2928
4085
  return list;
2929
4086
  }
2930
- if (Array.isArray(todoList)) {
2931
- stateRef.todoList = todoList;
4087
+ if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
4088
+ return {
4089
+ status: "error",
4090
+ operation,
4091
+ error: "Plan version mismatch. Read the latest plan and retry.",
4092
+ version: currentVersion,
4093
+ adherenceScore: stateRef.adherenceScore || 0
4094
+ };
4095
+ }
4096
+ if (!Array.isArray(todoList)) {
4097
+ return {
4098
+ status: "error",
4099
+ operation,
4100
+ error: "todoList is required for write and update operations.",
4101
+ version: currentVersion,
4102
+ adherenceScore: stateRef.adherenceScore || 0
4103
+ };
2932
4104
  }
4105
+ if (operation === "write") {
4106
+ const parsed = z.array(todoWriteItemSchema).safeParse(todoList);
4107
+ if (!parsed.success) {
4108
+ return {
4109
+ status: "error",
4110
+ operation,
4111
+ error: parsed.error.issues[0]?.message || "Invalid todoList for write.",
4112
+ version: currentVersion,
4113
+ adherenceScore: stateRef.adherenceScore || 0
4114
+ };
4115
+ }
4116
+ const normalizedList = parsed.data.map((item) => normalizeTodoItem(item));
4117
+ const validation = validatePlanInvariants(normalizedList, true);
4118
+ if (!validation.ok) {
4119
+ return {
4120
+ status: "error",
4121
+ operation,
4122
+ error: validation.error,
4123
+ version: currentVersion,
4124
+ adherenceScore: stateRef.adherenceScore || 0
4125
+ };
4126
+ }
4127
+ stateRef.todoList = normalizedList;
4128
+ } else if (operation === "update") {
4129
+ const parsed = z.array(todoUpdateItemSchema).safeParse(todoList);
4130
+ if (!parsed.success) {
4131
+ return {
4132
+ status: "error",
4133
+ operation,
4134
+ error: parsed.error.issues[0]?.message || "Invalid todoList for update.",
4135
+ version: currentVersion,
4136
+ adherenceScore: stateRef.adherenceScore || 0
4137
+ };
4138
+ }
4139
+ const currentList = Array.isArray(stateRef.todoList) ? stateRef.todoList : [];
4140
+ if (currentList.length === 0) {
4141
+ return {
4142
+ status: "error",
4143
+ operation,
4144
+ error: "No existing plan to update. Use write first.",
4145
+ version: currentVersion,
4146
+ adherenceScore: stateRef.adherenceScore || 0
4147
+ };
4148
+ }
4149
+ const patchIds = /* @__PURE__ */ new Set();
4150
+ for (const item of parsed.data) {
4151
+ if (patchIds.has(item.id)) {
4152
+ return {
4153
+ status: "error",
4154
+ operation,
4155
+ error: "Update payload contains duplicate todo ids.",
4156
+ version: currentVersion,
4157
+ adherenceScore: stateRef.adherenceScore || 0
4158
+ };
4159
+ }
4160
+ patchIds.add(item.id);
4161
+ }
4162
+ const currentMap = new Map(currentList.map((item) => [item.id, item]));
4163
+ for (const item of parsed.data) {
4164
+ if (!currentMap.has(item.id)) {
4165
+ return {
4166
+ status: "error",
4167
+ operation,
4168
+ error: `Cannot update missing todo id ${item.id}. Use write to replace the plan.`,
4169
+ version: currentVersion,
4170
+ adherenceScore: stateRef.adherenceScore || 0
4171
+ };
4172
+ }
4173
+ }
4174
+ const mergedList = currentList.map((item) => {
4175
+ const patch = parsed.data.find((candidate) => candidate.id === item.id);
4176
+ return patch ? normalizeTodoItem(patch, item) : item;
4177
+ });
4178
+ const validation = validatePlanInvariants(mergedList, false);
4179
+ if (!validation.ok) {
4180
+ return {
4181
+ status: "error",
4182
+ operation,
4183
+ error: validation.error,
4184
+ version: currentVersion,
4185
+ adherenceScore: stateRef.adherenceScore || 0
4186
+ };
4187
+ }
4188
+ stateRef.todoList = mergedList;
4189
+ }
4190
+ stateRef.planVersion = currentVersion + 1;
4191
+ stateRef.adherenceScore = calculateAdherenceScore(stateRef.todoList || []);
2933
4192
  const payload = {
2934
4193
  status: "ok",
2935
4194
  operation,
2936
- count: Array.isArray(todoList) ? todoList.length : void 0
4195
+ count: Array.isArray(todoList) ? todoList.length : void 0,
4196
+ version: stateRef.planVersion || 1,
4197
+ adherenceScore: stateRef.adherenceScore || 0
2937
4198
  };
2938
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "write", todoList });
4199
+ onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
2939
4200
  return payload;
2940
4201
  }
2941
4202
  });
@@ -2944,12 +4205,13 @@ function createContextTools(stateRef, opts) {
2944
4205
  }
2945
4206
  const getTool = createTool({
2946
4207
  name: "get_tool_response",
2947
- description: "RETRIEVE tool execution response: Use this to access the full output of a tool execution that shows as 'SUMMARIZED' in the tool history.",
4208
+ 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.",
2948
4209
  schema: z.object({ executionId: z.string().describe("Tool execution id") }),
2949
4210
  func: async ({ executionId }) => {
2950
- let execution = stateRef.toolHistory?.find((t) => t.executionId === executionId);
4211
+ const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
4212
+ let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));
2951
4213
  if (!execution) {
2952
- execution = stateRef.toolHistoryArchived?.find((t) => t.executionId === executionId);
4214
+ execution = stateRef.toolHistoryArchived?.find((t) => matchesExecution(t));
2953
4215
  }
2954
4216
  if (execution) {
2955
4217
  return execution.rawOutput || execution.output;
@@ -2980,19 +4242,33 @@ function createContextTools(stateRef, opts) {
2980
4242
  }
2981
4243
 
2982
4244
  // src/graph/decisions.ts
4245
+ function isSyntheticSummaryMessage2(message) {
4246
+ if (!message) return false;
4247
+ if (message.role === "tool" && message.name === "summarize_context") {
4248
+ return true;
4249
+ }
4250
+ if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
4251
+ return message.tool_calls.some((toolCall) => {
4252
+ const toolName = toolCall?.function?.name || toolCall?.name;
4253
+ return toolName === "summarize_context";
4254
+ });
4255
+ }
4256
+ return false;
4257
+ }
2983
4258
  function needsSummarization(state, opts, summarizationEnabled) {
2984
4259
  if (!summarizationEnabled) {
2985
4260
  return false;
2986
4261
  }
2987
- let maxTok;
2988
- if (typeof opts.summarization === "object" && typeof opts.summarization.maxTokens === "number") {
2989
- maxTok = opts.summarization.maxTokens;
4262
+ if (state.ctx?.__needsSummarization) {
4263
+ return true;
2990
4264
  }
2991
- if (maxTok === void 0) {
2992
- maxTok = 5e4;
4265
+ const resolved = getResolvedSmartConfig(opts, state.agent);
4266
+ if (!resolved.summarization.enable) {
4267
+ return false;
2993
4268
  }
4269
+ const maxTok = resolved.summarization.summaryTriggerTokens;
2994
4270
  try {
2995
- const allText = (state.messages || []).map((m) => {
4271
+ const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
2996
4272
  if (typeof m.content === "string") return m.content;
2997
4273
  if (Array.isArray(m.content)) {
2998
4274
  return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
@@ -3003,7 +4279,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
3003
4279
  return "";
3004
4280
  }).join("\n");
3005
4281
  const tokenCount = countApproxTokens(allText);
3006
- const needsSum = tokenCount > maxTok;
4282
+ const contextRotScore = state.watchdog?.contextRotScore || 0;
4283
+ const needsSum = tokenCount > maxTok || resolved.watchdog.enabled && resolved.watchdog.autoCompaction && contextRotScore >= resolved.watchdog.contextRotThreshold;
3007
4284
  if (process.env.DEBUG_SUMMARIZATION) {
3008
4285
  const msgCount = (state.messages || []).length;
3009
4286
  const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
@@ -3013,10 +4290,10 @@ function needsSummarization(state, opts, summarizationEnabled) {
3013
4290
  });
3014
4291
  console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
3015
4292
  toolDetails.forEach((d) => console.log(` ${d}`));
3016
- console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
4293
+ console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Context rot: ${contextRotScore}, Needs summarization: ${needsSum}`);
3017
4294
  }
3018
4295
  return needsSum;
3019
- } catch (e) {
4296
+ } catch {
3020
4297
  return false;
3021
4298
  }
3022
4299
  }
@@ -3034,33 +4311,205 @@ function toolsDecisionFactory(opts, summarizationEnabled) {
3034
4311
  };
3035
4312
  }
3036
4313
 
4314
+ // src/smart/memory.ts
4315
+ function normalizeFact(scope, fact) {
4316
+ return {
4317
+ ...fact,
4318
+ scope,
4319
+ obsolete: fact.obsolete ?? false,
4320
+ lastUpdatedAt: fact.lastUpdatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4321
+ confidence: Number.isFinite(fact.confidence) ? fact.confidence : 0.5
4322
+ };
4323
+ }
4324
+ var InMemoryMemoryStore = class {
4325
+ constructor() {
4326
+ this.buckets = /* @__PURE__ */ new Map();
4327
+ }
4328
+ async get(scope, options) {
4329
+ const includeObsolete = options?.includeObsolete === true;
4330
+ 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());
4331
+ return typeof options?.limit === "number" ? all.slice(0, options.limit) : all;
4332
+ }
4333
+ async upsert(scope, facts) {
4334
+ const existing = [...this.buckets.get(scope) || []];
4335
+ for (const fact of facts) {
4336
+ const normalized = normalizeFact(scope, fact);
4337
+ const sameKey = existing.filter((entry) => entry.key === normalized.key && !entry.obsolete);
4338
+ for (const entry of sameKey) {
4339
+ if (entry.value !== normalized.value) {
4340
+ entry.obsolete = true;
4341
+ entry.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
4342
+ }
4343
+ }
4344
+ const match = existing.find((entry) => entry.key === normalized.key && entry.value === normalized.value && !entry.obsolete);
4345
+ if (match) {
4346
+ match.confidence = Math.max(match.confidence, normalized.confidence);
4347
+ match.sourceTurn = normalized.sourceTurn;
4348
+ match.ttl = normalized.ttl;
4349
+ match.tags = normalized.tags;
4350
+ match.lastUpdatedAt = normalized.lastUpdatedAt;
4351
+ } else {
4352
+ existing.push(normalized);
4353
+ }
4354
+ }
4355
+ this.buckets.set(scope, existing);
4356
+ }
4357
+ async markObsolete(scope, keys) {
4358
+ const bucket = this.buckets.get(scope) || [];
4359
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4360
+ for (const fact of bucket) {
4361
+ if (keys.includes(fact.key)) {
4362
+ fact.obsolete = true;
4363
+ fact.lastUpdatedAt = now;
4364
+ }
4365
+ }
4366
+ this.buckets.set(scope, bucket);
4367
+ }
4368
+ async semanticSearch(scope, query, options) {
4369
+ const normalizedQuery = query.toLowerCase();
4370
+ const bucket = (this.buckets.get(scope) || []).filter((fact) => !fact.obsolete);
4371
+ const scored = bucket.map((fact) => {
4372
+ const haystack = `${fact.key} ${fact.value} ${(fact.tags || []).join(" ")}`.toLowerCase();
4373
+ const score = haystack.includes(normalizedQuery) ? 2 : normalizedQuery.split(/\s+/).filter(Boolean).reduce((acc, token) => acc + (haystack.includes(token) ? 1 : 0), 0);
4374
+ return { fact, score };
4375
+ }).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || right.fact.confidence - left.fact.confidence).map((entry) => entry.fact);
4376
+ return typeof options?.limit === "number" ? scored.slice(0, options.limit) : scored;
4377
+ }
4378
+ };
4379
+ function resolveMemoryStore(config) {
4380
+ if (config.memory.store) return config.memory.store;
4381
+ if (config.memory.provider === "inMemory") return new InMemoryMemoryStore();
4382
+ return void 0;
4383
+ }
4384
+ async function readMemoryFacts(store, config, query) {
4385
+ if (!store) return [];
4386
+ const scope = config.memory.scope;
4387
+ const limit = config.context.lastTurnsToKeep;
4388
+ const policy = config.memory.readPolicy;
4389
+ if (policy === "recent_only") {
4390
+ return store.get(scope, { limit });
4391
+ }
4392
+ if (policy === "semantic" && typeof store.semanticSearch === "function") {
4393
+ return store.semanticSearch(scope, query, { limit });
4394
+ }
4395
+ if (policy === "hybrid") {
4396
+ const recent = await store.get(scope, { limit: Math.max(3, Math.ceil(limit / 2)) });
4397
+ const semantic = typeof store.semanticSearch === "function" ? await store.semanticSearch(scope, query, { limit: Math.max(3, Math.ceil(limit / 2)) }) : [];
4398
+ const deduped = /* @__PURE__ */ new Map();
4399
+ for (const fact of [...semantic, ...recent]) {
4400
+ deduped.set(`${fact.key}:${fact.value}`, fact);
4401
+ }
4402
+ return [...deduped.values()].slice(0, limit);
4403
+ }
4404
+ return store.get(scope, { limit });
4405
+ }
4406
+ async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
4407
+ if (!store || !summary) return [];
4408
+ const facts = summary.stable_facts.map((fact) => ({
4409
+ key: fact.key,
4410
+ value: fact.value,
4411
+ sourceTurn,
4412
+ confidence: fact.confidence ?? 0.8,
4413
+ obsolete: false,
4414
+ tags: ["summary_fact"]
4415
+ }));
4416
+ if (summary.discarded_obsolete.length > 0) {
4417
+ await store.markObsolete(config.memory.scope, summary.discarded_obsolete);
4418
+ }
4419
+ if (facts.length > 0) {
4420
+ await store.upsert(config.memory.scope, facts);
4421
+ }
4422
+ return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
4423
+ }
4424
+ function renderMemoryFacts(facts) {
4425
+ if (facts.length === 0) return "";
4426
+ return [
4427
+ "Memory facts:",
4428
+ ...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
4429
+ ].join("\n");
4430
+ }
4431
+
3037
4432
  // src/smart/index.ts
3038
4433
  function createSmartAgent(opts) {
3039
- const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0 };
3040
- const planningEnabled = opts.useTodoList === true;
4434
+ const resolved = normalizeSmartAgentOptions(opts);
4435
+ const planningEnabled = resolved.planning.mode !== "off";
4436
+ const memoryStore = resolveMemoryStore(resolved);
4437
+ const runtimeOpts = {
4438
+ ...opts,
4439
+ runtimeProfile: resolved.runtimeProfile,
4440
+ limits: resolved.limits,
4441
+ summarization: resolved.summarization,
4442
+ context: resolved.context,
4443
+ planning: resolved.planning,
4444
+ delegation: resolved.delegation,
4445
+ memory: { ...resolved.memory, store: memoryStore },
4446
+ toolResponses: resolved.toolResponses,
4447
+ watchdog: resolved.watchdog,
4448
+ useTodoList: planningEnabled
4449
+ };
4450
+ const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
3041
4451
  const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
3042
4452
  const mergedTools = [...opts.tools ?? [], ...contextTools];
3043
- const base = createAgent({ ...opts, tools: mergedTools });
3044
- let summarizationEnabled = false;
3045
- if (typeof opts.summarization === "object") {
3046
- summarizationEnabled = opts.summarization.enable !== false;
3047
- } else {
3048
- summarizationEnabled = opts.summarization !== false;
3049
- }
3050
- const summarizer = summarizationEnabled ? createContextSummarizeNode(opts) : void 0;
3051
- const decideBefore = resolverDecisionFactory(opts, summarizationEnabled);
3052
- const decideAfter = toolsDecisionFactory(opts, summarizationEnabled);
4453
+ const base = createAgent({ ...runtimeOpts, tools: mergedTools });
4454
+ base.__runtime.runtimeProfile = resolved.runtimeProfile;
4455
+ base.__runtime.smart = resolved;
4456
+ const summarizationEnabled = resolved.summarization.enable !== false;
4457
+ const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
4458
+ const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
4459
+ const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
3053
4460
  const structuredOutputHint = opts.outputSchema ? [
3054
4461
  "A structured output schema is active.",
3055
4462
  "Do NOT output the final JSON directly as an assistant message.",
3056
4463
  "When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
3057
4464
  "Call it exactly once then STOP producing further assistant messages."
3058
4465
  ].join("\n") : "";
4466
+ const runtimeHint = [
4467
+ resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
4468
+ `Context policy: ${resolved.context.policy}.`,
4469
+ `Planning mode: ${resolved.planning.mode}. Replan policy: ${resolved.planning.replanPolicy}.`,
4470
+ `Delegation mode: ${resolved.delegation.mode}. Child context policy: ${resolved.delegation.childContextPolicy}.`,
4471
+ `Tool response policy: ${resolved.context.toolResponsePolicy}.`
4472
+ ].join("\n");
4473
+ function latestUserPrompt(messages) {
4474
+ const latest = [...messages].reverse().find((message) => message.role === "user");
4475
+ if (!latest) return "";
4476
+ return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
4477
+ }
4478
+ async function syncMemory(state) {
4479
+ const query = latestUserPrompt(state.messages || []);
4480
+ const memoryFacts = await readMemoryFacts(memoryStore, resolved, query);
4481
+ return { ...state, memoryFacts };
4482
+ }
4483
+ async function persistLatestSummary(state) {
4484
+ if (!memoryStore) return state;
4485
+ if (resolved.memory.writePolicy === "manual") return state;
4486
+ const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
4487
+ if (!latestSummary) return state;
4488
+ if (resolved.memory.writePolicy === "auto_important" && latestSummary.stable_facts.length === 0) {
4489
+ return state;
4490
+ }
4491
+ const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
4492
+ return { ...state, memoryFacts };
4493
+ }
4494
+ function syncPlanState(state) {
4495
+ if (!Array.isArray(stateRef.todoList)) return state;
4496
+ return {
4497
+ ...state,
4498
+ plan: {
4499
+ version: stateRef.planVersion || state.planVersion || 1,
4500
+ steps: stateRef.todoList,
4501
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
4502
+ adherenceScore: stateRef.adherenceScore || 0
4503
+ },
4504
+ planVersion: stateRef.planVersion || state.planVersion || 1
4505
+ };
4506
+ }
3059
4507
  function systemMessage2() {
3060
4508
  const sys = buildSystemPrompt(
3061
- [opts.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
3062
- opts.useTodoList === true,
3063
- opts.name || "Agent"
4509
+ [opts.systemPrompt, runtimeHint, structuredOutputHint].filter(Boolean).join("\n"),
4510
+ planningEnabled,
4511
+ opts.name || "Agent",
4512
+ opts.todoListPrompt
3064
4513
  );
3065
4514
  return { role: "system", content: sys };
3066
4515
  }
@@ -3068,38 +4517,81 @@ function createSmartAgent(opts) {
3068
4517
  invoke: async (input, config) => {
3069
4518
  stateRef.toolHistory = input.toolHistory;
3070
4519
  stateRef.toolHistoryArchived = input.toolHistoryArchived;
4520
+ stateRef.todoList = input.plan?.steps;
4521
+ stateRef.planVersion = input.planVersion || input.plan?.version || 0;
4522
+ stateRef.adherenceScore = input.plan?.adherenceScore || 0;
3071
4523
  const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
3072
4524
  const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
3073
- let state = { ...input, messages: seedMessages };
4525
+ let state = await syncMemory({ ...input, messages: seedMessages });
3074
4526
  let lastResult = null;
3075
- const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
4527
+ let rawMessages = [...seedMessages];
4528
+ const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
3076
4529
  const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
4530
+ let previousTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
3077
4531
  for (let i = 0; i < iterationLimit; i++) {
4532
+ state = { ...state, messages: rawMessages };
3078
4533
  const next = summarizationEnabled ? decideBefore(state) : "agent";
3079
4534
  if (next === "contextSummarize" && summarizer) {
3080
4535
  const delta = await summarizer(state);
3081
4536
  if (!delta || Object.keys(delta).length === 0) ; else {
3082
- state = { ...state, ...delta };
4537
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4538
+ rawMessages = [...state.messages || rawMessages];
3083
4539
  continue;
3084
4540
  }
3085
4541
  }
3086
- const res = await base.invoke(state, config);
4542
+ const modelMessages = buildModelMessages(state, resolved);
4543
+ const res = await base.invoke({ ...state, messages: modelMessages }, config);
3087
4544
  lastResult = res;
3088
4545
  const currentSummaries = state.summaries;
4546
+ const currentSummaryRecords = state.summaryRecords;
4547
+ const currentPlan = state.plan;
4548
+ const appendedMessages = res.messages.slice(modelMessages.length);
4549
+ rawMessages = [...rawMessages, ...appendedMessages];
3089
4550
  state = res.state || { ...state, messages: res.messages };
4551
+ state = { ...state, messages: rawMessages };
3090
4552
  if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
3091
4553
  state = { ...state, summaries: currentSummaries };
3092
4554
  }
3093
- if (state.ctx?.__awaitingApproval || state.ctx?.__waitingForInput) break;
4555
+ if (currentSummaryRecords && currentSummaryRecords.length > 0 && (!state.summaryRecords || state.summaryRecords.length === 0)) {
4556
+ state = { ...state, summaryRecords: currentSummaryRecords };
4557
+ }
4558
+ if (currentPlan && !state.plan) {
4559
+ state = { ...state, plan: currentPlan };
4560
+ }
4561
+ state = syncPlanState(await syncMemory(state));
4562
+ stateRef.toolHistory = state.toolHistory;
4563
+ stateRef.toolHistoryArchived = state.toolHistoryArchived;
4564
+ const currentTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
4565
+ const tokenDrift = Math.max(0, currentTokenCount - previousTokenCount);
4566
+ const toolMessagesThisTurn = appendedMessages.filter((message) => message.role === "tool").length;
4567
+ const overToolingRate = appendedMessages.length === 0 ? 0 : Number((toolMessagesThisTurn / appendedMessages.length).toFixed(2));
4568
+ state = {
4569
+ ...state,
4570
+ watchdog: {
4571
+ tokenDrift,
4572
+ contextRotScore: estimateContextRotScore(state),
4573
+ overToolingRate,
4574
+ compactions: state.watchdog?.compactions || 0,
4575
+ lastAction: appendedMessages.length > 0 ? "agent_turn" : state.watchdog?.lastAction
4576
+ }
4577
+ };
4578
+ previousTokenCount = currentTokenCount;
4579
+ if (resolved.watchdog.enabled && resolved.watchdog.autoCompaction) {
4580
+ const shouldCompact = tokenDrift >= resolved.watchdog.tokenDriftThreshold || (state.watchdog?.overToolingRate || 0) >= resolved.watchdog.overToolingSpikeThreshold || (state.watchdog?.contextRotScore || 0) >= resolved.watchdog.contextRotThreshold;
4581
+ if (shouldCompact) {
4582
+ state = { ...state, ctx: { ...state.ctx || {}, __needsSummarization: true } };
4583
+ }
4584
+ }
3094
4585
  if (state.ctx?.__needsSummarization && summarizer) {
3095
4586
  const ctx = { ...state.ctx || {} };
3096
4587
  delete ctx.__needsSummarization;
3097
4588
  state = { ...state, ctx };
3098
4589
  const delta = await summarizer(state);
3099
4590
  if (delta && Object.keys(delta).length > 0) {
3100
- state = { ...state, ...delta };
4591
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4592
+ rawMessages = [...state.messages || rawMessages];
4593
+ continue;
3101
4594
  }
3102
- continue;
3103
4595
  }
3104
4596
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3105
4597
  if (summarizationEnabled) {
@@ -3107,7 +4599,8 @@ function createSmartAgent(opts) {
3107
4599
  if (after === "contextSummarize" && summarizer) {
3108
4600
  const delta = await summarizer(state);
3109
4601
  if (delta && Object.keys(delta).length > 0) {
3110
- state = { ...state, ...delta };
4602
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4603
+ rawMessages = [...state.messages || rawMessages];
3111
4604
  continue;
3112
4605
  }
3113
4606
  }
@@ -3120,10 +4613,12 @@ function createSmartAgent(opts) {
3120
4613
  }
3121
4614
  if (state.summaries && state.summaries.length > 0) {
3122
4615
  if (lastResult.state) {
3123
- lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
4616
+ 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 } };
3124
4617
  } else {
3125
- lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
4618
+ lastResult = { ...lastResult, state: { ...state, summaries: state.summaries, summaryRecords: state.summaryRecords, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
3126
4619
  }
4620
+ } else if (lastResult.state) {
4621
+ lastResult = { ...lastResult, state: { ...lastResult.state, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
3127
4622
  }
3128
4623
  return lastResult;
3129
4624
  },
@@ -3137,6 +4632,141 @@ function createSmartAgent(opts) {
3137
4632
  return instance;
3138
4633
  }
3139
4634
 
4635
+ // src/smart/eval.ts
4636
+ function normalizeProfileTarget(target) {
4637
+ if (typeof target === "string") {
4638
+ return {
4639
+ label: target,
4640
+ runtimeProfile: target,
4641
+ baseProfile: target === "custom" ? "balanced" : target
4642
+ };
4643
+ }
4644
+ return {
4645
+ label: target.label,
4646
+ runtimeProfile: target.runtimeProfile,
4647
+ baseProfile: target.baseProfile ?? (target.runtimeProfile === "custom" ? target.customProfile?.extends ?? "balanced" : target.runtimeProfile),
4648
+ customProfile: target.customProfile
4649
+ };
4650
+ }
4651
+ function normalizeText(value) {
4652
+ return (value || "").toLowerCase();
4653
+ }
4654
+ function ratio(numerator, denominator) {
4655
+ if (denominator <= 0) return 1;
4656
+ return Number((numerator / denominator).toFixed(3));
4657
+ }
4658
+ function sum(values) {
4659
+ return values.reduce((acc, value) => acc + value, 0);
4660
+ }
4661
+ function mean(values) {
4662
+ if (values.length === 0) return 0;
4663
+ return Number((sum(values) / values.length).toFixed(3));
4664
+ }
4665
+ function extractTotalTokens(usage) {
4666
+ if (!usage?.totals || typeof usage.totals !== "object") return 0;
4667
+ return Object.values(usage.totals).reduce((acc, entry) => acc + (Number(entry?.total) || 0), 0);
4668
+ }
4669
+ function caseResultFromRun(descriptor, testCase, content, events, state, latencyMs, totalTokens) {
4670
+ const normalizedContent = normalizeText(content);
4671
+ const matchedPhrases = (testCase.expectedPhrases || []).filter((phrase) => normalizedContent.includes(normalizeText(phrase)));
4672
+ const matchedFacts = (testCase.expectedFacts || []).filter((fact) => {
4673
+ const memoryMatch = state?.memoryFacts?.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value)));
4674
+ const summaryMatch = state?.summaryRecords?.some((record) => record.stable_facts.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value))));
4675
+ return Boolean(memoryMatch || summaryMatch || normalizedContent.includes(normalizeText(fact.value)));
4676
+ });
4677
+ const forbiddenMisses = (testCase.forbiddenPhrases || []).filter((phrase) => !normalizedContent.includes(normalizeText(phrase)));
4678
+ const toolCalls = events.filter((event) => event.type === "tool_call" && event.phase === "success");
4679
+ const expectedToolHits = (testCase.expectedToolNames || []).filter((toolName) => toolCalls.some((event) => event.name === toolName));
4680
+ const toolPrecision = ratio(expectedToolHits.length, Math.max(toolCalls.length, 1));
4681
+ const recallAccuracy = ratio(matchedPhrases.length + matchedFacts.length, (testCase.expectedPhrases?.length || 0) + (testCase.expectedFacts?.length || 0));
4682
+ const obsoleteDropAccuracy = ratio(forbiddenMisses.length, testCase.forbiddenPhrases?.length || 0);
4683
+ const trajectoryScore = ratio(expectedToolHits.length, testCase.expectedToolNames?.length || 0);
4684
+ const hadRecoverableError = events.some((event) => event.type === "tool_call" && event.phase === "error");
4685
+ const recoveryRate = hadRecoverableError ? Number((normalizedContent.length > 0 ? 1 : 0).toFixed(3)) : 1;
4686
+ const overToolingRate = toolCalls.length === 0 ? 0 : Number((Math.max(0, toolCalls.length - Math.max(testCase.expectedToolNames?.length || 0, 1)) / toolCalls.length).toFixed(3));
4687
+ const success = recallAccuracy >= 0.8 && obsoleteDropAccuracy >= 0.8;
4688
+ return {
4689
+ id: testCase.id,
4690
+ family: testCase.family,
4691
+ success,
4692
+ recallAccuracy,
4693
+ obsoleteDropAccuracy,
4694
+ trajectoryScore: Number(((trajectoryScore + toolPrecision) / 2).toFixed(3)),
4695
+ recoveryRate,
4696
+ overToolingRate,
4697
+ latencyMs,
4698
+ totalTokens,
4699
+ profile: descriptor.runtimeProfile,
4700
+ profileLabel: descriptor.label,
4701
+ baseProfile: descriptor.baseProfile,
4702
+ notes: success ? [] : [
4703
+ recallAccuracy < 0.8 ? "Recall below threshold." : "",
4704
+ obsoleteDropAccuracy < 0.8 ? "Obsolete filtering below threshold." : "",
4705
+ trajectoryScore < 0.5 ? "Tool trajectory diverged from expectation." : ""
4706
+ ].filter(Boolean)
4707
+ };
4708
+ }
4709
+ function aggregateMetrics(caseResults) {
4710
+ const taskSuccess = ratio(caseResults.filter((result) => result.success).length, caseResults.length);
4711
+ const recallAccuracy = mean(caseResults.map((result) => result.recallAccuracy));
4712
+ const obsoleteDropAccuracy = mean(caseResults.map((result) => result.obsoleteDropAccuracy));
4713
+ const trajectoryScore = mean(caseResults.map((result) => result.trajectoryScore));
4714
+ const recoveryRate = mean(caseResults.map((result) => result.recoveryRate));
4715
+ const overToolingRate = mean(caseResults.map((result) => result.overToolingRate));
4716
+ const latencyMs = mean(caseResults.map((result) => result.latencyMs));
4717
+ const totalTokens = sum(caseResults.map((result) => result.totalTokens || 0));
4718
+ const toolPrecision = trajectoryScore;
4719
+ const costScore = totalTokens <= 0 ? 1 : Number((1 / (1 + totalTokens / 1e4)).toFixed(3));
4720
+ const score = Number((0.45 * taskSuccess + 0.2 * trajectoryScore + 0.15 * toolPrecision + 0.1 * recoveryRate + 0.1 * costScore).toFixed(3));
4721
+ return {
4722
+ taskSuccess,
4723
+ recallAccuracy,
4724
+ obsoleteDropAccuracy,
4725
+ trajectoryScore,
4726
+ recoveryRate,
4727
+ overToolingRate,
4728
+ latencyMs,
4729
+ totalTokens,
4730
+ score
4731
+ };
4732
+ }
4733
+ async function runSmartAgentEvalHarness(params) {
4734
+ const results = [];
4735
+ for (const profileTarget of params.profiles) {
4736
+ const descriptor = normalizeProfileTarget(profileTarget);
4737
+ const caseResults = [];
4738
+ for (const testCase of params.cases) {
4739
+ const agent = params.createAgent(descriptor.runtimeProfile, descriptor);
4740
+ const events = [];
4741
+ const startedAt = Date.now();
4742
+ const result = await agent.invoke({
4743
+ messages: [{ role: "user", content: testCase.prompt }]
4744
+ }, {
4745
+ onEvent: (event) => {
4746
+ events.push(event);
4747
+ }
4748
+ });
4749
+ caseResults.push(caseResultFromRun(
4750
+ descriptor,
4751
+ testCase,
4752
+ result.content,
4753
+ events,
4754
+ result.state,
4755
+ Date.now() - startedAt,
4756
+ extractTotalTokens(result.metadata?.usage)
4757
+ ));
4758
+ }
4759
+ results.push({
4760
+ profile: descriptor.runtimeProfile,
4761
+ profileLabel: descriptor.label,
4762
+ baseProfile: descriptor.baseProfile,
4763
+ metrics: aggregateMetrics(caseResults),
4764
+ cases: caseResults
4765
+ });
4766
+ }
4767
+ return results;
4768
+ }
4769
+
3140
4770
  // src/utils/content.ts
3141
4771
  function contentToString(content) {
3142
4772
  try {
@@ -3414,6 +5044,6 @@ function createCodeGuardrail(options) {
3414
5044
  });
3415
5045
  }
3416
5046
 
3417
- export { GuardrailPhase, agentVerdictRule, applyTokenLimits, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGuardrail, createJsonGuardrail, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, evaluateGuardrails, fileSink, fromLangchainModel, fromLangchainTools, httpSink, isSmartModel, jsonSchemaRule, regexRule, resolveToolApprovalState, restoreSnapshot, startStreamingSession, withTools };
5047
+ export { DEFAULT_PROFILE_CONFIGS, GuardrailPhase, InMemoryMemoryStore, agentVerdictRule, applyTokenLimits, buildSystemPrompt, captureSnapshot, codePresenceRule, cognipeerSink, countApproxTokens, countMessagesTokens, createAgent, createAgentNode, createCodeGuardrail, createContextSummarizeNode, createContextTools, createGuardrail, createJsonGuardrail, createRegexGuardrail, createResolverNode, createSmartAgent, createTool, createToolLimitFinalizeNode, createToolsNode, customCallbackRule, customSink, evaluateGuardrails, fileSink, fromLangchainModel, fromLangchainTools, generateSpanId, generateTraceId, getResolvedSmartConfig, httpSink, isSmartModel, jsonSchemaRule, normalizeSmartAgentOptions, otlpSink, readMemoryFacts, regexRule, renderMemoryFacts, resolveMemoryStore, resolveToolApprovalState, restoreSnapshot, runSmartAgentEvalHarness, startStreamingSession, traceSessionToOtlp, withTools, writeSummaryFactsToMemory };
3418
5048
  //# sourceMappingURL=index.mjs.map
3419
5049
  //# sourceMappingURL=index.mjs.map