@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
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
collectImmutableAssetPaths,
|
|
33
33
|
IMMUTABLE_ASSET_CACHE_HEADERS,
|
|
34
34
|
} from "./immutable-assets.js";
|
|
35
|
+
import { AGENT_CHAT_PROCESS_RUN_PATH } from "../agent/durable-background.js";
|
|
35
36
|
|
|
36
37
|
export type WorkspaceDeployPreset = "cloudflare_pages" | "netlify" | "vercel";
|
|
37
38
|
|
|
@@ -665,6 +666,126 @@ function copyNetlifyFunctionIntoWorkspace(
|
|
|
665
666
|
fs.rmSync(dest, { recursive: true, force: true });
|
|
666
667
|
copyDir(src, dest);
|
|
667
668
|
patchNetlifyFunctionEntry(dest, app, workspaceApps, staticDir);
|
|
669
|
+
|
|
670
|
+
// Durable background agent runs (off by default). Additive ONLY: when the
|
|
671
|
+
// flag is off this emits nothing and the single-function deploy is unchanged.
|
|
672
|
+
if (isDurableBackgroundDeployEnabled()) {
|
|
673
|
+
emitNetlifyBackgroundFunction(workspaceRoot, app, src, workspaceApps);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Deploy-time gate for emitting the second `-background` Netlify function. Reads
|
|
679
|
+
* the same env flag the runtime gate uses (`AGENT_CHAT_DURABLE_BACKGROUND`).
|
|
680
|
+
* Off by default — when off, the deploy emits exactly one function per app
|
|
681
|
+
* (today's behavior, byte-for-byte).
|
|
682
|
+
*/
|
|
683
|
+
function isDurableBackgroundDeployEnabled(): boolean {
|
|
684
|
+
const raw = process.env.AGENT_CHAT_DURABLE_BACKGROUND;
|
|
685
|
+
if (raw == null) return false;
|
|
686
|
+
const v = raw.trim().toLowerCase();
|
|
687
|
+
return v === "1" || v === "true" || v === "yes" || v === "on";
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Emit a SECOND Netlify function for `app` whose name ends in `-background`,
|
|
692
|
+
* re-exporting the SAME `main.mjs` handler bundle. Netlify invokes any function
|
|
693
|
+
* whose deployed name ends in `-background` asynchronously (202 immediately, up
|
|
694
|
+
* to 15-min budget), which is exactly what the durable-background chat
|
|
695
|
+
* dispatch (`fireInternalDispatch` → AGENT_CHAT_PROCESS_RUN_PATH) needs.
|
|
696
|
+
*
|
|
697
|
+
* The function is given a `config.path` of the chat process-run route so
|
|
698
|
+
* Netlify routes that POST to this async function instead of the synchronous
|
|
699
|
+
* `<app>-server` function. It shares the same bundle (`includedFiles: ["**"]`)
|
|
700
|
+
* so `A2A_SECRET`, the DB URL, and the rest of the env/bundle are present.
|
|
701
|
+
*
|
|
702
|
+
* ⚠️ REAL-DEPLOY VERIFICATION REQUIRED. The exact Netlify routing/precedence
|
|
703
|
+
* between this `-background` function's `config.path` and the synchronous
|
|
704
|
+
* `<app>-server` function's broader `config.path` (which also matches
|
|
705
|
+
* `/_agent-native/*` for the dispatch app, or `/<app>/*` otherwise) cannot be
|
|
706
|
+
* verified in this environment. It is emitted additively and only under the
|
|
707
|
+
* flag; if Netlify resolves the overlap to the wrong function, the dispatch
|
|
708
|
+
* would land on the synchronous function (no 15-min budget) — the run would
|
|
709
|
+
* still work via the existing 40s soft-timeout path, just without the durable
|
|
710
|
+
* win. See packages/core/docs/design/durable-agent-runs.md.
|
|
711
|
+
*/
|
|
712
|
+
function emitNetlifyBackgroundFunction(
|
|
713
|
+
workspaceRoot: string,
|
|
714
|
+
app: string,
|
|
715
|
+
srcServerDir: string,
|
|
716
|
+
workspaceApps: WorkspaceAppManifestEntry[],
|
|
717
|
+
): void {
|
|
718
|
+
// Name MUST end in `-background` for Netlify async invocation.
|
|
719
|
+
const backgroundName = `${app}-agent-background`;
|
|
720
|
+
const dest = path.join(netlifyFunctionsDir(workspaceRoot), backgroundName);
|
|
721
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
722
|
+
copyDir(srcServerDir, dest);
|
|
723
|
+
|
|
724
|
+
const basePath = `/${app}`;
|
|
725
|
+
const workspaceAppAudience = workspaceAppAudienceForApp(workspaceApps, app);
|
|
726
|
+
const workspaceAppRouteAccess = workspaceAppRouteAccessForApp(
|
|
727
|
+
workspaceApps,
|
|
728
|
+
app,
|
|
729
|
+
);
|
|
730
|
+
// Only the chat process-run route needs the async function. Keeping the path
|
|
731
|
+
// narrow avoids stealing any other traffic from the synchronous function.
|
|
732
|
+
// The dispatch URL is app-base-path-prefixed (same as the agent-teams
|
|
733
|
+
// processor dispatch), so the route Netlify must map to this async function
|
|
734
|
+
// is `/<app>/_agent-native/agent-chat/_process-run`.
|
|
735
|
+
const processRunPath = `${basePath}${AGENT_CHAT_PROCESS_RUN_PATH}`;
|
|
736
|
+
const pathConfig = [processRunPath];
|
|
737
|
+
const server = `const basePath = ${JSON.stringify(basePath)};
|
|
738
|
+
|
|
739
|
+
function setBasePathEnv() {
|
|
740
|
+
const processRef = globalThis.process ??= { env: {} };
|
|
741
|
+
processRef.env ??= {};
|
|
742
|
+
Object.assign(processRef.env, {
|
|
743
|
+
AGENT_NATIVE_WORKSPACE: "1",
|
|
744
|
+
AGENT_NATIVE_WORKSPACE_APP_ID: ${JSON.stringify(app)},
|
|
745
|
+
APP_BASE_PATH: basePath,
|
|
746
|
+
AGENT_NATIVE_WORKSPACE_APP_AUDIENCE: ${JSON.stringify(workspaceAppAudience)},
|
|
747
|
+
AGENT_NATIVE_WORKSPACE_APP_PUBLIC_PATHS: ${JSON.stringify(JSON.stringify(workspaceAppRouteAccess.publicPaths))},
|
|
748
|
+
AGENT_NATIVE_WORKSPACE_APP_PROTECTED_PATHS: ${JSON.stringify(JSON.stringify(workspaceAppRouteAccess.protectedPaths))},
|
|
749
|
+
VITE_AGENT_NATIVE_WORKSPACE: "1",
|
|
750
|
+
VITE_AGENT_NATIVE_WORKSPACE_APP_ID: ${JSON.stringify(app)},
|
|
751
|
+
VITE_APP_BASE_PATH: basePath,
|
|
752
|
+
VITE_AGENT_NATIVE_WORKSPACE_APP_AUDIENCE: ${JSON.stringify(workspaceAppAudience)},
|
|
753
|
+
VITE_AGENT_NATIVE_WORKSPACE_APP_PUBLIC_PATHS: ${JSON.stringify(JSON.stringify(workspaceAppRouteAccess.publicPaths))},
|
|
754
|
+
VITE_AGENT_NATIVE_WORKSPACE_APP_PROTECTED_PATHS: ${JSON.stringify(JSON.stringify(workspaceAppRouteAccess.protectedPaths))},
|
|
755
|
+
VITE_AGENT_NATIVE_WORKSPACE_APPS_JSON: ${JSON.stringify(JSON.stringify(workspaceApps))},
|
|
756
|
+
${JSON.stringify(WORKSPACE_APPS_ENV_KEY)}: ${JSON.stringify(JSON.stringify(workspaceApps))},
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
setBasePathEnv();
|
|
761
|
+
|
|
762
|
+
let cachedHandler;
|
|
763
|
+
|
|
764
|
+
export default async function handler(...args) {
|
|
765
|
+
setBasePathEnv();
|
|
766
|
+
cachedHandler ??= (await import("./main.mjs")).default;
|
|
767
|
+
return cachedHandler(...args);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
export const config = {
|
|
771
|
+
name: ${JSON.stringify(`${app} agent background handler`)},
|
|
772
|
+
generator: "agent-native workspace deploy",
|
|
773
|
+
path: ${JSON.stringify(pathConfig)},
|
|
774
|
+
nodeBundler: "none",
|
|
775
|
+
includedFiles: ["**"],
|
|
776
|
+
preferStatic: false,
|
|
777
|
+
};
|
|
778
|
+
`;
|
|
779
|
+
// Remove the original Nitro entry (server.mjs) so only our background entry
|
|
780
|
+
// is the function entrypoint, mirroring patchNetlifyFunctionEntry.
|
|
781
|
+
fs.rmSync(path.join(dest, "server.mjs"), { force: true });
|
|
782
|
+
fs.writeFileSync(path.join(dest, `${backgroundName}.mjs`), server);
|
|
783
|
+
console.log(
|
|
784
|
+
`[workspace-deploy] Emitted durable-background function "${backgroundName}" ` +
|
|
785
|
+
`for app "${app}" (path ${processRunPath}). ` +
|
|
786
|
+
`REQUIRES real-deploy verification of Netlify async routing — see ` +
|
|
787
|
+
`docs/design/durable-agent-runs.md.`,
|
|
788
|
+
);
|
|
668
789
|
}
|
|
669
790
|
|
|
670
791
|
function patchNetlifyFunctionEntry(
|
|
@@ -952,12 +952,13 @@ async function resolveMcpAppResource(
|
|
|
952
952
|
): Promise<ResolvedMcpAppResource | null> {
|
|
953
953
|
const resource = entry.mcpApp?.resource;
|
|
954
954
|
if (!resource) return null;
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
//
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
|
|
955
|
+
// NB: the inline kill switch is intentionally NOT enforced here. This
|
|
956
|
+
// resolver also backs `resources/read`, which must keep serving the shell
|
|
957
|
+
// for a URI the host already holds (e.g. a cached descriptor) so it degrades
|
|
958
|
+
// gracefully instead of throwing a hard `-32603`. The switch is enforced at
|
|
959
|
+
// the *advertisement/render* sites (`tools/list` descriptor meta,
|
|
960
|
+
// `tools/call` result meta, `resources/list`) so disabled embeds never get
|
|
961
|
+
// advertised in the first place.
|
|
961
962
|
const resolvedUri = getMcpAppResourceUri(config, actionName, entry);
|
|
962
963
|
if (!resolvedUri) return null;
|
|
963
964
|
const description = resource.description ?? entry.tool.description;
|
|
@@ -1006,6 +1007,9 @@ async function getMcpAppResources(
|
|
|
1006
1007
|
actions: Record<string, ActionEntry>,
|
|
1007
1008
|
requestMeta?: MCPRequestMeta,
|
|
1008
1009
|
): Promise<ResolvedMcpAppResource[]> {
|
|
1010
|
+
// Advertisement path (resources/list + resources/templates/list): suppressed
|
|
1011
|
+
// by the inline kill switch so disabled embeds are never listed.
|
|
1012
|
+
if (!requestMeta?.inlineMcpApps) return [];
|
|
1009
1013
|
const resources = await Promise.all(
|
|
1010
1014
|
Object.entries(actions).map(([name, entry]) =>
|
|
1011
1015
|
resolveMcpAppResourceSafely(config, name, entry, requestMeta),
|
|
@@ -1383,7 +1387,10 @@ export async function createMCPServerForRequest(
|
|
|
1383
1387
|
: {};
|
|
1384
1388
|
const toolMeta = {
|
|
1385
1389
|
...rawToolMeta,
|
|
1386
|
-
|
|
1390
|
+
// Advertisement path: only tag the tool with its inline-embed
|
|
1391
|
+
// descriptor when the kill switch is on, so disabled embeds never
|
|
1392
|
+
// prompt a host to render/read the `ui://` resource.
|
|
1393
|
+
...(mcpAppResource && requestMeta?.inlineMcpApps
|
|
1387
1394
|
? {
|
|
1388
1395
|
...openAiToolDescriptorMeta(mcpAppResource),
|
|
1389
1396
|
[MCP_APP_RESOURCE_URI_META_KEY]: mcpAppResource.uri,
|
|
@@ -1524,6 +1531,7 @@ export async function createMCPServerForRequest(
|
|
|
1524
1531
|
userEmail: getRequestUserEmail(),
|
|
1525
1532
|
orgId: getRequestOrgId() ?? null,
|
|
1526
1533
|
caller: "mcp",
|
|
1534
|
+
actionName: name,
|
|
1527
1535
|
});
|
|
1528
1536
|
const mcpResult = isMcpActionResult(result) ? result : null;
|
|
1529
1537
|
const rawResult = mcpResult ? mcpResult.raw : result;
|
|
@@ -1533,12 +1541,14 @@ export async function createMCPServerForRequest(
|
|
|
1533
1541
|
!!mcpResult.raw &&
|
|
1534
1542
|
typeof mcpResult.raw === "object" &&
|
|
1535
1543
|
(mcpResult.raw as Record<string, unknown>).isError === true;
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1544
|
+
// Render path: only treat the result as an inline embed when the kill
|
|
1545
|
+
// switch is on. When off, `mcpAppResource` is null so every embed
|
|
1546
|
+
// branch below degrades to the plain deep-link artifacts the tool would
|
|
1547
|
+
// otherwise return — no `openai/outputTemplate`, no minted embed-start,
|
|
1548
|
+
// no embed structuredContent — so the host shows a link, not an iframe.
|
|
1549
|
+
const mcpAppResource = requestMeta?.inlineMcpApps
|
|
1550
|
+
? await resolveMcpAppResourceSafely(config, name, entry, requestMeta)
|
|
1551
|
+
: null;
|
|
1542
1552
|
const rawResultForClient = mcpAppResource
|
|
1543
1553
|
? await withServerMintedMcpAppEmbedStart(rawResult, requestMeta)
|
|
1544
1554
|
: rawResult;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDbExec, isPostgres, intType } from "../db/client.js";
|
|
2
|
+
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
2
3
|
import {
|
|
3
4
|
encryptSecretValue,
|
|
4
5
|
decryptSecretValue,
|
|
@@ -81,6 +82,11 @@ async function ensureTable(): Promise<void> {
|
|
|
81
82
|
await client.execute(
|
|
82
83
|
`UPDATE ${table} SET owner = account_id WHERE owner IS NULL`,
|
|
83
84
|
);
|
|
85
|
+
// Older deployments have a 32-bit `updated_at`; on Postgres the
|
|
86
|
+
// `Date.now()` written on every token save overflows int4. Widen in place
|
|
87
|
+
// (no-op once done / on fresh DBs). Unqualified name — the helper scopes
|
|
88
|
+
// to the `public` schema.
|
|
89
|
+
await widenIntColumnsToBigInt("oauth_tokens", ["updated_at"]);
|
|
84
90
|
})().catch((err) => {
|
|
85
91
|
// Retry init on the next call after a failed startup.
|
|
86
92
|
_initPromise = undefined;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import { getDbExec, isPostgres } from "../db/client.js";
|
|
24
|
+
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
24
25
|
import { isBlockedExtensionUrlWithDns } from "../extensions/url-safety.js";
|
|
25
26
|
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
@@ -94,6 +95,13 @@ async function ensureTable(): Promise<void> {
|
|
|
94
95
|
? CREATE_SQL.replace(/\bINTEGER\b/g, "BIGINT")
|
|
95
96
|
: CREATE_SQL;
|
|
96
97
|
await client.execute(sql);
|
|
98
|
+
// Fresh Postgres tables get BIGINT via the replace above, but tables
|
|
99
|
+
// created before that compat existed kept int4 timestamp columns; widen
|
|
100
|
+
// them so `Date.now()` writes don't overflow. No-op once done.
|
|
101
|
+
await widenIntColumnsToBigInt("custom_api_providers", [
|
|
102
|
+
"created_at",
|
|
103
|
+
"updated_at",
|
|
104
|
+
]);
|
|
97
105
|
})().catch((err) => {
|
|
98
106
|
_initPromise = undefined;
|
|
99
107
|
throw err;
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
retryOnDdlRace,
|
|
6
6
|
type DbExec,
|
|
7
7
|
} from "../db/client.js";
|
|
8
|
+
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
8
9
|
import { emitResourceChange, emitResourceDelete } from "./emitter.js";
|
|
9
10
|
import type { StoreWriteOptions } from "../settings/store.js";
|
|
10
11
|
import {
|
|
@@ -781,6 +782,15 @@ async function _doEnsureTable(): Promise<void> {
|
|
|
781
782
|
await ensureResourceColumn(client, `expires_at ${intType()}`);
|
|
782
783
|
await ensureResourceColumn(client, "metadata TEXT");
|
|
783
784
|
|
|
785
|
+
// Older deployments have 32-bit timestamp columns; on Postgres the
|
|
786
|
+
// `Date.now()` written on insert/update overflows int4. Widen in place
|
|
787
|
+
// (no-op once done / on fresh DBs).
|
|
788
|
+
await widenIntColumnsToBigInt("resources", [
|
|
789
|
+
"created_at",
|
|
790
|
+
"updated_at",
|
|
791
|
+
"expires_at",
|
|
792
|
+
]);
|
|
793
|
+
|
|
784
794
|
// Seed default shared resources if they don't exist (INSERT OR IGNORE to avoid race conditions)
|
|
785
795
|
const now = Date.now();
|
|
786
796
|
const seedSql = isPostgres()
|
|
@@ -228,11 +228,14 @@ function parsePositionalJsonArg(args: string[]): Record<string, unknown> {
|
|
|
228
228
|
* resolves `AGENT_USER_EMAIL` / the dev session); we never inject a dev
|
|
229
229
|
* identity here beyond what that wrap already established.
|
|
230
230
|
*/
|
|
231
|
-
function cliActionCtx(
|
|
231
|
+
function cliActionCtx(
|
|
232
|
+
actionName: string,
|
|
233
|
+
): import("../action.js").ActionRunContext {
|
|
232
234
|
return {
|
|
233
235
|
userEmail: getRequestUserEmail(),
|
|
234
236
|
orgId: getRequestOrgId() ?? null,
|
|
235
237
|
caller: "cli",
|
|
238
|
+
actionName,
|
|
236
239
|
};
|
|
237
240
|
}
|
|
238
241
|
|
|
@@ -268,7 +271,7 @@ async function dispatchAction(
|
|
|
268
271
|
typeof handler.run === "function"
|
|
269
272
|
) {
|
|
270
273
|
const parsed = parseActionArgs(args, { coerceBooleans: true });
|
|
271
|
-
const result = await handler.run(parsed, cliActionCtx());
|
|
274
|
+
const result = await handler.run(parsed, cliActionCtx(actionName));
|
|
272
275
|
if (handler.readOnly !== true) {
|
|
273
276
|
await notifyActionChange({ actionName }).catch(() => {});
|
|
274
277
|
}
|
|
@@ -298,7 +301,7 @@ async function dispatchAction(
|
|
|
298
301
|
const parsed = parseActionArgs(args, { coerceBooleans: true });
|
|
299
302
|
const result = await packageAction.run(
|
|
300
303
|
parsed as Record<string, string>,
|
|
301
|
-
cliActionCtx(),
|
|
304
|
+
cliActionCtx(actionName),
|
|
302
305
|
);
|
|
303
306
|
if (packageAction.readOnly !== true) {
|
|
304
307
|
await notifyActionChange({ actionName }).catch(() => {});
|
|
@@ -580,6 +580,12 @@ export async function mergeCoreSharingActions(
|
|
|
580
580
|
() => import("../appearance/actions/change-appearance.js"),
|
|
581
581
|
],
|
|
582
582
|
["toggle-demo-mode", () => import("../demo/actions/toggle-demo-mode.js")],
|
|
583
|
+
// Audit log — read surface (who changed what, when, agent vs human).
|
|
584
|
+
[
|
|
585
|
+
"list-audit-events",
|
|
586
|
+
() => import("../audit/actions/list-audit-events.js"),
|
|
587
|
+
],
|
|
588
|
+
["get-audit-event", () => import("../audit/actions/get-audit-event.js")],
|
|
583
589
|
// Org service tokens (CI credentials, e.g. PLAN_RECAP_TOKEN). Mint/revoke
|
|
584
590
|
// are toolCallable:false — preserved via preserveActionFlags below.
|
|
585
591
|
[
|
|
@@ -150,6 +150,10 @@ import {
|
|
|
150
150
|
hasConfiguredA2ASecret,
|
|
151
151
|
isA2AProductionRuntime,
|
|
152
152
|
} from "../a2a/auth-policy.js";
|
|
153
|
+
import {
|
|
154
|
+
AGENT_CHAT_PROCESS_RUN_PATH,
|
|
155
|
+
prepareProcessRunRequest,
|
|
156
|
+
} from "../agent/durable-background.js";
|
|
153
157
|
import {
|
|
154
158
|
getBuilderBrowserConnectUrlForOwner,
|
|
155
159
|
resolveBuilderBranchProjectId,
|
|
@@ -7695,6 +7699,134 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
7695
7699
|
}),
|
|
7696
7700
|
);
|
|
7697
7701
|
|
|
7702
|
+
// Shared per-request invocation: resolve auth/org/timezone context, then
|
|
7703
|
+
// pick the dev/prod/anonymous handler and run it inside the request
|
|
7704
|
+
// context. Used by the main chat POST and by the durable-background
|
|
7705
|
+
// `_process-run` processor route (which re-enters the same handler set as
|
|
7706
|
+
// the background worker), so both go through identical context + handler
|
|
7707
|
+
// selection.
|
|
7708
|
+
const invokeAgentChatHandler = async (event: any) => {
|
|
7709
|
+
// Resolve per-request auth context
|
|
7710
|
+
const ownerContext = await resolveOwnerContext(event);
|
|
7711
|
+
const owner = ownerContext.owner;
|
|
7712
|
+
|
|
7713
|
+
// Resolve org ID: explicit callback > session.orgId from Better Auth
|
|
7714
|
+
// > implicit org membership. Better Auth leaves session.orgId null
|
|
7715
|
+
// until the user explicitly switches orgs, so a fresh signup with
|
|
7716
|
+
// implicit membership (e.g. domain-matched org) would otherwise see
|
|
7717
|
+
// no org-scoped credentials. getOrgContext() does the same DB lookup
|
|
7718
|
+
// the /builder/status endpoint uses to decide "Connected".
|
|
7719
|
+
let resolvedOrgId: string | undefined;
|
|
7720
|
+
if (options?.resolveOrgId) {
|
|
7721
|
+
resolvedOrgId = (await options.resolveOrgId(event)) ?? undefined;
|
|
7722
|
+
} else {
|
|
7723
|
+
try {
|
|
7724
|
+
const session = await getSession(event);
|
|
7725
|
+
resolvedOrgId = session?.orgId ?? undefined;
|
|
7726
|
+
} catch {
|
|
7727
|
+
// Session not available
|
|
7728
|
+
}
|
|
7729
|
+
if (!resolvedOrgId) {
|
|
7730
|
+
try {
|
|
7731
|
+
const { getOrgContext } = await import("../org/context.js");
|
|
7732
|
+
const ctx = await getOrgContext(event);
|
|
7733
|
+
resolvedOrgId = ctx.orgId ?? undefined;
|
|
7734
|
+
} catch {
|
|
7735
|
+
// org_members table may not exist yet on first boot
|
|
7736
|
+
}
|
|
7737
|
+
}
|
|
7738
|
+
}
|
|
7739
|
+
|
|
7740
|
+
// Propagate the caller's IANA timezone from `x-user-timezone` so that
|
|
7741
|
+
// tool calls made by the agent (e.g. log-meal with no explicit date)
|
|
7742
|
+
// resolve "today" in the user's local timezone instead of server UTC.
|
|
7743
|
+
const tzRaw = getHeader(event, "x-user-timezone");
|
|
7744
|
+
const timezone =
|
|
7745
|
+
typeof tzRaw === "string" &&
|
|
7746
|
+
tzRaw.trim().length > 0 &&
|
|
7747
|
+
tzRaw.trim().length < 64
|
|
7748
|
+
? tzRaw.trim()
|
|
7749
|
+
: undefined;
|
|
7750
|
+
|
|
7751
|
+
return runWithRequestContext(
|
|
7752
|
+
{
|
|
7753
|
+
userEmail: owner,
|
|
7754
|
+
userName: ownerContext.name,
|
|
7755
|
+
orgId: resolvedOrgId,
|
|
7756
|
+
timezone,
|
|
7757
|
+
},
|
|
7758
|
+
() => {
|
|
7759
|
+
// App-rendered chat can't host direct code edits — HMR/full
|
|
7760
|
+
// reloads would kill the same chat surface mid-run. Force the
|
|
7761
|
+
// prod handler (no shell / no fs); the prompt block injected by
|
|
7762
|
+
// `prodHandler.systemPrompt` then steers source changes to a
|
|
7763
|
+
// separate agent surface such as Builder or the dev frame.
|
|
7764
|
+
const blockInProductCodeEditing =
|
|
7765
|
+
shouldBlockInProductCodeEditing(event);
|
|
7766
|
+
const handler =
|
|
7767
|
+
ownerContext.anonymous && anonymousHandler
|
|
7768
|
+
? anonymousHandler
|
|
7769
|
+
: !blockInProductCodeEditing && currentDevMode && devHandler
|
|
7770
|
+
? devHandler
|
|
7771
|
+
: prodHandler;
|
|
7772
|
+
return handler(event);
|
|
7773
|
+
},
|
|
7774
|
+
);
|
|
7775
|
+
};
|
|
7776
|
+
|
|
7777
|
+
// ─── Durable background agent-chat run processor ──────────────────────
|
|
7778
|
+
// Self-fire target for a long chat turn. The foreground POST claims the
|
|
7779
|
+
// run slot, inserts the run row, and `fireInternalDispatch`es here; this
|
|
7780
|
+
// route runs INSIDE the Netlify background function (15-min budget). It
|
|
7781
|
+
// HMAC-verifies the dispatch (same internal-token scheme as the agent-
|
|
7782
|
+
// teams / A2A / webhook processors), injects the background-run marker,
|
|
7783
|
+
// and re-enters the SAME agent-chat handler as the background worker,
|
|
7784
|
+
// which runs the full multi-step turn inline with the ~13min soft
|
|
7785
|
+
// timeout. With AGENT_CHAT_DURABLE_BACKGROUND off, the foreground never
|
|
7786
|
+
// dispatches here, so this route is never exercised.
|
|
7787
|
+
getH3App(nitroApp).use(
|
|
7788
|
+
AGENT_CHAT_PROCESS_RUN_PATH,
|
|
7789
|
+
defineEventHandler(async (event) => {
|
|
7790
|
+
if (getMethod(event) !== "POST") {
|
|
7791
|
+
setResponseStatus(event, 405);
|
|
7792
|
+
return { error: "Method not allowed" };
|
|
7793
|
+
}
|
|
7794
|
+
// Consume the body ONCE (h3 v2's web Request stream is single-use).
|
|
7795
|
+
let processBody: any;
|
|
7796
|
+
try {
|
|
7797
|
+
processBody = await readBody(event);
|
|
7798
|
+
} catch {
|
|
7799
|
+
setResponseStatus(event, 400);
|
|
7800
|
+
return { error: "Invalid request body" };
|
|
7801
|
+
}
|
|
7802
|
+
|
|
7803
|
+
// Validate + HMAC-authenticate the self-dispatch and prepare the
|
|
7804
|
+
// background-worker body. Pure decision (unit-tested in
|
|
7805
|
+
// durable-background.spec.ts); the route only wires it to h3.
|
|
7806
|
+
const prepared = prepareProcessRunRequest(
|
|
7807
|
+
processBody,
|
|
7808
|
+
getHeader(event, "authorization"),
|
|
7809
|
+
);
|
|
7810
|
+
if (!prepared.ok) {
|
|
7811
|
+
setResponseStatus(event, prepared.status);
|
|
7812
|
+
return { error: prepared.error };
|
|
7813
|
+
}
|
|
7814
|
+
|
|
7815
|
+
// Stash the verified+augmented body for the handler — the body stream
|
|
7816
|
+
// is already consumed, so the handler reads this instead.
|
|
7817
|
+
(event as any).context = (event as any).context ?? {};
|
|
7818
|
+
(event as any).context.__agentChatBackgroundBody = prepared.body;
|
|
7819
|
+
|
|
7820
|
+
try {
|
|
7821
|
+
return await invokeAgentChatHandler(event);
|
|
7822
|
+
} catch (err: any) {
|
|
7823
|
+
console.error("[agent-chat] _process-run failed:", err);
|
|
7824
|
+
setResponseStatus(event, 500);
|
|
7825
|
+
return { error: "process-run failed" };
|
|
7826
|
+
}
|
|
7827
|
+
}),
|
|
7828
|
+
);
|
|
7829
|
+
|
|
7698
7830
|
// Mount the main chat handler — delegates to dev or prod handler based on current mode.
|
|
7699
7831
|
// This is mounted last because h3's use() is prefix-based, meaning /_agent-native/agent-chat
|
|
7700
7832
|
// also matches /_agent-native/agent-chat/threads/... — we skip sub-path requests here so the
|
|
@@ -7713,72 +7845,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
|
|
|
7713
7845
|
return { error: "Not found" };
|
|
7714
7846
|
}
|
|
7715
7847
|
|
|
7716
|
-
|
|
7717
|
-
const ownerContext = await resolveOwnerContext(event);
|
|
7718
|
-
const owner = ownerContext.owner;
|
|
7719
|
-
|
|
7720
|
-
// Resolve org ID: explicit callback > session.orgId from Better Auth
|
|
7721
|
-
// > implicit org membership. Better Auth leaves session.orgId null
|
|
7722
|
-
// until the user explicitly switches orgs, so a fresh signup with
|
|
7723
|
-
// implicit membership (e.g. domain-matched org) would otherwise see
|
|
7724
|
-
// no org-scoped credentials. getOrgContext() does the same DB lookup
|
|
7725
|
-
// the /builder/status endpoint uses to decide "Connected".
|
|
7726
|
-
let resolvedOrgId: string | undefined;
|
|
7727
|
-
if (options?.resolveOrgId) {
|
|
7728
|
-
resolvedOrgId = (await options.resolveOrgId(event)) ?? undefined;
|
|
7729
|
-
} else {
|
|
7730
|
-
try {
|
|
7731
|
-
const session = await getSession(event);
|
|
7732
|
-
resolvedOrgId = session?.orgId ?? undefined;
|
|
7733
|
-
} catch {
|
|
7734
|
-
// Session not available
|
|
7735
|
-
}
|
|
7736
|
-
if (!resolvedOrgId) {
|
|
7737
|
-
try {
|
|
7738
|
-
const { getOrgContext } = await import("../org/context.js");
|
|
7739
|
-
const ctx = await getOrgContext(event);
|
|
7740
|
-
resolvedOrgId = ctx.orgId ?? undefined;
|
|
7741
|
-
} catch {
|
|
7742
|
-
// org_members table may not exist yet on first boot
|
|
7743
|
-
}
|
|
7744
|
-
}
|
|
7745
|
-
}
|
|
7746
|
-
|
|
7747
|
-
// Propagate the caller's IANA timezone from `x-user-timezone` so that
|
|
7748
|
-
// tool calls made by the agent (e.g. log-meal with no explicit date)
|
|
7749
|
-
// resolve "today" in the user's local timezone instead of server UTC.
|
|
7750
|
-
const tzRaw = getHeader(event, "x-user-timezone");
|
|
7751
|
-
const timezone =
|
|
7752
|
-
typeof tzRaw === "string" &&
|
|
7753
|
-
tzRaw.trim().length > 0 &&
|
|
7754
|
-
tzRaw.trim().length < 64
|
|
7755
|
-
? tzRaw.trim()
|
|
7756
|
-
: undefined;
|
|
7757
|
-
|
|
7758
|
-
return runWithRequestContext(
|
|
7759
|
-
{
|
|
7760
|
-
userEmail: owner,
|
|
7761
|
-
userName: ownerContext.name,
|
|
7762
|
-
orgId: resolvedOrgId,
|
|
7763
|
-
timezone,
|
|
7764
|
-
},
|
|
7765
|
-
() => {
|
|
7766
|
-
// App-rendered chat can't host direct code edits — HMR/full
|
|
7767
|
-
// reloads would kill the same chat surface mid-run. Force the
|
|
7768
|
-
// prod handler (no shell / no fs); the prompt block injected by
|
|
7769
|
-
// `prodHandler.systemPrompt` then steers source changes to a
|
|
7770
|
-
// separate agent surface such as Builder or the dev frame.
|
|
7771
|
-
const blockInProductCodeEditing =
|
|
7772
|
-
shouldBlockInProductCodeEditing(event);
|
|
7773
|
-
const handler =
|
|
7774
|
-
ownerContext.anonymous && anonymousHandler
|
|
7775
|
-
? anonymousHandler
|
|
7776
|
-
: !blockInProductCodeEditing && currentDevMode && devHandler
|
|
7777
|
-
? devHandler
|
|
7778
|
-
: prodHandler;
|
|
7779
|
-
return handler(event);
|
|
7780
|
-
},
|
|
7781
|
-
);
|
|
7848
|
+
return invokeAgentChatHandler(event);
|
|
7782
7849
|
}),
|
|
7783
7850
|
);
|
|
7784
7851
|
|
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
retryOnDdlRace,
|
|
71
71
|
describeDbError,
|
|
72
72
|
} from "../db/client.js";
|
|
73
|
+
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
73
74
|
import { getBetterAuth, getBetterAuthSync } from "./better-auth-instance.js";
|
|
74
75
|
import type { BetterAuthConfig } from "./better-auth-instance.js";
|
|
75
76
|
import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
|
|
@@ -869,6 +870,10 @@ async function ensureSessionTable(): Promise<void> {
|
|
|
869
870
|
} catch {
|
|
870
871
|
// Column already exists
|
|
871
872
|
}
|
|
873
|
+
// Older deployments have a 32-bit `created_at`; on Postgres the
|
|
874
|
+
// `Date.now()` written on session create overflows int4. Widen in place
|
|
875
|
+
// (no-op once done / on fresh DBs).
|
|
876
|
+
await widenIntColumnsToBigInt("sessions", ["created_at"]);
|
|
872
877
|
})().catch((err) => {
|
|
873
878
|
// Don't cache the rejection — let the next caller retry a fresh init.
|
|
874
879
|
_sessionInitPromise = undefined;
|
|
@@ -757,6 +757,20 @@ export function createCoreRoutesPlugin(
|
|
|
757
757
|
// Observability module not available — skip
|
|
758
758
|
}
|
|
759
759
|
|
|
760
|
+
// Audit log — durable, append-only record of who mutated what app data,
|
|
761
|
+
// when, and (for the agent) in which run. Capture is automatic at the
|
|
762
|
+
// action seam; here we just ensure the table exists and start the
|
|
763
|
+
// retention purge. Best-effort so a missing DB never crashes boot.
|
|
764
|
+
try {
|
|
765
|
+
const { ensureAuditTables } = await import("../audit/store.js");
|
|
766
|
+
const { startAuditCleanupJob } =
|
|
767
|
+
await import("../audit/cleanup-job.js");
|
|
768
|
+
ensureAuditTables().catch(() => {});
|
|
769
|
+
startAuditCleanupJob();
|
|
770
|
+
} catch {
|
|
771
|
+
// Audit module not available — skip
|
|
772
|
+
}
|
|
773
|
+
|
|
760
774
|
const P = FRAMEWORK_ROUTE_PREFIX;
|
|
761
775
|
|
|
762
776
|
// Security response headers — emitted on every framework response.
|
|
@@ -19,11 +19,14 @@
|
|
|
19
19
|
* - `Referrer-Policy: strict-origin-when-cross-origin` — strips path/query
|
|
20
20
|
* from outbound Referer headers when the request crosses origin, so a
|
|
21
21
|
* public-share viewer's outbound link clicks never leak the share token.
|
|
22
|
-
* - `Permissions-Policy: camera
|
|
23
|
-
* screen-wake-lock=()` — allows
|
|
24
|
-
* for
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* - `Permissions-Policy: camera=*, microphone=(self), geolocation=(),
|
|
23
|
+
* screen-wake-lock=()` — allows microphone access for composer dictation
|
|
24
|
+
* and camera access for media-capture UI (the Clips recorder, and the
|
|
25
|
+
* Clips browser extension's camera bubble, which is injected as a
|
|
26
|
+
* cross-origin iframe and is therefore blocked outright by `camera=()`).
|
|
27
|
+
* Location and wake-lock stay disabled. The browser still gates actual
|
|
28
|
+
* camera/mic use behind a per-origin permission prompt, so `camera=*` only
|
|
29
|
+
* removes the policy-level block, not the user consent.
|
|
27
30
|
* - `Cross-Origin-Opener-Policy: same-origin` — isolates window.opener so
|
|
28
31
|
* a popup-window opener reference can't read or modify our document.
|
|
29
32
|
* - `Cross-Origin-Embedder-Policy: require-corp` — emitted only for
|
|
@@ -69,7 +72,7 @@ export function computeInlineScriptHash(scriptContent: string): string {
|
|
|
69
72
|
|
|
70
73
|
const HSTS = "max-age=31536000; includeSubDomains; preload";
|
|
71
74
|
const PERMISSIONS_POLICY =
|
|
72
|
-
"camera
|
|
75
|
+
"camera=*, microphone=(self), geolocation=(), screen-wake-lock=()";
|
|
73
76
|
|
|
74
77
|
/**
|
|
75
78
|
* Returns true when the request was received over HTTPS. We trust both the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "events";
|
|
2
2
|
import { getDbExec, isPostgres, intType } from "../db/client.js";
|
|
3
|
+
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
3
4
|
|
|
4
5
|
let _initPromise: Promise<void> | undefined;
|
|
5
6
|
|
|
@@ -25,6 +26,11 @@ async function ensureTable(): Promise<void> {
|
|
|
25
26
|
updated_at ${intType()} NOT NULL
|
|
26
27
|
)
|
|
27
28
|
`);
|
|
29
|
+
// Older deployments (pre BIGINT-compat) have a 32-bit `updated_at`; on
|
|
30
|
+
// Postgres the `Date.now()` written on every setSetting overflows int4.
|
|
31
|
+
// Widen in place (no-op once done / on fresh DBs). Pass the unqualified
|
|
32
|
+
// name — the helper scopes to the `public` schema.
|
|
33
|
+
await widenIntColumnsToBigInt("settings", ["updated_at"]);
|
|
28
34
|
// Index for the poll watermark query: `SELECT MAX(updated_at) FROM settings`.
|
|
29
35
|
// MAX on an indexed column avoids a full-table scan on every poll cycle.
|
|
30
36
|
// IF NOT EXISTS makes it idempotent on existing databases.
|