@agent-native/core 0.131.7 → 0.131.9

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 (41) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +24 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/application-state/store.ts +18 -31
  5. package/corpus/core/src/cli/create.ts +115 -16
  6. package/corpus/core/src/client/AssistantChat.tsx +10 -2
  7. package/corpus/core/src/client/frame-protocol.ts +5 -1
  8. package/corpus/core/src/workspace-files/tool.ts +18 -11
  9. package/corpus/templates/clips/actions/generate-workflow.ts +6 -4
  10. package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
  11. package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
  12. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
  13. package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
  14. package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
  15. package/corpus/templates/clips/shared/workflow.ts +5 -0
  16. package/dist/application-state/store.d.ts.map +1 -1
  17. package/dist/application-state/store.js +20 -36
  18. package/dist/application-state/store.js.map +1 -1
  19. package/dist/cli/create.d.ts +8 -1
  20. package/dist/cli/create.d.ts.map +1 -1
  21. package/dist/cli/create.js +98 -16
  22. package/dist/cli/create.js.map +1 -1
  23. package/dist/client/AssistantChat.d.ts.map +1 -1
  24. package/dist/client/AssistantChat.js +10 -2
  25. package/dist/client/AssistantChat.js.map +1 -1
  26. package/dist/client/frame-protocol.d.ts +1 -0
  27. package/dist/client/frame-protocol.d.ts.map +1 -1
  28. package/dist/client/frame-protocol.js.map +1 -1
  29. package/dist/collab/routes.d.ts +1 -1
  30. package/dist/observability/routes.d.ts +3 -3
  31. package/dist/server/realtime-token.d.ts +1 -1
  32. package/dist/server/transcribe-voice.d.ts +1 -1
  33. package/dist/workspace-files/tool.d.ts.map +1 -1
  34. package/dist/workspace-files/tool.js +18 -11
  35. package/dist/workspace-files/tool.js.map +1 -1
  36. package/package.json +3 -3
  37. package/src/application-state/store.ts +18 -31
  38. package/src/cli/create.ts +115 -16
  39. package/src/client/AssistantChat.tsx +10 -2
  40. package/src/client/frame-protocol.ts +5 -1
  41. package/src/workspace-files/tool.ts +18 -11
