@gajae-code/agent-core 0.11.1 → 0.11.3

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/CHANGELOG.md CHANGED
@@ -2,10 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.11.3] - 2026-07-19
6
+
7
+ ### Fixed
8
+ - Pre-compaction pruning now preserves bounded, actionable error evidence instead of discarding it, while enforcing exact positive-savings admission and accounting so a prune is only applied when it demonstrably reduces context cost (#2635).
9
+
5
10
  ## [0.11.1] - 2026-07-16
6
11
 
7
12
  ### Fixed
8
13
 
14
+ - Hardened the managed fallback attempt snapshot: staged agent events and assistant partials were cloned with a bare `structuredClone`, so a single non-cloneable value in a staged payload (e.g. a live `Headers` inside `transportFailure`) threw `DataCloneError` ("The object can not be cloned."), masked the real provider outcome, and deterministically failed every attempt until the fallback chain exhausted. The snapshot now degrades to a cycle-aware sanitizing deep clone that always returns a detached, JSON-serializable value (unsupported leaves become placeholders), so event-time replay semantics are preserved and no local snapshot failure can masquerade as a provider attempt failure. Byte accounting in the provisional buffer measures the raw event before the snapshot duplicates it (over-limit payloads are rejected pre-clone), re-measures degraded snapshots so the retained sanitized form is what gets accounted, and uses the sanitized detached form as the cycle-safe estimator for cyclic payloads.
15
+ - Enforced the managed fallback authority boundary for local staging failures: `ManagedAttemptBufferOverflowError` no longer carries a synthetic provider-like `503` status, so exceeding the provisional event buffer limit (like any other local snapshot failure) is non-retryable, never converts into `transportFailure { kind: "transport", status: 503 }` evidence, and never rotates or consumes the model fallback chain — it surfaces as an explicit local error instead. Only original typed provider transport facts may authorize provider fallback.
9
16
  - Added a bounded, neutralize-only `invalid_prompt` circuit breaker to the agent loop (#2282). A poisoned-history rejection (`Request blocked (code=invalid_prompt)`) is a deterministic content fault: re-sending the same history re-triggers it, so uncontrolled session auto-retry would burn its budget re-poisoning the model. On the first `invalid_prompt` of a run, leaked reserved control tokens are neutralized in place across history (no item is ever dropped). If that changes the outgoing bytes, the turn is resent exactly once with the repaired history; if neutralization cannot change anything, the run fails fast immediately with no resend. The repaired history is persisted for a clean resume, the breaker fires at most once per run (budget = one repaired resend), and it is scoped to the non-managed session path since managed fallback owns its own retry policy.
10
17
 
11
18
  ## [0.10.2] - 2026-07-14
@@ -27,6 +27,50 @@ export declare function agentLoop(prompts: AgentMessage[], context: AgentContext
27
27
  * This cannot be validated here since `convertToLlm` is only called once per turn.
28
28
  */
29
29
  export declare function agentLoopContinue(context: AgentContext, config: AgentLoopConfig, signal?: AbortSignal, streamFn?: StreamFn, emitManagedAgentStart?: boolean): EventStream<AgentEvent, AgentMessage[]>;
30
+ /**
31
+ * Hard work budget for one degraded snapshot: every visited node AND every
32
+ * enumerated own key is debited against this budget before it is processed
33
+ * (accessor keys and re-visits of shared objects included), and any remainder
34
+ * collapses to the deterministic `"[truncated]"` placeholder. Well above
35
+ * ordinary streamed events; it only bounds hostile graphs.
36
+ */
37
+ export declare const MANAGED_SNAPSHOT_MAX_NODES = 100000;
38
+ /**
39
+ * Cycle-aware deep clone that always returns a detached, JSON-serializable
40
+ * value. Used whenever a detached snapshot cannot be safely obtained or
41
+ * measured: after `structuredClone` fails, and again when a (successfully
42
+ * cloned) snapshot cannot be serialized for byte accounting.
43
+ *
44
+ * Totality rules — the walk must never dispatch through payload-controlled
45
+ * code, throw, or do unbounded work:
46
+ * - proxies (revoked or live) are collapsed to `"[unserializable]"` BEFORE
47
+ * any reflective operation, so `ownKeys`/descriptor traps are never
48
+ * dispatched (`util.types.isProxy` identifies proxies without touching
49
+ * their handlers);
50
+ * - only intrinsics are used on the remaining ordinary objects (no
51
+ * `input.map`, no `input.getTime()`, no `input.length` reads);
52
+ * - arrays are enumerated through their own present keys, never their
53
+ * declared length, so a sparse array cannot force a dense allocation
54
+ * proportional to `length`; sparse/exotic arrays degrade to a null-proto
55
+ * record of their present indices, and the dense-shape decision verifies
56
+ * every index against its ordinal;
57
+ * - the walk debits `maxNodes` budget per visited node and per enumerated
58
+ * key before processing it; anything beyond the budget becomes
59
+ * `"[truncated]"` (the one linear primitive per visited node is a single
60
+ * `Object.keys` call on a non-proxy object the process already holds);
61
+ * - property values are read via own-property descriptors, so accessors are
62
+ * never invoked (a snapshot must not cause observable side effects) and are
63
+ * replaced with `"[accessor]"`;
64
+ * - functions/symbols and any property that cannot be read safely become
65
+ * short placeholders, `bigint` becomes its decimal string, and references
66
+ * back into the current path collapse to `"[Circular]"`;
67
+ * - records are built on a null prototype so a `__proto__` key cannot mutate
68
+ * the clone's prototype chain.
69
+ *
70
+ * Exported for direct regression coverage of the budget accounting; runtime
71
+ * callers use the default budget via {@link managedAttemptSnapshot}.
72
+ */
73
+ export declare function sanitizedDetachedClone<T>(value: T, maxNodes?: number): T;
30
74
  /**
31
75
  * Detailed-result handle returned by {@link agentLoopDetailed}. Adds the
32
76
  * run-level telemetry/coverage rollup to the existing `AgentMessage[]`
@@ -184,6 +184,10 @@ export interface AgentPromptOptions {
184
184
  /** Receives a discarded managed attempt without exposing assistant lifecycle events. */
185
185
  onManagedAttemptOutcome?: AgentLoopConfig["onManagedAttemptOutcome"];
186
186
  }
187
+ export type AgentQueueSnapshot = {
188
+ steering: AgentMessage[];
189
+ followUp: AgentMessage[];
190
+ };
187
191
  export declare class Agent {
188
192
  #private;
189
193
  get intentTracing(): boolean;
@@ -376,6 +380,10 @@ export declare class Agent {
376
380
  snapshotFollowUp(): AgentMessage[];
377
381
  /** Restore previously snapshotted follow-up messages ahead of any newly queued ones. */
378
382
  restoreFollowUp(messages: AgentMessage[]): void;
383
+ /** Snapshot both executable queues as one atomic session-level view. */
384
+ snapshotQueues(): AgentQueueSnapshot;
385
+ /** Replace both executable queues with a prior snapshot. */
386
+ restoreQueues(snapshot: AgentQueueSnapshot): void;
379
387
  /**
380
388
  * Remove and return the last steering message from the queue (LIFO).
381
389
  * Used by dequeue keybinding.
@@ -192,8 +192,7 @@ export interface SummaryOptions {
192
192
  /**
193
193
  * Optional telemetry handle. When provided, every LLM call emitted during
194
194
  * compaction is wrapped in an OTEL chat span tagged with
195
- * `pi.gen_ai.oneshot.kind` (`compaction_summary`, `compaction_short_summary`,
196
- * or `compaction_turn_prefix`). `undefined` keeps the call paths zero-cost.
195
+ * `pi.gen_ai.oneshot.kind` (`compaction_summary` or `compaction_turn_prefix`).
197
196
  */
198
197
  telemetry?: AgentTelemetry;
199
198
  authCredentialType?: "api_key" | "oauth";
@@ -301,6 +300,11 @@ export interface PrepareCompactionOptions {
301
300
  * (the confounded raw promptTokens/estimatedTokens quotient is never used).
302
301
  */
303
302
  tokenCorrectionRatio?: number;
303
+ /**
304
+ * Model context-window size. Windows below 66k retain the legacy fixed
305
+ * keepRecentTokens behavior; larger windows scale the keep window to 30%.
306
+ */
307
+ contextWindow?: number;
304
308
  }
305
309
  export declare function prepareCompaction(pathEntries: SessionEntry[], settings: CompactionSettings, options?: PrepareCompactionOptions): CompactionPreparation | undefined;
306
310
  /**
@@ -49,7 +49,7 @@ export declare function pruneAssistantToolArguments(entries: SessionEntry[], con
49
49
  * mutating any entry. Returns 0 savings when below the configured minimum so the
50
50
  * caller sees the same gate the real prune enforces.
51
51
  */
52
- export declare function estimateToolOutputPruneSavings(entries: SessionEntry[], config?: PruneConfig): {
52
+ export declare function estimateToolOutputPruneSavings(entries: SessionEntry[], config?: PruneConfig, options?: PruneToolOutputsOptions): {
53
53
  prunableCount: number;
54
54
  tokensSaved: number;
55
55
  };
@@ -66,4 +66,8 @@ export declare function shouldRunMaintenancePrune(args: {
66
66
  minSavings: number;
67
67
  cacheEpochResetCost: number;
68
68
  }): boolean;
69
- export declare function pruneToolOutputs(entries: SessionEntry[], config?: PruneConfig): PruneResult;
69
+ export interface PruneToolOutputsOptions {
70
+ /** Lower the usual minimum only when the caller is already over its compaction threshold. */
71
+ relaxedMinimum?: number;
72
+ }
73
+ export declare function pruneToolOutputs(entries: SessionEntry[], config?: PruneConfig, options?: PruneToolOutputsOptions): PruneResult;
@@ -33,6 +33,17 @@ export type ProxyAssistantMessageEvent = {
33
33
  type: "thinking_end";
34
34
  contentIndex: number;
35
35
  contentSignature?: string;
36
+ } | {
37
+ type: "reasoning_summary_start";
38
+ contentIndex: number;
39
+ } | {
40
+ type: "reasoning_summary_delta";
41
+ contentIndex: number;
42
+ delta: string;
43
+ } | {
44
+ type: "reasoning_summary_end";
45
+ contentIndex: number;
46
+ content?: string;
36
47
  } | {
37
48
  type: "toolcall_start";
38
49
  contentIndex: number;
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, Effort, ImageContent, Message, Model, SimpleStreamOptions, Static, streamSimple, TextContent, Tool, ToolChoice, ToolResultMessage, TSchema } from "@gajae-code/ai";
1
+ import type { AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, Effort, ImageContent, Message, Model, SimpleStreamOptions, Static, streamSimple, TextContent, Tool, ToolChoice, ToolResultMessage, TransportFailureFacts, TSchema } from "@gajae-code/ai";
2
2
  import type { AppendOnlyContextManager } from "./append-only-context";
3
3
  import type { HarmonyAuditEvent } from "./harmony-leak";
4
4
  import type { AgentRunCoverage, AgentRunSummary } from "./run-collector";
@@ -35,6 +35,9 @@ export type ManagedAttemptContinuation = (ownership: ManagedAttemptContinuationO
35
35
  export type ManagedAttemptDecision = {
36
36
  type: "retry";
37
37
  continuation: ManagedAttemptContinuation;
38
+ } | {
39
+ type: "maintenance";
40
+ continuation: ManagedAttemptContinuation;
38
41
  } | {
39
42
  type: "terminal";
40
43
  terminal: RunTerminalRequest;
@@ -45,8 +48,11 @@ export type ManagedAttemptOutcome = {
45
48
  failure: {
46
49
  message: AssistantMessage;
47
50
  /** Exact provider transport facts, including retry headers, for fallback policy. */
48
- transportFailure?: import("@gajae-code/ai").TransportFailureFacts;
51
+ transportFailure?: TransportFailureFacts;
49
52
  };
53
+ } | {
54
+ type: "context_overflow_discarded";
55
+ message: AssistantMessage;
50
56
  } | {
51
57
  type: "run_terminal";
52
58
  reason: "cancelled" | "error" | "exhausted";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/agent-core",
4
- "version": "0.11.1",
4
+ "version": "0.11.3",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -32,9 +32,9 @@
32
32
  "fmt": "biome format --write ."
33
33
  },
34
34
  "dependencies": {
35
- "@gajae-code/ai": "0.11.1",
36
- "@gajae-code/natives": "0.11.1",
37
- "@gajae-code/utils": "0.11.1",
35
+ "@gajae-code/ai": "0.11.3",
36
+ "@gajae-code/natives": "0.11.3",
37
+ "@gajae-code/utils": "0.11.3",
38
38
  "@opentelemetry/api": "^1.9.0"
39
39
  },
40
40
  "devDependencies": {