@agent-native/core 0.220.0 → 0.220.1-nightly-20261024183236

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/action.d.ts +18 -0
  2. package/dist/action.js +15 -0
  3. package/dist/agent/production-agent.js +42 -15
  4. package/dist/agent/run-manager.js +24 -4
  5. package/dist/automation/index.d.ts +1 -1
  6. package/dist/client/sse-event-processor.d.ts +7 -0
  7. package/dist/client/sse-event-processor.js +3 -2
  8. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  9. package/dist/integrations/google-docs-poller.d.ts +9 -1
  10. package/dist/integrations/google-docs-poller.js +16 -5
  11. package/dist/integrations/pending-tasks-retry-job.d.ts +1 -1
  12. package/dist/integrations/pending-tasks-retry-job.js +6 -2
  13. package/dist/integrations/plugin.js +36 -5
  14. package/dist/integrations/remote-push-delivery-job.d.ts +1 -1
  15. package/dist/integrations/remote-push-delivery-job.js +6 -2
  16. package/dist/integrations/remote-retry-job.d.ts +1 -1
  17. package/dist/integrations/remote-retry-job.js +6 -2
  18. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  19. package/dist/notifications/routes.d.ts +4 -4
  20. package/dist/observability/routes.d.ts +7 -7
  21. package/dist/org/handlers.d.ts +1 -1
  22. package/dist/progress/routes.d.ts +1 -1
  23. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  24. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  25. package/dist/provider-api/corpus-jobs.js +3 -21
  26. package/dist/provider-api/index.js +17 -23
  27. package/dist/provider-api/request-body.d.ts +1 -0
  28. package/dist/provider-api/request-body.js +50 -0
  29. package/dist/provider-api/staging.js +2 -20
  30. package/dist/secrets/routes.d.ts +10 -10
  31. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  32. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  33. package/dist/server/background-jobs.d.ts +3 -0
  34. package/dist/server/background-jobs.js +33 -0
  35. package/dist/server/core-routes-plugin.js +20 -2
  36. package/dist/server/dev-database-lifecycle.js +13 -3
  37. package/dist/server/interval-job.d.ts +2 -1
  38. package/dist/server/interval-job.js +1 -1
  39. package/dist/shared/poll-engine.d.ts +2 -1
  40. package/dist/shared/poll-engine.js +25 -1
  41. package/dist/shared/progress-events.d.ts +1 -0
  42. package/dist/shared/progress-events.js +13 -0
  43. package/dist/triggers/actions/manage-automation.d.ts +6 -6
  44. package/dist/triggers/dispatcher.d.ts +1 -0
  45. package/dist/triggers/dispatcher.js +28 -2
  46. package/package.json +3 -3
@@ -73,9 +73,9 @@ export declare const setWorkspaceAppDefaultVisibilityHandler: import("h3").Event
73
73
  visibility: WorkspaceAppDefaultVisibility;
74
74
  }>>;
75
75
  export declare const createOrgHandler: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
76
+ name?: undefined;
76
77
  success: boolean;
77
78
  id?: undefined;
78
- name?: undefined;
79
79
  role?: undefined;
80
80
  } | {
81
81
  success?: undefined;
@@ -2,6 +2,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
2
2
  error: string;
3
3
  ok?: undefined;
4
4
  } | {
5
- ok: boolean;
6
5
  error?: undefined;
6
+ ok: boolean;
7
7
  }>>;
@@ -75,7 +75,6 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
75
75
  user: "user";
76
76
  }>>;