@@ -0,0 +1,257 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import {
3
+ compareAndSetAppState,
4
+ compareAndSetManyAppState,
5
+ readAppState,
6
+ } from "@agent-native/core/application-state";
7
+ import { assertAccess } from "@agent-native/core/sharing";
8
+ import { z } from "zod";
9
+
10
+ import { WorkflowKindSchema } from "../shared/workflow.js";
11
+
12
+ const WorkflowStateSchema = z
13
+ .object({
14
+ kind: WorkflowKindSchema.optional(),
15
+ status: z.string().optional(),
16
+ content: z.string().optional(),
17
+ recordingId: z.string().optional(),
18
+ requestedAt: z.string().optional(),
19
+ tabId: z.string().optional(),
20
+ claimedAt: z.string().optional(),
21
+ })
22
+ .passthrough();
23
+
24
+ const WorkflowRequestSchema = z
25
+ .object({
26
+ requestedAt: z.string(),
27
+ deliveredAt: z.string().optional(),
28
+ deliveredTabId: z.string().optional(),
29
+ })
30
+ .passthrough();
31
+
32
+ const CLAIM_LEASE_MS = 30_000;
33
+
34
+ export default defineAction({
35
+ description:
36
+ "Track and reconcile the agent run responsible for a generated workflow.",
37
+ agentTool: false,
38
+ schema: z.object({
39
+ operation: z.enum([
40
+ "track",
41
+ "release",
42
+ "mark-delivered",
43
+ "consume",
44
+ "stop",
45
+ ]),
46
+ recordingId: z.string().min(1),
47
+ requestedAt: z.string().min(1),
48
+ tabId: z.string().min(1),
49
+ }),
50
+ run: async ({ operation, recordingId, requestedAt, tabId }) => {
51
+ await assertAccess("recording", recordingId, "viewer");
52
+
53
+ const requestKey = `clips-ai-request-${recordingId}`;
54
+ if (operation === "mark-delivered" || operation === "consume") {
55
+ const rawRequest = await readAppState(requestKey);
56
+ if (rawRequest === null) {
57
+ return operation === "consume"
58
+ ? { reconciled: false, consumed: true, reason: "missing" as const }
59
+ : { reconciled: false, delivered: true, reason: "missing" as const };
60
+ }
61
+ const parsedRequest = WorkflowRequestSchema.safeParse(rawRequest);
62
+ if (!parsedRequest.success) {
63
+ throw new Error(`Invalid workflow request state for ${recordingId}`);
64
+ }
65
+ if (parsedRequest.data.requestedAt !== requestedAt) {
66
+ return operation === "consume"
67
+ ? {
68
+ reconciled: false,
69
+ consumed: true,
70
+ reason: "newer-request" as const,
71
+ }
72
+ : {
73
+ reconciled: false,
74
+ delivered: true,
75
+ reason: "newer-request" as const,
76
+ };
77
+ }
78
+ if (operation === "mark-delivered") {
79
+ if (parsedRequest.data.deliveredTabId === tabId) {
80
+ return { reconciled: false, delivered: true };
81
+ }
82
+ if (parsedRequest.data.deliveredTabId) {
83
+ return {
84
+ reconciled: false,
85
+ delivered: false,
86
+ reason: "different-run" as const,
87
+ };
88
+ }
89
+
90
+ const stateKey = `clips-workflow-${recordingId}`;
91
+ const rawState = await readAppState(stateKey);
92
+ if (rawState === null) {
93
+ return { reconciled: false, delivered: false, reason: "missing" };
94
+ }
95
+ const parsedState = WorkflowStateSchema.safeParse(rawState);
96
+ if (!parsedState.success) {
97
+ throw new Error(
98
+ `Invalid generated workflow state for ${recordingId}`,
99
+ );
100
+ }
101
+ if (parsedState.data.status !== "generating") {
102
+ const consumed = await compareAndSetAppState(
103
+ requestKey,
104
+ rawRequest,
105
+ null,
106
+ );
107
+ return consumed
108
+ ? {
109
+ reconciled: false,
110
+ delivered: true,
111
+ consumed: true,
112
+ reason: "terminal",
113
+ }
114
+ : {
115
+ reconciled: false,
116
+ delivered: false,
117
+ consumed: false,
118
+ reason: "stale",
119
+ };
120
+ }
121
+ if (
122
+ parsedState.data.requestedAt !== requestedAt ||
123
+ parsedState.data.tabId !== tabId
124
+ ) {
125
+ return {
126
+ reconciled: false,
127
+ delivered: false,
128
+ reason: "different-run",
129
+ };
130
+ }
131
+
132
+ const deliveredAt = new Date().toISOString();
133
+ const delivered = await compareAndSetManyAppState([
134
+ {
135
+ key: stateKey,
136
+ expectedValue: rawState,
137
+ nextValue: { ...rawState, claimedAt: deliveredAt },
138
+ },
139
+ {
140
+ key: requestKey,
141
+ expectedValue: rawRequest,
142
+ nextValue: { ...rawRequest, deliveredAt, deliveredTabId: tabId },
143
+ },
144
+ ]);
145
+ return delivered
146
+ ? { reconciled: false, delivered: true }
147
+ : { reconciled: false, delivered: false, reason: "stale" as const };
148
+ }
149
+ if (parsedRequest.data.deliveredTabId !== tabId) {
150
+ return {
151
+ reconciled: false,
152
+ consumed: false,
153
+ reason: "not-delivered" as const,
154
+ };
155
+ }
156
+ const consumed = await compareAndSetAppState(
157
+ requestKey,
158
+ rawRequest,
159
+ null,
160
+ );
161
+ return consumed
162
+ ? { reconciled: false, consumed: true }
163
+ : { reconciled: false, consumed: false, reason: "stale" as const };
164
+ }
165
+
166
+ const stateKey = `clips-workflow-${recordingId}`;
167
+ const rawState = await readAppState(stateKey);
168
+ if (rawState === null) {
169
+ return { reconciled: false, reason: "missing" as const };
170
+ }
171
+
172
+ const parsedState = WorkflowStateSchema.safeParse(rawState);
173
+ if (!parsedState.success) {
174
+ throw new Error(`Invalid generated workflow state for ${recordingId}`);
175
+ }
176
+
177
+ const state = parsedState.data;
178
+ if (state.status !== "generating") {
179
+ return { reconciled: false, reason: "terminal" as const };
180
+ }
181
+ if (state.requestedAt !== requestedAt) {
182
+ return { reconciled: false, reason: "newer-request" as const };
183
+ }
184
+
185
+ if (operation === "track") {
186
+ if (state.tabId === tabId) {
187
+ return { reconciled: false, tracked: true };
188
+ }
189
+ if (state.tabId) {
190
+ const claimedAt = Date.parse(
191
+ state.claimedAt ?? state.requestedAt ?? "",
192
+ );
193
+ if (
194
+ !Number.isFinite(claimedAt) ||
195
+ Date.now() - claimedAt < CLAIM_LEASE_MS
196
+ ) {
197
+ return {
198
+ reconciled: false,
199
+ tracked: false,
200
+ reason: "claimed" as const,
201
+ };
202
+ }
203
+ }
204
+ const rawRequest = await readAppState(requestKey);
205
+ if (rawRequest === null) {
206
+ return { reconciled: false, reason: "request-missing" as const };
207
+ }
208
+ const parsedRequest = WorkflowRequestSchema.safeParse(rawRequest);
209
+ if (!parsedRequest.success) {
210
+ throw new Error(`Invalid workflow request state for ${recordingId}`);
211
+ }
212
+ if (parsedRequest.data.requestedAt !== requestedAt) {
213
+ return { reconciled: false, reason: "newer-request" as const };
214
+ }
215
+
216
+ const tracked = await compareAndSetAppState(stateKey, rawState, {
217
+ ...rawState,
218
+ tabId,
219
+ claimedAt: new Date().toISOString(),
220
+ });
221
+ return tracked
222
+ ? { reconciled: false, tracked: true }
223
+ : { reconciled: false, tracked: false, reason: "stale" as const };
224
+ }
225
+ if (state.tabId !== tabId) {
226
+ return operation === "release"
227
+ ? {
228
+ reconciled: false,
229
+ released: true,
230
+ reason: "different-run" as const,
231
+ }
232
+ : { reconciled: false, reason: "different-run" as const };
233
+ }
234
+ if (operation === "release") {
235
+ const untrackedState = { ...rawState };
236
+ delete untrackedState.tabId;
237
+ delete untrackedState.claimedAt;
238
+ const released = await compareAndSetAppState(
239
+ stateKey,
240
+ rawState,
241
+ untrackedState,
242
+ );
243
+ return released
244
+ ? { reconciled: false, released: true }
245
+ : { reconciled: false, released: false, reason: "stale" as const };
246
+ }
247
+
248
+ const reconciled = await compareAndSetAppState(stateKey, rawState, {
249
+ ...rawState,
250
+ status: "failed",
251
+ failedAt: new Date().toISOString(),
252
+ });
253
+ return reconciled
254
+ ? { reconciled: true }
255
+ : { reconciled: false, reason: "stale" as const };
256
+ },
257
+ });
@@ -1,5 +1,7 @@
1
1
  import {
2
+ generateTabId,
2
3
  sendToAgentChat,
4
+ sendToAgentChatAndConfirm,
3
5
  type AgentChatMessage,
4
6
  } from "@agent-native/core/client/agent-chat";
