@agent-native/core 0.149.0 → 0.149.2
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/dashboard-management/SKILL.md +2 -0
- 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/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/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/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- 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/app/global.css +1 -0
- 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/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- 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/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- 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/server/ui-plan-html.ts +4 -1
- package/corpus/templates/slides/AGENTS.md +11 -10
- 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.ts +29 -1
- package/corpus/templates/slides/actions/patch-deck.ts +139 -10
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
- package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +10 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +74 -63
- 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 +239 -116
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +29 -7
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -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/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/hooks/use-generating-slide-preview.ts +25 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
- package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +16 -27
- package/corpus/templates/slides/app/pages/Index.tsx +256 -98
- package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -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/server/handlers/import/html-converter.ts +115 -22
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
- package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
- package/corpus/templates/slides/shared/api.ts +2 -1
- package/corpus/templates/slides/shared/deck-title.ts +135 -0
- 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 +129 -10
- 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 +3 -2
- package/dist/client/AssistantChat.js +7 -20
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat-adapter.js +11 -0
- 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 +6 -2
- package/dist/client/chat/message-components.js +25 -47
- package/dist/client/chat/tool-call-display.d.ts +3 -5
- package/dist/client/chat/tool-call-display.js +7 -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 +2 -2
- 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/progress/RunsTray.js +14 -1
- package/dist/client/resources/McpConnectionSuggestion.js +9 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/use-chat-models.js +156 -80
- package/dist/integrations/webhook-handler.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/auth.js +262 -31
- package/dist/server/onboarding-html.js +56 -9
- 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/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
|
@@ -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;
|
|
@@ -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,
|
|
@@ -6433,6 +6491,12 @@ export function createProductionAgentHandler(options) {
|
|
|
6433
6491
|
...historyMessages,
|
|
6434
6492
|
{ role: "user", content: userContent },
|
|
6435
6493
|
];
|
|
6494
|
+
const requestedApprovedToolCalls = Array.isArray(body.approvedToolCalls) && body.approvedToolCalls.length > 0
|
|
6495
|
+
? body.approvedToolCalls
|
|
6496
|
+
.filter((key) => typeof key === "string")
|
|
6497
|
+
.slice(0, 200)
|
|
6498
|
+
: undefined;
|
|
6499
|
+
const exactApprovedToolCall = findApprovedStructuredToolCall(structuredHistory, requestedApprovedToolCalls);
|
|
6436
6500
|
const firstRequestPayloadDetail = buildFirstRequestPayloadDetail({
|
|
6437
6501
|
isFirstRequest: history.length === 0,
|
|
6438
6502
|
systemPrompt: requestSystemPrompt,
|
|
@@ -6486,6 +6550,38 @@ export function createProductionAgentHandler(options) {
|
|
|
6486
6550
|
: typeof requestTurnId === "string" && requestTurnId.trim()
|
|
6487
6551
|
? requestTurnId.trim()
|
|
6488
6552
|
: runId;
|
|
6553
|
+
const approvalStoreBinding = (binding) => {
|
|
6554
|
+
if (!ownerEmail) {
|
|
6555
|
+
throw new Error("Cannot create or consume an approval without an authenticated owner");
|
|
6556
|
+
}
|
|
6557
|
+
return {
|
|
6558
|
+
ownerEmail,
|
|
6559
|
+
orgId: getRequestOrgId() ?? null,
|
|
6560
|
+
threadId: effectiveThreadId,
|
|
6561
|
+
turnId: effectiveTurnId,
|
|
6562
|
+
toolName: binding.toolName,
|
|
6563
|
+
callId: binding.callId,
|
|
6564
|
+
approvalKey: binding.approvalKey,
|
|
6565
|
+
};
|
|
6566
|
+
};
|
|
6567
|
+
const approvalHooks = {
|
|
6568
|
+
onApprovalRequired: async (binding) => {
|
|
6569
|
+
await createAgentToolApproval(approvalStoreBinding(binding));
|
|
6570
|
+
},
|
|
6571
|
+
consumeApproval: async (binding) => {
|
|
6572
|
+
if (!ownerEmail)
|
|
6573
|
+
return false;
|
|
6574
|
+
return consumeAgentToolApproval(approvalStoreBinding(binding));
|
|
6575
|
+
},
|
|
6576
|
+
};
|
|
6577
|
+
const exactApprovedToolEntry = exactApprovedToolCall
|
|
6578
|
+
? requestActions[exactApprovedToolCall.name]
|
|
6579
|
+
: undefined;
|
|
6580
|
+
const approvedToolCallsForExecution = exactApprovedToolCall && exactApprovedToolEntry?.needsApproval
|
|
6581
|
+
? [
|
|
6582
|
+
toolCallCacheKey(exactApprovedToolCall.name, exactApprovedToolCall.input),
|
|
6583
|
+
]
|
|
6584
|
+
: undefined;
|
|
6489
6585
|
if (isBackgroundWorker &&
|
|
6490
6586
|
(await isTurnAborted(effectiveThreadId, effectiveTurnId))) {
|
|
6491
6587
|
await markRunAborted(runId, "user").catch(() => { });
|
|
@@ -7236,15 +7332,13 @@ export function createProductionAgentHandler(options) {
|
|
|
7236
7332
|
? { threadId: effectiveThreadId, turnId: effectiveTurnId }
|
|
7237
7333
|
: {}),
|
|
7238
7334
|
// 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
|
-
}
|
|
7335
|
+
// request is untrusted; only the exact structured call is passed to
|
|
7336
|
+
// the loop, where the durable grant is consumed atomically.
|
|
7337
|
+
...(approvedToolCallsForExecution
|
|
7338
|
+
? { approvedToolCalls: approvedToolCallsForExecution }
|
|
7247
7339
|
: {}),
|
|
7340
|
+
onApprovalRequired: approvalHooks.onApprovalRequired,
|
|
7341
|
+
consumeApproval: approvalHooks.consumeApproval,
|
|
7248
7342
|
// A worker PROVEN to be running inside the real 15-min Netlify
|
|
7249
7343
|
// `-background` function (`runsInBackgroundFunction`) has minutes of
|
|
7250
7344
|
// budget left on THIS invocation. Let it catch a recoverable
|
|
@@ -7269,6 +7363,31 @@ export function createProductionAgentHandler(options) {
|
|
|
7269
7363
|
send({ type: "activity", label: "Contacting model" });
|
|
7270
7364
|
let instrumented = false;
|
|
7271
7365
|
try {
|
|
7366
|
+
if (exactApprovedToolCall &&
|
|
7367
|
+
approvedToolCallsForExecution &&
|
|
7368
|
+
requestActions[exactApprovedToolCall.name]) {
|
|
7369
|
+
send({
|
|
7370
|
+
type: "activity",
|
|
7371
|
+
label: `Running approved ${exactApprovedToolCall.name} action`,
|
|
7372
|
+
tool: exactApprovedToolCall.name,
|
|
7373
|
+
});
|
|
7374
|
+
await executeAgentToolCall({
|
|
7375
|
+
actions: requestActions,
|
|
7376
|
+
name: exactApprovedToolCall.name,
|
|
7377
|
+
input: exactApprovedToolCall.input,
|
|
7378
|
+
callId: exactApprovedToolCall.callId,
|
|
7379
|
+
signal: agentLoopOpts.signal,
|
|
7380
|
+
ownerEmail,
|
|
7381
|
+
orgId: getRequestOrgId() ?? null,
|
|
7382
|
+
threadId: effectiveThreadId,
|
|
7383
|
+
turnId: effectiveTurnId,
|
|
7384
|
+
approvedToolCalls: approvedToolCallsForExecution,
|
|
7385
|
+
onApprovalRequired: approvalHooks.onApprovalRequired,
|
|
7386
|
+
consumeApproval: approvalHooks.consumeApproval,
|
|
7387
|
+
send,
|
|
7388
|
+
});
|
|
7389
|
+
return;
|
|
7390
|
+
}
|
|
7272
7391
|
try {
|
|
7273
7392
|
const { getObservabilityConfig, instrumentAgentLoop } = await import("../observability/traces.js");
|
|
7274
7393
|
const obsConfig = await getObservabilityConfig();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface AgentToolApprovalBinding {
|
|
2
|
+
ownerEmail: string;
|
|
3
|
+
orgId?: string | null;
|
|
4
|
+
threadId?: string | null;
|
|
5
|
+
turnId?: string | null;
|
|
6
|
+
toolName: string;
|
|
7
|
+
callId: string;
|
|
8
|
+
approvalKey: string;
|
|
9
|
+
expiresAt?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function hashAgentToolApprovalKey(approvalKey: string): string;
|
|
12
|
+
export declare function createAgentToolApproval(binding: AgentToolApprovalBinding): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Atomically consume the server-created approval for one exact tool call.
|
|
15
|
+
* Client history and approval keys are only lookup input; the pending row is
|
|
16
|
+
* the authorization boundary and cannot be manufactured by the client.
|
|
17
|
+
*/
|
|
18
|
+
export declare function consumeAgentToolApproval(binding: AgentToolApprovalBinding): Promise<boolean>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { getDbExec, intType, isPostgres, retryOnDdlRace, } from "../db/client.js";
|
|
3
|
+
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
4
|
+
const APPROVAL_TTL_MS = 15 * 60_000;
|
|
5
|
+
const APPROVAL_CLEANUP_AGE_MS = 24 * 60 * 60_000;
|
|
6
|
+
let initPromise;
|
|
7
|
+
async function ensureAgentToolApprovalTable() {
|
|
8
|
+
if (!initPromise) {
|
|
9
|
+
initPromise = (async () => {
|
|
10
|
+
const createSql = `CREATE TABLE IF NOT EXISTS agent_tool_approvals (
|
|
11
|
+
id TEXT PRIMARY KEY,
|
|
12
|
+
owner_email TEXT NOT NULL,
|
|
13
|
+
org_id TEXT,
|
|
14
|
+
thread_id TEXT,
|
|
15
|
+
turn_id TEXT,
|
|
16
|
+
tool_name TEXT NOT NULL,
|
|
17
|
+
call_id TEXT NOT NULL,
|
|
18
|
+
approval_key_hash TEXT NOT NULL,
|
|
19
|
+
status TEXT NOT NULL,
|
|
20
|
+
expires_at ${intType()} NOT NULL,
|
|
21
|
+
consumed_at ${intType()},
|
|
22
|
+
created_at ${intType()} NOT NULL,
|
|
23
|
+
updated_at ${intType()} NOT NULL
|
|
24
|
+
)`;
|
|
25
|
+
const indexSql = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_binding
|
|
26
|
+
ON agent_tool_approvals(owner_email, org_id, thread_id, tool_name, call_id, status)`;
|
|
27
|
+
if (isPostgres()) {
|
|
28
|
+
await ensureTableExists("agent_tool_approvals", createSql);
|
|
29
|
+
await ensureIndexExists("idx_agent_tool_approvals_binding", indexSql);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const client = getDbExec();
|
|
33
|
+
await retryOnDdlRace(() => client.execute(createSql));
|
|
34
|
+
await retryOnDdlRace(() => client.execute(indexSql));
|
|
35
|
+
})().catch((error) => {
|
|
36
|
+
initPromise = undefined;
|
|
37
|
+
throw error;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return initPromise;
|
|
41
|
+
}
|
|
42
|
+
export function hashAgentToolApprovalKey(approvalKey) {
|
|
43
|
+
return createHash("sha256").update(approvalKey).digest("hex");
|
|
44
|
+
}
|
|
45
|
+
export async function createAgentToolApproval(binding) {
|
|
46
|
+
await ensureAgentToolApprovalTable();
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const expiresAt = binding.expiresAt ?? now + APPROVAL_TTL_MS;
|
|
49
|
+
await getDbExec().execute({
|
|
50
|
+
sql: `INSERT INTO agent_tool_approvals
|
|
51
|
+
(id, owner_email, org_id, thread_id, turn_id, tool_name, call_id,
|
|
52
|
+
approval_key_hash, status, expires_at, consumed_at, created_at, updated_at)
|
|
53
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, NULL, ?, ?)`,
|
|
54
|
+
args: [
|
|
55
|
+
`agent-tool-approval-${randomUUID()}`,
|
|
56
|
+
binding.ownerEmail,
|
|
57
|
+
binding.orgId ?? null,
|
|
58
|
+
binding.threadId ?? null,
|
|
59
|
+
binding.turnId ?? null,
|
|
60
|
+
binding.toolName,
|
|
61
|
+
binding.callId,
|
|
62
|
+
hashAgentToolApprovalKey(binding.approvalKey),
|
|
63
|
+
expiresAt,
|
|
64
|
+
now,
|
|
65
|
+
now,
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
// Cleanup is hygiene only. Expired rows remain unusable because consume uses
|
|
69
|
+
// an atomic status and expiry predicate, so cleanup failures must not affect
|
|
70
|
+
// the approval that was just recorded.
|
|
71
|
+
try {
|
|
72
|
+
await getDbExec().execute({
|
|
73
|
+
sql: `DELETE FROM agent_tool_approvals
|
|
74
|
+
WHERE expires_at < ? AND status <> 'pending'`,
|
|
75
|
+
args: [now - APPROVAL_CLEANUP_AGE_MS],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.warn("[agent] Could not clean expired tool approval records", error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Atomically consume the server-created approval for one exact tool call.
|
|
84
|
+
* Client history and approval keys are only lookup input; the pending row is
|
|
85
|
+
* the authorization boundary and cannot be manufactured by the client.
|
|
86
|
+
*/
|
|
87
|
+
export async function consumeAgentToolApproval(binding) {
|
|
88
|
+
await ensureAgentToolApprovalTable();
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
const result = await getDbExec().execute({
|
|
91
|
+
sql: `UPDATE agent_tool_approvals
|
|
92
|
+
SET status = 'consumed', consumed_at = ?, updated_at = ?
|
|
93
|
+
WHERE id = (
|
|
94
|
+
SELECT id FROM agent_tool_approvals
|
|
95
|
+
WHERE owner_email = ?
|
|
96
|
+
AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
|
|
97
|
+
AND ((thread_id IS NULL AND CAST(? AS TEXT) IS NULL) OR thread_id = ?)
|
|
98
|
+
AND tool_name = ?
|
|
99
|
+
AND call_id = ?
|
|
100
|
+
AND approval_key_hash = ?
|
|
101
|
+
AND status = 'pending'
|
|
102
|
+
AND expires_at > ?
|
|
103
|
+
ORDER BY created_at DESC
|
|
104
|
+
LIMIT 1
|
|
105
|
+
)
|
|
106
|
+
AND status = 'pending'`,
|
|
107
|
+
args: [
|
|
108
|
+
now,
|
|
109
|
+
now,
|
|
110
|
+
binding.ownerEmail,
|
|
111
|
+
binding.orgId ?? null,
|
|
112
|
+
binding.orgId ?? null,
|
|
113
|
+
binding.threadId ?? null,
|
|
114
|
+
binding.threadId ?? null,
|
|
115
|
+
binding.toolName,
|
|
116
|
+
binding.callId,
|
|
117
|
+
hashAgentToolApprovalKey(binding.approvalKey),
|
|
118
|
+
now,
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
return result.rowsAffected === 1;
|
|
122
|
+
}
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -189,7 +189,7 @@ export interface AgentChatRequest {
|
|
|
189
189
|
model?: string;
|
|
190
190
|
/** Per-request engine override (sent alongside model for cross-provider switches). */
|
|
191
191
|
engine?: string;
|
|
192
|
-
/** Per-request
|
|
192
|
+
/** Per-request effort override (ephemeral, from the composer picker). */
|
|
193
193
|
effort?: ReasoningEffort;
|
|
194
194
|
/** Usage-tracking label for this call (e.g. "chat", "summarize"). Default: "chat". */
|
|
195
195
|
usageLabel?: string;
|
|
@@ -471,7 +471,14 @@ async function executeClaudeCliRun(options) {
|
|
|
471
471
|
});
|
|
472
472
|
try {
|
|
473
473
|
const result = await runClaudeCodeParticipant({
|
|
474
|
-
|
|
474
|
+
// Claude's driver uses acceptEdits, which is the closest available
|
|
475
|
+
// mapping for auto-edit and full-auto. Keep ask-before-edit on the
|
|
476
|
+
// read-only watchdog path because this non-interactive runner has no
|
|
477
|
+
// approval channel to honor an edit prompt safely.
|
|
478
|
+
role: options.permissionMode === "auto-edit" ||
|
|
479
|
+
options.permissionMode === "full-auto"
|
|
480
|
+
? "driver"
|
|
481
|
+
: "watchdog",
|
|
475
482
|
prompt: buildClaudeCliPrompt(options.run, options.prompt),
|
|
476
483
|
cwd,
|
|
477
484
|
model,
|
|
@@ -604,7 +611,9 @@ async function executeClaudeCliRun(options) {
|
|
|
604
611
|
}
|
|
605
612
|
function buildClaudeCliPrompt(run, prompt) {
|
|
606
613
|
const permissionMode = run.permissionMode ?? "full-auto";
|
|
607
|
-
const mode = permissionMode === "
|
|
614
|
+
const mode = permissionMode === "auto-edit" || permissionMode === "full-auto"
|
|
615
|
+
? "Auto"
|
|
616
|
+
: "Plan";
|
|
608
617
|
const modeInstruction = mode === "Plan"
|
|
609
618
|
? "Inspect and explain only. Do not edit files or run mutating commands."
|
|
610
619
|
: "Edit and verify as needed. Do not create, switch, reset, rebase, or stash git branches.";
|
|
@@ -24,6 +24,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
24
24
|
import { Tooltip as DesignSystemTooltip } from "@agent-native/toolkit/design-system";
|
|
25
25
|
import { IconMessageCircle, IconMessageDots, IconTerminal2, IconSettings, IconLayoutSidebarRightCollapse, IconLayoutGrid, IconCheck, IconPlus, IconX, IconDotsVertical, IconHistory, IconArrowsHorizontal, IconArrowsMaximize, IconExternalLink, } from "@tabler/icons-react";
|
|
26
26
|
import React, { useState, useEffect, useRef, useCallback, useMemo, lazy, Suspense, startTransition, } from "react";
|
|
27
|
+
import { flushSync } from "react-dom";
|
|
27
28
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from "./components/ui/dropdown-menu.js";
|
|
28
29
|
import { normalizeTooltipText } from "./components/ui/tooltip.js";
|
|
29
30
|
import { ErrorReportActions } from "./ErrorReportActions.js";
|
|
@@ -1700,6 +1701,26 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1700
1701
|
return next;
|
|
1701
1702
|
});
|
|
1702
1703
|
}, []);
|
|
1704
|
+
// `view-transition-name` is only legal to carry while a transition is
|
|
1705
|
+
// actually capturing. Left on permanently it makes the panel its own
|
|
1706
|
+
// stacking context and the containing block for every fixed/absolute
|
|
1707
|
+
// descendant, and enlists it as a captured group in unrelated transitions
|
|
1708
|
+
// (any React Router `viewTransition` navigation), which is how overlays end
|
|
1709
|
+
// up painted at stale offsets. Apply it only around the drawer morph, and
|
|
1710
|
+
// flush it into the DOM first: `startViewTransition` captures the old state
|
|
1711
|
+
// before it invokes the callback, so a name applied in a normal React commit
|
|
1712
|
+
// would land too late to be captured.
|
|
1713
|
+
const [drawerMorphing, setDrawerMorphing] = useState(false);
|
|
1714
|
+
const runDrawerMorph = useCallback((apply) => {
|
|
1715
|
+
flushSync(() => setDrawerMorphing(true));
|
|
1716
|
+
const settle = () => setDrawerMorphing(false);
|
|
1717
|
+
const transition = startAgentChatViewTransition(apply);
|
|
1718
|
+
if (!transition) {
|
|
1719
|
+
settle();
|
|
1720
|
+
return;
|
|
1721
|
+
}
|
|
1722
|
+
transition.finished.then(settle, settle);
|
|
1723
|
+
}, []);
|
|
1703
1724
|
const snapTo75Percent = useCallback(() => {
|
|
1704
1725
|
if (drawerExitTimerRef.current !== null) {
|
|
1705
1726
|
clearTimeout(drawerExitTimerRef.current);
|
|
@@ -1719,8 +1740,8 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1719
1740
|
}
|
|
1720
1741
|
catch { }
|
|
1721
1742
|
};
|
|
1722
|
-
|
|
1723
|
-
}, [drawerPlaceholderWidth, isWideDrawer, width]);
|
|
1743
|
+
runDrawerMorph(apply);
|
|
1744
|
+
}, [runDrawerMorph, drawerPlaceholderWidth, isWideDrawer, width]);
|
|
1724
1745
|
const exitWideDrawer = useCallback(() => {
|
|
1725
1746
|
if (!isWideDrawer || drawerExitTimerRef.current !== null)
|
|
1726
1747
|
return;
|
|
@@ -1738,8 +1759,8 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1738
1759
|
setIsWideDrawer(false);
|
|
1739
1760
|
}, SIDEBAR_ANIMATION_MS + 32);
|
|
1740
1761
|
};
|
|
1741
|
-
|
|
1742
|
-
}, [drawerPlaceholderWidth, isWideDrawer]);
|
|
1762
|
+
runDrawerMorph(apply);
|
|
1763
|
+
}, [runDrawerMorph, drawerPlaceholderWidth, isWideDrawer]);
|
|
1743
1764
|
const handleResizeStart = useCallback(() => setIsResizing(true), []);
|
|
1744
1765
|
const handleResizeEnd = useCallback(() => setIsResizing(false), []);
|
|
1745
1766
|
const isLeft = position === "left";
|
|
@@ -1820,7 +1841,9 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1820
1841
|
borderLeft: isLeft ? "none" : "1px solid hsl(var(--border))",
|
|
1821
1842
|
borderRight: isLeft ? "1px solid hsl(var(--border))" : "none",
|
|
1822
1843
|
display: "flex",
|
|
1823
|
-
|
|
1844
|
+
...(drawerMorphing
|
|
1845
|
+
? { viewTransitionName: SIDEBAR_DRAWER_VIEW_TRANSITION_NAME }
|
|
1846
|
+
: null),
|
|
1824
1847
|
};
|
|
1825
1848
|
}
|
|
1826
1849
|
else {
|
|
@@ -1841,7 +1864,9 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1841
1864
|
display: desktopAnimationEnabled || panelOpen ? "flex" : "none",
|
|
1842
1865
|
minWidth: desktopAnimationEnabled ? 0 : undefined,
|
|
1843
1866
|
pointerEvents: desktopAnimationEnabled && !panelOpen ? "none" : undefined,
|
|
1844
|
-
|
|
1867
|
+
...(drawerMorphing
|
|
1868
|
+
? { viewTransitionName: SIDEBAR_DRAWER_VIEW_TRANSITION_NAME }
|
|
1869
|
+
: null),
|
|
1845
1870
|
};
|
|
1846
1871
|
}
|
|
1847
1872
|
// Mount the live chat surface only while visible or actively needed. Keeping
|
|
@@ -136,6 +136,7 @@ type QueuedMessage = {
|
|
|
136
136
|
recoveryAction?: AgentRecoveryAction;
|
|
137
137
|
trackInRunsTray?: boolean;
|
|
138
138
|
hideUserMessage?: boolean;
|
|
139
|
+
approvedToolCalls?: string[];
|
|
139
140
|
/**
|
|
140
141
|
* Model/engine/effort snapshotted at enqueue time, for the same reason
|
|
141
142
|
* `requestMode` is: the picker is global and live, so a queue that flushes
|
|
@@ -306,7 +307,7 @@ export interface AssistantChatProps {
|
|
|
306
307
|
defaultModel?: string;
|
|
307
308
|
/** Selected engine override for this conversation */
|
|
308
309
|
selectedEngine?: string;
|
|
309
|
-
/** Selected
|
|
310
|
+
/** Selected effort override for this conversation */
|
|
310
311
|
selectedEffort?: ReasoningEffort;
|
|
311
312
|
/** Available engine/model list for the model picker */
|
|
312
313
|
availableModels?: Array<{
|
|
@@ -319,7 +320,7 @@ export interface AssistantChatProps {
|
|
|
319
320
|
modelListLoading?: boolean;
|
|
320
321
|
/** Callback when user picks a model from the picker */
|
|
321
322
|
onModelChange?: (model: string, engine: string) => void;
|
|
322
|
-
/** Callback when user picks
|
|
323
|
+
/** Callback when user picks an effort from the picker */
|
|
323
324
|
onEffortChange?: (effort: ReasoningEffort) => void;
|
|
324
325
|
/**
|
|
325
326
|
* Optional secondary model menu (e.g. an image-generation model) shown inside
|
|
@@ -3102,7 +3102,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3102
3102
|
...(messageAttachments.length > 0
|
|
3103
3103
|
? { attachments: messageAttachments }
|
|
3104
3104
|
: {}),
|
|
3105
|
-
...createUserMessageRunConfig(next.references, next.requestMode, next.recoveryAction, next.trackInRunsTray,
|
|
3105
|
+
...createUserMessageRunConfig(next.references, next.requestMode, next.recoveryAction, next.trackInRunsTray, next.approvedToolCalls, next.id, next.hideUserMessage, {
|
|
3106
3106
|
model: next.model,
|
|
3107
3107
|
engine: next.engine,
|
|
3108
3108
|
effort: next.effort,
|
|
@@ -3433,7 +3433,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3433
3433
|
stopActiveRunRef.current({ preserveQueuedMessages: true });
|
|
3434
3434
|
}, [applyLocalQueuedMessages]);
|
|
3435
3435
|
const visibleQueuedMessages = useMemo(() => queuedMessages.filter((message) => !message.hideUserMessage), [queuedMessages]);
|
|
3436
|
-
const addToQueue = useCallback(async (text, images, references, attachments, requestMode, intent = "queued", recoveryAction, includeComposerContext = false, trackInRunsTray = false, preserveReconnectAutoRecoveryBudget = false, hideUserMessage = false, submitMessageId) => {
|
|
3436
|
+
const addToQueue = useCallback(async (text, images, references, attachments, requestMode, intent = "queued", recoveryAction, includeComposerContext = false, trackInRunsTray = false, preserveReconnectAutoRecoveryBudget = false, hideUserMessage = false, submitMessageId, approvedToolCalls) => {
|
|
3437
3437
|
if (isAgentChatSubmitCancelled(submitMessageId))
|
|
3438
3438
|
return;
|
|
3439
3439
|
if (engineSetupRequired) {
|
|
@@ -3582,6 +3582,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3582
3582
|
recoveryAction,
|
|
3583
3583
|
trackInRunsTray,
|
|
3584
3584
|
hideUserMessage,
|
|
3585
|
+
approvedToolCalls,
|
|
3585
3586
|
...modelSnapshot,
|
|
3586
3587
|
},
|
|
3587
3588
|
]);
|
|
@@ -3602,6 +3603,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3602
3603
|
recoveryAction,
|
|
3603
3604
|
trackInRunsTray,
|
|
3604
3605
|
hideUserMessage,
|
|
3606
|
+
approvedToolCalls,
|
|
3605
3607
|
...modelSnapshot,
|
|
3606
3608
|
},
|
|
3607
3609
|
]);
|
|
@@ -3615,7 +3617,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
3615
3617
|
...(messageAttachments.length > 0
|
|
3616
3618
|
? { attachments: messageAttachments }
|
|
3617
3619
|
: {}),
|
|
3618
|
-
...createUserMessageRunConfig(references, effectiveRequestMode, recoveryAction, trackInRunsTray,
|
|
3620
|
+
...createUserMessageRunConfig(references, effectiveRequestMode, recoveryAction, trackInRunsTray, approvedToolCalls, undefined, hideUserMessage),
|
|
3619
3621
|
});
|
|
3620
3622
|
}
|
|
3621
3623
|
catch (error) {
|
|
@@ -4006,31 +4008,16 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
4006
4008
|
getApprovalResolution,
|
|
4007
4009
|
onApprovalResolved: recordApprovalResolution,
|
|
4008
4010
|
onApprove: (approvalKey) => {
|
|
4009
|
-
|
|
4010
|
-
appendThreadMessage({
|
|
4011
|
-
role: "user",
|
|
4012
|
-
content: [
|
|
4013
|
-
{
|
|
4014
|
-
type: "text",
|
|
4015
|
-
text: "Approved. Go ahead and run the requested action.",
|
|
4016
|
-
},
|
|
4017
|
-
],
|
|
4018
|
-
...createUserMessageRunConfig(undefined, execMode === "plan"
|
|
4019
|
-
? "plan"
|
|
4020
|
-
: execMode === "build"
|
|
4021
|
-
? "act"
|
|
4022
|
-
: undefined, undefined, undefined, [approvalKey]),
|
|
4023
|
-
});
|
|
4011
|
+
void addToQueue("Approved. Go ahead and run the requested action.", undefined, undefined, undefined, undefined, "queued", undefined, false, false, false, false, undefined, [approvalKey]);
|
|
4024
4012
|
},
|
|
4025
4013
|
...(approvalActions?.onDeny ? { onDeny: approvalActions.onDeny } : {}),
|
|
4026
4014
|
...(approvalActions?.onAlwaysAllow
|
|
4027
4015
|
? { onAlwaysAllow: approvalActions.onAlwaysAllow }
|
|
4028
4016
|
: {}),
|
|
4029
4017
|
}), [
|
|
4030
|
-
|
|
4018
|
+
addToQueue,
|
|
4031
4019
|
execMode,
|
|
4032
4020
|
getApprovalResolution,
|
|
4033
|
-
markOptimisticRunning,
|
|
4034
4021
|
approvalActions,
|
|
4035
4022
|
recordApprovalResolution,
|
|
4036
4023
|
]);
|
|
@@ -39,4 +39,4 @@ export { AGENT_SIDEBAR_DEFAULT_MAX_WIDTH, AGENT_SIDEBAR_MIN_WIDTH, AGENT_SIDEBAR
|
|
|
39
39
|
export { clearReservedToolRenderersForTests, clearToolRenderersForTests, registerActionChatRenderer, registerFallbackToolRenderer, registerReservedActionChatRenderer, registerReservedFallbackToolRenderer, registerReservedToolRenderer, registerToolRenderer, resolveToolRenderer, type ActionChatRendererRegistration, type ToolRendererComponent, type ToolRendererContext, type ToolRendererMatch, type ToolRendererProps, type ToolRendererRegistration, } from "../chat/tool-render-registry.js";
|
|
40
40
|
export * from "../chat/connectors.js";
|
|
41
41
|
export * from "../chat/runtime.js";
|
|
42
|
-
export { CHAT_FIRST_APP_LAYOUT_STORAGE_KEY, CHAT_FIRST_DEFAULT_APP_IDS, CHAT_FIRST_SURFACE_TABS_STORAGE_KEY, CHAT_FIRST_SURFACE_PANEL_STORAGE_KEY, CHAT_FIRST_SURFACE_WIDTH_DEFAULT, CHAT_FIRST_SURFACE_WIDTH_MIN, clampChatFirstSurfaceWidth, CHAT_FIRST_MODE_STORAGE_KEY, CHAT_FIRST_MODE_CHANGED_EVENT, CHAT_FIRST_OPEN_APP_EVENT, CHAT_FIRST_OPEN_BROWSER_EVENT, CHAT_FIRST_WATCH_SESSION_EVENT, CHAT_FIRST_SURFACE_CATALOG, chatFirstSurfaceTabId, closeChatFirstSessionWatch, emitChatFirstOpenApp, emitChatFirstOpenBrowser, emitChatFirstSessionWatch, getChatFirstSessionWatchStore, getChatFirstSurfaceTabsStore, getChatFirstSurfacePanelStore, orderChatFirstAppIds, readChatFirstAppLayout, resolveChatFirstAppTarget, resolveChatFirstBrowserTarget, resolveChatFirstSessionId, normalizeChatFirstSessionReference, readChatFirstMode, readChatFirstModeState, readChatFirstSurfaceWidth, subscribeChatFirstOpenApp, subscribeChatFirstOpenBrowser, subscribeChatFirstSessionWatch, useChatFirstSessionWatch, useChatFirstSurfaceResize, useChatFirstSurfacePanel, useChatFirstSurfaceTabs, writeChatFirstAppLayout, writeChatFirstMode, writeChatFirstSurfaceWidth, type ChatFirstAppLayoutPreference, type ChatFirstModeReadResult, type ChatFirstAppRegistration, type ChatFirstAppResolution, type ChatFirstAppTarget, type ChatFirstAgentActivity, type ChatFirstAgentActivityStatus, type ChatFirstOpenAppDelivery, type ChatFirstOpenAppDetail, type ChatFirstOpenBrowserDetail, type ChatFirstBrowserResolution, type ChatFirstSessionKind, type ChatFirstSessionReference, type ChatFirstSessionWatchDelivery, type ChatFirstSessionWatchState, type ChatFirstSessionWatchStore, type ChatFirstSurfaceKind, type ChatFirstSurfaceTab, type ChatFirstSurfacePanelState, type ChatFirstSurfacePanelStore, type ChatFirstSurfaceTabsState, type ChatFirstSurfaceTabsStore, } from "../chat-first.js";
|
|
42
|
+
export { CHAT_FIRST_APP_LAYOUT_STORAGE_KEY, CHAT_FIRST_DEFAULT_APP_IDS, CHAT_FIRST_SURFACE_TABS_STORAGE_KEY, CHAT_FIRST_SURFACE_PANEL_STORAGE_KEY, CHAT_FIRST_SURFACE_WIDTH_DEFAULT, CHAT_FIRST_SURFACE_WIDTH_MIN, clampChatFirstSurfaceWidth, CHAT_FIRST_MODE_STORAGE_KEY, CHAT_FIRST_MODE_CHANGED_EVENT, CHAT_FIRST_OPEN_APP_EVENT, CHAT_FIRST_OPEN_BROWSER_EVENT, CHAT_FIRST_WATCH_SESSION_EVENT, CHAT_FIRST_SURFACE_CATALOG, chatFirstSurfaceTabId, closeChatFirstSessionWatch, emitChatFirstOpenApp, emitChatFirstOpenBrowser, emitChatFirstSessionWatch, getChatFirstSessionWatchStore, getChatFirstSurfaceTabsStore, getChatFirstSurfacePanelStore, orderChatFirstAppIds, readChatFirstAppLayout, resolveChatFirstAppTarget, resolveChatFirstBrowserTarget, resolveChatFirstSessionId, normalizeChatFirstSessionReference, readChatFirstMode, readChatFirstModeState, readChatFirstSurfaceWidth, subscribeChatFirstOpenApp, subscribeChatFirstOpenBrowser, subscribeChatFirstSessionWatch, useChatFirstSessionWatch, useChatFirstSurfaceResize, useChatFirstSurfacePanel, useChatFirstSurfaceTabs, writeChatFirstAppLayout, writeChatFirstMode, writeChatFirstSurfaceWidth, type ChatFirstAppLayoutPreference, type ChatFirstModeReadResult, type ChatFirstAppRegistration, type ChatFirstAppResolution, type ChatFirstAppTarget, type ChatFirstAppSurfacePlacement, type ChatFirstAgentActivity, type ChatFirstAgentActivityStatus, type ChatFirstOpenAppDelivery, type ChatFirstOpenAppDetail, type ChatFirstOpenBrowserDetail, type ChatFirstBrowserResolution, type ChatFirstSessionKind, type ChatFirstSessionReference, type ChatFirstSessionWatchDelivery, type ChatFirstSessionWatchState, type ChatFirstSessionWatchStore, type ChatFirstSurfaceKind, type ChatFirstSurfaceTab, type ChatFirstSurfacePanelState, type ChatFirstSurfacePanelStore, type ChatFirstSurfaceTabsState, type ChatFirstSurfaceTabsStore, } from "../chat-first.js";
|
|
@@ -85,6 +85,16 @@ const LARGE_INPUT_TOOL_NAMES = new Set([
|
|
|
85
85
|
]);
|
|
86
86
|
const MAX_HISTORY_LARGE_TOOL_ARGS_CHARS = 200_000;
|
|
87
87
|
const STARTUP_RESPONSE_TIMEOUT_MS = 45_000;
|
|
88
|
+
function dispatchTerminalChatUiCleanup(tabId) {
|
|
89
|
+
if (typeof window === "undefined")
|
|
90
|
+
return;
|
|
91
|
+
// Some terminal outcomes are synthesized by the adapter after recovery
|
|
92
|
+
// (for example, a completed tool whose final response timed out) and never
|
|
93
|
+
// pass through a terminal SSE frame. Clear both halves of the status UI so
|
|
94
|
+
// the wrap-up result cannot leave a preparation label or "Resuming" state.
|
|
95
|
+
window.dispatchEvent(new CustomEvent("agent-chat:activity-clear", { detail: { tabId } }));
|
|
96
|
+
window.dispatchEvent(new CustomEvent("agent-chat:stream-progress", { detail: { tabId } }));
|
|
97
|
+
}
|
|
88
98
|
// ── Background follow mode ──────────────────────────────────────────────────
|
|
89
99
|
// For server-continued runs (dispatchMode starts with "background" or equals
|
|
90
100
|
// "foreground-self-chain") the SERVER normally chains continuation chunks
|
|
@@ -3526,6 +3536,7 @@ export function createAgentChatAdapter(options) {
|
|
|
3526
3536
|
}
|
|
3527
3537
|
finally {
|
|
3528
3538
|
if (typeof window !== "undefined") {
|
|
3539
|
+
dispatchTerminalChatUiCleanup(tabId);
|
|
3529
3540
|
window.dispatchEvent(new CustomEvent("agentNative.chatRunning", {
|
|
3530
3541
|
detail: { isRunning: false, tabId },
|
|
3531
3542
|
}));
|
|
@@ -42,7 +42,7 @@ export interface AgentChatMessage {
|
|
|
42
42
|
model?: string;
|
|
43
43
|
/** Engine preference paired with model for cross-provider switches. */
|
|
44
44
|
engine?: string;
|
|
45
|
-
/**
|
|
45
|
+
/** Effort preference paired with model. */
|
|
46
46
|
effort?: ReasoningEffort;
|
|
47
47
|
/**
|
|
48
48
|
* Execution mode for this submitted turn. When omitted, sendToAgentChat
|
|
@@ -264,6 +264,8 @@ function rememberStreamingText(resetKey, targetText, visibleText) {
|
|
|
264
264
|
if (oldestKey !== undefined)
|
|
265
265
|
smoothStreamingTextCache.delete(oldestKey);
|
|
266
266
|
}
|
|
267
|
+
/** Stable placeholder so the grapheme ref stays `string[]` before it is seeded. */
|
|
268
|
+
const EMPTY_GRAPHEMES = [];
|
|
267
269
|
export function useSmoothStreamingText(targetText, streaming, resetKey) {
|
|
268
270
|
const prefersReducedMotion = usePrefersReducedMotion();
|
|
269
271
|
const [visibleText, setVisibleText] = useState(() => {
|
|
@@ -276,9 +278,19 @@ export function useSmoothStreamingText(targetText, streaming, resetKey) {
|
|
|
276
278
|
return sliceGraphemes(targetText, graphemes, initialSmoothStreamingGraphemeCount(graphemes));
|
|
277
279
|
});
|
|
278
280
|
const visibleTextRef = useRef(visibleText);
|
|
279
|
-
const visibleCountRef = useRef(splitStreamingTextGraphemes(visibleText).length);
|
|
280
281
|
const targetTextRef = useRef(targetText);
|
|
281
|
-
|
|
282
|
+
// `useRef(expr)` evaluates `expr` on EVERY render and throws it away after
|
|
283
|
+
// mount. Segmenting here therefore ran a full Intl.Segmenter pass per commit
|
|
284
|
+
// — up to 60Hz per streaming message — and, because `visibleText` is a strict
|
|
285
|
+
// prefix of `targetText`, that pass could never hit the incremental path and
|
|
286
|
+
// evicted the shared segmenter cache for every other live stream too. Seed
|
|
287
|
+
// once; the streaming effect below keeps both refs authoritative after that.
|
|
288
|
+
const visibleCountRef = useRef(-1);
|
|
289
|
+
const targetGraphemesRef = useRef(EMPTY_GRAPHEMES);
|
|
290
|
+
if (visibleCountRef.current < 0) {
|
|
291
|
+
targetGraphemesRef.current = splitStreamingTextGraphemes(targetText);
|
|
292
|
+
visibleCountRef.current = splitStreamingTextGraphemes(visibleText).length;
|
|
293
|
+
}
|
|
282
294
|
const frameRef = useRef(null);
|
|
283
295
|
const lastCommitAtRef = useRef(0);
|
|
284
296
|
const pauseUntilRef = useRef(0);
|