@agent-native/core 0.70.3 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +51 -0
- package/corpus/core/docs/content/actions.md +23 -0
- package/corpus/core/docs/content/audit-log.md +111 -0
- package/corpus/core/docs/content/multi-app-workspace.md +2 -2
- package/corpus/core/docs/design/durable-agent-runs.md +458 -4
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/action.ts +80 -1
- package/corpus/core/src/agent/durable-background.ts +192 -0
- package/corpus/core/src/agent/production-agent.ts +357 -13
- package/corpus/core/src/agent/run-manager.ts +66 -3
- package/corpus/core/src/agent/run-store.ts +143 -23
- package/corpus/core/src/agent/types.ts +20 -0
- package/corpus/core/src/application-state/store.ts +5 -0
- package/corpus/core/src/audit/actions/get-audit-event.ts +23 -0
- package/corpus/core/src/audit/actions/list-audit-events.ts +65 -0
- package/corpus/core/src/audit/cleanup-job.ts +100 -0
- package/corpus/core/src/audit/config.ts +91 -0
- package/corpus/core/src/audit/index.ts +43 -0
- package/corpus/core/src/audit/record.ts +143 -0
- package/corpus/core/src/audit/redact.ts +109 -0
- package/corpus/core/src/audit/store.ts +244 -0
- package/corpus/core/src/audit/types.ts +125 -0
- package/corpus/core/src/chat-threads/store.ts +10 -0
- package/corpus/core/src/client/AgentPanel.tsx +12 -9
- package/corpus/core/src/client/AssistantChat.tsx +6 -0
- package/corpus/core/src/client/blocks/library/FileTreeBlock.tsx +72 -14
- package/corpus/core/src/client/chat/repo-helpers.ts +38 -0
- package/corpus/core/src/db/client.ts +4 -0
- package/corpus/core/src/db/widen-columns.ts +75 -0
- package/corpus/core/src/deploy/build.ts +96 -0
- package/corpus/core/src/deploy/workspace-deploy.ts +121 -0
- package/corpus/core/src/mcp/build-server.ts +23 -13
- package/corpus/core/src/oauth-tokens/store.ts +6 -0
- package/corpus/core/src/provider-api/custom-registry.ts +8 -0
- package/corpus/core/src/resources/store.ts +10 -0
- package/corpus/core/src/scripts/runner.ts +6 -3
- package/corpus/core/src/server/action-discovery.ts +6 -0
- package/corpus/core/src/server/action-routes.ts +1 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +133 -66
- package/corpus/core/src/server/auth.ts +5 -0
- package/corpus/core/src/server/core-routes-plugin.ts +14 -0
- package/corpus/core/src/server/security-headers.ts +9 -6
- package/corpus/core/src/settings/store.ts +6 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
- package/corpus/core/src/usage/store.ts +6 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -0
- package/corpus/templates/analytics/AGENTS.md +8 -0
- package/corpus/templates/analytics/actions/compose-dashboard.ts +317 -0
- package/corpus/templates/analytics/changelog/2026-06-23-build-large-first-party-analytics-dashboards-in-one-fast-cal.md +6 -0
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +574 -0
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +60 -6
- package/corpus/templates/calendar/changelog/2026-06-23-added-a-heads-up-explaining-google-s-app-not-verified-screen.md +6 -0
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +20 -43
- package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +5 -1
- package/corpus/templates/clips/app/lib/countdown-audio-cue.ts +3 -51
- package/corpus/templates/clips/changelog/2026-06-23-dragging-the-desktop-camera-bubble-now-glides-to-a-stop-at-t.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-23-public-clips-now-play-inline-in-slack-connect-a-workspac.md +6 -0
- package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +72 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -10
- package/corpus/templates/clips/chrome-extension/src/background.ts +163 -32
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +301 -24
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +245 -67
- package/corpus/templates/clips/chrome-extension/src/overlay.css +77 -43
- package/corpus/templates/clips/chrome-extension/src/overlay.html +7 -1
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +102 -38
- package/corpus/templates/clips/chrome-extension/src/popup.html +62 -5
- package/corpus/templates/clips/chrome-extension/src/popup.ts +275 -4
- package/corpus/templates/clips/chrome-extension/src/styles.css +34 -0
- package/corpus/templates/clips/chrome-extension/vite.config.ts +5 -0
- package/corpus/templates/clips/desktop/src/lib/audio-cue.ts +21 -16
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +10 -17
- package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +73 -23
- package/corpus/templates/clips/desktop/src/overlays/countdown.tsx +0 -6
- package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +56 -46
- package/corpus/templates/clips/desktop/src/styles.css +33 -5
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +113 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +3 -0
- package/corpus/templates/clips/server/lib/media-permissions.ts +5 -1
- package/corpus/templates/clips/shared/recording-audio.ts +62 -0
- package/corpus/templates/clips/shared/recording-core.ts +94 -0
- package/dist/action.d.ts +31 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +45 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/context-xray/schema.d.ts +1 -1
- package/dist/agent/durable-background.d.ts +60 -0
- package/dist/agent/durable-background.d.ts.map +1 -0
- package/dist/agent/durable-background.js +144 -0
- package/dist/agent/durable-background.js.map +1 -0
- package/dist/agent/observational-memory/schema.d.ts +1 -1
- package/dist/agent/production-agent.d.ts +20 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +320 -14
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +48 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +45 -3
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +30 -1
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +138 -24
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +20 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +5 -0
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/actions/get-audit-event.d.ts +11 -0
- package/dist/audit/actions/get-audit-event.d.ts.map +1 -0
- package/dist/audit/actions/get-audit-event.js +22 -0
- package/dist/audit/actions/get-audit-event.js.map +1 -0
- package/dist/audit/actions/list-audit-events.d.ts +22 -0
- package/dist/audit/actions/list-audit-events.d.ts.map +1 -0
- package/dist/audit/actions/list-audit-events.js +61 -0
- package/dist/audit/actions/list-audit-events.js.map +1 -0
- package/dist/audit/cleanup-job.d.ts +12 -0
- package/dist/audit/cleanup-job.d.ts.map +1 -0
- package/dist/audit/cleanup-job.js +93 -0
- package/dist/audit/cleanup-job.js.map +1 -0
- package/dist/audit/config.d.ts +30 -0
- package/dist/audit/config.d.ts.map +1 -0
- package/dist/audit/config.js +65 -0
- package/dist/audit/config.js.map +1 -0
- package/dist/audit/index.d.ts +13 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +6 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/record.d.ts +24 -0
- package/dist/audit/record.d.ts.map +1 -0
- package/dist/audit/record.js +106 -0
- package/dist/audit/record.js.map +1 -0
- package/dist/audit/redact.d.ts +28 -0
- package/dist/audit/redact.d.ts.map +1 -0
- package/dist/audit/redact.js +109 -0
- package/dist/audit/redact.js.map +1 -0
- package/dist/audit/store.d.ts +14 -0
- package/dist/audit/store.d.ts.map +1 -0
- package/dist/audit/store.js +219 -0
- package/dist/audit/store.js.map +1 -0
- package/dist/audit/types.d.ts +114 -0
- package/dist/audit/types.d.ts.map +1 -0
- package/dist/audit/types.js +15 -0
- package/dist/audit/types.js.map +1 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +10 -0
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -2
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +6 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
- package/dist/client/blocks/library/FileTreeBlock.js +37 -4
- package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
- package/dist/client/chat/repo-helpers.d.ts +16 -0
- package/dist/client/chat/repo-helpers.d.ts.map +1 -1
- package/dist/client/chat/repo-helpers.js +40 -0
- package/dist/client/chat/repo-helpers.js.map +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +3 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/widen-columns.d.ts +39 -0
- package/dist/db/widen-columns.d.ts.map +1 -0
- package/dist/db/widen-columns.js +73 -0
- package/dist/db/widen-columns.js.map +1 -0
- package/dist/deploy/build.d.ts +29 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +85 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-deploy.d.ts.map +1 -1
- package/dist/deploy/workspace-deploy.js +108 -0
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +24 -9
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/oauth-tokens/store.d.ts.map +1 -1
- package/dist/oauth-tokens/store.js +6 -0
- package/dist/oauth-tokens/store.js.map +1 -1
- package/dist/provider-api/custom-registry.d.ts.map +1 -1
- package/dist/provider-api/custom-registry.js +8 -0
- package/dist/provider-api/custom-registry.js.map +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +9 -0
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/runner.js +4 -3
- package/dist/scripts/runner.js.map +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +6 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +1 -0
- 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 +68 -13
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +5 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +13 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/security-headers.d.ts +8 -5
- package/dist/server/security-headers.d.ts.map +1 -1
- package/dist/server/security-headers.js +9 -6
- package/dist/server/security-headers.js.map +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +6 -0
- package/dist/settings/store.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +5 -0
- package/dist/usage/store.js.map +1 -1
- package/docs/content/actions.md +23 -0
- package/docs/content/audit-log.md +111 -0
- package/docs/content/multi-app-workspace.md +2 -2
- package/docs/design/durable-agent-runs.md +458 -4
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/audit-log/SKILL.md +93 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the framework audit log.
|
|
3
|
+
*
|
|
4
|
+
* The audit log is a durable, complete, access-scoped, append-only record of
|
|
5
|
+
* *who mutated what app data, when, from where, and — when it was the agent —
|
|
6
|
+
* in which run*. It is deliberately distinct from observability (sampled
|
|
7
|
+
* agent-run telemetry) and tracking (fire-and-forget product analytics): audit
|
|
8
|
+
* rows are complete, locally queryable, and scoped to the data they describe.
|
|
9
|
+
*
|
|
10
|
+
* Capture happens automatically at the action-execution seam (`defineAction`):
|
|
11
|
+
* every mutating action records an audit event after it runs. Read-only (GET /
|
|
12
|
+
* `readOnly`) actions are skipped unless they opt in via `audit.onRead`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** Outcome of an audited action call. */
|
|
16
|
+
export type AuditStatus = "success" | "error" | "denied";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Who performed the mutation.
|
|
20
|
+
* - `agent` — the in-app assistant / sub-agents / A2A (caller `"tool"`).
|
|
21
|
+
* - `human` — a person via the UI, HTTP, CLI, or an external MCP client.
|
|
22
|
+
* - `system` — no resolved identity (background jobs, schedules).
|
|
23
|
+
*/
|
|
24
|
+
export type AuditActorKind = "agent" | "human" | "system";
|
|
25
|
+
|
|
26
|
+
export type AuditVisibility = "private" | "org" | "public";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The resource an action mutated, plus the ownership used to scope who can
|
|
30
|
+
* read the resulting audit event. Returned by an action's `audit.target`.
|
|
31
|
+
*
|
|
32
|
+
* `ownerEmail` / `orgId` / `visibility` default to the actor's identity when
|
|
33
|
+
* omitted, which is correct for the common case where a user mutates their own
|
|
34
|
+
* data. Provide them explicitly when a user edits a resource owned by someone
|
|
35
|
+
* else so the *owner* sees the change in their audit trail.
|
|
36
|
+
*/
|
|
37
|
+
export interface AuditTarget {
|
|
38
|
+
type?: string;
|
|
39
|
+
id?: string;
|
|
40
|
+
ownerEmail?: string | null;
|
|
41
|
+
orgId?: string | null;
|
|
42
|
+
visibility?: AuditVisibility;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Metadata about the call, passed to `audit.target` / `audit.summary`. */
|
|
46
|
+
export interface AuditCallMeta {
|
|
47
|
+
status: AuditStatus;
|
|
48
|
+
caller: string;
|
|
49
|
+
userEmail?: string;
|
|
50
|
+
orgId?: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Per-action audit configuration, set on `defineAction({ audit })`.
|
|
55
|
+
*
|
|
56
|
+
* Audit is **default-on for mutating actions** — you only need this object to
|
|
57
|
+
* *tune* capture (declare the target, customize the summary) or to opt a
|
|
58
|
+
* read-only action in / a noisy action out.
|
|
59
|
+
*/
|
|
60
|
+
export interface ActionAuditConfig {
|
|
61
|
+
/**
|
|
62
|
+
* Force audit on (`true`) or off (`false`). Overrides every default,
|
|
63
|
+
* including the read-only skip and the high-frequency denylist. When
|
|
64
|
+
* omitted, mutating actions are audited and read-only actions are not.
|
|
65
|
+
*/
|
|
66
|
+
enabled?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Audit this action even though it is read-only (GET). Use for sensitive
|
|
69
|
+
* reads worth recording — secret access, bulk export. Ignored when
|
|
70
|
+
* `enabled` is set explicitly.
|
|
71
|
+
*/
|
|
72
|
+
onRead?: boolean;
|
|
73
|
+
/** Capture the (redacted) call arguments. Default `true`. */
|
|
74
|
+
recordInputs?: boolean;
|
|
75
|
+
/** Resolve the mutated resource + its ownership for scoped reads. */
|
|
76
|
+
target?: (
|
|
77
|
+
args: any,
|
|
78
|
+
result: unknown,
|
|
79
|
+
meta: AuditCallMeta,
|
|
80
|
+
) => AuditTarget | null | undefined;
|
|
81
|
+
/** Build a short human-readable summary line for the event. */
|
|
82
|
+
summary?: (args: any, result: unknown, meta: AuditCallMeta) => string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A persisted audit event. */
|
|
86
|
+
export interface AuditEvent {
|
|
87
|
+
id: string;
|
|
88
|
+
createdAt: number;
|
|
89
|
+
/** Action name (the registry key, e.g. `delete-recording`). */
|
|
90
|
+
action: string;
|
|
91
|
+
/** Invocation surface: tool | frontend | http | cli | mcp | a2a. */
|
|
92
|
+
caller: string;
|
|
93
|
+
actorKind: AuditActorKind;
|
|
94
|
+
/** Human on whose behalf it ran — populated even for agent calls. */
|
|
95
|
+
actorEmail: string | null;
|
|
96
|
+
orgId: string | null;
|
|
97
|
+
/** Agent conversation thread, when caller is the agent loop. */
|
|
98
|
+
threadId: string | null;
|
|
99
|
+
/** Agent turn that produced the mutation — the unit of one agent response. */
|
|
100
|
+
turnId: string | null;
|
|
101
|
+
targetType: string | null;
|
|
102
|
+
targetId: string | null;
|
|
103
|
+
status: AuditStatus;
|
|
104
|
+
summary: string | null;
|
|
105
|
+
/** Redacted JSON of the call arguments, or null. */
|
|
106
|
+
input: string | null;
|
|
107
|
+
errorCode: string | null;
|
|
108
|
+
/** Denormalized owner used to scope reads (defaults to the actor). */
|
|
109
|
+
ownerEmail: string | null;
|
|
110
|
+
visibility: AuditVisibility;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Filters for `queryAuditEvents`. */
|
|
114
|
+
export interface AuditQueryFilters {
|
|
115
|
+
targetType?: string;
|
|
116
|
+
targetId?: string;
|
|
117
|
+
actorKind?: AuditActorKind;
|
|
118
|
+
actorEmail?: string;
|
|
119
|
+
status?: AuditStatus;
|
|
120
|
+
threadId?: string;
|
|
121
|
+
turnId?: string;
|
|
122
|
+
action?: string;
|
|
123
|
+
sinceMs?: number;
|
|
124
|
+
limit?: number;
|
|
125
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { getDbExec, intType } from "../db/client.js";
|
|
3
|
+
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
3
4
|
import {
|
|
4
5
|
mergeThreadDataForClientSave,
|
|
5
6
|
normalizeThreadRepository,
|
|
@@ -87,6 +88,15 @@ async function ensureTable(): Promise<void> {
|
|
|
87
88
|
// Column already exists.
|
|
88
89
|
}
|
|
89
90
|
}
|
|
91
|
+
// Widen millisecond-timestamp columns that older deployments created as
|
|
92
|
+
// 32-bit `INTEGER`; on Postgres the `Date.now()` written on every turn
|
|
93
|
+
// overflows int4. No-op once widened / on fresh BIGINT databases.
|
|
94
|
+
await widenIntColumnsToBigInt("chat_threads", [
|
|
95
|
+
"created_at",
|
|
96
|
+
"updated_at",
|
|
97
|
+
"pinned_at",
|
|
98
|
+
"archived_at",
|
|
99
|
+
]);
|
|
90
100
|
// Indexes for the hot read paths. Both the sidebar list and the
|
|
91
101
|
// scoped/per-resource list filter on owner_email (and optionally
|
|
92
102
|
// scope) and sort by updated_at. Keep these dialect-agnostic (no
|
|
@@ -1072,6 +1072,18 @@ function AgentPanelInner({
|
|
|
1072
1072
|
</button>
|
|
1073
1073
|
</DropdownMenuTrigger>
|
|
1074
1074
|
<DropdownMenuContent align="end" sideOffset={6} className="w-48">
|
|
1075
|
+
{onCollapse && (
|
|
1076
|
+
<>
|
|
1077
|
+
<DropdownMenuItem onSelect={onCollapse}>
|
|
1078
|
+
<IconLayoutSidebarRightCollapse
|
|
1079
|
+
size={14}
|
|
1080
|
+
className="shrink-0"
|
|
1081
|
+
/>
|
|
1082
|
+
Collapse sidebar
|
|
1083
|
+
</DropdownMenuItem>
|
|
1084
|
+
<DropdownMenuSeparator />
|
|
1085
|
+
</>
|
|
1086
|
+
)}
|
|
1075
1087
|
{mode === "chat" && toggleHistory && (
|
|
1076
1088
|
<DropdownMenuItem onSelect={toggleHistory}>
|
|
1077
1089
|
<IconHistory size={14} className="shrink-0" />
|
|
@@ -1133,15 +1145,6 @@ function AgentPanelInner({
|
|
|
1133
1145
|
{isFullscreen ? "Exit fullscreen" : "Fullscreen"}
|
|
1134
1146
|
</DropdownMenuItem>
|
|
1135
1147
|
)}
|
|
1136
|
-
{onCollapse && (
|
|
1137
|
-
<DropdownMenuItem onSelect={onCollapse}>
|
|
1138
|
-
<IconLayoutSidebarRightCollapse
|
|
1139
|
-
size={14}
|
|
1140
|
-
className="shrink-0"
|
|
1141
|
-
/>
|
|
1142
|
-
Collapse sidebar
|
|
1143
|
-
</DropdownMenuItem>
|
|
1144
|
-
)}
|
|
1145
1148
|
{((mode === "chat" && activeTabId) ||
|
|
1146
1149
|
(mode === "cli" && canUseCodeTools && activeCliTab)) && (
|
|
1147
1150
|
<>
|
|
@@ -151,6 +151,7 @@ import {
|
|
|
151
151
|
getRepoMessages,
|
|
152
152
|
getRepoMessage,
|
|
153
153
|
shouldImportServerThreadData,
|
|
154
|
+
dedupeRepoMessagesById,
|
|
154
155
|
} from "./chat/repo-helpers.js";
|
|
155
156
|
|
|
156
157
|
export {
|
|
@@ -829,6 +830,11 @@ export function clearChatStorage(tabId?: string) {
|
|
|
829
830
|
* messages missing these fields crash.
|
|
830
831
|
*/
|
|
831
832
|
function ensureMessageMetadata(repo: any): any {
|
|
833
|
+
// Drop duplicate message ids before import — assistant-ui's MessageRepository
|
|
834
|
+
// throws "performOp/link: A message with the same id already exists in the
|
|
835
|
+
// parent tree" (Sentry AGENT-NATIVE-BROWSER-2Q) when fed repeated ids. No-op
|
|
836
|
+
// for the normal no-duplicate case. See dedupeRepoMessagesById.
|
|
837
|
+
repo = dedupeRepoMessagesById(repo);
|
|
832
838
|
if (!repo?.messages || !Array.isArray(repo.messages)) return repo;
|
|
833
839
|
for (const entry of repo.messages) {
|
|
834
840
|
// Handle both wrapped ({ message: { ... } }) and flat ({ role, ... }) formats
|
|
@@ -119,6 +119,10 @@ interface FolderNode {
|
|
|
119
119
|
/** Full slash path of the folder, used as a stable key. */
|
|
120
120
|
path: string;
|
|
121
121
|
children: TreeNode[];
|
|
122
|
+
/** Present when the folder was authored as an explicit directory entry (a
|
|
123
|
+
* `path` ending in `/`), carrying its note/metadata + flat `entries` index. */
|
|
124
|
+
entry?: FileTreeEntry;
|
|
125
|
+
index?: number;
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
type TreeNode = FolderNode | FileLeaf;
|
|
@@ -136,6 +140,10 @@ interface FolderBuild {
|
|
|
136
140
|
files: FileLeaf[];
|
|
137
141
|
/** Insertion order of child names (folders + files) for stable rendering. */
|
|
138
142
|
order: string[];
|
|
143
|
+
/** Set when an explicit directory entry (trailing slash) targets this folder,
|
|
144
|
+
* carrying its note/metadata + flat `entries` index. */
|
|
145
|
+
entry?: FileTreeEntry;
|
|
146
|
+
index?: number;
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
function makeFolder(name: string, path: string): FolderBuild {
|
|
@@ -154,8 +162,15 @@ function buildTree(entries: FileTreeEntry[]): TreeNode[] {
|
|
|
154
162
|
entries.forEach((entry, index) => {
|
|
155
163
|
const segments = entry.path.split("/").filter(Boolean);
|
|
156
164
|
if (segments.length === 0) return;
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
|
|
166
|
+
// A trailing slash marks a DIRECTORY entry: every segment is a folder and
|
|
167
|
+
// the entry's note/metadata attaches to the deepest folder (the last segment
|
|
168
|
+
// does NOT become a file leaf). Because folders are keyed by name, this also
|
|
169
|
+
// merges with any folder the sibling file paths already implied — so
|
|
170
|
+
// `packages/shared/` and `packages/shared/src/…` collapse onto one `shared`
|
|
171
|
+
// folder instead of a duplicate folder + file pair.
|
|
172
|
+
const isDir = /\/\s*$/.test(entry.path);
|
|
173
|
+
const folderSegments = isDir ? segments : segments.slice(0, -1);
|
|
159
174
|
|
|
160
175
|
let cursor = root;
|
|
161
176
|
let prefix = "";
|
|
@@ -170,6 +185,17 @@ function buildTree(entries: FileTreeEntry[]): TreeNode[] {
|
|
|
170
185
|
cursor = next;
|
|
171
186
|
}
|
|
172
187
|
|
|
188
|
+
if (isDir) {
|
|
189
|
+
// Attach metadata to the deepest folder (first declaration wins, so an
|
|
190
|
+
// explicit note isn't clobbered by a later bare prefix re-declaration).
|
|
191
|
+
if (!cursor.entry) {
|
|
192
|
+
cursor.entry = entry;
|
|
193
|
+
cursor.index = index;
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const fileName = segments[segments.length - 1] as string;
|
|
173
199
|
cursor.files.push({
|
|
174
200
|
kind: "file",
|
|
175
201
|
name: fileName,
|
|
@@ -191,6 +217,8 @@ function buildTree(entries: FileTreeEntry[]): TreeNode[] {
|
|
|
191
217
|
name: child.name,
|
|
192
218
|
path: child.path,
|
|
193
219
|
children: materialize(child),
|
|
220
|
+
entry: child.entry,
|
|
221
|
+
index: child.index,
|
|
194
222
|
});
|
|
195
223
|
} else {
|
|
196
224
|
const file = folder.files[Number(key.slice(2))];
|
|
@@ -212,7 +240,16 @@ function compactFolderNode(folder: FolderNode): FolderNode {
|
|
|
212
240
|
let path = folder.path;
|
|
213
241
|
let children = folder.children;
|
|
214
242
|
|
|
215
|
-
|
|
243
|
+
// Collapse single-child folder chains (a/b/c) into one row — but never across
|
|
244
|
+
// a folder that carries its own directory note, so an explicitly authored
|
|
245
|
+
// directory (e.g. `packages/shared/` with a note) keeps its own row instead of
|
|
246
|
+
// being folded into its parent or child and losing the note.
|
|
247
|
+
while (
|
|
248
|
+
!folder.entry &&
|
|
249
|
+
children.length === 1 &&
|
|
250
|
+
children[0]?.kind === "folder" &&
|
|
251
|
+
!children[0].entry
|
|
252
|
+
) {
|
|
216
253
|
const child = children[0];
|
|
217
254
|
names.push(child.name);
|
|
218
255
|
path = child.path;
|
|
@@ -223,6 +260,8 @@ function compactFolderNode(folder: FolderNode): FolderNode {
|
|
|
223
260
|
kind: "folder",
|
|
224
261
|
name: names.join("/"),
|
|
225
262
|
path,
|
|
263
|
+
entry: folder.entry,
|
|
264
|
+
index: folder.index,
|
|
226
265
|
children: compactTree(children),
|
|
227
266
|
};
|
|
228
267
|
}
|
|
@@ -353,30 +392,49 @@ export function FileTreeRead({
|
|
|
353
392
|
const indent = depth * INDENT_STEP;
|
|
354
393
|
if (node.kind === "folder") {
|
|
355
394
|
const collapsed = collapsedFolders[node.path] ?? false;
|
|
395
|
+
// An explicit directory entry (trailing-slash path) can be a leaf folder
|
|
396
|
+
// with no children — e.g. `apps/mail/`. It still renders as a folder, just
|
|
397
|
+
// without a toggle chevron. Its authored note shows inline like a file's.
|
|
398
|
+
const expandable = node.children.length > 0;
|
|
399
|
+
const note = node.entry?.note?.trim();
|
|
400
|
+
const open = expandable && !collapsed;
|
|
356
401
|
return (
|
|
357
402
|
<div key={`d:${node.path}`}>
|
|
358
403
|
<button
|
|
359
404
|
type="button"
|
|
360
405
|
data-plan-interactive
|
|
361
|
-
|
|
362
|
-
|
|
406
|
+
disabled={!expandable}
|
|
407
|
+
aria-expanded={expandable ? !collapsed : undefined}
|
|
408
|
+
onClick={expandable ? () => toggleFolder(node.path) : undefined}
|
|
363
409
|
style={{ paddingLeft: indent + 8 }}
|
|
364
|
-
className=
|
|
410
|
+
className={cn(
|
|
411
|
+
"flex w-full items-center gap-1.5 rounded-md py-1 pr-2 text-left text-[13px] transition-colors",
|
|
412
|
+
expandable ? "hover:bg-accent/40" : "cursor-default",
|
|
413
|
+
)}
|
|
365
414
|
>
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
<IconFolder className="size-4 shrink-0 text-plan-muted" />
|
|
415
|
+
{expandable ? (
|
|
416
|
+
<IconChevronRight
|
|
417
|
+
className={cn(
|
|
418
|
+
"size-3.5 shrink-0 text-plan-muted transition-transform",
|
|
419
|
+
!collapsed && "rotate-90",
|
|
420
|
+
)}
|
|
421
|
+
/>
|
|
374
422
|
) : (
|
|
423
|
+
<span className="size-3.5 shrink-0" aria-hidden />
|
|
424
|
+
)}
|
|
425
|
+
{open ? (
|
|
375
426
|
<IconFolderOpen className="size-4 shrink-0 text-plan-muted" />
|
|
427
|
+
) : (
|
|
428
|
+
<IconFolder className="size-4 shrink-0 text-plan-muted" />
|
|
376
429
|
)}
|
|
377
430
|
<span className="min-w-0 truncate font-medium text-plan-text">
|
|
378
431
|
{node.name}
|
|
379
432
|
</span>
|
|
433
|
+
{note && (
|
|
434
|
+
<span className="ml-1 min-w-0 flex-1 truncate text-xs text-plan-muted">
|
|
435
|
+
{note}
|
|
436
|
+
</span>
|
|
437
|
+
)}
|
|
380
438
|
</button>
|
|
381
439
|
</div>
|
|
382
440
|
);
|
|
@@ -56,6 +56,44 @@ export function getRepoMessage(entry: RepoEntry): RepoMessage | null {
|
|
|
56
56
|
return (entry?.message ?? entry) as RepoMessage | null;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Collapse duplicate message ids before a repository is handed to
|
|
61
|
+
* `threadRuntime.import()`. assistant-ui's `MessageRepository` throws
|
|
62
|
+
* "MessageRepository(performOp/link): A message with the same id already exists
|
|
63
|
+
* in the parent tree" when the imported messages contain the same id more than
|
|
64
|
+
* once (Sentry AGENT-NATIVE-BROWSER-2Q). Duplicate ids are never valid thread
|
|
65
|
+
* data — they come from optimistic+echo races, streaming reconnect replays, or
|
|
66
|
+
* multi-tab merges — so keep only the LAST occurrence of each id (the most
|
|
67
|
+
* recent, most complete copy). parentId references stay valid because the
|
|
68
|
+
* surviving entry keeps the same id.
|
|
69
|
+
*
|
|
70
|
+
* Returns the input unchanged (same reference) when there are no duplicates, so
|
|
71
|
+
* the overwhelmingly common no-dupe case is a cheap no-op with zero behavioural
|
|
72
|
+
* change for normal threads.
|
|
73
|
+
*/
|
|
74
|
+
export function dedupeRepoMessagesById<T extends NormalizedRepo>(
|
|
75
|
+
repo: T | null | undefined,
|
|
76
|
+
): T | null | undefined {
|
|
77
|
+
if (!repo || !Array.isArray(repo.messages)) return repo;
|
|
78
|
+
const entries = repo.messages;
|
|
79
|
+
const lastIndexById = new Map<string, number>();
|
|
80
|
+
let hasDuplicate = false;
|
|
81
|
+
entries.forEach((entry, index) => {
|
|
82
|
+
const id = getRepoMessage(entry)?.id;
|
|
83
|
+
if (typeof id !== "string" || !id) return;
|
|
84
|
+
if (lastIndexById.has(id)) hasDuplicate = true;
|
|
85
|
+
lastIndexById.set(id, index);
|
|
86
|
+
});
|
|
87
|
+
if (!hasDuplicate) return repo;
|
|
88
|
+
const deduped = entries.filter((entry, index) => {
|
|
89
|
+
const id = getRepoMessage(entry)?.id;
|
|
90
|
+
// Keep id-less entries untouched; for duplicated ids keep only the last.
|
|
91
|
+
if (typeof id !== "string" || !id) return true;
|
|
92
|
+
return lastIndexById.get(id) === index;
|
|
93
|
+
});
|
|
94
|
+
return { ...repo, messages: deduped };
|
|
95
|
+
}
|
|
96
|
+
|
|
59
97
|
export function isAssistantMessageTerminal(
|
|
60
98
|
message: RepoMessage | null,
|
|
61
99
|
): boolean {
|
|
@@ -318,6 +318,10 @@ export function intType(): string {
|
|
|
318
318
|
return isPostgres() ? "BIGINT" : "INTEGER";
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
+
// `widenIntColumnsToBigInt` lives in `./widen-columns.js` (it depends only on
|
|
322
|
+
// `isPostgres`/`getDbExec` from here) so stores can import it without every
|
|
323
|
+
// `vi.mock("./client.js")` test having to stub the export.
|
|
324
|
+
|
|
321
325
|
// ---------------------------------------------------------------------------
|
|
322
326
|
// Parameter conversion: ? -> $1, $2, $3
|
|
323
327
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-place widening of legacy 32-bit `integer` columns to 64-bit `BIGINT` on
|
|
3
|
+
* Postgres.
|
|
4
|
+
*
|
|
5
|
+
* Lives in its own module (rather than `client.js`) so that stores can import
|
|
6
|
+
* it without every `vi.mock("../db/client.js")` test needing to stub it: the
|
|
7
|
+
* helper resolves `isPostgres()` / `getDbExec()` through `client.js`, so a test
|
|
8
|
+
* that mocks the client to SQLite (`isPostgres: () => false`) makes this a
|
|
9
|
+
* no-op automatically.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { isPostgres, getDbExec, type DbExec } from "./client.js";
|
|
13
|
+
|
|
14
|
+
const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Widen pre-existing 32-bit `integer` columns to 64-bit `BIGINT` in place on
|
|
18
|
+
* Postgres. No-op on SQLite, whose `INTEGER` is already 64-bit.
|
|
19
|
+
*
|
|
20
|
+
* Several stores historically created millisecond-timestamp columns (e.g.
|
|
21
|
+
* `agent_runs.started_at`, `application_state.updated_at`) as a literal
|
|
22
|
+
* `INTEGER`. On Postgres that is int4 (max 2,147,483,647), so a millisecond
|
|
23
|
+
* epoch such as 1782269273204 overflows with:
|
|
24
|
+
* `value "1782269273204" is out of range for type integer`
|
|
25
|
+
* The CREATE TABLE source was later switched to `intType()` (BIGINT on PG),
|
|
26
|
+
* but `CREATE TABLE IF NOT EXISTS` cannot re-type a column that already
|
|
27
|
+
* exists, so long-lived Neon databases keep the int4 column and every write
|
|
28
|
+
* into it fails. (Migrations don't hit this — the migration runner rewrites
|
|
29
|
+
* `INTEGER` → `BIGINT` for Postgres; only raw `ensureTable()` CREATE strings
|
|
30
|
+
* that predate `intType()` are affected.)
|
|
31
|
+
*
|
|
32
|
+
* This widens such columns once, then no-ops: it only ALTERs columns whose
|
|
33
|
+
* current type is `integer`, so already-bigint tables are never rewritten.
|
|
34
|
+
* Widening int4 → int8 is additive and non-destructive (no data loss, no
|
|
35
|
+
* narrowing), so it is safe to run unconditionally on every boot.
|
|
36
|
+
*
|
|
37
|
+
* Call from a store's `ensureTable()` right after its `CREATE TABLE IF NOT
|
|
38
|
+
* EXISTS`, passing the millisecond-timestamp columns for that table. Pass the
|
|
39
|
+
* UNQUALIFIED table name (the lookup is scoped to the `public` schema); the
|
|
40
|
+
* `ALTER` resolves the table through the search path.
|
|
41
|
+
*/
|
|
42
|
+
export async function widenIntColumnsToBigInt(
|
|
43
|
+
table: string,
|
|
44
|
+
columns: string[],
|
|
45
|
+
// Injectable for tests; production callers use the configured client.
|
|
46
|
+
injectedClient?: DbExec,
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
if (!isPostgres() || columns.length === 0) return;
|
|
49
|
+
if (!PLAIN_IDENTIFIER.test(table)) return;
|
|
50
|
+
const client = injectedClient ?? getDbExec();
|
|
51
|
+
let int4Columns: Set<string>;
|
|
52
|
+
try {
|
|
53
|
+
const { rows } = await client.execute({
|
|
54
|
+
sql: `SELECT column_name FROM information_schema.columns
|
|
55
|
+
WHERE table_schema = 'public' AND table_name = ? AND data_type = 'integer'`,
|
|
56
|
+
args: [table],
|
|
57
|
+
});
|
|
58
|
+
int4Columns = new Set(rows.map((r) => String(r.column_name)));
|
|
59
|
+
} catch {
|
|
60
|
+
// information_schema unreadable (permissions / non-standard backend) —
|
|
61
|
+
// skip silently and leave the pre-existing behaviour unchanged.
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
for (const col of columns) {
|
|
65
|
+
if (!int4Columns.has(col) || !PLAIN_IDENTIFIER.test(col)) continue;
|
|
66
|
+
try {
|
|
67
|
+
await client.execute(
|
|
68
|
+
`ALTER TABLE ${table} ALTER COLUMN ${col} TYPE BIGINT`,
|
|
69
|
+
);
|
|
70
|
+
} catch {
|
|
71
|
+
// A concurrent boot already widened it, or the role lacks ALTER — both
|
|
72
|
+
// are safe to ignore; a later boot retries if still needed.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "./workspace-core.js";
|
|
35
35
|
import { generateActionRegistryForProject } from "../vite/action-types-plugin.js";
|
|
36
36
|
import { mcpEmbedStaticAssetRouteRules } from "../shared/mcp-embed-headers.js";
|
|
37
|
+
import { AGENT_CHAT_PROCESS_RUN_PATH } from "../agent/durable-background.js";
|
|
37
38
|
import {
|
|
38
39
|
AGENT_NATIVE_SOCIAL_IMAGE_ALT,
|
|
39
40
|
AGENT_NATIVE_SOCIAL_IMAGE_CACHE_BUSTER,
|
|
@@ -1476,6 +1477,85 @@ export function findInstalledResvgPackages(
|
|
|
1476
1477
|
.map(([packageName, packageDir]) => ({ packageName, packageDir }));
|
|
1477
1478
|
}
|
|
1478
1479
|
|
|
1480
|
+
/**
|
|
1481
|
+
* Deploy-time gate for emitting the second `-background` Netlify function.
|
|
1482
|
+
* Reads the same env flag the runtime gate uses
|
|
1483
|
+
* (`AGENT_CHAT_DURABLE_BACKGROUND`). Off by default — when off, the deploy
|
|
1484
|
+
* emits exactly one function (today's behavior, byte-for-byte).
|
|
1485
|
+
*/
|
|
1486
|
+
export function isDurableBackgroundDeployEnabled(): boolean {
|
|
1487
|
+
const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
|
|
1488
|
+
if (raw == null) return false;
|
|
1489
|
+
const v = raw.trim().toLowerCase();
|
|
1490
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Single-template Netlify build: emit a SECOND function whose name ends in
|
|
1495
|
+
* `-background`, re-exporting the same `main.mjs` handler bundle, so the chat
|
|
1496
|
+
* `_process-run` POST lands on Netlify's async (15-min) function instead of the
|
|
1497
|
+
* synchronous one. Additive + flag-gated (see `isDurableBackgroundDeployEnabled`).
|
|
1498
|
+
*
|
|
1499
|
+
* Nitro's `netlify` preset emits a single function at
|
|
1500
|
+
* `.netlify/functions-internal/server` (`server.mjs` → `main.mjs`). We copy
|
|
1501
|
+
* that directory to a sibling `<...>-background` function and write an entry
|
|
1502
|
+
* with a `config.path` of the process-run route.
|
|
1503
|
+
*
|
|
1504
|
+
* ⚠️ REAL-DEPLOY VERIFICATION REQUIRED. Whether Netlify routes
|
|
1505
|
+
* `/_agent-native/agent-chat/_process-run` to this `-background` function (and
|
|
1506
|
+
* invokes it asynchronously with the 15-min budget) vs the synchronous Nitro
|
|
1507
|
+
* function cannot be verified in this environment — Nitro owns the primary
|
|
1508
|
+
* function's routing manifest, and the precedence between the two functions for
|
|
1509
|
+
* that path is a Netlify runtime behavior. If routing resolves to the
|
|
1510
|
+
* synchronous function, the run still completes via the existing 40s
|
|
1511
|
+
* soft-timeout path (no durable win, no regression). See
|
|
1512
|
+
* docs/design/durable-agent-runs.md (Open risks #1).
|
|
1513
|
+
*/
|
|
1514
|
+
export function emitSingleTemplateNetlifyBackgroundFunction(
|
|
1515
|
+
projectCwd: string,
|
|
1516
|
+
): void {
|
|
1517
|
+
const functionsDir = path.join(projectCwd, ".netlify", "functions-internal");
|
|
1518
|
+
const serverDir = path.join(functionsDir, "server");
|
|
1519
|
+
if (!fs.existsSync(path.join(serverDir, "main.mjs"))) {
|
|
1520
|
+
// Nitro output layout differs from what we expected — skip rather than
|
|
1521
|
+
// guess. The single-function deploy is unaffected.
|
|
1522
|
+
console.warn(
|
|
1523
|
+
"[build] Durable-background emit skipped: expected Nitro Netlify function " +
|
|
1524
|
+
"at .netlify/functions-internal/server/main.mjs was not found.",
|
|
1525
|
+
);
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
const backgroundName = "server-agent-background";
|
|
1529
|
+
const dest = path.join(functionsDir, backgroundName);
|
|
1530
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
1531
|
+
copyDir(serverDir, dest);
|
|
1532
|
+
// Drop the original Nitro entry so our background entry is the entrypoint.
|
|
1533
|
+
fs.rmSync(path.join(dest, "server.mjs"), { force: true });
|
|
1534
|
+
|
|
1535
|
+
const entry = `let cachedHandler;
|
|
1536
|
+
|
|
1537
|
+
export default async function handler(...args) {
|
|
1538
|
+
cachedHandler ??= (await import("./main.mjs")).default;
|
|
1539
|
+
return cachedHandler(...args);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
export const config = {
|
|
1543
|
+
name: "agent background handler",
|
|
1544
|
+
generator: "agent-native build",
|
|
1545
|
+
path: ${JSON.stringify([AGENT_CHAT_PROCESS_RUN_PATH])},
|
|
1546
|
+
nodeBundler: "none",
|
|
1547
|
+
includedFiles: ["**"],
|
|
1548
|
+
preferStatic: false,
|
|
1549
|
+
};
|
|
1550
|
+
`;
|
|
1551
|
+
fs.writeFileSync(path.join(dest, `${backgroundName}.mjs`), entry);
|
|
1552
|
+
console.log(
|
|
1553
|
+
`[build] Emitted durable-background function "${backgroundName}" ` +
|
|
1554
|
+
`(path ${AGENT_CHAT_PROCESS_RUN_PATH}). REQUIRES real-deploy verification ` +
|
|
1555
|
+
`of Netlify async routing — see docs/design/durable-agent-runs.md.`,
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1479
1559
|
function copyInstalledLibsqlNativePackages(serverDir: string | undefined) {
|
|
1480
1560
|
if (!serverDir || !fs.existsSync(serverDir)) return;
|
|
1481
1561
|
const nodeModulesRoots = nodeModulesAncestors(cwd);
|
|
@@ -1938,6 +2018,22 @@ export default bundle;
|
|
|
1938
2018
|
copyInstalledFfmpegStaticPackage(nitro.options.output.serverDir);
|
|
1939
2019
|
}
|
|
1940
2020
|
|
|
2021
|
+
// Durable background agent runs (off by default). Additive ONLY: emits a
|
|
2022
|
+
// SECOND Netlify function whose name ends in `-background` re-exporting the
|
|
2023
|
+
// same handler bundle, so the chat `_process-run` POST lands on Netlify's
|
|
2024
|
+
// async (15-min) function. When the flag is off this is a no-op and the
|
|
2025
|
+
// single-function deploy is byte-for-byte unchanged.
|
|
2026
|
+
if (preset === "netlify" && isDurableBackgroundDeployEnabled()) {
|
|
2027
|
+
try {
|
|
2028
|
+
emitSingleTemplateNetlifyBackgroundFunction(cwd);
|
|
2029
|
+
} catch (err) {
|
|
2030
|
+
console.warn(
|
|
2031
|
+
"[build] Failed to emit durable-background Netlify function (non-fatal):",
|
|
2032
|
+
err instanceof Error ? err.message : err,
|
|
2033
|
+
);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
|
|
1941
2037
|
// Resolve remaining bare npm imports by bundling them into _libs/.
|
|
1942
2038
|
// Nitro sometimes leaves small packages as externals even with noExternals.
|
|
1943
2039
|
if (preset.startsWith("cloudflare") || preset.startsWith("deno")) {
|