@cognipeer/agent-sdk 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,523 @@ 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
+ return JSON.stringify(value);
2143
+ } catch {
2144
+ return String(value);
2145
+ }
2146
+ }
2147
+ function summarizeObject(value) {
2148
+ if (value == null) return "null";
2149
+ if (typeof value === "string") {
2150
+ return value.length > 240 ? `${value.slice(0, 240)}...` : value;
2151
+ }
2152
+ if (Array.isArray(value)) {
2153
+ const preview = value.slice(0, 3).map((entry) => safeStringify(entry).slice(0, 120)).join(" | ");
2154
+ return `array(length=${value.length}) ${preview}`.trim();
2155
+ }
2156
+ if (typeof value === "object") {
2157
+ const keys = Object.keys(value);
2158
+ const preview = keys.slice(0, 6).map((key) => `${key}=${safeStringify(value[key]).slice(0, 80)}`).join("; ");
2159
+ return `object(keys=${keys.length}) ${preview}`.trim();
2160
+ }
2161
+ return safeStringify(value);
2162
+ }
2163
+ function classifyToolResponse(toolName, output, config) {
2164
+ const serialized = safeStringify(output);
2165
+ const tokenCount = countApproxTokens(serialized);
2166
+ const criticalTools = new Set(config.toolResponses.criticalTools);
2167
+ if (criticalTools.has(toolName)) return "critical";
2168
+ if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
2169
+ return "verbose";
2170
+ }
2171
+ if (serialized === "ok" || serialized === "true" || serialized === "null") {
2172
+ return "redundant";
2173
+ }
2174
+ return "informative";
2175
+ }
2176
+ function resolveToolResponsePolicy(toolName, output, config) {
2177
+ const serialized = safeStringify(output);
2178
+ const tokenCount = countApproxTokens(serialized);
2179
+ const classification = classifyToolResponse(toolName, output, config);
2180
+ const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2181
+ let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
2182
+ if (classification === "critical") {
2183
+ retentionPolicy = byTool || "keep_structured";
2184
+ } else if (classification === "verbose") {
2185
+ retentionPolicy = byTool || config.toolResponses.largeResponsePolicy;
2186
+ } else if (classification === "redundant") {
2187
+ retentionPolicy = byTool || "drop";
2188
+ }
2189
+ const summary = summarizeObject(output);
2190
+ if (retentionPolicy === "keep_full") {
2191
+ return { classification, retentionPolicy, content: serialized, rawOutput: output, summary, tokenCount };
2192
+ }
2193
+ if (retentionPolicy === "keep_structured") {
2194
+ return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
2195
+ }
2196
+ if (retentionPolicy === "summarize_archive") {
2197
+ return {
2198
+ classification,
2199
+ retentionPolicy,
2200
+ content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
2201
+ rawOutput: output,
2202
+ summary,
2203
+ tokenCount
2204
+ };
2205
+ }
2206
+ return {
2207
+ classification,
2208
+ retentionPolicy,
2209
+ content: `DROPPED_TOOL_RESPONSE: ${summary}`,
2210
+ rawOutput: output,
2211
+ summary,
2212
+ tokenCount
2213
+ };
2214
+ }
2215
+ function validateToolArgs(tool, args) {
2216
+ const schema = tool.schema;
2217
+ if (!schema || typeof schema.parse !== "function") {
2218
+ return { ok: true, value: args };
2219
+ }
2220
+ try {
2221
+ return { ok: true, value: schema.parse(args) };
2222
+ } catch (error) {
2223
+ if (error instanceof ZodError) {
2224
+ const message = error.issues.map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
2225
+ return { ok: false, message };
2226
+ }
2227
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
2228
+ }
2229
+ }
2230
+
2231
+ // src/nodes/tools.ts
2232
+ function normalizeMaxExecutionsPerRun(value) {
2233
+ if (value === null || value === void 0 || value === "") return null;
2234
+ const parsed = Number(value);
2235
+ if (!Number.isFinite(parsed)) return null;
2236
+ const normalized = Math.trunc(parsed);
2237
+ return normalized >= 0 ? normalized : null;
2238
+ }
2239
+ function countSuccessfulToolExecutions(toolHistory, toolName) {
2240
+ if (!Array.isArray(toolHistory)) return 0;
2241
+ return toolHistory.filter((entry) => entry?.toolName === toolName && (entry?.status === "success" || entry?.status === "handoff")).length;
2242
+ }
1463
2243
  function createToolsNode(initialTools, opts) {
1464
- const baseToolByName = /* @__PURE__ */ new Map();
1465
- for (const t of initialTools) baseToolByName.set(t.name, t);
1466
2244
  return async (state) => {
1467
2245
  const runtime = state.agent || {
1468
2246
  name: opts?.name,
@@ -1470,15 +2248,17 @@ function createToolsNode(initialTools, opts) {
1470
2248
  tools: initialTools,
1471
2249
  limits: opts?.limits,
1472
2250
  systemPrompt: opts?.systemPrompt,
2251
+ todoListPrompt: opts?.todoListPrompt,
1473
2252
  useTodoList: opts?.useTodoList,
1474
2253
  outputSchema: opts?.outputSchema
1475
2254
  };
2255
+ const resolved = getResolvedSmartConfig(opts || {}, runtime);
1476
2256
  const activeTools = runtime.tools;
1477
2257
  const toolByName = /* @__PURE__ */ new Map();
1478
- for (const t of activeTools) toolByName.set(t.name, t);
2258
+ for (const tool of activeTools) toolByName.set(tool.name, tool);
1479
2259
  const limits = {
1480
- maxToolCalls: runtime.limits?.maxToolCalls ?? 10,
1481
- maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? 1)
2260
+ maxToolCalls: runtime.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10,
2261
+ maxParallelTools: Math.max(1, runtime.limits?.maxParallelTools ?? resolved.limits.maxParallelTools ?? 1)
1482
2262
  };
1483
2263
  const appended = [];
1484
2264
  const onEvent = state.ctx?.__onEvent;
@@ -1489,20 +2269,19 @@ function createToolsNode(initialTools, opts) {
1489
2269
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals.map((entry) => ({ ...entry })) : [];
1490
2270
  const pendingByCallId = new Map(pendingApprovals.map((entry) => [entry.toolCallId, entry]));
1491
2271
  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;
2272
+ for (const tool of toolByName.values()) {
2273
+ const anyTool = tool;
2274
+ if (anyTool._stateRef && typeof anyTool._stateRef === "object") {
2275
+ anyTool._stateRef.toolHistory = state.toolHistory;
2276
+ anyTool._stateRef.toolHistoryArchived = state.toolHistoryArchived;
2277
+ anyTool._stateRef.__onEvent = onEvent;
1498
2278
  }
1499
2279
  }
1500
2280
  const last = state.messages[state.messages.length - 1];
1501
2281
  let toolCount = state.toolCallCount || 0;
1502
- const toolCalls = Array.isArray(
1503
- last?.tool_calls
1504
- ) ? last.tool_calls : [];
2282
+ const toolCalls = Array.isArray(last?.tool_calls) ? last.tool_calls : [];
1505
2283
  const toolHistory = state.toolHistory || [];
2284
+ const toolHistoryArchived = state.toolHistoryArchived || [];
1506
2285
  const remaining = Math.max(0, limits.maxToolCalls - toolCount);
1507
2286
  const planned = toolCalls.slice(0, remaining);
1508
2287
  const skipped = toolCalls.slice(remaining);
@@ -1511,6 +2290,17 @@ function createToolsNode(initialTools, opts) {
1511
2290
  if (cancellationToken && cancellationToken.isCancellationRequested) return { cancelled: true, reason: "cancelled" };
1512
2291
  return { cancelled: false, reason: void 0 };
1513
2292
  };
2293
+ const markToolFailure = (message, toolName, toolCallId) => {
2294
+ const ctx = { ...state.ctx || {} };
2295
+ ctx.__lastToolError = message;
2296
+ if (toolCallId) {
2297
+ ctx.__toolSchemaError = { toolName, toolCallId, message };
2298
+ }
2299
+ if (resolved.watchdog.autoReplanOnFailure) {
2300
+ ctx.__planNeedsReplan = true;
2301
+ }
2302
+ state.ctx = ctx;
2303
+ };
1514
2304
  const runOne = async (tc) => {
1515
2305
  const cancelState = isCancelled();
1516
2306
  if (cancelState.cancelled) {
@@ -1520,15 +2310,10 @@ function createToolsNode(initialTools, opts) {
1520
2310
  onProgress?.({ stage: "tools", message: "Cancelled", detail: { reason: cancelState.reason } });
1521
2311
  return { status: "error" };
1522
2312
  }
1523
- const t = toolByName.get(tc.name);
1524
- if (!t) {
2313
+ const tool = toolByName.get(tc.name);
2314
+ if (!tool) {
2315
+ appended.push({ role: "tool", content: `Tool not found: ${tc.name}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
1525
2316
  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
2317
  toolCount += 1;
1533
2318
  return { status: "error" };
1534
2319
  }
@@ -1536,22 +2321,67 @@ function createToolsNode(initialTools, opts) {
1536
2321
  if (typeof args === "string") {
1537
2322
  try {
1538
2323
  args = JSON.parse(args);
1539
- } catch (_) {
2324
+ } catch {
1540
2325
  }
1541
2326
  }
1542
2327
  const toolCallId = tc.id || `${tc.name}_${toolCount + 1}`;
2328
+ const validatedArgs = validateToolArgs(tool, args);
2329
+ if (!validatedArgs.ok && resolved.toolResponses.schemaValidation === "strict") {
2330
+ const errorMessage = `Tool argument validation failed for ${tc.name}: ${validatedArgs.message}`;
2331
+ appended.push({ role: "tool", content: errorMessage, tool_call_id: toolCallId, name: tc.name });
2332
+ markToolFailure(errorMessage, tc.name, toolCallId);
2333
+ onEvent?.({ type: "tool_call", phase: "error", name: tc.name, id: tc.id, args, error: { message: errorMessage } });
2334
+ toolCount += 1;
2335
+ return { status: "error" };
2336
+ }
2337
+ if (validatedArgs.ok) {
2338
+ args = validatedArgs.value;
2339
+ }
2340
+ const toolName = tool.name || tc.name;
2341
+ const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
2342
+ if (maxExecutionsPerRun !== null) {
2343
+ const currentExecutions = countSuccessfulToolExecutions(toolHistory, toolName);
2344
+ if (currentExecutions >= maxExecutionsPerRun) {
2345
+ const limitMessage = `Skipped tool due to per-tool execution limit: ${toolName} (${maxExecutionsPerRun}/run)`;
2346
+ appended.push({ role: "tool", content: limitMessage, tool_call_id: toolCallId, name: tc.name });
2347
+ onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args });
2348
+ recordTraceEvent(traceSession, {
2349
+ type: "tool_call",
2350
+ label: `Tool Skipped - ${toolName}`,
2351
+ actor: { scope: "tool", name: toolName, role: "tool" },
2352
+ status: "skipped",
2353
+ toolExecutionId: tc.id,
2354
+ messageList: [
2355
+ {
2356
+ role: "assistant",
2357
+ name: toolName,
2358
+ content: limitMessage,
2359
+ tool_calls: [
2360
+ {
2361
+ id: tc.id,
2362
+ type: "function",
2363
+ function: { name: toolName, arguments: sanitizeTracePayload(args) }
2364
+ }
2365
+ ]
2366
+ }
2367
+ ]
2368
+ });
2369
+ toolCount += 1;
2370
+ return { status: "error" };
2371
+ }
2372
+ }
1543
2373
  let approvalEntry = pendingByCallId.get(toolCallId);
1544
- const needsApproval = Boolean(t.needsApproval);
2374
+ const needsApproval = Boolean(tool.needsApproval);
1545
2375
  if (needsApproval) {
1546
2376
  if (!approvalEntry) {
1547
2377
  approvalEntry = {
1548
2378
  id: nanoid(),
1549
2379
  toolCallId,
1550
- toolName: t.name || tc.name,
2380
+ toolName: tool.name || tc.name,
1551
2381
  args,
1552
2382
  status: "pending",
1553
2383
  requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
1554
- metadata: t.approvalPrompt || t.approvalDefaults ? { prompt: t.approvalPrompt, defaults: t.approvalDefaults } : void 0
2384
+ metadata: tool.approvalPrompt || tool.approvalDefaults ? { prompt: tool.approvalPrompt, defaults: tool.approvalDefaults } : void 0
1555
2385
  };
1556
2386
  pendingApprovals.push(approvalEntry);
1557
2387
  pendingByCallId.set(toolCallId, approvalEntry);
@@ -1573,71 +2403,97 @@ function createToolsNode(initialTools, opts) {
1573
2403
  }
1574
2404
  if (approvalEntry.status === "rejected") {
1575
2405
  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 });
2406
+ appended.push({ role: "tool", content: `Tool call rejected: ${rejectionMessage}`, tool_call_id: toolCallId, name: tc.name });
1583
2407
  approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution: "rejected" };
1584
2408
  approvalEntry.status = "executed";
1585
2409
  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
2410
  pendingByCallId.set(toolCallId, approvalEntry);
2411
+ 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" });
2412
+ onEvent?.({ type: "tool_approval", status: "rejected", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, comment: approvalEntry.comment, decidedBy: approvalEntry.decidedBy });
1588
2413
  toolCount += 1;
1589
2414
  return { status: "rejected", approval: approvalEntry };
1590
2415
  }
1591
- if (approvalEntry.status === "approved") {
1592
- if (approvalEntry.approvedArgs !== void 0) {
1593
- args = approvalEntry.approvedArgs;
1594
- }
2416
+ if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
2417
+ args = approvalEntry.approvedArgs;
1595
2418
  onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
1596
2419
  }
1597
2420
  }
1598
2421
  const start = Date.now();
2422
+ const sanitizedArgs = sanitizeTracePayload(args);
2423
+ const inputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedArgs) : void 0;
1599
2424
  try {
1600
- onEvent?.({ type: "tool_call", phase: "start", name: t.name, id: tc.id, args });
2425
+ onEvent?.({ type: "tool_call", phase: "start", name: toolName, id: tc.id, args });
1601
2426
  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;
2427
+ const anyTool = tool;
1606
2428
  const callOptions = { cancellationToken, signal: abortSignal };
2429
+ let output;
1607
2430
  if (typeof anyTool.func === "function") output = await anyTool.func(args, callOptions);
1608
2431
  else if (typeof anyTool.invoke === "function") output = await anyTool.invoke(args, callOptions);
1609
2432
  else if (typeof anyTool.call === "function") output = await anyTool.call(args, callOptions);
1610
2433
  else if (typeof anyTool._call === "function") output = await anyTool._call(args, callOptions);
1611
2434
  else if (typeof anyTool.run === "function") output = await anyTool.run(args, callOptions);
1612
2435
  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 });
2436
+ const durationMs = Date.now() - start;
2437
+ const executionId = nanoid();
2438
+ if (output && typeof output === "object" && output.__handoff && output.__handoff.runtime) {
2439
+ toolHistory.push({ executionId, toolName, args, output: "handoff:ok", rawOutput: output, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "handoff" });
1617
2440
  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;
2441
+ state.agent = output.__handoff.runtime;
2442
+ onEvent?.({ type: "handoff", from: runtime.name, to: state.agent?.name, toolName });
2443
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: "handoff", durationMs });
1622
2444
  if (needsApproval && approvalEntry) {
1623
2445
  approvalEntry.status = "executed";
1624
2446
  approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
1625
- approvalEntry.executionId = executionId2;
2447
+ approvalEntry.executionId = executionId;
1626
2448
  pendingByCallId.set(toolCallId, approvalEntry);
1627
2449
  }
2450
+ toolCount += 1;
1628
2451
  return { status: "success", approval: approvalEntry };
1629
2452
  }
1630
- const content = typeof output === "string" ? output : JSON.stringify(output);
1631
2453
  if (output && typeof output === "object" && output.__finalStructuredOutput) {
1632
2454
  if (!state.ctx) state.ctx = {};
1633
2455
  state.ctx.__structuredOutputParsed = output.data;
1634
2456
  state.ctx.__finalizedDueToStructuredOutput = true;
1635
2457
  }
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 });
2458
+ const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
2459
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2460
+ const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
2461
+ toolHistory.push({
2462
+ executionId,
2463
+ toolName,
2464
+ args,
2465
+ output: storedOutput,
2466
+ rawOutput: output,
2467
+ timestamp,
2468
+ tool_call_id: tc.id,
2469
+ summarized: responsePolicy.retentionPolicy !== "keep_full",
2470
+ originalTokenCount: responsePolicy.tokenCount,
2471
+ classification: responsePolicy.classification,
2472
+ retentionPolicy: responsePolicy.retentionPolicy,
2473
+ summary: responsePolicy.summary,
2474
+ archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
2475
+ status: "success"
2476
+ });
2477
+ if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
2478
+ toolHistoryArchived.push({
2479
+ executionId,
2480
+ toolName,
2481
+ args,
2482
+ output,
2483
+ rawOutput: output,
2484
+ timestamp,
2485
+ tool_call_id: tc.id,
2486
+ summarized: true,
2487
+ originalTokenCount: responsePolicy.tokenCount,
2488
+ classification: responsePolicy.classification,
2489
+ retentionPolicy: responsePolicy.retentionPolicy,
2490
+ summary: responsePolicy.summary,
2491
+ archiveId: executionId,
2492
+ status: "success"
2493
+ });
2494
+ }
2495
+ appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2496
+ onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
1641
2497
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
1642
2498
  if (needsApproval && approvalEntry) {
1643
2499
  approvalEntry.status = "executed";
@@ -1647,7 +2503,6 @@ function createToolsNode(initialTools, opts) {
1647
2503
  }
1648
2504
  const sanitizedOutput = sanitizeTracePayload(output);
1649
2505
  const outputBytes = traceSession?.resolvedConfig.logData ? estimatePayloadBytes(sanitizedOutput) : void 0;
1650
- const toolName = t.name || tc.name;
1651
2506
  const messageList = [
1652
2507
  {
1653
2508
  role: "assistant",
@@ -1667,7 +2522,7 @@ function createToolsNode(initialTools, opts) {
1667
2522
  {
1668
2523
  role: "tool",
1669
2524
  name: toolName,
1670
- content: sanitizedOutput ?? output ?? ""
2525
+ content: responsePolicy.content
1671
2526
  }
1672
2527
  ];
1673
2528
  recordTraceEvent(traceSession, {
@@ -1678,43 +2533,24 @@ function createToolsNode(initialTools, opts) {
1678
2533
  requestBytes: inputBytes,
1679
2534
  responseBytes: outputBytes,
1680
2535
  toolExecutionId: executionId,
1681
- messageList
2536
+ messageList,
2537
+ debug: {
2538
+ classification: responsePolicy.classification,
2539
+ retentionPolicy: responsePolicy.retentionPolicy,
2540
+ originalTokenCount: responsePolicy.tokenCount
2541
+ }
1682
2542
  });
1683
2543
  toolCount += 1;
1684
2544
  return { status: "success", approval: approvalEntry };
1685
- } catch (e) {
2545
+ } catch (error) {
1686
2546
  const durationMs = Date.now() - start;
1687
2547
  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
- ];
2548
+ const message = error?.message || String(error);
2549
+ toolHistory.push({ executionId, toolName, args, output: `Error executing tool: ${message}`, rawOutput: null, timestamp: (/* @__PURE__ */ new Date()).toISOString(), tool_call_id: tc.id, status: "error" });
2550
+ appended.push({ role: "tool", content: `Error executing tool: ${message}`, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2551
+ markToolFailure(message, toolName, toolCallId);
2552
+ onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args, error: { message } });
2553
+ onProgress?.({ stage: "tools", message: `Tool ${tc.name} failed`, detail: { error: message } });
1718
2554
  recordTraceEvent(traceSession, {
1719
2555
  type: "tool_call",
1720
2556
  label: `Tool Error - ${toolName}`,
@@ -1723,8 +2559,26 @@ function createToolsNode(initialTools, opts) {
1723
2559
  durationMs,
1724
2560
  requestBytes: inputBytes,
1725
2561
  toolExecutionId: executionId,
1726
- error: { message: e?.message || String(e), stack: e?.stack },
1727
- messageList
2562
+ error: { message, stack: error?.stack },
2563
+ messageList: [
2564
+ {
2565
+ role: "assistant",
2566
+ name: toolName,
2567
+ content: "",
2568
+ tool_calls: [
2569
+ {
2570
+ id: tc.id || executionId,
2571
+ type: "function",
2572
+ function: { name: toolName, arguments: sanitizedArgs }
2573
+ }
2574
+ ]
2575
+ },
2576
+ {
2577
+ role: "tool",
2578
+ name: toolName,
2579
+ content: `Error executing tool: ${message}`
2580
+ }
2581
+ ]
1728
2582
  });
1729
2583
  toolCount += 1;
1730
2584
  return { status: "error", approval: approvalEntry };
@@ -1741,7 +2595,7 @@ function createToolsNode(initialTools, opts) {
1741
2595
  break;
1742
2596
  }
1743
2597
  const result = await runOne(tc);
1744
- if (result?.status === "awaiting_approval") {
2598
+ if (result.status === "awaiting_approval") {
1745
2599
  awaitingApproval = true;
1746
2600
  break;
1747
2601
  }
@@ -1749,37 +2603,28 @@ function createToolsNode(initialTools, opts) {
1749
2603
  for (const tc of skipped) {
1750
2604
  onEvent?.({ type: "tool_call", phase: "skipped", name: tc.name, id: tc.id, args: tc.args });
1751
2605
  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
2606
  recordTraceEvent(traceSession, {
1770
2607
  type: "tool_call",
1771
2608
  label: `Tool Skipped - ${tc.name}`,
1772
2609
  actor: { scope: "tool", name: tc.name, role: "tool" },
1773
2610
  status: "skipped",
1774
2611
  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
2612
+ messageList: [
2613
+ {
2614
+ role: "assistant",
2615
+ name: tc.name,
2616
+ content: `Skipped tool due to tool-call limit: ${tc.name}`,
2617
+ tool_calls: [
2618
+ {
2619
+ id: tc.id,
2620
+ type: "function",
2621
+ function: { name: tc.name, arguments: sanitizedArgs }
2622
+ }
2623
+ ]
2624
+ }
2625
+ ]
1782
2626
  });
2627
+ 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
2628
  toolCount += 1;
1784
2629
  }
1785
2630
  if (!awaitingApproval && state.ctx?.__awaitingApproval) {
@@ -1794,6 +2639,7 @@ function createToolsNode(initialTools, opts) {
1794
2639
  messages: [...state.messages, ...appended],
1795
2640
  toolCallCount: toolCount,
1796
2641
  toolHistory,
2642
+ toolHistoryArchived,
1797
2643
  agent: state.agent,
1798
2644
  pendingApprovals
1799
2645
  };
@@ -2018,68 +2864,20 @@ function resolveToolApprovalState(state, resolution) {
2018
2864
  };
2019
2865
  }
2020
2866
 
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
- }
2867
+ // src/agent.ts
2868
+ function isSyntheticSummaryMessage(message) {
2869
+ if (!message) return false;
2870
+ if (message.role === "tool" && message.name === "summarize_context") {
2871
+ return true;
2063
2872
  }
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
- }
2873
+ if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
2874
+ return message.tool_calls.some((toolCall) => {
2875
+ const toolName = toolCall?.function?.name || toolCall?.name;
2876
+ return toolName === "summarize_context";
2877
+ });
2073
2878
  }
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;
2879
+ return false;
2080
2880
  }
2081
-
2082
- // src/agent.ts
2083
2881
  function createAgent(opts) {
2084
2882
  const resolver = createResolverNode();
2085
2883
  const agentCore = createAgentCoreNode(opts);
@@ -2122,6 +2920,7 @@ function createAgent(opts) {
2122
2920
  tools: toolsBase,
2123
2921
  guardrails: opts.guardrails,
2124
2922
  systemPrompt: void 0,
2923
+ todoListPrompt: opts.todoListPrompt,
2125
2924
  limits: opts.limits,
2126
2925
  useTodoList: void 0,
2127
2926
  outputSchema: opts.outputSchema,
@@ -2197,6 +2996,18 @@ function createAgent(opts) {
2197
2996
  };
2198
2997
  while (iterations < iterationLimit) {
2199
2998
  iterations++;
2999
+ const traceSession = state.ctx?.__traceSession;
3000
+ if (traceSession) {
3001
+ const iterEvent = recordTraceEvent(traceSession, {
3002
+ type: "agent_iteration",
3003
+ label: `Iteration ${iterations}`,
3004
+ actor: { scope: "agent", name: opts.name || "agent", role: "orchestrator" },
3005
+ parentSpanId: traceSession.rootSpanId
3006
+ });
3007
+ if (iterEvent) {
3008
+ traceSession.currentIterationSpanId = iterEvent.spanId;
3009
+ }
3010
+ }
2200
3011
  if (cancelIfRequested("loop")) break;
2201
3012
  const skippingAgent = resumeStage === "tools";
2202
3013
  if (!skippingAgent) {
@@ -2247,11 +3058,21 @@ function createAgent(opts) {
2247
3058
  maxTok = 5e4;
2248
3059
  }
2249
3060
  if (maxTok) {
2250
- const tokenCount = countMessagesTokens(state.messages || []);
3061
+ const tokCountMessages = (state.messages || []).filter((message) => {
3062
+ if (isSyntheticSummaryMessage(message)) return false;
3063
+ if (message.role === "system" && (message.name === "context_summary" || message.name === "memory_context")) return false;
3064
+ return true;
3065
+ });
3066
+ const tokenCount = countMessagesTokens(tokCountMessages);
2251
3067
  if (tokenCount > maxTok) {
2252
- const ctx = { ...state.ctx || {}, __needsSummarization: true };
2253
- state = { ...state, ctx };
2254
- break;
3068
+ const hasFreshSummary = (state.messages || []).some(
3069
+ (m) => m.role === "tool" && m.content === "SUMMARIZED"
3070
+ );
3071
+ if (hasFreshSummary && tokenCount <= maxTok * 1.15) ; else {
3072
+ const ctx = { ...state.ctx || {}, __needsSummarization: true };
3073
+ state = { ...state, ctx };
3074
+ break;
3075
+ }
2255
3076
  }
2256
3077
  }
2257
3078
  onProgress?.({ stage: "agent", message: "Invoking model" });
@@ -2489,7 +3310,11 @@ function createAgent(opts) {
2489
3310
  schema,
2490
3311
  func: async ({ input }) => {
2491
3312
  const res = await instance.invoke({ messages: [{ role: "user", content: input }] });
2492
- return { content: res.content };
3313
+ return {
3314
+ content: res.content,
3315
+ output: res.output,
3316
+ summary: res.state?.summaries?.[res.state.summaries.length - 1]
3317
+ };
2493
3318
  }
2494
3319
  });
2495
3320
  },
@@ -2530,6 +3355,7 @@ function createAgentNode(opts) {
2530
3355
  model: opts.model,
2531
3356
  tools: opts.tools || [],
2532
3357
  systemPrompt: opts.systemPrompt,
3358
+ todoListPrompt: opts.todoListPrompt,
2533
3359
  limits: opts.limits,
2534
3360
  useTodoList: opts.useTodoList,
2535
3361
  outputSchema: opts.outputSchema
@@ -2547,7 +3373,8 @@ function createAgentNode(opts) {
2547
3373
  content: buildSystemPrompt(
2548
3374
  [runtime.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
2549
3375
  runtime.useTodoList === true,
2550
- runtime.name || "Agent"
3376
+ runtime.name || "Agent",
3377
+ runtime.todoListPrompt
2551
3378
  )
2552
3379
  };
2553
3380
  const messages = [systemMsg, ...state.messages];
@@ -2629,9 +3456,119 @@ function createAgentNode(opts) {
2629
3456
  };
2630
3457
  }
2631
3458
 
3459
+ // src/smart/contextPolicy.ts
3460
+ function messageText(message) {
3461
+ if (typeof message.content === "string") return message.content;
3462
+ if (Array.isArray(message.content)) {
3463
+ return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? JSON.stringify(part)).join(" ");
3464
+ }
3465
+ return "";
3466
+ }
3467
+ function collectRecentTurns(messages, lastTurnsToKeep) {
3468
+ if (lastTurnsToKeep <= 0) return [];
3469
+ const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
3470
+ const body = systemPrefix.length > 0 ? messages.slice(1) : [...messages];
3471
+ let userTurnCount = 0;
3472
+ const collected = [];
3473
+ for (let index = body.length - 1; index >= 0; index -= 1) {
3474
+ const message = body[index];
3475
+ collected.unshift(message);
3476
+ if (message.role === "user") {
3477
+ userTurnCount += 1;
3478
+ if (userTurnCount >= lastTurnsToKeep) break;
3479
+ }
3480
+ }
3481
+ return [...systemPrefix, ...collected];
3482
+ }
3483
+ function renderStructuredSummary(summary) {
3484
+ if (!summary) return "";
3485
+ const lines = ["Context summary:"];
3486
+ if (summary.stable_facts.length > 0) {
3487
+ lines.push("Stable facts:");
3488
+ lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
3489
+ }
3490
+ if (summary.active_goals.length > 0) {
3491
+ lines.push("Active goals:");
3492
+ lines.push(...summary.active_goals.map((goal) => `- ${goal}`));
3493
+ }
3494
+ if (summary.open_questions.length > 0) {
3495
+ lines.push("Open questions:");
3496
+ lines.push(...summary.open_questions.map((question) => `- ${question}`));
3497
+ }
3498
+ if (summary.discarded_obsolete.length > 0) {
3499
+ lines.push("Discarded obsolete:");
3500
+ lines.push(...summary.discarded_obsolete.map((item) => `- ${item}`));
3501
+ }
3502
+ return lines.join("\n");
3503
+ }
3504
+ function renderMemoryBlock(facts) {
3505
+ if (!facts || facts.length === 0) return "";
3506
+ return [
3507
+ "Retrieved memory:",
3508
+ ...facts.map((fact) => `- ${fact.key}: ${fact.value}`)
3509
+ ].join("\n");
3510
+ }
3511
+ function clampToBudget(messages, maxContextTokens) {
3512
+ let working = [...messages];
3513
+ while (working.length > 2) {
3514
+ const tokenCount = countApproxTokens(working.map(messageText).join("\n"));
3515
+ if (tokenCount <= maxContextTokens) return working;
3516
+ const firstNonSystem = working.findIndex((message, index) => !(index === 0 && message.role === "system"));
3517
+ if (firstNonSystem < 0) break;
3518
+ working.splice(firstNonSystem, 1);
3519
+ }
3520
+ return working;
3521
+ }
3522
+ function buildModelMessages(state, config) {
3523
+ const rawMessages = state.messages || [];
3524
+ if (config.context.policy === "raw") {
3525
+ return clampToBudget(rawMessages, config.limits.maxContextTokens);
3526
+ }
3527
+ const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
3528
+ const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
3529
+ const summaryText = renderStructuredSummary(latestSummary);
3530
+ const memoryText = renderMemoryBlock(state.memoryFacts);
3531
+ const systemMessage2 = recentMessages[0]?.role === "system" ? recentMessages[0] : void 0;
3532
+ const body = systemMessage2 ? recentMessages.slice(1) : recentMessages;
3533
+ const syntheticContextMessages = [];
3534
+ if (summaryText) {
3535
+ syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
3536
+ }
3537
+ if (memoryText) {
3538
+ syntheticContextMessages.push({ role: "system", name: "memory_context", content: memoryText });
3539
+ }
3540
+ const assembled = [
3541
+ ...systemMessage2 ? [systemMessage2] : [],
3542
+ ...syntheticContextMessages,
3543
+ ...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
3544
+ ];
3545
+ return clampToBudget(assembled, config.limits.maxContextTokens);
3546
+ }
3547
+ function estimateContextRotScore(state) {
3548
+ const summaryCount = state.summaryRecords?.length || 0;
3549
+ const archivedCount = state.toolHistoryArchived?.length || 0;
3550
+ const messageCount = state.messages?.length || 0;
3551
+ const summarizedMessages = state.messages?.filter((message) => message.role === "tool" && message.content === "SUMMARIZED").length || 0;
3552
+ const numerator = summaryCount * 2 + archivedCount + summarizedMessages;
3553
+ const denominator = Math.max(messageCount, 1);
3554
+ return Number((numerator / denominator).toFixed(3));
3555
+ }
3556
+
2632
3557
  // src/nodes/contextSummarize.ts
2633
3558
  var systemMessage = (content) => ({ role: "system", content });
2634
3559
  var humanMessage = (content) => ({ role: "user", content });
3560
+ function isSyntheticSummaryToolMessage(message) {
3561
+ return message.role === "tool" && message.name === "summarize_context";
3562
+ }
3563
+ function isSyntheticSummaryAssistantMessage(message) {
3564
+ if (message.role !== "assistant" || !Array.isArray(message.tool_calls)) {
3565
+ return false;
3566
+ }
3567
+ return message.tool_calls.some((toolCall) => {
3568
+ const toolName = toolCall?.function?.name || toolCall?.name;
3569
+ return toolName === "summarize_context";
3570
+ });
3571
+ }
2635
3572
  function validateMessageSequence(messages) {
2636
3573
  const validated = [];
2637
3574
  const pendingToolCallIds = /* @__PURE__ */ new Set();
@@ -2657,33 +3594,137 @@ function validateMessageSequence(messages) {
2657
3594
  }
2658
3595
  return validated;
2659
3596
  }
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
- };
3597
+ function extractJsonObject(text) {
3598
+ const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
3599
+ if (fenced?.[1]) return fenced[1].trim();
3600
+ const start = text.indexOf("{");
3601
+ const end = text.lastIndexOf("}");
3602
+ if (start >= 0 && end > start) {
3603
+ return text.slice(start, end + 1);
3604
+ }
3605
+ return null;
3606
+ }
3607
+ function normalizeStructuredSummary(input, fallbackText) {
3608
+ const summary = {
3609
+ stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
3610
+ key: String(fact.key || "unknown"),
3611
+ value: String(fact.value || ""),
3612
+ confidence: typeof fact.confidence === "number" ? fact.confidence : 0.7,
3613
+ source: fact.source ? String(fact.source) : void 0
3614
+ })) : [],
3615
+ active_goals: Array.isArray(input?.active_goals) ? input.active_goals.map((goal) => String(goal)) : [],
3616
+ open_questions: Array.isArray(input?.open_questions) ? input.open_questions.map((question) => String(question)) : [],
3617
+ discarded_obsolete: Array.isArray(input?.discarded_obsolete || input?.obsolete_discarded) ? (input.discarded_obsolete || input.obsolete_discarded).map((item) => String(item)) : [],
3618
+ rawSummary: typeof input?.rawSummary === "string" ? input.rawSummary : fallbackText
3619
+ };
3620
+ if (summary.stable_facts.length === 0 && fallbackText) {
3621
+ const importantLines = fallbackText.split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 8);
3622
+ summary.stable_facts = importantLines.map((line, index) => ({
3623
+ key: `fact_${index + 1}`,
3624
+ value: line,
3625
+ confidence: 0.5
3626
+ }));
3627
+ }
3628
+ return summary;
3629
+ }
3630
+ function normalizeFactKeySegment(value) {
3631
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "item";
3632
+ }
3633
+ function extractCanonicalFacts(messages) {
3634
+ const facts = /* @__PURE__ */ new Map();
3635
+ for (const message of messages) {
3636
+ const content = typeof message.content === "string" ? message.content : Array.isArray(message.content) ? message.content.map((part) => part?.text ?? part?.content ?? String(part ?? "")).join("\n") : "";
3637
+ if (!content) continue;
3638
+ const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
3639
+ for (const line of lines) {
3640
+ if (!/^[A-Z0-9_]+\|/.test(line) || !line.includes("=")) continue;
3641
+ const segments = line.split("|").map((segment) => segment.trim()).filter(Boolean);
3642
+ const label = segments[0];
3643
+ const entries = segments.slice(1).map((segment) => {
3644
+ const eqIndex = segment.indexOf("=");
3645
+ if (eqIndex <= 0) return null;
3646
+ return {
3647
+ key: segment.slice(0, eqIndex).trim(),
3648
+ value: segment.slice(eqIndex + 1).trim()
3649
+ };
3650
+ }).filter((entry) => Boolean(entry?.key));
3651
+ if (entries.length === 0) continue;
3652
+ const identifier = entries.find((entry) => ["code", "id", "project", "name", "key"].includes(entry.key.toLowerCase()))?.value;
3653
+ const keyPrefix = [normalizeFactKeySegment(label), identifier ? normalizeFactKeySegment(identifier) : void 0].filter(Boolean).join(".");
3654
+ for (const entry of entries) {
3655
+ const normalizedEntryKey = normalizeFactKeySegment(entry.key);
3656
+ const factKey = keyPrefix ? `${keyPrefix}.${normalizedEntryKey}` : `${normalizeFactKeySegment(label)}.${normalizedEntryKey}`;
3657
+ facts.set(factKey, {
3658
+ key: factKey,
3659
+ value: entry.value,
3660
+ confidence: 0.98,
3661
+ source: "canonical_tool_output"
3662
+ });
3663
+ }
3664
+ }
3665
+ }
3666
+ return [...facts.values()];
3667
+ }
3668
+ function mergeStableFacts(summary, additionalFacts) {
3669
+ if (additionalFacts.length === 0) return summary;
3670
+ const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
3671
+ for (const fact of additionalFacts) {
3672
+ const existing = merged.get(fact.key);
3673
+ if (!existing || (fact.confidence || 0) >= (existing.confidence || 0)) {
3674
+ merged.set(fact.key, fact);
3675
+ }
2670
3676
  }
2671
3677
  return {
2672
- enabled: opts.summarization !== false,
2673
- maxTokens: 5e4,
2674
- summaryPromptMaxTokens: 8e3,
2675
- promptTemplate: void 0
3678
+ ...summary,
3679
+ stable_facts: [...merged.values()]
2676
3680
  };
2677
3681
  }
3682
+ function runIntegrityCheck(previous, current) {
3683
+ const notes = [];
3684
+ const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
3685
+ const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
3686
+ const obsoleteKeys = new Set(current.discarded_obsolete);
3687
+ const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key));
3688
+ const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
3689
+ if (criticalFactLoss) {
3690
+ notes.push("Missing previously retained facts; merged forward during integrity repair.");
3691
+ }
3692
+ if (obsoleteFactRevived) {
3693
+ notes.push("Obsolete facts reappeared; removed during integrity repair.");
3694
+ }
3695
+ return {
3696
+ passed: !criticalFactLoss && !obsoleteFactRevived,
3697
+ criticalFactLoss,
3698
+ obsoleteFactRevived,
3699
+ notes
3700
+ };
3701
+ }
3702
+ function repairStructuredSummary(previous, current, integrity) {
3703
+ if (!previous) return current;
3704
+ const nextFacts = [...current.stable_facts];
3705
+ if (integrity.criticalFactLoss) {
3706
+ const existingKeys = new Set(nextFacts.map((fact) => fact.key));
3707
+ for (const fact of previous.stable_facts) {
3708
+ if (!existingKeys.has(fact.key) && !current.discarded_obsolete.includes(fact.key)) {
3709
+ nextFacts.push(fact);
3710
+ }
3711
+ }
3712
+ }
3713
+ const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
3714
+ return { ...current, stable_facts: filteredFacts };
3715
+ }
2678
3716
  function createContextSummarizeNode(opts) {
2679
3717
  return async (state) => {
2680
- const config = getSummarizationConfig(opts);
2681
- if (!config.enabled) return {};
3718
+ const resolved = getResolvedSmartConfig(opts, state.agent);
3719
+ const config = resolved.summarization;
3720
+ if (!config.enable) return {};
2682
3721
  const model = opts.model;
2683
3722
  const messages = state.messages || [];
2684
3723
  const onEvent = state.ctx?.__onEvent;
2685
3724
  const traceSession = state.ctx?.__traceSession;
2686
- const compressableMessages = messages.filter((m) => m.role === "tool" && m.content !== "SUMMARIZED");
3725
+ const compressableMessages = messages.filter(
3726
+ (m) => m.role === "tool" && m.content !== "SUMMARIZED" && !isSyntheticSummaryToolMessage(m)
3727
+ );
2687
3728
  if (compressableMessages.length === 0) {
2688
3729
  return {};
2689
3730
  }
@@ -2695,6 +3736,9 @@ function createContextSummarizeNode(opts) {
2695
3736
  let accumulatedTokens = 0;
2696
3737
  for (let i = messages.length - 1; i >= 0; i--) {
2697
3738
  const m = messages[i];
3739
+ if (isSyntheticSummaryToolMessage(m) || isSyntheticSummaryAssistantMessage(m)) {
3740
+ continue;
3741
+ }
2698
3742
  let content = "";
2699
3743
  if (typeof m.content === "string") content = m.content;
2700
3744
  else if (Array.isArray(m.content)) {
@@ -2706,39 +3750,60 @@ function createContextSummarizeNode(opts) {
2706
3750
  }
2707
3751
  let prefix = `${m.role.toUpperCase()}`;
2708
3752
  if (m.name) prefix += ` (${m.name})`;
2709
- const messageText = `${prefix}: ${content}`;
2710
- const messageTokens = countApproxTokens(messageText);
3753
+ const messageText2 = `${prefix}: ${content}`;
3754
+ const messageTokens = countApproxTokens(messageText2);
2711
3755
  if (accumulatedTokens + messageTokens > availableTokensForConversation) {
2712
3756
  messageTexts.unshift("[Earlier conversation history omitted for brevity]");
2713
3757
  break;
2714
3758
  }
2715
- messageTexts.unshift(messageText);
3759
+ messageTexts.unshift(messageText2);
2716
3760
  accumulatedTokens += messageTokens;
2717
3761
  }
2718
3762
  const conversationText = messageTexts.join("\n\n");
3763
+ const canonicalFacts = extractCanonicalFacts(compressableMessages);
2719
3764
  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.
3765
+ const previousStructuredSummary = Array.isArray(state.summaryRecords) && state.summaryRecords.length > 0 ? state.summaryRecords[state.summaryRecords.length - 1] : void 0;
3766
+ const defaultPrompt = `Summarize the conversation into strict JSON.
3767
+
3768
+ Return exactly one JSON object with this schema:
3769
+ {
3770
+ "stable_facts": [{ "key": string, "value": string, "confidence": number }],
3771
+ "active_goals": [string],
3772
+ "open_questions": [string],
3773
+ "discarded_obsolete": [string],
3774
+ "rawSummary": string
3775
+ }
3776
+
3777
+ Rules:
3778
+ - Keep stable_facts only for facts that future turns must remember.
3779
+ - Put invalidated or superseded fact keys in discarded_obsolete.
3780
+ - Preserve active_goals still relevant to the user.
3781
+ - Preserve unresolved questions.
3782
+ - Do not include prose outside JSON.
3783
+ - Target compression ratio: ${config.summaryCompressionRatioTarget}.
3784
+ - Summary mode: ${config.summaryMode}.
2726
3785
 
2727
- Previous summary (if any):
3786
+ Previous summary text:
2728
3787
  ${previousSummary || "(none)"}
2729
3788
 
3789
+ Previous structured summary:
3790
+ ${previousStructuredSummary ? JSON.stringify(previousStructuredSummary) : "(none)"}
3791
+
2730
3792
  Conversation:
2731
3793
  ${conversationText}
2732
3794
 
2733
- Summary:`;
3795
+ Canonical facts extracted from tool outputs:
3796
+ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fact.value}`).join("\n") : "(none)"}`;
2734
3797
  const template = config.promptTemplate || defaultPrompt;
2735
3798
  const promptBody = template.replace(/\{\{\s*conversation\s*\}\}/g, conversationText).replace(/\{\{\s*previousSummary\s*\}\}/g, previousSummary || "");
2736
3799
  const summaryPrompt = [
2737
- systemMessage("You are a helpful assistant that summarizes conversation history efficiently."),
3800
+ systemMessage("You are a helpful assistant that summarizes conversation history efficiently. Return strict JSON only."),
2738
3801
  humanMessage(promptBody)
2739
3802
  ];
2740
3803
  const inputTokensEstimate = countApproxTokens(summaryPrompt.map((m) => m.content).join("\n"));
2741
3804
  let summaryText = "Summary unavailable.";
3805
+ let structuredSummary;
3806
+ let integrity;
2742
3807
  let outputTokensEstimate = 0;
2743
3808
  let durationMs = 0;
2744
3809
  let inputTokensActual;
@@ -2747,10 +3812,23 @@ Summary:`;
2747
3812
  let totalTokensActual;
2748
3813
  const startTime = Date.now();
2749
3814
  try {
3815
+ if (!model || typeof model.invoke !== "function") {
3816
+ throw new Error("Summarization model is unavailable.");
3817
+ }
2750
3818
  const response = await model.invoke(summaryPrompt);
2751
3819
  durationMs = Date.now() - startTime;
2752
3820
  summaryText = typeof response.content === "string" ? response.content : Array.isArray(response.content) ? response.content.map((c) => c.text || "").join("") : JSON.stringify(response.content);
2753
3821
  outputTokensEstimate = countApproxTokens(summaryText);
3822
+ const jsonText = extractJsonObject(summaryText);
3823
+ const parsed = jsonText ? JSON.parse(jsonText) : void 0;
3824
+ structuredSummary = normalizeStructuredSummary(parsed, summaryText);
3825
+ structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
3826
+ integrity = config.integrityCheck ? runIntegrityCheck(previousStructuredSummary, structuredSummary) : { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: [] };
3827
+ if (integrity && !integrity.passed) {
3828
+ structuredSummary = repairStructuredSummary(previousStructuredSummary, structuredSummary, integrity);
3829
+ integrity = { ...integrity, passed: true };
3830
+ }
3831
+ summaryText = renderStructuredSummary(structuredSummary);
2754
3832
  const rawUsage = response?.usage || response?.response_metadata?.token_usage || response?.response_metadata?.tokenUsage || response?.response_metadata?.usage;
2755
3833
  const normalized = normalizeUsage(rawUsage);
2756
3834
  if (normalized) {
@@ -2761,17 +3839,22 @@ Summary:`;
2761
3839
  }
2762
3840
  } catch (err) {
2763
3841
  durationMs = Date.now() - startTime;
2764
- console.error("[ContextSummarize] Failed to generate summary:", err);
3842
+ structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
3843
+ structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
3844
+ integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
3845
+ summaryText = renderStructuredSummary(structuredSummary);
2765
3846
  const errorEvent = {
2766
3847
  type: "summarization",
2767
- summary: "",
2768
- messagesCompressed: 0,
3848
+ summary: summaryText,
3849
+ messagesCompressed: compressableMessages.length,
2769
3850
  inputTokens: inputTokensEstimate,
2770
- outputTokens: 0,
3851
+ outputTokens: countApproxTokens(summaryText),
2771
3852
  durationMs,
2772
3853
  previousSummary: previousSummary || void 0,
2773
3854
  tokenCountBefore,
2774
- tokenCountAfter: tokenCountBefore
3855
+ tokenCountAfter: tokenCountBefore,
3856
+ structuredSummary,
3857
+ integrity
2775
3858
  };
2776
3859
  onEvent?.(errorEvent);
2777
3860
  recordTraceEvent(traceSession, {
@@ -2781,11 +3864,10 @@ Summary:`;
2781
3864
  status: "error",
2782
3865
  durationMs,
2783
3866
  inputTokens: inputTokensEstimate,
2784
- outputTokens: 0,
3867
+ outputTokens: countApproxTokens(summaryText),
2785
3868
  error: { message: err?.message || String(err), stack: err?.stack },
2786
3869
  messageList: sanitizeTracePayload(summaryPrompt)
2787
3870
  });
2788
- return {};
2789
3871
  }
2790
3872
  const toolCallIdToAssistantIdx = /* @__PURE__ */ new Map();
2791
3873
  messages.forEach((m, idx) => {
@@ -2799,6 +3881,9 @@ Summary:`;
2799
3881
  });
2800
3882
  const newMessages = messages.map((m) => {
2801
3883
  if (m.role === "tool") {
3884
+ if (isSyntheticSummaryToolMessage(m)) {
3885
+ return m;
3886
+ }
2802
3887
  const toolCallId = m.tool_call_id;
2803
3888
  if (!toolCallId || !toolCallIdToAssistantIdx.has(toolCallId)) {
2804
3889
  return null;
@@ -2836,6 +3921,7 @@ Summary:`;
2836
3921
  content: summaryText
2837
3922
  };
2838
3923
  const summaries = Array.isArray(state.summaries) ? [...state.summaries, summaryText] : [summaryText];
3924
+ 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
3925
  const finalMessages = [...validatedMessages, assistantSummaryCall, toolSummaryResponse];
2840
3926
  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
3927
  const tokenCountAfter = countApproxTokens(allTextAfter);
@@ -2851,8 +3937,10 @@ Summary:`;
2851
3937
  previousSummary: previousSummary || void 0,
2852
3938
  tokenCountBefore,
2853
3939
  tokenCountAfter,
2854
- archivedCount: compressableMessages.length
3940
+ archivedCount: compressableMessages.length,
2855
3941
  // deprecated field for backward compatibility
3942
+ structuredSummary,
3943
+ integrity
2856
3944
  };
2857
3945
  onEvent?.(summarizationEvent);
2858
3946
  recordTraceEvent(traceSession, {
@@ -2876,12 +3964,19 @@ Summary:`;
2876
3964
  tokensSaved: tokenCountBefore - tokenCountAfter,
2877
3965
  previousSummaryLength: previousSummary?.length || 0,
2878
3966
  usageFromModel: inputTokensActual !== void 0,
2879
- cachedInputTokens
3967
+ cachedInputTokens,
3968
+ integrity
2880
3969
  }
2881
3970
  });
2882
3971
  return {
2883
3972
  messages: finalMessages,
2884
- summaries
3973
+ summaries,
3974
+ summaryRecords,
3975
+ watchdog: {
3976
+ ...state.watchdog || {},
3977
+ compactions: (state.watchdog?.compactions || 0) + 1,
3978
+ lastAction: "summarized"
3979
+ }
2885
3980
  };
2886
3981
  };
2887
3982
  }
@@ -2904,38 +3999,200 @@ async function applyTokenLimits({
2904
3999
  }
2905
4000
  return { ...state, messages: newMessages };
2906
4001
  }
4002
+ var todoStatusSchema = z.enum(["not-started", "in-progress", "completed", "blocked"]);
4003
+ var todoWriteItemSchema = z.object({
4004
+ id: z.number().int().min(1).describe("Sequential id starting from 1"),
4005
+ step: z.string().min(1).optional(),
4006
+ title: z.string().min(1).optional(),
4007
+ description: z.string().min(1).optional(),
4008
+ owner: z.string().min(1).optional(),
4009
+ exitCriteria: z.string().min(1).optional(),
4010
+ status: todoStatusSchema,
4011
+ evidence: z.string().max(300).optional()
4012
+ });
4013
+ var todoUpdateItemSchema = z.object({
4014
+ id: z.number().int().min(1).describe("Existing todo id to update"),
4015
+ step: z.string().min(1).optional(),
4016
+ title: z.string().min(1).optional(),
4017
+ description: z.string().min(1).optional(),
4018
+ owner: z.string().min(1).optional(),
4019
+ exitCriteria: z.string().min(1).optional(),
4020
+ status: todoStatusSchema.optional(),
4021
+ evidence: z.string().max(300).optional()
4022
+ });
4023
+ function normalizeTodoItem(item, existing) {
4024
+ const id = item.id ?? existing?.id;
4025
+ const step = item.step || item.title || item.description || existing?.step || existing?.title || existing?.description || `Step ${id}`;
4026
+ const title = item.title || item.step || existing?.title || existing?.step || `Step ${id}`;
4027
+ const description = item.description || item.step || item.title || existing?.description || existing?.step || existing?.title || `Step ${id}`;
4028
+ return {
4029
+ ...existing,
4030
+ ...item,
4031
+ id,
4032
+ step,
4033
+ title,
4034
+ description,
4035
+ owner: item.owner || existing?.owner || "agent",
4036
+ exitCriteria: item.exitCriteria || existing?.exitCriteria || description || `Complete step ${id}`,
4037
+ status: item.status || existing?.status,
4038
+ evidence: item.evidence ?? existing?.evidence
4039
+ };
4040
+ }
4041
+ function validatePlanInvariants(todoList, requireSequentialIds) {
4042
+ const ids = todoList.map((item) => item.id);
4043
+ const uniqueIds = new Set(ids);
4044
+ if (uniqueIds.size !== ids.length) {
4045
+ return { ok: false, error: "Todo ids must be unique." };
4046
+ }
4047
+ if (requireSequentialIds) {
4048
+ const expectedIds = Array.from({ length: todoList.length }, (_, index) => index + 1);
4049
+ const isSequential = ids.every((id, index) => id === expectedIds[index]);
4050
+ if (!isSequential) {
4051
+ return { ok: false, error: "Todo ids must be sequential starting from 1 for write operations." };
4052
+ }
4053
+ }
4054
+ const inProgressCount = todoList.filter((item) => item.status === "in-progress").length;
4055
+ if (inProgressCount > 1) {
4056
+ return { ok: false, error: "Only one todo item may be in-progress at a time." };
4057
+ }
4058
+ return { ok: true };
4059
+ }
4060
+ function calculateAdherenceScore(todoList) {
4061
+ const completedCount = todoList.filter((item) => item.status === "completed").length;
4062
+ return todoList.length === 0 ? 1 : Number((completedCount / todoList.length).toFixed(2));
4063
+ }
2907
4064
  function createContextTools(stateRef, opts) {
2908
4065
  const tools = [];
2909
4066
  if (opts?.planningEnabled) {
2910
4067
  const manageTodo = createTool({
2911
4068
  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.",
4069
+ 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
4070
  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()
4071
+ operation: z.enum(["write", "read", "update"]),
4072
+ expectedVersion: z.number().int().min(0).optional(),
4073
+ todoList: z.array(z.union([todoWriteItemSchema, todoUpdateItemSchema])).optional()
2922
4074
  }),
2923
- func: async ({ operation, todoList }) => {
4075
+ func: async ({ operation, expectedVersion, todoList }) => {
2924
4076
  const onEvent = manageTodo._stateRef?.__onEvent;
4077
+ const currentVersion = stateRef.planVersion || 0;
2925
4078
  if (operation === "read") {
2926
4079
  const list = stateRef.todoList || [];
2927
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list });
4080
+ onEvent?.({ type: "plan", source: "manage_todo_list", operation: "read", todoList: list, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
2928
4081
  return list;
2929
4082
  }
2930
- if (Array.isArray(todoList)) {
2931
- stateRef.todoList = todoList;
4083
+ if (typeof expectedVersion === "number" && expectedVersion !== currentVersion) {
4084
+ return {
4085
+ status: "error",
4086
+ operation,
4087
+ error: "Plan version mismatch. Read the latest plan and retry.",
4088
+ version: currentVersion,
4089
+ adherenceScore: stateRef.adherenceScore || 0
4090
+ };
4091
+ }
4092
+ if (!Array.isArray(todoList)) {
4093
+ return {
4094
+ status: "error",
4095
+ operation,
4096
+ error: "todoList is required for write and update operations.",
4097
+ version: currentVersion,
4098
+ adherenceScore: stateRef.adherenceScore || 0
4099
+ };
2932
4100
  }
4101
+ if (operation === "write") {
4102
+ const parsed = z.array(todoWriteItemSchema).safeParse(todoList);
4103
+ if (!parsed.success) {
4104
+ return {
4105
+ status: "error",
4106
+ operation,
4107
+ error: parsed.error.issues[0]?.message || "Invalid todoList for write.",
4108
+ version: currentVersion,
4109
+ adherenceScore: stateRef.adherenceScore || 0
4110
+ };
4111
+ }
4112
+ const normalizedList = parsed.data.map((item) => normalizeTodoItem(item));
4113
+ const validation = validatePlanInvariants(normalizedList, true);
4114
+ if (!validation.ok) {
4115
+ return {
4116
+ status: "error",
4117
+ operation,
4118
+ error: validation.error,
4119
+ version: currentVersion,
4120
+ adherenceScore: stateRef.adherenceScore || 0
4121
+ };
4122
+ }
4123
+ stateRef.todoList = normalizedList;
4124
+ } else if (operation === "update") {
4125
+ const parsed = z.array(todoUpdateItemSchema).safeParse(todoList);
4126
+ if (!parsed.success) {
4127
+ return {
4128
+ status: "error",
4129
+ operation,
4130
+ error: parsed.error.issues[0]?.message || "Invalid todoList for update.",
4131
+ version: currentVersion,
4132
+ adherenceScore: stateRef.adherenceScore || 0
4133
+ };
4134
+ }
4135
+ const currentList = Array.isArray(stateRef.todoList) ? stateRef.todoList : [];
4136
+ if (currentList.length === 0) {
4137
+ return {
4138
+ status: "error",
4139
+ operation,
4140
+ error: "No existing plan to update. Use write first.",
4141
+ version: currentVersion,
4142
+ adherenceScore: stateRef.adherenceScore || 0
4143
+ };
4144
+ }
4145
+ const patchIds = /* @__PURE__ */ new Set();
4146
+ for (const item of parsed.data) {
4147
+ if (patchIds.has(item.id)) {
4148
+ return {
4149
+ status: "error",
4150
+ operation,
4151
+ error: "Update payload contains duplicate todo ids.",
4152
+ version: currentVersion,
4153
+ adherenceScore: stateRef.adherenceScore || 0
4154
+ };
4155
+ }
4156
+ patchIds.add(item.id);
4157
+ }
4158
+ const currentMap = new Map(currentList.map((item) => [item.id, item]));
4159
+ for (const item of parsed.data) {
4160
+ if (!currentMap.has(item.id)) {
4161
+ return {
4162
+ status: "error",
4163
+ operation,
4164
+ error: `Cannot update missing todo id ${item.id}. Use write to replace the plan.`,
4165
+ version: currentVersion,
4166
+ adherenceScore: stateRef.adherenceScore || 0
4167
+ };
4168
+ }
4169
+ }
4170
+ const mergedList = currentList.map((item) => {
4171
+ const patch = parsed.data.find((candidate) => candidate.id === item.id);
4172
+ return patch ? normalizeTodoItem(patch, item) : item;
4173
+ });
4174
+ const validation = validatePlanInvariants(mergedList, false);
4175
+ if (!validation.ok) {
4176
+ return {
4177
+ status: "error",
4178
+ operation,
4179
+ error: validation.error,
4180
+ version: currentVersion,
4181
+ adherenceScore: stateRef.adherenceScore || 0
4182
+ };
4183
+ }
4184
+ stateRef.todoList = mergedList;
4185
+ }
4186
+ stateRef.planVersion = currentVersion + 1;
4187
+ stateRef.adherenceScore = calculateAdherenceScore(stateRef.todoList || []);
2933
4188
  const payload = {
2934
4189
  status: "ok",
2935
4190
  operation,
2936
- count: Array.isArray(todoList) ? todoList.length : void 0
4191
+ count: Array.isArray(todoList) ? todoList.length : void 0,
4192
+ version: stateRef.planVersion || 1,
4193
+ adherenceScore: stateRef.adherenceScore || 0
2937
4194
  };
2938
- onEvent?.({ type: "plan", source: "manage_todo_list", operation: "write", todoList });
4195
+ onEvent?.({ type: "plan", source: "manage_todo_list", operation, todoList: stateRef.todoList, version: stateRef.planVersion || 1, adherenceScore: stateRef.adherenceScore || 0 });
2939
4196
  return payload;
2940
4197
  }
2941
4198
  });
@@ -2944,12 +4201,13 @@ function createContextTools(stateRef, opts) {
2944
4201
  }
2945
4202
  const getTool = createTool({
2946
4203
  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.",
4204
+ 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
4205
  schema: z.object({ executionId: z.string().describe("Tool execution id") }),
2949
4206
  func: async ({ executionId }) => {
2950
- let execution = stateRef.toolHistory?.find((t) => t.executionId === executionId);
4207
+ const matchesExecution = (t) => t?.executionId === executionId || t?.tool_call_id === executionId;
4208
+ let execution = stateRef.toolHistory?.find((t) => matchesExecution(t));
2951
4209
  if (!execution) {
2952
- execution = stateRef.toolHistoryArchived?.find((t) => t.executionId === executionId);
4210
+ execution = stateRef.toolHistoryArchived?.find((t) => matchesExecution(t));
2953
4211
  }
2954
4212
  if (execution) {
2955
4213
  return execution.rawOutput || execution.output;
@@ -2980,19 +4238,33 @@ function createContextTools(stateRef, opts) {
2980
4238
  }
2981
4239
 
2982
4240
  // src/graph/decisions.ts
4241
+ function isSyntheticSummaryMessage2(message) {
4242
+ if (!message) return false;
4243
+ if (message.role === "tool" && message.name === "summarize_context") {
4244
+ return true;
4245
+ }
4246
+ if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
4247
+ return message.tool_calls.some((toolCall) => {
4248
+ const toolName = toolCall?.function?.name || toolCall?.name;
4249
+ return toolName === "summarize_context";
4250
+ });
4251
+ }
4252
+ return false;
4253
+ }
2983
4254
  function needsSummarization(state, opts, summarizationEnabled) {
2984
4255
  if (!summarizationEnabled) {
2985
4256
  return false;
2986
4257
  }
2987
- let maxTok;
2988
- if (typeof opts.summarization === "object" && typeof opts.summarization.maxTokens === "number") {
2989
- maxTok = opts.summarization.maxTokens;
4258
+ if (state.ctx?.__needsSummarization) {
4259
+ return true;
2990
4260
  }
2991
- if (maxTok === void 0) {
2992
- maxTok = 5e4;
4261
+ const resolved = getResolvedSmartConfig(opts, state.agent);
4262
+ if (!resolved.summarization.enable) {
4263
+ return false;
2993
4264
  }
4265
+ const maxTok = resolved.summarization.summaryTriggerTokens;
2994
4266
  try {
2995
- const allText = (state.messages || []).map((m) => {
4267
+ const allText = (state.messages || []).filter((message) => !isSyntheticSummaryMessage2(message)).map((m) => {
2996
4268
  if (typeof m.content === "string") return m.content;
2997
4269
  if (Array.isArray(m.content)) {
2998
4270
  return m.content.map((c) => typeof c === "string" ? c : c?.text ?? c?.content ?? JSON.stringify(c)).join("");
@@ -3003,7 +4275,8 @@ function needsSummarization(state, opts, summarizationEnabled) {
3003
4275
  return "";
3004
4276
  }).join("\n");
3005
4277
  const tokenCount = countApproxTokens(allText);
3006
- const needsSum = tokenCount > maxTok;
4278
+ const contextRotScore = state.watchdog?.contextRotScore || 0;
4279
+ const needsSum = tokenCount > maxTok || resolved.watchdog.enabled && resolved.watchdog.autoCompaction && contextRotScore >= resolved.watchdog.contextRotThreshold;
3007
4280
  if (process.env.DEBUG_SUMMARIZATION) {
3008
4281
  const msgCount = (state.messages || []).length;
3009
4282
  const toolMsgs = (state.messages || []).filter((m) => m.role === "tool");
@@ -3013,10 +4286,10 @@ function needsSummarization(state, opts, summarizationEnabled) {
3013
4286
  });
3014
4287
  console.log(`[Summarization] Messages: ${msgCount}, Tool messages: ${toolMsgs.length}`);
3015
4288
  toolDetails.forEach((d) => console.log(` ${d}`));
3016
- console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Needs summarization: ${needsSum}`);
4289
+ console.log(`[Summarization] Token count: ${tokenCount}, Max: ${maxTok}, Context rot: ${contextRotScore}, Needs summarization: ${needsSum}`);
3017
4290
  }
3018
4291
  return needsSum;
3019
- } catch (e) {
4292
+ } catch {
3020
4293
  return false;
3021
4294
  }
3022
4295
  }
@@ -3034,33 +4307,205 @@ function toolsDecisionFactory(opts, summarizationEnabled) {
3034
4307
  };
3035
4308
  }
3036
4309
 
4310
+ // src/smart/memory.ts
4311
+ function normalizeFact(scope, fact) {
4312
+ return {
4313
+ ...fact,
4314
+ scope,
4315
+ obsolete: fact.obsolete ?? false,
4316
+ lastUpdatedAt: fact.lastUpdatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
4317
+ confidence: Number.isFinite(fact.confidence) ? fact.confidence : 0.5
4318
+ };
4319
+ }
4320
+ var InMemoryMemoryStore = class {
4321
+ constructor() {
4322
+ this.buckets = /* @__PURE__ */ new Map();
4323
+ }
4324
+ async get(scope, options) {
4325
+ const includeObsolete = options?.includeObsolete === true;
4326
+ 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());
4327
+ return typeof options?.limit === "number" ? all.slice(0, options.limit) : all;
4328
+ }
4329
+ async upsert(scope, facts) {
4330
+ const existing = [...this.buckets.get(scope) || []];
4331
+ for (const fact of facts) {
4332
+ const normalized = normalizeFact(scope, fact);
4333
+ const sameKey = existing.filter((entry) => entry.key === normalized.key && !entry.obsolete);
4334
+ for (const entry of sameKey) {
4335
+ if (entry.value !== normalized.value) {
4336
+ entry.obsolete = true;
4337
+ entry.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
4338
+ }
4339
+ }
4340
+ const match = existing.find((entry) => entry.key === normalized.key && entry.value === normalized.value && !entry.obsolete);
4341
+ if (match) {
4342
+ match.confidence = Math.max(match.confidence, normalized.confidence);
4343
+ match.sourceTurn = normalized.sourceTurn;
4344
+ match.ttl = normalized.ttl;
4345
+ match.tags = normalized.tags;
4346
+ match.lastUpdatedAt = normalized.lastUpdatedAt;
4347
+ } else {
4348
+ existing.push(normalized);
4349
+ }
4350
+ }
4351
+ this.buckets.set(scope, existing);
4352
+ }
4353
+ async markObsolete(scope, keys) {
4354
+ const bucket = this.buckets.get(scope) || [];
4355
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4356
+ for (const fact of bucket) {
4357
+ if (keys.includes(fact.key)) {
4358
+ fact.obsolete = true;
4359
+ fact.lastUpdatedAt = now;
4360
+ }
4361
+ }
4362
+ this.buckets.set(scope, bucket);
4363
+ }
4364
+ async semanticSearch(scope, query, options) {
4365
+ const normalizedQuery = query.toLowerCase();
4366
+ const bucket = (this.buckets.get(scope) || []).filter((fact) => !fact.obsolete);
4367
+ const scored = bucket.map((fact) => {
4368
+ const haystack = `${fact.key} ${fact.value} ${(fact.tags || []).join(" ")}`.toLowerCase();
4369
+ const score = haystack.includes(normalizedQuery) ? 2 : normalizedQuery.split(/\s+/).filter(Boolean).reduce((acc, token) => acc + (haystack.includes(token) ? 1 : 0), 0);
4370
+ return { fact, score };
4371
+ }).filter((entry) => entry.score > 0).sort((left, right) => right.score - left.score || right.fact.confidence - left.fact.confidence).map((entry) => entry.fact);
4372
+ return typeof options?.limit === "number" ? scored.slice(0, options.limit) : scored;
4373
+ }
4374
+ };
4375
+ function resolveMemoryStore(config) {
4376
+ if (config.memory.store) return config.memory.store;
4377
+ if (config.memory.provider === "inMemory") return new InMemoryMemoryStore();
4378
+ return void 0;
4379
+ }
4380
+ async function readMemoryFacts(store, config, query) {
4381
+ if (!store) return [];
4382
+ const scope = config.memory.scope;
4383
+ const limit = config.context.lastTurnsToKeep;
4384
+ const policy = config.memory.readPolicy;
4385
+ if (policy === "recent_only") {
4386
+ return store.get(scope, { limit });
4387
+ }
4388
+ if (policy === "semantic" && typeof store.semanticSearch === "function") {
4389
+ return store.semanticSearch(scope, query, { limit });
4390
+ }
4391
+ if (policy === "hybrid") {
4392
+ const recent = await store.get(scope, { limit: Math.max(3, Math.ceil(limit / 2)) });
4393
+ const semantic = typeof store.semanticSearch === "function" ? await store.semanticSearch(scope, query, { limit: Math.max(3, Math.ceil(limit / 2)) }) : [];
4394
+ const deduped = /* @__PURE__ */ new Map();
4395
+ for (const fact of [...semantic, ...recent]) {
4396
+ deduped.set(`${fact.key}:${fact.value}`, fact);
4397
+ }
4398
+ return [...deduped.values()].slice(0, limit);
4399
+ }
4400
+ return store.get(scope, { limit });
4401
+ }
4402
+ async function writeSummaryFactsToMemory(store, config, summary, sourceTurn) {
4403
+ if (!store || !summary) return [];
4404
+ const facts = summary.stable_facts.map((fact) => ({
4405
+ key: fact.key,
4406
+ value: fact.value,
4407
+ sourceTurn,
4408
+ confidence: fact.confidence ?? 0.8,
4409
+ obsolete: false,
4410
+ tags: ["summary_fact"]
4411
+ }));
4412
+ if (summary.discarded_obsolete.length > 0) {
4413
+ await store.markObsolete(config.memory.scope, summary.discarded_obsolete);
4414
+ }
4415
+ if (facts.length > 0) {
4416
+ await store.upsert(config.memory.scope, facts);
4417
+ }
4418
+ return store.get(config.memory.scope, { limit: config.context.lastTurnsToKeep + summary.stable_facts.length });
4419
+ }
4420
+ function renderMemoryFacts(facts) {
4421
+ if (facts.length === 0) return "";
4422
+ return [
4423
+ "Memory facts:",
4424
+ ...facts.map((fact) => `- ${fact.key}: ${fact.value} (confidence ${fact.confidence.toFixed(2)})`)
4425
+ ].join("\n");
4426
+ }
4427
+
3037
4428
  // src/smart/index.ts
3038
4429
  function createSmartAgent(opts) {
3039
- const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0 };
3040
- const planningEnabled = opts.useTodoList === true;
4430
+ const resolved = normalizeSmartAgentOptions(opts);
4431
+ const planningEnabled = resolved.planning.mode !== "off";
4432
+ const memoryStore = resolveMemoryStore(resolved);
4433
+ const runtimeOpts = {
4434
+ ...opts,
4435
+ runtimeProfile: resolved.runtimeProfile,
4436
+ limits: resolved.limits,
4437
+ summarization: resolved.summarization,
4438
+ context: resolved.context,
4439
+ planning: resolved.planning,
4440
+ delegation: resolved.delegation,
4441
+ memory: { ...resolved.memory, store: memoryStore },
4442
+ toolResponses: resolved.toolResponses,
4443
+ watchdog: resolved.watchdog,
4444
+ useTodoList: planningEnabled
4445
+ };
4446
+ const stateRef = { toolHistory: void 0, toolHistoryArchived: void 0, todoList: void 0, planVersion: 0, adherenceScore: 0 };
3041
4447
  const contextTools = createContextTools(stateRef, { planningEnabled, outputSchema: void 0 });
3042
4448
  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);
4449
+ const base = createAgent({ ...runtimeOpts, tools: mergedTools });
4450
+ base.__runtime.runtimeProfile = resolved.runtimeProfile;
4451
+ base.__runtime.smart = resolved;
4452
+ const summarizationEnabled = resolved.summarization.enable !== false;
4453
+ const summarizer = summarizationEnabled ? createContextSummarizeNode(runtimeOpts) : void 0;
4454
+ const decideBefore = resolverDecisionFactory(runtimeOpts, summarizationEnabled);
4455
+ const decideAfter = toolsDecisionFactory(runtimeOpts, summarizationEnabled);
3053
4456
  const structuredOutputHint = opts.outputSchema ? [
3054
4457
  "A structured output schema is active.",
3055
4458
  "Do NOT output the final JSON directly as an assistant message.",
3056
4459
  "When completely finished, call tool `response` passing the final JSON matching the schema as its arguments (direct object).",
3057
4460
  "Call it exactly once then STOP producing further assistant messages."
3058
4461
  ].join("\n") : "";
4462
+ const runtimeHint = [
4463
+ resolved.runtimeProfile === resolved.baseProfile ? `Runtime profile: ${resolved.runtimeProfile}.` : `Runtime profile: ${resolved.runtimeProfile} (base: ${resolved.baseProfile}).`,
4464
+ `Context policy: ${resolved.context.policy}.`,
4465
+ `Planning mode: ${resolved.planning.mode}. Replan policy: ${resolved.planning.replanPolicy}.`,
4466
+ `Delegation mode: ${resolved.delegation.mode}. Child context policy: ${resolved.delegation.childContextPolicy}.`,
4467
+ `Tool response policy: ${resolved.context.toolResponsePolicy}.`
4468
+ ].join("\n");
4469
+ function latestUserPrompt(messages) {
4470
+ const latest = [...messages].reverse().find((message) => message.role === "user");
4471
+ if (!latest) return "";
4472
+ return typeof latest.content === "string" ? latest.content : Array.isArray(latest.content) ? latest.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? "").join(" ") : "";
4473
+ }
4474
+ async function syncMemory(state) {
4475
+ const query = latestUserPrompt(state.messages || []);
4476
+ const memoryFacts = await readMemoryFacts(memoryStore, resolved, query);
4477
+ return { ...state, memoryFacts };
4478
+ }
4479
+ async function persistLatestSummary(state) {
4480
+ if (!memoryStore) return state;
4481
+ if (resolved.memory.writePolicy === "manual") return state;
4482
+ const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
4483
+ if (!latestSummary) return state;
4484
+ if (resolved.memory.writePolicy === "auto_important" && latestSummary.stable_facts.length === 0) {
4485
+ return state;
4486
+ }
4487
+ const memoryFacts = await writeSummaryFactsToMemory(memoryStore, resolved, latestSummary, Math.max(1, (state.messages || []).filter((message) => message.role === "user").length));
4488
+ return { ...state, memoryFacts };
4489
+ }
4490
+ function syncPlanState(state) {
4491
+ if (!Array.isArray(stateRef.todoList)) return state;
4492
+ return {
4493
+ ...state,
4494
+ plan: {
4495
+ version: stateRef.planVersion || state.planVersion || 1,
4496
+ steps: stateRef.todoList,
4497
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
4498
+ adherenceScore: stateRef.adherenceScore || 0
4499
+ },
4500
+ planVersion: stateRef.planVersion || state.planVersion || 1
4501
+ };
4502
+ }
3059
4503
  function systemMessage2() {
3060
4504
  const sys = buildSystemPrompt(
3061
- [opts.systemPrompt, structuredOutputHint].filter(Boolean).join("\n"),
3062
- opts.useTodoList === true,
3063
- opts.name || "Agent"
4505
+ [opts.systemPrompt, runtimeHint, structuredOutputHint].filter(Boolean).join("\n"),
4506
+ planningEnabled,
4507
+ opts.name || "Agent",
4508
+ opts.todoListPrompt
3064
4509
  );
3065
4510
  return { role: "system", content: sys };
3066
4511
  }
@@ -3068,38 +4513,81 @@ function createSmartAgent(opts) {
3068
4513
  invoke: async (input, config) => {
3069
4514
  stateRef.toolHistory = input.toolHistory;
3070
4515
  stateRef.toolHistoryArchived = input.toolHistoryArchived;
4516
+ stateRef.todoList = input.plan?.steps;
4517
+ stateRef.planVersion = input.planVersion || input.plan?.version || 0;
4518
+ stateRef.adherenceScore = input.plan?.adherenceScore || 0;
3071
4519
  const alreadyHasSystem = Array.isArray(input.messages) && input.messages[0]?.role === "system";
3072
4520
  const seedMessages = alreadyHasSystem ? [...input.messages || []] : [systemMessage2(), ...input.messages || []];
3073
- let state = { ...input, messages: seedMessages };
4521
+ let state = await syncMemory({ ...input, messages: seedMessages });
3074
4522
  let lastResult = null;
3075
- const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? opts.limits?.maxToolCalls ?? 10;
4523
+ let rawMessages = [...seedMessages];
4524
+ const effectiveMaxToolCalls = config?.limits?.maxToolCalls ?? resolved.limits.maxToolCalls ?? 10;
3076
4525
  const iterationLimit = Math.max(effectiveMaxToolCalls * 3 + 5, 30);
4526
+ let previousTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
3077
4527
  for (let i = 0; i < iterationLimit; i++) {
4528
+ state = { ...state, messages: rawMessages };
3078
4529
  const next = summarizationEnabled ? decideBefore(state) : "agent";
3079
4530
  if (next === "contextSummarize" && summarizer) {
3080
4531
  const delta = await summarizer(state);
3081
4532
  if (!delta || Object.keys(delta).length === 0) ; else {
3082
- state = { ...state, ...delta };
4533
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4534
+ rawMessages = [...state.messages || rawMessages];
3083
4535
  continue;
3084
4536
  }
3085
4537
  }
3086
- const res = await base.invoke(state, config);
4538
+ const modelMessages = buildModelMessages(state, resolved);
4539
+ const res = await base.invoke({ ...state, messages: modelMessages }, config);
3087
4540
  lastResult = res;
3088
4541
  const currentSummaries = state.summaries;
4542
+ const currentSummaryRecords = state.summaryRecords;
4543
+ const currentPlan = state.plan;
4544
+ const appendedMessages = res.messages.slice(modelMessages.length);
4545
+ rawMessages = [...rawMessages, ...appendedMessages];
3089
4546
  state = res.state || { ...state, messages: res.messages };
4547
+ state = { ...state, messages: rawMessages };
3090
4548
  if (currentSummaries && currentSummaries.length > 0 && (!state.summaries || state.summaries.length === 0)) {
3091
4549
  state = { ...state, summaries: currentSummaries };
3092
4550
  }
3093
- if (state.ctx?.__awaitingApproval || state.ctx?.__waitingForInput) break;
4551
+ if (currentSummaryRecords && currentSummaryRecords.length > 0 && (!state.summaryRecords || state.summaryRecords.length === 0)) {
4552
+ state = { ...state, summaryRecords: currentSummaryRecords };
4553
+ }
4554
+ if (currentPlan && !state.plan) {
4555
+ state = { ...state, plan: currentPlan };
4556
+ }
4557
+ state = syncPlanState(await syncMemory(state));
4558
+ stateRef.toolHistory = state.toolHistory;
4559
+ stateRef.toolHistoryArchived = state.toolHistoryArchived;
4560
+ const currentTokenCount = countApproxTokens(rawMessages.map((message) => typeof message.content === "string" ? message.content : JSON.stringify(message.content)).join("\n"));
4561
+ const tokenDrift = Math.max(0, currentTokenCount - previousTokenCount);
4562
+ const toolMessagesThisTurn = appendedMessages.filter((message) => message.role === "tool").length;
4563
+ const overToolingRate = appendedMessages.length === 0 ? 0 : Number((toolMessagesThisTurn / appendedMessages.length).toFixed(2));
4564
+ state = {
4565
+ ...state,
4566
+ watchdog: {
4567
+ tokenDrift,
4568
+ contextRotScore: estimateContextRotScore(state),
4569
+ overToolingRate,
4570
+ compactions: state.watchdog?.compactions || 0,
4571
+ lastAction: appendedMessages.length > 0 ? "agent_turn" : state.watchdog?.lastAction
4572
+ }
4573
+ };
4574
+ previousTokenCount = currentTokenCount;
4575
+ if (resolved.watchdog.enabled && resolved.watchdog.autoCompaction) {
4576
+ const shouldCompact = tokenDrift >= resolved.watchdog.tokenDriftThreshold || (state.watchdog?.overToolingRate || 0) >= resolved.watchdog.overToolingSpikeThreshold || (state.watchdog?.contextRotScore || 0) >= resolved.watchdog.contextRotThreshold;
4577
+ if (shouldCompact) {
4578
+ state = { ...state, ctx: { ...state.ctx || {}, __needsSummarization: true } };
4579
+ }
4580
+ }
3094
4581
  if (state.ctx?.__needsSummarization && summarizer) {
3095
4582
  const ctx = { ...state.ctx || {} };
3096
4583
  delete ctx.__needsSummarization;
3097
4584
  state = { ...state, ctx };
3098
4585
  const delta = await summarizer(state);
3099
4586
  if (delta && Object.keys(delta).length > 0) {
3100
- state = { ...state, ...delta };
4587
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4588
+ rawMessages = [...state.messages || rawMessages];
4589
+ continue;
3101
4590
  }
3102
- continue;
3103
4591
  }
3104
4592
  if (state.ctx?.__finalizedDueToStructuredOutput) break;
3105
4593
  if (summarizationEnabled) {
@@ -3107,7 +4595,8 @@ function createSmartAgent(opts) {
3107
4595
  if (after === "contextSummarize" && summarizer) {
3108
4596
  const delta = await summarizer(state);
3109
4597
  if (delta && Object.keys(delta).length > 0) {
3110
- state = { ...state, ...delta };
4598
+ state = await persistLatestSummary(syncPlanState({ ...state, ...delta }));
4599
+ rawMessages = [...state.messages || rawMessages];
3111
4600
  continue;
3112
4601
  }
3113
4602
  }
@@ -3120,10 +4609,12 @@ function createSmartAgent(opts) {
3120
4609
  }
3121
4610
  if (state.summaries && state.summaries.length > 0) {
3122
4611
  if (lastResult.state) {
3123
- lastResult = { ...lastResult, state: { ...lastResult.state, summaries: state.summaries } };
4612
+ 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
4613
  } else {
3125
- lastResult = { ...lastResult, state: { ...state, summaries: state.summaries } };
4614
+ 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
4615
  }
4616
+ } else if (lastResult.state) {
4617
+ lastResult = { ...lastResult, state: { ...lastResult.state, memoryFacts: state.memoryFacts, plan: state.plan, planVersion: state.planVersion, watchdog: state.watchdog, messages: rawMessages } };
3127
4618
  }
3128
4619
  return lastResult;
3129
4620
  },
@@ -3137,6 +4628,141 @@ function createSmartAgent(opts) {
3137
4628
  return instance;
3138
4629
  }
3139
4630
 
4631
+ // src/smart/eval.ts
4632
+ function normalizeProfileTarget(target) {
4633
+ if (typeof target === "string") {
4634
+ return {
4635
+ label: target,
4636
+ runtimeProfile: target,
4637
+ baseProfile: target === "custom" ? "balanced" : target
4638
+ };
4639
+ }
4640
+ return {
4641
+ label: target.label,
4642
+ runtimeProfile: target.runtimeProfile,
4643
+ baseProfile: target.baseProfile ?? (target.runtimeProfile === "custom" ? target.customProfile?.extends ?? "balanced" : target.runtimeProfile),
4644
+ customProfile: target.customProfile
4645
+ };
4646
+ }
4647
+ function normalizeText(value) {
4648
+ return (value || "").toLowerCase();
4649
+ }
4650
+ function ratio(numerator, denominator) {
4651
+ if (denominator <= 0) return 1;
4652
+ return Number((numerator / denominator).toFixed(3));
4653
+ }
4654
+ function sum(values) {
4655
+ return values.reduce((acc, value) => acc + value, 0);
4656
+ }
4657
+ function mean(values) {
4658
+ if (values.length === 0) return 0;
4659
+ return Number((sum(values) / values.length).toFixed(3));
4660
+ }
4661
+ function extractTotalTokens(usage) {
4662
+ if (!usage?.totals || typeof usage.totals !== "object") return 0;
4663
+ return Object.values(usage.totals).reduce((acc, entry) => acc + (Number(entry?.total) || 0), 0);
4664
+ }
4665
+ function caseResultFromRun(descriptor, testCase, content, events, state, latencyMs, totalTokens) {
4666
+ const normalizedContent = normalizeText(content);
4667
+ const matchedPhrases = (testCase.expectedPhrases || []).filter((phrase) => normalizedContent.includes(normalizeText(phrase)));
4668
+ const matchedFacts = (testCase.expectedFacts || []).filter((fact) => {
4669
+ const memoryMatch = state?.memoryFacts?.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value)));
4670
+ const summaryMatch = state?.summaryRecords?.some((record) => record.stable_facts.some((entry) => entry.key === fact.key && normalizeText(entry.value).includes(normalizeText(fact.value))));
4671
+ return Boolean(memoryMatch || summaryMatch || normalizedContent.includes(normalizeText(fact.value)));
4672
+ });
4673
+ const forbiddenMisses = (testCase.forbiddenPhrases || []).filter((phrase) => !normalizedContent.includes(normalizeText(phrase)));
4674
+ const toolCalls = events.filter((event) => event.type === "tool_call" && event.phase === "success");
4675
+ const expectedToolHits = (testCase.expectedToolNames || []).filter((toolName) => toolCalls.some((event) => event.name === toolName));
4676
+ const toolPrecision = ratio(expectedToolHits.length, Math.max(toolCalls.length, 1));
4677
+ const recallAccuracy = ratio(matchedPhrases.length + matchedFacts.length, (testCase.expectedPhrases?.length || 0) + (testCase.expectedFacts?.length || 0));
4678
+ const obsoleteDropAccuracy = ratio(forbiddenMisses.length, testCase.forbiddenPhrases?.length || 0);
4679
+ const trajectoryScore = ratio(expectedToolHits.length, testCase.expectedToolNames?.length || 0);
4680
+ const hadRecoverableError = events.some((event) => event.type === "tool_call" && event.phase === "error");
4681
+ const recoveryRate = hadRecoverableError ? Number((normalizedContent.length > 0 ? 1 : 0).toFixed(3)) : 1;
4682
+ const overToolingRate = toolCalls.length === 0 ? 0 : Number((Math.max(0, toolCalls.length - Math.max(testCase.expectedToolNames?.length || 0, 1)) / toolCalls.length).toFixed(3));
4683
+ const success = recallAccuracy >= 0.8 && obsoleteDropAccuracy >= 0.8;
4684
+ return {
4685
+ id: testCase.id,
4686
+ family: testCase.family,
4687
+ success,
4688
+ recallAccuracy,
4689
+ obsoleteDropAccuracy,
4690
+ trajectoryScore: Number(((trajectoryScore + toolPrecision) / 2).toFixed(3)),
4691
+ recoveryRate,
4692
+ overToolingRate,
4693
+ latencyMs,
4694
+ totalTokens,
4695
+ profile: descriptor.runtimeProfile,
4696
+ profileLabel: descriptor.label,
4697
+ baseProfile: descriptor.baseProfile,
4698
+ notes: success ? [] : [
4699
+ recallAccuracy < 0.8 ? "Recall below threshold." : "",
4700
+ obsoleteDropAccuracy < 0.8 ? "Obsolete filtering below threshold." : "",
4701
+ trajectoryScore < 0.5 ? "Tool trajectory diverged from expectation." : ""
4702
+ ].filter(Boolean)
4703
+ };
4704
+ }
4705
+ function aggregateMetrics(caseResults) {
4706
+ const taskSuccess = ratio(caseResults.filter((result) => result.success).length, caseResults.length);
4707
+ const recallAccuracy = mean(caseResults.map((result) => result.recallAccuracy));
4708
+ const obsoleteDropAccuracy = mean(caseResults.map((result) => result.obsoleteDropAccuracy));
4709
+ const trajectoryScore = mean(caseResults.map((result) => result.trajectoryScore));
4710
+ const recoveryRate = mean(caseResults.map((result) => result.recoveryRate));
4711
+ const overToolingRate = mean(caseResults.map((result) => result.overToolingRate));
4712
+ const latencyMs = mean(caseResults.map((result) => result.latencyMs));
4713
+ const totalTokens = sum(caseResults.map((result) => result.totalTokens || 0));
4714
+ const toolPrecision = trajectoryScore;
4715
+ const costScore = totalTokens <= 0 ? 1 : Number((1 / (1 + totalTokens / 1e4)).toFixed(3));
4716
+ const score = Number((0.45 * taskSuccess + 0.2 * trajectoryScore + 0.15 * toolPrecision + 0.1 * recoveryRate + 0.1 * costScore).toFixed(3));
4717
+ return {
4718
+ taskSuccess,
4719
+ recallAccuracy,
4720
+ obsoleteDropAccuracy,
4721
+ trajectoryScore,
4722
+ recoveryRate,
4723
+ overToolingRate,
4724
+ latencyMs,
4725
+ totalTokens,
4726
+ score
4727
+ };
4728
+ }
4729
+ async function runSmartAgentEvalHarness(params) {
4730
+ const results = [];
4731
+ for (const profileTarget of params.profiles) {
4732
+ const descriptor = normalizeProfileTarget(profileTarget);
4733
+ const caseResults = [];
4734
+ for (const testCase of params.cases) {
4735
+ const agent = params.createAgent(descriptor.runtimeProfile, descriptor);
4736
+ const events = [];
4737
+ const startedAt = Date.now();
4738
+ const result = await agent.invoke({
4739
+ messages: [{ role: "user", content: testCase.prompt }]
4740
+ }, {
4741
+ onEvent: (event) => {
4742
+ events.push(event);
4743
+ }
4744
+ });
4745
+ caseResults.push(caseResultFromRun(
4746
+ descriptor,
4747
+ testCase,
4748
+ result.content,
4749
+ events,
4750
+ result.state,
4751
+ Date.now() - startedAt,
4752
+ extractTotalTokens(result.metadata?.usage)
4753
+ ));
4754
+ }
4755
+ results.push({
4756
+ profile: descriptor.runtimeProfile,
4757
+ profileLabel: descriptor.label,
4758
+ baseProfile: descriptor.baseProfile,
4759
+ metrics: aggregateMetrics(caseResults),
4760
+ cases: caseResults
4761
+ });
4762
+ }
4763
+ return results;
4764
+ }
4765
+
3140
4766
  // src/utils/content.ts
3141
4767
  function contentToString(content) {
3142
4768
  try {
@@ -3414,6 +5040,6 @@ function createCodeGuardrail(options) {
3414
5040
  });
3415
5041
  }
3416
5042
 
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 };
5043
+ 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
5044
  //# sourceMappingURL=index.mjs.map
3419
5045
  //# sourceMappingURL=index.mjs.map