@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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +29 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/task-store.ts +25 -3
- package/corpus/core/src/agent/harness/store.ts +89 -55
- package/corpus/core/src/agent/observational-memory/store.ts +40 -14
- package/corpus/core/src/agent/run-store.ts +116 -59
- package/corpus/core/src/application-state/store.ts +36 -2
- package/corpus/core/src/audit/store.ts +37 -6
- package/corpus/core/src/browser-sessions/store.ts +31 -12
- package/corpus/core/src/chat-threads/store.ts +67 -3
- package/corpus/core/src/checkpoints/store.ts +14 -3
- package/corpus/core/src/cli/create.ts +9 -0
- package/corpus/core/src/collab/storage.ts +20 -2
- package/corpus/core/src/db/ddl-guard.ts +112 -0
- package/corpus/core/src/extensions/store.ts +76 -3
- package/corpus/core/src/integrations/a2a-continuations-store.ts +69 -26
- package/corpus/core/src/integrations/config-store.ts +19 -18
- package/corpus/core/src/integrations/pending-tasks-store.ts +40 -16
- package/corpus/core/src/integrations/remote-commands-store.ts +37 -23
- package/corpus/core/src/integrations/remote-devices-store.ts +64 -20
- package/corpus/core/src/integrations/remote-push-store.ts +75 -35
- package/corpus/core/src/integrations/remote-run-events-store.ts +38 -12
- package/corpus/core/src/integrations/thread-mapping-store.ts +11 -2
- package/corpus/core/src/mcp/connect-store.ts +53 -16
- package/corpus/core/src/mcp/oauth-store.ts +32 -7
- package/corpus/core/src/notifications/store.ts +20 -4
- package/corpus/core/src/oauth-tokens/store.ts +45 -2
- package/corpus/core/src/observability/store.ts +154 -47
- package/corpus/core/src/progress/store.ts +36 -21
- package/corpus/core/src/provider-api/corpus-jobs-store.ts +25 -5
- package/corpus/core/src/provider-api/custom-registry.ts +15 -4
- package/corpus/core/src/provider-api/quota-governor.ts +15 -3
- package/corpus/core/src/provider-api/staged-datasets-store.ts +22 -5
- package/corpus/core/src/resources/store.ts +66 -31
- package/corpus/core/src/secrets/storage.ts +18 -22
- package/corpus/core/src/server/agent-teams-run-queue.ts +22 -10
- package/corpus/core/src/server/auth.ts +22 -4
- package/corpus/core/src/server/better-auth-instance.ts +49 -15
- package/corpus/core/src/server/embed-session.ts +19 -4
- package/corpus/core/src/server/identity-sso-store.ts +40 -11
- package/corpus/core/src/server/recap-image-store.ts +28 -9
- package/corpus/core/src/settings/store.ts +14 -19
- package/corpus/core/src/usage/store.ts +51 -16
- package/corpus/core/src/workspace-connections/store.ts +200 -22
- package/corpus/templates/calendar/changelog/2026-06-25-booking-link-previews-now-show-the-content-directly-on-the-g.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-og-image.ts +0 -2
- package/corpus/templates/forms/changelog/2026-06-25-form-previews-now-show-the-content-directly-on-the-grid-back.md +6 -0
- package/corpus/templates/forms/server/lib/form-og-image.ts +0 -2
- package/corpus/templates/plan/app/global.css +6 -11
- package/corpus/templates/plan/app/pages/PlansPage.tsx +12 -10
- package/corpus/templates/plan/changelog/2026-06-25-plan-access-request-pages-stay-visible-during-background-ref.md +6 -0
- package/corpus/templates/plan/changelog/2026-06-25-plan-documents-keep-their-full-reading-width-below-1200px-an.md +6 -0
- package/dist/a2a/task-store.d.ts.map +1 -1
- package/dist/a2a/task-store.js +19 -3
- package/dist/a2a/task-store.js.map +1 -1
- package/dist/agent/harness/store.d.ts.map +1 -1
- package/dist/agent/harness/store.js +63 -34
- package/dist/agent/harness/store.js.map +1 -1
- package/dist/agent/observational-memory/store.d.ts.map +1 -1
- package/dist/agent/observational-memory/store.js +30 -8
- package/dist/agent/observational-memory/store.js.map +1 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +103 -46
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +28 -2
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/store.d.ts.map +1 -1
- package/dist/audit/store.js +18 -3
- package/dist/audit/store.js.map +1 -1
- package/dist/browser-sessions/store.d.ts.map +1 -1
- package/dist/browser-sessions/store.js +22 -7
- package/dist/browser-sessions/store.js.map +1 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +51 -3
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/checkpoints/store.d.ts.map +1 -1
- package/dist/checkpoints/store.js +12 -3
- package/dist/checkpoints/store.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +9 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/storage.d.ts.map +1 -1
- package/dist/collab/storage.js +14 -2
- package/dist/collab/storage.js.map +1 -1
- package/dist/db/ddl-guard.d.ts +63 -0
- package/dist/db/ddl-guard.d.ts.map +1 -1
- package/dist/db/ddl-guard.js +85 -0
- package/dist/db/ddl-guard.js.map +1 -1
- package/dist/extensions/store.d.ts.map +1 -1
- package/dist/extensions/store.js +32 -3
- package/dist/extensions/store.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +44 -24
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +17 -11
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +24 -16
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/remote-commands-store.d.ts.map +1 -1
- package/dist/integrations/remote-commands-store.js +29 -21
- package/dist/integrations/remote-commands-store.js.map +1 -1
- package/dist/integrations/remote-devices-store.d.ts.map +1 -1
- package/dist/integrations/remote-devices-store.js +33 -18
- package/dist/integrations/remote-devices-store.js.map +1 -1
- package/dist/integrations/remote-push-store.d.ts.map +1 -1
- package/dist/integrations/remote-push-store.js +53 -31
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/integrations/remote-run-events-store.d.ts.map +1 -1
- package/dist/integrations/remote-run-events-store.js +26 -10
- package/dist/integrations/remote-run-events-store.js.map +1 -1
- package/dist/integrations/thread-mapping-store.d.ts.map +1 -1
- package/dist/integrations/thread-mapping-store.js +10 -2
- package/dist/integrations/thread-mapping-store.js.map +1 -1
- package/dist/mcp/connect-store.d.ts.map +1 -1
- package/dist/mcp/connect-store.js +34 -16
- package/dist/mcp/connect-store.js.map +1 -1
- package/dist/mcp/oauth-store.d.ts.map +1 -1
- package/dist/mcp/oauth-store.js +22 -7
- package/dist/mcp/oauth-store.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/notifications/store.d.ts.map +1 -1
- package/dist/notifications/store.js +15 -3
- package/dist/notifications/store.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +33 -2
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/observability/store.d.ts.map +1 -1
- package/dist/observability/store.js +72 -29
- package/dist/observability/store.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/progress/store.d.ts.map +1 -1
- package/dist/progress/store.js +31 -16
- package/dist/progress/store.js.map +1 -1
- package/dist/provider-api/corpus-jobs-store.d.ts.map +1 -1
- package/dist/provider-api/corpus-jobs-store.js +16 -5
- package/dist/provider-api/corpus-jobs-store.js.map +1 -1
- package/dist/provider-api/custom-registry.d.ts.map +1 -1
- package/dist/provider-api/custom-registry.js +14 -4
- package/dist/provider-api/custom-registry.js.map +1 -1
- package/dist/provider-api/quota-governor.d.ts.map +1 -1
- package/dist/provider-api/quota-governor.js +12 -3
- package/dist/provider-api/quota-governor.js.map +1 -1
- package/dist/provider-api/staged-datasets-store.d.ts.map +1 -1
- package/dist/provider-api/staged-datasets-store.js +16 -5
- package/dist/provider-api/staged-datasets-store.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +56 -25
- package/dist/resources/store.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +10 -14
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams-run-queue.d.ts.map +1 -1
- package/dist/server/agent-teams-run-queue.js +15 -4
- package/dist/server/agent-teams-run-queue.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +16 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +55 -21
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/embed-session.d.ts.map +1 -1
- package/dist/server/embed-session.js +15 -4
- package/dist/server/embed-session.js.map +1 -1
- package/dist/server/identity-sso-store.d.ts.map +1 -1
- package/dist/server/identity-sso-store.js +30 -11
- package/dist/server/identity-sso-store.js.map +1 -1
- package/dist/server/recap-image-store.d.ts.map +1 -1
- package/dist/server/recap-image-store.js +21 -7
- package/dist/server/recap-image-store.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +11 -13
- package/dist/settings/store.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +36 -11
- package/dist/usage/store.js.map +1 -1
- package/dist/workspace-connections/store.d.ts.map +1 -1
- package/dist/workspace-connections/store.js +57 -7
- package/dist/workspace-connections/store.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.77.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 12545bc: Keep standalone scaffold installs stable when fresh Sentry transitive packages are published.
|
|
8
|
+
|
|
9
|
+
## 0.77.9
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 3eeec7f: Generalize the `ensureTable()` DDL guard to ALL on-demand schema-init paths
|
|
14
|
+
(~36 stores: agent run-store, application-state, chat-threads, usage,
|
|
15
|
+
oauth-tokens, resources, observability, integrations, provider-api, mcp,
|
|
16
|
+
workspace-connections, extensions, audit, harness, a2a, notifications,
|
|
17
|
+
browser-sessions, auth/sessions, agent-teams run-queue, better-auth, and more).
|
|
18
|
+
|
|
19
|
+
Every `ensureTable` now probes `information_schema`/`pg_indexes` before issuing
|
|
20
|
+
`CREATE TABLE`/`ALTER TABLE ADD COLUMN`/`CREATE INDEX`, so the already-migrated
|
|
21
|
+
hot path takes NO `ACCESS EXCLUSIVE` lock on Postgres. Any DDL that must run is
|
|
22
|
+
wrapped in a transaction-scoped `SET LOCAL lock_timeout` (never leaks onto the
|
|
23
|
+
pooled connection), and a swallowed lock-timeout triggers a RE-PROBE: if the
|
|
24
|
+
schema is still missing it throws (so the per-store init memo rejects and retries)
|
|
25
|
+
rather than memoizing init success against absent schema. This fixes
|
|
26
|
+
background-function (durable agent-chat) workers hanging indefinitely on the
|
|
27
|
+
first-touch DDL lock of any table on shared Neon. Shared helpers live in
|
|
28
|
+
`db/ddl-guard.ts` (`ensureSchemaObject`/`ensureTableExists`/`ensureColumnExists`/
|
|
29
|
+
`ensureIndexExists`). SQLite (local dev) behavior is unchanged. CREATE SQL that
|
|
30
|
+
references `intType()` is built at runtime, not module scope.
|
|
31
|
+
|
|
3
32
|
## 0.77.8
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.77.
|
|
3
|
+
"version": "0.77.10",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
2
|
|
|
3
|
-
import { getDbExec, intType } from "../db/client.js";
|
|
3
|
+
import { getDbExec, intType, isPostgres } from "../db/client.js";
|
|
4
|
+
import { ensureTableExists, ensureColumnExists } from "../db/ddl-guard.js";
|
|
4
5
|
import type { Task, Message, TaskState, Artifact } from "./types.js";
|
|
5
6
|
|
|
6
7
|
let _initPromise: Promise<void> | undefined;
|
|
@@ -9,7 +10,7 @@ async function ensureTable(): Promise<void> {
|
|
|
9
10
|
if (!_initPromise) {
|
|
10
11
|
_initPromise = (async () => {
|
|
11
12
|
const client = getDbExec();
|
|
12
|
-
|
|
13
|
+
const createSql = `
|
|
13
14
|
CREATE TABLE IF NOT EXISTS a2a_tasks (
|
|
14
15
|
id TEXT PRIMARY KEY,
|
|
15
16
|
context_id TEXT,
|
|
@@ -22,7 +23,28 @@ async function ensureTable(): Promise<void> {
|
|
|
22
23
|
created_at ${intType()} NOT NULL,
|
|
23
24
|
updated_at ${intType()} NOT NULL
|
|
24
25
|
)
|
|
25
|
-
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
if (isPostgres()) {
|
|
29
|
+
// PG-guard: probe information_schema before issuing DDL to avoid ACCESS
|
|
30
|
+
// EXCLUSIVE lock contention in fresh background-worker processes.
|
|
31
|
+
await ensureTableExists("a2a_tasks", createSql);
|
|
32
|
+
// Additive migration: owner_email column. Bound to the JWT-verified
|
|
33
|
+
// caller at task-creation time so handleGet / handleCancel can reject
|
|
34
|
+
// mismatched callers (the IDOR class fixed in PR #369). Existing rows
|
|
35
|
+
// have NULL owner_email and remain accessible to legacy callers via
|
|
36
|
+
// the legacy-token apiKeyEnv path; new rows are scoped from this point
|
|
37
|
+
// forward.
|
|
38
|
+
await ensureColumnExists(
|
|
39
|
+
"a2a_tasks",
|
|
40
|
+
"owner_email",
|
|
41
|
+
`ALTER TABLE a2a_tasks ADD COLUMN IF NOT EXISTS owner_email TEXT`,
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
47
|
+
await client.execute(createSql);
|
|
26
48
|
// Additive migration: owner_email column. Bound to the JWT-verified
|
|
27
49
|
// caller at task-creation time so handleGet / handleCancel can reject
|
|
28
50
|
// mismatched callers (the IDOR class fixed in PR #369). Existing rows
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { getDbExec, intType, isPostgres } from "../../db/client.js";
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "../../db/client.js";
|
|
3
|
+
ensureColumnExists,
|
|
4
|
+
ensureIndexExists,
|
|
5
|
+
ensureTableExists,
|
|
6
|
+
} from "../../db/ddl-guard.js";
|
|
7
7
|
|
|
8
8
|
export type AgentHarnessSessionStatus =
|
|
9
9
|
| "running"
|
|
@@ -50,26 +50,66 @@ export async function ensureAgentHarnessSessionTables(): Promise<void> {
|
|
|
50
50
|
if (!initPromise) {
|
|
51
51
|
initPromise = (async () => {
|
|
52
52
|
const client = getDbExec();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
53
|
+
const createSql = `
|
|
54
|
+
CREATE TABLE IF NOT EXISTS agent_harness_sessions (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
harness_name TEXT NOT NULL,
|
|
57
|
+
thread_id TEXT NOT NULL,
|
|
58
|
+
run_id TEXT,
|
|
59
|
+
provider_session_id TEXT,
|
|
60
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
61
|
+
resume_state TEXT,
|
|
62
|
+
workspace_ref TEXT,
|
|
63
|
+
pending_approval TEXT,
|
|
64
|
+
owner_email TEXT,
|
|
65
|
+
org_id TEXT,
|
|
66
|
+
created_at ${intType()} NOT NULL,
|
|
67
|
+
updated_at ${intType()} NOT NULL,
|
|
68
|
+
stopped_at ${intType()}
|
|
69
|
+
)
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
if (isPostgres()) {
|
|
73
|
+
// PG-guard: probe information_schema / pg_indexes before issuing DDL to
|
|
74
|
+
// avoid ACCESS EXCLUSIVE lock contention in fresh background-worker processes.
|
|
75
|
+
await ensureTableExists("agent_harness_sessions", createSql);
|
|
76
|
+
for (const col of [
|
|
77
|
+
"run_id",
|
|
78
|
+
"provider_session_id",
|
|
79
|
+
"resume_state",
|
|
80
|
+
"workspace_ref",
|
|
81
|
+
"pending_approval",
|
|
82
|
+
"owner_email",
|
|
83
|
+
"org_id",
|
|
84
|
+
] as const) {
|
|
85
|
+
await ensureColumnExists(
|
|
86
|
+
"agent_harness_sessions",
|
|
87
|
+
col,
|
|
88
|
+
`ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS ${col} TEXT`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
await ensureColumnExists(
|
|
92
|
+
"agent_harness_sessions",
|
|
93
|
+
"stopped_at",
|
|
94
|
+
`ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS stopped_at ${intType()}`,
|
|
95
|
+
);
|
|
96
|
+
await ensureIndexExists(
|
|
97
|
+
"idx_agent_harness_sessions_thread",
|
|
98
|
+
`CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_thread ON agent_harness_sessions(thread_id, updated_at)`,
|
|
99
|
+
);
|
|
100
|
+
await ensureIndexExists(
|
|
101
|
+
"idx_agent_harness_sessions_status",
|
|
102
|
+
`CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_status ON agent_harness_sessions(status, updated_at)`,
|
|
103
|
+
);
|
|
104
|
+
await ensureIndexExists(
|
|
105
|
+
"idx_agent_harness_sessions_owner",
|
|
106
|
+
`CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_owner ON agent_harness_sessions(owner_email, updated_at)`,
|
|
107
|
+
);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
112
|
+
await client.execute(createSql);
|
|
73
113
|
for (const col of [
|
|
74
114
|
"run_id",
|
|
75
115
|
"provider_session_id",
|
|
@@ -80,47 +120,41 @@ export async function ensureAgentHarnessSessionTables(): Promise<void> {
|
|
|
80
120
|
"org_id",
|
|
81
121
|
] as const) {
|
|
82
122
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
);
|
|
87
|
-
} else {
|
|
88
|
-
await client.execute(
|
|
89
|
-
`ALTER TABLE agent_harness_sessions ADD COLUMN ${col} TEXT`,
|
|
90
|
-
);
|
|
91
|
-
}
|
|
123
|
+
await client.execute(
|
|
124
|
+
`ALTER TABLE agent_harness_sessions ADD COLUMN ${col} TEXT`,
|
|
125
|
+
);
|
|
92
126
|
} catch {
|
|
93
127
|
// Column already exists.
|
|
94
128
|
}
|
|
95
129
|
}
|
|
96
130
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
);
|
|
101
|
-
} else {
|
|
102
|
-
await client.execute(
|
|
103
|
-
`ALTER TABLE agent_harness_sessions ADD COLUMN stopped_at ${intType()}`,
|
|
104
|
-
);
|
|
105
|
-
}
|
|
131
|
+
await client.execute(
|
|
132
|
+
`ALTER TABLE agent_harness_sessions ADD COLUMN stopped_at ${intType()}`,
|
|
133
|
+
);
|
|
106
134
|
} catch {
|
|
107
135
|
// Column already exists.
|
|
108
136
|
}
|
|
109
|
-
|
|
110
|
-
client.execute(
|
|
137
|
+
try {
|
|
138
|
+
await client.execute(
|
|
111
139
|
`CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_thread ON agent_harness_sessions(thread_id, updated_at)`,
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
140
|
+
);
|
|
141
|
+
} catch {
|
|
142
|
+
// Index already exists.
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
await client.execute(
|
|
116
146
|
`CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_status ON agent_harness_sessions(status, updated_at)`,
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
);
|
|
148
|
+
} catch {
|
|
149
|
+
// Index already exists.
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await client.execute(
|
|
121
153
|
`CREATE INDEX IF NOT EXISTS idx_agent_harness_sessions_owner ON agent_harness_sessions(owner_email, updated_at)`,
|
|
122
|
-
)
|
|
123
|
-
|
|
154
|
+
);
|
|
155
|
+
} catch {
|
|
156
|
+
// Index already exists.
|
|
157
|
+
}
|
|
124
158
|
})().catch((err) => {
|
|
125
159
|
initPromise = undefined;
|
|
126
160
|
throw err;
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
* runtime even before the migration plugin is registered.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { getDbExec } from "../../db/client.js";
|
|
16
|
-
import {
|
|
15
|
+
import { getDbExec, isPostgres } from "../../db/client.js";
|
|
16
|
+
import { ensureTableExists, ensureIndexExists } from "../../db/ddl-guard.js";
|
|
17
17
|
import type {
|
|
18
18
|
ObservationalMemoryEntry,
|
|
19
19
|
ObservationalMemoryOwner,
|
|
@@ -27,8 +27,7 @@ async function ensureTable(): Promise<void> {
|
|
|
27
27
|
tableReady = (async () => {
|
|
28
28
|
const client = getDbExec();
|
|
29
29
|
const intType = isPostgres() ? "BIGINT" : "INTEGER";
|
|
30
|
-
|
|
31
|
-
`CREATE TABLE IF NOT EXISTS observational_memory (
|
|
30
|
+
const createSql = `CREATE TABLE IF NOT EXISTS observational_memory (
|
|
32
31
|
id TEXT PRIMARY KEY,
|
|
33
32
|
thread_id TEXT NOT NULL,
|
|
34
33
|
tier TEXT NOT NULL,
|
|
@@ -42,16 +41,43 @@ async function ensureTable(): Promise<void> {
|
|
|
42
41
|
owner_email TEXT NOT NULL,
|
|
43
42
|
org_id TEXT,
|
|
44
43
|
visibility TEXT NOT NULL DEFAULT 'private'
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
)`;
|
|
45
|
+
|
|
46
|
+
if (isPostgres()) {
|
|
47
|
+
// PG-guard: probe information_schema / pg_indexes before issuing DDL to
|
|
48
|
+
// avoid ACCESS EXCLUSIVE lock contention in fresh background-worker processes.
|
|
49
|
+
await ensureTableExists("observational_memory", createSql);
|
|
50
|
+
await ensureIndexExists(
|
|
51
|
+
"observational_memory_thread_tier_idx",
|
|
52
|
+
`CREATE INDEX IF NOT EXISTS observational_memory_thread_tier_idx
|
|
53
|
+
ON observational_memory(thread_id, tier, created_at)`,
|
|
54
|
+
);
|
|
55
|
+
await ensureIndexExists(
|
|
56
|
+
"observational_memory_thread_owner_idx",
|
|
57
|
+
`CREATE INDEX IF NOT EXISTS observational_memory_thread_owner_idx
|
|
58
|
+
ON observational_memory(thread_id, owner_email)`,
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
64
|
+
await client.execute(createSql);
|
|
65
|
+
try {
|
|
66
|
+
await client.execute(
|
|
67
|
+
`CREATE INDEX IF NOT EXISTS observational_memory_thread_tier_idx
|
|
68
|
+
ON observational_memory(thread_id, tier, created_at)`,
|
|
69
|
+
);
|
|
70
|
+
} catch {
|
|
71
|
+
// Index already exists.
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await client.execute(
|
|
75
|
+
`CREATE INDEX IF NOT EXISTS observational_memory_thread_owner_idx
|
|
76
|
+
ON observational_memory(thread_id, owner_email)`,
|
|
77
|
+
);
|
|
78
|
+
} catch {
|
|
79
|
+
// Index already exists.
|
|
80
|
+
}
|
|
55
81
|
})().catch((err) => {
|
|
56
82
|
// Reset so a transient failure (e.g. SQLITE_BUSY on HMR) can retry.
|
|
57
83
|
tableReady = null;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* reliable reconnection after page refreshes.
|
|
5
5
|
*/
|
|
6
6
|
import { getDbExec, intType, isPostgres } from "../db/client.js";
|
|
7
|
+
import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
7
8
|
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
8
9
|
import { captureError } from "../server/capture-error.js";
|
|
9
10
|
import type { AgentChatEvent } from "./types.js";
|
|
@@ -82,7 +83,10 @@ async function ensureRunTables(): Promise<void> {
|
|
|
82
83
|
if (!_initPromise) {
|
|
83
84
|
_initPromise = (async () => {
|
|
84
85
|
const client = getDbExec();
|
|
85
|
-
|
|
86
|
+
|
|
87
|
+
// Shared CREATE SQL strings — referenced by both the Postgres and SQLite
|
|
88
|
+
// branches so the column definitions stay in one place.
|
|
89
|
+
const agentRunsCreateSql = `
|
|
86
90
|
CREATE TABLE IF NOT EXISTS agent_runs (
|
|
87
91
|
id TEXT PRIMARY KEY,
|
|
88
92
|
thread_id TEXT NOT NULL,
|
|
@@ -98,30 +102,117 @@ async function ensureRunTables(): Promise<void> {
|
|
|
98
102
|
dispatch_mode TEXT,
|
|
99
103
|
diag_stage TEXT
|
|
100
104
|
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
`;
|
|
106
|
+
const agentRunEventsCreateSql = `
|
|
107
|
+
CREATE TABLE IF NOT EXISTS agent_run_events (
|
|
108
|
+
run_id TEXT NOT NULL,
|
|
109
|
+
seq ${intType()} NOT NULL,
|
|
110
|
+
event_data TEXT NOT NULL,
|
|
111
|
+
PRIMARY KEY (run_id, seq)
|
|
112
|
+
)
|
|
113
|
+
`;
|
|
114
|
+
// Tool-call result ledger: persists the outcome of write tool calls that
|
|
115
|
+
// completed AFTER their chunk was abandoned (zombie completions). A
|
|
116
|
+
// resumed continuation can recover the real result by matching
|
|
117
|
+
// thread_id + tool_key (name:stableInputHash) instead of re-executing
|
|
118
|
+
// the side effect. Entries are scoped to the thread and expire with it.
|
|
119
|
+
const agentToolLedgerCreateSql = `
|
|
120
|
+
CREATE TABLE IF NOT EXISTS agent_tool_ledger (
|
|
121
|
+
thread_id TEXT NOT NULL,
|
|
122
|
+
tool_key TEXT NOT NULL,
|
|
123
|
+
result_summary TEXT NOT NULL,
|
|
124
|
+
completed_at ${intType()} NOT NULL,
|
|
125
|
+
PRIMARY KEY (thread_id, tool_key)
|
|
126
|
+
)
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
if (isPostgres()) {
|
|
130
|
+
// Hot path: in production the tables and all additive columns are
|
|
131
|
+
// virtually always already present. Issuing `CREATE TABLE`/`ALTER TABLE
|
|
132
|
+
// ADD COLUMN` still takes an ACCESS EXCLUSIVE lock — which, in a fresh
|
|
133
|
+
// background-worker process behind a concurrent connection on the shared
|
|
134
|
+
// Neon DB, can block ~indefinitely. So check `information_schema` first
|
|
135
|
+
// (plain reads, no lock) and run DDL ONLY for what is actually missing.
|
|
136
|
+
// The `ensureTableExists` / `ensureColumnExists` wrappers probe →
|
|
137
|
+
// guarded-DDL (bounded `lock_timeout`) → re-probe, and THROW if the
|
|
138
|
+
// schema is still missing after a swallowed lock-timeout so a poisoned
|
|
139
|
+
// init never memoizes success against absent schema (the `_initPromise`
|
|
140
|
+
// rejects and the next call retries).
|
|
141
|
+
await ensureTableExists("agent_runs", agentRunsCreateSql);
|
|
142
|
+
// Additive columns — all listed in the CREATE TABLE above, so on a
|
|
143
|
+
// fresh DB they already exist after the CREATE and these checks are
|
|
144
|
+
// instant short-circuits. On an older deployment that predates a
|
|
145
|
+
// column, the wrapper issues one bounded ALTER.
|
|
146
|
+
//
|
|
147
|
+
// Backfill heartbeat_at on older deployments.
|
|
148
|
+
// heartbeat_at = "the producer process is alive" (bumped on a timer).
|
|
149
|
+
// last_progress_at = "the agent is actually emitting events" (bumped on
|
|
150
|
+
// each emit). The gap between them is the stuck-detector signal.
|
|
151
|
+
for (const [col, colType] of [
|
|
152
|
+
["heartbeat_at", intType()],
|
|
153
|
+
["abort_reason", "TEXT"],
|
|
154
|
+
["last_progress_at", intType()],
|
|
155
|
+
// Backfill turn_id / error_code / error_detail.
|
|
156
|
+
// turn_id = stable identity for one logical assistant turn that may
|
|
157
|
+
// span several continuation runs, so the durable record
|
|
158
|
+
// can be folded across runs instead of dropped per-run.
|
|
159
|
+
// error_code / error_detail = terminal failure classification captured
|
|
160
|
+
// at completion so errored/cut-off runs are queryable for
|
|
161
|
+
// pattern analysis (see listErroredRuns).
|
|
162
|
+
// dispatch_mode marks how a run was started: NULL/"foreground" for the
|
|
163
|
+
// normal synchronous path, "background" for a run dispatched into a
|
|
164
|
+
// Netlify background function. The reaper/claim widen the stale window
|
|
165
|
+
// for background rows so a slow cold-start isn't falsely reaped.
|
|
166
|
+
// diag_stage records the last reached pipeline stage (+ any error) for a
|
|
167
|
+
// background-dispatched run so a silent worker death is DIAGNOSABLE from
|
|
168
|
+
// the client (/runs/active surfaces it) without reading the unreadable
|
|
169
|
+
// Netlify background-function logs. See recordRunDiagnostic.
|
|
170
|
+
["turn_id", "TEXT"],
|
|
171
|
+
["error_code", "TEXT"],
|
|
172
|
+
["error_detail", "TEXT"],
|
|
173
|
+
["dispatch_mode", "TEXT"],
|
|
174
|
+
["diag_stage", "TEXT"],
|
|
175
|
+
] as const) {
|
|
176
|
+
await ensureColumnExists(
|
|
177
|
+
"agent_runs",
|
|
178
|
+
col,
|
|
179
|
+
`ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS ${col} ${colType}`,
|
|
114
180
|
);
|
|
115
181
|
}
|
|
182
|
+
await ensureTableExists("agent_run_events", agentRunEventsCreateSql);
|
|
183
|
+
await ensureTableExists("agent_tool_ledger", agentToolLedgerCreateSql);
|
|
184
|
+
// Widen millisecond-timestamp columns that older deployments created as
|
|
185
|
+
// 32-bit `INTEGER`. `insertRun()` writes `Date.now()` into `started_at`
|
|
186
|
+
// on every turn, so an int4 column makes every agent prompt fail on
|
|
187
|
+
// Postgres with "value … is out of range for type integer". No-op once
|
|
188
|
+
// widened (and on fresh DBs that already use BIGINT). See
|
|
189
|
+
// widenIntColumnsToBigInt.
|
|
190
|
+
await widenIntColumnsToBigInt("agent_runs", [
|
|
191
|
+
"started_at",
|
|
192
|
+
"completed_at",
|
|
193
|
+
"heartbeat_at",
|
|
194
|
+
"last_progress_at",
|
|
195
|
+
]);
|
|
196
|
+
await widenIntColumnsToBigInt("agent_tool_ledger", ["completed_at"]);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// SQLite (local dev): no ACCESS EXCLUSIVE lock problem — keep the
|
|
201
|
+
// original create-then-additive-alter behaviour. SQLite has no
|
|
202
|
+
// `ADD COLUMN IF NOT EXISTS`, so the ALTERs stay wrapped in try/catch.
|
|
203
|
+
await client.execute(agentRunsCreateSql);
|
|
204
|
+
// Backfill heartbeat_at on older deployments.
|
|
205
|
+
try {
|
|
206
|
+
await client.execute(
|
|
207
|
+
`ALTER TABLE agent_runs ADD COLUMN heartbeat_at ${intType()}`,
|
|
208
|
+
);
|
|
116
209
|
} catch {
|
|
117
210
|
// Column already exists — ignore
|
|
118
211
|
}
|
|
119
212
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
);
|
|
124
|
-
}
|
|
213
|
+
await client.execute(
|
|
214
|
+
`ALTER TABLE agent_runs ADD COLUMN abort_reason TEXT`,
|
|
215
|
+
);
|
|
125
216
|
} catch {
|
|
126
217
|
// Column already exists — ignore
|
|
127
218
|
}
|
|
@@ -130,15 +221,9 @@ async function ensureRunTables(): Promise<void> {
|
|
|
130
221
|
// last_progress_at = "the agent is actually emitting events" (bumped on
|
|
131
222
|
// each emit). The gap between them is the stuck-detector signal.
|
|
132
223
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
);
|
|
137
|
-
} else {
|
|
138
|
-
await client.execute(
|
|
139
|
-
`ALTER TABLE agent_runs ADD COLUMN last_progress_at ${intType()}`,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
224
|
+
await client.execute(
|
|
225
|
+
`ALTER TABLE agent_runs ADD COLUMN last_progress_at ${intType()}`,
|
|
226
|
+
);
|
|
142
227
|
} catch {
|
|
143
228
|
// Column already exists — ignore
|
|
144
229
|
}
|
|
@@ -165,41 +250,13 @@ async function ensureRunTables(): Promise<void> {
|
|
|
165
250
|
"diag_stage",
|
|
166
251
|
] as const) {
|
|
167
252
|
try {
|
|
168
|
-
|
|
169
|
-
await client.execute(
|
|
170
|
-
`ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS ${col} TEXT`,
|
|
171
|
-
);
|
|
172
|
-
} else {
|
|
173
|
-
await client.execute(
|
|
174
|
-
`ALTER TABLE agent_runs ADD COLUMN ${col} TEXT`,
|
|
175
|
-
);
|
|
176
|
-
}
|
|
253
|
+
await client.execute(`ALTER TABLE agent_runs ADD COLUMN ${col} TEXT`);
|
|
177
254
|
} catch {
|
|
178
255
|
// Column already exists — ignore
|
|
179
256
|
}
|
|
180
257
|
}
|
|
181
|
-
await client.execute(
|
|
182
|
-
|
|
183
|
-
run_id TEXT NOT NULL,
|
|
184
|
-
seq ${intType()} NOT NULL,
|
|
185
|
-
event_data TEXT NOT NULL,
|
|
186
|
-
PRIMARY KEY (run_id, seq)
|
|
187
|
-
)
|
|
188
|
-
`);
|
|
189
|
-
// Tool-call result ledger: persists the outcome of write tool calls that
|
|
190
|
-
// completed AFTER their chunk was abandoned (zombie completions). A
|
|
191
|
-
// resumed continuation can recover the real result by matching
|
|
192
|
-
// thread_id + tool_key (name:stableInputHash) instead of re-executing
|
|
193
|
-
// the side effect. Entries are scoped to the thread and expire with it.
|
|
194
|
-
await client.execute(`
|
|
195
|
-
CREATE TABLE IF NOT EXISTS agent_tool_ledger (
|
|
196
|
-
thread_id TEXT NOT NULL,
|
|
197
|
-
tool_key TEXT NOT NULL,
|
|
198
|
-
result_summary TEXT NOT NULL,
|
|
199
|
-
completed_at ${intType()} NOT NULL,
|
|
200
|
-
PRIMARY KEY (thread_id, tool_key)
|
|
201
|
-
)
|
|
202
|
-
`);
|
|
258
|
+
await client.execute(agentRunEventsCreateSql);
|
|
259
|
+
await client.execute(agentToolLedgerCreateSql);
|
|
203
260
|
// Widen millisecond-timestamp columns that older deployments created as
|
|
204
261
|
// 32-bit `INTEGER`. `insertRun()` writes `Date.now()` into `started_at`
|
|
205
262
|
// on every turn, so an int4 column makes every agent prompt fail on
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isPostgres,
|
|
5
5
|
intType,
|
|
6
6
|
} from "../db/client.js";
|
|
7
|
+
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
7
8
|
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
8
9
|
import type { StoreWriteOptions } from "../settings/store.js";
|
|
9
10
|
import { emitAppStateChange, emitAppStateDelete } from "./emitter.js";
|
|
@@ -22,7 +23,7 @@ async function ensureTable(): Promise<void> {
|
|
|
22
23
|
if (!_initPromise) {
|
|
23
24
|
_initPromise = (async () => {
|
|
24
25
|
const client = getDbExec();
|
|
25
|
-
|
|
26
|
+
const createSql = `
|
|
26
27
|
CREATE TABLE IF NOT EXISTS application_state (
|
|
27
28
|
session_id TEXT NOT NULL,
|
|
28
29
|
key TEXT NOT NULL,
|
|
@@ -30,7 +31,40 @@ async function ensureTable(): Promise<void> {
|
|
|
30
31
|
updated_at ${intType()} NOT NULL,
|
|
31
32
|
PRIMARY KEY (session_id, key)
|
|
32
33
|
)
|
|
33
|
-
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
if (isPostgres()) {
|
|
37
|
+
// Hot path: the `application_state` table and its poll indexes are
|
|
38
|
+
// virtually always already present in production. Issuing `CREATE
|
|
39
|
+
// TABLE`/ `CREATE INDEX` still takes a lock that, in a fresh
|
|
40
|
+
// background-worker process behind a concurrent connection on the
|
|
41
|
+
// shared Neon DB, can block ~indefinitely (ACCESS EXCLUSIVE for CREATE
|
|
42
|
+
// TABLE; a write-blocking SHARE lock for CREATE INDEX). The ensure*
|
|
43
|
+
// wrappers probe `information_schema`/`pg_indexes` first (plain reads,
|
|
44
|
+
// no lock) and run DDL ONLY for what is actually missing, bounded by a
|
|
45
|
+
// transaction-scoped `lock_timeout`. If a swallowed lock-timeout leaves
|
|
46
|
+
// the schema still missing they RE-PROBE and THROW rather than letting
|
|
47
|
+
// init memoize success against absent schema.
|
|
48
|
+
await ensureTableExists("application_state", createSql);
|
|
49
|
+
// Older deployments created `updated_at` as 32-bit `INTEGER`; on Postgres
|
|
50
|
+
// the `Date.now()` written by appStatePut() on every turn overflows int4.
|
|
51
|
+
// Widen it in place (no-op once done / on fresh BIGINT databases).
|
|
52
|
+
await widenIntColumnsToBigInt("application_state", ["updated_at"]);
|
|
53
|
+
// Indexes for the two hot poll paths. Probe pg_indexes first (no lock)
|
|
54
|
+
// and skip the SHARE-locking CREATE INDEX when already present.
|
|
55
|
+
await ensureIndexExists(
|
|
56
|
+
"app_state_updated_at_idx",
|
|
57
|
+
`CREATE INDEX IF NOT EXISTS app_state_updated_at_idx ON application_state (updated_at)`,
|
|
58
|
+
);
|
|
59
|
+
await ensureIndexExists(
|
|
60
|
+
"app_state_key_updated_idx",
|
|
61
|
+
`CREATE INDEX IF NOT EXISTS app_state_key_updated_idx ON application_state (key, updated_at)`,
|
|
62
|
+
);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// SQLite (local dev): no lock problem — keep the original behaviour.
|
|
67
|
+
await client.execute(createSql);
|
|
34
68
|
// Older deployments created `updated_at` as 32-bit `INTEGER`; on Postgres
|
|
35
69
|
// the `Date.now()` written by appStatePut() on every turn overflows int4.
|
|
36
70
|
// Widen it in place (no-op once done / on fresh BIGINT databases).
|