77
77
  }, z.core.$strip>>, {
78
- message?: undefined;
79
78
  id?: undefined;
80
79
  deleted?: undefined;
81
80
  provider?: undefined;
@@ -92,8 +91,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
92
91
  found?: undefined;
93
92
  registered?: undefined;
94
93
  label?: undefined;
95
- } | {
96
94
  message?: undefined;
95
+ } | {
97
96
  id?: undefined;
98
97
  deleted?: undefined;
99
98
  count?: undefined;
@@ -102,8 +101,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
102
101
  provider: import("../custom-registry.js").CustomProviderConfig;
103
102
  registered?: undefined;
104
103
  label?: undefined;
105
- } | {
106
104
  message?: undefined;
105
+ } | {
107
106
  deleted?: undefined;
108
107
  provider?: undefined;
109
108
  count?: undefined;
@@ -112,8 +111,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
112
111
  id: string;
113
112
  registered?: undefined;
114
113
  label?: undefined;
115
- } | {
116
114
  message?: undefined;
115
+ } | {
117
116
  provider?: undefined;
118
117
  count?: undefined;
119
118
  providers?: undefined;
@@ -122,6 +121,7 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
122
121
  id: string;
123
122
  registered?: undefined;
124
123
  label?: undefined;
124
+ message?: undefined;
125
125
  } | {
126
126
  deleted?: undefined;
127
127
  provider?: undefined;
@@ -316,9 +316,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
316
316
  notes?: string | undefined;
317
317
  scope?: "org" | "user" | undefined;
318
318
  }, {
319
+ message?: undefined;
319
320
  provider?: undefined;
320
321
  id?: undefined;
321
- message?: undefined;
322
322
  deleted?: undefined;
323
323
  providers: {
324
324
  id: string;
@@ -334,9 +334,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
334
334
  registered?: undefined;
335
335
  label?: undefined;
336
336
  } | {
337
+ message?: undefined;
337
338
  id?: undefined;
338
339
  count?: undefined;
339
- message?: undefined;
340
340
  deleted?: undefined;
341
341
  providers?: undefined;
342
342
  found: boolean;
@@ -344,9 +344,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
344
344
  registered?: undefined;
345
345
  label?: undefined;
346
346
  } | {
347
+ message?: undefined;
347
348
  provider?: undefined;
348
349
  count?: undefined;
349
- message?: undefined;
350
350
  deleted?: undefined;
351
351
  providers?: undefined;
352
352
  found: boolean;
@@ -354,9 +354,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
354
354
  registered?: undefined;
355
355
  label?: undefined;
356
356
  } | {
357
+ message?: undefined;
357
358
  provider?: undefined;
358
359
  count?: undefined;
359
- message?: undefined;
360
360
  providers?: undefined;
361
361
  found?: undefined;
362
362
  deleted: boolean;
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { defineAction } from "../action.js";
4
4
  import { getCredentialContext } from "../server/request-context.js";
5
5
  import { appendProviderCorpusJobHits, createProviderCorpusJob, deleteProviderCorpusJob, getProviderCorpusJob, getProviderCorpusJobHits, listProviderCorpusJobs, updateProviderCorpusJob, } from "./corpus-jobs-store.js";
6
+ import { setBodyValueAtPath } from "./request-body.js";
6
7
  import { getStagedDatasetMeta, getStagedDatasetRows, stagedDatasetVersion, } from "./staged-datasets-store.js";
7
8
  const MethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
8
9
  const ProviderRequestSchema = z.object({
@@ -745,7 +746,7 @@ function buildPaginatedRequest(request, pagination, state) {
745
746
  query = { ...query, [pagination.cursorParam]: state.cursor };
746
747
  }
747
748
  if (pagination.cursorBodyPath) {
748
- body = setAtPath(body, pagination.cursorBodyPath, state.cursor);
749
+ body = setBodyValueAtPath(body, pagination.cursorBodyPath, state.cursor);
749
750
  }
750
751
  }
751
752
  return { ...request, query, body };
@@ -758,7 +759,7 @@ function buildBatchRequest(request, batch, values) {
758
759
  query = { ...query, [batch.itemQueryParam]: payload };
759
760
  }
760
761
  if (batch.itemBodyPath) {
761
- body = setAtPath(body, batch.itemBodyPath, values);
762
+ body = setBodyValueAtPath(body, batch.itemBodyPath, values);
762
763
  }
763
764
  return { ...request, query, body };
764
765
  }
@@ -1114,25 +1115,6 @@ function getAtPath(value, path) {
1114
1115
  }
1115
1116
  return current;
1116
1117
  }
1117
- function setAtPath(base, path, value) {
1118
- const parts = pathParts(path);
1119
- if (!parts.length)
1120
- return value;
1121
- const root = base && typeof base === "object" && !Array.isArray(base)
1122
- ? JSON.parse(JSON.stringify(base))
1123
- : {};
1124
- let current = root;
1125
- for (const part of parts.slice(0, -1)) {
1126
- const existing = current[part];
1127
- const next = existing && typeof existing === "object" && !Array.isArray(existing)
1128
- ? { ...existing }
1129
- : {};
1130
- current[part] = next;
1131
- current = next;
1132
- }
1133
- current[parts[parts.length - 1]] = value;
1134
- return root;
1135
- }
1136
1118
  function collectSearchStrings(item, textPaths, maxFields) {
1137
1119
  const paths = (textPaths ?? []).filter((path) => path.trim());
1138
1120
  if (!paths.length)
@@ -14,6 +14,7 @@ import { resolveWorkspaceConnectionCredentialForApp } from "../workspace-connect
14
14
  import { resolveWorkspaceConnectionForApp, } from "../workspace-connections/store.js";
15
15
  import { createProviderQuotaIdentity, createProviderRequestDedupeKey, executeWithProviderQuota, } from "./quota-governor.js";
16
16
  export { upsertCustomProvider, deleteCustomProvider, listCustomProviders, getCustomProvider, validateCustomBaseUrl, assertCanMutateCustomProviderScope, CustomProviderAuthError, } from "./custom-registry.js";
17
+ import { setBodyValueAtPath } from "./request-body.js";
17
18
  export const PROVIDER_API_IDS = [
18
19
  "amplitude",
19
20
  "apollo",
@@ -1583,7 +1584,7 @@ export async function executeProviderApiRequest(args, runtime) {
1583
1584
  query: queryWithCursor,
1584
1585
  });
1585
1586
  const bodyWithCursor = extra?.bodyCursor
1586
- ? setValueAtPath(substituteUnknown(args.body, placeholders), extra.bodyCursor.path, extra.bodyCursor.value)
1587
+ ? setBodyValueAtPath(substituteUnknown(args.body, placeholders), extra.bodyCursor.path, extra.bodyCursor.value)
1587
1588
  : substituteUnknown(args.body, placeholders);
1588
1589
  const pageBody = prepareBody(bodyWithCursor, headers);
1589
1590
  const requestKey = createProviderRequestDedupeKey({
@@ -2371,7 +2372,7 @@ async function executeCustomProviderApiRequest(args, customConfig, runtime) {
2371
2372
  query: queryWithCursor,
2372
2373
  });
2373
2374
  const bodyWithCursor = extra?.bodyCursor
2374
- ? setValueAtPath(args.body, extra.bodyCursor.path, extra.bodyCursor.value)
2375
+ ? setBodyValueAtPath(args.body, extra.bodyCursor.path, extra.bodyCursor.value)
2375
2376
  : args.body;
2376
2377
  const pageBody = prepareBody(bodyWithCursor, headers);
2377
2378
  const requestKey = createProviderRequestDedupeKey({
@@ -4594,9 +4595,21 @@ function sanitizeOutboundHeaders(value) {
4594
4595
  function prepareBody(body, headers) {
4595
4596
  if (body === undefined || body === null)
4596
4597
  return undefined;
4597
- if (typeof body === "string")
4598
- return body;
4599
4598
  const hasContentType = Object.keys(headers).some((name) => name.toLowerCase() === "content-type");
4599
+ if (typeof body === "string") {
4600
+ let isJson = false;
4601
+ try {
4602
+ JSON.parse(body);
4603
+ isJson = true;
4604
+ }
4605
+ catch {
4606
+ // coercion-ok: JSON.parse is a probe; a throw means plain text, which keeps its existing content type.
4607
+ }
4608
+ if (isJson && !hasContentType) {
4609
+ headers["Content-Type"] = "application/json";
4610
+ }
4611
+ return body;
4612
+ }
4600
4613
  if (!hasContentType)
4601
4614
  headers["Content-Type"] = "application/json";
4602
4615
  return JSON.stringify(body);
@@ -4919,25 +4932,6 @@ function mergeQueryObjects(base, extra) {
4919
4932
  }
4920
4933
  return extra;
4921
4934
  }
4922
- function setValueAtPath(base, path, value) {
4923
- const root = base && typeof base === "object" && !Array.isArray(base)
4924
- ? { ...base }
4925
- : {};
4926
- const parts = path.split(".").filter(Boolean);
4927
- if (!parts.length)
4928
- return root;
4929
- let current = root;
4930
- for (const part of parts.slice(0, -1)) {
4931
- const existing = current[part];
4932
- const next = existing && typeof existing === "object" && !Array.isArray(existing)
4933
- ? { ...existing }
4934
- : {};
4935
- current[part] = next;
4936
- current = next;
4937
- }
4938
- current[parts[parts.length - 1]] = value;
4939
- return root;
4940
- }
4941
4935
  function clampTimeout(timeoutMs) {
4942
4936
  if (!Number.isFinite(timeoutMs))
4943
4937
  return DEFAULT_TIMEOUT_MS;
@@ -0,0 +1 @@
1
+ export declare function setBodyValueAtPath(base: unknown, path: string, value: unknown): unknown;
@@ -0,0 +1,50 @@
1
+ const JSON_OBJECT_BODY_REQUIRED = "Body cursor pagination requires a JSON object request body.";
2
+ // The one place a pagination cursor or batch is written into a request body.
3
+ // A JSON-string body keeps its other fields and is re-serialized as a string.
4
+ export function setBodyValueAtPath(base, path, value) {
5
+ const wasString = typeof base === "string";
6
+ let parsed = base;
7
+ // A blank string body is an empty body.
8
+ if (typeof parsed === "string" && parsed.trim() === "")
9
+ parsed = undefined;
10
+ if (typeof parsed === "string") {
11
+ try {
12
+ parsed = JSON.parse(parsed);
13
+ }
14
+ catch {
15
+ throw new Error(JSON_OBJECT_BODY_REQUIRED);
16
+ }
17
+ }
18
+ if (parsed !== undefined &&
19
+ parsed !== null &&
20
+ (typeof parsed !== "object" || Array.isArray(parsed))) {
21
+ throw new Error(JSON_OBJECT_BODY_REQUIRED);
22
+ }
23
+ const root = parsed
24
+ ? { ...parsed }
25
+ : {};
26
+ // `ids[0]` and `ids.0` are the same path.
27
+ const parts = path
28
+ .replace(/\[(\d+)\]/g, ".$1")
29
+ .split(".")
30
+ .map((part) => part.trim())
31
+ .filter(Boolean);
32
+ const isIndex = (part) => part !== undefined && /^\d+$/.test(part);
33
+ let current = root;
34
+ parts.slice(0, -1).forEach((part, index) => {
35
+ const existing = current[part];
36
+ const indexNext = isIndex(parts[index + 1]);
37
+ const next = Array.isArray(existing) && indexNext
38
+ ? [...existing]
39
+ : existing && typeof existing === "object" && !Array.isArray(existing)
40
+ ? { ...existing }
41
+ : indexNext
42
+ ? []
43
+ : {};
44
+ current[part] = next;
45
+ current = next;
46
+ });
47
+ if (parts.length)
48
+ current[parts[parts.length - 1]] = value;
49
+ return wasString ? JSON.stringify(root) : root;
50
+ }
@@ -24,6 +24,7 @@
24
24
  * here; redaction is applied to the response returned to the model.
25
25
  */
26
26
  import { fail } from "../action.js";
27
+ import { setBodyValueAtPath } from "./request-body.js";
27
28
  import { rejectFailedProviderResult } from "./result-outcome.js";
28
29
  import { upsertStagedDataset, deriveColumns, legacyStagedDatasetId, stagedDatasetId, MAX_ROWS_PER_OWNER, } from "./staged-datasets-store.js";
29
30
  function getAtPath(obj, path) {
@@ -64,25 +65,6 @@ function extractNextCursor(body, path) {
64
65
  return null;
65
66
  return String(val);
66
67
  }
67
- function setAtPath(base, path, value) {
68
- const root = base && typeof base === "object" && !Array.isArray(base)
69
- ? { ...base }
70
- : {};
71
- const parts = path.split(".").filter(Boolean);
72
- if (!parts.length)
73
- return root;
74
- let current = root;
75
- for (const part of parts.slice(0, -1)) {
76
- const existing = current[part];
77
- const next = existing && typeof existing === "object" && !Array.isArray(existing)
78
- ? { ...existing }
79
- : {};
80
- current[part] = next;
81
- current = next;
82
- }
83
- current[parts[parts.length - 1]] = value;
84
- return root;
85
- }
86
68
  async function sleepMs(ms) {
87
69
  await new Promise((resolve) => setTimeout(resolve, ms));
88
70
  }
@@ -153,7 +135,7 @@ export async function stagingExecuteRequest(args, execute, ctx) {
153
135
  extraQuery[pagination.cursorParam] = lastCursor;
154
136
  }
155
137
  if (pagination.cursorBodyPath && lastCursor !== null) {
156
- nextBody = setAtPath(baseArgs.body, pagination.cursorBodyPath, lastCursor);
138
+ nextBody = setBodyValueAtPath(baseArgs.body, pagination.cursorBodyPath, lastCursor);
157
139
  }
158
140
  const hasCursorMode = Boolean(pagination.cursorParam) || Boolean(pagination.cursorBodyPath);
159
141
  if (pagination.pageParam && !hasCursorMode) {
@@ -57,39 +57,39 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
57
57
  } | {
58
58
  error: string;
59
59
  errorCode?: undefined;
60
- ok?: undefined;
61
60
  status?: undefined;
61
+ ok?: undefined;
62
62
  } | {
63
63
  error: string;
64
64
  errorCode: string;
65
- ok?: undefined;
66
65
  status?: undefined;
66
+ ok?: undefined;
67
67
  } | {
68
- error?: undefined;
69
68
  errorCode?: undefined;
70
69
  ok: boolean;
71
70
  status: string;
71
+ error?: undefined;
72
72
  } | {
73
- ok?: undefined;
74
73
  error: string;
75
74
  removed?: undefined;
75
+ ok?: undefined;
76
76
  } | {
77
- error?: undefined;
78
77
  ok: boolean;
79
78
  removed: boolean;
79
+ error?: undefined;
80
80
  }>>;
81
81
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
82
- ok?: undefined;
83
82
  error: string;
84
83
  note?: undefined;
84
+ ok?: undefined;
85
85
  } | {
86
- error?: undefined;
87
86
  ok: boolean;
88
87
  note?: undefined;
89
- } | {
90
88
  error?: undefined;
89
+ } | {
91
90
  ok: boolean;
92
91
  note: string;
92
+ error?: undefined;
93
93
  } | {
94
94
  note?: undefined;
95
95
  ok: boolean;
@@ -117,11 +117,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
117
117
  } | {
118
118
  error: string;
119
119
  } | {
120
- error?: undefined;
121
120
  ok: boolean;
122
121
  key: string;
123
- } | {
124
122
  error?: undefined;
123
+ } | {
125
124
  ok: boolean;
126
125
  removed: boolean;
126
+ error?: undefined;
127
127
  }>>;
@@ -8,8 +8,8 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
8
8
  * resolveEngine's credential detection.
9
9
  */
10
10
  export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
11
- error: string;
12
11
  ok?: undefined;
12
+ error: string;
13
13
  scope?: undefined;
14
14
  } | {
15
15
  ok: boolean;
@@ -1,6 +1,6 @@
1
1
  export declare function createAgentEngineOllamaModelsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
2
- ok?: undefined;
3
2
  error: string;
3
+ ok?: undefined;
4
4
  models?: undefined;
5
5
  } | {
6
6
  error?: undefined;
@@ -0,0 +1,3 @@
1
+ export type BackgroundJobStop = () => void | Promise<void>;
2
+ export declare function registerBackgroundJobStop(stop: BackgroundJobStop): void;
3
+ export declare function stopBackgroundJobs(timeoutMs?: number): Promise<void>;
@@ -0,0 +1,33 @@
1
+ /** How long shutdown waits for jobs to stop before it carries on without them. */
2
+ const STOP_TIMEOUT_MS = 5_000;
3
+ const stoppers = new Set();
4
+ export function registerBackgroundJobStop(stop) {
5
+ stoppers.add(stop);
6
+ }
7
+ export async function stopBackgroundJobs(timeoutMs = STOP_TIMEOUT_MS) {
8
+ const toStop = [...stoppers];
9
+ stoppers.clear();
10
+ const pending = new Set(toStop);
11
+ const stopped = Promise.all(toStop.map(async (stop) => {
12
+ try {
13
+ await stop();
14
+ }
15
+ catch (error) {
16
+ console.error("[agent-native] background job stop failed:", error);
17
+ }
18
+ finally {
19
+ pending.delete(stop);
20
+ }
21
+ }));
22
+ let timer;
23
+ const timedOut = new Promise((resolve) => {
24
+ timer = setTimeout(resolve, timeoutMs);
25
+ timer.unref?.();
26
+ });
27
+ await Promise.race([stopped, timedOut]);
28
+ clearTimeout(timer);
29
+ if (pending.size > 0) {
30
+ const names = [...pending].map((stop) => stop.name || "anonymous");
31
+ console.warn(`[agent-native] background jobs still stopping after ${timeoutMs}ms; shutting down without them: ${names.join(", ")}`);
32
+ }
33
+ }
@@ -62,6 +62,7 @@ import { getConfiguredAppBasePath, stripAppBasePath } from "./app-base-path.js";
62
62
  import { readAnalyticsSessionId } from "./attribution.js";
63
63
  import { getSession } from "./auth.js";
64
64
  import { createAutomationFailureUnsubscribeHandler } from "./automation-failure-notifications.js";
65
+ import { registerBackgroundJobStop } from "./background-jobs.js";
65
66
  import { getBetterAuthInternalAdapter, getBetterAuthSync, } from "./better-auth-instance.js";
66
67
  import { resolveBuilderRequestAuthorization } from "./builder-api-auth.js";
67
68
  import { BUILDER_CONNECT_PARAM, BUILDER_CONNECT_MODE_PARAM, BUILDER_AGENT_NATIVE_PROVISION_MODE, BUILDER_CONNECT_ATTEMPT_PARAM, BUILDER_CONNECT_STATE_COOKIE, BUILDER_ENV_KEYS, BUILDER_OPENER_PARAM, BUILDER_RELAY_FLOW_HEADER, BUILDER_RELAY_SIGNATURE_HEADER, BUILDER_RELAY_STATE_PARAM, BUILDER_RELAY_TIMESTAMP_HEADER, appendBuilderConnectToken, appendBuilderConnectStateCookie, builderConnectTrackingProperties, BUILDER_UPSTREAM_FAILURE_STATUS, createBuilderConnectState, createBuilderBrowserCallbackErrorPage, createBuilderBrowserCallbackPage, createBuilderRelayRequest, getBuilderConnectCallbackOriginFromUrl, getBuilderConnectTrackingParams, getBuilderBrowserOriginForEvent, getBuilderBrowserStatusForEvent, isBuilderAccountAlreadyExistsError, isBuilderAccountProvisioningEnabled, isBuilderConnectCallbackUrlAllowed, isSignedBuilderConnectState, normalizeBuilderAgentContext, parseBuilderConnectStateCookie, provisionBuilderAccount, resolveBuilderBranchProjectId, resolveBuilderConnectCallbackUrl, resolveBuilderConnectCallbackState, resolveBuilderPreviewRelayParentOrigin, removeBuilderConnectStateCookie, runBuilderAgent, sendBuilderPopupErrorPage, verifyBuilderRelayRequest, verifyBuilderPreviewRelayStateForCallback, verifyBuilderConnectTokenAndGetOwner, signBuilderProvisioningToken, verifyBuilderProvisioningToken, withBuilderConnectTrackingParams, } from "./builder-browser.js";
@@ -2677,8 +2678,21 @@ export function createCoreRoutesPlugin(options = {}) {
2677
2678
  return;
2678
2679
  let lastSweep = 0;
2679
2680
  const SWEEP_INTERVAL_MS = 2 * 60 * 1000;
2680
- setTimeout(() => {
2681
- setInterval(() => {
2681
+ let startupTimer = null;
2682
+ let sweepTimer = null;
2683
+ const stopSandboxExecutionSweep = () => {
2684
+ if (startupTimer) {
2685
+ clearTimeout(startupTimer);
2686
+ startupTimer = null;
2687
+ }
2688
+ if (sweepTimer) {
2689
+ clearInterval(sweepTimer);
2690
+ sweepTimer = null;
2691
+ }
2692
+ };
2693
+ startupTimer = setTimeout(() => {
2694
+ startupTimer = null;
2695
+ sweepTimer = setInterval(() => {
2682
2696
  const now = Date.now();
2683
2697
  if (now - lastSweep < SWEEP_INTERVAL_MS)
2684
2698
  return;
@@ -2690,7 +2704,11 @@ export function createCoreRoutesPlugin(options = {}) {
2690
2704
  // best-effort — never break the server
2691
2705
  });
2692
2706
  }, 30_000); // Check every 30s but only sweep once per 2min
2707
+ sweepTimer.unref?.();
2693
2708
  }, 25_000); // Start 25s after init (after the agent sweeps)
2709
+ startupTimer.unref?.();
2710
+ registerBackgroundJobStop(stopSandboxExecutionSweep);
2711
+ nitroApp.hooks?.hook?.("close", stopSandboxExecutionSweep);
2694
2712
  })();
2695
2713
  getH3App(nitroApp).use(`${P}/debug/runtime`, defineEventHandler(async (event) => {
2696
2714
  setResponseHeader(event, "cache-control", "no-store");
@@ -1,4 +1,5 @@
1
1
  import { beginPgliteClientShutdown, closeDbExec, resumePgliteClientAccess, waitForPgliteClientOperations, } from "../db/client.js";
2
+ import { stopBackgroundJobs } from "./background-jobs.js";
2
3
  const devDatabaseCloseApps = new WeakSet();
3
4
  let devDatabaseClosePromise;
4
5
  const devDatabaseHot = import.meta.hot;
@@ -41,10 +42,19 @@ async function closeDevDatabase(requestId) {
41
42
  }
42
43
  }
43
44
  export function installDevDatabaseCloseHook(nitroApp) {
44
- if (process.env.NODE_ENV !== "development")
45
- return;
46
45
  if (!nitroApp?.hooks?.hook || devDatabaseCloseApps.has(nitroApp))
47
46
  return;
48
47
  devDatabaseCloseApps.add(nitroApp);
49
- nitroApp.hooks.hook("close", closeDevDatabase);
48
+ // Jobs must stop first: a tick against a closing client becomes an
49
+ // unhandled rejection and its next interval keeps the process alive.
50
+ // Keep this on the Nitro close hook (not inside closeDevDatabase): a
51
+ // Vite HMR restart closes/resumes the DB through that same function and
52
+ // nothing restarts jobs on resume.
53
+ nitroApp.hooks.hook("close", async () => {
54
+ await stopBackgroundJobs();
55
+ // Only the dev server owns its database. In production, detached work
56
+ // such as a sandbox run can still be saving its result while Nitro closes.
57
+ if (process.env.NODE_ENV === "development")
58
+ await closeDevDatabase();
59
+ });
50
60
  }
@@ -6,6 +6,7 @@ export interface IntervalJobOptions {
6
6
  leading?: boolean;
7
7
  }
8
8
  export interface IntervalJobHandle {
9
- stop(): void;
9
+ /** Stops scheduling and waits for any in-flight run to settle. */
10
+ stop(): Promise<void>;
10
11
  }
11
12
  export declare function startIntervalJob(runOnce: (signal: AbortSignal) => Promise<void>, options: IntervalJobOptions): IntervalJobHandle;
@@ -4,7 +4,7 @@ export function startIntervalJob(runOnce, options) {
4
4
  engine.start();
5
5
  return {
6
6
  stop() {
7
- engine.stop();
7
+ return engine.stop();
8
8
  },
9
9
  };
10
10
  }
@@ -9,7 +9,8 @@ export interface PollEngineOptions {
9
9
  }
10
10
  export interface PollEngineHandle {
11
11
  start(): void;
12
- stop(): void;
12
+ /** Stops scheduling and waits (bounded by timeoutMs) for any in-flight attempt. */
13
+ stop(): Promise<void>;
13
14
  pollNow(): void;
14
15
  reschedule(): void;
15
16
  readonly isRunning: boolean;
@@ -26,6 +26,7 @@ export function createPollEngine(attempt, options) {
26
26
  let activeController = null;
27
27
  let activeStopRequested = false;
28
28
  let inFlightReport = null;
29
+ let currentAttempt = null;
29
30
  function clearTimer() {
30
31
  if (timer != null) {
31
32
  clearTimeout(timer);
@@ -83,6 +84,7 @@ export function createPollEngine(attempt, options) {
83
84
  return;
84
85
  report(err);
85
86
  });
87
+ currentAttempt = settled;
86
88
  let timeoutError;
87
89
  try {
88
90
  await Promise.race([
@@ -113,6 +115,8 @@ export function createPollEngine(attempt, options) {
113
115
  activeController = null;
114
116
  if (inFlightReport === report)
115
117
  inFlightReport = null;
118
+ if (currentAttempt === settled)
119
+ currentAttempt = null;
116
120
  inFlight = false;
117
121
  if (gen === generation && running)
118
122
  schedule(gen);
@@ -130,12 +134,32 @@ export function createPollEngine(attempt, options) {
130
134
  else
131
135
  schedule(gen);
132
136
  },
133
- stop() {
137
+ async stop() {
134
138
  running = false;
135
139
  generation++;
136
140
  clearTimer();
137
141
  activeStopRequested = true;
138
142
  activeController?.abort();
143
+ const pending = currentAttempt;
144
+ if (!pending)
145
+ return;
146
+ // Abort alone is not enough: an attempt that ignores the signal can
147
+ // hang forever and block Nitro close via stopBackgroundJobs().
148
+ const waitMs = getTimeoutMs();
149
+ let timer;
150
+ try {
151
+ await Promise.race([
152
+ pending,
153
+ new Promise((resolve) => {
154
+ timer = setTimeout(resolve, waitMs);
155
+ maybeUnref(timer);
156
+ }),
157
+ ]);
158
+ }
159
+ finally {
160
+ if (timer != null)
161
+ clearTimeout(timer);
162
+ }
139
163
  },
140
164
  pollNow() {
141
165
  if (!running || inFlight)
@@ -0,0 +1 @@
1
+ export declare function isNonProgressEventType(type: string): boolean;
@@ -0,0 +1,13 @@
1
+ // Event types that never count as real run progress. The server's
2
+ // `last_progress_at` write (run-manager) and the browser's durable liveness
3
+ // cursor (sse-event-processor) both start from this list, so a reattached
4
+ // client cannot see progress the server never persisted. Both layers add their
5
+ // own stateful rule for action-preparation activity on top.
6
+ const NON_PROGRESS_EVENT_TYPES = new Set([
7
+ "stream_keepalive",
8
+ "model_stream",
9
+ "clear",
10
+ ]);
11
+ export function isNonProgressEventType(type) {
12
+ return NON_PROGRESS_EVENT_TYPES.has(type);
13
+ }