5
7
  import { agentNativePath } from "@agent-native/core/client/api-path";
@@ -11,6 +13,8 @@ import { useRecordings, type RecordingSummary } from "./use-library";
11
13
 
12
14
  const DEFAULT_TITLE = "Untitled recording";
13
15
  const TWO_MINUTES_MS = 2 * 60 * 1000;
16
+ export const WORKFLOW_ACTION_MAX_ATTEMPTS = 5;
17
+ const WORKFLOW_ACTION_RETRY_DELAY_MS = 1000;
14
18
 
15
19
  /** True when `title` is blank or equal to the server-seeded default. */
16
20
  export function isDefaultTitle(title: string | null | undefined): boolean {
@@ -45,6 +49,8 @@ interface AiRequest {
45
49
  message?: string;
46
50
  includeFullVideoInAi?: boolean;
47
51
  openInChat?: boolean;
52
+ deliveredAt?: string;
53
+ deliveredTabId?: string;
48
54
  }
49
55
 
50
56
  const DISPATCHABLE_REQUESTS = new Set([
@@ -99,6 +105,36 @@ export function useAutoTitleBridge(): void {
99
105
  const dispatched = useRef<Set<string>>(new Set());
100
106
  const inflight = useRef<boolean>(false);
101
107
 
108
+ useEffect(() => {
109
+ const handleChatRunning = (event: Event) => {
110
+ const detail = (event as CustomEvent).detail;
111
+ if (
112
+ detail?.isRunning !== false ||
113
+ (detail.reason !== "stopped" && detail.reason !== "failed") ||
114
+ typeof detail.tabId !== "string"
115
+ )
116
+ return;
117
+
118
+ const recordingId = recordingIdFromTab(detail.tabId);
119
+ const requestedAt = requestedAtFromTab(detail.tabId);
120
+ if (!recordingId || !requestedAt) return;
121
+
122
+ void retryWorkflowAction(
123
+ {
124
+ operation: "stop",
125
+ recordingId,
126
+ requestedAt,
127
+ tabId: detail.tabId,
128
+ },
129
+ "reconciled",
130
+ );
131
+ };
132
+
133
+ window.addEventListener("agentNative.chatRunning", handleChatRunning);
134
+ return () =>
135
+ window.removeEventListener("agentNative.chatRunning", handleChatRunning);
136
+ }, []);
137
+
102
138
  const readyRecordings = recordings.filter((r) => r.status === "ready");
103
139
  const readyRecordingsKey = readyRecordings
104
140
  .map(
@@ -144,7 +180,6 @@ export function useAutoTitleBridge(): void {
144
180
  request.requestedAt ?? "0"
145
181
  }`;
146
182
  if (dispatched.current.has(dispatchKey)) continue;
147
- dispatched.current.add(dispatchKey);
148
183
  if (
149
184
  request.kind === "generate-metadata" ||
150
185
  request.kind === "regenerate-title"
@@ -155,8 +190,67 @@ export function useAutoTitleBridge(): void {
155
190
  dispatched.current.add(`${rec.id}:fallback`);
156
191
  }
157
192
 
158
- dispatchAiRequest(rec, request);
193
+ if (
194
+ request.kind === "generate-workflow" &&
195
+ typeof request.requestedAt === "string"
196
+ ) {
197
+ if (request.deliveredTabId) {
198
+ dispatched.current.add(dispatchKey);
199
+ void consumeWorkflowRequest({
200
+ recordingId: rec.id,
201
+ requestedAt: request.requestedAt,
202
+ tabId: request.deliveredTabId,
203
+ });
204
+ continue;
205
+ }
159
206
 
207
+ const tabId = workflowTabId(rec.id, request.requestedAt);
208
+ try {
209
+ const result = (await callAction(
210
+ "reconcile-workflow-generation" as any,
211
+ {
212
+ operation: "track",
213
+ recordingId: rec.id,
214
+ requestedAt: request.requestedAt,
215
+ tabId,
216
+ } as any,
217
+ )) as { tracked?: boolean };
218
+ if (result.tracked !== true) {
219
+ fallbackTimer = setTimeout(() => void tick(), 1000);
220
+ continue;
221
+ }
222
+ } catch {
223
+ fallbackTimer = setTimeout(() => void tick(), 1000);
224
+ continue;
225
+ }
226
+ const delivery = await sendToAgentChatAndConfirm({
227
+ ...buildAiRequestChatOptions(rec, request),
228
+ tabId,
229
+ chatTarget: "local",
230
+ });
231
+ if (!delivery.delivered) {
232
+ await retryWorkflowAction(
233
+ {
234
+ operation: "release",
235
+ recordingId: rec.id,
236
+ requestedAt: request.requestedAt,
237
+ tabId,
238
+ },
239
+ "released",
240
+ );
241
+ fallbackTimer = setTimeout(() => void tick(), 1000);
242
+ continue;
243
+ }
244
+ dispatched.current.add(dispatchKey);
245
+ void persistAndConsumeWorkflowRequest({
246
+ recordingId: rec.id,
247
+ requestedAt: request.requestedAt,
248
+ tabId,
249
+ });
250
+ continue;
251
+ }
252
+ dispatchAiRequest(rec, request);
253
+ dispatched.current.add(dispatchKey);
160
254
  void clearRequest(rec.id);
161
255
  } else if (isAutoTitleReplaceable(rec.title, rec.titleSource)) {
162
256
  // No server-queued delegation. Only dispatch the fallback for
@@ -294,8 +388,76 @@ export function buildAiRequestChatOptions(
294
388
  };
295
389
  }
296
390
 
297
- function dispatchAiRequest(rec: RecordingSummary, request: AiRequest) {
298
- sendToAgentChat(buildAiRequestChatOptions(rec, request));
391
+ interface WorkflowRunRequest {
392
+ recordingId: string;
393
+ requestedAt: string;
394
+ tabId: string;
395
+ }
396
+
397
+ export async function retryWorkflowAction(
398
+ request: WorkflowRunRequest & { operation: string },
399
+ successKey: string,
400
+ ): Promise<boolean> {
401
+ for (let attempt = 0; attempt < WORKFLOW_ACTION_MAX_ATTEMPTS; attempt += 1) {
402
+ try {
403
+ const result = (await callAction(
404
+ "reconcile-workflow-generation" as any,
405
+ request as any,
406
+ )) as Record<string, unknown>;
407
+ if (result[successKey] === true) return true;
408
+ if (typeof result.reason === "string" && result.reason !== "stale") {
409
+ return false;
410
+ }
411
+ } catch {}
412
+
413
+ if (attempt === WORKFLOW_ACTION_MAX_ATTEMPTS - 1) return false;
414
+ await new Promise((resolve) =>
415
+ setTimeout(resolve, WORKFLOW_ACTION_RETRY_DELAY_MS * 2 ** attempt),
416
+ );
417
+ }
418
+
419
+ return false;
420
+ }
421
+
422
+ async function consumeWorkflowRequest(
423
+ request: WorkflowRunRequest,
424
+ ): Promise<boolean> {
425
+ return retryWorkflowAction({ ...request, operation: "consume" }, "consumed");
426
+ }
427
+
428
+ async function persistAndConsumeWorkflowRequest(
429
+ request: WorkflowRunRequest,
430
+ ): Promise<void> {
431
+ const delivered = await retryWorkflowAction(
432
+ { ...request, operation: "mark-delivered" },
433
+ "delivered",
434
+ );
435
+ if (delivered) await consumeWorkflowRequest(request);
436
+ }
437
+
438
+ function workflowTabId(recordingId: string, requestedAt: string) {
439
+ return `clips-workflow:${recordingId}:${encodeURIComponent(requestedAt)}:${generateTabId()}`;
440
+ }
441
+
442
+ function recordingIdFromTab(tabId: string) {
443
+ const match = /^clips-workflow:([^:]+):/.exec(tabId);
444
+ return match?.[1];
445
+ }
446
+
447
+ function requestedAtFromTab(tabId: string) {
448
+ const match = /^clips-workflow:[^:]+:([^:]+):/.exec(tabId);
449
+ return match ? decodeURIComponent(match[1]) : undefined;
450
+ }
451
+
452
+ function dispatchAiRequest(
453
+ rec: RecordingSummary,
454
+ request: AiRequest,
455
+ tabId?: string,
456
+ ) {
457
+ return sendToAgentChat({
458
+ ...buildAiRequestChatOptions(rec, request),
459
+ ...(tabId ? { tabId } : {}),
460
+ });
299
461
  }
300
462
 
301
463
  function parseJsonArray(raw: string | undefined): unknown[] {
@@ -27,6 +27,7 @@ import {
27
27
  DASHBOARD_REDIRECT_VALUE,
28
28
  REF_PARAM,
29
29
  } from "@shared/share-attribution";
30
+ import type { WorkflowKind } from "@shared/workflow";
30
31
  import {
31
32
  IconShare3,
32
33
  IconArrowLeft,
@@ -109,7 +110,6 @@ export function meta() {
109
110
  }
110
111
 
111
112
  type SidePanel = "transcript" | "comments" | "insights" | "agent" | "settings";
112
- type WorkflowKind = "pr" | "sop" | "ticket" | "email";
113
113
 
114
114
  const WORKFLOW_MENU_ITEMS: Array<{
115
115
  kind: WorkflowKind;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-27
4
+ ---
5
+
6
+ Stopped agent runs no longer leave generated workflow cards spinning indefinitely.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-30
4
+ ---
5
+
6
+ Workflow generation now stops retrying after repeated failures instead of remaining active indefinitely.
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+
3
+ export const WORKFLOW_KINDS = ["pr", "sop", "ticket", "email"] as const;
4
+ export const WorkflowKindSchema = z.enum(WORKFLOW_KINDS);
5
+ export type WorkflowKind = z.infer<typeof WorkflowKindSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/application-state/store.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAyF9D,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAgBzC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAsBzD;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EAC7C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACzC,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAED,MAAM,WAAW,8BAA8B;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3C;AA0GD,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,SAAS,8BAA8B,EAAE,EACrD,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CA+FlB;AAED,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC,CAWjE;AAED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAsBjB"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/application-state/store.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAyF9D,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CASzC;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAiBzD;AAED,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EAC7C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACzC,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAED,MAAM,WAAW,8BAA8B;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3C;AA0GD,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,SAAS,8BAA8B,EAAE,EACrD,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,OAAO,CAAC,CA+FlB;AAED,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC,CAWjE;AAED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAsBjB"}
@@ -1,4 +1,4 @@
1
- import { getDbExec, getDialect, isLocalDatabase, isConnectionError, isPostgres, intType, } from "../db/client.js";
1
+ import { getDbExec, getDialect, isLocalDatabase, isPostgres, intType, } from "../db/client.js";
2
2
  import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
3
3
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
4
4
  import { emitAppStateChange, emitAppStateDelete } from "./emitter.js";
@@ -79,24 +79,15 @@ async function ensureTable() {
79
79
  return _initPromise;
80
80
  }
81
81
  export async function appStateGet(sessionId, key) {
82
- try {
83
- await ensureTable();
84
- const client = getDbExec();
85
- const { rows } = await client.execute({
86
- sql: `SELECT value FROM application_state WHERE session_id = ? AND key = ?`,
87
- args: [sessionId, key],
88
- });
89
- if (rows.length === 0)
90
- return null;
91
- return JSON.parse(rows[0].value);
92
- }
93
- catch (err) {
94
- // Transient WS / connection drops (Neon serverless) — caller polls every
95
- // 2s and will see the value on the next tick. Swallow rather than 500.
96
- if (isConnectionError(err))
97
- return null;
98
- throw err;
99
- }
82
+ await ensureTable();
83
+ const client = getDbExec();
84
+ const { rows } = await client.execute({
85
+ sql: `SELECT value FROM application_state WHERE session_id = ? AND key = ?`,
86
+ args: [sessionId, key],
87
+ });
88
+ if (rows.length === 0)
89
+ return null;
90
+ return JSON.parse(rows[0].value);
100
91
  }
101
92
  /**
102
93
  * Read several application-state keys for one session in a single SQL query.
@@ -110,24 +101,17 @@ export async function appStateGetMany(sessionId, keys) {
110
101
  values[key] = null;
111
102
  if (uniqueKeys.length === 0)
112
103
  return values;
113
- try {
114
- await ensureTable();
115
- const client = getDbExec();
116
- const placeholders = uniqueKeys.map(() => "?").join(", ");
117
- const { rows } = await client.execute({
118
- sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key IN (${placeholders})`,
119
- args: [sessionId, ...uniqueKeys],
120
- });
121
- for (const row of rows) {
122
- values[row.key] = JSON.parse(row.value);
123
- }
124
- return values;
125
- }
126
- catch (err) {
127
- if (isConnectionError(err))
128
- return values;
129
- throw err;
104
+ await ensureTable();
105
+ const client = getDbExec();
106
+ const placeholders = uniqueKeys.map(() => "?").join(", ");
107
+ const { rows } = await client.execute({
108
+ sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key IN (${placeholders})`,
109
+ args: [sessionId, ...uniqueKeys],
110
+ });
111
+ for (const row of rows) {
112
+ values[row.key] = JSON.parse(row.value);
130
113
  }
114
+ return values;
131
115
  }
132
116
  export async function appStatePut(sessionId, key, value, options) {
133
117
  await ensureTable();