@agent-native/core 0.84.57 → 0.84.58
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/core/CHANGELOG.md +8 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action.ts +11 -0
- package/corpus/core/src/agent/production-agent.ts +24 -46
- package/corpus/core/src/client/AssistantChat.tsx +69 -12
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
- package/corpus/core/src/client/conversation/use-near-bottom-autoscroll.ts +45 -2
- package/corpus/core/src/coding-tools/run-code.ts +1 -0
- package/corpus/core/src/integrations/webhook-handler.ts +10 -9
- package/corpus/core/src/server/action-discovery.ts +3 -0
- package/corpus/core/src/server/credential-provider.ts +11 -6
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +29 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +9 -1
- package/corpus/templates/design/actions/list-design-extensions.ts +2 -2
- package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +11 -89
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +314 -181
- package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +92 -134
- package/corpus/templates/design/app/components/design/EditPanel.tsx +93 -19
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +18 -26
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +220 -192
- package/corpus/templates/design/app/components/design/code-workbench-theme.ts +150 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +5 -7
- package/corpus/templates/design/app/i18n-data.ts +61 -77
- package/corpus/templates/design/app/lib/design-import.ts +4 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +97 -48
- package/corpus/templates/design/changelog/2026-07-02-design-inspector-and-canvas-controls-stay-consistent-when-se.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-fixed-a-crash-when-opening-the-code-editor.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-fixed-motion-drawer-open-transition.md +6 -0
- package/corpus/templates/design/changelog/2026-07-02-improved-design-assets-panel.md +6 -0
- package/dist/action.d.ts +8 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +3 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/production-agent.d.ts +10 -11
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +21 -44
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +61 -11
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
- package/dist/client/conversation/use-near-bottom-autoscroll.js +43 -2
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
- package/dist/coding-tools/run-code.d.ts.map +1 -1
- package/dist/coding-tools/run-code.js +1 -0
- package/dist/coding-tools/run-code.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/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/integrations/webhook-handler.d.ts +1 -0
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +9 -9
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +3 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/credential-provider.d.ts +4 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +13 -7
- package/dist/server/credential-provider.js.map +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.84.58
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cdc9033: Recover durable background chat runs when action input preparation stops making byte progress, and avoid duplicate tool cards when reconnects replay completed tool events.
|
|
8
|
+
- cdc9033: Remove the shadow from agent chat context scope pills.
|
|
9
|
+
- cdc9033: Prevent agent chat sidebars from jittering around nested scroll areas and suppress replayed reconnect tool cards that are already rendered in the saved thread.
|
|
10
|
+
|
|
3
11
|
## 0.84.57
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.84.
|
|
3
|
+
"version": "0.84.58",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -345,6 +345,10 @@ interface DefineActionWithSchema<
|
|
|
345
345
|
* Only set this manually when you need to override the inference — e.g. a
|
|
346
346
|
* POST action that only reads data but can't use GET for a protocol reason. */
|
|
347
347
|
readOnly?: boolean;
|
|
348
|
+
/** Set false for read-only tools that should stay available in Act mode but
|
|
349
|
+
* must not run during Plan mode because they perform substantive work
|
|
350
|
+
* rather than lightweight inspection. Defaults to allowed when read-only. */
|
|
351
|
+
allowInPlanMode?: boolean;
|
|
348
352
|
/** If true, the agent may execute this action concurrently with other
|
|
349
353
|
* read-only or parallel-safe tool calls emitted in the same model turn.
|
|
350
354
|
* Only set this for mutating actions that are internally concurrency-safe
|
|
@@ -460,6 +464,9 @@ interface DefineActionWithParams<
|
|
|
460
464
|
/** If true, the framework will NOT emit a screen-refresh change event after a
|
|
461
465
|
* successful call. Auto-inferred as `true` when `http.method === "GET"`. */
|
|
462
466
|
readOnly?: boolean;
|
|
467
|
+
/** Set false for read-only tools that should stay available in Act mode but
|
|
468
|
+
* must not run during Plan mode. See the schema overload above. */
|
|
469
|
+
allowInPlanMode?: boolean;
|
|
463
470
|
/** If true, the agent may execute this action concurrently with other
|
|
464
471
|
* read-only or parallel-safe tool calls emitted in the same model turn. */
|
|
465
472
|
parallelSafe?: boolean;
|
|
@@ -525,6 +532,7 @@ export interface ActionDefinition<TInput, TReturn> {
|
|
|
525
532
|
readonly maxBodyBytes?: number;
|
|
526
533
|
readonly agentTool?: boolean;
|
|
527
534
|
readonly readOnly?: boolean;
|
|
535
|
+
readonly allowInPlanMode?: boolean;
|
|
528
536
|
readonly parallelSafe?: boolean;
|
|
529
537
|
readonly toolCallable?: boolean;
|
|
530
538
|
readonly publicAgent?: PublicAgentActionConfig;
|
|
@@ -752,6 +760,9 @@ export function defineAction(options: any) {
|
|
|
752
760
|
: {}),
|
|
753
761
|
...(typeof agentTool === "boolean" ? { agentTool } : {}),
|
|
754
762
|
...(typeof readOnly === "boolean" ? { readOnly } : {}),
|
|
763
|
+
...(typeof options.allowInPlanMode === "boolean"
|
|
764
|
+
? { allowInPlanMode: options.allowInPlanMode }
|
|
765
|
+
: {}),
|
|
755
766
|
...(typeof parallelSafe === "boolean" ? { parallelSafe } : {}),
|
|
756
767
|
...(typeof toolCallable === "boolean" ? { toolCallable } : {}),
|
|
757
768
|
...(publicAgent ? { publicAgent } : {}),
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
parseFrontmatter,
|
|
27
27
|
} from "../resources/metadata.js";
|
|
28
28
|
import {
|
|
29
|
-
|
|
29
|
+
canUseDeployCredentialFallbackForRequest,
|
|
30
30
|
readDeployCredentialEnv,
|
|
31
31
|
} from "../server/credential-provider.js";
|
|
32
32
|
import { readBody } from "../server/h3-helpers.js";
|
|
@@ -457,32 +457,16 @@ export function engineToProvider(engineName: string): string {
|
|
|
457
457
|
return engineName.startsWith("ai-sdk:") ? engineName.slice(7) : engineName;
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
/**
|
|
461
|
-
* Returns true when this process should block generic deploy-level provider
|
|
462
|
-
* credentials for signed-in chat requests.
|
|
463
|
-
*
|
|
464
|
-
* Self-hosted single-tenant deployments keep the env-var fallback so the
|
|
465
|
-
* original BYO-server UX continues to work without a per-user key.
|
|
466
|
-
*/
|
|
467
|
-
function shouldBlockDeployCredentialFallback(): boolean {
|
|
468
|
-
return !isDeployCredentialFallbackAllowed();
|
|
469
|
-
}
|
|
470
|
-
|
|
471
460
|
/**
|
|
472
461
|
* Resolve the active engine's provider and look up the user's API key for it.
|
|
473
462
|
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
* prompt logging tied to the deployment owner) — exactly the prior-incident
|
|
479
|
-
* pattern we hit on 2026-04-29.
|
|
480
|
-
*
|
|
481
|
-
* Single-tenant (local-dev, self-hosted SQLite) keeps the env fallback.
|
|
463
|
+
* If the owner has no scoped key, fall back to provider keys supplied by the
|
|
464
|
+
* hosting environment only when the current request can safely use deploy-level
|
|
465
|
+
* credentials. This is a read from process-level config, not a request-scoped
|
|
466
|
+
* write to `process.env`.
|
|
482
467
|
*
|
|
483
|
-
* Callers
|
|
484
|
-
*
|
|
485
|
-
* another deployment-key fallback after this must keep the same gate.
|
|
468
|
+
* Callers that layer another deployment-key fallback after this should keep the
|
|
469
|
+
* same precedence: scoped key first, host-provided env key second.
|
|
486
470
|
*/
|
|
487
471
|
export async function getOwnerActiveApiKey(
|
|
488
472
|
ownerEmail: string | null | undefined,
|
|
@@ -495,14 +479,7 @@ export async function getOwnerActiveApiKey(
|
|
|
495
479
|
const provider = engineToProvider(activeEngine);
|
|
496
480
|
const userKey = await getOwnerApiKey(provider, ownerEmail);
|
|
497
481
|
if (userKey) return userKey;
|
|
498
|
-
if (
|
|
499
|
-
// Shared hosted default: refuse the env fallback. A null user
|
|
500
|
-
// (unauthenticated / background context with no owner) gets undefined
|
|
501
|
-
// here too — there's no user to bill, and the call site must surface a
|
|
502
|
-
// "configure a key" error to the requester rather than silently using
|
|
503
|
-
// the deploy key.
|
|
504
|
-
return undefined;
|
|
505
|
-
}
|
|
482
|
+
if (!canUseDeployCredentialFallbackForRequest()) return undefined;
|
|
506
483
|
const envVar = PROVIDER_TO_ENV[provider];
|
|
507
484
|
return envVar ? readDeployCredentialEnv(envVar) : undefined;
|
|
508
485
|
} catch {
|
|
@@ -552,6 +529,10 @@ export interface ActionEntry {
|
|
|
552
529
|
/** If true, completion does NOT trigger a screen-refresh change event.
|
|
553
530
|
* Set automatically by `defineAction` when `http.method === "GET"`. */
|
|
554
531
|
readOnly?: boolean;
|
|
532
|
+
/** False keeps a read-only tool available in Act mode but hides/blocks it in
|
|
533
|
+
* Plan mode. Use for tools that perform substantive work even without
|
|
534
|
+
* mutating state. */
|
|
535
|
+
allowInPlanMode?: boolean;
|
|
555
536
|
/** If true, this action can run concurrently with other same-turn
|
|
556
537
|
* read-only/parallel-safe tool calls. Only use for actions that handle
|
|
557
538
|
* their own write ordering and idempotency. */
|
|
@@ -705,6 +686,7 @@ export function isPlanModeToolCallAllowed(
|
|
|
705
686
|
input: unknown,
|
|
706
687
|
entry: ActionEntry,
|
|
707
688
|
): boolean {
|
|
689
|
+
if (entry.allowInPlanMode === false) return false;
|
|
708
690
|
if (PLAN_MODE_BLOCKED_READONLY_TOOLS.has(name)) return false;
|
|
709
691
|
|
|
710
692
|
if (name === "web-request") {
|
|
@@ -802,6 +784,7 @@ export function createPlanModeActionRegistry(
|
|
|
802
784
|
|
|
803
785
|
for (const [name, entry] of Object.entries(actions)) {
|
|
804
786
|
if (name === TOOL_SEARCH_ACTION_NAME) continue;
|
|
787
|
+
if (entry.allowInPlanMode === false) continue;
|
|
805
788
|
if (PLAN_MODE_BLOCKED_READONLY_TOOLS.has(name)) continue;
|
|
806
789
|
|
|
807
790
|
const allowedActions = PLAN_MODE_ALLOWED_ACTIONS[name];
|
|
@@ -4760,15 +4743,14 @@ export function createProductionAgentHandler(
|
|
|
4760
4743
|
// for that provider instead of the global active engine's provider.
|
|
4761
4744
|
// DIAGNOSTIC-ONLY: bracket per-owner API-key resolution (settings/app_secrets reads).
|
|
4762
4745
|
workerStep("apikey_start");
|
|
4746
|
+
const allowDeployCredentialFallback =
|
|
4747
|
+
canUseDeployCredentialFallbackForRequest();
|
|
4763
4748
|
let userApiKey: string | undefined;
|
|
4764
4749
|
if (requestEngine) {
|
|
4765
4750
|
const provider = engineToProvider(requestEngine);
|
|
4766
4751
|
userApiKey = await getOwnerApiKey(provider, ownerEmail);
|
|
4767
|
-
if (!userApiKey &&
|
|
4768
|
-
//
|
|
4769
|
-
// hosted deploys never silently substitute the deploy-level key for
|
|
4770
|
-
// an authenticated user (see getOwnerActiveApiKey for the full
|
|
4771
|
-
// rationale).
|
|
4752
|
+
if (!userApiKey && allowDeployCredentialFallback) {
|
|
4753
|
+
// Read-only env fallback for the requested provider.
|
|
4772
4754
|
const envVar = PROVIDER_TO_ENV[provider];
|
|
4773
4755
|
userApiKey = envVar ? readDeployCredentialEnv(envVar) : undefined;
|
|
4774
4756
|
}
|
|
@@ -4779,16 +4761,12 @@ export function createProductionAgentHandler(
|
|
|
4779
4761
|
workerStep("apikey_done");
|
|
4780
4762
|
|
|
4781
4763
|
// `options.apiKey` is the value the template constructed the plugin with
|
|
4782
|
-
// (
|
|
4783
|
-
//
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
const effectiveApiKey =
|
|
4788
|
-
? userApiKey
|
|
4789
|
-
: (userApiKey ??
|
|
4790
|
-
options.apiKey ??
|
|
4791
|
-
readDeployCredentialEnv("ANTHROPIC_API_KEY"));
|
|
4764
|
+
// (often wired from a deployment env var). Honor it as host-provided
|
|
4765
|
+
// read-only configuration after scoped keys when deploy fallback is safe.
|
|
4766
|
+
const hostApiKey = allowDeployCredentialFallback
|
|
4767
|
+
? (options.apiKey ?? readDeployCredentialEnv("ANTHROPIC_API_KEY"))
|
|
4768
|
+
: undefined;
|
|
4769
|
+
const effectiveApiKey = userApiKey ?? hostApiKey;
|
|
4792
4770
|
|
|
4793
4771
|
// Resolve engine — per-request engine override takes priority
|
|
4794
4772
|
// DIAGNOSTIC-ONLY: bracket engine resolution (Builder credential / app-default
|
|
@@ -588,6 +588,61 @@ export function reconnectActivityFallbackContent(
|
|
|
588
588
|
];
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
function toolCallIdFromContentPart(part: unknown): string | null {
|
|
592
|
+
if (!part || typeof part !== "object") return null;
|
|
593
|
+
const candidate = part as { type?: unknown; toolCallId?: unknown };
|
|
594
|
+
if (candidate.type !== "tool-call") return null;
|
|
595
|
+
return typeof candidate.toolCallId === "string" && candidate.toolCallId
|
|
596
|
+
? candidate.toolCallId
|
|
597
|
+
: null;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function toolCallPartHasResult(part: unknown): boolean {
|
|
601
|
+
if (!part || typeof part !== "object") return false;
|
|
602
|
+
const candidate = part as { type?: unknown; result?: unknown };
|
|
603
|
+
return candidate.type === "tool-call" && "result" in candidate;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function collectRenderedToolCallStates(
|
|
607
|
+
messages: readonly unknown[],
|
|
608
|
+
): Map<string, { hasResult: boolean }> {
|
|
609
|
+
const states = new Map<string, { hasResult: boolean }>();
|
|
610
|
+
for (const message of messages) {
|
|
611
|
+
const msg = (message as { message?: unknown })?.message ?? message;
|
|
612
|
+
const content = (msg as { content?: unknown })?.content;
|
|
613
|
+
if (!Array.isArray(content)) continue;
|
|
614
|
+
for (const part of content) {
|
|
615
|
+
const id = toolCallIdFromContentPart(part);
|
|
616
|
+
if (!id) continue;
|
|
617
|
+
const existing = states.get(id);
|
|
618
|
+
states.set(id, {
|
|
619
|
+
hasResult: Boolean(existing?.hasResult || toolCallPartHasResult(part)),
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return states;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function dedupeReconnectContentAgainstMessages(
|
|
627
|
+
content: ContentPart[],
|
|
628
|
+
messages: readonly unknown[],
|
|
629
|
+
): ContentPart[] {
|
|
630
|
+
if (content.length === 0 || messages.length === 0) return content;
|
|
631
|
+
const renderedToolCallStates = collectRenderedToolCallStates(messages);
|
|
632
|
+
if (renderedToolCallStates.size === 0) return content;
|
|
633
|
+
|
|
634
|
+
let changed = false;
|
|
635
|
+
const filtered = content.filter((part) => {
|
|
636
|
+
const id = toolCallIdFromContentPart(part);
|
|
637
|
+
const existing = id ? renderedToolCallStates.get(id) : undefined;
|
|
638
|
+
if (!id || !existing) return true;
|
|
639
|
+
if (toolCallPartHasResult(part) && !existing.hasResult) return true;
|
|
640
|
+
changed = true;
|
|
641
|
+
return false;
|
|
642
|
+
});
|
|
643
|
+
return changed ? filtered : content;
|
|
644
|
+
}
|
|
645
|
+
|
|
591
646
|
const RECOVERY_USER_MESSAGE_PREFIXES = [
|
|
592
647
|
"Continue from where you left off",
|
|
593
648
|
"Continue from where you stopped",
|
|
@@ -1540,15 +1595,10 @@ const AssistantChatInner = forwardRef<
|
|
|
1540
1595
|
});
|
|
1541
1596
|
const reconnectActivityContent = useMemo(
|
|
1542
1597
|
() =>
|
|
1543
|
-
|
|
1598
|
+
isReconnecting || reconnectFrozen
|
|
1544
1599
|
? reconnectActivityFallbackContent(runningActivityTool)
|
|
1545
1600
|
: [],
|
|
1546
|
-
[
|
|
1547
|
-
isReconnecting,
|
|
1548
|
-
reconnectFrozen,
|
|
1549
|
-
reconnectContent.length,
|
|
1550
|
-
runningActivityTool,
|
|
1551
|
-
],
|
|
1601
|
+
[isReconnecting, reconnectFrozen, runningActivityTool],
|
|
1552
1602
|
);
|
|
1553
1603
|
const lastBroadcastRunningRef = useRef(isRunning);
|
|
1554
1604
|
const tiptapRef = useRef<TiptapComposerHandle>(null);
|
|
@@ -3287,14 +3337,19 @@ const AssistantChatInner = forwardRef<
|
|
|
3287
3337
|
],
|
|
3288
3338
|
);
|
|
3289
3339
|
|
|
3340
|
+
const visibleReconnectContent = useMemo(
|
|
3341
|
+
() => dedupeReconnectContentAgainstMessages(reconnectContent, messages),
|
|
3342
|
+
[messages, reconnectContent],
|
|
3343
|
+
);
|
|
3344
|
+
|
|
3290
3345
|
const autoscrollFollowKey = useMemo(
|
|
3291
3346
|
() =>
|
|
3292
3347
|
[
|
|
3293
3348
|
messages.map(messageFollowKey).join(";"),
|
|
3294
3349
|
`q:${queuedMessages.map(queuedMessageFollowKey).join("|")}`,
|
|
3295
|
-
`r:${reconnectContentFollowKey(
|
|
3350
|
+
`r:${reconnectContentFollowKey(visibleReconnectContent)}`,
|
|
3296
3351
|
].join(";;"),
|
|
3297
|
-
[messages, queuedMessages,
|
|
3352
|
+
[messages, queuedMessages, visibleReconnectContent],
|
|
3298
3353
|
);
|
|
3299
3354
|
|
|
3300
3355
|
const {
|
|
@@ -3802,11 +3857,13 @@ const AssistantChatInner = forwardRef<
|
|
|
3802
3857
|
/>
|
|
3803
3858
|
)}
|
|
3804
3859
|
{(isReconnecting || reconnectFrozen) &&
|
|
3805
|
-
|
|
3806
|
-
<ReconnectStreamMessage
|
|
3860
|
+
visibleReconnectContent.length > 0 && (
|
|
3861
|
+
<ReconnectStreamMessage
|
|
3862
|
+
content={visibleReconnectContent}
|
|
3863
|
+
/>
|
|
3807
3864
|
)}
|
|
3808
3865
|
{(isReconnecting || reconnectFrozen) &&
|
|
3809
|
-
|
|
3866
|
+
visibleReconnectContent.length === 0 &&
|
|
3810
3867
|
reconnectActivityContent.length > 0 && (
|
|
3811
3868
|
<ReconnectStreamMessage
|
|
3812
3869
|
content={reconnectActivityContent}
|
|
@@ -283,7 +283,7 @@ function ScopeBadge({
|
|
|
283
283
|
<PopoverTrigger asChild>
|
|
284
284
|
<button
|
|
285
285
|
type="button"
|
|
286
|
-
className="inline-flex h-7 min-w-0 max-w-full cursor-pointer items-center gap-1.5 rounded-t-lg border border-b-0 border-input bg-background px-3 text-muted-foreground
|
|
286
|
+
className="inline-flex h-7 min-w-0 max-w-full cursor-pointer items-center gap-1.5 rounded-t-lg border border-b-0 border-input bg-background px-3 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground sm:max-w-72"
|
|
287
287
|
aria-label={heading}
|
|
288
288
|
>
|
|
289
289
|
<IconLink size={11} className="shrink-0 opacity-70" />
|
|
@@ -7,6 +7,41 @@ export interface UseNearBottomAutoscrollOptions {
|
|
|
7
7
|
enabled?: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
function eventElementTarget(event: Event): HTMLElement | null {
|
|
11
|
+
const target = event.target;
|
|
12
|
+
if (!(target instanceof Node)) return null;
|
|
13
|
+
if (target instanceof HTMLElement) return target;
|
|
14
|
+
return target.parentElement;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function canScrollElementVertically(
|
|
18
|
+
element: HTMLElement,
|
|
19
|
+
deltaY: number,
|
|
20
|
+
): boolean {
|
|
21
|
+
if (element.scrollHeight <= element.clientHeight) return false;
|
|
22
|
+
const style = window.getComputedStyle(element);
|
|
23
|
+
const overflowY = style.overflowY || style.overflow;
|
|
24
|
+
if (!/(auto|scroll|overlay)/.test(overflowY)) return false;
|
|
25
|
+
if (deltaY < 0) return element.scrollTop > 0;
|
|
26
|
+
if (deltaY > 0) {
|
|
27
|
+
return element.scrollTop + element.clientHeight < element.scrollHeight - 1;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function nestedScrollableConsumesVerticalIntent(
|
|
33
|
+
event: Event,
|
|
34
|
+
root: HTMLElement,
|
|
35
|
+
deltaY: number,
|
|
36
|
+
): boolean {
|
|
37
|
+
let element = eventElementTarget(event);
|
|
38
|
+
while (element && element !== root) {
|
|
39
|
+
if (canScrollElementVertically(element, deltaY)) return true;
|
|
40
|
+
element = element.parentElement;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
10
45
|
export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
11
46
|
followKey,
|
|
12
47
|
streaming = false,
|
|
@@ -83,6 +118,9 @@ export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
|
83
118
|
let lastScrollHeight = el.scrollHeight;
|
|
84
119
|
|
|
85
120
|
const onWheel = (event: WheelEvent) => {
|
|
121
|
+
if (nestedScrollableConsumesVerticalIntent(event, el, event.deltaY)) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
86
124
|
if (event.deltaY < 0) detachFromBottom();
|
|
87
125
|
};
|
|
88
126
|
|
|
@@ -95,7 +133,10 @@ export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
|
95
133
|
if (nextTouchY == null) return;
|
|
96
134
|
const lastTouchY = lastTouchYRef.current;
|
|
97
135
|
if (lastTouchY != null && nextTouchY > lastTouchY) {
|
|
98
|
-
|
|
136
|
+
const deltaY = lastTouchY - nextTouchY;
|
|
137
|
+
if (!nestedScrollableConsumesVerticalIntent(event, el, deltaY)) {
|
|
138
|
+
detachFromBottom();
|
|
139
|
+
}
|
|
99
140
|
}
|
|
100
141
|
lastTouchYRef.current = nextTouchY;
|
|
101
142
|
};
|
|
@@ -104,7 +145,8 @@ export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
|
104
145
|
lastTouchYRef.current = null;
|
|
105
146
|
};
|
|
106
147
|
|
|
107
|
-
const onScroll = () => {
|
|
148
|
+
const onScroll = (event: Event) => {
|
|
149
|
+
if (event.target !== el) return;
|
|
108
150
|
const previousScrollTop = lastScrollTopRef.current;
|
|
109
151
|
const nextScrollTop = el.scrollTop;
|
|
110
152
|
const nextScrollHeight = el.scrollHeight;
|
|
@@ -133,6 +175,7 @@ export function useNearBottomAutoscroll<TElement extends HTMLElement>({
|
|
|
133
175
|
event.key === "Home" ||
|
|
134
176
|
(event.key === " " && event.shiftKey)
|
|
135
177
|
) {
|
|
178
|
+
if (nestedScrollableConsumesVerticalIntent(event, el, -1)) return;
|
|
136
179
|
detachFromBottom();
|
|
137
180
|
}
|
|
138
181
|
};
|
|
@@ -36,7 +36,10 @@ import { isLocalDatabase } from "../db/client.js";
|
|
|
36
36
|
import { resolveOrgIdForEmail } from "../org/context.js";
|
|
37
37
|
import { withConfiguredAppBasePath } from "../server/app-base-path.js";
|
|
38
38
|
import { FRAMEWORK_ROUTE_PREFIX } from "../server/core-routes-plugin.js";
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
canUseDeployCredentialFallbackForRequest,
|
|
41
|
+
readDeployCredentialEnv,
|
|
42
|
+
} from "../server/credential-provider.js";
|
|
40
43
|
import { runWithRequestContext } from "../server/request-context.js";
|
|
41
44
|
import { A2A_CONTINUATION_QUEUED_MARKER } from "./a2a-continuation-marker.js";
|
|
42
45
|
import { signInternalToken } from "./internal-token.js";
|
|
@@ -128,11 +131,6 @@ function explicitEngineName(
|
|
|
128
131
|
return undefined;
|
|
129
132
|
}
|
|
130
133
|
|
|
131
|
-
function isMultiTenantDeploy(): boolean {
|
|
132
|
-
if (process.env.NODE_ENV !== "production") return false;
|
|
133
|
-
return !isLocalDatabase();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
134
|
function collectToolResultSummaries(
|
|
137
135
|
completedRun: ActiveRun,
|
|
138
136
|
): A2AToolResultSummary[] {
|
|
@@ -142,23 +140,26 @@ function collectToolResultSummaries(
|
|
|
142
140
|
.map((event) => ({ tool: event.tool, result: event.result }));
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
async function resolveIntegrationApiKey(
|
|
143
|
+
export async function resolveIntegrationApiKey(
|
|
146
144
|
engineOption: WebhookHandlerOptions["engine"],
|
|
147
145
|
ownerEmail: string,
|
|
148
146
|
fallbackApiKey: string,
|
|
149
147
|
): Promise<string | undefined> {
|
|
148
|
+
const canUseDeployFallback = canUseDeployCredentialFallbackForRequest();
|
|
150
149
|
const engineName = explicitEngineName(engineOption);
|
|
151
150
|
if (engineName) {
|
|
152
151
|
const provider = engineToProvider(engineName);
|
|
153
152
|
const userApiKey = await getOwnerApiKey(provider, ownerEmail);
|
|
154
|
-
if (userApiKey
|
|
153
|
+
if (userApiKey) return userApiKey;
|
|
154
|
+
if (!canUseDeployFallback) return undefined;
|
|
155
155
|
const envVar = PROVIDER_TO_ENV[provider];
|
|
156
156
|
const providerEnvKey = envVar ? readDeployCredentialEnv(envVar) : undefined;
|
|
157
157
|
return providerEnvKey || fallbackApiKey.trim() || undefined;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
const userApiKey = await getOwnerActiveApiKey(ownerEmail);
|
|
161
|
-
if (userApiKey
|
|
161
|
+
if (userApiKey) return userApiKey;
|
|
162
|
+
if (!canUseDeployFallback) return undefined;
|
|
162
163
|
return fallbackApiKey.trim() || undefined;
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -186,6 +186,9 @@ function preserveActionFlags(entry: Record<string, any>): Partial<ActionEntry> {
|
|
|
186
186
|
out.requiresAuth = entry.requiresAuth;
|
|
187
187
|
}
|
|
188
188
|
if (typeof entry.readOnly === "boolean") out.readOnly = entry.readOnly;
|
|
189
|
+
if (typeof entry.allowInPlanMode === "boolean") {
|
|
190
|
+
out.allowInPlanMode = entry.allowInPlanMode;
|
|
191
|
+
}
|
|
189
192
|
if (typeof entry.parallelSafe === "boolean") {
|
|
190
193
|
out.parallelSafe = entry.parallelSafe;
|
|
191
194
|
}
|
|
@@ -78,6 +78,10 @@ export function readDeployCredentialEnv(key: string): string | undefined {
|
|
|
78
78
|
* single-tenant contexts. In hosted production with a shared database, every
|
|
79
79
|
* signed-in user needs their own user/org/workspace credential so one deploy
|
|
80
80
|
* key does not silently power another tenant's chat.
|
|
81
|
+
*
|
|
82
|
+
* @deprecated Use `canUseDeployCredentialFallbackForRequest()` for generic
|
|
83
|
+
* provider secrets. This stricter helper remains for legacy call sites with
|
|
84
|
+
* identity-bearing deploy credentials.
|
|
81
85
|
*/
|
|
82
86
|
export function isDeployCredentialFallbackAllowed(): boolean {
|
|
83
87
|
if (!isProductionLikeRuntime()) return true;
|
|
@@ -86,9 +90,10 @@ export function isDeployCredentialFallbackAllowed(): boolean {
|
|
|
86
90
|
|
|
87
91
|
export function canUseDeployCredentialFallbackForRequest(): boolean {
|
|
88
92
|
const email = getRequestUserEmail();
|
|
89
|
-
if (email && isHostedWorkspaceRuntime()) return false;
|
|
90
93
|
if (!email) return true;
|
|
91
|
-
|
|
94
|
+
if (isHostedWorkspaceRuntime()) return false;
|
|
95
|
+
if (!isProductionLikeRuntime()) return true;
|
|
96
|
+
return isLocalDatabase();
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
const BUILDER_CREDENTIAL_KEYS = [
|
|
@@ -962,10 +967,10 @@ export async function resolveSecret(key: string): Promise<string | null> {
|
|
|
962
967
|
}
|
|
963
968
|
// Secrets table not ready — treat as missing.
|
|
964
969
|
}
|
|
965
|
-
//
|
|
966
|
-
//
|
|
967
|
-
//
|
|
968
|
-
//
|
|
970
|
+
// Read deployment-provided env values as fallbacks; framework code must not
|
|
971
|
+
// write to `process.env`, but keys supplied by the host remain valid config.
|
|
972
|
+
// Builder credentials keep a narrower path below because those keys carry a
|
|
973
|
+
// Builder identity rather than just enabling a provider call.
|
|
969
974
|
const envFallback = (
|
|
970
975
|
isBuilderCredentialKey(key)
|
|
971
976
|
? canUseBuilderDeployCredentialFallbackForRequest()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ActionEntry } from "@agent-native/core/server";
|
|
2
|
+
|
|
3
|
+
export const PLAN_MODE_ACT_ONLY_TOOLS = new Set([
|
|
4
|
+
"query-agent-native-analytics",
|
|
5
|
+
"bigquery",
|
|
6
|
+
"provider-api-request",
|
|
7
|
+
"provider-corpus-job",
|
|
8
|
+
"query-staged-dataset",
|
|
9
|
+
"account-deep-dive",
|
|
10
|
+
"hubspot-deals",
|
|
11
|
+
"hubspot-records",
|
|
12
|
+
"gong-calls",
|
|
13
|
+
"jira-search",
|
|
14
|
+
"slack-messages",
|
|
15
|
+
"sentry",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
export function applyAnalyticsPlanModePolicy(
|
|
19
|
+
actions: Record<string, ActionEntry>,
|
|
20
|
+
): Record<string, ActionEntry> {
|
|
21
|
+
return Object.fromEntries(
|
|
22
|
+
Object.entries(actions).map(([name, entry]) => [
|
|
23
|
+
name,
|
|
24
|
+
PLAN_MODE_ACT_ONLY_TOOLS.has(name)
|
|
25
|
+
? { ...entry, allowInPlanMode: false }
|
|
26
|
+
: entry,
|
|
27
|
+
]),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "@agent-native/core/server";
|
|
8
8
|
|
|
9
9
|
import actionsRegistry from "../../.generated/actions-registry.js";
|
|
10
|
+
import { applyAnalyticsPlanModePolicy } from "../lib/agent-chat-plan-mode";
|
|
10
11
|
import { renderDataDictionary } from "../lib/data-dictionary-context";
|
|
11
12
|
import {
|
|
12
13
|
failedDataQueryAttemptMessage,
|
|
@@ -65,6 +66,11 @@ const INITIAL_TOOL_NAMES = [
|
|
|
65
66
|
"navigate",
|
|
66
67
|
];
|
|
67
68
|
|
|
69
|
+
export {
|
|
70
|
+
applyAnalyticsPlanModePolicy,
|
|
71
|
+
PLAN_MODE_ACT_ONLY_TOOLS,
|
|
72
|
+
} from "../lib/agent-chat-plan-mode";
|
|
73
|
+
|
|
68
74
|
function latestUserText(
|
|
69
75
|
messages: AgentLoopFinalResponseGuardContext["messages"],
|
|
70
76
|
): string {
|
|
@@ -160,7 +166,9 @@ function realDataFinalGuard(context: AgentLoopFinalResponseGuardContext) {
|
|
|
160
166
|
|
|
161
167
|
export default createAgentChatPlugin({
|
|
162
168
|
appId: "analytics",
|
|
163
|
-
actions:
|
|
169
|
+
actions: applyAnalyticsPlanModePolicy(
|
|
170
|
+
loadActionsFromStaticRegistry(actionsRegistry),
|
|
171
|
+
),
|
|
164
172
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
165
173
|
finalResponseGuard: realDataFinalGuard,
|
|
166
174
|
// Enable sandboxed JavaScript execution for analytics data processing.
|
|
@@ -68,7 +68,7 @@ const FIRST_PARTY_EXTENSIONS: FirstPartyDesignExtension[] = [
|
|
|
68
68
|
id: "design.asset-library",
|
|
69
69
|
name: "Asset Library",
|
|
70
70
|
description:
|
|
71
|
-
"Browse and insert Design
|
|
71
|
+
"Browse and insert Design primitives, generated or uploaded media, and rendered Figma library components into the active design screen. " +
|
|
72
72
|
"Selection-aware: inserts near the selected element when one is active.",
|
|
73
73
|
icon: "Photo",
|
|
74
74
|
availability: "available",
|
|
@@ -77,7 +77,7 @@ const FIRST_PARTY_EXTENSIONS: FirstPartyDesignExtension[] = [
|
|
|
77
77
|
capabilities: [
|
|
78
78
|
{
|
|
79
79
|
id: "native",
|
|
80
|
-
label: "Browse Design
|
|
80
|
+
label: "Browse Design assets",
|
|
81
81
|
status: "available",
|
|
82
82
|
reason:
|
|
83
83
|
"Uses list-design-native-assets and insert-design-native-asset for editable HTML primitives/components.",
|