@agent-native/core 0.149.1 → 0.149.3
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 +1 -1
- package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/analytics/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/analytics/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/analytics/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/analytics/.agents/skills/creative-context/SKILL.md +4 -2
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/analytics/AGENTS.md +25 -32
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
- package/corpus/templates/analytics/app/global.css +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +37 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
- package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
- package/corpus/templates/assets/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/assets/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/assets/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/assets/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/assets/.agents/skills/creative-context/SKILL.md +4 -2
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/assets/AGENTS.md +6 -9
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/assets/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
- package/corpus/templates/brain/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/brain/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/brain/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/brain/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/brain/AGENTS.md +5 -12
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/brain/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
- package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/calendar/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/calendar/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/calendar/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/calendar/AGENTS.md +8 -9
- package/corpus/templates/chat/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/chat/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/chat/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/chat/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/chat/AGENTS.md +32 -80
- package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/clips/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/clips/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/clips/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/clips/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/clips/AGENTS.md +25 -31
- package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
- package/corpus/templates/clips/desktop/src/app.tsx +274 -137
- package/corpus/templates/clips/desktop/src/styles.css +35 -0
- package/corpus/templates/content/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/content/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/content/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/content/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/content/.agents/skills/creative-context/SKILL.md +4 -2
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/content/AGENTS.md +5 -9
- package/corpus/templates/content/app/global.css +1 -0
- package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/crm/AGENTS.md +7 -11
- package/corpus/templates/crm/app/global.css +1 -1
- package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/crm/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
- package/corpus/templates/design/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/design/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/design/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/design/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/design/.agents/skills/creative-context/SKILL.md +4 -2
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +70 -3
- package/corpus/templates/design/AGENTS.md +13 -21
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +12 -0
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +19 -2
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +115 -7
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +82 -7
- package/corpus/templates/design/app/components/design/design-trace.ts +120 -0
- package/corpus/templates/design/app/components/design/dnd-debug.ts +2 -1
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +40 -26
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +4 -1
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +12 -3
- package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +4 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +7 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +3 -2
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +2 -2
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +3 -0
- package/corpus/templates/design/app/i18n-data.ts +1 -0
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +5 -5
- package/corpus/templates/design/app/pages/DesignSystems.tsx +3 -2
- package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +100 -13
- package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/data-operations.ts +14 -0
- package/corpus/templates/design/changelog/2026-08-10-marquee-selecting-layers-far-down-a-tall-screen-now-selects-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-10-the-frame-tool-draws-a-plain-frame-by-default-with-screen-av.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +27 -32
- package/corpus/templates/design/playwright.config.ts +3 -5
- package/corpus/templates/design/shared/breakpoint-media.ts +9 -2
- package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/dispatch/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/dispatch/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/dispatch/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/dispatch/AGENTS.md +9 -16
- package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/factory/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/factory/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/factory/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/factory/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/factory/AGENTS.md +10 -24
- package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/forms/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/forms/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/forms/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/forms/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/forms/AGENTS.md +11 -17
- package/corpus/templates/forms/app/global.css +1 -0
- package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-11-full-page-chat-composers-stay-at-a-focused-750px-width.md +6 -0
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/macros/AGENTS.md +8 -8
- package/corpus/templates/mail/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/mail/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/mail/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/mail/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/mail/AGENTS.md +5 -6
- package/corpus/templates/plan/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/plan/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/plan/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/plan/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/plan/AGENTS.md +5 -14
- package/corpus/templates/plan/app/global.css +1 -0
- package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/changelog/2026-08-11-plan-no-longer-reloads-when-runtime-state-changes.md +6 -0
- package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
- package/corpus/templates/plan/vite.config.ts +5 -0
- package/corpus/templates/slides/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/slides/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/slides/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -2
- package/corpus/templates/slides/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/slides/.agents/skills/creative-context/SKILL.md +6 -3
- package/corpus/templates/slides/.agents/skills/deck-management/SKILL.md +6 -1
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +3 -2
- package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +3 -0
- package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +3 -3
- package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/slides/AGENTS.md +33 -35
- package/corpus/templates/slides/actions/add-deck.ts +2 -0
- package/corpus/templates/slides/actions/add-slide.ts +15 -1
- package/corpus/templates/slides/actions/create-deck.ts +18 -6
- package/corpus/templates/slides/actions/get-deck-reference-context.ts +2 -2
- package/corpus/templates/slides/actions/get-deck.ts +83 -7
- package/corpus/templates/slides/actions/import-file.ts +2 -1
- package/corpus/templates/slides/actions/patch-deck.ts +165 -9
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/actions/update-slide.ts +1 -1
- package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +2 -2
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -23
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +2 -2
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +6 -6
- package/corpus/templates/slides/app/components/editor/AnimationsPanel.tsx +3 -3
- package/corpus/templates/slides/app/components/editor/DeckEditorSkeleton.tsx +4 -4
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +432 -668
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +296 -259
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +241 -116
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +42 -14
- package/corpus/templates/slides/app/components/editor/SpeakerNotesPanel.tsx +6 -12
- package/corpus/templates/slides/app/components/editor/editor-command-model.ts +35 -0
- package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +8 -10
- package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
- package/corpus/templates/slides/app/context/DeckContext.tsx +2 -1
- package/corpus/templates/slides/app/global.css +76 -1
- package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +2 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +52 -23
- package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +104 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +17 -33
- package/corpus/templates/slides/app/pages/DesignSystems.tsx +2 -2
- package/corpus/templates/slides/app/pages/Index.tsx +315 -151
- package/corpus/templates/slides/app/root.tsx +43 -0
- package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-faster-slides-chat-deck-reads.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-chat-can-read-one-slide-without-loading-the-full-deck.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-clearly-indicate-when-an-ai-agent-is-editing-alongsid.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/lib/validate-slide-animations.ts +163 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +8 -5
- package/corpus/templates/slides/shared/api.ts +47 -15
- package/corpus/templates/slides/shared/deck-title.ts +135 -0
- package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +54 -488
- package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +136 -0
- package/corpus/templates/tasks/.agents/skills/actions/references/examples.md +94 -0
- package/corpus/templates/tasks/.agents/skills/actions/references/provider-apis.md +95 -0
- package/corpus/templates/tasks/.agents/skills/agent-watchdog/SKILL.md +1 -0
- package/corpus/templates/tasks/.agents/skills/create-skill/SKILL.md +9 -35
- package/corpus/templates/tasks/.agents/skills/efficient-fable/SKILL.md +1 -0
- package/corpus/templates/tasks/.agents/skills/efficient-frontier/SKILL.md +1 -0
- package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +4 -5
- package/corpus/templates/tasks/.agents/skills/plan-arbiter/SKILL.md +1 -0
- package/corpus/templates/tasks/.agents/skills/plow-ahead/SKILL.md +1 -0
- package/corpus/templates/tasks/.agents/skills/quick-recap/SKILL.md +1 -0
- package/corpus/templates/tasks/.agents/skills/read-the-damn-docs/SKILL.md +1 -0
- package/corpus/templates/tasks/.agents/skills/stay-within-limits/SKILL.md +1 -0
- package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/corpus/templates/tasks/AGENTS.md +5 -20
- package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
- package/dist/agent/engine/ai-sdk-engine.js +1 -1
- package/dist/agent/production-agent.d.ts +21 -1
- package/dist/agent/production-agent.js +505 -355
- package/dist/agent/run-manager.js +156 -14
- package/dist/agent/run-store.d.ts +11 -10
- package/dist/agent/run-store.js +17 -13
- package/dist/agent/tool-approval-store.d.ts +18 -0
- package/dist/agent/tool-approval-store.js +122 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +11 -2
- package/dist/client/AgentPanel.js +31 -6
- package/dist/client/AssistantChat.d.ts +9 -4
- package/dist/client/AssistantChat.js +221 -149
- package/dist/client/active-run-state.d.ts +9 -1
- package/dist/client/active-run-state.js +34 -3
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat-adapter.d.ts +8 -0
- package/dist/client/agent-chat-adapter.js +125 -28
- package/dist/client/agent-chat.d.ts +1 -1
- package/dist/client/chat/markdown-renderer.js +14 -2
- package/dist/client/chat/message-components.d.ts +13 -3
- package/dist/client/chat/message-components.js +57 -52
- package/dist/client/chat/run-recovery.d.ts +1 -0
- package/dist/client/chat/run-recovery.js +8 -0
- package/dist/client/chat/tool-call-display.d.ts +5 -5
- package/dist/client/chat/tool-call-display.js +9 -212
- package/dist/client/chat-first/apps-rail.js +3 -3
- package/dist/client/chat-first/browser-pane.js +4 -1
- package/dist/client/chat-first/chat-history.d.ts +3 -1
- package/dist/client/chat-first/chat-history.js +7 -4
- package/dist/client/chat-first/surface-tabs.d.ts +1 -1
- package/dist/client/chat-first/surface-tabs.js +52 -41
- package/dist/client/chat-first/types.d.ts +3 -0
- package/dist/client/chat-first.d.ts +10 -0
- package/dist/client/chat-first.js +37 -2
- package/dist/client/components/ui/message-scroller.js +4 -1
- package/dist/client/i18n.js +3 -1
- package/dist/client/resources/McpConnectionSuggestion.js +9 -1
- package/dist/client/sse-event-processor.d.ts +4 -2
- package/dist/client/sse-event-processor.js +46 -6
- package/dist/client/use-chat-models.js +156 -80
- package/dist/client/use-run-stuck-detection.d.ts +2 -0
- package/dist/client/use-run-stuck-detection.js +85 -9
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/integrations/adapters/slack.d.ts +0 -4
- package/dist/integrations/adapters/slack.js +6 -30
- package/dist/integrations/catalog.js +1 -1
- package/dist/integrations/pending-tasks-store.d.ts +0 -8
- package/dist/integrations/pending-tasks-store.js +0 -21
- package/dist/integrations/slack-manifest.d.ts +2 -2
- package/dist/integrations/slack-manifest.js +0 -3
- package/dist/integrations/webhook-handler.js +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/scripts/docs/source-search.js +2 -2
- package/dist/server/agent-chat/framework-prompts.js +7 -2
- package/dist/server/agent-chat/prompt-resources.js +2 -2
- package/dist/server/agent-chat/skill-frontmatter.d.ts +31 -2
- package/dist/server/agent-chat/skill-frontmatter.js +41 -15
- package/dist/server/agent-chat-plugin.js +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agents-bundle.d.ts +16 -19
- package/dist/server/agents-bundle.js +19 -35
- package/dist/server/auth.js +262 -31
- package/dist/server/better-auth-instance.d.ts +1 -0
- package/dist/server/better-auth-instance.js +17 -2
- package/dist/server/onboarding-html.js +56 -9
- package/dist/server/prompts/framework-core-compact.d.ts +6 -1
- package/dist/server/prompts/framework-core-compact.js +12 -9
- package/dist/server/prompts/framework-core.d.ts +6 -1
- package/dist/server/prompts/framework-core.js +11 -9
- package/dist/server/prompts/index.d.ts +1 -1
- package/dist/server/prompts/index.js +1 -1
- package/dist/server/prompts/shared-rules.d.ts +15 -11
- package/dist/server/prompts/shared-rules.js +17 -13
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/reasoning-effort.d.ts +8 -8
- package/dist/shared/reasoning-effort.js +10 -10
- package/dist/shared/streaming-text-smoothing.js +41 -11
- package/dist/styles/agent-native.css +19 -32
- package/dist/templates/chat/.agents/skills/actions/SKILL.md +54 -488
- package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +136 -0
- package/dist/templates/chat/.agents/skills/actions/references/examples.md +94 -0
- package/dist/templates/chat/.agents/skills/actions/references/provider-apis.md +95 -0
- package/dist/templates/chat/.agents/skills/create-skill/SKILL.md +9 -35
- package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +4 -5
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/dist/templates/chat/AGENTS.md +32 -80
- package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/dist/templates/default/.agents/skills/actions/SKILL.md +54 -488
- package/dist/templates/default/.agents/skills/actions/references/action-fields.md +136 -0
- package/dist/templates/default/.agents/skills/actions/references/examples.md +94 -0
- package/dist/templates/default/.agents/skills/actions/references/provider-apis.md +95 -0
- package/dist/templates/default/.agents/skills/create-skill/SKILL.md +9 -35
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +4 -5
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/dist/templates/headless/.agents/skills/actions/SKILL.md +54 -488
- package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +136 -0
- package/dist/templates/headless/.agents/skills/actions/references/examples.md +94 -0
- package/dist/templates/headless/.agents/skills/actions/references/provider-apis.md +95 -0
- package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +54 -488
- package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +136 -0
- package/dist/templates/workspace-core/.agents/skills/actions/references/examples.md +94 -0
- package/dist/templates/workspace-core/.agents/skills/actions/references/provider-apis.md +95 -0
- package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +1 -0
- package/dist/templates/workspace-core/.agents/skills/agent-page/SKILL.md +1 -0
- package/dist/templates/workspace-core/.agents/skills/create-skill/SKILL.md +9 -35
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +4 -5
- package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +21 -29
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/dist/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +7 -17
- package/dist/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +3 -3
- package/dist/templates/workspace-core/AGENTS.md +7 -16
- package/docs/content/messaging.mdx +6 -7
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/.agents/skills/actions/SKILL.md +54 -488
- package/src/templates/chat/.agents/skills/actions/references/action-fields.md +136 -0
- package/src/templates/chat/.agents/skills/actions/references/examples.md +94 -0
- package/src/templates/chat/.agents/skills/actions/references/provider-apis.md +95 -0
- package/src/templates/chat/.agents/skills/create-skill/SKILL.md +9 -35
- package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +4 -5
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/src/templates/chat/AGENTS.md +32 -80
- package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/src/templates/default/.agents/skills/actions/SKILL.md +54 -488
- package/src/templates/default/.agents/skills/actions/references/action-fields.md +136 -0
- package/src/templates/default/.agents/skills/actions/references/examples.md +94 -0
- package/src/templates/default/.agents/skills/actions/references/provider-apis.md +95 -0
- package/src/templates/default/.agents/skills/create-skill/SKILL.md +9 -35
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +4 -5
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/src/templates/headless/.agents/skills/actions/SKILL.md +54 -488
- package/src/templates/headless/.agents/skills/actions/references/action-fields.md +136 -0
- package/src/templates/headless/.agents/skills/actions/references/examples.md +94 -0
- package/src/templates/headless/.agents/skills/actions/references/provider-apis.md +95 -0
- package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +54 -488
- package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +136 -0
- package/src/templates/workspace-core/.agents/skills/actions/references/examples.md +94 -0
- package/src/templates/workspace-core/.agents/skills/actions/references/provider-apis.md +95 -0
- package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +1 -0
- package/src/templates/workspace-core/.agents/skills/agent-page/SKILL.md +1 -0
- package/src/templates/workspace-core/.agents/skills/create-skill/SKILL.md +9 -35
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +4 -5
- package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +21 -29
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +18 -31
- package/src/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +7 -17
- package/src/templates/workspace-core/.agents/skills/writing-agent-instructions/SKILL.md +3 -3
- package/src/templates/workspace-core/AGENTS.md +7 -16
|
@@ -13,7 +13,7 @@ import { extractMcpToolResultImages } from "../mcp-client/index.js";
|
|
|
13
13
|
import { isMcpToolAllowedForRequest } from "../mcp-client/visibility.js";
|
|
14
14
|
import { shouldInferSentimentForTurn } from "../observability/sentiment.js";
|
|
15
15
|
import { completeRun as completeProgressRun, startRun as startProgressRun, updateRunProgress, } from "../progress/registry.js";
|
|
16
|
-
import {
|
|
16
|
+
import { isRuntimeVisibleScope, parseSkillFrontmatter, } from "../server/agent-chat/skill-frontmatter.js";
|
|
17
17
|
import { canUseDeployCredentialFallbackForRequest, getProviderCredentialAuthFailure, readDeployCredentialEnv, } from "../server/credential-provider.js";
|
|
18
18
|
import { readBody } from "../server/h3-helpers.js";
|
|
19
19
|
import { getRequestRunContext, ensureRequestRunContext, getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
|
|
@@ -39,6 +39,7 @@ import { ProcessorChain, TripWire, toolCallsFromContent, } from "./processors.js
|
|
|
39
39
|
import { startRun, subscribeToRun, getActiveRunForThread, getActiveRunForThreadAsync, getRun, abortRun, abortRunDurably, abortTurnDurably, tryClaimRunSlot, isHostedRuntime, resolveRunSoftTimeoutMs, resolveRunToolTimeoutCeilingMs, } from "./run-manager.js";
|
|
40
40
|
import { writeLedgerEntry, readLedgerEntry, clearLedgerForThread, insertRun, insertRunEvent, isTurnAborted, markRunAborted, updateRunHeartbeat, updateRunStatusIfRunning, setRunError, setRunTerminalReason, claimBackgroundRun, readBackgroundRunClaim, recordRunDiagnostic, countRunsForTurn, RUN_DIAG_STAGE, UNCLAIMED_BACKGROUND_RUN_GRACE_MS, } from "./run-store.js";
|
|
41
41
|
import { buildCurrentTimeUserContext } from "./runtime-context.js";
|
|
42
|
+
import { consumeAgentToolApproval, createAgentToolApproval, } from "./tool-approval-store.js";
|
|
42
43
|
import { findCompletedJournalEntry, } from "./tool-call-journal.js";
|
|
43
44
|
import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
|
|
44
45
|
import { describeToolResultImages, extractAgentImagesFromActionResult, } from "./tool-result-images.js";
|
|
@@ -688,7 +689,7 @@ const VISIBLE_RETRY_THRESHOLD_MS = 10_000;
|
|
|
688
689
|
*/
|
|
689
690
|
const FOREGROUND_FIRST_MODEL_EVENT_TIMEOUT_MS = 25_000;
|
|
690
691
|
// Raised from 1 -> 2 now that each retry actually adapts (raises the token
|
|
691
|
-
// ceiling and steps
|
|
692
|
+
// ceiling and steps effort down a tier) instead of re-issuing the
|
|
692
693
|
// exact same doomed request twice.
|
|
693
694
|
const EMPTY_FINAL_RESPONSE_RETRY_LIMIT = 2;
|
|
694
695
|
const MAIN_CHAT_INTERNAL_CONTINUATION_LIMIT = 6;
|
|
@@ -1198,9 +1199,9 @@ function escapeReferenceAttribute(value) {
|
|
|
1198
1199
|
return value.replace(/&/g, "&").replace(/"/g, """);
|
|
1199
1200
|
}
|
|
1200
1201
|
function isRuntimeVisibleSkillContent(content) {
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
return scope
|
|
1202
|
+
// Route through the shared normalizer so an unrecognized `scope:` stays
|
|
1203
|
+
// invisible here too. A local `!== "dev"` check silently readmits it.
|
|
1204
|
+
return isRuntimeVisibleScope(parseSkillFrontmatter(content).scope);
|
|
1204
1205
|
}
|
|
1205
1206
|
export async function resolveSkillReferenceContent(ref) {
|
|
1206
1207
|
if (!ref.path && !ref.name)
|
|
@@ -2120,6 +2121,8 @@ export async function executeAgentToolCall(options) {
|
|
|
2120
2121
|
threadId: options.threadId,
|
|
2121
2122
|
turnId: options.turnId,
|
|
2122
2123
|
approvedToolCalls: options.approvedToolCalls,
|
|
2124
|
+
onApprovalRequired: options.onApprovalRequired,
|
|
2125
|
+
consumeApproval: options.consumeApproval,
|
|
2123
2126
|
});
|
|
2124
2127
|
}
|
|
2125
2128
|
catch (error) {
|
|
@@ -2307,6 +2310,51 @@ function stableStringify(value) {
|
|
|
2307
2310
|
export function toolCallCacheKey(toolName, input) {
|
|
2308
2311
|
return `${toolName}:${stableStringify(normalizeToolCallInputForHistory(input))}`;
|
|
2309
2312
|
}
|
|
2313
|
+
export function findApprovedStructuredToolCall(history, approvedToolCalls) {
|
|
2314
|
+
if (!Array.isArray(history) || !approvedToolCalls?.length)
|
|
2315
|
+
return undefined;
|
|
2316
|
+
const approved = new Set(approvedToolCalls);
|
|
2317
|
+
const calls = new Map();
|
|
2318
|
+
let match;
|
|
2319
|
+
for (const message of history) {
|
|
2320
|
+
if (message.role === "assistant") {
|
|
2321
|
+
for (const part of message.content) {
|
|
2322
|
+
if (part.type !== "tool-call")
|
|
2323
|
+
continue;
|
|
2324
|
+
const callId = typeof part.id === "string"
|
|
2325
|
+
? part.id
|
|
2326
|
+
: typeof part.toolCallId === "string"
|
|
2327
|
+
? part.toolCallId
|
|
2328
|
+
: "";
|
|
2329
|
+
const name = typeof part.name === "string"
|
|
2330
|
+
? part.name
|
|
2331
|
+
: typeof part.toolName === "string"
|
|
2332
|
+
? part.toolName
|
|
2333
|
+
: "";
|
|
2334
|
+
if (!callId || !name)
|
|
2335
|
+
continue;
|
|
2336
|
+
calls.set(callId, { name, input: part.input ?? part.args ?? {} });
|
|
2337
|
+
}
|
|
2338
|
+
continue;
|
|
2339
|
+
}
|
|
2340
|
+
for (const part of message.content) {
|
|
2341
|
+
if (part.type !== "tool-result")
|
|
2342
|
+
continue;
|
|
2343
|
+
const call = calls.get(part.toolCallId);
|
|
2344
|
+
if (!call || !approved.has(toolCallCacheKey(call.name, call.input))) {
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
2347
|
+
const result = part.content.toLowerCase();
|
|
2348
|
+
if (!result.includes("did not execute") &&
|
|
2349
|
+
!result.includes("awaiting human approval") &&
|
|
2350
|
+
!result.includes("waiting for your approval")) {
|
|
2351
|
+
continue;
|
|
2352
|
+
}
|
|
2353
|
+
match = { ...call, callId: part.toolCallId };
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
return match;
|
|
2357
|
+
}
|
|
2310
2358
|
const INTERRUPTED_TOOL_LEDGER_POLL_MS = process.env.NODE_ENV === "test" ? 0 : 2_000;
|
|
2311
2359
|
async function waitForInterruptedToolLedgerEntry(opts) {
|
|
2312
2360
|
const pollMs = INTERRUPTED_TOOL_LEDGER_POLL_MS;
|
|
@@ -3965,7 +4013,16 @@ export async function runAgentLoop(opts) {
|
|
|
3965
4013
|
const approvalKey = toolCallCacheKey(toolCall.name, toolCall.input);
|
|
3966
4014
|
// Consume a grant on its first exact match. A second identical call in
|
|
3967
4015
|
// the same continuation must request its own human approval.
|
|
3968
|
-
const
|
|
4016
|
+
const approvalBinding = {
|
|
4017
|
+
toolName: toolCall.name,
|
|
4018
|
+
input: toolCall.input,
|
|
4019
|
+
callId: toolCall.id,
|
|
4020
|
+
approvalKey,
|
|
4021
|
+
};
|
|
4022
|
+
const requestedApproval = approvedToolCallKeys.delete(approvalKey);
|
|
4023
|
+
const wasApproved = requestedApproval && opts.consumeApproval
|
|
4024
|
+
? await opts.consumeApproval(approvalBinding)
|
|
4025
|
+
: requestedApproval;
|
|
3969
4026
|
if (actionEntry.needsApproval && !wasApproved) {
|
|
3970
4027
|
let mustApprove = false;
|
|
3971
4028
|
try {
|
|
@@ -3989,6 +4046,7 @@ export async function runAgentLoop(opts) {
|
|
|
3989
4046
|
mustApprove = true;
|
|
3990
4047
|
}
|
|
3991
4048
|
if (mustApprove) {
|
|
4049
|
+
await opts.onApprovalRequired?.(approvalBinding);
|
|
3992
4050
|
send({
|
|
3993
4051
|
type: "tool_start",
|
|
3994
4052
|
id: toolCall.id,
|
|
@@ -4231,370 +4289,401 @@ export async function runAgentLoop(opts) {
|
|
|
4231
4289
|
tool: toolCall.name,
|
|
4232
4290
|
input: toolCall.input,
|
|
4233
4291
|
});
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
//
|
|
4290
|
-
//
|
|
4291
|
-
//
|
|
4292
|
-
|
|
4292
|
+
let toolDoneEmitted = false;
|
|
4293
|
+
const emitToolDone = (event) => {
|
|
4294
|
+
send(event);
|
|
4295
|
+
toolDoneEmitted = true;
|
|
4296
|
+
};
|
|
4297
|
+
// Every tool_start must have exactly one matching tool_done. Keeping the
|
|
4298
|
+
// whole post-start path inside this finally block turns unexpected
|
|
4299
|
+
// synchronous failures in validation, cache handling, or future branches
|
|
4300
|
+
// into an explicit tool error instead of leaving run-manager's in-flight
|
|
4301
|
+
// counter latched forever.
|
|
4302
|
+
try {
|
|
4303
|
+
const toolCallSchemaError = toolCallErrors.get(toolCall.id);
|
|
4304
|
+
if (toolCallSchemaError && !toolInputNormalized) {
|
|
4305
|
+
const result = finalizeToolErrorResult(toolInputSchemaErrorResult(toolCall.name, toolCallSchemaError.input, toolCallSchemaError.error, actionEntry?.tool.parameters));
|
|
4306
|
+
emitToolDone({
|
|
4307
|
+
type: "tool_done",
|
|
4308
|
+
id: toolCall.id,
|
|
4309
|
+
tool: toolCall.name,
|
|
4310
|
+
input: toolCall.input,
|
|
4311
|
+
result,
|
|
4312
|
+
isError: true,
|
|
4313
|
+
completedSideEffect: false,
|
|
4314
|
+
});
|
|
4315
|
+
recordToolResult(result, true);
|
|
4316
|
+
return {
|
|
4317
|
+
type: "tool-result",
|
|
4318
|
+
toolCallId: toolCall.id,
|
|
4319
|
+
toolName: toolCall.name,
|
|
4320
|
+
toolInput: wireToolInput,
|
|
4321
|
+
content: result,
|
|
4322
|
+
isError: true,
|
|
4323
|
+
};
|
|
4324
|
+
}
|
|
4325
|
+
const rawToolInputError = validateRawToolInput(actionEntry, toolCall.input);
|
|
4326
|
+
if (rawToolInputError) {
|
|
4327
|
+
const result = finalizeToolErrorResult(toolInputSchemaErrorResult(toolCall.name, toolCall.input, rawToolInputError, actionEntry.tool.parameters));
|
|
4328
|
+
emitToolDone({
|
|
4329
|
+
type: "tool_done",
|
|
4330
|
+
id: toolCall.id,
|
|
4331
|
+
tool: toolCall.name,
|
|
4332
|
+
input: toolCall.input,
|
|
4333
|
+
result,
|
|
4334
|
+
isError: true,
|
|
4335
|
+
completedSideEffect: false,
|
|
4336
|
+
});
|
|
4337
|
+
recordToolResult(result, true);
|
|
4338
|
+
return {
|
|
4339
|
+
type: "tool-result",
|
|
4340
|
+
toolCallId: toolCall.id,
|
|
4341
|
+
toolName: toolCall.name,
|
|
4342
|
+
toolInput: wireToolInput,
|
|
4343
|
+
content: result,
|
|
4344
|
+
isError: true,
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
// dedupe: false opts a read-only tool out of the guard entirely — the
|
|
4348
|
+
// cacheKey stays null so it never gets skipped-as-duplicate and never
|
|
4349
|
+
// populates the cache (see the success handler below, which also
|
|
4350
|
+
// leaves dedupe:false results uncached and un-cleared).
|
|
4351
|
+
const cacheKey = actionEntry.readOnly === true && actionEntry.dedupe !== false
|
|
4352
|
+
? toolCallCacheKey(toolCall.name, toolCall.input)
|
|
4353
|
+
: null;
|
|
4354
|
+
if (cacheKey && readOnlyToolResultCache.has(cacheKey)) {
|
|
4355
|
+
const previousResult = readOnlyToolResultCache.get(cacheKey) ?? "";
|
|
4356
|
+
// `contextMessages` (not `messages`) is what the model actually sees
|
|
4357
|
+
// this iteration — context-xray eviction/summarization and
|
|
4358
|
+
// observational-memory trimming can drop the earlier result from
|
|
4359
|
+
// view even though it's still cached here. Only strike-count the
|
|
4360
|
+
// repeat when the model could have looked back and found it itself.
|
|
4361
|
+
const visible = isCachedToolResultVisibleInContext(contextMessages, toolCall, previousResult);
|
|
4362
|
+
let result;
|
|
4363
|
+
if (visible) {
|
|
4364
|
+
const repeats = (duplicateReadOnlyToolCalls.get(cacheKey) ?? 0) + 1;
|
|
4365
|
+
duplicateReadOnlyToolCalls.set(cacheKey, repeats);
|
|
4366
|
+
result = visibleDuplicateReadOnlyToolResult(toolCall.name);
|
|
4367
|
+
if (repeats >= 3) {
|
|
4368
|
+
requestedActionStop ??= {
|
|
4369
|
+
message: "I stopped because the agent kept asking for the same read-only context it already had. Please send the request again if you want me to retry from a fresh turn.",
|
|
4370
|
+
errorCode: "duplicate_read_only_tool",
|
|
4371
|
+
};
|
|
4372
|
+
}
|
|
4373
|
+
}
|
|
4374
|
+
else {
|
|
4375
|
+
// The earlier result was trimmed out of the model's visible
|
|
4376
|
+
// context — this isn't a repetitive loop, the model legitimately
|
|
4377
|
+
// can't see the answer anymore. Re-serve it in full and don't
|
|
4378
|
+
// count a strike.
|
|
4379
|
+
duplicateReadOnlyToolCalls.set(cacheKey, 0);
|
|
4380
|
+
result = resurfacedDuplicateReadOnlyToolResult(toolCall.name, previousResult);
|
|
4381
|
+
}
|
|
4382
|
+
emitToolDone({
|
|
4383
|
+
type: "tool_done",
|
|
4384
|
+
id: toolCall.id,
|
|
4385
|
+
tool: toolCall.name,
|
|
4386
|
+
input: toolCall.input,
|
|
4387
|
+
result,
|
|
4388
|
+
completedSideEffect: false,
|
|
4389
|
+
});
|
|
4390
|
+
recordToolResult(result, false);
|
|
4391
|
+
return {
|
|
4392
|
+
type: "tool-result",
|
|
4393
|
+
toolCallId: toolCall.id,
|
|
4394
|
+
toolName: toolCall.name,
|
|
4395
|
+
toolInput: wireToolInput,
|
|
4396
|
+
content: result,
|
|
4397
|
+
};
|
|
4398
|
+
}
|
|
4399
|
+
if (opts.executionMode === "plan" &&
|
|
4400
|
+
!isPlanModeToolCallAllowed(toolCall.name, toolCall.input, actionEntry)) {
|
|
4401
|
+
const result = planModeBlockedMessage(toolCall.name);
|
|
4402
|
+
emitToolDone({
|
|
4403
|
+
type: "tool_done",
|
|
4404
|
+
id: toolCall.id,
|
|
4405
|
+
tool: toolCall.name,
|
|
4406
|
+
input: toolCall.input,
|
|
4407
|
+
result,
|
|
4408
|
+
isError: true,
|
|
4409
|
+
completedSideEffect: false,
|
|
4410
|
+
});
|
|
4411
|
+
recordToolResult(result, true);
|
|
4412
|
+
return {
|
|
4413
|
+
type: "tool-result",
|
|
4414
|
+
toolCallId: toolCall.id,
|
|
4415
|
+
toolName: toolCall.name,
|
|
4416
|
+
toolInput: wireToolInput,
|
|
4417
|
+
content: result,
|
|
4418
|
+
isError: true,
|
|
4419
|
+
};
|
|
4420
|
+
}
|
|
4293
4421
|
let result;
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4422
|
+
let isError = false;
|
|
4423
|
+
let mcpApp;
|
|
4424
|
+
let toolResultImages;
|
|
4425
|
+
try {
|
|
4426
|
+
// The run may have been aborted while we waited above for an
|
|
4427
|
+
// interrupted tool's ledger result (the wait can poll for minutes).
|
|
4428
|
+
// Re-check before invoking the action: starting it now would spawn a
|
|
4429
|
+
// fresh zombie execution — a duplicate side effect / double charge —
|
|
4430
|
+
// which the ledger-recovery path exists to prevent. The Promise.race
|
|
4431
|
+
// "Run aborted" leg below only rejects AFTER the action is invoked, so
|
|
4432
|
+
// it cannot guard this. Throw here instead, handled like any abort.
|
|
4433
|
+
if (signal.aborted) {
|
|
4434
|
+
throw new Error("Run aborted");
|
|
4435
|
+
}
|
|
4436
|
+
const timeoutSignal = AbortSignal.timeout(toolTimeoutMs);
|
|
4437
|
+
const actionUserEmail = opts.ownerEmail ?? getRequestUserEmail();
|
|
4438
|
+
const actionOrgId = opts.orgId ?? getRequestOrgId() ?? null;
|
|
4439
|
+
const actionContext = {
|
|
4440
|
+
send,
|
|
4441
|
+
userEmail: actionUserEmail ?? undefined,
|
|
4442
|
+
orgId: actionOrgId,
|
|
4443
|
+
appId: opts.appId,
|
|
4444
|
+
caller: opts.actionCaller ?? "tool",
|
|
4445
|
+
automation: opts.automation,
|
|
4446
|
+
networkProtocol: opts.networkProtocol,
|
|
4447
|
+
networkId: opts.networkId,
|
|
4448
|
+
networkPeer: opts.networkPeer,
|
|
4449
|
+
delegationDepth: opts.delegationDepth,
|
|
4450
|
+
visitedApps: opts.visitedApps,
|
|
4451
|
+
attachments: opts.attachments,
|
|
4452
|
+
signal,
|
|
4453
|
+
// Audit attribution: the action name + the agent thread/turn that
|
|
4454
|
+
// triggered this call, so a mutation can be traced to its run.
|
|
4455
|
+
actionName: toolCall.name,
|
|
4456
|
+
...(wasApproved ? { approvedToolCallKey: approvalKey } : {}),
|
|
4457
|
+
...(opts.threadId ? { threadId: opts.threadId } : {}),
|
|
4458
|
+
...(opts.runId ? { runId: opts.runId } : {}),
|
|
4459
|
+
...(opts.turnId ? { turnId: opts.turnId } : {}),
|
|
4460
|
+
};
|
|
4461
|
+
const requestContext = getRequestContext();
|
|
4462
|
+
const invokeAction = () => actionEntry.run(toolCall.input, actionContext);
|
|
4463
|
+
// Keep a reference to the action promise so we can attach a zombie-
|
|
4464
|
+
// detection continuation AFTER Promise.race abandons it on run abort.
|
|
4465
|
+
// The promise itself is not awaited here — Promise.race owns the await.
|
|
4466
|
+
const actionPromise = Promise.resolve(runWithRequestContext({
|
|
4467
|
+
...(requestContext ?? {}),
|
|
4468
|
+
...(actionUserEmail ? { userEmail: actionUserEmail } : {}),
|
|
4469
|
+
...(actionOrgId ? { orgId: actionOrgId } : {}),
|
|
4470
|
+
...(requestContext?.run ? { run: requestContext.run } : {}),
|
|
4471
|
+
}, invokeAction));
|
|
4472
|
+
// When the run is aborted (soft-timeout / user cancel) while this tool
|
|
4473
|
+
// call is in flight, Promise.race below will throw "Run aborted" and the
|
|
4474
|
+
// action's promise becomes a zombie — it keeps running but its result is
|
|
4475
|
+
// never returned to the loop. If the zombie eventually resolves, write
|
|
4476
|
+
// the result to the durable ledger keyed by (threadId, toolKey) so the
|
|
4477
|
+
// next continuation chunk can recover it instead of re-executing the
|
|
4478
|
+
// side effect.
|
|
4479
|
+
if (opts.threadId && !actionEntry.readOnly) {
|
|
4480
|
+
const ledgerThreadId = opts.threadId;
|
|
4481
|
+
const ledgerToolKey = toolCallCacheKey(toolCall.name, toolCall.input);
|
|
4482
|
+
actionPromise
|
|
4483
|
+
.then((zombieRaw) => {
|
|
4484
|
+
const zombieMcp = isMcpActionResult(zombieRaw)
|
|
4485
|
+
? zombieRaw
|
|
4486
|
+
: null;
|
|
4487
|
+
if (zombieMcp &&
|
|
4488
|
+
zombieMcp.raw &&
|
|
4489
|
+
typeof zombieMcp.raw === "object" &&
|
|
4490
|
+
zombieMcp.raw.isError === true) {
|
|
4491
|
+
return;
|
|
4492
|
+
}
|
|
4493
|
+
const zombieText = zombieMcp ? zombieMcp.text : zombieRaw;
|
|
4494
|
+
const zombieStr = typeof zombieText === "string"
|
|
4495
|
+
? zombieText
|
|
4496
|
+
: JSON.stringify(zombieText, null, 2);
|
|
4497
|
+
void writeLedgerEntry(ledgerThreadId, ledgerToolKey, zombieStr);
|
|
4498
|
+
})
|
|
4499
|
+
.catch(() => {
|
|
4500
|
+
// Action errored in the zombie — no result to ledger.
|
|
4501
|
+
});
|
|
4502
|
+
}
|
|
4503
|
+
const raw = await Promise.race([
|
|
4504
|
+
actionPromise,
|
|
4505
|
+
new Promise((_, reject) => {
|
|
4506
|
+
timeoutSignal.addEventListener("abort", () => reject(new Error(`Tool call timed out after ${toolTimeoutMs / 1000} seconds`)));
|
|
4507
|
+
}),
|
|
4508
|
+
// Stop waiting on the tool when the run itself is aborted (e.g. the
|
|
4509
|
+
// run-manager soft timeout, or a user cancel). Without this leg the
|
|
4510
|
+
// loop blocks on an in-flight tool for up to TOOL_TIMEOUT_MS after
|
|
4511
|
+
// the run signal has already fired.
|
|
4512
|
+
new Promise((_, reject) => {
|
|
4513
|
+
if (signal.aborted) {
|
|
4514
|
+
reject(new Error("Run aborted"));
|
|
4515
|
+
return;
|
|
4516
|
+
}
|
|
4517
|
+
signal.addEventListener("abort", () => reject(new Error("Run aborted")), { once: true });
|
|
4518
|
+
}),
|
|
4519
|
+
]);
|
|
4520
|
+
const mcpResult = isMcpActionResult(raw) ? raw : null;
|
|
4521
|
+
const rawForAgent = mcpResult ? mcpResult.text : raw;
|
|
4522
|
+
if (mcpResult &&
|
|
4523
|
+
mcpResult.raw &&
|
|
4524
|
+
typeof mcpResult.raw === "object" &&
|
|
4525
|
+
mcpResult.raw.isError === true) {
|
|
4526
|
+
isError = true;
|
|
4527
|
+
}
|
|
4528
|
+
mcpApp = mcpResult?.mcpApp;
|
|
4529
|
+
// Demo mode is browser-local presentation state. The agent and MCP
|
|
4530
|
+
// layers always receive the real, access-scoped tool result.
|
|
4531
|
+
let resultForAgent = rawForAgent;
|
|
4532
|
+
// Vision images for the model: MCP tools return standard `image`
|
|
4533
|
+
// content parts; first-party actions opt in via the well-known
|
|
4534
|
+
// `_agentImages` result field (stripped from the JSON the model
|
|
4535
|
+
// reads). The images array
|
|
4536
|
+
// never touches the ledger — only the compact text notes appended
|
|
4537
|
+
// below are persisted.
|
|
4538
|
+
let imageNotes = [];
|
|
4539
|
+
if (mcpResult) {
|
|
4540
|
+
const mcpImages = extractMcpToolResultImages(mcpResult.raw);
|
|
4541
|
+
if (mcpImages.length > 0)
|
|
4542
|
+
toolResultImages = mcpImages;
|
|
4543
|
+
}
|
|
4544
|
+
else {
|
|
4545
|
+
const extracted = extractAgentImagesFromActionResult(resultForAgent);
|
|
4546
|
+
resultForAgent = extracted.value;
|
|
4547
|
+
imageNotes = extracted.notes;
|
|
4548
|
+
if (extracted.images.length > 0) {
|
|
4549
|
+
toolResultImages = extracted.images;
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
if (toolResultImages) {
|
|
4553
|
+
imageNotes = [
|
|
4554
|
+
...describeToolResultImages(toolResultImages),
|
|
4555
|
+
...imageNotes,
|
|
4556
|
+
];
|
|
4557
|
+
}
|
|
4558
|
+
let resultStr = typeof resultForAgent === "string"
|
|
4559
|
+
? resultForAgent
|
|
4560
|
+
: JSON.stringify(resultForAgent, null, 2);
|
|
4561
|
+
if (resultStr.length > toolMaxResultChars) {
|
|
4562
|
+
const truncated = resultStr.slice(0, toolMaxResultChars);
|
|
4563
|
+
resultStr = `${truncated}\n\n...[truncated — full result was ${resultStr.length.toLocaleString()} chars; only first ${toolMaxResultChars.toLocaleString()} shown]`;
|
|
4564
|
+
}
|
|
4565
|
+
// Image notes go after truncation so they always survive into the
|
|
4566
|
+
// persisted result string (the durable record that an image existed).
|
|
4567
|
+
if (imageNotes.length > 0) {
|
|
4568
|
+
resultStr = `${resultStr}\n\n${imageNotes.join("\n")}`;
|
|
4569
|
+
}
|
|
4570
|
+
result = resultStr;
|
|
4571
|
+
if (toolCall.name === TOOL_SEARCH_ACTION_NAME && !isError) {
|
|
4572
|
+
const added = expandActiveTools(extractToolSearchResultNames(rawForAgent));
|
|
4573
|
+
if (added.length > 0) {
|
|
4574
|
+
result += `\n\nLoaded matching tool schemas for next step: ${added.join(", ")}`;
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
catch (err) {
|
|
4579
|
+
if (isAgentActionStopError(err)) {
|
|
4580
|
+
const message = sanitizeToolErrorValue(err.message) ||
|
|
4581
|
+
`Stopped after ${toolCall.name} failed.`;
|
|
4582
|
+
result = sanitizeToolErrorValue(err.toolResult || message);
|
|
4299
4583
|
requestedActionStop ??= {
|
|
4300
|
-
message
|
|
4301
|
-
errorCode:
|
|
4584
|
+
message,
|
|
4585
|
+
...(err.errorCode ? { errorCode: err.errorCode } : {}),
|
|
4302
4586
|
};
|
|
4303
4587
|
}
|
|
4588
|
+
else {
|
|
4589
|
+
const message = sanitizeToolErrorValue(err);
|
|
4590
|
+
result = `Error running ${toolCall.name}: ${message}${rateLimitRecoveryHint(message)}`;
|
|
4591
|
+
}
|
|
4592
|
+
isError = true;
|
|
4304
4593
|
}
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
// context — this isn't a repetitive loop, the model legitimately
|
|
4308
|
-
// can't see the answer anymore. Re-serve it in full and don't
|
|
4309
|
-
// count a strike.
|
|
4310
|
-
duplicateReadOnlyToolCalls.set(cacheKey, 0);
|
|
4311
|
-
result = resurfacedDuplicateReadOnlyToolResult(toolCall.name, previousResult);
|
|
4594
|
+
if (isError) {
|
|
4595
|
+
result = finalizeToolErrorResult(result);
|
|
4312
4596
|
}
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
}
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4597
|
+
// Side-channel warnings raised anywhere inside the action's call stack
|
|
4598
|
+
// (see `action-warnings.ts`). Drained here rather than in the success
|
|
4599
|
+
// branch so an action that warns and then throws still surfaces it, and
|
|
4600
|
+
// so nothing stays pending to bleed into a later tool's result.
|
|
4601
|
+
const agentWarnings = drainAgentWarnings();
|
|
4602
|
+
if (agentWarnings.length > 0) {
|
|
4603
|
+
result = `${result}\n\n${formatAgentWarningsForToolResult(agentWarnings)}`;
|
|
4604
|
+
}
|
|
4605
|
+
// Auto-refresh the UI after a successful mutating tool call. Any call
|
|
4606
|
+
// that isn't read-only — by its own per-call Plan-mode effect, else the
|
|
4607
|
+
// action's readOnly flag — is assumed to mutate. The client's useDbSync
|
|
4608
|
+
// listener sees a change event with source:"action" and invalidates
|
|
4609
|
+
// ["action"] queries so list-* / get-* refetch. This makes refresh after
|
|
4610
|
+
// agent writes reliable without the model needing to remember to call
|
|
4611
|
+
// `refresh-screen` itself.
|
|
4612
|
+
if (!isError) {
|
|
4613
|
+
try {
|
|
4614
|
+
const { actionCallIsReadOnly, notifyActionChangeInBackground } = await import("../server/action-change.js");
|
|
4615
|
+
if (!actionCallIsReadOnly(actionEntry, toolCall.input, false)) {
|
|
4616
|
+
const owner = opts.ownerEmail ?? getRequestUserEmail() ?? undefined;
|
|
4617
|
+
const orgId = opts.orgId ?? getRequestOrgId() ?? undefined;
|
|
4618
|
+
notifyActionChangeInBackground({
|
|
4619
|
+
actionName: toolCall.name,
|
|
4620
|
+
...(owner ? { owner } : {}),
|
|
4621
|
+
...(orgId ? { orgId } : {}),
|
|
4622
|
+
});
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
catch (error) {
|
|
4626
|
+
// poll module may be unavailable in non-server contexts — keep the
|
|
4627
|
+
// tool result, but make the failed background refresh observable.
|
|
4628
|
+
console.warn("Could not notify the action-change poller after a tool call", error);
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
emitToolDone({
|
|
4334
4632
|
type: "tool_done",
|
|
4335
4633
|
id: toolCall.id,
|
|
4336
4634
|
tool: toolCall.name,
|
|
4337
4635
|
input: toolCall.input,
|
|
4338
4636
|
result,
|
|
4339
|
-
isError: true,
|
|
4340
|
-
|
|
4637
|
+
...(isError ? { isError: true } : {}),
|
|
4638
|
+
...(isError
|
|
4639
|
+
? { completedSideEffect: false }
|
|
4640
|
+
: actionEntry.readOnly !== true
|
|
4641
|
+
? { completedSideEffect: true }
|
|
4642
|
+
: {}),
|
|
4643
|
+
...(mcpApp ? { mcpApp } : {}),
|
|
4644
|
+
...(actionEntry.chatUI ? { chatUI: actionEntry.chatUI } : {}),
|
|
4341
4645
|
});
|
|
4342
|
-
recordToolResult(result,
|
|
4646
|
+
recordToolResult(result, isError);
|
|
4647
|
+
if (!isError) {
|
|
4648
|
+
if (cacheKey) {
|
|
4649
|
+
readOnlyToolResultCache.set(cacheKey, result);
|
|
4650
|
+
}
|
|
4651
|
+
else if (actionEntry.readOnly !== true) {
|
|
4652
|
+
// A genuine write invalidates all cached reads. A dedupe:false
|
|
4653
|
+
// read-only tool also has a null cacheKey (see above) but must NOT
|
|
4654
|
+
// clear the cache — it isn't a write and other tools' cached reads
|
|
4655
|
+
// are still valid.
|
|
4656
|
+
readOnlyToolResultCache.clear();
|
|
4657
|
+
duplicateReadOnlyToolCalls.clear();
|
|
4658
|
+
}
|
|
4659
|
+
}
|
|
4343
4660
|
return {
|
|
4344
4661
|
type: "tool-result",
|
|
4345
4662
|
toolCallId: toolCall.id,
|
|
4346
4663
|
toolName: toolCall.name,
|
|
4347
4664
|
toolInput: wireToolInput,
|
|
4348
4665
|
content: result,
|
|
4349
|
-
isError:
|
|
4666
|
+
...(isError ? { isError } : {}),
|
|
4667
|
+
...(!isError && toolResultImages && toolResultImages.length > 0
|
|
4668
|
+
? { images: toolResultImages }
|
|
4669
|
+
: {}),
|
|
4350
4670
|
};
|
|
4351
4671
|
}
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
// it cannot guard this. Throw here instead, handled like any abort.
|
|
4364
|
-
if (signal.aborted) {
|
|
4365
|
-
throw new Error("Run aborted");
|
|
4366
|
-
}
|
|
4367
|
-
const timeoutSignal = AbortSignal.timeout(toolTimeoutMs);
|
|
4368
|
-
const actionUserEmail = opts.ownerEmail ?? getRequestUserEmail();
|
|
4369
|
-
const actionOrgId = opts.orgId ?? getRequestOrgId() ?? null;
|
|
4370
|
-
const actionContext = {
|
|
4371
|
-
send,
|
|
4372
|
-
userEmail: actionUserEmail ?? undefined,
|
|
4373
|
-
orgId: actionOrgId,
|
|
4374
|
-
appId: opts.appId,
|
|
4375
|
-
caller: opts.actionCaller ?? "tool",
|
|
4376
|
-
automation: opts.automation,
|
|
4377
|
-
networkProtocol: opts.networkProtocol,
|
|
4378
|
-
networkId: opts.networkId,
|
|
4379
|
-
networkPeer: opts.networkPeer,
|
|
4380
|
-
delegationDepth: opts.delegationDepth,
|
|
4381
|
-
visitedApps: opts.visitedApps,
|
|
4382
|
-
attachments: opts.attachments,
|
|
4383
|
-
signal,
|
|
4384
|
-
// Audit attribution: the action name + the agent thread/turn that
|
|
4385
|
-
// triggered this call, so a mutation can be traced to its run.
|
|
4386
|
-
actionName: toolCall.name,
|
|
4387
|
-
...(wasApproved ? { approvedToolCallKey: approvalKey } : {}),
|
|
4388
|
-
...(opts.threadId ? { threadId: opts.threadId } : {}),
|
|
4389
|
-
...(opts.runId ? { runId: opts.runId } : {}),
|
|
4390
|
-
...(opts.turnId ? { turnId: opts.turnId } : {}),
|
|
4391
|
-
};
|
|
4392
|
-
const requestContext = getRequestContext();
|
|
4393
|
-
const invokeAction = () => actionEntry.run(toolCall.input, actionContext);
|
|
4394
|
-
// Keep a reference to the action promise so we can attach a zombie-
|
|
4395
|
-
// detection continuation AFTER Promise.race abandons it on run abort.
|
|
4396
|
-
// The promise itself is not awaited here — Promise.race owns the await.
|
|
4397
|
-
const actionPromise = Promise.resolve(runWithRequestContext({
|
|
4398
|
-
...(requestContext ?? {}),
|
|
4399
|
-
...(actionUserEmail ? { userEmail: actionUserEmail } : {}),
|
|
4400
|
-
...(actionOrgId ? { orgId: actionOrgId } : {}),
|
|
4401
|
-
...(requestContext?.run ? { run: requestContext.run } : {}),
|
|
4402
|
-
}, invokeAction));
|
|
4403
|
-
// When the run is aborted (soft-timeout / user cancel) while this tool
|
|
4404
|
-
// call is in flight, Promise.race below will throw "Run aborted" and the
|
|
4405
|
-
// action's promise becomes a zombie — it keeps running but its result is
|
|
4406
|
-
// never returned to the loop. If the zombie eventually resolves, write
|
|
4407
|
-
// the result to the durable ledger keyed by (threadId, toolKey) so the
|
|
4408
|
-
// next continuation chunk can recover it instead of re-executing the
|
|
4409
|
-
// side effect.
|
|
4410
|
-
if (opts.threadId && !actionEntry.readOnly) {
|
|
4411
|
-
const ledgerThreadId = opts.threadId;
|
|
4412
|
-
const ledgerToolKey = toolCallCacheKey(toolCall.name, toolCall.input);
|
|
4413
|
-
actionPromise
|
|
4414
|
-
.then((zombieRaw) => {
|
|
4415
|
-
const zombieMcp = isMcpActionResult(zombieRaw) ? zombieRaw : null;
|
|
4416
|
-
if (zombieMcp &&
|
|
4417
|
-
zombieMcp.raw &&
|
|
4418
|
-
typeof zombieMcp.raw === "object" &&
|
|
4419
|
-
zombieMcp.raw.isError === true) {
|
|
4420
|
-
return;
|
|
4421
|
-
}
|
|
4422
|
-
const zombieText = zombieMcp ? zombieMcp.text : zombieRaw;
|
|
4423
|
-
const zombieStr = typeof zombieText === "string"
|
|
4424
|
-
? zombieText
|
|
4425
|
-
: JSON.stringify(zombieText, null, 2);
|
|
4426
|
-
void writeLedgerEntry(ledgerThreadId, ledgerToolKey, zombieStr);
|
|
4427
|
-
})
|
|
4428
|
-
.catch(() => {
|
|
4429
|
-
// Action errored in the zombie — no result to ledger.
|
|
4672
|
+
finally {
|
|
4673
|
+
if (!toolDoneEmitted) {
|
|
4674
|
+
const result = `Error running ${toolCall.name}: tool execution ended before returning a result.`;
|
|
4675
|
+
emitToolDone({
|
|
4676
|
+
type: "tool_done",
|
|
4677
|
+
id: toolCall.id,
|
|
4678
|
+
tool: toolCall.name,
|
|
4679
|
+
input: toolCall.input,
|
|
4680
|
+
result,
|
|
4681
|
+
isError: true,
|
|
4682
|
+
completedSideEffect: false,
|
|
4430
4683
|
});
|
|
4431
|
-
|
|
4432
|
-
const raw = await Promise.race([
|
|
4433
|
-
actionPromise,
|
|
4434
|
-
new Promise((_, reject) => {
|
|
4435
|
-
timeoutSignal.addEventListener("abort", () => reject(new Error(`Tool call timed out after ${toolTimeoutMs / 1000} seconds`)));
|
|
4436
|
-
}),
|
|
4437
|
-
// Stop waiting on the tool when the run itself is aborted (e.g. the
|
|
4438
|
-
// run-manager soft timeout, or a user cancel). Without this leg the
|
|
4439
|
-
// loop blocks on an in-flight tool for up to TOOL_TIMEOUT_MS after
|
|
4440
|
-
// the run signal has already fired.
|
|
4441
|
-
new Promise((_, reject) => {
|
|
4442
|
-
if (signal.aborted) {
|
|
4443
|
-
reject(new Error("Run aborted"));
|
|
4444
|
-
return;
|
|
4445
|
-
}
|
|
4446
|
-
signal.addEventListener("abort", () => reject(new Error("Run aborted")), { once: true });
|
|
4447
|
-
}),
|
|
4448
|
-
]);
|
|
4449
|
-
const mcpResult = isMcpActionResult(raw) ? raw : null;
|
|
4450
|
-
const rawForAgent = mcpResult ? mcpResult.text : raw;
|
|
4451
|
-
if (mcpResult &&
|
|
4452
|
-
mcpResult.raw &&
|
|
4453
|
-
typeof mcpResult.raw === "object" &&
|
|
4454
|
-
mcpResult.raw.isError === true) {
|
|
4455
|
-
isError = true;
|
|
4456
|
-
}
|
|
4457
|
-
mcpApp = mcpResult?.mcpApp;
|
|
4458
|
-
// Demo mode is browser-local presentation state. The agent and MCP
|
|
4459
|
-
// layers always receive the real, access-scoped tool result.
|
|
4460
|
-
let resultForAgent = rawForAgent;
|
|
4461
|
-
// Vision images for the model: MCP tools return standard `image`
|
|
4462
|
-
// content parts; first-party actions opt in via the well-known
|
|
4463
|
-
// `_agentImages` result field (stripped from the JSON the model
|
|
4464
|
-
// reads). The images array
|
|
4465
|
-
// never touches the ledger — only the compact text notes appended
|
|
4466
|
-
// below are persisted.
|
|
4467
|
-
let imageNotes = [];
|
|
4468
|
-
if (mcpResult) {
|
|
4469
|
-
const mcpImages = extractMcpToolResultImages(mcpResult.raw);
|
|
4470
|
-
if (mcpImages.length > 0)
|
|
4471
|
-
toolResultImages = mcpImages;
|
|
4472
|
-
}
|
|
4473
|
-
else {
|
|
4474
|
-
const extracted = extractAgentImagesFromActionResult(resultForAgent);
|
|
4475
|
-
resultForAgent = extracted.value;
|
|
4476
|
-
imageNotes = extracted.notes;
|
|
4477
|
-
if (extracted.images.length > 0) {
|
|
4478
|
-
toolResultImages = extracted.images;
|
|
4479
|
-
}
|
|
4480
|
-
}
|
|
4481
|
-
if (toolResultImages) {
|
|
4482
|
-
imageNotes = [
|
|
4483
|
-
...describeToolResultImages(toolResultImages),
|
|
4484
|
-
...imageNotes,
|
|
4485
|
-
];
|
|
4486
|
-
}
|
|
4487
|
-
let resultStr = typeof resultForAgent === "string"
|
|
4488
|
-
? resultForAgent
|
|
4489
|
-
: JSON.stringify(resultForAgent, null, 2);
|
|
4490
|
-
if (resultStr.length > toolMaxResultChars) {
|
|
4491
|
-
const truncated = resultStr.slice(0, toolMaxResultChars);
|
|
4492
|
-
resultStr = `${truncated}\n\n...[truncated — full result was ${resultStr.length.toLocaleString()} chars; only first ${toolMaxResultChars.toLocaleString()} shown]`;
|
|
4493
|
-
}
|
|
4494
|
-
// Image notes go after truncation so they always survive into the
|
|
4495
|
-
// persisted result string (the durable record that an image existed).
|
|
4496
|
-
if (imageNotes.length > 0) {
|
|
4497
|
-
resultStr = `${resultStr}\n\n${imageNotes.join("\n")}`;
|
|
4498
|
-
}
|
|
4499
|
-
result = resultStr;
|
|
4500
|
-
if (toolCall.name === TOOL_SEARCH_ACTION_NAME && !isError) {
|
|
4501
|
-
const added = expandActiveTools(extractToolSearchResultNames(rawForAgent));
|
|
4502
|
-
if (added.length > 0) {
|
|
4503
|
-
result += `\n\nLoaded matching tool schemas for next step: ${added.join(", ")}`;
|
|
4504
|
-
}
|
|
4505
|
-
}
|
|
4506
|
-
}
|
|
4507
|
-
catch (err) {
|
|
4508
|
-
if (isAgentActionStopError(err)) {
|
|
4509
|
-
const message = sanitizeToolErrorValue(err.message) ||
|
|
4510
|
-
`Stopped after ${toolCall.name} failed.`;
|
|
4511
|
-
result = sanitizeToolErrorValue(err.toolResult || message);
|
|
4512
|
-
requestedActionStop ??= {
|
|
4513
|
-
message,
|
|
4514
|
-
...(err.errorCode ? { errorCode: err.errorCode } : {}),
|
|
4515
|
-
};
|
|
4516
|
-
}
|
|
4517
|
-
else {
|
|
4518
|
-
const message = sanitizeToolErrorValue(err);
|
|
4519
|
-
result = `Error running ${toolCall.name}: ${message}${rateLimitRecoveryHint(message)}`;
|
|
4520
|
-
}
|
|
4521
|
-
isError = true;
|
|
4522
|
-
}
|
|
4523
|
-
if (isError) {
|
|
4524
|
-
result = finalizeToolErrorResult(result);
|
|
4525
|
-
}
|
|
4526
|
-
// Side-channel warnings raised anywhere inside the action's call stack
|
|
4527
|
-
// (see `action-warnings.ts`). Drained here rather than in the success
|
|
4528
|
-
// branch so an action that warns and then throws still surfaces it, and
|
|
4529
|
-
// so nothing stays pending to bleed into a later tool's result.
|
|
4530
|
-
const agentWarnings = drainAgentWarnings();
|
|
4531
|
-
if (agentWarnings.length > 0) {
|
|
4532
|
-
result = `${result}\n\n${formatAgentWarningsForToolResult(agentWarnings)}`;
|
|
4533
|
-
}
|
|
4534
|
-
// Auto-refresh the UI after a successful mutating tool call. Any call
|
|
4535
|
-
// that isn't read-only — by its own per-call Plan-mode effect, else the
|
|
4536
|
-
// action's readOnly flag — is assumed to mutate. The client's useDbSync
|
|
4537
|
-
// listener sees a change event with source:"action" and invalidates
|
|
4538
|
-
// ["action"] queries so list-* / get-* refetch. This makes refresh after
|
|
4539
|
-
// agent writes reliable without the model needing to remember to call
|
|
4540
|
-
// `refresh-screen` itself.
|
|
4541
|
-
if (!isError) {
|
|
4542
|
-
try {
|
|
4543
|
-
const { actionCallIsReadOnly, notifyActionChangeInBackground } = await import("../server/action-change.js");
|
|
4544
|
-
if (!actionCallIsReadOnly(actionEntry, toolCall.input, false)) {
|
|
4545
|
-
const owner = opts.ownerEmail ?? getRequestUserEmail() ?? undefined;
|
|
4546
|
-
const orgId = opts.orgId ?? getRequestOrgId() ?? undefined;
|
|
4547
|
-
notifyActionChangeInBackground({
|
|
4548
|
-
actionName: toolCall.name,
|
|
4549
|
-
...(owner ? { owner } : {}),
|
|
4550
|
-
...(orgId ? { orgId } : {}),
|
|
4551
|
-
});
|
|
4552
|
-
}
|
|
4553
|
-
}
|
|
4554
|
-
catch {
|
|
4555
|
-
// poll module may be unavailable in non-server contexts — ignore
|
|
4556
|
-
}
|
|
4557
|
-
}
|
|
4558
|
-
send({
|
|
4559
|
-
type: "tool_done",
|
|
4560
|
-
id: toolCall.id,
|
|
4561
|
-
tool: toolCall.name,
|
|
4562
|
-
input: toolCall.input,
|
|
4563
|
-
result,
|
|
4564
|
-
...(isError ? { isError: true } : {}),
|
|
4565
|
-
...(isError
|
|
4566
|
-
? { completedSideEffect: false }
|
|
4567
|
-
: actionEntry.readOnly !== true
|
|
4568
|
-
? { completedSideEffect: true }
|
|
4569
|
-
: {}),
|
|
4570
|
-
...(mcpApp ? { mcpApp } : {}),
|
|
4571
|
-
...(actionEntry.chatUI ? { chatUI: actionEntry.chatUI } : {}),
|
|
4572
|
-
});
|
|
4573
|
-
recordToolResult(result, isError);
|
|
4574
|
-
if (!isError) {
|
|
4575
|
-
if (cacheKey) {
|
|
4576
|
-
readOnlyToolResultCache.set(cacheKey, result);
|
|
4577
|
-
}
|
|
4578
|
-
else if (actionEntry.readOnly !== true) {
|
|
4579
|
-
// A genuine write invalidates all cached reads. A dedupe:false
|
|
4580
|
-
// read-only tool also has a null cacheKey (see above) but must NOT
|
|
4581
|
-
// clear the cache — it isn't a write and other tools' cached reads
|
|
4582
|
-
// are still valid.
|
|
4583
|
-
readOnlyToolResultCache.clear();
|
|
4584
|
-
duplicateReadOnlyToolCalls.clear();
|
|
4684
|
+
recordToolResult(result, true);
|
|
4585
4685
|
}
|
|
4586
4686
|
}
|
|
4587
|
-
return {
|
|
4588
|
-
type: "tool-result",
|
|
4589
|
-
toolCallId: toolCall.id,
|
|
4590
|
-
toolName: toolCall.name,
|
|
4591
|
-
toolInput: wireToolInput,
|
|
4592
|
-
content: result,
|
|
4593
|
-
...(isError ? { isError } : {}),
|
|
4594
|
-
...(!isError && toolResultImages && toolResultImages.length > 0
|
|
4595
|
-
? { images: toolResultImages }
|
|
4596
|
-
: {}),
|
|
4597
|
-
};
|
|
4598
4687
|
};
|
|
4599
4688
|
const getParallelBatchKind = (toolCall) => {
|
|
4600
4689
|
const entry = actions[toolCall.name];
|
|
@@ -6433,6 +6522,12 @@ export function createProductionAgentHandler(options) {
|
|
|
6433
6522
|
...historyMessages,
|
|
6434
6523
|
{ role: "user", content: userContent },
|
|
6435
6524
|
];
|
|
6525
|
+
const requestedApprovedToolCalls = Array.isArray(body.approvedToolCalls) && body.approvedToolCalls.length > 0
|
|
6526
|
+
? body.approvedToolCalls
|
|
6527
|
+
.filter((key) => typeof key === "string")
|
|
6528
|
+
.slice(0, 200)
|
|
6529
|
+
: undefined;
|
|
6530
|
+
const exactApprovedToolCall = findApprovedStructuredToolCall(structuredHistory, requestedApprovedToolCalls);
|
|
6436
6531
|
const firstRequestPayloadDetail = buildFirstRequestPayloadDetail({
|
|
6437
6532
|
isFirstRequest: history.length === 0,
|
|
6438
6533
|
systemPrompt: requestSystemPrompt,
|
|
@@ -6486,6 +6581,38 @@ export function createProductionAgentHandler(options) {
|
|
|
6486
6581
|
: typeof requestTurnId === "string" && requestTurnId.trim()
|
|
6487
6582
|
? requestTurnId.trim()
|
|
6488
6583
|
: runId;
|
|
6584
|
+
const approvalStoreBinding = (binding) => {
|
|
6585
|
+
if (!ownerEmail) {
|
|
6586
|
+
throw new Error("Cannot create or consume an approval without an authenticated owner");
|
|
6587
|
+
}
|
|
6588
|
+
return {
|
|
6589
|
+
ownerEmail,
|
|
6590
|
+
orgId: getRequestOrgId() ?? null,
|
|
6591
|
+
threadId: effectiveThreadId,
|
|
6592
|
+
turnId: effectiveTurnId,
|
|
6593
|
+
toolName: binding.toolName,
|
|
6594
|
+
callId: binding.callId,
|
|
6595
|
+
approvalKey: binding.approvalKey,
|
|
6596
|
+
};
|
|
6597
|
+
};
|
|
6598
|
+
const approvalHooks = {
|
|
6599
|
+
onApprovalRequired: async (binding) => {
|
|
6600
|
+
await createAgentToolApproval(approvalStoreBinding(binding));
|
|
6601
|
+
},
|
|
6602
|
+
consumeApproval: async (binding) => {
|
|
6603
|
+
if (!ownerEmail)
|
|
6604
|
+
return false;
|
|
6605
|
+
return consumeAgentToolApproval(approvalStoreBinding(binding));
|
|
6606
|
+
},
|
|
6607
|
+
};
|
|
6608
|
+
const exactApprovedToolEntry = exactApprovedToolCall
|
|
6609
|
+
? requestActions[exactApprovedToolCall.name]
|
|
6610
|
+
: undefined;
|
|
6611
|
+
const approvedToolCallsForExecution = exactApprovedToolCall && exactApprovedToolEntry?.needsApproval
|
|
6612
|
+
? [
|
|
6613
|
+
toolCallCacheKey(exactApprovedToolCall.name, exactApprovedToolCall.input),
|
|
6614
|
+
]
|
|
6615
|
+
: undefined;
|
|
6489
6616
|
if (isBackgroundWorker &&
|
|
6490
6617
|
(await isTurnAborted(effectiveThreadId, effectiveTurnId))) {
|
|
6491
6618
|
await markRunAborted(runId, "user").catch(() => { });
|
|
@@ -7236,15 +7363,13 @@ export function createProductionAgentHandler(options) {
|
|
|
7236
7363
|
? { threadId: effectiveThreadId, turnId: effectiveTurnId }
|
|
7237
7364
|
: {}),
|
|
7238
7365
|
// Human-in-the-loop approval grants for this turn (sanitized — the
|
|
7239
|
-
// request is untrusted;
|
|
7240
|
-
|
|
7241
|
-
|
|
7242
|
-
? {
|
|
7243
|
-
approvedToolCalls: body.approvedToolCalls
|
|
7244
|
-
.filter((k) => typeof k === "string")
|
|
7245
|
-
.slice(0, 200),
|
|
7246
|
-
}
|
|
7366
|
+
// request is untrusted; only the exact structured call is passed to
|
|
7367
|
+
// the loop, where the durable grant is consumed atomically.
|
|
7368
|
+
...(approvedToolCallsForExecution
|
|
7369
|
+
? { approvedToolCalls: approvedToolCallsForExecution }
|
|
7247
7370
|
: {}),
|
|
7371
|
+
onApprovalRequired: approvalHooks.onApprovalRequired,
|
|
7372
|
+
consumeApproval: approvalHooks.consumeApproval,
|
|
7248
7373
|
// A worker PROVEN to be running inside the real 15-min Netlify
|
|
7249
7374
|
// `-background` function (`runsInBackgroundFunction`) has minutes of
|
|
7250
7375
|
// budget left on THIS invocation. Let it catch a recoverable
|
|
@@ -7269,6 +7394,31 @@ export function createProductionAgentHandler(options) {
|
|
|
7269
7394
|
send({ type: "activity", label: "Contacting model" });
|
|
7270
7395
|
let instrumented = false;
|
|
7271
7396
|
try {
|
|
7397
|
+
if (exactApprovedToolCall &&
|
|
7398
|
+
approvedToolCallsForExecution &&
|
|
7399
|
+
requestActions[exactApprovedToolCall.name]) {
|
|
7400
|
+
send({
|
|
7401
|
+
type: "activity",
|
|
7402
|
+
label: `Running approved ${exactApprovedToolCall.name} action`,
|
|
7403
|
+
tool: exactApprovedToolCall.name,
|
|
7404
|
+
});
|
|
7405
|
+
await executeAgentToolCall({
|
|
7406
|
+
actions: requestActions,
|
|
7407
|
+
name: exactApprovedToolCall.name,
|
|
7408
|
+
input: exactApprovedToolCall.input,
|
|
7409
|
+
callId: exactApprovedToolCall.callId,
|
|
7410
|
+
signal: agentLoopOpts.signal,
|
|
7411
|
+
ownerEmail,
|
|
7412
|
+
orgId: getRequestOrgId() ?? null,
|
|
7413
|
+
threadId: effectiveThreadId,
|
|
7414
|
+
turnId: effectiveTurnId,
|
|
7415
|
+
approvedToolCalls: approvedToolCallsForExecution,
|
|
7416
|
+
onApprovalRequired: approvalHooks.onApprovalRequired,
|
|
7417
|
+
consumeApproval: approvalHooks.consumeApproval,
|
|
7418
|
+
send,
|
|
7419
|
+
});
|
|
7420
|
+
return;
|
|
7421
|
+
}
|
|
7272
7422
|
try {
|
|
7273
7423
|
const { getObservabilityConfig, instrumentAgentLoop } = await import("../observability/traces.js");
|
|
7274
7424
|
const obsConfig = await getObservabilityConfig();
|