@agent-native/core 0.77.8 → 0.77.10

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 (189) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +29 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/task-store.ts +25 -3
  5. package/corpus/core/src/agent/harness/store.ts +89 -55
  6. package/corpus/core/src/agent/observational-memory/store.ts +40 -14
  7. package/corpus/core/src/agent/run-store.ts +116 -59
  8. package/corpus/core/src/application-state/store.ts +36 -2
  9. package/corpus/core/src/audit/store.ts +37 -6
  10. package/corpus/core/src/browser-sessions/store.ts +31 -12
  11. package/corpus/core/src/chat-threads/store.ts +67 -3
  12. package/corpus/core/src/checkpoints/store.ts +14 -3
  13. package/corpus/core/src/cli/create.ts +9 -0
  14. package/corpus/core/src/collab/storage.ts +20 -2
  15. package/corpus/core/src/db/ddl-guard.ts +112 -0
  16. package/corpus/core/src/extensions/store.ts +76 -3
  17. package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
  18. package/corpus/core/src/integrations/config-store.ts +19 -18
  19. package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
  20. package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
  21. package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
  22. package/corpus/core/src/integrations/remote-push-store.ts +75 -35
  23. package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
  24. package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
  25. package/corpus/core/src/mcp/connect-store.ts +53 -16
  26. package/corpus/core/src/mcp/oauth-store.ts +32 -7
  27. package/corpus/core/src/notifications/store.ts +20 -4
  28. package/corpus/core/src/oauth-tokens/store.ts +45 -2
  29. package/corpus/core/src/observability/store.ts +154 -47
  30. package/corpus/core/src/progress/store.ts +36 -21
  31. package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
  32. package/corpus/core/src/provider-api/custom-registry.ts +15 -4
  33. package/corpus/core/src/provider-api/quota-governor.ts +15 -3
  34. package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
  35. package/corpus/core/src/resources/store.ts +66 -31
  36. package/corpus/core/src/secrets/storage.ts +18 -22
  37. package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
  38. package/corpus/core/src/server/auth.ts +22 -4
  39. package/corpus/core/src/server/better-auth-instance.ts +49 -15
  40. package/corpus/core/src/server/embed-session.ts +19 -4
  41. package/corpus/core/src/server/identity-sso-store.ts +40 -11
  42. package/corpus/core/src/server/recap-image-store.ts +28 -9
  43. package/corpus/core/src/settings/store.ts +14 -19
  44. package/corpus/core/src/usage/store.ts +51 -16
  45. package/corpus/core/src/workspace-connections/store.ts +200 -22
  46. package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
  47. package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
  48. package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
  49. package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
  50. package/corpus/templates/plan/app/global.css +6 -11
  51. package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
  52. package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
  53. package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
  54. package/dist/a2a/task-store.d.ts.map +1 -1
  55. package/dist/a2a/task-store.js +19 -3
  56. package/dist/a2a/task-store.js.map +1 -1
  57. package/dist/agent/harness/store.d.ts.map +1 -1
  58. package/dist/agent/harness/store.js +63 -34
  59. package/dist/agent/harness/store.js.map +1 -1
  60. package/dist/agent/observational-memory/store.d.ts.map +1 -1
  61. package/dist/agent/observational-memory/store.js +30 -8
  62. package/dist/agent/observational-memory/store.js.map +1 -1
  63. package/dist/agent/run-store.d.ts.map +1 -1
  64. package/dist/agent/run-store.js +103 -46
  65. package/dist/agent/run-store.js.map +1 -1
  66. package/dist/application-state/store.d.ts.map +1 -1
  67. package/dist/application-state/store.js +28 -2
  68. package/dist/application-state/store.js.map +1 -1
  69. package/dist/audit/store.d.ts.map +1 -1
  70. package/dist/audit/store.js +18 -3
  71. package/dist/audit/store.js.map +1 -1
  72. package/dist/browser-sessions/store.d.ts.map +1 -1
  73. package/dist/browser-sessions/store.js +22 -7
  74. package/dist/browser-sessions/store.js.map +1 -1
  75. package/dist/chat-threads/store.d.ts.map +1 -1
  76. package/dist/chat-threads/store.js +51 -3
  77. package/dist/chat-threads/store.js.map +1 -1
  78. package/dist/checkpoints/store.d.ts.map +1 -1
  79. package/dist/checkpoints/store.js +12 -3
  80. package/dist/checkpoints/store.js.map +1 -1
  81. package/dist/cli/create.d.ts.map +1 -1
  82. package/dist/cli/create.js +9 -0
  83. package/dist/cli/create.js.map +1 -1
  84. package/dist/collab/routes.d.ts +2 -2
  85. package/dist/collab/storage.d.ts.map +1 -1
  86. package/dist/collab/storage.js +14 -2
  87. package/dist/collab/storage.js.map +1 -1
  88. package/dist/db/ddl-guard.d.ts +63 -0
  89. package/dist/db/ddl-guard.d.ts.map +1 -1
  90. package/dist/db/ddl-guard.js +85 -0
  91. package/dist/db/ddl-guard.js.map +1 -1
  92. package/dist/extensions/store.d.ts.map +1 -1
  93. package/dist/extensions/store.js +32 -3
  94. package/dist/extensions/store.js.map +1 -1
  95. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  96. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  97. package/dist/integrations/a2a-continuations-store.js +44 -24
  98. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  99. package/dist/integrations/config-store.d.ts.map +1 -1
  100. package/dist/integrations/config-store.js +17 -11
  101. package/dist/integrations/config-store.js.map +1 -1
  102. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  103. package/dist/integrations/pending-tasks-store.js +24 -16
  104. package/dist/integrations/pending-tasks-store.js.map +1 -1
  105. package/dist/integrations/remote-commands-store.d.ts.map +1 -1
  106. package/dist/integrations/remote-commands-store.js +29 -21
  107. package/dist/integrations/remote-commands-store.js.map +1 -1
  108. package/dist/integrations/remote-devices-store.d.ts.map +1 -1
  109. package/dist/integrations/remote-devices-store.js +33 -18
  110. package/dist/integrations/remote-devices-store.js.map +1 -1
  111. package/dist/integrations/remote-push-store.d.ts.map +1 -1
  112. package/dist/integrations/remote-push-store.js +53 -31
  113. package/dist/integrations/remote-push-store.js.map +1 -1
  114. package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
  115. package/dist/integrations/remote-run-events-store.js +26 -10
  116. package/dist/integrations/remote-run-events-store.js.map +1 -1
  117. package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
  118. package/dist/integrations/thread-mapping-store.js +10 -2
  119. package/dist/integrations/thread-mapping-store.js.map +1 -1
  120. package/dist/mcp/connect-store.d.ts.map +1 -1
  121. package/dist/mcp/connect-store.js +34 -16
  122. package/dist/mcp/connect-store.js.map +1 -1
  123. package/dist/mcp/oauth-store.d.ts.map +1 -1
  124. package/dist/mcp/oauth-store.js +22 -7
  125. package/dist/mcp/oauth-store.js.map +1 -1
  126. package/dist/notifications/routes.d.ts +2 -2
  127. package/dist/notifications/store.d.ts.map +1 -1
  128. package/dist/notifications/store.js +15 -3
  129. package/dist/notifications/store.js.map +1 -1
  130. package/dist/oauth-tokens/store.d.ts.map +1 -1
  131. package/dist/oauth-tokens/store.js +33 -2
  132. package/dist/oauth-tokens/store.js.map +1 -1
  133. package/dist/observability/routes.d.ts +7 -7
  134. package/dist/observability/store.d.ts.map +1 -1
  135. package/dist/observability/store.js +72 -29
  136. package/dist/observability/store.js.map +1 -1
  137. package/dist/progress/routes.d.ts +1 -1
  138. package/dist/progress/store.d.ts.map +1 -1
  139. package/dist/progress/store.js +31 -16
  140. package/dist/progress/store.js.map +1 -1
  141. package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
  142. package/dist/provider-api/corpus-jobs-store.js +16 -5
  143. package/dist/provider-api/corpus-jobs-store.js.map +1 -1
  144. package/dist/provider-api/custom-registry.d.ts.map +1 -1
  145. package/dist/provider-api/custom-registry.js +14 -4
  146. package/dist/provider-api/custom-registry.js.map +1 -1
  147. package/dist/provider-api/quota-governor.d.ts.map +1 -1
  148. package/dist/provider-api/quota-governor.js +12 -3
  149. package/dist/provider-api/quota-governor.js.map +1 -1
  150. package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
  151. package/dist/provider-api/staged-datasets-store.js +16 -5
  152. package/dist/provider-api/staged-datasets-store.js.map +1 -1
  153. package/dist/resources/handlers.d.ts +3 -3
  154. package/dist/resources/store.d.ts.map +1 -1
  155. package/dist/resources/store.js +56 -25
  156. package/dist/resources/store.js.map +1 -1
  157. package/dist/secrets/storage.d.ts.map +1 -1
  158. package/dist/secrets/storage.js +10 -14
  159. package/dist/secrets/storage.js.map +1 -1
  160. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  161. package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
  162. package/dist/server/agent-teams-run-queue.js +15 -4
  163. package/dist/server/agent-teams-run-queue.js.map +1 -1
  164. package/dist/server/auth.d.ts.map +1 -1
  165. package/dist/server/auth.js +16 -2
  166. package/dist/server/auth.js.map +1 -1
  167. package/dist/server/better-auth-instance.d.ts.map +1 -1
  168. package/dist/server/better-auth-instance.js +55 -21
  169. package/dist/server/better-auth-instance.js.map +1 -1
  170. package/dist/server/embed-session.d.ts.map +1 -1
  171. package/dist/server/embed-session.js +15 -4
  172. package/dist/server/embed-session.js.map +1 -1
  173. package/dist/server/identity-sso-store.d.ts.map +1 -1
  174. package/dist/server/identity-sso-store.js +30 -11
  175. package/dist/server/identity-sso-store.js.map +1 -1
  176. package/dist/server/recap-image-store.d.ts.map +1 -1
  177. package/dist/server/recap-image-store.js +21 -7
  178. package/dist/server/recap-image-store.js.map +1 -1
  179. package/dist/server/transcribe-voice.d.ts +1 -1
  180. package/dist/settings/store.d.ts.map +1 -1
  181. package/dist/settings/store.js +11 -13
  182. package/dist/settings/store.js.map +1 -1
  183. package/dist/usage/store.d.ts.map +1 -1
  184. package/dist/usage/store.js +36 -11
  185. package/dist/usage/store.js.map +1 -1
  186. package/dist/workspace-connections/store.d.ts.map +1 -1
  187. package/dist/workspace-connections/store.js +57 -7
  188. package/dist/workspace-connections/store.js.map +1 -1
  189. package/package.json +1 -1
