@superblocksteam/vite-plugin-file-sync 2.0.130-next.1 → 2.0.130-next.2

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.
Files changed (120) hide show
  1. package/dist/ai-service/agent/tool-message-utils.d.ts.map +1 -1
  2. package/dist/ai-service/agent/tool-message-utils.js +30 -0
  3. package/dist/ai-service/agent/tool-message-utils.js.map +1 -1
  4. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts +8 -0
  5. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.d.ts.map +1 -1
  6. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js +94 -4
  7. package/dist/ai-service/agent/tools2/tools/exit-plan-mode.js.map +1 -1
  8. package/dist/ai-service/agent/utils.d.ts.map +1 -1
  9. package/dist/ai-service/agent/utils.js +6 -1
  10. package/dist/ai-service/agent/utils.js.map +1 -1
  11. package/dist/ai-service/app-skills/helpers.d.ts.map +1 -1
  12. package/dist/ai-service/app-skills/helpers.js +17 -18
  13. package/dist/ai-service/app-skills/helpers.js.map +1 -1
  14. package/dist/ai-service/chat/chat-push-metrics.d.ts +12 -0
  15. package/dist/ai-service/chat/chat-push-metrics.d.ts.map +1 -0
  16. package/dist/ai-service/chat/chat-push-metrics.js +68 -0
  17. package/dist/ai-service/chat/chat-push-metrics.js.map +1 -0
  18. package/dist/ai-service/chat/chat-session-store.d.ts +61 -5
  19. package/dist/ai-service/chat/chat-session-store.d.ts.map +1 -1
  20. package/dist/ai-service/chat/chat-session-store.js +214 -8
  21. package/dist/ai-service/chat/chat-session-store.js.map +1 -1
  22. package/dist/ai-service/chat/transcript-budget.d.ts +108 -0
  23. package/dist/ai-service/chat/transcript-budget.d.ts.map +1 -0
  24. package/dist/ai-service/chat/transcript-budget.js +175 -0
  25. package/dist/ai-service/chat/transcript-budget.js.map +1 -0
  26. package/dist/ai-service/chat/transcript-metrics.d.ts +16 -0
  27. package/dist/ai-service/chat/transcript-metrics.d.ts.map +1 -0
  28. package/dist/ai-service/chat/transcript-metrics.js +128 -0
  29. package/dist/ai-service/chat/transcript-metrics.js.map +1 -0
  30. package/dist/ai-service/index.d.ts.map +1 -1
  31. package/dist/ai-service/index.js +4 -0
  32. package/dist/ai-service/index.js.map +1 -1
  33. package/dist/ai-service/llm/clark-stream-telemetry-sink.d.ts +4 -0
  34. package/dist/ai-service/llm/clark-stream-telemetry-sink.d.ts.map +1 -0
  35. package/dist/ai-service/llm/clark-stream-telemetry-sink.js +105 -0
  36. package/dist/ai-service/llm/clark-stream-telemetry-sink.js.map +1 -0
  37. package/dist/ai-service/llm/client.d.ts.map +1 -1
  38. package/dist/ai-service/llm/client.js +63 -39
  39. package/dist/ai-service/llm/client.js.map +1 -1
  40. package/dist/ai-service/llm/context-v2/compaction/client-side.d.ts.map +1 -1
  41. package/dist/ai-service/llm/context-v2/compaction/client-side.js +10 -1
  42. package/dist/ai-service/llm/context-v2/compaction/client-side.js.map +1 -1
  43. package/dist/ai-service/llm/context-v2/context.d.ts.map +1 -1
  44. package/dist/ai-service/llm/context-v2/context.js +43 -2
  45. package/dist/ai-service/llm/context-v2/context.js.map +1 -1
  46. package/dist/ai-service/llm/context-v2/storage/event-types.d.ts +10 -1
  47. package/dist/ai-service/llm/context-v2/storage/event-types.d.ts.map +1 -1
  48. package/dist/ai-service/llm/context-v2/storage/event-types.js +7 -1
  49. package/dist/ai-service/llm/context-v2/storage/event-types.js.map +1 -1
  50. package/dist/ai-service/llm/stream/config.d.ts +30 -0
  51. package/dist/ai-service/llm/stream/config.d.ts.map +1 -1
  52. package/dist/ai-service/llm/stream/config.js +34 -17
  53. package/dist/ai-service/llm/stream/config.js.map +1 -1
  54. package/dist/ai-service/llm/stream/errors.d.ts +10 -1
  55. package/dist/ai-service/llm/stream/errors.d.ts.map +1 -1
  56. package/dist/ai-service/llm/stream/errors.js +3 -1
  57. package/dist/ai-service/llm/stream/errors.js.map +1 -1
  58. package/dist/ai-service/llm/stream/idle-monitor.d.ts +13 -4
  59. package/dist/ai-service/llm/stream/idle-monitor.d.ts.map +1 -1
  60. package/dist/ai-service/llm/stream/idle-monitor.js +28 -6
  61. package/dist/ai-service/llm/stream/idle-monitor.js.map +1 -1
  62. package/dist/ai-service/llm/stream/index.d.ts +1 -1
  63. package/dist/ai-service/llm/stream/index.d.ts.map +1 -1
  64. package/dist/ai-service/llm/stream/index.js.map +1 -1
  65. package/dist/ai-service/llm/stream/managed-stream.d.ts.map +1 -1
  66. package/dist/ai-service/llm/stream/managed-stream.js +1 -0
  67. package/dist/ai-service/llm/stream/managed-stream.js.map +1 -1
  68. package/dist/ai-service/llm/stream/orchestrator.d.ts +19 -0
  69. package/dist/ai-service/llm/stream/orchestrator.d.ts.map +1 -1
  70. package/dist/ai-service/llm/stream/orchestrator.js +277 -29
  71. package/dist/ai-service/llm/stream/orchestrator.js.map +1 -1
  72. package/dist/ai-service/llm/stream/retry-engine.d.ts +15 -0
  73. package/dist/ai-service/llm/stream/retry-engine.d.ts.map +1 -1
  74. package/dist/ai-service/llm/stream/retry-engine.js +55 -2
  75. package/dist/ai-service/llm/stream/retry-engine.js.map +1 -1
  76. package/dist/ai-service/llm/stream/session.d.ts +3 -0
  77. package/dist/ai-service/llm/stream/session.d.ts.map +1 -1
  78. package/dist/ai-service/llm/stream/session.js +13 -0
  79. package/dist/ai-service/llm/stream/session.js.map +1 -1
  80. package/dist/ai-service/llm/stream/wait-state-tracker.d.ts +54 -0
  81. package/dist/ai-service/llm/stream/wait-state-tracker.d.ts.map +1 -0
  82. package/dist/ai-service/llm/stream/wait-state-tracker.js +105 -0
  83. package/dist/ai-service/llm/stream/wait-state-tracker.js.map +1 -0
  84. package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
  85. package/dist/ai-service/state-machine/clark-fsm.js +13 -7
  86. package/dist/ai-service/state-machine/clark-fsm.js.map +1 -1
  87. package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
  88. package/dist/ai-service/state-machine/handlers/agent-planning.js +55 -43
  89. package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
  90. package/dist/ai-service/state-machine/handlers/flush-pending-chat-pushes.d.ts +11 -0
  91. package/dist/ai-service/state-machine/handlers/flush-pending-chat-pushes.d.ts.map +1 -0
  92. package/dist/ai-service/state-machine/handlers/flush-pending-chat-pushes.js +11 -0
  93. package/dist/ai-service/state-machine/handlers/flush-pending-chat-pushes.js.map +1 -0
  94. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
  95. package/dist/ai-service/state-machine/handlers/llm-generating.js +10 -0
  96. package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
  97. package/dist/ai-service/state-machine/helpers/background-chat-record-metrics.d.ts +8 -0
  98. package/dist/ai-service/state-machine/helpers/background-chat-record-metrics.d.ts.map +1 -0
  99. package/dist/ai-service/state-machine/helpers/background-chat-record-metrics.js +52 -0
  100. package/dist/ai-service/state-machine/helpers/background-chat-record-metrics.js.map +1 -0
  101. package/dist/ai-service/state-machine/helpers/background-chat-record.d.ts +33 -0
  102. package/dist/ai-service/state-machine/helpers/background-chat-record.d.ts.map +1 -0
  103. package/dist/ai-service/state-machine/helpers/background-chat-record.js +61 -0
  104. package/dist/ai-service/state-machine/helpers/background-chat-record.js.map +1 -0
  105. package/dist/ai-service/state-machine/mocks.d.ts.map +1 -1
  106. package/dist/ai-service/state-machine/mocks.js +1 -0
  107. package/dist/ai-service/state-machine/mocks.js.map +1 -1
  108. package/dist/ai-service/tasks/peer-push.d.ts +7 -4
  109. package/dist/ai-service/tasks/peer-push.d.ts.map +1 -1
  110. package/dist/ai-service/tasks/peer-push.js +7 -4
  111. package/dist/ai-service/tasks/peer-push.js.map +1 -1
  112. package/dist/migration-templates/app-fullstack/package.json +1 -1
  113. package/dist/socket-manager.d.ts +2 -1
  114. package/dist/socket-manager.d.ts.map +1 -1
  115. package/dist/socket-manager.js +17 -6
  116. package/dist/socket-manager.js.map +1 -1
  117. package/dist/util/logger.d.ts +1 -0
  118. package/dist/util/logger.d.ts.map +1 -1
  119. package/dist/util/logger.js.map +1 -1
  120. package/package.json +8 -8
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Why the push failed: `timeout` means the per-push bound elapsed (the RPC
3
+ * may still complete later — see `ChatSessionStore.PUSH_TIMEOUT_MS`);
4
+ * `rpc_error` means the call itself rejected.
5
+ */
6
+ export declare const CHAT_PUSH_FAILURE_REASONS: readonly ["rpc_error", "timeout"];
7
+ export type ChatPushFailureReason = (typeof CHAT_PUSH_FAILURE_REASONS)[number];
8
+ export declare function recordChatPushFailure(input: {
9
+ reason: ChatPushFailureReason;
10
+ }): void;
11
+ export declare function _resetChatPushMetricsForTesting(): void;
12
+ //# sourceMappingURL=chat-push-metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-push-metrics.d.ts","sourceRoot":"","sources":["../../../src/ai-service/chat/chat-push-metrics.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,mCAAoC,CAAC;AAC3E,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AA4C/E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,MAAM,EAAE,qBAAqB,CAAC;CAC/B,GAAG,IAAI,CAEP;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAEtD"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Chat-push failure metrics (APPS-4729).
3
+ *
4
+ * Chat pushes became fire-and-forget when the stream loop stopped awaiting
5
+ * `commitMessage`, so a failing or timing-out push no longer surfaces as a
6
+ * stalled generation — it surfaces only as an error log. This counter makes
7
+ * the failure *rate* alertable (e.g. "N% of pushes timing out over 5m"),
8
+ * which log-based monitors track too lazily for an on-call signal.
9
+ *
10
+ * Wiring follows `ClarkContextMetrics` (APPS-4708): the meter resolves
11
+ * lazily from the OTel global and the instrument re-binds when the global
12
+ * MeterProvider changes (tests swap providers per case). Zero-config process
13
+ * singleton — no DI plumbing.
14
+ *
15
+ * Metric:
16
+ *
17
+ * - `superblocks.clark.chat.push_failures` counter {reason}
18
+ *
19
+ * Cardinality budget: reason ≤ 2 → 2 worst-case series. The label is a
20
+ * compile-time closed union supplied by Superblocks-owned call sites (never
21
+ * derived from user input or error text).
22
+ */
23
+ import { metrics } from "@opentelemetry/api";
24
+ /**
25
+ * Why the push failed: `timeout` means the per-push bound elapsed (the RPC
26
+ * may still complete later — see `ChatSessionStore.PUSH_TIMEOUT_MS`);
27
+ * `rpc_error` means the call itself rejected.
28
+ */
29
+ export const CHAT_PUSH_FAILURE_REASONS = ["rpc_error", "timeout"];
30
+ const METER_NAME = "superblocks.clark.chat";
31
+ const METRIC_PUSH_FAILURES = "superblocks.clark.chat.push_failures";
32
+ class ChatPushMetrics {
33
+ cachedMeter;
34
+ cachedCounter;
35
+ /**
36
+ * Lazy instrument factory. Re-creates the counter when the global
37
+ * MeterProvider changes (a stale instrument bound to a replaced provider
38
+ * silently drops data).
39
+ */
40
+ getCounter() {
41
+ const meter = metrics.getMeter(METER_NAME);
42
+ if (meter === this.cachedMeter && this.cachedCounter) {
43
+ return this.cachedCounter;
44
+ }
45
+ this.cachedMeter = meter;
46
+ this.cachedCounter = meter.createCounter(METRIC_PUSH_FAILURES, {
47
+ description: "Chat-history pushes that failed or exceeded the per-push timeout after the stream loop stopped awaiting them (APPS-4729).",
48
+ unit: "{failure}",
49
+ });
50
+ return this.cachedCounter;
51
+ }
52
+ recordFailure(input) {
53
+ this.getCounter().add(1, { reason: input.reason });
54
+ }
55
+ /** Drop cached instrument bindings (tests swap the global provider). */
56
+ _resetForTesting() {
57
+ this.cachedMeter = undefined;
58
+ this.cachedCounter = undefined;
59
+ }
60
+ }
61
+ const chatPushMetrics = new ChatPushMetrics();
62
+ export function recordChatPushFailure(input) {
63
+ chatPushMetrics.recordFailure(input);
64
+ }
65
+ export function _resetChatPushMetricsForTesting() {
66
+ chatPushMetrics._resetForTesting();
67
+ }
68
+ //# sourceMappingURL=chat-push-metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-push-metrics.js","sourceRoot":"","sources":["../../../src/ai-service/chat/chat-push-metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,OAAO,EAAc,MAAM,oBAAoB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,WAAW,EAAE,SAAS,CAAU,CAAC;AAG3E,MAAM,UAAU,GAAG,wBAAwB,CAAC;AAE5C,MAAM,oBAAoB,GAAG,sCAAsC,CAAC;AAIpE,MAAM,eAAe;IACX,WAAW,CAAoB;IAC/B,aAAa,CAAsB;IAE3C;;;;OAIG;IACK,UAAU;QAChB,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC3C,IAAI,KAAK,KAAK,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACrD,OAAO,IAAI,CAAC,aAAa,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC,oBAAoB,EAAE;YAC7D,WAAW,EACT,2HAA2H;YAC7H,IAAI,EAAE,WAAW;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,aAAa,CAAC,KAAwC;QACpD,IAAI,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,wEAAwE;IACxE,gBAAgB;QACd,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;QAC7B,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;IACjC,CAAC;CACF;AAED,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;AAE9C,MAAM,UAAU,qBAAqB,CAAC,KAErC;IACC,eAAe,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,+BAA+B;IAC7C,eAAe,CAAC,gBAAgB,EAAE,CAAC;AACrC,CAAC"}
@@ -3,6 +3,7 @@ import type { AiPromptCostUsageEntry, AiWorkflowEvent } from "@superblocksteam/s
3
3
  import type { AutoConnectingRpcClient } from "../../server-rpc/index.js";
