@elevasis/sdk 1.39.0 → 1.41.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/cli.cjs +10 -26
- package/dist/index.d.ts +200 -8
- package/dist/index.js +9 -25
- package/dist/node/index.d.ts +180 -8
- package/dist/test-utils/index.d.ts +200 -8
- package/dist/test-utils/index.js +360 -179
- package/dist/types/worker/adapters/llm.d.ts +1 -1
- package/dist/worker/index.js +361 -180
- package/package.json +4 -4
- package/reference/claude-config/hooks/scaffold-registry-reminder.mjs +187 -188
- package/reference/claude-config/sync-notes/2026-07-24-claude-5-models-and-session-surface-fixes.md +116 -0
- package/reference/claude-config/sync-notes/2026-07-27-agent-strict-output-and-turn-drift.md +73 -0
- package/reference/index.mdx +1 -1
- package/reference/packages/core/src/business/README.md +52 -52
- package/reference/rules/frontend.md +1 -1
- package/reference/rules/package-taxonomy.md +1 -1
- package/reference/rules/platform.md +3 -3
- package/reference/scaffold/operations/scaffold-maintenance.md +112 -112
- package/reference/scaffold/operations/workflow-recipes.md +525 -525
- package/reference/scaffold/recipes/customize-crm-actions.md +391 -391
- package/reference/scaffold/recipes/extend-crm.md +4 -4
- package/reference/scaffold/recipes/extend-lead-gen.md +4 -4
- package/reference/scaffold/reference/glossary.md +1 -1
- package/reference/scaffold/ui/customization.md +243 -243
- package/reference/sdk/platform-tools/index.mdx +1 -1
- package/reference/sdk/platform-tools/type-safety.mdx +1 -1
package/dist/node/index.d.ts
CHANGED
|
@@ -296,7 +296,7 @@ type GoogleModel = 'gemini-3-flash-preview' | 'gemini-3.1-flash-lite-preview';
|
|
|
296
296
|
/**
|
|
297
297
|
* Supported Anthropic models (direct SDK access via @anthropic-ai/sdk)
|
|
298
298
|
*/
|
|
299
|
-
type AnthropicModel = 'claude-opus-
|
|
299
|
+
type AnthropicModel = 'claude-opus-5' | 'claude-sonnet-5' | 'claude-haiku-4-5-20251001' | 'claude-haiku-4-5';
|
|
300
300
|
/** Supported LLM models */
|
|
301
301
|
type LLMModel = OpenAIModel | OpenRouterModel | GoogleModel | AnthropicModel | 'mock';
|
|
302
302
|
/**
|
|
@@ -932,6 +932,20 @@ interface LLMGenerateResponse<T = unknown> {
|
|
|
932
932
|
totalTokens: number;
|
|
933
933
|
};
|
|
934
934
|
cost?: number;
|
|
935
|
+
/**
|
|
936
|
+
* Why this call went out WITHOUT `strict`, on an adapter that tried to send it with one.
|
|
937
|
+
*
|
|
938
|
+
* Present only on a refusal, so absence means either "strict was in effect" or "this adapter
|
|
939
|
+
* does not attempt strict at all" — the two are distinguished by which adapter answered, not by
|
|
940
|
+
* this field. `toStrictSchema` already computes these reasons and, until now, nothing consumed
|
|
941
|
+
* them at the call site: an unstrict call was indistinguishable from a strict one anywhere
|
|
942
|
+
* outside a dev-only flow log. That invisibility is what let every tenant run unstrict against a
|
|
943
|
+
* strict-capable API for as long as it took someone to recognise a pre-strict error signature.
|
|
944
|
+
*
|
|
945
|
+
* Internal-only, like `usage` — `UniversalLLMAdapter` lifts it onto the `ai_calls` row and
|
|
946
|
+
* strips it before the response reaches callers.
|
|
947
|
+
*/
|
|
948
|
+
strictRefusalReasons?: string[];
|
|
935
949
|
}
|
|
936
950
|
/**
|
|
937
951
|
* LLM Adapter interface
|
|
@@ -963,6 +977,14 @@ interface LLMAdapter {
|
|
|
963
977
|
* Memory types mirror action types for clarity and filtering
|
|
964
978
|
*/
|
|
965
979
|
type MemoryEntryType = 'context' | 'input' | 'reasoning' | 'tool-result' | 'delegation-result' | 'error';
|
|
980
|
+
/**
|
|
981
|
+
* Who authored an entry's content.
|
|
982
|
+
*
|
|
983
|
+
* This is what lets the assembled prompt tell framework-authored text apart from text that
|
|
984
|
+
* originated outside the trust boundary. `'framework'` content is ours; the other three are not
|
|
985
|
+
* and are rendered inside the JSON data envelope (see `MemoryManager.toContextParts`).
|
|
986
|
+
*/
|
|
987
|
+
type MemoryEntrySource = 'framework' | 'user' | 'tool' | 'model';
|
|
966
988
|
/**
|
|
967
989
|
* Memory entry - represents a single entry in agent memory
|
|
968
990
|
* Stored in agent memory, translated by adapters to vendor-specific formats
|
|
@@ -973,6 +995,16 @@ interface MemoryEntry {
|
|
|
973
995
|
timestamp: number;
|
|
974
996
|
turnNumber: number | null;
|
|
975
997
|
iterationNumber: number | null;
|
|
998
|
+
/**
|
|
999
|
+
* Provenance. **Optional on purpose** — `undefined` means unknown, which is what every
|
|
1000
|
+
* pre-existing snapshot and every not-yet-redeployed tenant bundle produces. Read sites MUST
|
|
1001
|
+
* test `== null`, never `=== undefined`: the `inTurnScope` predicate in `manager.ts` is the
|
|
1002
|
+
* cautionary precedent, where a `=== undefined` check silently dropped every `null`-stamped
|
|
1003
|
+
* entry. `isMemoryEntry` is deliberately NOT tightened to require this field; doing so would
|
|
1004
|
+
* make every stored snapshot fail validation, and `restoreSessionMemory` fails open by
|
|
1005
|
+
* starting the agent with empty memory rather than throwing.
|
|
1006
|
+
*/
|
|
1007
|
+
source?: MemoryEntrySource;
|
|
976
1008
|
}
|
|
977
1009
|
/**
|
|
978
1010
|
* Agent memory - Self-orchestrated memory with session + working storage
|
|
@@ -1000,8 +1032,17 @@ interface MemoryStatus {
|
|
|
1000
1032
|
sessionMemoryKeys: number;
|
|
1001
1033
|
sessionMemoryLimit: number;
|
|
1002
1034
|
currentKeys: string[];
|
|
1035
|
+
sessionMemoryTokens: number;
|
|
1036
|
+
sessionMemoryTokenLimit: number;
|
|
1037
|
+
/**
|
|
1038
|
+
* History tokens as a percentage of `historyBudget` — history ALONE, not history plus session
|
|
1039
|
+
* memory. It previously reported the combined total under this name, so session memory growth
|
|
1040
|
+
* read as history pressure and triggered history compaction that could not relieve it.
|
|
1041
|
+
*/
|
|
1003
1042
|
historyPercent: number;
|
|
1004
1043
|
historyTokens: number;
|
|
1044
|
+
historyBudget: number;
|
|
1045
|
+
totalTokens: number;
|
|
1005
1046
|
tokenBudget: number;
|
|
1006
1047
|
}
|
|
1007
1048
|
/**
|
|
@@ -1018,6 +1059,20 @@ interface MemoryConstraints {
|
|
|
1018
1059
|
* Agent provides strings, framework handles wrapping and auto-compaction
|
|
1019
1060
|
*/
|
|
1020
1061
|
|
|
1062
|
+
/**
|
|
1063
|
+
* The framework's own framing message and the untrusted data envelope, as separate strings.
|
|
1064
|
+
*
|
|
1065
|
+
* They are separate because the model must be able to tell them apart, and so must the input
|
|
1066
|
+
* sanitizer: the framing is framework-authored and trusted, the envelope is not. Concatenating
|
|
1067
|
+
* them — which is what this replaced — made that distinction undecidable at the adapter and left
|
|
1068
|
+
* the framework's own section headers inside the region scanned for delimiter injection.
|
|
1069
|
+
*/
|
|
1070
|
+
interface MemoryContextParts {
|
|
1071
|
+
/** Framework-authored. Memory status and a description of the envelope. Carries NO stored content. */
|
|
1072
|
+
framing: string;
|
|
1073
|
+
/** Every stored fragment, JSON-encoded and source-tagged. Untrusted. */
|
|
1074
|
+
dataEnvelope: string;
|
|
1075
|
+
}
|
|
1021
1076
|
/**
|
|
1022
1077
|
* Memory Manager - Agent memory orchestration
|
|
1023
1078
|
* Provides ultra-simple API for agents (strings only)
|
|
@@ -1034,7 +1089,7 @@ declare class MemoryManager {
|
|
|
1034
1089
|
* @param key - Session memory key
|
|
1035
1090
|
* @param content - String content from agent
|
|
1036
1091
|
*/
|
|
1037
|
-
set(key: string, content: string): void;
|
|
1092
|
+
set(key: string, content: string, source?: MemoryEntrySource): void;
|
|
1038
1093
|
/**
|
|
1039
1094
|
* Get session memory entry content
|
|
1040
1095
|
* @param key - Session memory key
|
|
@@ -1063,6 +1118,15 @@ declare class MemoryManager {
|
|
|
1063
1118
|
* Emergency fallback if agent exceeds limits
|
|
1064
1119
|
*/
|
|
1065
1120
|
enforceHardLimits(): void;
|
|
1121
|
+
/**
|
|
1122
|
+
* Evict oldest session memory entries until the pool fits its token limit.
|
|
1123
|
+
*
|
|
1124
|
+
* Key count and token count are different constraints: 25 short keys are fine, 25 large ones
|
|
1125
|
+
* are not. Eviction is oldest-first by timestamp, matching the key-count path, and always
|
|
1126
|
+
* leaves at least one entry so a single oversized key degrades to "one key" rather than to
|
|
1127
|
+
* "memory silently emptied".
|
|
1128
|
+
*/
|
|
1129
|
+
private enforceSessionMemoryTokenLimit;
|
|
1066
1130
|
/**
|
|
1067
1131
|
* Get history length (for logging and introspection)
|
|
1068
1132
|
* @returns Number of entries in history
|
|
@@ -1086,14 +1150,29 @@ declare class MemoryManager {
|
|
|
1086
1150
|
*/
|
|
1087
1151
|
getSnapshot(): AgentMemory | undefined;
|
|
1088
1152
|
/**
|
|
1089
|
-
* Build
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1153
|
+
* Build the framework framing and the untrusted data envelope for an LLM call.
|
|
1154
|
+
*
|
|
1155
|
+
* These are two separate strings because they are two different trust levels, and they used to
|
|
1156
|
+
* be one. Concatenated, the framework's own `=== ... ===` section headers sat in the same string
|
|
1157
|
+
* as stored tool output and user text, so the input sanitizer matched its own scaffolding on
|
|
1158
|
+
* every call and nothing downstream could tell which half a match came from. Splitting them
|
|
1159
|
+
* makes that distinction structural: the framing is ours, the envelope is not.
|
|
1160
|
+
*
|
|
1161
|
+
* The envelope is JSON, which additionally neutralizes the anchored-delimiter attack class —
|
|
1162
|
+
* `JSON.stringify` escapes newlines, so a stored fragment cannot produce a line that starts
|
|
1163
|
+
* with `===` no matter what it contains.
|
|
1164
|
+
*
|
|
1165
|
+
* The current turn's own input is deliberately NOT in either string. It travels as its own
|
|
1166
|
+
* `role:'user'` message (see `buildAgentMessages`), which is the whole point: a model asked to
|
|
1167
|
+
* treat "everything in this block" as data was also being handed the live question inside that
|
|
1168
|
+
* block.
|
|
1169
|
+
*
|
|
1170
|
+
* Shows current iteration entries FIRST (reverse chronological) for LLM attention.
|
|
1171
|
+
*
|
|
1092
1172
|
* @param currentIteration - Current iteration number (0 = pre-iteration)
|
|
1093
1173
|
* @param currentTurn - Current turn number (optional, for session context filtering)
|
|
1094
|
-
* @returns Formatted memory context for LLM prompt
|
|
1095
1174
|
*/
|
|
1096
|
-
|
|
1175
|
+
toContextParts(currentIteration: number, currentTurn?: number): MemoryContextParts;
|
|
1097
1176
|
}
|
|
1098
1177
|
|
|
1099
1178
|
/**
|
|
@@ -1215,7 +1294,7 @@ interface AgentConfig extends ResourceDefinition {
|
|
|
1215
1294
|
* Security level for system prompt hardening (auto-derived if omitted)
|
|
1216
1295
|
*
|
|
1217
1296
|
* - 'standard': Lightweight defense (3 rules) - default for non-session agents
|
|
1218
|
-
* - 'hardened': Comprehensive defense (
|
|
1297
|
+
* - 'hardened': Comprehensive defense (5 rules) - default for session-capable agents
|
|
1219
1298
|
* - 'none': No security prompt - for pure internal agents with no external input
|
|
1220
1299
|
*
|
|
1221
1300
|
* If omitted, derived from sessionCapable:
|
|
@@ -1292,6 +1371,12 @@ interface IterationContext {
|
|
|
1292
1371
|
modelConfig: ModelConfig;
|
|
1293
1372
|
adapterFactory: LLMAdapterFactory;
|
|
1294
1373
|
knowledgeMap?: KnowledgeMap;
|
|
1374
|
+
/**
|
|
1375
|
+
* The validated input for this execution, serialized. It travels here because the model gets
|
|
1376
|
+
* it as its own `role:'user'` message; nothing else in this context carried it, so the input
|
|
1377
|
+
* had to be read back out of memory history and shipped inside the memory block.
|
|
1378
|
+
*/
|
|
1379
|
+
currentInput: string;
|
|
1295
1380
|
}
|
|
1296
1381
|
|
|
1297
1382
|
declare const OntologyScopeSchema: z.ZodDefault<z.ZodObject<{
|
|
@@ -2593,6 +2678,25 @@ declare class MetricsCollector {
|
|
|
2593
2678
|
buildExecutionMetrics(metricsConfig?: ResourceMetricsConfig): ExecutionMetricsSummary;
|
|
2594
2679
|
}
|
|
2595
2680
|
|
|
2681
|
+
/**
|
|
2682
|
+
* Which `role:'user'` message slot a sanitizer warning came from, derived from the request's
|
|
2683
|
+
* message array (no new plumbing from callers):
|
|
2684
|
+
* - `'memory-context'` — the framework's framing message, identified by the `=== MEMORY STATUS ===`
|
|
2685
|
+
* banner. Framework-authored and trusted; it is no longer scanned at all, so this source should
|
|
2686
|
+
* not appear for agent calls. Retained because stale tenant bundles still emit the old combined
|
|
2687
|
+
* block, and their rows must stay readable.
|
|
2688
|
+
* - `'data-envelope'` — the JSON envelope carrying every stored fragment. Untrusted, and the slot
|
|
2689
|
+
* where a match is genuine signal.
|
|
2690
|
+
* - `'input'` — the turn's own input, on its own message. Also untrusted.
|
|
2691
|
+
* - `'history'` — replayed prior turns. Untrusted, but already screened at their own front door,
|
|
2692
|
+
* so warnings here are recorded and never block (see `screenInput`).
|
|
2693
|
+
*/
|
|
2694
|
+
type InputWarningSource = 'memory-context' | 'data-envelope' | 'history' | 'input';
|
|
2695
|
+
/** Per-source breakdown of sanitizer warnings, so a memory-context echo is distinguishable from a genuine hit. */
|
|
2696
|
+
interface SourcedInputWarnings {
|
|
2697
|
+
source: InputWarningSource;
|
|
2698
|
+
warnings: string[];
|
|
2699
|
+
}
|
|
2596
2700
|
interface BaseAICall {
|
|
2597
2701
|
callSequence: number;
|
|
2598
2702
|
callType: 'agent-reasoning' | 'agent-completion' | 'workflow-step' | 'tool' | 'other';
|
|
@@ -2602,6 +2706,62 @@ interface BaseAICall {
|
|
|
2602
2706
|
costUsd: number;
|
|
2603
2707
|
latencyMs: number;
|
|
2604
2708
|
context?: AICallContext;
|
|
2709
|
+
/**
|
|
2710
|
+
* Distinct prompt-injection pattern types detected in the request's user-role messages.
|
|
2711
|
+
* Present only when the input sanitizer matched something. Non-blocking matches ride along on
|
|
2712
|
+
* the successful call's row; a blocked call records a row of its own (see `inputBlocked`).
|
|
2713
|
+
*
|
|
2714
|
+
* Flat union across all `role:'user'` messages — unchanged shape, kept for existing readers.
|
|
2715
|
+
* See `inputWarningsBySource` for the per-slot breakdown.
|
|
2716
|
+
*/
|
|
2717
|
+
inputWarnings?: string[];
|
|
2718
|
+
/**
|
|
2719
|
+
* Additive breakdown of `inputWarnings` by message slot (see `InputWarningSource`). Present only
|
|
2720
|
+
* when at least one source produced a warning. Existing readers that only look at the flat
|
|
2721
|
+
* `inputWarnings` array are unaffected.
|
|
2722
|
+
*/
|
|
2723
|
+
inputWarningsBySource?: SourcedInputWarnings[];
|
|
2724
|
+
/**
|
|
2725
|
+
* True when the sanitizer blocked the request and no provider call was made.
|
|
2726
|
+
* Such a row carries zero tokens, zero cost, and zero latency — it exists so a hard,
|
|
2727
|
+
* user-visible failure is observable at all. Before this, a blocked call produced no row.
|
|
2728
|
+
*/
|
|
2729
|
+
inputBlocked?: boolean;
|
|
2730
|
+
/**
|
|
2731
|
+
* The validator's message when the provider responded but its output failed `responseSchema`
|
|
2732
|
+
* validation (e.g. `missing required field 'nextActions'`). Present only on such a row.
|
|
2733
|
+
*
|
|
2734
|
+
* This is NOT the blocked-input case: the provider DID respond and tokens WERE spent, so the row
|
|
2735
|
+
* carries real `inputTokens`, `outputTokens`, cost and latency. It is a paid call that produced
|
|
2736
|
+
* nothing usable, and before this it produced no row at all.
|
|
2737
|
+
*
|
|
2738
|
+
* One row per failed attempt — the adapter retries a validation failure up to `LLM_MAX_ATTEMPTS`,
|
|
2739
|
+
* so a turn that exhausts its retries records three. Reading `inputTokens` across these rows is
|
|
2740
|
+
* what measures malformed-output rate against context size rather than inferring it.
|
|
2741
|
+
*
|
|
2742
|
+
* Existing readers that only look at the fields above are unaffected.
|
|
2743
|
+
*/
|
|
2744
|
+
outputValidationError?: string;
|
|
2745
|
+
/**
|
|
2746
|
+
* The raw model output that failed validation, JSON-stringified and truncated to a bounded
|
|
2747
|
+
* length. Truncation is visible in the value itself (a trailing `…[truncated: N chars total]`),
|
|
2748
|
+
* never silent. Present only alongside `outputValidationError`.
|
|
2749
|
+
*/
|
|
2750
|
+
unvalidatedOutput?: string;
|
|
2751
|
+
/**
|
|
2752
|
+
* Why this call went out without `strict` structured output, on an adapter that tried to send it
|
|
2753
|
+
* with one. Present ONLY on a refusal — its absence on a row from a strict-capable adapter means
|
|
2754
|
+
* strict was in effect.
|
|
2755
|
+
*
|
|
2756
|
+
* This is the row that answers "is this agent's output actually being enforced?" without reading
|
|
2757
|
+
* source. Counting rows that carry it, per resource, is the refusal count: a redeployed agent on
|
|
2758
|
+
* a current bundle should read zero. It exists because the previous answer was a dev-only flow
|
|
2759
|
+
* log, which meant production had no answer — and every sync-managed tenant ran unstrict against
|
|
2760
|
+
* a strict-capable API until a 14-turn session surfaced a pre-strict error signature.
|
|
2761
|
+
*
|
|
2762
|
+
* Existing readers that only look at the fields above are unaffected.
|
|
2763
|
+
*/
|
|
2764
|
+
strictRefusalReasons?: string[];
|
|
2605
2765
|
}
|
|
2606
2766
|
type AICallContext = AgentReasoningContext | AgentCompletionContext | WorkflowStepContext | ToolCallContext | OtherCallContext;
|
|
2607
2767
|
interface AgentReasoningContext {
|
|
@@ -2647,6 +2807,18 @@ interface LLMUsageData {
|
|
|
2647
2807
|
latencyMs: number;
|
|
2648
2808
|
/** Actual cost from provider in USD (when available, e.g., OpenRouter) */
|
|
2649
2809
|
cost?: number;
|
|
2810
|
+
/** Distinct prompt-injection pattern types detected in the request's user-role messages */
|
|
2811
|
+
inputWarnings?: string[];
|
|
2812
|
+
/** Additive per-source breakdown of `inputWarnings` — see `SourcedInputWarnings` */
|
|
2813
|
+
inputWarningsBySource?: SourcedInputWarnings[];
|
|
2814
|
+
/** True when the sanitizer blocked the request and no provider call was made */
|
|
2815
|
+
inputBlocked?: boolean;
|
|
2816
|
+
/** Validator message when the provider responded but the output failed `responseSchema` validation */
|
|
2817
|
+
outputValidationError?: string;
|
|
2818
|
+
/** Raw model output that failed validation — JSON-stringified, truncated, truncation marked inline */
|
|
2819
|
+
unvalidatedOutput?: string;
|
|
2820
|
+
/** Why the call went out unstrict, when a strict-capable adapter refused the schema */
|
|
2821
|
+
strictRefusalReasons?: string[];
|
|
2650
2822
|
}
|
|
2651
2823
|
interface AIUsageSummary {
|
|
2652
2824
|
model: LLMModel;
|
|
@@ -259,7 +259,7 @@ type GoogleModel = 'gemini-3-flash-preview' | 'gemini-3.1-flash-lite-preview';
|
|
|
259
259
|
/**
|
|
260
260
|
* Supported Anthropic models (direct SDK access via @anthropic-ai/sdk)
|
|
261
261
|
*/
|
|
262
|
-
type AnthropicModel = 'claude-opus-
|
|
262
|
+
type AnthropicModel = 'claude-opus-5' | 'claude-sonnet-5' | 'claude-haiku-4-5-20251001' | 'claude-haiku-4-5';
|
|
263
263
|
/** Supported LLM models */
|
|
264
264
|
type LLMModel = OpenAIModel | OpenRouterModel | GoogleModel | AnthropicModel | 'mock';
|
|
265
265
|
/**
|
|
@@ -895,6 +895,20 @@ interface LLMGenerateResponse<T = unknown> {
|
|
|
895
895
|
totalTokens: number;
|
|
896
896
|
};
|
|
897
897
|
cost?: number;
|
|
898
|
+
/**
|
|
899
|
+
* Why this call went out WITHOUT `strict`, on an adapter that tried to send it with one.
|
|
900
|
+
*
|
|
901
|
+
* Present only on a refusal, so absence means either "strict was in effect" or "this adapter
|
|
902
|
+
* does not attempt strict at all" — the two are distinguished by which adapter answered, not by
|
|
903
|
+
* this field. `toStrictSchema` already computes these reasons and, until now, nothing consumed
|
|
904
|
+
* them at the call site: an unstrict call was indistinguishable from a strict one anywhere
|
|
905
|
+
* outside a dev-only flow log. That invisibility is what let every tenant run unstrict against a
|
|
906
|
+
* strict-capable API for as long as it took someone to recognise a pre-strict error signature.
|
|
907
|
+
*
|
|
908
|
+
* Internal-only, like `usage` — `UniversalLLMAdapter` lifts it onto the `ai_calls` row and
|
|
909
|
+
* strips it before the response reaches callers.
|
|
910
|
+
*/
|
|
911
|
+
strictRefusalReasons?: string[];
|
|
898
912
|
}
|
|
899
913
|
/**
|
|
900
914
|
* LLM Adapter interface
|
|
@@ -926,6 +940,14 @@ interface LLMAdapter {
|
|
|
926
940
|
* Memory types mirror action types for clarity and filtering
|
|
927
941
|
*/
|
|
928
942
|
type MemoryEntryType = 'context' | 'input' | 'reasoning' | 'tool-result' | 'delegation-result' | 'error';
|
|
943
|
+
/**
|
|
944
|
+
* Who authored an entry's content.
|
|
945
|
+
*
|
|
946
|
+
* This is what lets the assembled prompt tell framework-authored text apart from text that
|
|
947
|
+
* originated outside the trust boundary. `'framework'` content is ours; the other three are not
|
|
948
|
+
* and are rendered inside the JSON data envelope (see `MemoryManager.toContextParts`).
|
|
949
|
+
*/
|
|
950
|
+
type MemoryEntrySource = 'framework' | 'user' | 'tool' | 'model';
|
|
929
951
|
/**
|
|
930
952
|
* Memory entry - represents a single entry in agent memory
|
|
931
953
|
* Stored in agent memory, translated by adapters to vendor-specific formats
|
|
@@ -936,6 +958,16 @@ interface MemoryEntry {
|
|
|
936
958
|
timestamp: number;
|
|
937
959
|
turnNumber: number | null;
|
|
938
960
|
iterationNumber: number | null;
|
|
961
|
+
/**
|
|
962
|
+
* Provenance. **Optional on purpose** — `undefined` means unknown, which is what every
|
|
963
|
+
* pre-existing snapshot and every not-yet-redeployed tenant bundle produces. Read sites MUST
|
|
964
|
+
* test `== null`, never `=== undefined`: the `inTurnScope` predicate in `manager.ts` is the
|
|
965
|
+
* cautionary precedent, where a `=== undefined` check silently dropped every `null`-stamped
|
|
966
|
+
* entry. `isMemoryEntry` is deliberately NOT tightened to require this field; doing so would
|
|
967
|
+
* make every stored snapshot fail validation, and `restoreSessionMemory` fails open by
|
|
968
|
+
* starting the agent with empty memory rather than throwing.
|
|
969
|
+
*/
|
|
970
|
+
source?: MemoryEntrySource;
|
|
939
971
|
}
|
|
940
972
|
/**
|
|
941
973
|
* Agent memory - Self-orchestrated memory with session + working storage
|
|
@@ -963,8 +995,17 @@ interface MemoryStatus {
|
|
|
963
995
|
sessionMemoryKeys: number;
|
|
964
996
|
sessionMemoryLimit: number;
|
|
965
997
|
currentKeys: string[];
|
|
998
|
+
sessionMemoryTokens: number;
|
|
999
|
+
sessionMemoryTokenLimit: number;
|
|
1000
|
+
/**
|
|
1001
|
+
* History tokens as a percentage of `historyBudget` — history ALONE, not history plus session
|
|
1002
|
+
* memory. It previously reported the combined total under this name, so session memory growth
|
|
1003
|
+
* read as history pressure and triggered history compaction that could not relieve it.
|
|
1004
|
+
*/
|
|
966
1005
|
historyPercent: number;
|
|
967
1006
|
historyTokens: number;
|
|
1007
|
+
historyBudget: number;
|
|
1008
|
+
totalTokens: number;
|
|
968
1009
|
tokenBudget: number;
|
|
969
1010
|
}
|
|
970
1011
|
/**
|
|
@@ -981,6 +1022,20 @@ interface MemoryConstraints {
|
|
|
981
1022
|
* Agent provides strings, framework handles wrapping and auto-compaction
|
|
982
1023
|
*/
|
|
983
1024
|
|
|
1025
|
+
/**
|
|
1026
|
+
* The framework's own framing message and the untrusted data envelope, as separate strings.
|
|
1027
|
+
*
|
|
1028
|
+
* They are separate because the model must be able to tell them apart, and so must the input
|
|
1029
|
+
* sanitizer: the framing is framework-authored and trusted, the envelope is not. Concatenating
|
|
1030
|
+
* them — which is what this replaced — made that distinction undecidable at the adapter and left
|
|
1031
|
+
* the framework's own section headers inside the region scanned for delimiter injection.
|
|
1032
|
+
*/
|
|
1033
|
+
interface MemoryContextParts {
|
|
1034
|
+
/** Framework-authored. Memory status and a description of the envelope. Carries NO stored content. */
|
|
1035
|
+
framing: string;
|
|
1036
|
+
/** Every stored fragment, JSON-encoded and source-tagged. Untrusted. */
|
|
1037
|
+
dataEnvelope: string;
|
|
1038
|
+
}
|
|
984
1039
|
/**
|
|
985
1040
|
* Memory Manager - Agent memory orchestration
|
|
986
1041
|
* Provides ultra-simple API for agents (strings only)
|
|
@@ -997,7 +1052,7 @@ declare class MemoryManager {
|
|
|
997
1052
|
* @param key - Session memory key
|
|
998
1053
|
* @param content - String content from agent
|
|
999
1054
|
*/
|
|
1000
|
-
set(key: string, content: string): void;
|
|
1055
|
+
set(key: string, content: string, source?: MemoryEntrySource): void;
|
|
1001
1056
|
/**
|
|
1002
1057
|
* Get session memory entry content
|
|
1003
1058
|
* @param key - Session memory key
|
|
@@ -1026,6 +1081,15 @@ declare class MemoryManager {
|
|
|
1026
1081
|
* Emergency fallback if agent exceeds limits
|
|
1027
1082
|
*/
|
|
1028
1083
|
enforceHardLimits(): void;
|
|
1084
|
+
/**
|
|
1085
|
+
* Evict oldest session memory entries until the pool fits its token limit.
|
|
1086
|
+
*
|
|
1087
|
+
* Key count and token count are different constraints: 25 short keys are fine, 25 large ones
|
|
1088
|
+
* are not. Eviction is oldest-first by timestamp, matching the key-count path, and always
|
|
1089
|
+
* leaves at least one entry so a single oversized key degrades to "one key" rather than to
|
|
1090
|
+
* "memory silently emptied".
|
|
1091
|
+
*/
|
|
1092
|
+
private enforceSessionMemoryTokenLimit;
|
|
1029
1093
|
/**
|
|
1030
1094
|
* Get history length (for logging and introspection)
|
|
1031
1095
|
* @returns Number of entries in history
|
|
@@ -1049,14 +1113,29 @@ declare class MemoryManager {
|
|
|
1049
1113
|
*/
|
|
1050
1114
|
getSnapshot(): AgentMemory | undefined;
|
|
1051
1115
|
/**
|
|
1052
|
-
* Build
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1116
|
+
* Build the framework framing and the untrusted data envelope for an LLM call.
|
|
1117
|
+
*
|
|
1118
|
+
* These are two separate strings because they are two different trust levels, and they used to
|
|
1119
|
+
* be one. Concatenated, the framework's own `=== ... ===` section headers sat in the same string
|
|
1120
|
+
* as stored tool output and user text, so the input sanitizer matched its own scaffolding on
|
|
1121
|
+
* every call and nothing downstream could tell which half a match came from. Splitting them
|
|
1122
|
+
* makes that distinction structural: the framing is ours, the envelope is not.
|
|
1123
|
+
*
|
|
1124
|
+
* The envelope is JSON, which additionally neutralizes the anchored-delimiter attack class —
|
|
1125
|
+
* `JSON.stringify` escapes newlines, so a stored fragment cannot produce a line that starts
|
|
1126
|
+
* with `===` no matter what it contains.
|
|
1127
|
+
*
|
|
1128
|
+
* The current turn's own input is deliberately NOT in either string. It travels as its own
|
|
1129
|
+
* `role:'user'` message (see `buildAgentMessages`), which is the whole point: a model asked to
|
|
1130
|
+
* treat "everything in this block" as data was also being handed the live question inside that
|
|
1131
|
+
* block.
|
|
1132
|
+
*
|
|
1133
|
+
* Shows current iteration entries FIRST (reverse chronological) for LLM attention.
|
|
1134
|
+
*
|
|
1055
1135
|
* @param currentIteration - Current iteration number (0 = pre-iteration)
|
|
1056
1136
|
* @param currentTurn - Current turn number (optional, for session context filtering)
|
|
1057
|
-
* @returns Formatted memory context for LLM prompt
|
|
1058
1137
|
*/
|
|
1059
|
-
|
|
1138
|
+
toContextParts(currentIteration: number, currentTurn?: number): MemoryContextParts;
|
|
1060
1139
|
}
|
|
1061
1140
|
|
|
1062
1141
|
/**
|
|
@@ -1178,7 +1257,7 @@ interface AgentConfig extends ResourceDefinition {
|
|
|
1178
1257
|
* Security level for system prompt hardening (auto-derived if omitted)
|
|
1179
1258
|
*
|
|
1180
1259
|
* - 'standard': Lightweight defense (3 rules) - default for non-session agents
|
|
1181
|
-
* - 'hardened': Comprehensive defense (
|
|
1260
|
+
* - 'hardened': Comprehensive defense (5 rules) - default for session-capable agents
|
|
1182
1261
|
* - 'none': No security prompt - for pure internal agents with no external input
|
|
1183
1262
|
*
|
|
1184
1263
|
* If omitted, derived from sessionCapable:
|
|
@@ -1255,6 +1334,12 @@ interface IterationContext {
|
|
|
1255
1334
|
modelConfig: ModelConfig;
|
|
1256
1335
|
adapterFactory: LLMAdapterFactory;
|
|
1257
1336
|
knowledgeMap?: KnowledgeMap;
|
|
1337
|
+
/**
|
|
1338
|
+
* The validated input for this execution, serialized. It travels here because the model gets
|
|
1339
|
+
* it as its own `role:'user'` message; nothing else in this context carried it, so the input
|
|
1340
|
+
* had to be read back out of memory history and shipped inside the memory block.
|
|
1341
|
+
*/
|
|
1342
|
+
currentInput: string;
|
|
1258
1343
|
}
|
|
1259
1344
|
|
|
1260
1345
|
type Json = string | number | boolean | null | {
|
|
@@ -4001,6 +4086,7 @@ type Database = {
|
|
|
4001
4086
|
deleted_at: string | null;
|
|
4002
4087
|
ended_at: string | null;
|
|
4003
4088
|
memory_snapshot: Json;
|
|
4089
|
+
memory_version: number;
|
|
4004
4090
|
metadata: Json | null;
|
|
4005
4091
|
organization_id: string;
|
|
4006
4092
|
resource_id: string;
|
|
@@ -4017,6 +4103,7 @@ type Database = {
|
|
|
4017
4103
|
deleted_at?: string | null;
|
|
4018
4104
|
ended_at?: string | null;
|
|
4019
4105
|
memory_snapshot: Json;
|
|
4106
|
+
memory_version?: number;
|
|
4020
4107
|
metadata?: Json | null;
|
|
4021
4108
|
organization_id: string;
|
|
4022
4109
|
resource_id: string;
|
|
@@ -4033,6 +4120,7 @@ type Database = {
|
|
|
4033
4120
|
deleted_at?: string | null;
|
|
4034
4121
|
ended_at?: string | null;
|
|
4035
4122
|
memory_snapshot?: Json;
|
|
4123
|
+
memory_version?: number;
|
|
4036
4124
|
metadata?: Json | null;
|
|
4037
4125
|
organization_id?: string;
|
|
4038
4126
|
resource_id?: string;
|
|
@@ -4370,9 +4458,14 @@ type Database = {
|
|
|
4370
4458
|
p_session_id: string;
|
|
4371
4459
|
};
|
|
4372
4460
|
Returns: {
|
|
4461
|
+
context_window_size: number;
|
|
4373
4462
|
created_at: string;
|
|
4463
|
+
cumulative_input_tokens: number;
|
|
4464
|
+
cumulative_output_tokens: number;
|
|
4465
|
+
deleted_at: string;
|
|
4374
4466
|
ended_at: string;
|
|
4375
4467
|
memory_snapshot: Json;
|
|
4468
|
+
memory_version: number;
|
|
4376
4469
|
metadata: Json;
|
|
4377
4470
|
organization_id: string;
|
|
4378
4471
|
resource_id: string;
|
|
@@ -4414,6 +4507,18 @@ type Database = {
|
|
|
4414
4507
|
};
|
|
4415
4508
|
Returns: boolean;
|
|
4416
4509
|
};
|
|
4510
|
+
increment_session_tokens: {
|
|
4511
|
+
Args: {
|
|
4512
|
+
p_input_tokens: number;
|
|
4513
|
+
p_output_tokens: number;
|
|
4514
|
+
p_session_id: string;
|
|
4515
|
+
};
|
|
4516
|
+
Returns: {
|
|
4517
|
+
context_window_size: number;
|
|
4518
|
+
cumulative_input_tokens: number;
|
|
4519
|
+
cumulative_output_tokens: number;
|
|
4520
|
+
}[];
|
|
4521
|
+
};
|
|
4417
4522
|
is_org_member: {
|
|
4418
4523
|
Args: {
|
|
4419
4524
|
org_id: string;
|
|
@@ -10146,6 +10251,25 @@ declare class MetricsCollector {
|
|
|
10146
10251
|
buildExecutionMetrics(metricsConfig?: ResourceMetricsConfig): ExecutionMetricsSummary;
|
|
10147
10252
|
}
|
|
10148
10253
|
|
|
10254
|
+
/**
|
|
10255
|
+
* Which `role:'user'` message slot a sanitizer warning came from, derived from the request's
|
|
10256
|
+
* message array (no new plumbing from callers):
|
|
10257
|
+
* - `'memory-context'` — the framework's framing message, identified by the `=== MEMORY STATUS ===`
|
|
10258
|
+
* banner. Framework-authored and trusted; it is no longer scanned at all, so this source should
|
|
10259
|
+
* not appear for agent calls. Retained because stale tenant bundles still emit the old combined
|
|
10260
|
+
* block, and their rows must stay readable.
|
|
10261
|
+
* - `'data-envelope'` — the JSON envelope carrying every stored fragment. Untrusted, and the slot
|
|
10262
|
+
* where a match is genuine signal.
|
|
10263
|
+
* - `'input'` — the turn's own input, on its own message. Also untrusted.
|
|
10264
|
+
* - `'history'` — replayed prior turns. Untrusted, but already screened at their own front door,
|
|
10265
|
+
* so warnings here are recorded and never block (see `screenInput`).
|
|
10266
|
+
*/
|
|
10267
|
+
type InputWarningSource = 'memory-context' | 'data-envelope' | 'history' | 'input';
|
|
10268
|
+
/** Per-source breakdown of sanitizer warnings, so a memory-context echo is distinguishable from a genuine hit. */
|
|
10269
|
+
interface SourcedInputWarnings {
|
|
10270
|
+
source: InputWarningSource;
|
|
10271
|
+
warnings: string[];
|
|
10272
|
+
}
|
|
10149
10273
|
interface BaseAICall {
|
|
10150
10274
|
callSequence: number;
|
|
10151
10275
|
callType: 'agent-reasoning' | 'agent-completion' | 'workflow-step' | 'tool' | 'other';
|
|
@@ -10155,6 +10279,62 @@ interface BaseAICall {
|
|
|
10155
10279
|
costUsd: number;
|
|
10156
10280
|
latencyMs: number;
|
|
10157
10281
|
context?: AICallContext;
|
|
10282
|
+
/**
|
|
10283
|
+
* Distinct prompt-injection pattern types detected in the request's user-role messages.
|
|
10284
|
+
* Present only when the input sanitizer matched something. Non-blocking matches ride along on
|
|
10285
|
+
* the successful call's row; a blocked call records a row of its own (see `inputBlocked`).
|
|
10286
|
+
*
|
|
10287
|
+
* Flat union across all `role:'user'` messages — unchanged shape, kept for existing readers.
|
|
10288
|
+
* See `inputWarningsBySource` for the per-slot breakdown.
|
|
10289
|
+
*/
|
|
10290
|
+
inputWarnings?: string[];
|
|
10291
|
+
/**
|
|
10292
|
+
* Additive breakdown of `inputWarnings` by message slot (see `InputWarningSource`). Present only
|
|
10293
|
+
* when at least one source produced a warning. Existing readers that only look at the flat
|
|
10294
|
+
* `inputWarnings` array are unaffected.
|
|
10295
|
+
*/
|
|
10296
|
+
inputWarningsBySource?: SourcedInputWarnings[];
|
|
10297
|
+
/**
|
|
10298
|
+
* True when the sanitizer blocked the request and no provider call was made.
|
|
10299
|
+
* Such a row carries zero tokens, zero cost, and zero latency — it exists so a hard,
|
|
10300
|
+
* user-visible failure is observable at all. Before this, a blocked call produced no row.
|
|
10301
|
+
*/
|
|
10302
|
+
inputBlocked?: boolean;
|
|
10303
|
+
/**
|
|
10304
|
+
* The validator's message when the provider responded but its output failed `responseSchema`
|
|
10305
|
+
* validation (e.g. `missing required field 'nextActions'`). Present only on such a row.
|
|
10306
|
+
*
|
|
10307
|
+
* This is NOT the blocked-input case: the provider DID respond and tokens WERE spent, so the row
|
|
10308
|
+
* carries real `inputTokens`, `outputTokens`, cost and latency. It is a paid call that produced
|
|
10309
|
+
* nothing usable, and before this it produced no row at all.
|
|
10310
|
+
*
|
|
10311
|
+
* One row per failed attempt — the adapter retries a validation failure up to `LLM_MAX_ATTEMPTS`,
|
|
10312
|
+
* so a turn that exhausts its retries records three. Reading `inputTokens` across these rows is
|
|
10313
|
+
* what measures malformed-output rate against context size rather than inferring it.
|
|
10314
|
+
*
|
|
10315
|
+
* Existing readers that only look at the fields above are unaffected.
|
|
10316
|
+
*/
|
|
10317
|
+
outputValidationError?: string;
|
|
10318
|
+
/**
|
|
10319
|
+
* The raw model output that failed validation, JSON-stringified and truncated to a bounded
|
|
10320
|
+
* length. Truncation is visible in the value itself (a trailing `…[truncated: N chars total]`),
|
|
10321
|
+
* never silent. Present only alongside `outputValidationError`.
|
|
10322
|
+
*/
|
|
10323
|
+
unvalidatedOutput?: string;
|
|
10324
|
+
/**
|
|
10325
|
+
* Why this call went out without `strict` structured output, on an adapter that tried to send it
|
|
10326
|
+
* with one. Present ONLY on a refusal — its absence on a row from a strict-capable adapter means
|
|
10327
|
+
* strict was in effect.
|
|
10328
|
+
*
|
|
10329
|
+
* This is the row that answers "is this agent's output actually being enforced?" without reading
|
|
10330
|
+
* source. Counting rows that carry it, per resource, is the refusal count: a redeployed agent on
|
|
10331
|
+
* a current bundle should read zero. It exists because the previous answer was a dev-only flow
|
|
10332
|
+
* log, which meant production had no answer — and every sync-managed tenant ran unstrict against
|
|
10333
|
+
* a strict-capable API until a 14-turn session surfaced a pre-strict error signature.
|
|
10334
|
+
*
|
|
10335
|
+
* Existing readers that only look at the fields above are unaffected.
|
|
10336
|
+
*/
|
|
10337
|
+
strictRefusalReasons?: string[];
|
|
10158
10338
|
}
|
|
10159
10339
|
type AICallContext = AgentReasoningContext | AgentCompletionContext | WorkflowStepContext | ToolCallContext | OtherCallContext;
|
|
10160
10340
|
interface AgentReasoningContext {
|
|
@@ -10200,6 +10380,18 @@ interface LLMUsageData {
|
|
|
10200
10380
|
latencyMs: number;
|
|
10201
10381
|
/** Actual cost from provider in USD (when available, e.g., OpenRouter) */
|
|
10202
10382
|
cost?: number;
|
|
10383
|
+
/** Distinct prompt-injection pattern types detected in the request's user-role messages */
|
|
10384
|
+
inputWarnings?: string[];
|
|
10385
|
+
/** Additive per-source breakdown of `inputWarnings` — see `SourcedInputWarnings` */
|
|
10386
|
+
inputWarningsBySource?: SourcedInputWarnings[];
|
|
10387
|
+
/** True when the sanitizer blocked the request and no provider call was made */
|
|
10388
|
+
inputBlocked?: boolean;
|
|
10389
|
+
/** Validator message when the provider responded but the output failed `responseSchema` validation */
|
|
10390
|
+
outputValidationError?: string;
|
|
10391
|
+
/** Raw model output that failed validation — JSON-stringified, truncated, truncation marked inline */
|
|
10392
|
+
unvalidatedOutput?: string;
|
|
10393
|
+
/** Why the call went out unstrict, when a strict-capable adapter refused the schema */
|
|
10394
|
+
strictRefusalReasons?: string[];
|
|
10203
10395
|
}
|
|
10204
10396
|
interface AIUsageSummary {
|
|
10205
10397
|
model: LLMModel;
|