@@ -8,6 +8,11 @@
8
8
  * Cost is stored as "centicents" (1/100th of a cent) for integer precision.
9
9
  */
10
10
  import { getDbExec, intType, isPostgres } from "../db/client.js";
11
+ import {
12
+ ensureColumnExists,
13
+ ensureIndexExists,
14
+ ensureTableExists,
15
+ } from "../db/ddl-guard.js";
11
16
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
12
17
 
13
18
  /**
@@ -191,7 +196,7 @@ async function ensureUsageTable(): Promise<void> {
191
196
  if (!_initPromise) {
192
197
  _initPromise = (async () => {
193
198
  const client = getDbExec();
194
- await client.execute(`
199
+ const createSql = `
195
200
  CREATE TABLE IF NOT EXISTS token_usage (
196
201
  id ${intType()} PRIMARY KEY,
197
202
  owner_email TEXT NOT NULL,
@@ -206,11 +211,9 @@ async function ensureUsageTable(): Promise<void> {
206
211
  ref_id TEXT NOT NULL DEFAULT '',
207
212
  created_at ${intType()} NOT NULL
208
213
  )
209
- `);
214
+ `;
210
215
 
211
- // Add columns on older deployments that pre-date the label/cache
212
- // fields. Each ALTER is wrapped so a dialect without IF NOT EXISTS
213
- // (SQLite) still makes progress if only some columns are missing.
216
+ // Additive columns for older deployments that pre-date the label/cache fields.
214
217
  const additions: Array<[string, string]> = [
215
218
  ["cache_read_tokens", `${intType()} NOT NULL DEFAULT 0`],
216
219
  ["cache_write_tokens", `${intType()} NOT NULL DEFAULT 0`],
@@ -218,27 +221,59 @@ async function ensureUsageTable(): Promise<void> {
218
221
  ["app", `TEXT NOT NULL DEFAULT ''`],
219
222
  ["ref_id", `TEXT NOT NULL DEFAULT ''`],
220
223
  ];
224
+
225
+ if (isPostgres()) {
226
+ // Hot path: the `token_usage` table and its index are virtually always
227
+ // already present in production. Issuing `CREATE TABLE`/`ALTER TABLE`/
228
+ // `CREATE INDEX` still takes a lock that, in a fresh background-worker
229
+ // process behind a concurrent connection on the shared Neon DB, can
230
+ // block ~indefinitely. The ensure* wrappers probe `information_schema`/
231
+ // `pg_indexes` first (plain reads, no lock) and run DDL ONLY for what is
232
+ // actually missing, bounded by a transaction-scoped `lock_timeout`. If a
233
+ // swallowed lock-timeout leaves the schema still missing they RE-PROBE
234
+ // and THROW rather than letting init memoize success against absent
235
+ // schema.
236
+ await ensureTableExists("token_usage", createSql);
237
+ // Add columns on older deployments — guarded so the hot path (columns
238
+ // already present) skips the ACCESS EXCLUSIVE ALTER.
239
+ for (const [col, def] of additions) {
240
+ await ensureColumnExists(
241
+ "token_usage",
242
+ col,
243
+ `ALTER TABLE token_usage ADD COLUMN IF NOT EXISTS ${col} ${def}`,
244
+ );
245
+ }
246
+ // Older deployments created `created_at` as 32-bit `INTEGER`; on Postgres
247
+ // the `Date.now()` written per run by recordUsage() overflows int4. Widen
248
+ // it in place (no-op once done / on fresh BIGINT databases).
249
+ await widenIntColumnsToBigInt("token_usage", ["created_at"]);
250
+ // Probe pg_indexes first (no lock) and skip the SHARE-locking CREATE
251
+ // INDEX when the index is already present.
252
+ await ensureIndexExists(
253
+ "idx_token_usage_owner_created",
254
+ `CREATE INDEX IF NOT EXISTS idx_token_usage_owner_created ON token_usage (owner_email, created_at)`,
255
+ );
256
+ return;
257
+ }
258
+
259
+ // SQLite (local dev): no lock problem — keep the original behaviour.
260
+ await client.execute(createSql);
261
+ // Add columns on older deployments that pre-date the label/cache
262
+ // fields. Each ALTER is wrapped so a dialect without IF NOT EXISTS
263
+ // (SQLite) still makes progress if only some columns are missing.
221
264
  for (const [col, def] of additions) {
222
265
  try {
223
- if (isPostgres()) {
224
- await client.execute(
225
- `ALTER TABLE token_usage ADD COLUMN IF NOT EXISTS ${col} ${def}`,
226
- );
227
- } else {
228
- await client.execute(
229
- `ALTER TABLE token_usage ADD COLUMN ${col} ${def}`,
230
- );
231
- }
266
+ await client.execute(
267
+ `ALTER TABLE token_usage ADD COLUMN ${col} ${def}`,
268
+ );
232
269
  } catch {
233
270
  // Column already exists — ignore
234
271
  }
235
272
  }
236
-
237
273
  // Older deployments created `created_at` as 32-bit `INTEGER`; on Postgres
238
274
  // the `Date.now()` written per run by recordUsage() overflows int4. Widen
239
275
  // it in place (no-op once done / on fresh BIGINT databases).
240
276
  await widenIntColumnsToBigInt("token_usage", ["created_at"]);
241
-
242
277
  try {
243
278
  await client.execute(
244
279
  `CREATE INDEX IF NOT EXISTS idx_token_usage_owner_created ON token_usage (owner_email, created_at)`,
@@ -15,6 +15,11 @@ import {
15
15
  safeJsonParse,
16
16
  type DbExec,
17
17
  } from "../db/client.js";
18
+ import {
19
+ ensureColumnExists,
20
+ ensureIndexExists,
21
+ ensureTableExists,
22
+ } from "../db/ddl-guard.js";
18
23
  import {
19
24
  getRequestOrgId,
20
25
  getRequestUserEmail,
@@ -459,8 +464,8 @@ export async function ensureWorkspaceConnectionsTable(): Promise<void> {
459
464
  const client = getDbExec();
460
465
  const table = workspaceConnectionsTable();
461
466
  const grantsTable = workspaceConnectionGrantsTable();
462
- await retryOnDdlRace(() =>
463
- client.execute(`
467
+
468
+ const createConnectionsSql = `
464
469
  CREATE TABLE IF NOT EXISTS ${table} (
465
470
  id TEXT PRIMARY KEY,
466
471
  provider TEXT NOT NULL DEFAULT '',
@@ -480,23 +485,8 @@ export async function ensureWorkspaceConnectionsTable(): Promise<void> {
480
485
  last_checked_at ${intType()},
481
486
  last_error TEXT
482
487
  )
483
- `),
484
- );
485
-
486
- await ensureWorkspaceConnectionColumns(client, table);
487
-
488
- await retryOnDdlRace(() =>
489
- client.execute(
490
- `CREATE INDEX IF NOT EXISTS idx_workspace_connections_scope_provider ON ${table} (org_id, owner_email, provider)`,
491
- ),
492
- );
493
- await retryOnDdlRace(() =>
494
- client.execute(
495
- `CREATE INDEX IF NOT EXISTS idx_workspace_connections_updated_at ON ${table} (updated_at)`,
496
- ),
497
- );
498
- await retryOnDdlRace(() =>
499
- client.execute(`
488
+ `;
489
+ const createGrantsSql = `
500
490
  CREATE TABLE IF NOT EXISTS ${grantsTable} (
501
491
  id TEXT PRIMARY KEY,
502
492
  connection_id TEXT NOT NULL DEFAULT '',
@@ -512,11 +502,199 @@ export async function ensureWorkspaceConnectionsTable(): Promise<void> {
512
502
  updated_at ${intType()} NOT NULL DEFAULT 0,
513
503
  last_used_at ${intType()}
514
504
  )
515
- `),
516
- );
505
+ `;
506
+
507
+ if (isPostgres()) {
508
+ // PG-guard: probe information_schema / pg_indexes first (no lock) and
509
+ // only issue DDL when the table/column/index is actually missing,
510
+ // wrapped in a transaction-scoped lock_timeout so a contended lock
511
+ // fails fast. Uses unqualified table names for the probe (the helpers
512
+ // always search the `public` schema).
513
+
514
+ // --- workspace_connections ---
515
+ await ensureTableExists("workspace_connections", createConnectionsSql);
516
+ await ensureColumnExists(
517
+ "workspace_connections",
518
+ "provider",
519
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS provider TEXT NOT NULL DEFAULT ''`,
520
+ );
521
+ await ensureColumnExists(
522
+ "workspace_connections",
523
+ "label",
524
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS label TEXT NOT NULL DEFAULT ''`,
525
+ );
526
+ await ensureColumnExists(
527
+ "workspace_connections",
528
+ "account_id",
529
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS account_id TEXT`,
530
+ );
531
+ await ensureColumnExists(
532
+ "workspace_connections",
533
+ "account_label",
534
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS account_label TEXT`,
535
+ );
536
+ await ensureColumnExists(
537
+ "workspace_connections",
538
+ "status",
539
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'connected'`,
540
+ );
541
+ await ensureColumnExists(
542
+ "workspace_connections",
543
+ "scopes_json",
544
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS scopes_json TEXT NOT NULL DEFAULT '[]'`,
545
+ );
546
+ await ensureColumnExists(
547
+ "workspace_connections",
548
+ "config_json",
549
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS config_json TEXT NOT NULL DEFAULT '{}'`,
550
+ );
551
+ await ensureColumnExists(
552
+ "workspace_connections",
553
+ "allowed_apps_json",
554
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS allowed_apps_json TEXT NOT NULL DEFAULT '[]'`,
555
+ );
556
+ await ensureColumnExists(
557
+ "workspace_connections",
558
+ "credential_refs_json",
559
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS credential_refs_json TEXT NOT NULL DEFAULT '[]'`,
560
+ );
561
+ await ensureColumnExists(
562
+ "workspace_connections",
563
+ "owner_email",
564
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS owner_email TEXT NOT NULL DEFAULT ''`,
565
+ );
566
+ await ensureColumnExists(
567
+ "workspace_connections",
568
+ "org_id",
569
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS org_id TEXT`,
570
+ );
571
+ await ensureColumnExists(
572
+ "workspace_connections",
573
+ "created_at",
574
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS created_at ${intType()} NOT NULL DEFAULT 0`,
575
+ );
576
+ await ensureColumnExists(
577
+ "workspace_connections",
578
+ "updated_at",
579
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS updated_at ${intType()} NOT NULL DEFAULT 0`,
580
+ );
581
+ await ensureColumnExists(
582
+ "workspace_connections",
583
+ "last_checked_at",
584
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS last_checked_at ${intType()}`,
585
+ );
586
+ await ensureColumnExists(
587
+ "workspace_connections",
588
+ "last_used_at",
589
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS last_used_at ${intType()}`,
590
+ );
591
+ await ensureColumnExists(
592
+ "workspace_connections",
593
+ "last_error",
594
+ `ALTER TABLE workspace_connections ADD COLUMN IF NOT EXISTS last_error TEXT`,
595
+ );
596
+ await ensureIndexExists(
597
+ "idx_workspace_connections_scope_provider",
598
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connections_scope_provider ON ${table} (org_id, owner_email, provider)`,
599
+ );
600
+ await ensureIndexExists(
601
+ "idx_workspace_connections_updated_at",
602
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connections_updated_at ON ${table} (updated_at)`,
603
+ );
517
604
 
518
- await ensureWorkspaceConnectionGrantColumns(client, grantsTable);
605
+ // --- workspace_connection_grants ---
606
+ await ensureTableExists("workspace_connection_grants", createGrantsSql);
607
+ await ensureColumnExists(
608
+ "workspace_connection_grants",
609
+ "connection_id",
610
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS connection_id TEXT NOT NULL DEFAULT ''`,
611
+ );
612
+ await ensureColumnExists(
613
+ "workspace_connection_grants",
614
+ "provider",
615
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS provider TEXT NOT NULL DEFAULT ''`,
616
+ );
617
+ await ensureColumnExists(
618
+ "workspace_connection_grants",
619
+ "app_id",
620
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS app_id TEXT NOT NULL DEFAULT ''`,
621
+ );
622
+ await ensureColumnExists(
623
+ "workspace_connection_grants",
624
+ "scopes_json",
625
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS scopes_json TEXT NOT NULL DEFAULT '[]'`,
626
+ );
627
+ await ensureColumnExists(
628
+ "workspace_connection_grants",
629
+ "config_json",
630
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS config_json TEXT NOT NULL DEFAULT '{}'`,
631
+ );
632
+ await ensureColumnExists(
633
+ "workspace_connection_grants",
634
+ "credential_refs_json",
635
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS credential_refs_json TEXT NOT NULL DEFAULT '[]'`,
636
+ );
637
+ await ensureColumnExists(
638
+ "workspace_connection_grants",
639
+ "granted_by_email",
640
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS granted_by_email TEXT NOT NULL DEFAULT ''`,
641
+ );
642
+ await ensureColumnExists(
643
+ "workspace_connection_grants",
644
+ "owner_email",
645
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS owner_email TEXT NOT NULL DEFAULT ''`,
646
+ );
647
+ await ensureColumnExists(
648
+ "workspace_connection_grants",
649
+ "org_id",
650
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS org_id TEXT`,
651
+ );
652
+ await ensureColumnExists(
653
+ "workspace_connection_grants",
654
+ "created_at",
655
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS created_at ${intType()} NOT NULL DEFAULT 0`,
656
+ );
657
+ await ensureColumnExists(
658
+ "workspace_connection_grants",
659
+ "updated_at",
660
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS updated_at ${intType()} NOT NULL DEFAULT 0`,
661
+ );
662
+ await ensureColumnExists(
663
+ "workspace_connection_grants",
664
+ "last_used_at",
665
+ `ALTER TABLE workspace_connection_grants ADD COLUMN IF NOT EXISTS last_used_at ${intType()}`,
666
+ );
667
+ await ensureIndexExists(
668
+ "idx_workspace_connection_grants_connection_app",
669
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_connection_grants_connection_app ON ${grantsTable} (connection_id, app_id)`,
670
+ );
671
+ await ensureIndexExists(
672
+ "idx_workspace_connection_grants_scope_app",
673
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connection_grants_scope_app ON ${grantsTable} (org_id, owner_email, app_id)`,
674
+ );
675
+ await ensureIndexExists(
676
+ "idx_workspace_connection_grants_updated_at",
677
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connection_grants_updated_at ON ${grantsTable} (updated_at)`,
678
+ );
679
+ return;
680
+ }
519
681
 
682
+ // SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep existing
683
+ // retryOnDdlRace + ensureColumn behaviour.
684
+ await retryOnDdlRace(() => client.execute(createConnectionsSql));
685
+ await ensureWorkspaceConnectionColumns(client, table);
686
+ await retryOnDdlRace(() =>
687
+ client.execute(
688
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connections_scope_provider ON ${table} (org_id, owner_email, provider)`,
689
+ ),
690
+ );
691
+ await retryOnDdlRace(() =>
692
+ client.execute(
693
+ `CREATE INDEX IF NOT EXISTS idx_workspace_connections_updated_at ON ${table} (updated_at)`,
694
+ ),
695
+ );
696
+ await retryOnDdlRace(() => client.execute(createGrantsSql));
697
+ await ensureWorkspaceConnectionGrantColumns(client, grantsTable);
520
698
  await retryOnDdlRace(() =>
521
699
  client.execute(
522
700
  `CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_connection_grants_connection_app ON ${grantsTable} (connection_id, app_id)`,
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Booking link previews now show the content directly on the grid background.
@@ -267,8 +267,6 @@ export function renderBookingOgImageSvg(input: BookingOgImageInput): string {
267
267
  </defs>
268
268
  <rect width="${WIDTH}" height="${HEIGHT}" fill="${BG}"/>
269
269
  <rect width="${WIDTH}" height="${HEIGHT}" fill="url(#grid)"/>
270
- <rect x="64" y="64" width="1072" height="502" rx="28" fill="${BG}" fill-opacity="0.72" stroke="${BORDER}" stroke-width="1"/>
271
- <path d="M80 154 H1120" stroke="${BORDER}"/>
272
270
  <g transform="translate(80 86)">
273
271
  <g transform="scale(0.62)">
274
272
  ${LOGO_MARK}
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Form previews now show the content directly on the grid background.
@@ -168,8 +168,6 @@ export function renderFormOgImageSvg(input: FormOgImageInput = {}): string {
168
168
  </defs>
169
169
  <rect width="${WIDTH}" height="${HEIGHT}" fill="${BG}"/>
170
170
  <rect width="${WIDTH}" height="${HEIGHT}" fill="url(#grid)"/>
171
- <rect x="64" y="64" width="1072" height="502" rx="28" fill="${BG}" fill-opacity="0.72" stroke="${BORDER}" stroke-width="1"/>
172
- <path d="M80 154 H1120" stroke="${BORDER}"/>
173
171
  <g transform="translate(80 86)">
174
172
  <g transform="scale(0.62)">
175
173
  ${LOGO_MARK}
@@ -486,8 +486,10 @@
486
486
  padding-inline-start: 0.75rem;
487
487
  }
488
488
 
489
- /* Narrow-width stand-in for the contents rail; swapped in/out by the queries below. */
489
+ /* Hidden below the rail breakpoint: the document should use the full reading
490
+ * width instead of replacing the rail with an inline contents widget. */
490
491
  .plan-document-toc-inline {
492
+ display: none;
491
493
  margin-top: 1.25rem;
492
494
  border: 1px solid var(--plan-line);
493
495
  border-radius: 0.75rem;
@@ -548,23 +550,16 @@
548
550
  list-style: none;
549
551
  }
