@agent-native/core 0.32.1 → 0.32.17
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/README.md +3 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +48 -10
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +12 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +104 -6
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/app-skill.js +2 -2
- package/dist/cli/app-skill.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +6 -1
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/code-agent-output-smoother.d.ts +7 -0
- package/dist/cli/code-agent-output-smoother.d.ts.map +1 -0
- package/dist/cli/code-agent-output-smoother.js +111 -0
- package/dist/cli/code-agent-output-smoother.js.map +1 -0
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +5 -0
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +17 -42
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/skills.d.ts +23 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +405 -41
- package/dist/cli/skills.js.map +1 -1
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +7 -105
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +41 -7
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AgentTaskCard.d.ts.map +1 -1
- package/dist/client/AgentTaskCard.js +0 -28
- package/dist/client/AgentTaskCard.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +8 -23
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +359 -205
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +254 -14
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +14 -9
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-chat.d.ts +24 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +73 -0
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/assistant-ui-recovery.d.ts +34 -0
- package/dist/client/assistant-ui-recovery.d.ts.map +1 -0
- package/dist/client/assistant-ui-recovery.js +122 -0
- package/dist/client/assistant-ui-recovery.js.map +1 -0
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +7 -1
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +7 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +22 -2
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/frame-protocol.d.ts +6 -2
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +2 -1
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/progress/RunsTray.d.ts +13 -3
- package/dist/client/progress/RunsTray.d.ts.map +1 -1
- package/dist/client/progress/RunsTray.js +105 -36
- package/dist/client/progress/RunsTray.js.map +1 -1
- package/dist/client/route-warmup.d.ts +61 -0
- package/dist/client/route-warmup.d.ts.map +1 -0
- package/dist/client/route-warmup.js +456 -0
- package/dist/client/route-warmup.js.map +1 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +2 -1
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +5 -0
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +10 -4
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/use-action.d.ts +1 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +22 -4
- package/dist/client/use-action.js.map +1 -1
- package/dist/code-agents/background-run.d.ts +2 -0
- package/dist/code-agents/background-run.d.ts.map +1 -1
- package/dist/code-agents/background-run.js.map +1 -1
- package/dist/db/client.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +25 -1
- package/dist/db/client.js.map +1 -1
- package/dist/deploy/build.d.ts +4 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +171 -14
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/immutable-assets.d.ts +1 -0
- package/dist/deploy/immutable-assets.d.ts.map +1 -1
- package/dist/deploy/immutable-assets.js +1 -0
- package/dist/deploy/immutable-assets.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/connect-route.d.ts.map +1 -1
- package/dist/mcp/connect-route.js +118 -82
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/progress/routes.d.ts.map +1 -1
- package/dist/progress/routes.js +1 -0
- package/dist/progress/routes.js.map +1 -1
- package/dist/progress/store.d.ts +13 -0
- package/dist/progress/store.d.ts.map +1 -1
- package/dist/progress/store.js +18 -0
- package/dist/progress/store.js.map +1 -1
- package/dist/progress/types.d.ts +2 -0
- package/dist/progress/types.d.ts.map +1 -1
- package/dist/progress/types.js.map +1 -1
- package/dist/scripts/db/wipe-leaked-builder-keys.d.ts +2 -2
- package/dist/scripts/db/wipe-leaked-builder-keys.d.ts.map +1 -1
- package/dist/scripts/db/wipe-leaked-builder-keys.js +14 -3
- package/dist/scripts/db/wipe-leaked-builder-keys.js.map +1 -1
- package/dist/server/action-routes.d.ts +1 -0
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +36 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +123 -25
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-discovery.d.ts.map +1 -1
- package/dist/server/agent-discovery.js +14 -1
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/agent-teams-run-queue.d.ts +80 -0
- package/dist/server/agent-teams-run-queue.d.ts.map +1 -0
- package/dist/server/agent-teams-run-queue.js +208 -0
- package/dist/server/agent-teams-run-queue.js.map +1 -0
- package/dist/server/agent-teams.d.ts +67 -0
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +607 -180
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/auth-marketing.d.ts.map +1 -1
- package/dist/server/auth-marketing.js +0 -64
- package/dist/server/auth-marketing.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +67 -14
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +12 -2
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +24 -0
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +66 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +10 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +82 -3
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/csrf.d.ts.map +1 -1
- package/dist/server/csrf.js +3 -0
- package/dist/server/csrf.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +14 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/self-dispatch.d.ts +44 -0
- package/dist/server/self-dispatch.d.ts.map +1 -0
- package/dist/server/self-dispatch.js +113 -0
- package/dist/server/self-dispatch.js.map +1 -0
- package/dist/server/social-og-image.d.ts +14 -0
- package/dist/server/social-og-image.d.ts.map +1 -0
- package/dist/server/social-og-image.js +251 -0
- package/dist/server/social-og-image.js.map +1 -0
- package/dist/server/ssr-handler.d.ts +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +27 -11
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/shared/cache-control.d.ts +7 -0
- package/dist/shared/cache-control.d.ts.map +1 -1
- package/dist/shared/cache-control.js +7 -0
- package/dist/shared/cache-control.js.map +1 -1
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/route-warmup-config.d.ts +28 -0
- package/dist/shared/route-warmup-config.d.ts.map +1 -0
- package/dist/shared/route-warmup-config.js +58 -0
- package/dist/shared/route-warmup-config.js.map +1 -0
- package/dist/shared/social-meta.d.ts +5 -0
- package/dist/shared/social-meta.d.ts.map +1 -1
- package/dist/shared/social-meta.js +36 -2
- package/dist/shared/social-meta.js.map +1 -1
- package/dist/shared/streaming-text-smoothing.d.ts +12 -0
- package/dist/shared/streaming-text-smoothing.d.ts.map +1 -0
- package/dist/shared/streaming-text-smoothing.js +52 -0
- package/dist/shared/streaming-text-smoothing.js.map +1 -0
- package/dist/styles/agent-native.css +4 -4
- package/dist/templates/default/AGENTS.md +9 -4
- package/dist/templates/default/DEVELOPING.md +15 -1
- package/dist/templates/workspace-core/AGENTS.md +7 -3
- package/dist/templates/workspace-root/AGENTS.md +7 -3
- package/dist/vite/client.d.ts +13 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +36 -1
- package/dist/vite/client.js.map +1 -1
- package/dist/vite/index.d.ts +1 -0
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js.map +1 -1
- package/docs/content/client.md +62 -1
- package/docs/content/code-agents-ui.md +6 -13
- package/docs/content/context-awareness.md +186 -21
- package/docs/content/deployment.md +8 -11
- package/docs/content/dispatch.md +1 -1
- package/docs/content/external-agents.md +32 -2
- package/docs/content/migration-workbench.md +4 -21
- package/docs/content/multi-app-workspace.md +1 -1
- package/docs/content/recurring-jobs.md +1 -1
- package/docs/content/security.md +0 -1
- package/docs/content/sharing.md +1 -3
- package/docs/content/skills-guide.md +12 -10
- package/docs/content/template-assets.md +21 -1
- package/docs/content/template-design.md +23 -5
- package/docs/content/template-dispatch.md +1 -1
- package/package.json +2 -1
- package/src/templates/default/AGENTS.md +9 -4
- package/src/templates/default/DEVELOPING.md +15 -1
- package/src/templates/workspace-core/AGENTS.md +7 -3
- package/src/templates/workspace-root/AGENTS.md +7 -3
package/README.md
CHANGED
|
@@ -212,7 +212,9 @@ npx @agent-native/core@latest code resume --last "check the auth edge cases next
|
|
|
212
212
|
|
|
213
213
|
Slash goals can run from the interactive shell or directly from the command line, and `agent-native code goals` shows the goals registered in your checkout. A bare prompt starts a local coding-agent session, streams work, records transcript/status/tool events, and accepts follow-up prompts; `/migrate` is one specialized capability inside that general Code workspace. Project-specific slash commands live in `.agents/commands/*.md`, so teams can add prompts such as `/release-check` or `/migrate-commerce` without changing the framework. Installed `agent-native` with no arguments launches the Code workspace; `agent-native "fix tests"` starts an Agent-Native Code task directly. Use `agent-native create` when you want to create apps or workspaces.
|
|
214
214
|
|
|
215
|
-
Working inside this repository? Use `pnpm dev
|
|
215
|
+
Working inside this repository? Use `pnpm dev` for the lazy framework dev gateway. It exposes the core templates on one local origin and starts each app only when you visit it, which keeps memory usage much lower than booting every server at once. To focus on specific templates, pass `--apps`, for example `pnpm dev -- --apps analytics,brain`. Use `pnpm dev:eager` only when you intentionally want the older eager mode that starts the selected template servers, frame, docs, and core watcher immediately. The old `pnpm dev:all` name still works as an alias for eager mode.
|
|
216
|
+
|
|
217
|
+
Use `pnpm dev:cli ...` to run the source CLI without building first, for example `pnpm dev:cli --help` or `pnpm dev:cli code goals`.
|
|
216
218
|
|
|
217
219
|
The Code workspace uses the familiar Codex/Claude-style session loop: pick a previous session, list runs, check status, attach to live output, print logs, stop work, resume with context, open the local UI, and continue the same run from Desktop or CLI. The Desktop Code tab, `agent-native code ui`, background sessions, and sub-agent sessions all converge on the shared run harness/controller model instead of separate ad hoc runners. The primary modes are intentionally simple:
|
|
218
220
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-store.d.ts","sourceRoot":"","sources":["../../src/agent/run-store.ts"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH,eAAO,MAAM,YAAY,OAAQ,CAAC;AAElC,eAAO,MAAM,qBAAqB;;;;;;CAQxB,CAAC;AAuGX,wBAAsB,SAAS,CAC7B,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED,+EAA+E;AAC/E,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrE;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOlE;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,MAAqB,GAChC,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"run-store.d.ts","sourceRoot":"","sources":["../../src/agent/run-store.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/run-store.js
CHANGED
|
@@ -175,14 +175,24 @@ export async function bumpRunProgress(runId) {
|
|
|
175
175
|
export async function reapIfStale(runId, maxStaleMs = RUN_STALE_MS) {
|
|
176
176
|
await ensureRunTables();
|
|
177
177
|
const client = getDbExec();
|
|
178
|
-
const
|
|
178
|
+
const completedAt = Date.now();
|
|
179
|
+
const cutoff = completedAt - maxStaleMs;
|
|
179
180
|
const { rowsAffected } = await client.execute({
|
|
180
181
|
sql: `UPDATE agent_runs
|
|
181
|
-
SET status = 'errored',
|
|
182
|
+
SET status = 'errored',
|
|
183
|
+
completed_at = ?,
|
|
184
|
+
error_code = ?,
|
|
185
|
+
error_detail = ?
|
|
182
186
|
WHERE id = ?
|
|
183
187
|
AND status = 'running'
|
|
184
188
|
AND COALESCE(heartbeat_at, started_at) < ?`,
|
|
185
|
-
args: [
|
|
189
|
+
args: [
|
|
190
|
+
completedAt,
|
|
191
|
+
STALE_RUN_ERROR_EVENT.errorCode,
|
|
192
|
+
STALE_RUN_ERROR_EVENT.details,
|
|
193
|
+
runId,
|
|
194
|
+
cutoff,
|
|
195
|
+
],
|
|
186
196
|
});
|
|
187
197
|
const reaped = (rowsAffected ?? 0) > 0;
|
|
188
198
|
if (reaped) {
|
|
@@ -306,12 +316,21 @@ export async function reapAllStaleRuns() {
|
|
|
306
316
|
AND COALESCE(heartbeat_at, started_at) < ?`,
|
|
307
317
|
args: [heartbeatCutoff],
|
|
308
318
|
});
|
|
319
|
+
const completedAt = Date.now();
|
|
309
320
|
const { rowsAffected } = await client.execute({
|
|
310
321
|
sql: `UPDATE agent_runs
|
|
311
|
-
SET status = 'errored',
|
|
322
|
+
SET status = 'errored',
|
|
323
|
+
completed_at = ?,
|
|
324
|
+
error_code = ?,
|
|
325
|
+
error_detail = ?
|
|
312
326
|
WHERE status = 'running'
|
|
313
327
|
AND COALESCE(heartbeat_at, started_at) < ?`,
|
|
314
|
-
args: [
|
|
328
|
+
args: [
|
|
329
|
+
completedAt,
|
|
330
|
+
STALE_RUN_ERROR_EVENT.errorCode,
|
|
331
|
+
STALE_RUN_ERROR_EVENT.details,
|
|
332
|
+
heartbeatCutoff,
|
|
333
|
+
],
|
|
315
334
|
});
|
|
316
335
|
for (const row of stale.rows) {
|
|
317
336
|
const id = row.id;
|
|
@@ -343,20 +362,39 @@ export async function cleanupOldRuns(olderThanMs, erroredOlderThanMs) {
|
|
|
343
362
|
AND (
|
|
344
363
|
COALESCE(heartbeat_at, started_at) < ?
|
|
345
364
|
OR started_at < ?
|
|
346
|
-
|
|
365
|
+
)`,
|
|
347
366
|
args: [heartbeatCutoff, cutoff],
|
|
348
367
|
});
|
|
368
|
+
const completedAt = Date.now();
|
|
349
369
|
await client.execute({
|
|
350
|
-
sql: `UPDATE agent_runs
|
|
351
|
-
|
|
370
|
+
sql: `UPDATE agent_runs
|
|
371
|
+
SET status = 'errored',
|
|
372
|
+
completed_at = ?,
|
|
373
|
+
error_code = ?,
|
|
374
|
+
error_detail = ?
|
|
375
|
+
WHERE status = 'running' AND started_at < ?`,
|
|
376
|
+
args: [
|
|
377
|
+
completedAt,
|
|
378
|
+
STALE_RUN_ERROR_EVENT.errorCode,
|
|
379
|
+
STALE_RUN_ERROR_EVENT.details,
|
|
380
|
+
cutoff,
|
|
381
|
+
],
|
|
352
382
|
});
|
|
353
383
|
// Also expire runs whose heartbeat is stale — producer has died.
|
|
354
384
|
await client.execute({
|
|
355
385
|
sql: `UPDATE agent_runs
|
|
356
|
-
SET status = 'errored',
|
|
386
|
+
SET status = 'errored',
|
|
387
|
+
completed_at = ?,
|
|
388
|
+
error_code = ?,
|
|
389
|
+
error_detail = ?
|
|
357
390
|
WHERE status = 'running'
|
|
358
391
|
AND COALESCE(heartbeat_at, started_at) < ?`,
|
|
359
|
-
args: [
|
|
392
|
+
args: [
|
|
393
|
+
completedAt,
|
|
394
|
+
STALE_RUN_ERROR_EVENT.errorCode,
|
|
395
|
+
STALE_RUN_ERROR_EVENT.details,
|
|
396
|
+
heartbeatCutoff,
|
|
397
|
+
],
|
|
360
398
|
});
|
|
361
399
|
for (const row of stale.rows) {
|
|
362
400
|
const id = row.id;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-store.js","sourceRoot":"","sources":["../../src/agent/run-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQL persistence for agent runs and events.\n * Enables cross-isolate access on Cloudflare Workers and\n * reliable reconnection after page refreshes.\n */\nimport { getDbExec, intType, isPostgres } from \"../db/client.js\";\nimport { captureError } from \"../server/capture-error.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Max time without a heartbeat before a \"running\" run is considered dead.\n * The run-manager heartbeats every 1.5s, so 6s tolerates 3 missed writes.\n * Short window is what makes reload recovery feel instant instead of\n * stranding the user on \"Thinking...\" for up to 90s after a process death.\n */\nexport const RUN_STALE_MS = 6_000;\n\nexport const STALE_RUN_ERROR_EVENT = {\n type: \"error\",\n error:\n \"The agent stopped before it could finish. It may have hit a server timeout or the worker may have been interrupted.\",\n errorCode: \"stale_run\",\n recoverable: true,\n details:\n \"The run heartbeat stopped while the run was still marked running. Partial output and tool calls were preserved when available.\",\n} as const;\n\nasync function ensureRunTables(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n await client.execute(`\n CREATE TABLE IF NOT EXISTS agent_runs (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'running',\n abort_reason TEXT,\n started_at ${intType()} NOT NULL,\n completed_at ${intType()},\n heartbeat_at ${intType()},\n last_progress_at ${intType()},\n turn_id TEXT,\n error_code TEXT,\n error_detail TEXT\n )\n `);\n // Backfill heartbeat_at on older deployments.\n try {\n if (isPostgres()) {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS heartbeat_at ${intType()}`,\n );\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS abort_reason TEXT`,\n );\n } else {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN heartbeat_at ${intType()}`,\n );\n }\n } catch {\n // Column already exists — ignore\n }\n try {\n if (!isPostgres()) {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN abort_reason TEXT`,\n );\n }\n } catch {\n // Column already exists — ignore\n }\n // Backfill last_progress_at — this is distinct from heartbeat_at.\n // heartbeat_at = \"the producer process is alive\" (bumped on a timer).\n // last_progress_at = \"the agent is actually emitting events\" (bumped on\n // each emit). The gap between them is the stuck-detector signal.\n try {\n if (isPostgres()) {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS last_progress_at ${intType()}`,\n );\n } else {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN last_progress_at ${intType()}`,\n );\n }\n } catch {\n // Column already exists — ignore\n }\n // Backfill turn_id / error_code / error_detail.\n // turn_id = stable identity for one logical assistant turn that may\n // span several continuation runs, so the durable record\n // can be folded across runs instead of dropped per-run.\n // error_code / error_detail = terminal failure classification captured\n // at completion so errored/cut-off runs are queryable for\n // pattern analysis (see listErroredRuns).\n for (const col of [\"turn_id\", \"error_code\", \"error_detail\"] as const) {\n try {\n if (isPostgres()) {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS ${col} TEXT`,\n );\n } else {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN ${col} TEXT`,\n );\n }\n } catch {\n // Column already exists — ignore\n }\n }\n await client.execute(`\n CREATE TABLE IF NOT EXISTS agent_run_events (\n run_id TEXT NOT NULL,\n seq ${intType()} NOT NULL,\n event_data TEXT NOT NULL,\n PRIMARY KEY (run_id, seq)\n )\n `);\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nexport async function insertRun(\n id: string,\n threadId: string,\n turnId?: string,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `INSERT INTO agent_runs (id, thread_id, status, started_at, heartbeat_at, last_progress_at, turn_id) VALUES (?, ?, 'running', ?, ?, ?, ?)`,\n args: [id, threadId, now, now, now, turnId ?? id],\n });\n}\n\n/**\n * Record terminal failure classification for a run so cut-off / errored runs\n * can be surfaced for pattern analysis (see listErroredRuns). Best-effort —\n * never throws, since it runs on the completion path that must not fail the run.\n */\nexport async function setRunError(\n runId: string,\n errorCode: string | undefined,\n errorDetail: string | undefined,\n): Promise<void> {\n if (!errorCode && !errorDetail) return;\n try {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET error_code = ?, error_detail = ? WHERE id = ?`,\n args: [\n errorCode ?? null,\n errorDetail ? errorDetail.slice(0, 2000) : null,\n runId,\n ],\n });\n } catch {\n // Diagnostics are best-effort; never let them break completion.\n }\n}\n\n/** Update the run's liveness heartbeat. Called periodically by run-manager. */\nexport async function updateRunHeartbeat(runId: string): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET heartbeat_at = ? WHERE id = ?`,\n args: [Date.now(), runId],\n });\n}\n\n/**\n * Bump `last_progress_at` — call this whenever the agent actually emits an\n * event (token, tool call, message). Distinct from `heartbeat_at` so the\n * stuck-detector can tell \"process alive but nothing happening\" from\n * \"process dead.\" Callers should throttle (run-manager debounces to ~1/s).\n */\nexport async function bumpRunProgress(runId: string): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET last_progress_at = ? WHERE id = ?`,\n args: [Date.now(), runId],\n });\n}\n\n/**\n * If the given run is marked \"running\" in SQL but its heartbeat is stale\n * (producer likely crashed), flip it to \"errored\" so watchers stop waiting.\n * Returns true if the row was reaped.\n */\nexport async function reapIfStale(\n runId: string,\n maxStaleMs: number = RUN_STALE_MS,\n): Promise<boolean> {\n await ensureRunTables();\n const client = getDbExec();\n const cutoff = Date.now() - maxStaleMs;\n const { rowsAffected } = await client.execute({\n sql: `UPDATE agent_runs\n SET status = 'errored', completed_at = ?\n WHERE id = ?\n AND status = 'running'\n AND COALESCE(heartbeat_at, started_at) < ?`,\n args: [Date.now(), runId, cutoff],\n });\n const reaped = (rowsAffected ?? 0) > 0;\n if (reaped) {\n await safeAppendTerminalRunEvent(\n runId,\n STALE_RUN_ERROR_EVENT,\n \"reap-if-stale\",\n );\n }\n return reaped;\n}\n\nexport async function updateRunStatus(\n runId: string,\n status: \"completed\" | \"errored\" | \"aborted\",\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET status = ?, completed_at = ? WHERE id = ?`,\n args: [status, Date.now(), runId],\n });\n}\n\nexport async function markRunAborted(\n runId: string,\n reason?: string,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ? WHERE id = ?`,\n args: [reason ?? \"user\", Date.now(), runId],\n });\n await safeAppendTerminalRunEvent(runId, { type: \"done\" }, \"mark-aborted\");\n}\n\nexport async function isRunAborted(runId: string): Promise<boolean> {\n return (await getRunAbortState(runId)).aborted;\n}\n\nexport async function getRunAbortState(\n runId: string,\n): Promise<{ aborted: boolean; reason?: string }> {\n await ensureRunTables();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT status, abort_reason FROM agent_runs WHERE id = ?`,\n args: [runId],\n });\n if (rows.length === 0) return { aborted: false };\n const row = rows[0] as { status: string; abort_reason?: string | null };\n if (row.status !== \"aborted\") return { aborted: false };\n return {\n aborted: true,\n ...(row.abort_reason ? { reason: row.abort_reason } : {}),\n };\n}\n\nexport async function insertRunEvent(\n runId: string,\n seq: number,\n eventData: string,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n // ON CONFLICT DO NOTHING: a (runId, seq) collision can happen on the\n // soft-timeout / terminal-event path where `pendingTerminalEvent` was\n // assigned a seq that later gets reused by an event pushed after it.\n // It can also race with `appendTerminalRunEvent` (max-seq + 1) when a\n // run aborts at the same time the producer emits its final event.\n // Treat the second write as a no-op so the run completes cleanly.\n await client.execute({\n sql: `INSERT INTO agent_run_events (run_id, seq, event_data) VALUES (?, ?, ?) ON CONFLICT (run_id, seq) DO NOTHING`,\n args: [runId, seq, eventData],\n });\n}\n\nexport async function getRunEventsSince(\n runId: string,\n fromSeq: number,\n): Promise<Array<{ seq: number; eventData: string }>> {\n await ensureRunTables();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT seq, event_data FROM agent_run_events WHERE run_id = ? AND seq >= ? ORDER BY seq ASC`,\n args: [runId, fromSeq],\n });\n return rows.map((r) => {\n const row = r as { seq: number | string; event_data: string };\n return { seq: Number(row.seq), eventData: row.event_data };\n });\n}\n\nexport async function getRunById(runId: string): Promise<{\n id: string;\n threadId: string;\n status: string;\n startedAt: number;\n} | null> {\n await ensureRunTables();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, thread_id, status, started_at FROM agent_runs WHERE id = ?`,\n args: [runId],\n });\n if (rows.length === 0) return null;\n const r = rows[0] as {\n id: string;\n thread_id: string;\n status: string;\n started_at: number | string;\n };\n return {\n id: r.id,\n threadId: r.thread_id,\n status: r.status,\n startedAt: Number(r.started_at),\n };\n}\n\nexport async function getRunByThread(\n threadId: string,\n options?: { includeTerminal?: boolean },\n): Promise<{\n id: string;\n threadId: string;\n status: string;\n startedAt: number;\n heartbeatAt: number | null;\n completedAt: number | null;\n lastProgressAt: number | null;\n} | null> {\n await ensureRunTables();\n const client = getDbExec();\n const sql = options?.includeTerminal\n ? `SELECT id, thread_id, status, started_at, heartbeat_at, completed_at, last_progress_at FROM agent_runs WHERE thread_id = ? ORDER BY started_at DESC LIMIT 1`\n : `SELECT id, thread_id, status, started_at, heartbeat_at, completed_at, last_progress_at FROM agent_runs WHERE thread_id = ? AND status = 'running' ORDER BY started_at DESC LIMIT 1`;\n const { rows } = await client.execute({ sql, args: [threadId] });\n if (rows.length === 0) return null;\n const r = rows[0] as {\n id: string;\n thread_id: string;\n status: string;\n started_at: number | string;\n heartbeat_at: number | string | null;\n completed_at: number | string | null;\n last_progress_at: number | string | null;\n };\n return {\n id: r.id,\n threadId: r.thread_id,\n status: r.status,\n startedAt: Number(r.started_at),\n heartbeatAt: r.heartbeat_at == null ? null : Number(r.heartbeat_at),\n completedAt: r.completed_at == null ? null : Number(r.completed_at),\n lastProgressAt:\n r.last_progress_at == null ? null : Number(r.last_progress_at),\n };\n}\n\n/**\n * Expire any \"running\" rows whose heartbeat is stale — producer died.\n * Safe to call at server startup on multi-isolate deployments: only rows\n * without a fresh heartbeat get reaped, so runs owned by OTHER live\n * isolates (which keep heartbeating) are left alone.\n */\nexport async function reapAllStaleRuns(): Promise<number> {\n await ensureRunTables();\n const client = getDbExec();\n const heartbeatCutoff = Date.now() - RUN_STALE_MS;\n const stale = await client.execute({\n sql: `SELECT id FROM agent_runs\n WHERE status = 'running'\n AND COALESCE(heartbeat_at, started_at) < ?`,\n args: [heartbeatCutoff],\n });\n const { rowsAffected } = await client.execute({\n sql: `UPDATE agent_runs\n SET status = 'errored', completed_at = ?\n WHERE status = 'running'\n AND COALESCE(heartbeat_at, started_at) < ?`,\n args: [Date.now(), heartbeatCutoff],\n });\n for (const row of stale.rows) {\n const id = (row as { id?: unknown }).id;\n if (typeof id === \"string\") {\n await safeAppendTerminalRunEvent(\n id,\n STALE_RUN_ERROR_EVENT,\n \"reap-all-stale\",\n );\n }\n }\n return rowsAffected ?? 0;\n}\n\n/** Delete old runs and expire stale \"running\" rows that haven't had activity\n * (e.g. worker crashed before updating status). Completed runs are pruned at\n * `olderThanMs`; errored/aborted runs are kept until `erroredOlderThanMs` (a\n * longer window, falling back to `olderThanMs`) so their event log survives\n * for cut-off pattern analysis via listErroredRuns. */\nexport async function cleanupOldRuns(\n olderThanMs: number,\n erroredOlderThanMs?: number,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n const cutoff = Date.now() - olderThanMs;\n const erroredCutoff =\n Date.now() - Math.max(erroredOlderThanMs ?? 0, olderThanMs);\n // Expire stale running rows on the absolute-age threshold — safety net\n // for runs that never received a heartbeat (very old deployments). The\n // SELECT covers BOTH UPDATE conditions so the terminal-event-append loop\n // below catches every row we're about to flip — a 24h-old row with a\n // somehow-fresh heartbeat would slip past a heartbeat-only SELECT.\n const heartbeatCutoff = Date.now() - RUN_STALE_MS;\n const stale = await client.execute({\n sql: `SELECT id FROM agent_runs\n WHERE status = 'running'\n AND (\n COALESCE(heartbeat_at, started_at) < ?\n OR started_at < ?\n )`,\n args: [heartbeatCutoff, cutoff],\n });\n await client.execute({\n sql: `UPDATE agent_runs SET status = 'errored', completed_at = ? WHERE status = 'running' AND started_at < ?`,\n args: [Date.now(), cutoff],\n });\n // Also expire runs whose heartbeat is stale — producer has died.\n await client.execute({\n sql: `UPDATE agent_runs\n SET status = 'errored', completed_at = ?\n WHERE status = 'running'\n AND COALESCE(heartbeat_at, started_at) < ?`,\n args: [Date.now(), heartbeatCutoff],\n });\n for (const row of stale.rows) {\n const id = (row as { id?: unknown }).id;\n if (typeof id === \"string\") {\n await safeAppendTerminalRunEvent(\n id,\n STALE_RUN_ERROR_EVENT,\n \"cleanup-old-runs\",\n );\n }\n }\n // Delete events for old terminal runs. Completed runs prune at `cutoff`;\n // errored/aborted runs are retained until the (longer) `erroredCutoff`.\n await client.execute({\n sql: `DELETE FROM agent_run_events WHERE run_id IN (\n SELECT id FROM agent_runs\n WHERE (status = 'completed' AND completed_at < ?)\n OR (status IN ('errored', 'aborted') AND completed_at < ?)\n )`,\n args: [cutoff, erroredCutoff],\n });\n await client.execute({\n sql: `DELETE FROM agent_runs\n WHERE (status = 'completed' AND completed_at < ?)\n OR (status IN ('errored', 'aborted') AND completed_at < ?)`,\n args: [cutoff, erroredCutoff],\n });\n}\n\n/**\n * List recent errored/aborted runs for cut-off pattern analysis. Read-only,\n * bounded, and ordered newest-first. Surfaced via the list-errored-runs action\n * so the team can see why chats are failing (terminal error code, duration,\n * turn linkage) instead of discovering it ad hoc.\n */\nexport async function listErroredRuns(options?: {\n limit?: number;\n sinceMs?: number;\n}): Promise<\n Array<{\n id: string;\n threadId: string;\n turnId: string | null;\n status: string;\n errorCode: string | null;\n errorDetail: string | null;\n startedAt: number;\n completedAt: number | null;\n durationMs: number | null;\n }>\n> {\n await ensureRunTables();\n const client = getDbExec();\n const limit = Math.min(Math.max(Math.floor(options?.limit ?? 100), 1), 1000);\n const since =\n options?.sinceMs && options.sinceMs > 0 ? Date.now() - options.sinceMs : 0;\n const { rows } = await client.execute({\n sql: `SELECT id, thread_id, turn_id, status, error_code, error_detail, started_at, completed_at\n FROM agent_runs\n WHERE status IN ('errored', 'aborted')\n AND COALESCE(completed_at, started_at) >= ?\n ORDER BY COALESCE(completed_at, started_at) DESC\n LIMIT ${limit}`,\n args: [since],\n });\n return rows.map((r) => {\n const row = r as {\n id: string;\n thread_id: string;\n turn_id: string | null;\n status: string;\n error_code: string | null;\n error_detail: string | null;\n started_at: number | string;\n completed_at: number | string | null;\n };\n const startedAt = Number(row.started_at);\n const completedAt =\n row.completed_at == null ? null : Number(row.completed_at);\n return {\n id: row.id,\n threadId: row.thread_id,\n turnId: row.turn_id ?? null,\n status: row.status,\n errorCode: row.error_code ?? null,\n errorDetail: row.error_detail ?? null,\n startedAt,\n completedAt,\n durationMs: completedAt == null ? null : completedAt - startedAt,\n };\n });\n}\n\n/**\n * Idempotently append a terminal event to a run's event stream. No-op if the\n * stream already ends in a terminal event. Used by reapers AND by SSE\n * reconnect paths that discover an `errored` run row with no terminal event\n * (e.g. an earlier reaper's silent `.catch(() => {})` swallowed the append).\n *\n * Persisting from the reconnect path is what keeps the system self-healing:\n * subsequent reconnects replay the proper terminal event from SQL instead of\n * synthesizing a fresh one each time.\n */\nexport async function ensureTerminalRunEvent(\n runId: string,\n event: Record<string, unknown>,\n): Promise<void> {\n return appendTerminalRunEvent(runId, event);\n}\n\n/**\n * Append a terminal run event, retrying once on failure and reporting to\n * Sentry if both attempts fail. Background reaper paths can't surface errors\n * to a user, but they MUST eventually persist a terminal event — losing it\n * leaves reconnecting clients staring at a bare `status='errored'` row with\n * no payload to render. The previous `.catch(() => {})` callsites silently\n * dropped transient SQL blips and produced exactly that bug. Never throws.\n */\nasync function safeAppendTerminalRunEvent(\n runId: string,\n event: Record<string, unknown>,\n source: string,\n): Promise<void> {\n let firstError: unknown;\n try {\n await appendTerminalRunEvent(runId, event);\n return;\n } catch (err) {\n firstError = err;\n }\n // Brief backoff — most \"transient\" SQL failures (connection blip, lock\n // contention) clear within a couple hundred ms.\n await new Promise<void>((resolve) => setTimeout(resolve, 100));\n try {\n await appendTerminalRunEvent(runId, event);\n } catch (retryErr) {\n captureError(retryErr, {\n tags: {\n component: \"agent-run-store\",\n operation: \"append-terminal-event\",\n source,\n },\n extra: {\n runId,\n eventType: typeof event.type === \"string\" ? event.type : \"(unknown)\",\n firstError:\n firstError instanceof Error ? firstError.message : String(firstError),\n },\n });\n }\n}\n\nasync function appendTerminalRunEvent(\n runId: string,\n event: Record<string, unknown>,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT seq, event_data FROM agent_run_events WHERE run_id = ? ORDER BY seq DESC LIMIT 1`,\n args: [runId],\n });\n const last = rows[0] as\n | { seq?: number | string; event_data?: string }\n | undefined;\n if (last?.event_data) {\n try {\n const parsed = JSON.parse(last.event_data);\n if (\n parsed?.type === \"done\" ||\n parsed?.type === \"error\" ||\n parsed?.type === \"missing_api_key\" ||\n parsed?.type === \"loop_limit\" ||\n parsed?.type === \"auto_continue\"\n ) {\n return;\n }\n } catch {\n // Ignore malformed rows and append the terminal event.\n }\n }\n const nextSeq = last ? Number(last.seq ?? -1) + 1 : 0;\n await client.execute({\n sql: `INSERT INTO agent_run_events (run_id, seq, event_data) VALUES (?, ?, ?) ON CONFLICT (run_id, seq) DO NOTHING`,\n args: [runId, nextSeq, JSON.stringify(event)],\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"run-store.js","sourceRoot":"","sources":["../../src/agent/run-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SQL persistence for agent runs and events.\n * Enables cross-isolate access on Cloudflare Workers and\n * reliable reconnection after page refreshes.\n */\nimport { getDbExec, intType, isPostgres } from \"../db/client.js\";\nimport { captureError } from \"../server/capture-error.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Max time without a heartbeat before a \"running\" run is considered dead.\n * The run-manager heartbeats every 1.5s, so 6s tolerates 3 missed writes.\n * Short window is what makes reload recovery feel instant instead of\n * stranding the user on \"Thinking...\" for up to 90s after a process death.\n */\nexport const RUN_STALE_MS = 6_000;\n\nexport const STALE_RUN_ERROR_EVENT = {\n type: \"error\",\n error:\n \"The agent stopped before it could finish. It may have hit a server timeout or the worker may have been interrupted.\",\n errorCode: \"stale_run\",\n recoverable: true,\n details:\n \"The run heartbeat stopped while the run was still marked running. Partial output and tool calls were preserved when available.\",\n} as const;\n\nasync function ensureRunTables(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n await client.execute(`\n CREATE TABLE IF NOT EXISTS agent_runs (\n id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'running',\n abort_reason TEXT,\n started_at ${intType()} NOT NULL,\n completed_at ${intType()},\n heartbeat_at ${intType()},\n last_progress_at ${intType()},\n turn_id TEXT,\n error_code TEXT,\n error_detail TEXT\n )\n `);\n // Backfill heartbeat_at on older deployments.\n try {\n if (isPostgres()) {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS heartbeat_at ${intType()}`,\n );\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS abort_reason TEXT`,\n );\n } else {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN heartbeat_at ${intType()}`,\n );\n }\n } catch {\n // Column already exists — ignore\n }\n try {\n if (!isPostgres()) {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN abort_reason TEXT`,\n );\n }\n } catch {\n // Column already exists — ignore\n }\n // Backfill last_progress_at — this is distinct from heartbeat_at.\n // heartbeat_at = \"the producer process is alive\" (bumped on a timer).\n // last_progress_at = \"the agent is actually emitting events\" (bumped on\n // each emit). The gap between them is the stuck-detector signal.\n try {\n if (isPostgres()) {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS last_progress_at ${intType()}`,\n );\n } else {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN last_progress_at ${intType()}`,\n );\n }\n } catch {\n // Column already exists — ignore\n }\n // Backfill turn_id / error_code / error_detail.\n // turn_id = stable identity for one logical assistant turn that may\n // span several continuation runs, so the durable record\n // can be folded across runs instead of dropped per-run.\n // error_code / error_detail = terminal failure classification captured\n // at completion so errored/cut-off runs are queryable for\n // pattern analysis (see listErroredRuns).\n for (const col of [\"turn_id\", \"error_code\", \"error_detail\"] as const) {\n try {\n if (isPostgres()) {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN IF NOT EXISTS ${col} TEXT`,\n );\n } else {\n await client.execute(\n `ALTER TABLE agent_runs ADD COLUMN ${col} TEXT`,\n );\n }\n } catch {\n // Column already exists — ignore\n }\n }\n await client.execute(`\n CREATE TABLE IF NOT EXISTS agent_run_events (\n run_id TEXT NOT NULL,\n seq ${intType()} NOT NULL,\n event_data TEXT NOT NULL,\n PRIMARY KEY (run_id, seq)\n )\n `);\n })().catch((err) => {\n // Retry init on the next call after a failed startup.\n _initPromise = undefined;\n throw err;\n });\n }\n return _initPromise;\n}\n\nexport async function insertRun(\n id: string,\n threadId: string,\n turnId?: string,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n const now = Date.now();\n await client.execute({\n sql: `INSERT INTO agent_runs (id, thread_id, status, started_at, heartbeat_at, last_progress_at, turn_id) VALUES (?, ?, 'running', ?, ?, ?, ?)`,\n args: [id, threadId, now, now, now, turnId ?? id],\n });\n}\n\n/**\n * Record terminal failure classification for a run so cut-off / errored runs\n * can be surfaced for pattern analysis (see listErroredRuns). Best-effort —\n * never throws, since it runs on the completion path that must not fail the run.\n */\nexport async function setRunError(\n runId: string,\n errorCode: string | undefined,\n errorDetail: string | undefined,\n): Promise<void> {\n if (!errorCode && !errorDetail) return;\n try {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET error_code = ?, error_detail = ? WHERE id = ?`,\n args: [\n errorCode ?? null,\n errorDetail ? errorDetail.slice(0, 2000) : null,\n runId,\n ],\n });\n } catch {\n // Diagnostics are best-effort; never let them break completion.\n }\n}\n\n/** Update the run's liveness heartbeat. Called periodically by run-manager. */\nexport async function updateRunHeartbeat(runId: string): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET heartbeat_at = ? WHERE id = ?`,\n args: [Date.now(), runId],\n });\n}\n\n/**\n * Bump `last_progress_at` — call this whenever the agent actually emits an\n * event (token, tool call, message). Distinct from `heartbeat_at` so the\n * stuck-detector can tell \"process alive but nothing happening\" from\n * \"process dead.\" Callers should throttle (run-manager debounces to ~1/s).\n */\nexport async function bumpRunProgress(runId: string): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET last_progress_at = ? WHERE id = ?`,\n args: [Date.now(), runId],\n });\n}\n\n/**\n * If the given run is marked \"running\" in SQL but its heartbeat is stale\n * (producer likely crashed), flip it to \"errored\" so watchers stop waiting.\n * Returns true if the row was reaped.\n */\nexport async function reapIfStale(\n runId: string,\n maxStaleMs: number = RUN_STALE_MS,\n): Promise<boolean> {\n await ensureRunTables();\n const client = getDbExec();\n const completedAt = Date.now();\n const cutoff = completedAt - maxStaleMs;\n const { rowsAffected } = await client.execute({\n sql: `UPDATE agent_runs\n SET status = 'errored',\n completed_at = ?,\n error_code = ?,\n error_detail = ?\n WHERE id = ?\n AND status = 'running'\n AND COALESCE(heartbeat_at, started_at) < ?`,\n args: [\n completedAt,\n STALE_RUN_ERROR_EVENT.errorCode,\n STALE_RUN_ERROR_EVENT.details,\n runId,\n cutoff,\n ],\n });\n const reaped = (rowsAffected ?? 0) > 0;\n if (reaped) {\n await safeAppendTerminalRunEvent(\n runId,\n STALE_RUN_ERROR_EVENT,\n \"reap-if-stale\",\n );\n }\n return reaped;\n}\n\nexport async function updateRunStatus(\n runId: string,\n status: \"completed\" | \"errored\" | \"aborted\",\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET status = ?, completed_at = ? WHERE id = ?`,\n args: [status, Date.now(), runId],\n });\n}\n\nexport async function markRunAborted(\n runId: string,\n reason?: string,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ? WHERE id = ?`,\n args: [reason ?? \"user\", Date.now(), runId],\n });\n await safeAppendTerminalRunEvent(runId, { type: \"done\" }, \"mark-aborted\");\n}\n\nexport async function isRunAborted(runId: string): Promise<boolean> {\n return (await getRunAbortState(runId)).aborted;\n}\n\nexport async function getRunAbortState(\n runId: string,\n): Promise<{ aborted: boolean; reason?: string }> {\n await ensureRunTables();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT status, abort_reason FROM agent_runs WHERE id = ?`,\n args: [runId],\n });\n if (rows.length === 0) return { aborted: false };\n const row = rows[0] as { status: string; abort_reason?: string | null };\n if (row.status !== \"aborted\") return { aborted: false };\n return {\n aborted: true,\n ...(row.abort_reason ? { reason: row.abort_reason } : {}),\n };\n}\n\nexport async function insertRunEvent(\n runId: string,\n seq: number,\n eventData: string,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n // ON CONFLICT DO NOTHING: a (runId, seq) collision can happen on the\n // soft-timeout / terminal-event path where `pendingTerminalEvent` was\n // assigned a seq that later gets reused by an event pushed after it.\n // It can also race with `appendTerminalRunEvent` (max-seq + 1) when a\n // run aborts at the same time the producer emits its final event.\n // Treat the second write as a no-op so the run completes cleanly.\n await client.execute({\n sql: `INSERT INTO agent_run_events (run_id, seq, event_data) VALUES (?, ?, ?) ON CONFLICT (run_id, seq) DO NOTHING`,\n args: [runId, seq, eventData],\n });\n}\n\nexport async function getRunEventsSince(\n runId: string,\n fromSeq: number,\n): Promise<Array<{ seq: number; eventData: string }>> {\n await ensureRunTables();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT seq, event_data FROM agent_run_events WHERE run_id = ? AND seq >= ? ORDER BY seq ASC`,\n args: [runId, fromSeq],\n });\n return rows.map((r) => {\n const row = r as { seq: number | string; event_data: string };\n return { seq: Number(row.seq), eventData: row.event_data };\n });\n}\n\nexport async function getRunById(runId: string): Promise<{\n id: string;\n threadId: string;\n status: string;\n startedAt: number;\n} | null> {\n await ensureRunTables();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, thread_id, status, started_at FROM agent_runs WHERE id = ?`,\n args: [runId],\n });\n if (rows.length === 0) return null;\n const r = rows[0] as {\n id: string;\n thread_id: string;\n status: string;\n started_at: number | string;\n };\n return {\n id: r.id,\n threadId: r.thread_id,\n status: r.status,\n startedAt: Number(r.started_at),\n };\n}\n\nexport async function getRunByThread(\n threadId: string,\n options?: { includeTerminal?: boolean },\n): Promise<{\n id: string;\n threadId: string;\n status: string;\n startedAt: number;\n heartbeatAt: number | null;\n completedAt: number | null;\n lastProgressAt: number | null;\n} | null> {\n await ensureRunTables();\n const client = getDbExec();\n const sql = options?.includeTerminal\n ? `SELECT id, thread_id, status, started_at, heartbeat_at, completed_at, last_progress_at FROM agent_runs WHERE thread_id = ? ORDER BY started_at DESC LIMIT 1`\n : `SELECT id, thread_id, status, started_at, heartbeat_at, completed_at, last_progress_at FROM agent_runs WHERE thread_id = ? AND status = 'running' ORDER BY started_at DESC LIMIT 1`;\n const { rows } = await client.execute({ sql, args: [threadId] });\n if (rows.length === 0) return null;\n const r = rows[0] as {\n id: string;\n thread_id: string;\n status: string;\n started_at: number | string;\n heartbeat_at: number | string | null;\n completed_at: number | string | null;\n last_progress_at: number | string | null;\n };\n return {\n id: r.id,\n threadId: r.thread_id,\n status: r.status,\n startedAt: Number(r.started_at),\n heartbeatAt: r.heartbeat_at == null ? null : Number(r.heartbeat_at),\n completedAt: r.completed_at == null ? null : Number(r.completed_at),\n lastProgressAt:\n r.last_progress_at == null ? null : Number(r.last_progress_at),\n };\n}\n\n/**\n * Expire any \"running\" rows whose heartbeat is stale — producer died.\n * Safe to call at server startup on multi-isolate deployments: only rows\n * without a fresh heartbeat get reaped, so runs owned by OTHER live\n * isolates (which keep heartbeating) are left alone.\n */\nexport async function reapAllStaleRuns(): Promise<number> {\n await ensureRunTables();\n const client = getDbExec();\n const heartbeatCutoff = Date.now() - RUN_STALE_MS;\n const stale = await client.execute({\n sql: `SELECT id FROM agent_runs\n WHERE status = 'running'\n AND COALESCE(heartbeat_at, started_at) < ?`,\n args: [heartbeatCutoff],\n });\n const completedAt = Date.now();\n const { rowsAffected } = await client.execute({\n sql: `UPDATE agent_runs\n SET status = 'errored',\n completed_at = ?,\n error_code = ?,\n error_detail = ?\n WHERE status = 'running'\n AND COALESCE(heartbeat_at, started_at) < ?`,\n args: [\n completedAt,\n STALE_RUN_ERROR_EVENT.errorCode,\n STALE_RUN_ERROR_EVENT.details,\n heartbeatCutoff,\n ],\n });\n for (const row of stale.rows) {\n const id = (row as { id?: unknown }).id;\n if (typeof id === \"string\") {\n await safeAppendTerminalRunEvent(\n id,\n STALE_RUN_ERROR_EVENT,\n \"reap-all-stale\",\n );\n }\n }\n return rowsAffected ?? 0;\n}\n\n/** Delete old runs and expire stale \"running\" rows that haven't had activity\n * (e.g. worker crashed before updating status). Completed runs are pruned at\n * `olderThanMs`; errored/aborted runs are kept until `erroredOlderThanMs` (a\n * longer window, falling back to `olderThanMs`) so their event log survives\n * for cut-off pattern analysis via listErroredRuns. */\nexport async function cleanupOldRuns(\n olderThanMs: number,\n erroredOlderThanMs?: number,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n const cutoff = Date.now() - olderThanMs;\n const erroredCutoff =\n Date.now() - Math.max(erroredOlderThanMs ?? 0, olderThanMs);\n // Expire stale running rows on the absolute-age threshold — safety net\n // for runs that never received a heartbeat (very old deployments). The\n // SELECT covers BOTH UPDATE conditions so the terminal-event-append loop\n // below catches every row we're about to flip — a 24h-old row with a\n // somehow-fresh heartbeat would slip past a heartbeat-only SELECT.\n const heartbeatCutoff = Date.now() - RUN_STALE_MS;\n const stale = await client.execute({\n sql: `SELECT id FROM agent_runs\n WHERE status = 'running'\n AND (\n COALESCE(heartbeat_at, started_at) < ?\n OR started_at < ?\n )`,\n args: [heartbeatCutoff, cutoff],\n });\n const completedAt = Date.now();\n await client.execute({\n sql: `UPDATE agent_runs\n SET status = 'errored',\n completed_at = ?,\n error_code = ?,\n error_detail = ?\n WHERE status = 'running' AND started_at < ?`,\n args: [\n completedAt,\n STALE_RUN_ERROR_EVENT.errorCode,\n STALE_RUN_ERROR_EVENT.details,\n cutoff,\n ],\n });\n // Also expire runs whose heartbeat is stale — producer has died.\n await client.execute({\n sql: `UPDATE agent_runs\n SET status = 'errored',\n completed_at = ?,\n error_code = ?,\n error_detail = ?\n WHERE status = 'running'\n AND COALESCE(heartbeat_at, started_at) < ?`,\n args: [\n completedAt,\n STALE_RUN_ERROR_EVENT.errorCode,\n STALE_RUN_ERROR_EVENT.details,\n heartbeatCutoff,\n ],\n });\n for (const row of stale.rows) {\n const id = (row as { id?: unknown }).id;\n if (typeof id === \"string\") {\n await safeAppendTerminalRunEvent(\n id,\n STALE_RUN_ERROR_EVENT,\n \"cleanup-old-runs\",\n );\n }\n }\n // Delete events for old terminal runs. Completed runs prune at `cutoff`;\n // errored/aborted runs are retained until the (longer) `erroredCutoff`.\n await client.execute({\n sql: `DELETE FROM agent_run_events WHERE run_id IN (\n SELECT id FROM agent_runs\n WHERE (status = 'completed' AND completed_at < ?)\n OR (status IN ('errored', 'aborted') AND completed_at < ?)\n )`,\n args: [cutoff, erroredCutoff],\n });\n await client.execute({\n sql: `DELETE FROM agent_runs\n WHERE (status = 'completed' AND completed_at < ?)\n OR (status IN ('errored', 'aborted') AND completed_at < ?)`,\n args: [cutoff, erroredCutoff],\n });\n}\n\n/**\n * List recent errored/aborted runs for cut-off pattern analysis. Read-only,\n * bounded, and ordered newest-first. Surfaced via the list-errored-runs action\n * so the team can see why chats are failing (terminal error code, duration,\n * turn linkage) instead of discovering it ad hoc.\n */\nexport async function listErroredRuns(options?: {\n limit?: number;\n sinceMs?: number;\n}): Promise<\n Array<{\n id: string;\n threadId: string;\n turnId: string | null;\n status: string;\n errorCode: string | null;\n errorDetail: string | null;\n startedAt: number;\n completedAt: number | null;\n durationMs: number | null;\n }>\n> {\n await ensureRunTables();\n const client = getDbExec();\n const limit = Math.min(Math.max(Math.floor(options?.limit ?? 100), 1), 1000);\n const since =\n options?.sinceMs && options.sinceMs > 0 ? Date.now() - options.sinceMs : 0;\n const { rows } = await client.execute({\n sql: `SELECT id, thread_id, turn_id, status, error_code, error_detail, started_at, completed_at\n FROM agent_runs\n WHERE status IN ('errored', 'aborted')\n AND COALESCE(completed_at, started_at) >= ?\n ORDER BY COALESCE(completed_at, started_at) DESC\n LIMIT ${limit}`,\n args: [since],\n });\n return rows.map((r) => {\n const row = r as {\n id: string;\n thread_id: string;\n turn_id: string | null;\n status: string;\n error_code: string | null;\n error_detail: string | null;\n started_at: number | string;\n completed_at: number | string | null;\n };\n const startedAt = Number(row.started_at);\n const completedAt =\n row.completed_at == null ? null : Number(row.completed_at);\n return {\n id: row.id,\n threadId: row.thread_id,\n turnId: row.turn_id ?? null,\n status: row.status,\n errorCode: row.error_code ?? null,\n errorDetail: row.error_detail ?? null,\n startedAt,\n completedAt,\n durationMs: completedAt == null ? null : completedAt - startedAt,\n };\n });\n}\n\n/**\n * Idempotently append a terminal event to a run's event stream. No-op if the\n * stream already ends in a terminal event. Used by reapers AND by SSE\n * reconnect paths that discover an `errored` run row with no terminal event\n * (e.g. an earlier reaper's silent `.catch(() => {})` swallowed the append).\n *\n * Persisting from the reconnect path is what keeps the system self-healing:\n * subsequent reconnects replay the proper terminal event from SQL instead of\n * synthesizing a fresh one each time.\n */\nexport async function ensureTerminalRunEvent(\n runId: string,\n event: Record<string, unknown>,\n): Promise<void> {\n return appendTerminalRunEvent(runId, event);\n}\n\n/**\n * Append a terminal run event, retrying once on failure and reporting to\n * Sentry if both attempts fail. Background reaper paths can't surface errors\n * to a user, but they MUST eventually persist a terminal event — losing it\n * leaves reconnecting clients staring at a bare `status='errored'` row with\n * no payload to render. The previous `.catch(() => {})` callsites silently\n * dropped transient SQL blips and produced exactly that bug. Never throws.\n */\nasync function safeAppendTerminalRunEvent(\n runId: string,\n event: Record<string, unknown>,\n source: string,\n): Promise<void> {\n let firstError: unknown;\n try {\n await appendTerminalRunEvent(runId, event);\n return;\n } catch (err) {\n firstError = err;\n }\n // Brief backoff — most \"transient\" SQL failures (connection blip, lock\n // contention) clear within a couple hundred ms.\n await new Promise<void>((resolve) => setTimeout(resolve, 100));\n try {\n await appendTerminalRunEvent(runId, event);\n } catch (retryErr) {\n captureError(retryErr, {\n tags: {\n component: \"agent-run-store\",\n operation: \"append-terminal-event\",\n source,\n },\n extra: {\n runId,\n eventType: typeof event.type === \"string\" ? event.type : \"(unknown)\",\n firstError:\n firstError instanceof Error ? firstError.message : String(firstError),\n },\n });\n }\n}\n\nasync function appendTerminalRunEvent(\n runId: string,\n event: Record<string, unknown>,\n): Promise<void> {\n await ensureRunTables();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT seq, event_data FROM agent_run_events WHERE run_id = ? ORDER BY seq DESC LIMIT 1`,\n args: [runId],\n });\n const last = rows[0] as\n | { seq?: number | string; event_data?: string }\n | undefined;\n if (last?.event_data) {\n try {\n const parsed = JSON.parse(last.event_data);\n if (\n parsed?.type === \"done\" ||\n parsed?.type === \"error\" ||\n parsed?.type === \"missing_api_key\" ||\n parsed?.type === \"loop_limit\" ||\n parsed?.type === \"auto_continue\"\n ) {\n return;\n }\n } catch {\n // Ignore malformed rows and append the terminal event.\n }\n }\n const nextSeq = last ? Number(last.seq ?? -1) + 1 : 0;\n await client.execute({\n sql: `INSERT INTO agent_run_events (run_id, seq, event_data) VALUES (?, ?, ?) ON CONFLICT (run_id, seq) DO NOTHING`,\n args: [runId, nextSeq, JSON.stringify(event)],\n });\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentChatAttachment, RunEvent } from "./types.js";
|
|
2
|
+
import type { EngineMessage } from "./engine/types.js";
|
|
2
3
|
import { type CodeAgentTranscriptEvent as CoreCodeAgentTranscriptEvent } from "../code-agents/transcript-normalizer.js";
|
|
3
4
|
interface ContentPart {
|
|
4
5
|
type: string;
|
|
@@ -46,6 +47,17 @@ export declare function buildAssistantMessage(events: RunEvent[], runId?: string
|
|
|
46
47
|
* fail with "Parent message not found".
|
|
47
48
|
*/
|
|
48
49
|
export declare function normalizeThreadRepository(repo: any): any;
|
|
50
|
+
/**
|
|
51
|
+
* Rebuild a flat `EngineMessage[]` from persisted thread_data (the
|
|
52
|
+
* assistant-ui ExportedMessageRepository shape). Text-only — tool calls/results
|
|
53
|
+
* are flattened to their text so a continuation run gets the conversation
|
|
54
|
+
* prefix as plain context (Anthropic's prompt cache makes the resume cheap).
|
|
55
|
+
*
|
|
56
|
+
* Used to resume a background sub-agent in a fresh function invocation (the
|
|
57
|
+
* server-side analog of the browser re-POSTing history for the main chat).
|
|
58
|
+
* Originally inlined in `integrations/webhook-handler.ts`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function threadDataToEngineMessages(threadData: string | Record<string, unknown> | null | undefined): EngineMessage[];
|
|
49
61
|
export interface CodeAgentThreadTranscriptEvent {
|
|
50
62
|
id: string;
|
|
51
63
|
runId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thread-data-builder.d.ts","sourceRoot":"","sources":["../../src/agent/thread-data-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAEL,KAAK,wBAAwB,IAAI,4BAA4B,EAI9D,MAAM,yCAAyC,CAAC;AAEjD,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,4BAA4B;IACpC,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,gBAAgB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC,CAAC;AAC9E,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAwCvD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,QAAQ,EAAE,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,GAAE,4BAAiC,GACzC;IACD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EACF;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GACpC;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"thread-data-builder.d.ts","sourceRoot":"","sources":["../../src/agent/thread-data-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAEL,KAAK,wBAAwB,IAAI,4BAA4B,EAI9D,MAAM,yCAAyC,CAAC;AAEjD,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,4BAA4B;IACpC,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,KAAK,gBAAgB,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC,CAAC;AAC9E,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAwCvD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,QAAQ,EAAE,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,GAAE,4BAAiC,GACzC;IACD,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,MAAM,EACF;QAAE,IAAI,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GACpC;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,GAAG,IAAI,CAyIP;AA+ND;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAqCxD;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS,GAC9D,aAAa,EAAE,CA4BjB;AAED,MAAM,WAAW,8BAA8B;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,4BAA4B,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,EAAE,4BAA4B,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,6CAA6C;IAC5D,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,wBAAgB,sCAAsC,CACpD,MAAM,EAAE,SAAS,8BAA8B,EAAE,EACjD,OAAO,GAAE,6CAAkD,GAC1D,GAAG,CA4GL;AAaD;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,8BAA8B,CAAC,EAAE,OAAO,CAAC;IACzC,4BAA4B,CAAC,EAAE,OAAO,CAAC;CACxC;AAED,wBAAgB,4BAA4B,CAC1C,YAAY,EAAE,GAAG,EACjB,YAAY,EAAE,GAAG,EACjB,OAAO,GAAE,sBAA2B,OAkFrC;AAqFD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,GAAG;IACF,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,CAcA;AAyID,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,GAAG,GAAG,CAetE;AA+BD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,GAAG,EACT,YAAY,EAAE,gBAAgB,GAC7B,GAAG,CA0BL;AAqDD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,GAAG,EACT,YAAY,EAAE,gBAAgB,EAC9B,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C,GAAG,CAqFL;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAG3D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,GAAG,GAAG;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CA0BA"}
|
|
@@ -60,7 +60,10 @@ export function buildAssistantMessage(events, runId, options = {}) {
|
|
|
60
60
|
continue;
|
|
61
61
|
}
|
|
62
62
|
if (event.type === "tool_start") {
|
|
63
|
-
|
|
63
|
+
toolCallCounter += 1;
|
|
64
|
+
const toolCallId = runId
|
|
65
|
+
? `${runId}:tc_${toolCallCounter}`
|
|
66
|
+
: `tc_${toolCallCounter}`;
|
|
64
67
|
const args = (event.input ?? {});
|
|
65
68
|
content.push({
|
|
66
69
|
type: "tool-call",
|
|
@@ -211,6 +214,21 @@ function normalizeAttachmentIdentity(attachments) {
|
|
|
211
214
|
contentType: att?.contentType,
|
|
212
215
|
}));
|
|
213
216
|
}
|
|
217
|
+
// Strip the render-only `toolCallId` before fingerprinting. The id is generated
|
|
218
|
+
// differently depending on who built the message — the server now scopes it by
|
|
219
|
+
// run (`${runId}:tc_1`) while the client's live stream uses a bare counter
|
|
220
|
+
// (`tc_1`) — so the client export and the server fold of the SAME tool-call turn
|
|
221
|
+
// would otherwise hash to different fingerprints and fail to dedupe, leaving the
|
|
222
|
+
// turn rendered twice. The id never participates in message identity (history
|
|
223
|
+
// replay regenerates its own ids), so hashing content without it is the correct
|
|
224
|
+
// notion of "same message".
|
|
225
|
+
function normalizeContentForFingerprint(content) {
|
|
226
|
+
if (!Array.isArray(content))
|
|
227
|
+
return content;
|
|
228
|
+
return content.map((part) => part && typeof part === "object" && part.type === "tool-call"
|
|
229
|
+
? { ...part, toolCallId: undefined }
|
|
230
|
+
: part);
|
|
231
|
+
}
|
|
214
232
|
function messageIdentityKeys(message) {
|
|
215
233
|
const keys = [];
|
|
216
234
|
if (typeof message?.id === "string" && message.id) {
|
|
@@ -239,7 +257,7 @@ function messageIdentityKeys(message) {
|
|
|
239
257
|
try {
|
|
240
258
|
keys.push(`fingerprint:${JSON.stringify({
|
|
241
259
|
role: message?.role,
|
|
242
|
-
content: message?.content,
|
|
260
|
+
content: normalizeContentForFingerprint(message?.content),
|
|
243
261
|
attachments: normalizeAttachmentIdentity(message?.attachments),
|
|
244
262
|
})}`);
|
|
245
263
|
}
|
|
@@ -250,7 +268,7 @@ function messageIdentityKeys(message) {
|
|
|
250
268
|
try {
|
|
251
269
|
keys.push(`user-fingerprint:${JSON.stringify({
|
|
252
270
|
role: message.role,
|
|
253
|
-
content: message.content,
|
|
271
|
+
content: normalizeContentForFingerprint(message.content),
|
|
254
272
|
attachments: normalizeAttachmentIdentity(message.attachments),
|
|
255
273
|
})}`);
|
|
256
274
|
}
|
|
@@ -300,13 +318,46 @@ function normalizeMessageEntry(entry, parentId) {
|
|
|
300
318
|
const message = getStoredMessage(entry);
|
|
301
319
|
if (!messageId(message))
|
|
302
320
|
return null;
|
|
321
|
+
const normalizedMessage = normalizeAssistantToolCallIds(message);
|
|
303
322
|
const runConfig = getStoredRunConfig(entry);
|
|
304
323
|
return {
|
|
305
|
-
message,
|
|
324
|
+
message: normalizedMessage,
|
|
306
325
|
parentId,
|
|
307
326
|
...(runConfig !== undefined ? { runConfig } : {}),
|
|
308
327
|
};
|
|
309
328
|
}
|
|
329
|
+
function uniqueToolCallId(toolCallId, seen) {
|
|
330
|
+
if (!seen.has(toolCallId))
|
|
331
|
+
return toolCallId;
|
|
332
|
+
let suffix = 2;
|
|
333
|
+
let candidate = `${toolCallId}__dedup_${suffix}`;
|
|
334
|
+
while (seen.has(candidate)) {
|
|
335
|
+
suffix += 1;
|
|
336
|
+
candidate = `${toolCallId}__dedup_${suffix}`;
|
|
337
|
+
}
|
|
338
|
+
return candidate;
|
|
339
|
+
}
|
|
340
|
+
function normalizeAssistantToolCallIds(message) {
|
|
341
|
+
if (message?.role !== "assistant" || !Array.isArray(message.content)) {
|
|
342
|
+
return message;
|
|
343
|
+
}
|
|
344
|
+
const seen = new Set();
|
|
345
|
+
let changed = false;
|
|
346
|
+
const content = message.content.map((part) => {
|
|
347
|
+
if (part?.type !== "tool-call" ||
|
|
348
|
+
typeof part.toolCallId !== "string" ||
|
|
349
|
+
part.toolCallId.length === 0) {
|
|
350
|
+
return part;
|
|
351
|
+
}
|
|
352
|
+
const nextToolCallId = uniqueToolCallId(part.toolCallId, seen);
|
|
353
|
+
seen.add(nextToolCallId);
|
|
354
|
+
if (nextToolCallId === part.toolCallId)
|
|
355
|
+
return part;
|
|
356
|
+
changed = true;
|
|
357
|
+
return { ...part, toolCallId: nextToolCallId };
|
|
358
|
+
});
|
|
359
|
+
return changed ? { ...message, content } : message;
|
|
360
|
+
}
|
|
310
361
|
/**
|
|
311
362
|
* Convert legacy/partially merged thread data into assistant-ui's exported
|
|
312
363
|
* repository shape and repair parent links so `threadRuntime.import()` cannot
|
|
@@ -342,6 +393,47 @@ export function normalizeThreadRepository(repo) {
|
|
|
342
393
|
headId && seenIds.has(headId) ? headId : (previousId ?? null);
|
|
343
394
|
return normalized;
|
|
344
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Rebuild a flat `EngineMessage[]` from persisted thread_data (the
|
|
398
|
+
* assistant-ui ExportedMessageRepository shape). Text-only — tool calls/results
|
|
399
|
+
* are flattened to their text so a continuation run gets the conversation
|
|
400
|
+
* prefix as plain context (Anthropic's prompt cache makes the resume cheap).
|
|
401
|
+
*
|
|
402
|
+
* Used to resume a background sub-agent in a fresh function invocation (the
|
|
403
|
+
* server-side analog of the browser re-POSTing history for the main chat).
|
|
404
|
+
* Originally inlined in `integrations/webhook-handler.ts`.
|
|
405
|
+
*/
|
|
406
|
+
export function threadDataToEngineMessages(threadData) {
|
|
407
|
+
const messages = [];
|
|
408
|
+
if (!threadData)
|
|
409
|
+
return messages;
|
|
410
|
+
let data;
|
|
411
|
+
try {
|
|
412
|
+
data = typeof threadData === "string" ? JSON.parse(threadData) : threadData;
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return messages;
|
|
416
|
+
}
|
|
417
|
+
if (!Array.isArray(data?.messages))
|
|
418
|
+
return messages;
|
|
419
|
+
for (const entry of data.messages) {
|
|
420
|
+
const m = entry?.message ?? entry;
|
|
421
|
+
if (!m || (m.role !== "user" && m.role !== "assistant"))
|
|
422
|
+
continue;
|
|
423
|
+
const text = typeof m.content === "string"
|
|
424
|
+
? m.content
|
|
425
|
+
: Array.isArray(m.content)
|
|
426
|
+
? m.content
|
|
427
|
+
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
428
|
+
.map((c) => c.text)
|
|
429
|
+
.join("\n")
|
|
430
|
+
: "";
|
|
431
|
+
if (!text.trim())
|
|
432
|
+
continue;
|
|
433
|
+
messages.push({ role: m.role, content: [{ type: "text", text }] });
|
|
434
|
+
}
|
|
435
|
+
return messages;
|
|
436
|
+
}
|
|
345
437
|
export function buildRepositoryFromCodeAgentTranscript(events, options = {}) {
|
|
346
438
|
const normalized = normalizeCodeAgentTranscript(events.map(toCoreCodeAgentTranscriptEvent));
|
|
347
439
|
const repo = {
|
|
@@ -823,7 +915,10 @@ function appendFoldedContent(existing, incoming) {
|
|
|
823
915
|
merged.push({ ...part });
|
|
824
916
|
}
|
|
825
917
|
}
|
|
826
|
-
return
|
|
918
|
+
return normalizeAssistantToolCallIds({
|
|
919
|
+
role: "assistant",
|
|
920
|
+
content: merged,
|
|
921
|
+
}).content;
|
|
827
922
|
}
|
|
828
923
|
/**
|
|
829
924
|
* Fold a continuation run's assistant message onto the single durable message
|
|
@@ -896,7 +991,10 @@ export function foldAssistantTurn(repo, assistantMsg, options) {
|
|
|
896
991
|
delete mergedCustom.continued;
|
|
897
992
|
const mergedMessage = {
|
|
898
993
|
...lastMsg,
|
|
899
|
-
content:
|
|
994
|
+
content: normalizeAssistantToolCallIds({
|
|
995
|
+
role: "assistant",
|
|
996
|
+
content: mergedContent,
|
|
997
|
+
}).content,
|
|
900
998
|
// The freshest chunk's status wins: a clean done supersedes a prior
|
|
901
999
|
// partial; a real error supersedes a partial.
|
|
902
1000
|
status: assistantMsg.status ?? lastMsg.status,
|