@agent-native/core 0.77.7 → 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 +2 -2
- package/corpus/core/CHANGELOG.md +46 -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/production-agent.ts +18 -0
- 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 +302 -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 +32 -2
- 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 +35 -6
- 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/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -0
- package/dist/agent/production-agent.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 +151 -0
- package/dist/db/ddl-guard.d.ts.map +1 -0
- package/dist/db/ddl-guard.js +265 -0
- package/dist/db/ddl-guard.js.map +1 -0
- 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/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 +2 -2
- 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 +22 -2
- 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 +30 -6
- 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,51 @@
|
|
|
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
|
+
|
|
32
|
+
## 0.77.8
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- 7dc2828: Guard on-demand `ensureTable()` schema-init so the already-migrated path takes no
|
|
37
|
+
`ACCESS EXCLUSIVE` lock on Postgres. The app-secrets (`app_secrets`) and `settings`
|
|
38
|
+
stores now probe `information_schema`/`pg_indexes` first (plain reads, no lock) and
|
|
39
|
+
issue `CREATE`/`ALTER`/`CREATE INDEX` only when something is actually missing; any
|
|
40
|
+
DDL that must run is wrapped in a transaction-scoped `SET LOCAL lock_timeout` so a
|
|
41
|
+
contended lock fails fast instead of hanging (and never leaks onto the pooled
|
|
42
|
+
connection). This fixes background-function workers hanging indefinitely on
|
|
43
|
+
first-touch schema DDL behind a concurrent connection on shared Neon — observed as
|
|
44
|
+
durable agent-chat workers stalling right after auth and never claiming the run.
|
|
45
|
+
SQLite (local dev) behavior is unchanged. Adds a shared `db/ddl-guard.ts` helper
|
|
46
|
+
(`pgTableExists`/`pgColumnExists`/`pgIndexExists`/`runGuardedDdl`). Also adds
|
|
47
|
+
diagnostic-only worker setup sub-stage breadcrumbs to localize such stalls.
|
|
48
|
+
|
|
3
49
|
## 0.77.7
|
|
4
50
|
|
|
5
51
|
### 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;
|
|
@@ -4023,6 +4023,8 @@ export function createProductionAgentHandler(
|
|
|
4023
4023
|
// owner/request-context awaits.
|
|
4024
4024
|
workerStep("db_request_ctx");
|
|
4025
4025
|
|
|
4026
|
+
// DIAGNOSTIC-ONLY: bracket attachment upload + text-attachment persistence.
|
|
4027
|
+
workerStep("attach_start");
|
|
4026
4028
|
// Pre-upload chat attachments (images AND files/PDFs) through the framework
|
|
4027
4029
|
// file-upload provider (Builder.io by default). The model still sees the
|
|
4028
4030
|
// base64 multimodal content for the current turn; each uploaded attachment
|
|
@@ -4085,9 +4087,13 @@ export function createProductionAgentHandler(
|
|
|
4085
4087
|
);
|
|
4086
4088
|
}
|
|
4087
4089
|
}
|
|
4090
|
+
// DIAGNOSTIC-ONLY: attachment upload + persistence finished.
|
|
4091
|
+
workerStep("attach_done");
|
|
4088
4092
|
|
|
4089
4093
|
// When a per-request engine override is specified, resolve the API key
|
|
4090
4094
|
// for that provider instead of the global active engine's provider.
|
|
4095
|
+
// DIAGNOSTIC-ONLY: bracket per-owner API-key resolution (settings/app_secrets reads).
|
|
4096
|
+
workerStep("apikey_start");
|
|
4091
4097
|
let userApiKey: string | undefined;
|
|
4092
4098
|
if (requestEngine) {
|
|
4093
4099
|
const provider = engineToProvider(requestEngine);
|
|
@@ -4103,6 +4109,8 @@ export function createProductionAgentHandler(
|
|
|
4103
4109
|
} else {
|
|
4104
4110
|
userApiKey = await getOwnerActiveApiKey(ownerEmail);
|
|
4105
4111
|
}
|
|
4112
|
+
// DIAGNOSTIC-ONLY: API-key resolution finished.
|
|
4113
|
+
workerStep("apikey_done");
|
|
4106
4114
|
|
|
4107
4115
|
// `options.apiKey` is the value the template constructed the plugin with
|
|
4108
4116
|
// (e.g. wired from a deployment env var). On a shared hosted deploy this
|
|
@@ -4117,6 +4125,9 @@ export function createProductionAgentHandler(
|
|
|
4117
4125
|
readDeployCredentialEnv("ANTHROPIC_API_KEY"));
|
|
4118
4126
|
|
|
4119
4127
|
// Resolve engine — per-request engine override takes priority
|
|
4128
|
+
// DIAGNOSTIC-ONLY: bracket engine resolution (Builder credential / app-default
|
|
4129
|
+
// settings reads inside resolveEngine).
|
|
4130
|
+
workerStep("engine_start");
|
|
4120
4131
|
let engine: AgentEngine;
|
|
4121
4132
|
try {
|
|
4122
4133
|
engine = await resolveEngine({
|
|
@@ -4131,17 +4142,24 @@ export function createProductionAgentHandler(
|
|
|
4131
4142
|
appId: options.appId,
|
|
4132
4143
|
});
|
|
4133
4144
|
}
|
|
4145
|
+
// DIAGNOSTIC-ONLY: engine resolution finished.
|
|
4146
|
+
workerStep("engine_done");
|
|
4134
4147
|
|
|
4135
4148
|
// Honor the model the user picked in the settings UI (written via
|
|
4136
4149
|
// `manage-agent-engine` action="set"), but only when the caller hasn't overridden it for
|
|
4137
4150
|
// this request or at plugin construction time. Read per-request so a
|
|
4138
4151
|
// dropdown change in the UI takes effect without a server restart. Skip
|
|
4139
4152
|
// the DB read entirely when a higher-precedence value is set.
|
|
4153
|
+
// DIAGNOSTIC-ONLY: bracket stored-model resolution (getStoredModelForEngine
|
|
4154
|
+
// settings read).
|
|
4155
|
+
workerStep("model_start");
|
|
4140
4156
|
const modelCandidate =
|
|
4141
4157
|
requestModel ??
|
|
4142
4158
|
configuredModel ??
|
|
4143
4159
|
(await getStoredModelForEngine(engine, { appId: options.appId })) ??
|
|
4144
4160
|
engine.defaultModel;
|
|
4161
|
+
// DIAGNOSTIC-ONLY: stored-model resolution finished.
|
|
4162
|
+
workerStep("model_done");
|
|
4145
4163
|
const model = normalizeModelForEngine(engine, modelCandidate);
|
|
4146
4164
|
const reasoningEffort = normalizeReasoningEffortForModel(
|
|
4147
4165
|
model,
|
|
@@ -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
|