@agent-native/core 0.131.7 → 0.132.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +123 -0
- package/corpus/core/docs/content/durable-background-runs.mdx +15 -6
- package/corpus/core/docs/design/durable-agent-runs.md +15 -11
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/activity.ts +127 -10
- package/corpus/core/src/a2a/index.ts +3 -0
- package/corpus/core/src/agent/durable-background.ts +38 -28
- package/corpus/core/src/agent/harness/background.ts +12 -2
- package/corpus/core/src/agent/production-agent.ts +18 -15
- package/corpus/core/src/agent/run-store.ts +45 -3
- package/corpus/core/src/application-state/handlers.ts +45 -0
- package/corpus/core/src/application-state/index.ts +3 -0
- package/corpus/core/src/application-state/store.ts +38 -34
- package/corpus/core/src/audit/redact.ts +104 -18
- package/corpus/core/src/cli/create.ts +115 -16
- package/corpus/core/src/client/AgentPanel.tsx +2 -7
- package/corpus/core/src/client/AssistantChat.tsx +10 -2
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +29 -6
- package/corpus/core/src/client/agent-chat.ts +4 -9
- package/corpus/core/src/client/application-state.ts +115 -12
- package/corpus/core/src/client/dynamic-suggestions.ts +4 -12
- package/corpus/core/src/client/frame-protocol.ts +5 -1
- package/corpus/core/src/client/guided-questions.tsx +12 -23
- package/corpus/core/src/db/client.ts +148 -28
- package/corpus/core/src/db/create-get-db.ts +39 -6
- package/corpus/core/src/db/runtime-diagnostics.ts +41 -6
- package/corpus/core/src/deploy/build.ts +26 -33
- package/corpus/core/src/feature-flags/store.ts +10 -5
- package/corpus/core/src/integrations/a2a-continuations-store.ts +11 -0
- package/corpus/core/src/integrations/config-store.ts +13 -0
- package/corpus/core/src/integrations/google-docs-poller.ts +26 -2
- package/corpus/core/src/mcp-client/routes.ts +33 -2
- package/corpus/core/src/onboarding/plugin.ts +31 -25
- package/corpus/core/src/org/accept-pending.ts +2 -0
- package/corpus/core/src/org/auto-join-domain.ts +2 -0
- package/corpus/core/src/org/context.ts +35 -10
- package/corpus/core/src/org/handlers.ts +6 -0
- package/corpus/core/src/org/request-org-cache.ts +71 -0
- package/corpus/core/src/resources/store.ts +10 -9
- package/corpus/core/src/scripts/runner.ts +6 -3
- package/corpus/core/src/secrets/storage.ts +97 -5
- package/corpus/core/src/server/action-change.ts +33 -0
- package/corpus/core/src/server/action-routes.ts +11 -9
- package/corpus/core/src/server/agent-teams.ts +10 -2
- package/corpus/core/src/server/better-auth-instance.ts +39 -12
- package/corpus/core/src/server/core-routes-plugin.ts +19 -2
- package/corpus/core/src/server/credential-provider.ts +57 -14
- package/corpus/core/src/server/http-response-telemetry.ts +1 -0
- package/corpus/core/src/server/poll.ts +95 -57
- package/corpus/core/src/server/voice-providers-status.ts +19 -4
- package/corpus/core/src/workspace-files/tool.ts +18 -11
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
- package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +299 -0
- package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +34 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +82 -0
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +2 -2
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +40 -34
- package/corpus/templates/analytics/app/hooks/use-user-pref.ts +3 -3
- package/corpus/templates/analytics/app/i18n-data.ts +40 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +18 -10
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +14 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
- package/corpus/templates/analytics/app/routes/chart.tsx +3 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-dashboards-now-expose-native-funnel-heatmap-callou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-now-ships-reusable-native-v2-dashboard-manifests-w.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +81 -1
- package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +457 -0
- package/corpus/templates/analytics/server/lib/report-chart-svg.ts +73 -2
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/analytics/shared/dashboard-funnel.ts +83 -0
- package/corpus/templates/clips/actions/generate-workflow.ts +6 -4
- package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
- package/corpus/templates/clips/shared/workflow.ts +5 -0
- package/corpus/templates/content/changelog/2026-07-30-long-running-delegated-requests-now-continue-reliably.md +6 -0
- package/corpus/templates/content/netlify.toml +3 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -1
- package/corpus/templates/slides/actions/patch-deck.ts +25 -1
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +17 -4
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +9 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +11 -3
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +39 -11
- package/corpus/templates/slides/app/pages/Index.tsx +2 -2
- package/corpus/templates/slides/changelog/2026-07-30-slide-previews-keep-their-aspect-ratio-isolate-embedded-styl.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slides-no-longer-lose-decks-while-organization-access-is-loa.md +6 -0
- package/corpus/toolkit/CHANGELOG.md +8 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/composer/runtime-adapters.tsx +16 -11
- package/dist/a2a/activity.d.ts +22 -4
- package/dist/a2a/activity.d.ts.map +1 -1
- package/dist/a2a/activity.js +97 -11
- package/dist/a2a/activity.js.map +1 -1
- package/dist/a2a/index.d.ts +1 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/agent/durable-background.d.ts +13 -8
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +36 -28
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/harness/background.d.ts.map +1 -1
- package/dist/agent/harness/background.js +12 -2
- package/dist/agent/harness/background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -15
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts +2 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +44 -3
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/handlers.d.ts +15 -0
- package/dist/application-state/handlers.d.ts.map +1 -1
- package/dist/application-state/handlers.js +42 -2
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/application-state/index.d.ts +2 -2
- package/dist/application-state/index.d.ts.map +1 -1
- package/dist/application-state/index.js +2 -2
- package/dist/application-state/index.js.map +1 -1
- package/dist/application-state/store.d.ts +11 -0
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +37 -40
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/redact.d.ts +25 -2
- package/dist/audit/redact.d.ts.map +1 -1
- package/dist/audit/redact.js +85 -21
- package/dist/audit/redact.js.map +1 -1
- package/dist/cli/create.d.ts +8 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +98 -16
- package/dist/cli/create.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -7
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +10 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts +11 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +20 -7
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +4 -6
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/application-state.d.ts +11 -0
- package/dist/client/application-state.d.ts.map +1 -1
- package/dist/client/application-state.js +87 -9
- package/dist/client/application-state.js.map +1 -1
- package/dist/client/dynamic-suggestions.d.ts.map +1 -1
- package/dist/client/dynamic-suggestions.js +4 -13
- package/dist/client/dynamic-suggestions.js.map +1 -1
- package/dist/client/frame-protocol.d.ts +1 -0
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +6 -22
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db/client.d.ts +57 -10
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +122 -31
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +31 -7
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/runtime-diagnostics.d.ts +1 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -1
- package/dist/db/runtime-diagnostics.js +31 -6
- package/dist/db/runtime-diagnostics.js.map +1 -1
- package/dist/deploy/build.d.ts +5 -20
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +18 -30
- package/dist/deploy/build.js.map +1 -1
- package/dist/feature-flags/store.d.ts.map +1 -1
- package/dist/feature-flags/store.js +10 -5
- package/dist/feature-flags/store.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +12 -0
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts +1 -0
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +11 -0
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/google-docs-poller.d.ts.map +1 -1
- package/dist/integrations/google-docs-poller.js +19 -2
- package/dist/integrations/google-docs-poller.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +30 -2
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/onboarding/plugin.d.ts.map +1 -1
- package/dist/onboarding/plugin.js +12 -8
- package/dist/onboarding/plugin.js.map +1 -1
- package/dist/org/accept-pending.d.ts.map +1 -1
- package/dist/org/accept-pending.js +2 -0
- package/dist/org/accept-pending.js.map +1 -1
- package/dist/org/auto-join-domain.d.ts.map +1 -1
- package/dist/org/auto-join-domain.js +2 -0
- package/dist/org/auto-join-domain.js.map +1 -1
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +29 -9
- package/dist/org/context.js.map +1 -1
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +5 -0
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/request-org-cache.d.ts +16 -0
- package/dist/org/request-org-cache.d.ts.map +1 -0
- package/dist/org/request-org-cache.js +62 -0
- package/dist/org/request-org-cache.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +11 -9
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/runner.d.ts.map +1 -1
- package/dist/scripts/runner.js +3 -3
- package/dist/scripts/runner.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +82 -5
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-change.d.ts +20 -0
- package/dist/server/action-change.d.ts.map +1 -1
- package/dist/server/action-change.js +29 -0
- package/dist/server/action-change.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +7 -8
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +10 -2
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +31 -14
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +18 -4
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +16 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +49 -13
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +1 -0
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/server/poll.d.ts +6 -0
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +84 -47
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/voice-providers-status.d.ts.map +1 -1
- package/dist/server/voice-providers-status.js +12 -3
- package/dist/server/voice-providers-status.js.map +1 -1
- package/dist/workspace-files/tool.d.ts.map +1 -1
- package/dist/workspace-files/tool.js +18 -11
- package/dist/workspace-files/tool.js.map +1 -1
- package/docs/content/durable-background-runs.mdx +15 -6
- package/docs/design/durable-agent-runs.md +15 -11
- package/package.json +3 -3
- package/src/a2a/activity.ts +127 -10
- package/src/a2a/index.ts +3 -0
- package/src/agent/durable-background.ts +38 -28
- package/src/agent/harness/background.ts +12 -2
- package/src/agent/production-agent.ts +18 -15
- package/src/agent/run-store.ts +45 -3
- package/src/application-state/handlers.ts +45 -0
- package/src/application-state/index.ts +3 -0
- package/src/application-state/store.ts +38 -34
- package/src/audit/redact.ts +104 -18
- package/src/cli/create.ts +115 -16
- package/src/client/AgentPanel.tsx +2 -7
- package/src/client/AssistantChat.tsx +10 -2
- package/src/client/MultiTabAssistantChat.tsx +3 -0
- package/src/client/RunStuckBanner.tsx +29 -6
- package/src/client/agent-chat.ts +4 -9
- package/src/client/application-state.ts +115 -12
- package/src/client/dynamic-suggestions.ts +4 -12
- package/src/client/frame-protocol.ts +5 -1
- package/src/client/guided-questions.tsx +12 -23
- package/src/db/client.ts +148 -28
- package/src/db/create-get-db.ts +39 -6
- package/src/db/runtime-diagnostics.ts +41 -6
- package/src/deploy/build.ts +26 -33
- package/src/feature-flags/store.ts +10 -5
- package/src/integrations/a2a-continuations-store.ts +11 -0
- package/src/integrations/config-store.ts +13 -0
- package/src/integrations/google-docs-poller.ts +26 -2
- package/src/mcp-client/routes.ts +33 -2
- package/src/onboarding/plugin.ts +31 -25
- package/src/org/accept-pending.ts +2 -0
- package/src/org/auto-join-domain.ts +2 -0
- package/src/org/context.ts +35 -10
- package/src/org/handlers.ts +6 -0
- package/src/org/request-org-cache.ts +71 -0
- package/src/resources/store.ts +10 -9
- package/src/scripts/runner.ts +6 -3
- package/src/secrets/storage.ts +97 -5
- package/src/server/action-change.ts +33 -0
- package/src/server/action-routes.ts +11 -9
- package/src/server/agent-teams.ts +10 -2
- package/src/server/better-auth-instance.ts +39 -12
- package/src/server/core-routes-plugin.ts +19 -2
- package/src/server/credential-provider.ts +57 -14
- package/src/server/http-response-telemetry.ts +1 -0
- package/src/server/poll.ts +95 -57
- package/src/server/voice-providers-status.ts +19 -4
- package/src/workspace-files/tool.ts +18 -11
|
@@ -5673,23 +5673,26 @@ export async function runAgentLoop(opts: {
|
|
|
5673
5673
|
result = `${result}\n\n${formatAgentWarningsForToolResult(agentWarnings)}`;
|
|
5674
5674
|
}
|
|
5675
5675
|
|
|
5676
|
-
// Auto-refresh the UI after a successful mutating tool call. Any
|
|
5677
|
-
// that isn't
|
|
5678
|
-
//
|
|
5679
|
-
//
|
|
5680
|
-
//
|
|
5681
|
-
// remember to call
|
|
5682
|
-
|
|
5676
|
+
// Auto-refresh the UI after a successful mutating tool call. Any call
|
|
5677
|
+
// that isn't read-only — by its own per-call Plan-mode effect, else the
|
|
5678
|
+
// action's readOnly flag — is assumed to mutate. The client's useDbSync
|
|
5679
|
+
// listener sees a change event with source:"action" and invalidates
|
|
5680
|
+
// ["action"] queries so list-* / get-* refetch. This makes refresh after
|
|
5681
|
+
// agent writes reliable without the model needing to remember to call
|
|
5682
|
+
// `refresh-screen` itself.
|
|
5683
|
+
if (!isError) {
|
|
5683
5684
|
try {
|
|
5684
|
-
const { notifyActionChange } =
|
|
5685
|
+
const { actionCallIsReadOnly, notifyActionChange } =
|
|
5685
5686
|
await import("../server/action-change.js");
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5687
|
+
if (!actionCallIsReadOnly(actionEntry, toolCall.input, false)) {
|
|
5688
|
+
const owner = opts.ownerEmail ?? getRequestUserEmail() ?? undefined;
|
|
5689
|
+
const orgId = opts.orgId ?? getRequestOrgId() ?? undefined;
|
|
5690
|
+
await notifyActionChange({
|
|
5691
|
+
actionName: toolCall.name,
|
|
5692
|
+
...(owner ? { owner } : {}),
|
|
5693
|
+
...(orgId ? { orgId } : {}),
|
|
5694
|
+
});
|
|
5695
|
+
}
|
|
5693
5696
|
} catch {
|
|
5694
5697
|
// poll module may be unavailable in non-server contexts — ignore
|
|
5695
5698
|
}
|
|
@@ -163,6 +163,46 @@ export const UNCLAIMED_BACKGROUND_RUN_REDISPATCH_BOUND_MS = 5 * 60_000;
|
|
|
163
163
|
*/
|
|
164
164
|
export const UNCLAIMED_BACKGROUND_RUN_FAST_SWEEP_MS = 20_000;
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* How long a negative `status='running'` probe stays trusted.
|
|
168
|
+
*
|
|
169
|
+
* MUST stay well below the tightest staleness window any sweep enforces
|
|
170
|
+
* (`RUN_STALE_MS`, 15s): the probe sees running rows of EVERY age, so a
|
|
171
|
+
* negative result proves there were zero running rows that instant, and a row
|
|
172
|
+
* inserted since — by this process or another one — cannot yet be old enough
|
|
173
|
+
* for a sweep to act on it. Widen this past a staleness window and a genuinely
|
|
174
|
+
* dead run becomes invisible for a whole extra tick.
|
|
175
|
+
*/
|
|
176
|
+
const NO_RUNNING_RUNS_CACHE_MS = 5_000;
|
|
177
|
+
|
|
178
|
+
let _noRunningRunsUntil = 0;
|
|
179
|
+
|
|
180
|
+
/** Test seam — the probe cache is module state, so suites must clear it. */
|
|
181
|
+
export function __resetNoRunningRunsProbeForTests(): void {
|
|
182
|
+
_noRunningRunsUntil = 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* `status = 'running'` is a strict superset of every sweep predicate in this
|
|
187
|
+
* file, so one probe answers all of them. The fast sweep runs `reapAllStaleRuns`
|
|
188
|
+
* and `listUnclaimedBackgroundRunRows` back to back every 20s; on an idle app
|
|
189
|
+
* both scan for rows that do not exist, which on a remote database is two
|
|
190
|
+
* round trips per tick per app, forever. The probe collapses that pair to one.
|
|
191
|
+
*/
|
|
192
|
+
async function hasRunningRuns(): Promise<boolean> {
|
|
193
|
+
if (Date.now() < _noRunningRunsUntil) return false;
|
|
194
|
+
const { rows } = await getDbExec().execute({
|
|
195
|
+
sql: `SELECT id FROM agent_runs WHERE status = 'running' LIMIT 1`,
|
|
196
|
+
args: [],
|
|
197
|
+
});
|
|
198
|
+
if ((rows?.length ?? 0) > 0) {
|
|
199
|
+
_noRunningRunsUntil = 0;
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
_noRunningRunsUntil = Date.now() + NO_RUNNING_RUNS_CACHE_MS;
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
166
206
|
/**
|
|
167
207
|
* FIX 3 (durable-background incident) per-turn run-count ceiling for
|
|
168
208
|
* stale-run recovery — mirrors `chainServerDrivenContinuation`'s own ledger
|
|
@@ -920,6 +960,7 @@ export async function listUnclaimedBackgroundRunRows(): Promise<
|
|
|
920
960
|
UnclaimedBackgroundRunRow[]
|
|
921
961
|
> {
|
|
922
962
|
await ensureRunTables();
|
|
963
|
+
if (!(await hasRunningRuns())) return [];
|
|
923
964
|
const client = getDbExec();
|
|
924
965
|
const { rows } = await client.execute({
|
|
925
966
|
// CAST keeps the ms-epoch param 64-bit on Postgres (see
|
|
@@ -2538,6 +2579,7 @@ export async function getCurrentTurnEventsForThread(
|
|
|
2538
2579
|
*/
|
|
2539
2580
|
export async function reapAllStaleRuns(): Promise<number> {
|
|
2540
2581
|
await ensureRunTables();
|
|
2582
|
+
if (!(await hasRunningRuns())) return 0;
|
|
2541
2583
|
const client = getDbExec();
|
|
2542
2584
|
const now = Date.now();
|
|
2543
2585
|
// Background-dispatched runs use the wider window; everything else 15s. The
|
|
@@ -2570,9 +2612,9 @@ export async function reapAllStaleRuns(): Promise<number> {
|
|
|
2570
2612
|
// `reapSingleStaleRun` re-applies the identical staleness clause per row
|
|
2571
2613
|
// (same as the bulk UPDATE previously did), so a row whose heartbeat
|
|
2572
2614
|
// landed between the SELECT above and this loop is still correctly
|
|
2573
|
-
// excluded.
|
|
2574
|
-
//
|
|
2575
|
-
//
|
|
2615
|
+
// excluded. The per-row round trips only ever run for rows the SELECT above
|
|
2616
|
+
// already found stale; on an idle app `hasRunningRuns` returns before any of
|
|
2617
|
+
// this, so the 20s fast sweep (agent-chat-plugin.ts) costs one probe.
|
|
2576
2618
|
let reapedCount = 0;
|
|
2577
2619
|
for (const row of stale.rows) {
|
|
2578
2620
|
const id = (row as { id?: unknown }).id;
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
defineEventHandler,
|
|
4
4
|
getRouterParam,
|
|
5
5
|
getHeader,
|
|
6
|
+
getQuery,
|
|
6
7
|
setResponseStatus,
|
|
7
8
|
type H3Event,
|
|
8
9
|
} from "h3";
|
|
@@ -11,6 +12,7 @@ import { getSession } from "../server/auth.js";
|
|
|
11
12
|
import { readBody } from "../server/h3-helpers.js";
|
|
12
13
|
import {
|
|
13
14
|
appStateGet,
|
|
15
|
+
appStateGetManyEntries,
|
|
14
16
|
appStatePut,
|
|
15
17
|
appStateDelete,
|
|
16
18
|
appStateList,
|
|
@@ -45,6 +47,49 @@ export const getState = defineEventHandler(async (event: H3Event) => {
|
|
|
45
47
|
return value ?? null;
|
|
46
48
|
});
|
|
47
49
|
|
|
50
|
+
/** Upper bound on one batched read, so a crafted URL can't fan out unbounded. */
|
|
51
|
+
export const MAX_APP_STATE_BATCH_KEYS = 100;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `GET /_agent-native/application-state?keys=a,b,c` — read many keys for the
|
|
55
|
+
* caller's session in one round trip.
|
|
56
|
+
*
|
|
57
|
+
* `values` carries only the keys that have a stored row; every other requested
|
|
58
|
+
* key is listed in `missing`. A key whose stored value is `null` therefore
|
|
59
|
+
* appears in `values` with a `null` value and NOT in `missing`, which is what
|
|
60
|
+
* keeps "no row" distinguishable from "row holding an empty value".
|
|
61
|
+
*/
|
|
62
|
+
export const getStateMany = defineEventHandler(async (event: H3Event) => {
|
|
63
|
+
const sessionId = await getSessionId(event);
|
|
64
|
+
const raw = getQuery(event).keys;
|
|
65
|
+
const requested = (Array.isArray(raw) ? raw : [raw])
|
|
66
|
+
.flatMap((entry) => String(entry ?? "").split(","))
|
|
67
|
+
.map((key) => safeKey(key.trim()))
|
|
68
|
+
.filter((key) => key.length > 0);
|
|
69
|
+
const keys = [...new Set(requested)];
|
|
70
|
+
|
|
71
|
+
if (keys.length === 0) {
|
|
72
|
+
throw createError({
|
|
73
|
+
statusCode: 400,
|
|
74
|
+
statusMessage: "Query parameter `keys` is required",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (keys.length > MAX_APP_STATE_BATCH_KEYS) {
|
|
78
|
+
throw createError({
|
|
79
|
+
statusCode: 400,
|
|
80
|
+
statusMessage: `At most ${MAX_APP_STATE_BATCH_KEYS} keys may be read at once`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const entries = await appStateGetManyEntries(sessionId, keys);
|
|
85
|
+
const values: Record<string, unknown> = {};
|
|
86
|
+
for (const entry of entries) values[entry.key] = entry.value;
|
|
87
|
+
return {
|
|
88
|
+
values,
|
|
89
|
+
missing: keys.filter((key) => !(key in values)),
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
|
|
48
93
|
export const putState = defineEventHandler(async (event: H3Event) => {
|
|
49
94
|
const sessionId = await getSessionId(event);
|
|
50
95
|
const key = safeKey(String(getRouterParam(event, "key")));
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
export {
|
|
3
3
|
appStateGet,
|
|
4
4
|
appStateGetMany,
|
|
5
|
+
appStateGetManyEntries,
|
|
5
6
|
appStatePut,
|
|
6
7
|
appStateDelete,
|
|
7
8
|
appStateCompareAndSet,
|
|
@@ -22,6 +23,8 @@ export {
|
|
|
22
23
|
// H3 route handlers (for templates)
|
|
23
24
|
export {
|
|
24
25
|
getState,
|
|
26
|
+
getStateMany,
|
|
27
|
+
MAX_APP_STATE_BATCH_KEYS,
|
|
25
28
|
putState,
|
|
26
29
|
deleteState,
|
|
27
30
|
listComposeDrafts,
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
getDbExec,
|
|
3
3
|
getDialect,
|
|
4
4
|
isLocalDatabase,
|
|
5
|
-
isConnectionError,
|
|
6
5
|
isPostgres,
|
|
7
6
|
intType,
|
|
8
7
|
type DbExec,
|
|
@@ -102,21 +101,40 @@ export async function appStateGet(
|
|
|
102
101
|
sessionId: string,
|
|
103
102
|
key: string,
|
|
104
103
|
): Promise<Record<string, unknown> | null> {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
104
|
+
await ensureTable();
|
|
105
|
+
const client = getDbExec();
|
|
106
|
+
const { rows } = await client.execute({
|
|
107
|
+
sql: `SELECT value FROM application_state WHERE session_id = ? AND key = ?`,
|
|
108
|
+
args: [sessionId, key],
|
|
109
|
+
});
|
|
110
|
+
if (rows.length === 0) return null;
|
|
111
|
+
return JSON.parse(rows[0].value as string);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Read several application-state keys for one session in a single SQL query,
|
|
116
|
+
* returning ONLY the rows that exist. A key absent from the result has no row;
|
|
117
|
+
* a key present with a `null` value has a row that stores `null`. Callers that
|
|
118
|
+
* must not conflate the two (the batched HTTP read) depend on that difference,
|
|
119
|
+
* so this deliberately does not pad missing keys.
|
|
120
|
+
*/
|
|
121
|
+
export async function appStateGetManyEntries(
|
|
122
|
+
sessionId: string,
|
|
123
|
+
keys: readonly string[],
|
|
124
|
+
): Promise<Array<{ key: string; value: Record<string, unknown> | null }>> {
|
|
125
|
+
const uniqueKeys = [...new Set(keys)];
|
|
126
|
+
if (uniqueKeys.length === 0) return [];
|
|
127
|
+
await ensureTable();
|
|
128
|
+
const client = getDbExec();
|
|
129
|
+
const placeholders = uniqueKeys.map(() => "?").join(", ");
|
|
130
|
+
const { rows } = await client.execute({
|
|
131
|
+
sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key IN (${placeholders})`,
|
|
132
|
+
args: [sessionId, ...uniqueKeys],
|
|
133
|
+
});
|
|
134
|
+
return rows.map((row) => ({
|
|
135
|
+
key: row.key as string,
|
|
136
|
+
value: JSON.parse(row.value as string),
|
|
137
|
+
}));
|
|
120
138
|
}
|
|
121
139
|
|
|
122
140
|
/**
|
|
@@ -128,27 +146,13 @@ export async function appStateGetMany(
|
|
|
128
146
|
sessionId: string,
|
|
129
147
|
keys: readonly string[],
|
|
130
148
|
): Promise<Record<string, Record<string, unknown> | null>> {
|
|
131
|
-
const uniqueKeys = [...new Set(keys)];
|
|
132
149
|
const values: Record<string, Record<string, unknown> | null> = {};
|
|
133
|
-
for (const key of
|
|
134
|
-
if (uniqueKeys.length === 0) return values;
|
|
150
|
+
for (const key of new Set(keys)) values[key] = null;
|
|
135
151
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const client = getDbExec();
|
|
139
|
-
const placeholders = uniqueKeys.map(() => "?").join(", ");
|
|
140
|
-
const { rows } = await client.execute({
|
|
141
|
-
sql: `SELECT key, value FROM application_state WHERE session_id = ? AND key IN (${placeholders})`,
|
|
142
|
-
args: [sessionId, ...uniqueKeys],
|
|
143
|
-
});
|
|
144
|
-
for (const row of rows) {
|
|
145
|
-
values[row.key as string] = JSON.parse(row.value as string);
|
|
146
|
-
}
|
|
147
|
-
return values;
|
|
148
|
-
} catch (err) {
|
|
149
|
-
if (isConnectionError(err)) return values;
|
|
150
|
-
throw err;
|
|
152
|
+
for (const entry of await appStateGetManyEntries(sessionId, keys)) {
|
|
153
|
+
values[entry.key] = entry.value;
|
|
151
154
|
}
|
|
155
|
+
return values;
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
export async function appStatePut(
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const SENSITIVE_KEY =
|
|
15
|
-
/(pass(word|phrase)?|secret|token|api[_-]?key|apikey|authorization|bearer|credential|cookie|session[_-]?(id|token)|private[_-]?key|client[_-]?secret|signing[_-]?secret|access[_-]?key|refresh[_-]?token|webhook[_-]?(url|secret))/i;
|
|
15
|
+
/(?:pass(?:word|phrase)?|secret|token|api[_-]?key|apikey|authorization|bearer|credential|cookie|session[_-]?(?:id|token)|private[_-]?key|client[_-]?secret|signing[_-]?secret|access[_-]?key|refresh[_-]?token|webhook[_-]?(?:url|secret))/i;
|
|
16
16
|
|
|
17
17
|
const REDACTED = "[redacted]";
|
|
18
18
|
const MAX_STRING = 2000;
|
|
@@ -20,6 +20,11 @@ const MAX_DEPTH = 6;
|
|
|
20
20
|
const MAX_KEYS = 100;
|
|
21
21
|
const MAX_ARRAY = 100;
|
|
22
22
|
const MAX_JSON = 8000;
|
|
23
|
+
/**
|
|
24
|
+
* Head room reserved for the `…(N more chars)` marker so a truncated summary
|
|
25
|
+
* still fits its caller's cap. Callers below this cannot carry a marker at all.
|
|
26
|
+
*/
|
|
27
|
+
const MARKER_SLACK = 32;
|
|
23
28
|
|
|
24
29
|
/** Heuristic: does a bare string value look like a secret? */
|
|
25
30
|
function looksSecret(value: string): boolean {
|
|
@@ -42,21 +47,32 @@ function looksSecret(value: string): boolean {
|
|
|
42
47
|
return false;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
function redactString(value: string): string {
|
|
50
|
+
function redactString(value: string, maxString: number): string {
|
|
46
51
|
if (looksSecret(value)) return REDACTED;
|
|
47
|
-
if (value.length >
|
|
48
|
-
return `${value.slice(0,
|
|
52
|
+
if (value.length > maxString) {
|
|
53
|
+
return `${value.slice(0, maxString)}…(${value.length - maxString} more chars)`;
|
|
49
54
|
}
|
|
50
55
|
return value;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
function
|
|
58
|
+
function redactEmbeddedSecrets(value: string): string {
|
|
59
|
+
const bearer = value.replace(/\b(bearer\s+)([^\s"',}]+)/gi, `$1${REDACTED}`);
|
|
60
|
+
const credentialField = new RegExp(
|
|
61
|
+
`(["']?\\b${SENSITIVE_KEY.source}\\b["']?\\s*[:=]\\s*)(["']?)(?!bearer\\b)([^"'\\s,}]+)\\2`,
|
|
62
|
+
"gi",
|
|
63
|
+
);
|
|
64
|
+
return bearer.replace(credentialField, `$1$2${REDACTED}$2`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function redact(value: unknown, depth: number, maxString: number): unknown {
|
|
54
68
|
if (value == null) return value;
|
|
55
|
-
if (typeof value === "string") return redactString(value);
|
|
69
|
+
if (typeof value === "string") return redactString(value, maxString);
|
|
56
70
|
if (typeof value === "number" || typeof value === "boolean") return value;
|
|
57
71
|
if (depth >= MAX_DEPTH) return "[…]";
|
|
58
72
|
if (Array.isArray(value)) {
|
|
59
|
-
const out = value
|
|
73
|
+
const out = value
|
|
74
|
+
.slice(0, MAX_ARRAY)
|
|
75
|
+
.map((v) => redact(v, depth + 1, maxString));
|
|
60
76
|
if (value.length > MAX_ARRAY)
|
|
61
77
|
out.push(`…(${value.length - MAX_ARRAY} more)`);
|
|
62
78
|
return out;
|
|
@@ -70,7 +86,9 @@ function redact(value: unknown, depth: number): unknown {
|
|
|
70
86
|
break;
|
|
71
87
|
}
|
|
72
88
|
n += 1;
|
|
73
|
-
out[k] = SENSITIVE_KEY.test(k)
|
|
89
|
+
out[k] = SENSITIVE_KEY.test(k)
|
|
90
|
+
? REDACTED
|
|
91
|
+
: redact(v, depth + 1, maxString);
|
|
74
92
|
}
|
|
75
93
|
return out;
|
|
76
94
|
}
|
|
@@ -78,32 +96,100 @@ function redact(value: unknown, depth: number): unknown {
|
|
|
78
96
|
return undefined;
|
|
79
97
|
}
|
|
80
98
|
|
|
99
|
+
export interface RedactLimits {
|
|
100
|
+
/**
|
|
101
|
+
* Hard cap on the serialized output, INCLUDING the truncation envelope.
|
|
102
|
+
* Surfaces with their own wire budget (the A2A activity snapshot) pass a
|
|
103
|
+
* tighter cap than the audit-log default.
|
|
104
|
+
*/
|
|
105
|
+
maxJson?: number;
|
|
106
|
+
/** Cap on any single string value before it gets a truncation marker. */
|
|
107
|
+
maxString?: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
81
110
|
/**
|
|
82
111
|
* Redact and serialize call arguments to a capped JSON string, or `null` when
|
|
83
112
|
* there is nothing to record. Never throws.
|
|
84
113
|
*/
|
|
85
|
-
export function redactArgsToJson(
|
|
114
|
+
export function redactArgsToJson(
|
|
115
|
+
args: unknown,
|
|
116
|
+
limits?: RedactLimits,
|
|
117
|
+
): string | null {
|
|
118
|
+
const maxJson = Math.max(MARKER_SLACK * 4, limits?.maxJson ?? MAX_JSON);
|
|
119
|
+
const maxString = limits?.maxString ?? MAX_STRING;
|
|
86
120
|
try {
|
|
87
121
|
if (args == null) return null;
|
|
88
|
-
const redacted = redact(args, 0);
|
|
122
|
+
const redacted = redact(args, 0, maxString);
|
|
89
123
|
if (redacted === undefined) return null;
|
|
90
124
|
const json = JSON.stringify(redacted);
|
|
91
125
|
if (json == null) return null;
|
|
92
|
-
if (json.length
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
126
|
+
if (json.length <= maxJson) return json;
|
|
127
|
+
// Slicing the serialized JSON would yield an unparseable string. Wrap a
|
|
128
|
+
// preview in a valid envelope so `get-audit-event` can always JSON.parse
|
|
129
|
+
// the stored `input`. JSON escaping makes the envelope overhead
|
|
130
|
+
// content-dependent, so shrink the preview until the whole thing measures
|
|
131
|
+
// under the cap rather than guessing at the overhead.
|
|
132
|
+
const envelope = (preview: string) =>
|
|
133
|
+
JSON.stringify({
|
|
97
134
|
_auditTruncated: true,
|
|
98
135
|
originalBytes: json.length,
|
|
99
|
-
preview
|
|
136
|
+
preview,
|
|
100
137
|
});
|
|
138
|
+
let take = maxJson;
|
|
139
|
+
let out = envelope(json.slice(0, take));
|
|
140
|
+
while (out.length > maxJson && take > 0) {
|
|
141
|
+
take = Math.max(0, take - Math.max(1, out.length - maxJson));
|
|
142
|
+
out = envelope(json.slice(0, take));
|
|
101
143
|
}
|
|
102
|
-
return
|
|
144
|
+
return out;
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Same bounds as `redactArgsToJson`, returned as a structural value for
|
|
152
|
+
* surfaces whose consumers expect an object (transcript `metadata.input`).
|
|
153
|
+
*/
|
|
154
|
+
export function redactArgsToValue(
|
|
155
|
+
args: unknown,
|
|
156
|
+
limits?: RedactLimits,
|
|
157
|
+
): unknown {
|
|
158
|
+
const json = redactArgsToJson(args, limits);
|
|
159
|
+
if (json == null) return null;
|
|
160
|
+
try {
|
|
161
|
+
return JSON.parse(json);
|
|
103
162
|
} catch {
|
|
104
163
|
return null;
|
|
105
164
|
}
|
|
106
165
|
}
|
|
107
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Bounded, redacted plain-text summary of a tool result. Oversized text keeps
|
|
169
|
+
* its head and gains an explicit `…(N more chars)` marker, so a reader can
|
|
170
|
+
* always tell a short result from a clipped one.
|
|
171
|
+
*/
|
|
172
|
+
export function redactTextToSummary(
|
|
173
|
+
text: string,
|
|
174
|
+
maxChars = MAX_STRING,
|
|
175
|
+
): string | null {
|
|
176
|
+
if (!text) return null;
|
|
177
|
+
// Only ever walk a bounded head: a multi-megabyte tool result must not be
|
|
178
|
+
// scanned in full just to produce a preview of it.
|
|
179
|
+
const head = text.slice(0, maxChars + MARKER_SLACK);
|
|
180
|
+
const redactedHead = redactEmbeddedSecrets(head);
|
|
181
|
+
if (looksSecret(redactedHead.trim())) return REDACTED;
|
|
182
|
+
if (text.length <= maxChars && redactedHead.length <= maxChars) {
|
|
183
|
+
return redactedHead;
|
|
184
|
+
}
|
|
185
|
+
const keep = Math.max(0, maxChars - MARKER_SLACK);
|
|
186
|
+
return `${redactedHead.slice(0, keep)}…(${text.length - keep} more chars)`;
|
|
187
|
+
}
|
|
188
|
+
|
|
108
189
|
/** Exposed for tests. */
|
|
109
|
-
export const __test = {
|
|
190
|
+
export const __test = {
|
|
191
|
+
looksSecret,
|
|
192
|
+
redact,
|
|
193
|
+
redactEmbeddedSecrets,
|
|
194
|
+
SENSITIVE_KEY,
|
|
195
|
+
};
|