550
552
 
551
- /* Hide the accordion once its rail shows, and in screenshot capture. */
553
+ /* Hide the accordion in screenshot capture too. */
552
554
  .plan-content-surface[data-recap-screenshot-theme] .plan-document-toc-inline {
553
555
  display: none;
554
556
  }
555
- @container plan-doc (min-width: 48rem) {
556
- .plan-content-surface:not([data-recap-screenshot-theme])
557
- .plan-document-body[data-has-toc]
558
- > .plan-document-toc-inline {
559
- display: none;
560
- }
561
- }
562
557
 
563
558
  /* Wide layout: the contents rail is a real grid column. A matching empty left
564
559
  * column balances the rail so the document itself remains visually centered. */
565
560
 
566
561
  /* Shared cell placement. Inert until the parent is a grid. */
567
- @container plan-doc (min-width: 48rem) {
562
+ @container plan-doc (min-width: 1200px) {
568
563
  .plan-content-surface:not([data-recap-screenshot-theme])
569
564
  .plan-document-body
570
565
  > header {
@@ -588,7 +583,7 @@
588
583
  }
589
584
 
590
585
  /* Document + contents rail. The empty first column balances the right rail. */
591
- @container plan-doc (min-width: 48rem) {
586
+ @container plan-doc (min-width: 1200px) {
592
587
  .plan-content-surface:not([data-recap-screenshot-theme])
593
588
  .plan-document-body[data-has-toc] {
594
589
  display: grid;
@@ -593,22 +593,25 @@ export function shouldShowPlanLoadError(input: {
593
593
  hasSelectedId: boolean;
594
594
  localPlanMode: boolean;
595
595
  hasBundle: boolean;
596
- planQueryPending: boolean;
596
+ planQueryInitialPending: boolean;
597
597
  planQueryError: boolean;
598
598
  planQueryPaused: boolean;
599
- accessStatusPending: boolean;
599
+ accessStatusInitialPending: boolean;
600
600
  accessStatusPaused: boolean;
601
601
  accessDenied: boolean;
602
602
  }): boolean {
603
603
  if (!input.hasSelectedId || input.localPlanMode || input.hasBundle) {
604
604
  return false;
605
605
  }
606
- // While a read is actively in flight, keep showing the skeleton.
607
- if (input.planQueryPending) return false;
608
606
  if (input.planQueryError) return true;
607
+ if (!input.accessStatusInitialPending && input.accessDenied) return true;
608
+ // While the first read is actively in flight, keep showing the skeleton.
609
+ // Background refetches must not hide a settled access/error card.
610
+ if (input.planQueryInitialPending || input.accessStatusInitialPending) {
611
+ return false;
612
+ }
609
613
  // Paused/stalled read that will never settle on its own input.
610
614
  if (input.planQueryPaused || input.accessStatusPaused) return true;
611
- if (!input.accessStatusPending && input.accessDenied) return true;
612
615
  return false;
613
616
  }
614
617
 
@@ -3120,17 +3123,16 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
3120
3123
  Boolean(selectedId && !bundle && !localPlanMode),
3121
3124
  );
3122
3125
  const planAccessStatus = planAccessStatusQuery.data ?? null;
3123
- const planQueryPending = planQuery.isLoading || planQuery.isFetching;
3124
- const planAccessStatusPending =
3125
- planAccessStatusQuery.isLoading || planAccessStatusQuery.isFetching;
3126
+ const planQueryInitialPending = planQuery.isLoading;
3127
+ const planAccessStatusInitialPending = planAccessStatusQuery.isLoading;
3126
3128
  const showPlanLoadError = shouldShowPlanLoadError({
3127
3129
  hasSelectedId: Boolean(selectedId),
3128
3130
  localPlanMode,
3129
3131
  hasBundle: Boolean(bundle),
3130
- planQueryPending,
3132
+ planQueryInitialPending,
3131
3133
  planQueryError: planQuery.isError,
3132
3134
  planQueryPaused: planQuery.isPaused,
3133
- accessStatusPending: planAccessStatusPending,
3135
+ accessStatusInitialPending: planAccessStatusInitialPending,
3134
3136
  accessStatusPaused: planAccessStatusQuery.isPaused,
3135
3137
  accessDenied: Boolean(planAccessStatus && !planAccessStatus.hasAccess),
3136
3138
  });
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Plan access request pages stay visible during background refreshes instead of flashing back to loading.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Plan documents keep their full reading width below 1200px and show the contents rail only on wider screens.
@@ -1 +1 @@
1
- {"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../../src/a2a/task-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA2ErE,wBAAsB,UAAU,CAC9B,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GACzB,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAUrE;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAwBtB;AAED,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,IAAI,CAAC,CAeR;AAED,wBAAsB,0BAA0B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa7E;AAED,wBAAsB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAazE;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,MAAM,EACV,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC,CAiBlB;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAsBlB;AAED,wBAAsB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAS9D;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,MAAM,EACV,MAAM,EAAE;IACN,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;CACxB,GACA,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CA4CtB;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,wBAAsB,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAgBnE"}
1
+ {"version":3,"file":"task-store.d.ts","sourceRoot":"","sources":["../../src/a2a/task-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAgGrE,wBAAsB,UAAU,CAC9B,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GACzB,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAUrE;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAwBtB;AAED,wBAAsB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACjE,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC9C,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,IAAI,CAAC,CAeR;AAED,wBAAsB,0BAA0B,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa7E;AAED,wBAAsB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAazE;AAED,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,MAAM,EACV,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC,CAiBlB;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAsBlB;AAED,wBAAsB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAS9D;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,MAAM,EACV,MAAM,EAAE;IACN,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;CACxB,GACA,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CA4CtB;AAED,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,wBAAsB,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAgBnE"}
@@ -1,11 +1,12 @@
1
1
  import crypto from "crypto";
2
- import { getDbExec, intType } from "../db/client.js";
2
+ import { getDbExec, intType, isPostgres } from "../db/client.js";
3
+ import { ensureTableExists, ensureColumnExists } from "../db/ddl-guard.js";
3
4
  let _initPromise;
4
5
  async function ensureTable() {
5
6
  if (!_initPromise) {
6
7
  _initPromise = (async () => {
7
8
  const client = getDbExec();
8
- await client.execute(`
9
+ const createSql = `
9
10
  CREATE TABLE IF NOT EXISTS a2a_tasks (
10
11
  id TEXT PRIMARY KEY,
11
12
  context_id TEXT,
@@ -18,7 +19,22 @@ async function ensureTable() {
18
19
  created_at ${intType()} NOT NULL,
19
20
  updated_at ${intType()} NOT NULL
20
21
  )
21
- `);
22
+ `;
23
+ if (isPostgres()) {
24
+ // PG-guard: probe information_schema before issuing DDL to avoid ACCESS
25
+ // EXCLUSIVE lock contention in fresh background-worker processes.
26
+ await ensureTableExists("a2a_tasks", createSql);
27
+ // Additive migration: owner_email column. Bound to the JWT-verified
28
+ // caller at task-creation time so handleGet / handleCancel can reject
29
+ // mismatched callers (the IDOR class fixed in PR #369). Existing rows
30
+ // have NULL owner_email and remain accessible to legacy callers via
31
+ // the legacy-token apiKeyEnv path; new rows are scoped from this point
32
+ // forward.
33
+ await ensureColumnExists("a2a_tasks", "owner_email", `ALTER TABLE a2a_tasks ADD COLUMN IF NOT EXISTS owner_email TEXT`);
34
+ return;
35
+ }
36
+ // SQLite (local dev): no lock problem — keep the original behaviour.
37
+ await client.execute(createSql);
22
38
  // Additive migration: owner_email column. Bound to the JWT-verified
23
39
  // caller at task-creation time so handleGet / handleCancel can reject
24
40
  // mismatched callers (the IDOR class fixed in PR #369). Existing rows