4
4
  import { type Logger } from "../../util/logger.js";
5
5
  import type { LLMObsTracer } from "../llmobs/tracer.js";
6
+ import { type HeapMonitor } from "./transcript-budget.js";
6
7
  export type PromptCostUsageEntry = AiPromptCostUsageEntry;
7
8
  export type AiChatItem = AiChatMessage | AiChatSummary;
8
9
  export type AiChatSummaryWithOutcome = AiChatSummary & {
@@ -15,6 +16,12 @@ export declare function isAiChatSummary(item: AiChatItem): item is AiChatSummary
15
16
  * This prevents large requests from failing when there are many messages.
16
17
  */
17
18
  export declare const DEFAULT_MESSAGE_LIMIT = 3000;
19
+ export interface TranscriptBudgetConfig {
20
+ maxResidentBytes?: number;
21
+ maxResidentItems?: number;
22
+ minResidentItems?: number;
23
+ heapMonitor?: HeapMonitor;
24
+ }
18
25
  interface ChatSessionStoreConfig {
19
26
  applicationId: string;
20
27
  rpcClient: AutoConnectingRpcClient;
@@ -26,6 +33,11 @@ interface ChatSessionStoreConfig {
26
33
  messageLimit?: number;
27
34
  /** Injectable for tests; defaults to the LLMObs singleton. */
28
35
  tracer?: LLMObsTracer;
36
+ /**
37
+ * Bounds on the in-heap transcript. Defaults: see
38
+ * transcript-budget.ts; the count cap defaults to messageLimit.
39
+ */
40
+ transcriptBudget?: TranscriptBudgetConfig;
29
41
  }
30
42
  export declare class ChatSessionStore {
31
43
  private applicationId;
@@ -37,6 +49,17 @@ export declare class ChatSessionStore {
37
49
  private loadedFromServer;
38
50
  private inProgressMessages;
39
51
  private messageLimit;
52
+ private transcriptBudget;
53
+ /**
54
+ * Kill-switch for the in-heap transcript budget. When set, the transcript
55
+ * grows unbounded as it did before the fix — a remediation lever if
56
+ * eviction ever misbehaves in production. Read once at startup, so toggling
57
+ * the env var needs a pod restart (no code deploy or rebuild) to take
58
+ * effect.
59
+ */
60
+ private readonly transcriptBudgetDisabled;
61
+ private lastEvictionWarnAt;
62
+ private lastEvictionWarnTrigger;
40
63
  constructor(config: ChatSessionStoreConfig);
41
64
  /***
42
65
  * Validates and sanitizes the attachments array
@@ -89,13 +112,20 @@ export declare class ChatSessionStore {
89
112
  commitMessage(id: string, type: "text" | "reasoning", group?: string): Promise<void>;
90
113
  recordAssistant(payload: AiChatMessage): Promise<void>;
91
114
  recordAssistantChangeInfo(payload: AiChangeInfoPush): Promise<void>;
115
+ private pushChain;
116
+ private static readonly PUSH_TIMEOUT_MS;
117
+ private static readonly PUSH_TIMEOUT_MESSAGE;
118
+ private appendMessage;
92
119
  /**
93
- * Every recorded chat message funnels through here; the server push is an
94
- * RPC that fires several times per turn, so it runs inside a `chat.record`
95
- * span (APPS-4708). Push failures stay swallowed losing a chat record
96
- * must never fail the turn that produced it.
120
+ * Resolves when every push queued so far has completed (or failed/timed
121
+ * out). Never rejects. Callers use this to make chat history durable at a
122
+ * boundary (end of generation) without coupling stream consumption to the
123
+ * RPC socket (APPS-4729).
97
124
  */
98
- private appendMessage;
125
+ flushPendingPushes(): Promise<void>;
126
+ private boundedAppendMessage;
127
+ private static isPushTimeoutError;
128
+ private doAppendMessage;
99
129
  submitWorkflowSummary(summary: AiWorkflowEvent): Promise<void>;
100
130
  calculatePromptCost(promptId: string, usage: AiPromptCostUsageEntry[]): Promise<number>;
101
131
  private determineSummaryOutcome;
@@ -114,6 +144,32 @@ export declare class ChatSessionStore {
114
144
  * Used when checkpoint is restored to ensure ineligible summaries are filtered out
115
145
  */
116
146
  invalidateCache(): void;
147
+ /**
148
+ * Reset everything tied to the resident transcript for a fresh session
149
+ * (newChat) or a cache invalidation (checkpoint restore): the loaded flag,
150
+ * the message array, the byte accounting, and the eviction-warn throttle.
151
+ * The throttle must reset too, otherwise a fresh transcript that evicts on
152
+ * the same trigger within the throttle window would skip its first
153
+ * chatTranscriptEvicted warn even though it is a different session.
154
+ */
155
+ private resetTranscriptState;
156
+ /** Resident-transcript accounting, exposed for telemetry. */
157
+ getTranscriptStats(): {
158
+ residentBytes: number;
159
+ residentCount: number;
160
+ };
161
+ /**
162
+ * Drop the oldest evictable transcript entries when over the byte budget,
163
+ * over the count cap, or under live heap pressure. The durable
164
+ * transcript stays server-side; this only bounds the in-heap copy.
165
+ */
166
+ private enforceTranscriptBudget;
167
+ /**
168
+ * Throttle the eviction warn to one line per trigger per window. A trigger
169
+ * change always warns (meaningful transition); repeated same-trigger
170
+ * evictions on every append collapse to one line per EVICTION_WARN_THROTTLE_MS.
171
+ */
172
+ private shouldWarnForEviction;
117
173
  }
118
174
  export {};
119
175
  //# sourceMappingURL=chat-session-store.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-session-store.d.ts","sourceRoot":"","sources":["../../../src/ai-service/chat/chat-session-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-session-store.d.ts","sourceRoot":"","sources":["../../../src/ai-service/chat/chat-session-store.ts"],"names":[],"mappings":"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"}
@@ -35,7 +35,10 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
35
35
  import { EXCLUDED_MESSAGE_TYPES, TEXT_ATTACHMENT_MIME_TYPES, } from "@superblocksteam/library-shared/types";
36
36
  import { getErrorMeta } from "../../util/logger.js";
37
37
  import llmobs from "../llmobs/index.js";
38
+ import { recordChatPushFailure } from "./chat-push-metrics.js";
38
39
  import { getLastUserMessage } from "./extract-history.js";
40
+ import { TranscriptBudget, } from "./transcript-budget.js";
41
+ import { recordTranscriptEviction, registerTranscriptResidentBytesSource, } from "./transcript-metrics.js";
39
42
  import { inferTextAttachmentType, sanitizeAiProviderResponse, sanitizeToolOutput, } from "./utils.js";
40
43
  export function isAiChatMessage(item) {
41
44
  return "role" in item && "content" in item;
@@ -53,6 +56,40 @@ function isEligibleSummary(item) {
53
56
  * This prevents large requests from failing when there are many messages.
54
57
  */
55
58
  export const DEFAULT_MESSAGE_LIMIT = 3000;
59
+ /**
60
+ * Minimum gap between `chatTranscriptEvicted` warn logs for the *same*
61
+ * trigger. The OTel counters fire on every eviction (cheap, cumulative); the
62
+ * warn is a low-frequency sentinel. A pathological session evicts on every
63
+ * append, so an unthrottled warn would emit thousands of lines per session.
64
+ * A trigger change is always logged (it is a meaningful state transition);
65
+ * repeated same-trigger evictions are collapsed to one line per window.
66
+ */
67
+ const EVICTION_WARN_THROTTLE_MS = 30_000;
68
+ /**
69
+ * Chat item types that are never evicted from the resident transcript.
70
+ * They are byte-tiny but semantically load-bearing as presence sentinels:
71
+ * commit markers feed getLatestCommitId (checkpoint restore), draft
72
+ * accept/reject feed determineSummaryOutcome, and aws_infra_demo gates the
73
+ * one-time demo card off "has this type ever appeared in history". Eviction
74
+ * is oldest-first and the demo sentinel is emitted on the first prompt, so
75
+ * without protection it is the first item dropped in exactly the long
76
+ * sessions this budget targets, which re-fires the demo card mid-session.
77
+ * Summaries are protected separately via isAiChatSummary.
78
+ */
79
+ const EVICTION_PROTECTED_TYPES = new Set([
80
+ "aws_infra_demo",
81
+ "commitRestored",
82
+ "commitRestoring",
83
+ "draftAccepted",
84
+ "draftCommitted",
85
+ "draftRejected",
86
+ ]);
87
+ function isEvictableChatItem(item) {
88
+ if (isAiChatSummary(item)) {
89
+ return false;
90
+ }
91
+ return item.type === undefined || !EVICTION_PROTECTED_TYPES.has(item.type);
92
+ }
56
93
  let ChatSessionStore = (() => {
57
94
  let _instanceExtraInitializers = [];
58
95
  let _getMessages_decorators;
@@ -81,12 +118,36 @@ let ChatSessionStore = (() => {
81
118
  loadedFromServer = "no";
82
119
  inProgressMessages = {};
83
120
  messageLimit;
121
+ transcriptBudget;
122
+ /**
123
+ * Kill-switch for the in-heap transcript budget. When set, the transcript
124
+ * grows unbounded as it did before the fix — a remediation lever if
125
+ * eviction ever misbehaves in production. Read once at startup, so toggling
126
+ * the env var needs a pod restart (no code deploy or rebuild) to take
127
+ * effect.
128
+ */
129
+ transcriptBudgetDisabled = process.env.CLARK_TRANSCRIPT_BUDGET_DISABLED === "true";
130
+ lastEvictionWarnAt = 0;
131
+ lastEvictionWarnTrigger = null;
84
132
  constructor(config) {
85
133
  this.rpcClient = config.rpcClient;
86
134
  this.applicationId = config.applicationId;
87
135
  this.messages = [];
88
136
  this.logger = config.logger;
89
137
  this.messageLimit = config.messageLimit ?? DEFAULT_MESSAGE_LIMIT;
138
+ this.transcriptBudget = new TranscriptBudget({
139
+ isEvictable: isEvictableChatItem,
140
+ maxResidentBytes: config.transcriptBudget?.maxResidentBytes,
141
+ // The count cap defaults to messageLimit, which the load-time filter
142
+ // already enforces; it is a coarse backstop for items appended past the
143
+ // limit mid-session, behind the byte and heap-pressure triggers.
144
+ maxResidentItems: config.transcriptBudget?.maxResidentItems ?? this.messageLimit,
145
+ minResidentItems: config.transcriptBudget?.minResidentItems,
146
+ heapMonitor: config.transcriptBudget?.heapMonitor,
147
+ });
148
+ // The observable gauge pulls resident bytes from the live budget every
149
+ // collect interval (one store per dev-server process; replace semantics).
150
+ registerTranscriptResidentBytesSource(() => this.transcriptBudget.residentBytes);
90
151
  this.tracer = config.tracer ?? llmobs;
91
152
  // NOTE: This is used to serialize messages to be after any load.
92
153
  // We initialize it as resolved to handle the prompt-from-empty-state case.
@@ -661,6 +722,9 @@ let ChatSessionStore = (() => {
661
722
  if (entities) {
662
723
  baseMessage.entities = entities;
663
724
  }
725
+ if (msg.includesFreshDatabase === true) {
726
+ baseMessage.includesFreshDatabase = true;
727
+ }
664
728
  }
665
729
  // Preserve commitId for commitRestored, commitRestoring, and draftCommitted messages
666
730
  if ((msg.type === "commitRestored" ||
@@ -783,6 +847,8 @@ let ChatSessionStore = (() => {
783
847
  .sort((a, b) => {
784
848
  return (a.timestamp || 0) - (b.timestamp || 0);
785
849
  }) ?? [];
850
+ this.transcriptBudget.reset(this.messages);
851
+ this.enforceTranscriptBudget();
786
852
  this.logger.info(`[ai-service] chatHistoryLoadSummary accepted=${this.messages.length} dropped=${droppedTotal} total=${fromRpcTotal}`);
787
853
  if (droppedTotal > 0) {
788
854
  this.logger.warn(`[ai-service] chatHistoryDroppedMessages count=${droppedTotal}`);
@@ -835,8 +901,7 @@ let ChatSessionStore = (() => {
835
901
  const applicationId = this.applicationId;
836
902
  return await client.call.v1.ai.chat.new({ applicationId });
837
903
  });
838
- this.loadedFromServer = "no";
839
- this.messages = [];
904
+ this.resetTranscriptState();
840
905
  this.logger.info("[ai-service] chatSessionNewSuccess");
841
906
  }
842
907
  catch (error) {
@@ -1002,13 +1067,71 @@ let ChatSessionStore = (() => {
1002
1067
  };
1003
1068
  await this.appendMessage(message);
1004
1069
  }
1070
+ // Remote persistence is strictly serialized through this chain so message
1071
+ // order is preserved even when callers do not await (APPS-4729). The chain
1072
+ // link is caught so one failed push can never wedge all later pushes.
1073
+ pushChain = Promise.resolve();
1074
+ // Bounds a single remote push. The underlying socket's noResponseTimeout is
1075
+ // 5 minutes (sized for judge evaluation calls); a chat push on a half-open
1076
+ // socket must fail much faster than that or it stalls everything queued
1077
+ // behind it (APPS-4729). The underlying RPC may still complete later because
1078
+ // rpcClient.call does not expose cancellation yet; this bounds the queue, not
1079
+ // the socket call itself. Worst case on a dead socket: each timed-out push
1080
+ // leaves one orphaned RPC await holding a reference to this store for up to
1081
+ // the socket's 5-minute noResponseTimeout. The chain serializes pushes, so
1082
+ // orphans spawn at most one per PUSH_TIMEOUT_MS — concurrency is capped at
1083
+ // ~noResponseTimeout / PUSH_TIMEOUT_MS (~15) regardless of how many blocks
1084
+ // a generation committed. They settle on socket close and hold no other
1085
+ // resources. TODO(APPS-4769): switch to AbortSignal once rpcClient.call
1086
+ // supports it.
1087
+ static PUSH_TIMEOUT_MS = 20_000;
1088
+ static PUSH_TIMEOUT_MESSAGE = "chat push timed out";
1089
+ appendMessage(message) {
1090
+ const result = this.pushChain.then(() => this.boundedAppendMessage(message));
1091
+ this.pushChain = result.catch(() => undefined);
1092
+ return result;
1093
+ }
1005
1094
  /**
1006
- * Every recorded chat message funnels through here; the server push is an
1007
- * RPC that fires several times per turn, so it runs inside a `chat.record`
1008
- * span (APPS-4708). Push failures stay swallowed losing a chat record
1009
- * must never fail the turn that produced it.
1095
+ * Resolves when every push queued so far has completed (or failed/timed
1096
+ * out). Never rejects. Callers use this to make chat history durable at a
1097
+ * boundary (end of generation) without coupling stream consumption to the
1098
+ * RPC socket (APPS-4729).
1010
1099
  */
1011
- async appendMessage(message) {
1100
+ async flushPendingPushes() {
1101
+ await this.pushChain.catch(() => undefined);
1102
+ }
1103
+ async boundedAppendMessage(message) {
1104
+ let timer;
1105
+ try {
1106
+ return await Promise.race([
1107
+ this.doAppendMessage(message),
1108
+ new Promise((_, reject) => {
1109
+ timer = setTimeout(() => reject(new Error(`${ChatSessionStore.PUSH_TIMEOUT_MESSAGE} after ${ChatSessionStore.PUSH_TIMEOUT_MS}ms`)), ChatSessionStore.PUSH_TIMEOUT_MS);
1110
+ }),
1111
+ ]);
1112
+ }
1113
+ catch (error) {
1114
+ const reason = ChatSessionStore.isPushTimeoutError(error)
1115
+ ? "timeout"
1116
+ : "rpc_error";
1117
+ recordChatPushFailure({ reason });
1118
+ this.logger.error("[ai-service] Chat message push failed or timed out", {
1119
+ ...getErrorMeta(error),
1120
+ reason,
1121
+ });
1122
+ return undefined;
1123
+ }
1124
+ finally {
1125
+ if (timer !== undefined) {
1126
+ clearTimeout(timer);
1127
+ }
1128
+ }
1129
+ }
1130
+ static isPushTimeoutError(error) {
1131
+ return (error instanceof Error &&
1132
+ error.message.startsWith(ChatSessionStore.PUSH_TIMEOUT_MESSAGE));
1133
+ }
1134
+ async doAppendMessage(message) {
1012
1135
  const messageType = "type" in message && typeof message.type === "string"
1013
1136
  ? message.type
1014
1137
  : "unknown";
@@ -1028,6 +1151,8 @@ let ChatSessionStore = (() => {
1028
1151
  const localMessageCountBefore = this.messages.length;
1029
1152
  this.logger.info(`[ai-service] chatMessagePushStart count=${localMessageCountBefore}`);
1030
1153
  this.messages.push(message);
1154
+ this.transcriptBudget.track(message);
1155
+ this.enforceTranscriptBudget();
1031
1156
  const payload = {
1032
1157
  ...message,
1033
1158
  };
@@ -1052,7 +1177,11 @@ let ChatSessionStore = (() => {
1052
1177
  return response.data?.chatmessageId;
1053
1178
  }
1054
1179
  catch (error) {
1055
- this.logger.error("[ai-service] Failed to write chat message to server", getErrorMeta(error));
1180
+ recordChatPushFailure({ reason: "rpc_error" });
1181
+ this.logger.error("[ai-service] Failed to write chat message to server", {
1182
+ ...getErrorMeta(error),
1183
+ reason: "rpc_error",
1184
+ });
1056
1185
  annotate({ pushed: false, error });
1057
1186
  return undefined;
1058
1187
  }
@@ -1124,8 +1253,85 @@ let ChatSessionStore = (() => {
1124
1253
  * Used when checkpoint is restored to ensure ineligible summaries are filtered out
1125
1254
  */
1126
1255
  invalidateCache() {
1256
+ this.resetTranscriptState();
1257
+ }
1258
+ /**
1259
+ * Reset everything tied to the resident transcript for a fresh session
1260
+ * (newChat) or a cache invalidation (checkpoint restore): the loaded flag,
1261
+ * the message array, the byte accounting, and the eviction-warn throttle.
1262
+ * The throttle must reset too, otherwise a fresh transcript that evicts on
1263
+ * the same trigger within the throttle window would skip its first
1264
+ * chatTranscriptEvicted warn even though it is a different session.
1265
+ */
1266
+ resetTranscriptState() {
1127
1267
  this.loadedFromServer = "no";
1128
1268
  this.messages = [];
1269
+ this.transcriptBudget.reset([]);
1270
+ this.lastEvictionWarnAt = 0;
1271
+ this.lastEvictionWarnTrigger = null;
1272
+ }
1273
+ /** Resident-transcript accounting, exposed for telemetry. */
1274
+ getTranscriptStats() {
1275
+ return {
1276
+ residentBytes: this.transcriptBudget.residentBytes,
1277
+ residentCount: this.messages.length,
1278
+ };
1279
+ }
1280
+ /**
1281
+ * Drop the oldest evictable transcript entries when over the byte budget,
1282
+ * over the count cap, or under live heap pressure. The durable
1283
+ * transcript stays server-side; this only bounds the in-heap copy.
1284
+ */
1285
+ enforceTranscriptBudget() {
1286
+ if (this.transcriptBudgetDisabled) {
1287
+ return;
1288
+ }
1289
+ const result = this.transcriptBudget.enforce(this.messages);
1290
+ // enforce() returns a non-null trigger whenever it evicts; bail otherwise.
1291
+ if (result.evictedCount === 0 || !result.trigger) {
1292
+ return;
1293
+ }
1294
+ const trigger = result.trigger;
1295
+ this.messages = result.kept;
1296
+ // Counter fires on every eviction; the warn below is throttled.
1297
+ recordTranscriptEviction({
1298
+ trigger,
1299
+ messages: result.evictedCount,
1300
+ bytes: result.evictedBytes,
1301
+ });
1302
+ if (!this.shouldWarnForEviction(trigger)) {
1303
+ return;
1304
+ }
1305
+ const attributes = {
1306
+ evicted_count: result.evictedCount,
1307
+ evicted_bytes: result.evictedBytes,
1308
+ resident_count: this.messages.length,
1309
+ resident_bytes: this.transcriptBudget.residentBytes,
1310
+ trigger,
1311
+ used_heap_bytes: result.usedHeapBytes,
1312
+ heap_limit_bytes: result.heapLimitBytes,
1313
+ };
1314
+ if (this.logger.warnStructured) {
1315
+ this.logger.warnStructured("[ai-service] chatTranscriptEvicted", attributes);
1316
+ }
1317
+ else {
1318
+ this.logger.warn(`[ai-service] chatTranscriptEvicted trigger=${attributes.trigger} evicted_count=${attributes.evicted_count} evicted_bytes=${attributes.evicted_bytes} resident_count=${attributes.resident_count} resident_bytes=${attributes.resident_bytes}`);
1319
+ }
1320
+ }
1321
+ /**
1322
+ * Throttle the eviction warn to one line per trigger per window. A trigger
1323
+ * change always warns (meaningful transition); repeated same-trigger
1324
+ * evictions on every append collapse to one line per EVICTION_WARN_THROTTLE_MS.
1325
+ */
1326
+ shouldWarnForEviction(trigger) {
1327
+ const now = Date.now();
1328
+ if (trigger === this.lastEvictionWarnTrigger &&
1329
+ now - this.lastEvictionWarnAt < EVICTION_WARN_THROTTLE_MS) {
1330
+ return false;
1331
+ }
1332
+ this.lastEvictionWarnAt = now;
1333
+ this.lastEvictionWarnTrigger = trigger;
1334
+ return true;
1129
1335
  }
1130
1336
  };
1131
1337
  })();