@agent-native/core 0.131.5 → 0.131.7
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 +3 -3
- package/corpus/core/CHANGELOG.md +70 -0
- package/corpus/core/docs/content/agent-native-toolkit.mdx +13 -6
- package/corpus/core/docs/content/cloneable-saas.mdx +5 -5
- package/corpus/core/docs/content/creating-templates.mdx +9 -0
- package/corpus/core/docs/content/deployment.mdx +18 -12
- package/corpus/core/docs/content/doctor.mdx +8 -1
- package/corpus/core/docs/content/drop-in-agent.mdx +1 -1
- package/corpus/core/docs/content/extensions.mdx +18 -18
- package/corpus/core/docs/content/faq.mdx +4 -4
- package/corpus/core/docs/content/key-concepts.mdx +30 -21
- package/corpus/core/docs/content/security.mdx +2 -2
- package/corpus/core/docs/content/template-brain-developers.mdx +4 -1
- package/corpus/core/docs/content/template-mail-developers.mdx +4 -1
- package/corpus/core/docs/content/what-is-agent-native.mdx +6 -6
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/client.ts +449 -66
- package/corpus/core/src/a2a/correlation.ts +31 -0
- package/corpus/core/src/a2a/invoke.ts +4 -2
- package/corpus/core/src/a2a/types.ts +4 -0
- package/corpus/core/src/action.ts +3 -0
- package/corpus/core/src/agent/action-continuation-guidance.ts +2 -1
- package/corpus/core/src/agent/engine/registry.ts +98 -6
- package/corpus/core/src/agent/production-agent.ts +151 -18
- package/corpus/core/src/agent/run-loop-with-resume.ts +190 -3
- package/corpus/core/src/agent/run-manager.ts +117 -41
- package/corpus/core/src/agent/thread-debug-history.ts +86 -0
- package/corpus/core/src/agent/types.ts +3 -1
- package/corpus/core/src/cli/create.ts +11 -1
- package/corpus/core/src/cli/templates-meta.ts +2 -2
- package/corpus/core/src/client/agent-chat-adapter.ts +91 -13
- package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +11 -9
- package/corpus/core/src/client/sse-event-processor.ts +17 -3
- package/corpus/core/src/db/client.ts +57 -17
- package/corpus/core/src/guards/no-env-credentials.ts +2 -0
- package/corpus/core/src/integrations/adapters/slack.ts +8 -3
- package/corpus/core/src/localization/default-messages.ts +44 -0
- package/corpus/core/src/observability/traces.ts +78 -5
- package/corpus/core/src/scripts/call-agent.ts +294 -11
- package/corpus/core/src/scripts/describe-workspace-apps.ts +2 -2
- package/corpus/core/src/secrets/crypto.ts +156 -35
- package/corpus/core/src/secrets/storage.ts +64 -26
- package/corpus/core/src/server/agent-capabilities.ts +1 -1
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +113 -7
- package/corpus/core/src/server/agent-chat/context-tools.ts +2 -0
- package/corpus/core/src/server/agent-chat/plugin-options.ts +34 -1
- package/corpus/core/src/server/agent-chat/prompt-resources.ts +1 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +91 -59
- package/corpus/core/src/templates/default/.agents/skills/secrets/SKILL.md +19 -7
- package/corpus/core/src/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
- package/corpus/core/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
- package/corpus/core/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
- package/corpus/core/src/vite/client.ts +1 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-ops/SKILL.md +5 -4
- package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +6 -4
- package/corpus/templates/analytics/AGENTS.md +7 -4
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +4 -3
- package/corpus/templates/analytics/changelog/2026-07-30-delegated-analytics-questions-now-return-a-useful-current-me.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-30-scheduled-dashboard-emails-complete-every-panel-without-pool.md +6 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +28 -1
- package/corpus/templates/analytics/server/jobs/dashboard-report.ts +18 -11
- package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +4 -4
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -5
- package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +6 -2
- package/corpus/templates/analytics/server/lib/dashboard-panel-source-resolver.ts +11 -4
- package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +49 -31
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -8
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +9 -3
- package/corpus/templates/analytics/server/lib/production-serverless-runtime.ts +11 -0
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +10 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +3 -2
- package/corpus/templates/analytics/server/plugins/analytics-alert-jobs.ts +2 -1
- package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +2 -1
- package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +1 -12
- package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +54 -6
- package/corpus/templates/brain/.agents/skills/brain/SKILL.md +20 -9
- package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +33 -1
- package/corpus/templates/brain/AGENTS.md +3 -1
- package/corpus/templates/brain/README.md +4 -1
- package/corpus/templates/brain/actions/_schemas.ts +30 -8
- package/corpus/templates/brain/actions/ask-brain.ts +124 -14
- package/corpus/templates/brain/actions/create-source.ts +18 -2
- package/corpus/templates/brain/actions/enqueue-captures-distillation.ts +8 -123
- package/corpus/templates/brain/actions/enqueue-distillation.ts +6 -127
- package/corpus/templates/brain/actions/import-capture.ts +17 -5
- package/corpus/templates/brain/actions/import-transcript.ts +17 -5
- package/corpus/templates/brain/actions/update-source.ts +25 -5
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/brain/changelog/2026-07-30-blessed-resources-can-feed-cited-company-answers.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-30-the-left-sidebar-no-longer-shows-a-blank-organization-placeh.md +6 -0
- package/corpus/templates/brain/server/lib/brain.ts +49 -19
- package/corpus/templates/brain/server/lib/distillation-queue.ts +147 -0
- package/corpus/templates/brain/server/lib/source-policy.ts +264 -0
- package/corpus/templates/brain/server/plugins/agent-chat.ts +3 -2
- package/corpus/templates/brain/server/routes/api/_agent-native/brain/ingest.post.ts +116 -19
- package/corpus/templates/clips/app/components/meetings/google-oauth-identity-notice.tsx +51 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +3 -1
- package/corpus/templates/clips/changelog/2026-07-30-google-calendar-warning.md +6 -0
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +72 -8
- package/corpus/templates/content/changelog/2026-07-30-the-slash-command-hint-now-stays-on-only-the-active-empty-li.md +6 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +506 -11
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +148 -17
- package/corpus/templates/design/app/components/design/canvas-interactions/design-canvas-interactions.ts +357 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +7 -466
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +15 -311
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +19 -997
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +8 -1216
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +31 -38
- package/corpus/templates/design/package.json +0 -1
- package/corpus/templates/dispatch/AGENTS.md +8 -0
- package/corpus/templates/dispatch/DEVELOPING.md +29 -0
- package/corpus/templates/dispatch/actions/view-screen.ts +34 -2
- package/corpus/templates/dispatch/app/i18n-data.ts +457 -0
- package/corpus/templates/dispatch/changelog/2026-07-30-delegated-dispatch-work-continues-reliably-in-the-background.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-30-find-failed-runs-across-apps.md +6 -0
- package/corpus/templates/dispatch/netlify.toml +3 -0
- package/corpus/templates/plan/actions/get-visual-plan.ts +10 -6
- package/corpus/templates/plan/actions/patch-visual-plan-source.ts +117 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +56 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -1
- package/corpus/templates/plan/changelog/2026-07-30-plan-agents-recover-with-targeted-writes.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +35 -0
- package/corpus/templates/slides/.agents/skills/analytics-data-for-decks/SKILL.md +8 -12
- package/corpus/templates/slides/AGENTS.md +4 -4
- package/corpus/templates/slides/actions/create-deck.ts +1 -1
- package/corpus/templates/slides/actions/duplicate-deck.ts +19 -4
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +2 -10
- package/corpus/templates/slides/app/components/deck/MermaidRenderer.tsx +12 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +30 -13
- package/corpus/templates/slides/app/components/editor/DeckEditorSkeleton.tsx +35 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +63 -56
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +21 -76
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1110 -292
- package/corpus/templates/slides/app/components/editor/SlideOverflowWarning.tsx +53 -0
- package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +235 -18
- package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +4 -1
- package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +11 -0
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +129 -0
- package/corpus/templates/slides/app/components/editor/selection-overlay-measurement.ts +70 -0
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +519 -65
- package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +36 -426
- package/corpus/templates/slides/app/components/visual-editor/DrawOverlay.tsx +19 -458
- package/corpus/templates/slides/app/context/DeckContext.tsx +179 -72
- package/corpus/templates/slides/app/global.css +105 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +8 -0
- package/corpus/templates/slides/app/lib/deck-editor-loading.ts +26 -0
- package/corpus/templates/slides/app/lib/mermaid-blocks.ts +27 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +71 -38
- package/corpus/templates/slides/app/pages/Index.tsx +98 -41
- package/corpus/templates/slides/changelog/2026-07-30-deck-loading-no-longer-flashes-an-unavailable-message.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-duplicating-a-deck-from-the-deck-list-now-opens-the-copy-imm.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-layout-overflow-warning-is-readable-and-dismissible.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-new-decks-now-start-with-an-empty-slide-list-when-no-slides-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-shared-canvas-annotations.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slide-objects-can-be-moved-as-a-group-copied-and-pasted-with.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slide-text-editing-and-object-controls-now-match-google-slides.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-style-panel-now-scrolls-with-speaker-notes-open-shows-the-sl.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-top-toolbar-controls-now-use-consistent-sizing-with-undo-and.md +6 -0
- package/corpus/templates/slides/netlify.toml +3 -1
- package/corpus/templates/slides/package.json +0 -1
- package/corpus/templates/slides/server/lib/chat-attachments.ts +18 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -0
- package/corpus/templates/slides/vite.config.ts +0 -1
- package/corpus/toolkit/CHANGELOG.md +13 -0
- package/corpus/toolkit/README.md +19 -0
- package/corpus/toolkit/agent-native.eject.json +38 -0
- package/corpus/toolkit/package.json +29 -1
- package/corpus/toolkit/src/canvas-annotations/CanvasCommentPins.tsx +861 -0
- package/corpus/toolkit/src/canvas-annotations/DrawOverlay.tsx +1233 -0
- package/corpus/toolkit/src/canvas-annotations/index.ts +15 -0
- package/corpus/toolkit/src/canvas-annotations/types.ts +14 -0
- package/corpus/toolkit/src/canvas-interactions/canvas-interactions.ts +933 -0
- package/corpus/toolkit/src/canvas-interactions/index.ts +67 -0
- package/corpus/toolkit/src/design-tweaks/scrub-input-utils.ts +311 -0
- package/corpus/toolkit/src/design-tweaks/scrub-input.tsx +491 -0
- package/corpus/toolkit/src/design-tweaks/visual-style-controls.tsx +26 -171
- package/corpus/toolkit/src/index.ts +1 -0
- package/dist/a2a/client.d.ts +23 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +309 -44
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/correlation.d.ts +1 -0
- package/dist/a2a/correlation.d.ts.map +1 -1
- package/dist/a2a/correlation.js +27 -0
- package/dist/a2a/correlation.js.map +1 -1
- package/dist/a2a/invoke.d.ts.map +1 -1
- package/dist/a2a/invoke.js +4 -2
- package/dist/a2a/invoke.js.map +1 -1
- package/dist/a2a/types.d.ts +4 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/action.d.ts +3 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/agent/action-continuation-guidance.d.ts.map +1 -1
- package/dist/agent/action-continuation-guidance.js +2 -1
- package/dist/agent/action-continuation-guidance.js.map +1 -1
- package/dist/agent/engine/registry.d.ts.map +1 -1
- package/dist/agent/engine/registry.js +82 -8
- package/dist/agent/engine/registry.js.map +1 -1
- package/dist/agent/production-agent.d.ts +40 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +113 -15
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +21 -0
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +163 -4
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/agent/run-manager.d.ts +16 -9
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +93 -42
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/thread-debug-history.d.ts +10 -0
- package/dist/agent/thread-debug-history.d.ts.map +1 -0
- package/dist/agent/thread-debug-history.js +68 -0
- package/dist/agent/thread-debug-history.js.map +1 -0
- package/dist/agent/types.d.ts +3 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +6 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/templates-meta.js +2 -2
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +76 -13
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
- package/dist/client/resources/McpIntegrationDialog.js +1 -1
- package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +12 -3
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +48 -17
- package/dist/db/client.js.map +1 -1
- package/dist/guards/no-env-credentials.d.ts.map +1 -1
- package/dist/guards/no-env-credentials.js +2 -0
- package/dist/guards/no-env-credentials.js.map +1 -1
- package/dist/integrations/adapters/slack.js +7 -3
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/localization/default-messages.d.ts +43 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +43 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/observability/traces.d.ts +3 -1
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +73 -4
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +237 -12
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/scripts/describe-workspace-apps.js +2 -2
- package/dist/scripts/describe-workspace-apps.js.map +1 -1
- package/dist/secrets/crypto.d.ts +36 -21
- package/dist/secrets/crypto.d.ts.map +1 -1
- package/dist/secrets/crypto.js +134 -33
- package/dist/secrets/crypto.js.map +1 -1
- package/dist/secrets/storage.d.ts +6 -6
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +43 -26
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/agent-capabilities.js +1 -1
- package/dist/server/agent-capabilities.js.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.d.ts +17 -3
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +94 -3
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
- package/dist/server/agent-chat/context-tools.js +2 -0
- package/dist/server/agent-chat/context-tools.js.map +1 -1
- package/dist/server/agent-chat/plugin-options.d.ts +23 -0
- package/dist/server/agent-chat/plugin-options.d.ts.map +1 -1
- package/dist/server/agent-chat/plugin-options.js +4 -1
- package/dist/server/agent-chat/plugin-options.js.map +1 -1
- package/dist/server/agent-chat/prompt-resources.js +1 -1
- package/dist/server/agent-chat/prompt-resources.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +79 -56
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +19 -7
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
- package/dist/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/agent-native-toolkit.mdx +13 -6
- package/docs/content/cloneable-saas.mdx +5 -5
- package/docs/content/creating-templates.mdx +9 -0
- package/docs/content/deployment.mdx +18 -12
- package/docs/content/doctor.mdx +8 -1
- package/docs/content/drop-in-agent.mdx +1 -1
- package/docs/content/extensions.mdx +18 -18
- package/docs/content/faq.mdx +4 -4
- package/docs/content/key-concepts.mdx +30 -21
- package/docs/content/security.mdx +2 -2
- package/docs/content/template-brain-developers.mdx +4 -1
- package/docs/content/template-mail-developers.mdx +4 -1
- package/docs/content/what-is-agent-native.mdx +6 -6
- package/package.json +3 -3
- package/src/a2a/client.ts +449 -66
- package/src/a2a/correlation.ts +31 -0
- package/src/a2a/invoke.ts +4 -2
- package/src/a2a/types.ts +4 -0
- package/src/action.ts +3 -0
- package/src/agent/action-continuation-guidance.ts +2 -1
- package/src/agent/engine/registry.ts +98 -6
- package/src/agent/production-agent.ts +151 -18
- package/src/agent/run-loop-with-resume.ts +190 -3
- package/src/agent/run-manager.ts +117 -41
- package/src/agent/thread-debug-history.ts +86 -0
- package/src/agent/types.ts +3 -1
- package/src/cli/create.ts +11 -1
- package/src/cli/templates-meta.ts +2 -2
- package/src/client/agent-chat-adapter.ts +91 -13
- package/src/client/resources/McpIntegrationDialog.tsx +11 -9
- package/src/client/sse-event-processor.ts +17 -3
- package/src/db/client.ts +57 -17
- package/src/guards/no-env-credentials.ts +2 -0
- package/src/integrations/adapters/slack.ts +8 -3
- package/src/localization/default-messages.ts +44 -0
- package/src/observability/traces.ts +78 -5
- package/src/scripts/call-agent.ts +294 -11
- package/src/scripts/describe-workspace-apps.ts +2 -2
- package/src/secrets/crypto.ts +156 -35
- package/src/secrets/storage.ts +64 -26
- package/src/server/agent-capabilities.ts +1 -1
- package/src/server/agent-chat/action-filters-a2a.ts +113 -7
- package/src/server/agent-chat/context-tools.ts +2 -0
- package/src/server/agent-chat/plugin-options.ts +34 -1
- package/src/server/agent-chat/prompt-resources.ts +1 -1
- package/src/server/agent-chat-plugin.ts +91 -59
- package/src/templates/default/.agents/skills/secrets/SKILL.md +19 -7
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +19 -7
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +18 -7
- package/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +15 -14
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +19 -7
- package/src/vite/client.ts +1 -0
package/src/secrets/crypto.ts
CHANGED
|
@@ -10,25 +10,29 @@
|
|
|
10
10
|
* existing single-app deployments can keep reading and writing secrets while
|
|
11
11
|
* they migrate to the shared key.
|
|
12
12
|
*
|
|
13
|
-
* The
|
|
14
|
-
* `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then
|
|
15
|
-
* then `BETTER_AUTH_SECRET`.
|
|
16
|
-
*
|
|
17
|
-
* shared
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* apps
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
13
|
+
* The generic encryption key is derived from
|
|
14
|
+
* `<APP_NAME>_SECRETS_ENCRYPTION_KEY` when set, then
|
|
15
|
+
* `SECRETS_ENCRYPTION_KEY`, then `BETTER_AUTH_SECRET`. Workspace-shared vault
|
|
16
|
+
* rows use a different precedence: `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then
|
|
17
|
+
* the legacy shared `SECRETS_ENCRYPTION_KEY`, then material derived from the
|
|
18
|
+
* workspace-wide `A2A_SECRET`, then the app-local auth/key fallbacks retained
|
|
19
|
+
* for backward-compatible reads. A previous workspace key can be supplied
|
|
20
|
+
* during rotation with `WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS`. This keeps
|
|
21
|
+
* sibling apps on one stable key even when each app correctly has a different
|
|
22
|
+
* auth secret, and prevents A2A trust rotation from stranding vault data.
|
|
23
|
+
*
|
|
24
|
+
* In production we refuse to start without configured key material — a
|
|
25
|
+
* CWD-derived fallback would be effectively static (e.g. `/var/task` on
|
|
26
|
+
* Lambda), so anyone with read access to the DB could decrypt every secret.
|
|
26
27
|
*
|
|
27
28
|
* Encrypted values are tagged `v1:<iv-hex>:<ct-hex>:<tag-hex>`. The `v1:` prefix
|
|
28
29
|
* lets readers distinguish ciphertext from legacy plaintext during migration.
|
|
29
30
|
*/
|
|
30
31
|
|
|
31
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
deriveServerSecret,
|
|
34
|
+
getWorkspaceA2ADerivedSecret,
|
|
35
|
+
} from "../server/derived-secret.js";
|
|
32
36
|
|
|
33
37
|
type NodeCryptoModule = typeof import("node:crypto");
|
|
34
38
|
|
|
@@ -80,7 +84,12 @@ function appScopedEncryptionKey(): string | undefined {
|
|
|
80
84
|
: undefined;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Preserve the generic/app-local key precedence. Generic encrypted values are
|
|
89
|
+
* not a cross-app boundary, and changing their preferred key would strand
|
|
90
|
+
* existing OAuth tokens and credentials.
|
|
91
|
+
*/
|
|
92
|
+
function genericEncryptionKeyMaterial(): string | undefined {
|
|
84
93
|
if (typeof process === "undefined") return undefined;
|
|
85
94
|
return (
|
|
86
95
|
process.env.SECRETS_ENCRYPTION_KEY ||
|
|
@@ -89,13 +98,77 @@ function sharedEncryptionKeyMaterial(): string | undefined {
|
|
|
89
98
|
);
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
function workspaceSharedEncryptionKeyMaterial(): string | undefined {
|
|
102
|
+
if (typeof process === "undefined") return undefined;
|
|
103
|
+
return process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY?.trim() || undefined;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function previousWorkspaceSharedEncryptionKeyMaterial(): string | undefined {
|
|
107
|
+
if (typeof process === "undefined") return undefined;
|
|
108
|
+
return (
|
|
109
|
+
process.env.WORKSPACE_SECRETS_ENCRYPTION_KEY_PREVIOUS?.trim() || undefined
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Vault sharing follows configured A2A trust even on independently deployed
|
|
115
|
+
* sibling apps that are not launched by the workspace wrapper. The wrapper's
|
|
116
|
+
* `AGENT_NATIVE_WORKSPACE` flag remains required for auth/OAuth-derived
|
|
117
|
+
* secrets, but it must not be a hidden prerequisite for decrypting vault rows
|
|
118
|
+
* when every app already has the same explicit `A2A_SECRET`.
|
|
119
|
+
*/
|
|
120
|
+
function a2aSharedEncryptionKeyMaterial(): string | undefined {
|
|
121
|
+
const workspaceDerived = getWorkspaceA2ADerivedSecret("secrets-encryption");
|
|
122
|
+
if (workspaceDerived) return workspaceDerived;
|
|
123
|
+
if (typeof process === "undefined") return undefined;
|
|
124
|
+
const rootSecret = process.env.A2A_SECRET?.trim();
|
|
125
|
+
return rootSecret
|
|
126
|
+
? deriveServerSecret(rootSecret, "secrets-encryption")
|
|
127
|
+
: undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Stable materials that may be used for workspace-shared vault rows, ordered
|
|
132
|
+
* from current preference to legacy compatibility fallbacks.
|
|
133
|
+
*
|
|
134
|
+
* `BETTER_AUTH_SECRET` deliberately comes after the workspace and A2A-derived
|
|
135
|
+
* materials: sibling apps normally have different auth secrets but share
|
|
136
|
+
* workspace trust.
|
|
137
|
+
*/
|
|
138
|
+
function sharedEncryptionKeyMaterials(): string[] {
|
|
139
|
+
if (typeof process === "undefined") return [];
|
|
140
|
+
const candidates = [
|
|
141
|
+
workspaceSharedEncryptionKeyMaterial(),
|
|
142
|
+
process.env.SECRETS_ENCRYPTION_KEY,
|
|
143
|
+
a2aSharedEncryptionKeyMaterial(),
|
|
144
|
+
previousWorkspaceSharedEncryptionKeyMaterial(),
|
|
145
|
+
process.env.BETTER_AUTH_SECRET,
|
|
146
|
+
appScopedEncryptionKey(),
|
|
147
|
+
].filter((value): value is string => Boolean(value));
|
|
148
|
+
return [...new Set(candidates)];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function preferredSharedEncryptionKeyMaterial(): string | undefined {
|
|
152
|
+
if (typeof process === "undefined") return undefined;
|
|
153
|
+
return (
|
|
154
|
+
workspaceSharedEncryptionKeyMaterial() ||
|
|
155
|
+
process.env.SECRETS_ENCRYPTION_KEY ||
|
|
156
|
+
a2aSharedEncryptionKeyMaterial() ||
|
|
157
|
+
process.env.BETTER_AUTH_SECRET ||
|
|
158
|
+
appScopedEncryptionKey()
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function hashSecretEncryptionKey(material: string): Buffer {
|
|
163
|
+
const { createHash } = requireNodeCrypto();
|
|
164
|
+
return createHash("sha256").update(material).digest();
|
|
165
|
+
}
|
|
166
|
+
|
|
92
167
|
function deriveSecretEncryptionKey(
|
|
93
168
|
explicit: string | undefined,
|
|
94
169
|
errorMessage: string,
|
|
95
170
|
warningMessage: string,
|
|
96
171
|
): Buffer {
|
|
97
|
-
const { createHash } = requireNodeCrypto();
|
|
98
|
-
|
|
99
172
|
if (!explicit) {
|
|
100
173
|
if (processNodeEnv() === "production") {
|
|
101
174
|
throw new Error(errorMessage);
|
|
@@ -108,7 +181,7 @@ function deriveSecretEncryptionKey(
|
|
|
108
181
|
}
|
|
109
182
|
|
|
110
183
|
const material = explicit || `agent-native-secrets:${processCwd()}`;
|
|
111
|
-
return
|
|
184
|
+
return hashSecretEncryptionKey(material);
|
|
112
185
|
}
|
|
113
186
|
|
|
114
187
|
/**
|
|
@@ -125,7 +198,7 @@ export function getSecretEncryptionKey(): Buffer {
|
|
|
125
198
|
.replace(/[^A-Z0-9]+/g, "_")
|
|
126
199
|
.replace(/^_+|_+$/g, "");
|
|
127
200
|
return deriveSecretEncryptionKey(
|
|
128
|
-
appScopedEncryptionKey() ||
|
|
201
|
+
appScopedEncryptionKey() || genericEncryptionKeyMaterial(),
|
|
129
202
|
"[agent-native/secrets] Refusing to start in production without an encryption key. " +
|
|
130
203
|
`Set ${appName ? `${appName}_SECRETS_ENCRYPTION_KEY, ` : ""}SECRETS_ENCRYPTION_KEY, or BETTER_AUTH_SECRET in the deploy environment. ` +
|
|
131
204
|
"The previous CWD-derived fallback was effectively static (e.g. `/var/task` on Lambda), " +
|
|
@@ -138,30 +211,36 @@ export function getSecretEncryptionKey(): Buffer {
|
|
|
138
211
|
|
|
139
212
|
/**
|
|
140
213
|
* Derive the preferred workspace-shared key used by `app_secrets` rows.
|
|
141
|
-
* Unlike generic column-level encryption, workspace vault data
|
|
142
|
-
* in sibling apps
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
214
|
+
* Unlike generic column-level encryption, workspace vault data must decrypt
|
|
215
|
+
* in sibling apps. An explicit `WORKSPACE_SECRETS_ENCRYPTION_KEY` wins without
|
|
216
|
+
* changing the key for app-local OAuth and credential ciphertext. The legacy
|
|
217
|
+
* shared `SECRETS_ENCRYPTION_KEY` remains supported; hosted workspaces
|
|
218
|
+
* otherwise derive material from their shared `A2A_SECRET` before considering
|
|
219
|
+
* app-local `BETTER_AUTH_SECRET` or app-scoped key fallbacks. Reads try every
|
|
220
|
+
* configured legacy/previous candidate and report when the ciphertext should
|
|
221
|
+
* be refreshed under the preferred key.
|
|
149
222
|
*/
|
|
150
223
|
export function getSharedSecretEncryptionKey(): Buffer {
|
|
151
224
|
return deriveSecretEncryptionKey(
|
|
152
|
-
|
|
225
|
+
preferredSharedEncryptionKeyMaterial(),
|
|
153
226
|
"[agent-native/secrets] Refusing to start in production without encryption key material for workspace secrets. " +
|
|
154
|
-
"Set
|
|
155
|
-
"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
"or rely on A2A_SECRET-derived material on hosted workspace deploys.",
|
|
227
|
+
"Set WORKSPACE_SECRETS_ENCRYPTION_KEY (preferred), SECRETS_ENCRYPTION_KEY, ensure a hosted workspace has A2A_SECRET, " +
|
|
228
|
+
"or set BETTER_AUTH_SECRET / an app-scoped " +
|
|
229
|
+
"*_SECRETS_ENCRYPTION_KEY compatibility fallback in the deploy environment.",
|
|
230
|
+
"[agent-native/secrets] Workspace encryption key not set — using app-scoped or machine-local fallback for workspace secrets. " +
|
|
231
|
+
"Set WORKSPACE_SECRETS_ENCRYPTION_KEY or rely on A2A_SECRET-derived material on hosted workspace deploys so sibling apps share vault rows.",
|
|
159
232
|
);
|
|
160
233
|
}
|
|
161
234
|
|
|
162
235
|
/** Whether this deployment has stable workspace-shared key material. */
|
|
163
236
|
export function hasSharedSecretEncryptionKeyMaterial(): boolean {
|
|
164
|
-
|
|
237
|
+
if (typeof process === "undefined") return false;
|
|
238
|
+
return Boolean(
|
|
239
|
+
workspaceSharedEncryptionKeyMaterial() ||
|
|
240
|
+
process.env.SECRETS_ENCRYPTION_KEY ||
|
|
241
|
+
a2aSharedEncryptionKeyMaterial() ||
|
|
242
|
+
process.env.BETTER_AUTH_SECRET,
|
|
243
|
+
);
|
|
165
244
|
}
|
|
166
245
|
|
|
167
246
|
function encryptWithKey(plaintext: string, key: Buffer): string {
|
|
@@ -212,7 +291,49 @@ export function encryptSharedSecretValue(plaintext: string): string {
|
|
|
212
291
|
|
|
213
292
|
/** Decrypt a workspace-shared `app_secrets` value. */
|
|
214
293
|
export function decryptSharedSecretValue(encrypted: string): string {
|
|
215
|
-
return
|
|
294
|
+
return decryptSharedSecretValueDetailed(encrypted).value;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export interface DecryptedSharedSecretValue {
|
|
298
|
+
value: string;
|
|
299
|
+
/**
|
|
300
|
+
* True when a legacy app-local candidate decrypted the value. Callers that
|
|
301
|
+
* own the row should opportunistically re-encrypt it with the preferred
|
|
302
|
+
* workspace key.
|
|
303
|
+
*/
|
|
304
|
+
needsReencrypt: boolean;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Decrypt workspace ciphertext with the preferred key first, then configured
|
|
309
|
+
* legacy candidates. The result never exposes which secret material matched.
|
|
310
|
+
*/
|
|
311
|
+
export function decryptSharedSecretValueDetailed(
|
|
312
|
+
encrypted: string,
|
|
313
|
+
): DecryptedSharedSecretValue {
|
|
314
|
+
const preferredKey = getSharedSecretEncryptionKey();
|
|
315
|
+
const candidateKeys = [preferredKey];
|
|
316
|
+
for (const material of sharedEncryptionKeyMaterials()) {
|
|
317
|
+
const candidate = hashSecretEncryptionKey(material);
|
|
318
|
+
if (!candidateKeys.some((existing) => existing.equals(candidate))) {
|
|
319
|
+
candidateKeys.push(candidate);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let lastError: unknown;
|
|
324
|
+
for (const candidate of candidateKeys) {
|
|
325
|
+
try {
|
|
326
|
+
return {
|
|
327
|
+
value: decryptWithKey(encrypted, candidate),
|
|
328
|
+
needsReencrypt: !candidate.equals(preferredKey),
|
|
329
|
+
};
|
|
330
|
+
} catch (error) {
|
|
331
|
+
lastError = error;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
throw lastError instanceof Error
|
|
335
|
+
? lastError
|
|
336
|
+
: new Error("Unable to decrypt workspace secret");
|
|
216
337
|
}
|
|
217
338
|
|
|
218
339
|
/**
|
package/src/secrets/storage.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Storage layer for the framework secrets registry.
|
|
3
3
|
*
|
|
4
4
|
* Values are encrypted at rest with AES-256-GCM. The workspace-shared
|
|
5
|
-
* encryption key
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* encryption key prefers `WORKSPACE_SECRETS_ENCRYPTION_KEY`, then the legacy
|
|
6
|
+
* combined `SECRETS_ENCRYPTION_KEY`, then the workspace-wide `A2A_SECRET`.
|
|
7
|
+
* A configured previous workspace key, `BETTER_AUTH_SECRET`, and app-scoped
|
|
8
|
+
* keys remain read fallbacks for rows written before sibling apps converged on
|
|
9
|
+
* the shared key. Successful fallback reads race-safely refresh the shared
|
|
10
|
+
* ciphertext.
|
|
11
11
|
*
|
|
12
12
|
* Secret values are NEVER logged and NEVER returned from any route handler.
|
|
13
13
|
*/
|
|
@@ -19,7 +19,7 @@ import { ensureColumnExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
|
19
19
|
import {
|
|
20
20
|
encryptSecretValue as encryptLegacyValue,
|
|
21
21
|
encryptSharedSecretValue as encryptValue,
|
|
22
|
-
|
|
22
|
+
decryptSharedSecretValueDetailed as decryptValue,
|
|
23
23
|
decryptSecretValue as decryptLegacyValue,
|
|
24
24
|
hasSharedSecretEncryptionKeyMaterial,
|
|
25
25
|
} from "./crypto.js";
|
|
@@ -193,8 +193,9 @@ export async function writeAppSecret(args: WriteSecretArgs): Promise<string> {
|
|
|
193
193
|
// pointing at data that's no longer current. Siblings then get an honest
|
|
194
194
|
// cache miss (falling back to the legacy column or reporting missing) until
|
|
195
195
|
// the owning app's next read repopulates shared_encrypted_value via
|
|
196
|
-
// `populateSharedAppSecret`, which
|
|
197
|
-
//
|
|
196
|
+
// `populateSharedAppSecret`, which fills a NULL column or compare-and-swap
|
|
197
|
+
// replaces the exact legacy ciphertext it just decrypted. A temporary miss
|
|
198
|
+
// is safer than serving rotated-away plaintext.
|
|
198
199
|
const upsertSql = `INSERT INTO app_secrets (id, scope, scope_id, key, encrypted_value, shared_encrypted_value, description, url_allowlist, created_at, updated_at)
|
|
199
200
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
200
201
|
ON CONFLICT (scope, scope_id, key) DO UPDATE SET
|
|
@@ -253,8 +254,13 @@ interface DecryptedAppSecretValue {
|
|
|
253
254
|
value: string;
|
|
254
255
|
/** True when the app-scoped fallback decrypted encrypted_value. */
|
|
255
256
|
usedLegacyKey: boolean;
|
|
256
|
-
/** True when shared_encrypted_value
|
|
257
|
+
/** True when shared_encrypted_value needs to be created or refreshed. */
|
|
257
258
|
needsSharedCiphertext: boolean;
|
|
259
|
+
/**
|
|
260
|
+
* Existing ciphertext that must still match before a refresh. Null means the
|
|
261
|
+
* migration may only fill an empty shared column.
|
|
262
|
+
*/
|
|
263
|
+
sharedCiphertextToReplace?: string | null;
|
|
258
264
|
}
|
|
259
265
|
|
|
260
266
|
function decryptAppSecretValue(
|
|
@@ -263,10 +269,14 @@ function decryptAppSecretValue(
|
|
|
263
269
|
): DecryptedAppSecretValue {
|
|
264
270
|
if (sharedEncrypted) {
|
|
265
271
|
try {
|
|
272
|
+
const decrypted = decryptValue(sharedEncrypted);
|
|
266
273
|
return {
|
|
267
|
-
value:
|
|
274
|
+
value: decrypted.value,
|
|
268
275
|
usedLegacyKey: false,
|
|
269
|
-
needsSharedCiphertext:
|
|
276
|
+
needsSharedCiphertext: decrypted.needsReencrypt,
|
|
277
|
+
sharedCiphertextToReplace: decrypted.needsReencrypt
|
|
278
|
+
? sharedEncrypted
|
|
279
|
+
: undefined,
|
|
270
280
|
};
|
|
271
281
|
} catch {
|
|
272
282
|
// Fall through to the legacy column. A partially migrated row may have
|
|
@@ -274,30 +284,34 @@ function decryptAppSecretValue(
|
|
|
274
284
|
}
|
|
275
285
|
}
|
|
276
286
|
try {
|
|
287
|
+
const decrypted = decryptValue(encrypted);
|
|
277
288
|
return {
|
|
278
|
-
value:
|
|
289
|
+
value: decrypted.value,
|
|
279
290
|
usedLegacyKey: false,
|
|
280
291
|
needsSharedCiphertext: true,
|
|
292
|
+
sharedCiphertextToReplace: sharedEncrypted ?? null,
|
|
281
293
|
};
|
|
282
294
|
} catch {
|
|
283
295
|
return {
|
|
284
296
|
value: decryptLegacyValue(encrypted),
|
|
285
297
|
usedLegacyKey: true,
|
|
286
298
|
needsSharedCiphertext: true,
|
|
299
|
+
sharedCiphertextToReplace: sharedEncrypted ?? null,
|
|
287
300
|
};
|
|
288
301
|
}
|
|
289
302
|
}
|
|
290
303
|
|
|
291
304
|
/**
|
|
292
|
-
*
|
|
293
|
-
* intentionally best-effort: a read must still succeed if a deployment's
|
|
294
|
-
* role cannot update the row. The
|
|
295
|
-
*
|
|
296
|
-
*
|
|
305
|
+
* Create or refresh the shared column without touching encrypted_value. This
|
|
306
|
+
* is intentionally best-effort: a read must still succeed if a deployment's
|
|
307
|
+
* DB role cannot update the row. The compare-and-swap predicate prevents a
|
|
308
|
+
* concurrent writer from being overwritten, and leaving updated_at untouched
|
|
309
|
+
* preserves the row's user-visible ordering/metadata.
|
|
297
310
|
*/
|
|
298
311
|
async function populateSharedAppSecret(
|
|
299
312
|
id: unknown,
|
|
300
313
|
value: string,
|
|
314
|
+
sharedCiphertextToReplace: string | null,
|
|
301
315
|
): Promise<void> {
|
|
302
316
|
if (
|
|
303
317
|
id === undefined ||
|
|
@@ -307,10 +321,18 @@ async function populateSharedAppSecret(
|
|
|
307
321
|
return;
|
|
308
322
|
}
|
|
309
323
|
try {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
324
|
+
const encrypted = encryptValue(value);
|
|
325
|
+
if (sharedCiphertextToReplace === null) {
|
|
326
|
+
await getDbExec().execute({
|
|
327
|
+
sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value IS NULL`,
|
|
328
|
+
args: [encrypted, id],
|
|
329
|
+
});
|
|
330
|
+
} else {
|
|
331
|
+
await getDbExec().execute({
|
|
332
|
+
sql: `UPDATE app_secrets SET shared_encrypted_value = ? WHERE id = ? AND shared_encrypted_value = ?`,
|
|
333
|
+
args: [encrypted, id, sharedCiphertextToReplace],
|
|
334
|
+
});
|
|
335
|
+
}
|
|
314
336
|
} catch {
|
|
315
337
|
// Migration is opportunistic. Preserve the successful read if the update
|
|
316
338
|
// is unavailable due to a read-only role, transient DB failure, or race.
|
|
@@ -371,7 +393,11 @@ export async function readAppSecret(
|
|
|
371
393
|
rows[0].shared_encrypted_value as string | null,
|
|
372
394
|
);
|
|
373
395
|
if (decrypted.needsSharedCiphertext) {
|
|
374
|
-
await populateSharedAppSecret(
|
|
396
|
+
await populateSharedAppSecret(
|
|
397
|
+
rows[0].id,
|
|
398
|
+
decrypted.value,
|
|
399
|
+
decrypted.sharedCiphertextToReplace ?? null,
|
|
400
|
+
);
|
|
375
401
|
}
|
|
376
402
|
return {
|
|
377
403
|
value: decrypted.value,
|
|
@@ -410,7 +436,11 @@ export async function readAppSecrets(args: {
|
|
|
410
436
|
row.shared_encrypted_value as string | null,
|
|
411
437
|
);
|
|
412
438
|
if (decrypted.needsSharedCiphertext) {
|
|
413
|
-
await populateSharedAppSecret(
|
|
439
|
+
await populateSharedAppSecret(
|
|
440
|
+
row.id,
|
|
441
|
+
decrypted.value,
|
|
442
|
+
decrypted.sharedCiphertextToReplace ?? null,
|
|
443
|
+
);
|
|
414
444
|
}
|
|
415
445
|
results.set(key, {
|
|
416
446
|
value: decrypted.value,
|
|
@@ -473,7 +503,11 @@ export async function readAppSecretMeta(
|
|
|
473
503
|
row.shared_encrypted_value as string | null,
|
|
474
504
|
);
|
|
475
505
|
if (decrypted.needsSharedCiphertext) {
|
|
476
|
-
await populateSharedAppSecret(
|
|
506
|
+
await populateSharedAppSecret(
|
|
507
|
+
row.id,
|
|
508
|
+
decrypted.value,
|
|
509
|
+
decrypted.sharedCiphertextToReplace ?? null,
|
|
510
|
+
);
|
|
477
511
|
}
|
|
478
512
|
last4Value = last4(decrypted.value);
|
|
479
513
|
} catch {
|
|
@@ -516,7 +550,11 @@ export async function listAppSecretsForScope(
|
|
|
516
550
|
row.shared_encrypted_value as string | null,
|
|
517
551
|
);
|
|
518
552
|
if (decrypted.needsSharedCiphertext) {
|
|
519
|
-
await populateSharedAppSecret(
|
|
553
|
+
await populateSharedAppSecret(
|
|
554
|
+
row.id,
|
|
555
|
+
decrypted.value,
|
|
556
|
+
decrypted.sharedCiphertextToReplace ?? null,
|
|
557
|
+
);
|
|
520
558
|
}
|
|
521
559
|
last4Value = last4(decrypted.value);
|
|
522
560
|
} catch {
|
|
@@ -130,7 +130,7 @@ export function formatCapabilitySummary(
|
|
|
130
130
|
"",
|
|
131
131
|
lines.join("\n\n"),
|
|
132
132
|
"",
|
|
133
|
-
"Delegate with call-agent
|
|
133
|
+
"Delegate with call-agent using a natural-language message by default. The receiving app owns interpretation and its local tools. Use action + input only for an exact bounded read with a fully known schema, never as a workaround for failed delegation. Prefer the app that owns the data over rebuilding its capability here.",
|
|
134
134
|
]
|
|
135
135
|
.filter(Boolean)
|
|
136
136
|
.join("\n");
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
filterInitialEngineTools,
|
|
13
13
|
resolveAgentRequestReasoningEffort,
|
|
14
14
|
type ActionEntry,
|
|
15
|
+
type AgentLoopOutcome,
|
|
15
16
|
} from "../../agent/production-agent.js";
|
|
16
17
|
import { runAgentLoopDirectWithSoftTimeout } from "../../agent/run-loop-with-resume.js";
|
|
17
18
|
import type { AgentChatEvent } from "../../agent/types.js";
|
|
@@ -194,6 +195,10 @@ export function buildAuthenticatedAgentA2ASkills(
|
|
|
194
195
|
name,
|
|
195
196
|
description: entry.tool.description,
|
|
196
197
|
publicAgent: entry.publicAgent,
|
|
198
|
+
// Every action in this set is read-only by construction. Omitting the
|
|
199
|
+
// flag made discovery label all of them "(mutating)", which pushes a
|
|
200
|
+
// caller away from invoking them and back into open-ended delegation.
|
|
201
|
+
readOnly: true,
|
|
197
202
|
// Naming an action without its parameters is what makes a caller invoke
|
|
198
203
|
// it with `{}` and fail on a required field.
|
|
199
204
|
...(entry.tool.parameters
|
|
@@ -230,9 +235,14 @@ export function resolveArtifactBaseUrl(
|
|
|
230
235
|
export function assembleA2AFinalResponse(
|
|
231
236
|
events: readonly AgentChatEvent[],
|
|
232
237
|
toolResults: readonly A2AToolResultSummary[],
|
|
233
|
-
options: A2AArtifactResponseOptions & {
|
|
238
|
+
options: A2AArtifactResponseOptions & {
|
|
239
|
+
event?: any;
|
|
240
|
+
outcome?: AgentLoopOutcome;
|
|
241
|
+
} = {},
|
|
234
242
|
): { responseText: string; finalText: string } {
|
|
235
|
-
const terminalError =
|
|
243
|
+
const terminalError = options.outcome
|
|
244
|
+
? terminalErrorFromOutcome(options.outcome)
|
|
245
|
+
: getA2ATerminalErrorEvent(events);
|
|
236
246
|
const responseText = collectFinalResponseTextFromAgentEvents(events, {
|
|
237
247
|
fallbackToPreToolText: !terminalError,
|
|
238
248
|
});
|
|
@@ -241,12 +251,48 @@ export function assembleA2AFinalResponse(
|
|
|
241
251
|
includeReferencedArtifacts: true,
|
|
242
252
|
includePersistedArtifactMarker: true,
|
|
243
253
|
});
|
|
244
|
-
if (terminalError
|
|
245
|
-
|
|
254
|
+
if (terminalError) {
|
|
255
|
+
const partialResult = finalText.trim()
|
|
256
|
+
? `\n\nPartial verified results before the failure:\n${finalText.trim()}`
|
|
257
|
+
: "";
|
|
258
|
+
throw new Error(formatA2ATerminalError(terminalError) + partialResult);
|
|
259
|
+
}
|
|
260
|
+
if (!finalText.trim()) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
"Agent completed without a response or verified artifact.\ncode: empty_agent_response",
|
|
263
|
+
);
|
|
246
264
|
}
|
|
247
265
|
return { responseText, finalText };
|
|
248
266
|
}
|
|
249
267
|
|
|
268
|
+
function terminalErrorFromOutcome(
|
|
269
|
+
outcome: AgentLoopOutcome,
|
|
270
|
+
): Extract<AgentChatEvent, { type: "error" }> | null {
|
|
271
|
+
if (outcome.state === "completed") return null;
|
|
272
|
+
if (outcome.state === "failed") {
|
|
273
|
+
return {
|
|
274
|
+
type: "error",
|
|
275
|
+
error: outcome.message,
|
|
276
|
+
errorCode: outcome.code,
|
|
277
|
+
recoverable: outcome.retryable,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (outcome.state === "input_required") {
|
|
281
|
+
return {
|
|
282
|
+
type: "error",
|
|
283
|
+
error: outcome.message,
|
|
284
|
+
errorCode: outcome.code,
|
|
285
|
+
recoverable: true,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
type: "error",
|
|
290
|
+
error: outcome.message ?? "Agent run was canceled.",
|
|
291
|
+
errorCode: "canceled",
|
|
292
|
+
recoverable: false,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
250
296
|
function getA2ATerminalErrorEvent(
|
|
251
297
|
events: readonly AgentChatEvent[],
|
|
252
298
|
): Extract<AgentChatEvent, { type: "error" }> | null {
|
|
@@ -255,6 +301,23 @@ function getA2ATerminalErrorEvent(
|
|
|
255
301
|
if (event.type === "clear") continue;
|
|
256
302
|
if (event.type === "done") return null;
|
|
257
303
|
if (event.type === "error") return event;
|
|
304
|
+
if (event.type === "tripwire") {
|
|
305
|
+
return {
|
|
306
|
+
type: "error",
|
|
307
|
+
error: event.reason || "Agent stopped at a delegated-run guardrail.",
|
|
308
|
+
errorCode: event.processor ? `tripwire:${event.processor}` : "tripwire",
|
|
309
|
+
recoverable: true,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (event.type === "loop_limit") {
|
|
313
|
+
return {
|
|
314
|
+
type: "error",
|
|
315
|
+
error:
|
|
316
|
+
"Agent stopped before finishing at the delegated-run step limit.",
|
|
317
|
+
errorCode: "loop_limit",
|
|
318
|
+
recoverable: true,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
258
321
|
if (event.type === "auto_continue") {
|
|
259
322
|
return {
|
|
260
323
|
type: "error",
|
|
@@ -280,6 +343,29 @@ function formatA2ATerminalError(
|
|
|
280
343
|
|
|
281
344
|
type A2AAgentLoopRunner = typeof runAgentLoopDirectWithSoftTimeout;
|
|
282
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Delegated turns should do specialist work with the receiver's own tools,
|
|
348
|
+
* but must return a bounded caller-ready result. Interactive defaults allow
|
|
349
|
+
* extremely deep work (400 iterations / 20M cumulative input tokens), which
|
|
350
|
+
* made healthy Slides -> Analytics calls take 4-13 minutes and consume up to
|
|
351
|
+
* millions of input tokens as tool results accumulated. These defaults leave
|
|
352
|
+
* ample room for multi-source analysis while failing before a delegated turn
|
|
353
|
+
* can silently become an unbounded research session.
|
|
354
|
+
*/
|
|
355
|
+
export const DEFAULT_DELEGATED_MAX_ITERATIONS = 80;
|
|
356
|
+
export const DEFAULT_DELEGATED_MAX_RUN_INPUT_TOKENS = 750_000;
|
|
357
|
+
export const DEFAULT_DELEGATED_MAX_TOOL_RESULT_CHARS = 20_000;
|
|
358
|
+
|
|
359
|
+
export const DELEGATED_AGENT_EXECUTION_CONTRACT = `
|
|
360
|
+
<delegated-agent-contract>
|
|
361
|
+
This request was delegated by another app. You are the specialist owner of the work.
|
|
362
|
+
- Interpret the caller's natural-language objective using your own instructions, skills, data dictionary, credentials, and tools. Choose providers, schemas, queries, and joins here; never ask the caller to invent SQL or source-specific implementation details for you.
|
|
363
|
+
- Finish the objective autonomously in this delegated turn whenever a safe, reasonable default exists. Do not bounce the work back with UI-navigation requests, implementation questions, or intermediate status. Ask for input only when authorization, missing credentials, or a consequential user choice truly blocks progress.
|
|
364
|
+
- When evidence is incomplete, return the best grounded partial answer with explicit coverage gaps instead of refusing or asking the caller to choose your source, filter, dashboard, or workflow.
|
|
365
|
+
- Minimize round trips. Filter, join, aggregate, paginate, stage, and reduce large datasets inside your own tools rather than returning raw records or transcripts.
|
|
366
|
+
- Return a concise caller-ready result with the answer, source and coverage details, relevant counts or IDs, caveats/partial gaps, and exact artifact URLs. Do not return tool transcripts or large raw payloads.
|
|
367
|
+
</delegated-agent-contract>`;
|
|
368
|
+
|
|
283
369
|
export interface DelegatedAgentLoopTelemetry {
|
|
284
370
|
runId: string;
|
|
285
371
|
threadId: string | null;
|
|
@@ -302,14 +388,19 @@ async function runDelegatedAgentLoop(
|
|
|
302
388
|
runOptions: Parameters<A2AAgentLoopRunner>[0],
|
|
303
389
|
pluginOptions: Pick<
|
|
304
390
|
AgentChatPluginOptions,
|
|
305
|
-
"finalResponseGuard" | "runSoftTimeoutMs"
|
|
391
|
+
"delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs"
|
|
306
392
|
>,
|
|
307
393
|
timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
|
|
308
394
|
options: DelegatedAgentLoopOptions,
|
|
309
395
|
) {
|
|
310
396
|
const runner = options.runner ?? runAgentLoopDirectWithSoftTimeout;
|
|
397
|
+
const policy = pluginOptions.delegatedRunPolicy;
|
|
398
|
+
const configuredHardToolResultCap = runOptions.toolLimits?.hardMaxResultChars;
|
|
399
|
+
const delegatedHardToolResultCap =
|
|
400
|
+
policy?.maxToolResultChars ?? DEFAULT_DELEGATED_MAX_TOOL_RESULT_CHARS;
|
|
311
401
|
const resolvedRunOptions = {
|
|
312
402
|
...runOptions,
|
|
403
|
+
systemPrompt: runOptions.systemPrompt + DELEGATED_AGENT_EXECUTION_CONTRACT,
|
|
313
404
|
// Delegated runs resolve their own model and do not pass through the
|
|
314
405
|
// interactive request handler's output-token setup. Use the same
|
|
315
406
|
// model-aware headroom here so reasoning models (notably GPT-5.x) do
|
|
@@ -321,6 +412,21 @@ async function runDelegatedAgentLoop(
|
|
|
321
412
|
reasoningEffort:
|
|
322
413
|
runOptions.reasoningEffort ??
|
|
323
414
|
resolveAgentRequestReasoningEffort({ model: runOptions.model }),
|
|
415
|
+
maxIterations:
|
|
416
|
+
runOptions.maxIterations ??
|
|
417
|
+
policy?.maxIterations ??
|
|
418
|
+
DEFAULT_DELEGATED_MAX_ITERATIONS,
|
|
419
|
+
maxRunInputTokens:
|
|
420
|
+
runOptions.maxRunInputTokens ??
|
|
421
|
+
policy?.maxRunInputTokens ??
|
|
422
|
+
DEFAULT_DELEGATED_MAX_RUN_INPUT_TOKENS,
|
|
423
|
+
toolLimits: {
|
|
424
|
+
...(runOptions.toolLimits ?? {}),
|
|
425
|
+
hardMaxResultChars:
|
|
426
|
+
typeof configuredHardToolResultCap === "number"
|
|
427
|
+
? Math.min(configuredHardToolResultCap, delegatedHardToolResultCap)
|
|
428
|
+
: delegatedHardToolResultCap,
|
|
429
|
+
},
|
|
324
430
|
finalResponseGuard: pluginOptions.finalResponseGuard,
|
|
325
431
|
};
|
|
326
432
|
const execute = (loopOptions = resolvedRunOptions) =>
|
|
@@ -369,7 +475,7 @@ export function runA2AAgentLoop(
|
|
|
369
475
|
runOptions: Parameters<A2AAgentLoopRunner>[0],
|
|
370
476
|
pluginOptions: Pick<
|
|
371
477
|
AgentChatPluginOptions,
|
|
372
|
-
"finalResponseGuard" | "runSoftTimeoutMs"
|
|
478
|
+
"delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs"
|
|
373
479
|
>,
|
|
374
480
|
timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
|
|
375
481
|
options: DelegatedAgentLoopOptions = {},
|
|
@@ -391,7 +497,7 @@ export function runMCPAgentLoop(
|
|
|
391
497
|
runOptions: Parameters<A2AAgentLoopRunner>[0],
|
|
392
498
|
pluginOptions: Pick<
|
|
393
499
|
AgentChatPluginOptions,
|
|
394
|
-
"finalResponseGuard" | "runSoftTimeoutMs"
|
|
500
|
+
"delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs"
|
|
395
501
|
>,
|
|
396
502
|
timeoutOptions: Parameters<A2AAgentLoopRunner>[2],
|
|
397
503
|
options: DelegatedAgentLoopOptions = {},
|
|
@@ -152,6 +152,8 @@ Prefer \`web-request\` for simple API calls and static pages. Use browser automa
|
|
|
152
152
|
|
|
153
153
|
The \`call-agent\` tool sends a message to a DIFFERENT, separately-deployed app's agent (A2A protocol). It is **not** for calling actions within the current app.
|
|
154
154
|
|
|
155
|
+
Use a natural-language \`message\` by default. The receiving app owns interpretation and runs with its own instructions, skills, connected sources, data dictionary, and tools. Do not choose its provider, schema, query, joins, or SQL for it. A direct \`action\` + \`input\` call is only for an exact bounded read whose complete contract is already known; it is never a workaround for unreliable delegation.
|
|
156
|
+
|
|
155
157
|
**NEVER use \`call-agent\` to:**
|
|
156
158
|
- Call your own app by name
|
|
157
159
|
- Perform tasks you can accomplish with your own registered tools
|