@agent-native/core 0.147.0 → 0.148.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 +1 -1
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/analytics/DEVELOPING.md +50 -3
- package/corpus/templates/analytics/actions/purge-first-party-analytics-postgres.ts +190 -0
- package/corpus/templates/analytics/app/i18n/index.ts +14 -29
- package/corpus/templates/analytics/app/i18n-data.ts +10 -10
- package/corpus/templates/analytics/app/pages/Settings.tsx +5 -21
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -3
- package/corpus/templates/analytics/scripts/first-party-analytics-bigquery-backfill.ts +197 -18
- package/corpus/templates/analytics/server/db/schema.ts +23 -0
- package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +3 -1
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +1 -1
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +113 -13
- package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +140 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-volume.ts +207 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +40 -9
- package/corpus/templates/analytics/server/lib/gcloud.ts +148 -20
- package/corpus/templates/analytics/server/plugins/db.ts +42 -0
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/assets/app/i18n/index.ts +14 -29
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/brain/app/i18n/index.ts +14 -29
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/chat/app/i18n/index.ts +14 -29
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +2 -7
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +18 -13
- package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +87 -370
- package/corpus/templates/clips/app/components/settings/slack-section.tsx +59 -101
- package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +136 -213
- package/corpus/templates/clips/app/i18n/en-US.ts +11 -8
- package/corpus/templates/clips/desktop/src/app.tsx +1 -1
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/content/app/i18n/index.ts +14 -29
- package/corpus/templates/crm/app/i18n/index.ts +14 -29
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/design/actions/analyze-brand-assets.ts +15 -15
- package/corpus/templates/design/actions/import-code.ts +2 -43
- package/corpus/templates/design/app/i18n/index.ts +14 -29
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/dispatch/app/i18n-data.ts +1 -1
- package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/factory/AGENTS.md +40 -49
- package/corpus/templates/factory/actions/babysit-agent-native-pull-request.ts +66 -0
- package/corpus/templates/factory/actions/evaluate-triage-item.ts +37 -0
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +20 -0
- package/corpus/templates/factory/actions/get-triage-item.ts +25 -4
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +55 -0
- package/corpus/templates/factory/actions/ingest-github-observation.ts +21 -0
- package/corpus/templates/factory/actions/list-factory-audit.ts +118 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
- package/corpus/templates/factory/actions/list-triage-items.ts +24 -2
- package/corpus/templates/factory/actions/poll-github-sources.ts +60 -0
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +39 -0
- package/corpus/templates/factory/actions/poll-slack-channel.ts +45 -1
- package/corpus/templates/factory/actions/run-factory-automation.ts +1 -1
- package/corpus/templates/factory/actions/save-factory-automation.ts +2 -2
- package/corpus/templates/factory/actions/start-builder-for-item.ts +51 -0
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +685 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +6 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +25 -0
- package/corpus/templates/factory/app/routes/factory.tsx +95 -42
- package/corpus/templates/factory/server/db/schema.ts +37 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +5 -1
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +29 -0
- package/corpus/templates/factory/server/triage/audit.ts +79 -0
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/forms/app/i18n/index.ts +14 -29
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/macros/app/i18n/index.ts +14 -29
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/mail/app/i18n/index.ts +14 -29
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +7 -7
- package/corpus/templates/plan/app/i18n/index.ts +14 -29
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/slides/actions/analyze-brand-assets.ts +15 -15
- package/corpus/templates/slides/actions/import-code.ts +2 -43
- package/corpus/templates/slides/app/i18n/index.ts +14 -29
- package/corpus/templates/slides/server/routes/api/preview-images.get.ts +4 -1
- package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/tasks/app/components/layout/HeaderActions.tsx +1 -84
- package/corpus/templates/tasks/app/hooks/use-mobile.tsx +1 -21
- package/corpus/templates/tasks/app/i18n/index.ts +14 -29
- package/dist/agent/engine/ai-sdk-engine.js +2 -1
- package/dist/agent/engine/openai-compatible-endpoint.d.ts +2 -0
- package/dist/agent/engine/openai-compatible-endpoint.js +5 -1
- package/dist/agent/engine/provider-endpoint-validation.d.ts +9 -0
- package/dist/agent/engine/provider-endpoint-validation.js +26 -0
- package/dist/agent/engine/provider-env-vars.d.ts +1 -1
- package/dist/agent/engine/provider-env-vars.js +1 -1
- package/dist/agent/engine/registry.d.ts +8 -6
- package/dist/agent/engine/registry.js +37 -24
- package/dist/agent/tool-search.d.ts +1 -0
- package/dist/agent/tool-search.js +17 -1
- package/dist/automations/service.js +4 -10
- package/dist/brand-kit/brand-signals.d.ts +13 -0
- package/dist/brand-kit/brand-signals.js +15 -0
- package/dist/brand-kit/index.d.ts +2 -2
- package/dist/brand-kit/index.js +1 -1
- package/dist/cli/plan-blocks.d.ts +3 -18
- package/dist/cli/plan-blocks.js +2 -95
- package/dist/cli/plan-publish-store.d.ts +2 -61
- package/dist/cli/plan-publish-store.js +2 -127
- package/dist/cli/skills-content/connection.d.ts +1 -1
- package/dist/cli/skills-content/connection.js +1 -52
- package/dist/cli/skills-content/visual-recap-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-recap-skill.js +1 -553
- package/dist/client/agent-engine-key.d.ts +14 -4
- package/dist/client/agent-engine-key.js +51 -5
- package/dist/client/agent-provider-catalog.d.ts +20 -0
- package/dist/client/agent-provider-catalog.js +100 -0
- package/dist/client/blocks/index.d.ts +1 -0
- package/dist/client/blocks/index.js +1 -0
- package/dist/client/chat/run-recovery.js +4 -48
- package/dist/client/chat-first.js +4 -3
- package/dist/client/i18n.d.ts +14 -0
- package/dist/client/i18n.js +18 -0
- package/dist/client/settings/ProviderSetupForm.d.ts +21 -0
- package/dist/client/settings/ProviderSetupForm.js +145 -0
- package/dist/client/settings/SettingsPanel.js +52 -49
- package/dist/client/settings/index.d.ts +2 -0
- package/dist/client/settings/index.js +2 -0
- package/dist/coding-tools/run-code.d.ts +5 -0
- package/dist/coding-tools/run-code.js +196 -36
- package/dist/coding-tools/tool-orchestration.d.ts +10 -0
- package/dist/coding-tools/tool-orchestration.js +107 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/jobs/frontmatter.d.ts +9 -0
- package/dist/jobs/frontmatter.js +16 -0
- package/dist/jobs/scheduler.js +8 -8
- package/dist/jobs/tools.d.ts +1 -1
- package/dist/jobs/tools.js +2 -7
- package/dist/localization/default-messages.d.ts +20 -0
- package/dist/localization/default-messages.js +25 -5
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/app-profile.js +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/agent-engines/test-agent-engine.js +34 -10
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/run-code-tools.d.ts +1 -1
- package/dist/server/agent-chat/run-code-tools.js +3 -1
- package/dist/server/agent-chat-plugin.js +6 -6
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.js +43 -12
- package/dist/server/agents-bundle.js +2 -1
- package/dist/server/auth.js +21 -5
- package/dist/server/credential-provider.js +1 -0
- package/dist/server/design-token-utils.d.ts +18 -0
- package/dist/server/design-token-utils.js +31 -0
- package/dist/server/onboarding-html.js +22 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
- package/dist/templates/chat/app/i18n/index.ts +14 -29
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
- package/dist/templates/default/app/i18n/index.ts +14 -29
- package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
- package/dist/triggers/dispatcher.js +1 -8
- package/docs/content/environment-variables.mdx +39 -38
- package/package.json +4 -4
- package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +115 -68
- package/src/templates/chat/app/i18n/index.ts +14 -29
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +115 -68
- package/src/templates/default/app/i18n/index.ts +14 -29
- package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +22 -9
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +115 -68
- package/corpus/templates/plan/app/components/plan/wireframe/sanitize-html.ts +0 -343
|
@@ -6,8 +6,9 @@ import { IconChevronDown, IconChevronRight, IconCheck, IconExternalLink, IconBra
|
|
|
6
6
|
import React, { Suspense, lazy, useState, useEffect, useCallback, useMemo, useRef, } from "react";
|
|
7
7
|
import { PROVIDER_ENV_PLACEHOLDERS } from "../../agent/engine/provider-env-vars.js";
|
|
8
8
|
import { buildSettingsRoute } from "../../navigation/index.js";
|
|
9
|
-
import { saveAgentEngineProviderSettings } from "../agent-engine-key.js";
|
|
9
|
+
import { saveAgentEngineProviderSettings, setAgentEngineProvider, } from "../agent-engine-key.js";
|
|
10
10
|
import { AgentWorkspaceContent } from "../agent-page/AgentWorkspaceContent.js";
|
|
11
|
+
import { getAgentProviderOption, providerIdForEngine, } from "../agent-provider-catalog.js";
|
|
11
12
|
import { agentNativePath } from "../api-path.js";
|
|
12
13
|
import { BuilderBMark } from "../builder-mark.js";
|
|
13
14
|
import { Popover, PopoverContent, PopoverTrigger, } from "../components/ui/popover.js";
|
|
@@ -24,6 +25,7 @@ import { AgentsSection } from "./AgentsSection.js";
|
|
|
24
25
|
import { AutomationsSection } from "./AutomationsSection.js";
|
|
25
26
|
import { DemoModeSection } from "./DemoModeSection.js";
|
|
26
27
|
import { ExtensionsSettingsContent } from "./ExtensionsSettingsContent.js";
|
|
28
|
+
import { AgentProviderPicker } from "./ProviderSetupForm.js";
|
|
27
29
|
import { SecretsSection } from "./SecretsSection.js";
|
|
28
30
|
import { SettingsGroup, SettingsRow } from "./SettingsRow.js";
|
|
29
31
|
import { SettingsSection, SettingsSurfaceProvider, useSettingsSurface, } from "./SettingsSection.js";
|
|
@@ -343,8 +345,8 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
|
|
|
343
345
|
const [baseUrlConfigured, setBaseUrlConfigured] = useState(false);
|
|
344
346
|
const [clearBaseUrl, setClearBaseUrl] = useState(false);
|
|
345
347
|
const [advancedOpen, setAdvancedOpen] = useState(false);
|
|
346
|
-
const [manualSetupOpen, setManualSetupOpen] = useState(false);
|
|
347
348
|
const [applyNote, setApplyNote] = useState(false);
|
|
349
|
+
const [applyError, setApplyError] = useState(null);
|
|
348
350
|
const [testing, setTesting] = useState(false);
|
|
349
351
|
const [testResult, setTestResult] = useState(null);
|
|
350
352
|
const [settingsStatus, setSettingsStatus] = useState(null);
|
|
@@ -420,32 +422,26 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
|
|
|
420
422
|
envVar,
|
|
421
423
|
builderConnected,
|
|
422
424
|
});
|
|
423
|
-
const manualSetupHint =
|
|
424
|
-
? "Provide an OpenRouter key to use OpenRouter models like GLM 5.2."
|
|
425
|
-
: "Choose your AI provider and model.";
|
|
425
|
+
const manualSetupHint = "Select a provider.";
|
|
426
426
|
const engineChanged = selectedEngine !== currentEngine || selectedModel !== currentModel;
|
|
427
|
-
const
|
|
428
|
-
const
|
|
427
|
+
const selectedProvider = providerIdForEngine(selectedEngine) ?? "anthropic";
|
|
428
|
+
const selectedProviderOption = getAgentProviderOption(selectedProvider);
|
|
429
|
+
const isEndpointProvider = selectedProviderOption.supportsEndpoint === true;
|
|
430
|
+
const endpointChanged = isEndpointProvider && (!!baseUrl.trim() || clearBaseUrl);
|
|
429
431
|
const providerSettingsChanged = !!apiKey.trim() || endpointChanged;
|
|
430
|
-
// Hide the Anthropic-via-AI-SDK alias (redundant with the native entry)
|
|
431
|
-
// and Ollama (no API key to set here). The currently-selected engine is
|
|
432
|
-
// always kept so a stale setting doesn't vanish from the picker.
|
|
433
|
-
const providerOptions = engines
|
|
434
|
-
.filter((e) => e.name === selectedEngine ||
|
|
435
|
-
(e.name !== "ai-sdk:anthropic" && e.name !== "ai-sdk:ollama"))
|
|
436
|
-
.map((e) => ({ value: e.name, label: e.label }));
|
|
437
432
|
const modelOptions = latestModelsOnly(selectedEngineInfo?.supportedModels ?? []).map((m) => ({ value: m, label: friendlyModelName(m) }));
|
|
438
433
|
const handleSave = async () => {
|
|
439
|
-
if (!providerSettingsChanged || !envVar)
|
|
434
|
+
if (!providerSettingsChanged || (!envVar && !isEndpointProvider))
|
|
440
435
|
return;
|
|
441
436
|
setSaving(true);
|
|
442
437
|
try {
|
|
443
|
-
const nextBaseUrl =
|
|
438
|
+
const nextBaseUrl = isEndpointProvider ? baseUrl.trim() : "";
|
|
444
439
|
await saveAgentEngineProviderSettings({
|
|
445
|
-
|
|
440
|
+
provider: selectedProvider,
|
|
441
|
+
...(envVar ? { key: envVar } : {}),
|
|
446
442
|
...(apiKey.trim() ? { apiKey } : {}),
|
|
447
443
|
...(nextBaseUrl ? { baseUrl: nextBaseUrl } : {}),
|
|
448
|
-
...(
|
|
444
|
+
...(isEndpointProvider && clearBaseUrl ? { clearBaseUrl: true } : {}),
|
|
449
445
|
});
|
|
450
446
|
setSaved(true);
|
|
451
447
|
setApiKey("");
|
|
@@ -527,40 +523,46 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
|
|
|
527
523
|
}
|
|
528
524
|
};
|
|
529
525
|
const handleApply = async () => {
|
|
526
|
+
setApplyError(null);
|
|
530
527
|
try {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
body: JSON.stringify({
|
|
535
|
-
action: "set",
|
|
536
|
-
engine: selectedEngine,
|
|
537
|
-
model: selectedModel,
|
|
538
|
-
}),
|
|
528
|
+
await setAgentEngineProvider({
|
|
529
|
+
provider: selectedProvider,
|
|
530
|
+
model: selectedModel,
|
|
539
531
|
});
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
532
|
+
setCurrentEngine(selectedEngine);
|
|
533
|
+
setCurrentModel(selectedModel);
|
|
534
|
+
setApplyNote(true);
|
|
535
|
+
refreshSettingsStatus();
|
|
536
|
+
notifyConfigChanged();
|
|
537
|
+
setTimeout(() => setApplyNote(false), 4000);
|
|
538
|
+
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
setApplyError(err instanceof Error ? err.message : String(err));
|
|
548
541
|
}
|
|
549
|
-
catch { }
|
|
550
542
|
};
|
|
551
|
-
return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM",
|
|
543
|
+
return (_jsx(SettingsSection, { id: settingsSectionDomId("llm"), icon: _jsx(IconBrain, { size: 14 }), title: "LLM", required: true, connected: initialLoading ? undefined : anyKeyConfigured, subtitle: isPage
|
|
544
|
+
? undefined
|
|
545
|
+
: t("agentPanel.builderOrOwnKeys", {
|
|
546
|
+
defaultValue: "Choose Builder.io or custom keys.",
|
|
547
|
+
}), grouped: isPage && grouped, open: open, onToggle: onToggle, children: initialLoading ? (_jsx(SettingsLoadingRow, { controlCount: 2 })) : (_jsxs("div", { className: cn(isPage
|
|
552
548
|
? "flex items-center justify-between gap-4 px-5 py-4 sm:px-6"
|
|
553
|
-
: "
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
549
|
+
: "flex items-center justify-between gap-2"), children: [isPage && (_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-foreground", children: "AI provider" }), _jsx("p", { className: "mt-1 text-sm leading-6 text-muted-foreground", children: t("agentPanel.builderOrOwnKeys", {
|
|
550
|
+
defaultValue: "Choose Builder.io or custom keys.",
|
|
551
|
+
}) })] })), _jsxs("div", { className: cn("flex flex-wrap items-center justify-end gap-2"), children: [_jsx(UseBuilderCard, { builderFlow: builderFlow, connectUrl: connectUrl, connected: connected, orgName: orgName, envManaged: envManaged, credentialSource: credentialSource, trackingSource: "llm_settings", trackingFlow: "connect_llm", label: "Connect Builder.io", compact: true }), _jsx(ManualSetupCard, { id: "llm-manual-setup", title: "Custom keys", hint: manualSetupHint, sourceBadge: builderConnected ? undefined : sourceBadge, bare: isPage, popover: true, popoverLabel: settingsConfigured
|
|
552
|
+
? "Manage"
|
|
553
|
+
: t("agentPanel.addOwnKeys", {
|
|
554
|
+
defaultValue: "Custom keys",
|
|
555
|
+
}), children: _jsxs("div", { className: "space-y-2 mb-1", children: [_jsx(AgentProviderPicker, { value: selectedProvider, layout: isPage ? "page" : "compact", onChange: (provider) => {
|
|
556
|
+
const option = getAgentProviderOption(provider);
|
|
557
|
+
setSelectedEngine(option.engine);
|
|
558
|
+
setSelectedModel(option.defaultModel);
|
|
559
559
|
setApiKey("");
|
|
560
560
|
setBaseUrl("");
|
|
561
561
|
setClearBaseUrl(false);
|
|
562
562
|
setAdvancedOpen(false);
|
|
563
|
-
} }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: fieldLabelClass(isPage), children: "Model" }), _jsx("input", { type: "text", list: `model-suggestions-${selectedEngine}`, value: selectedModel, onChange: (e) => setSelectedModel(e.target.value), placeholder: selectedEngineInfo?.defaultModel ?? "e.g. model-id", spellCheck: false, autoComplete: "off", className: textInputClass(isPage), style: isPage ? CONTROL_STYLE_PAGE : CONTROL_STYLE }), modelOptions.length > 0 && (_jsx("datalist", { id: `model-suggestions-${selectedEngine}`, children: modelOptions.map((opt) => (_jsx("option", { value: opt.value, label: opt.label }, opt.value))) }))] }),
|
|
563
|
+
} }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { className: fieldLabelClass(isPage), children: "Model" }), _jsx("input", { type: "text", list: `model-suggestions-${selectedEngine}`, value: selectedModel, onChange: (e) => setSelectedModel(e.target.value), placeholder: selectedEngineInfo?.defaultModel ?? "e.g. model-id", spellCheck: false, autoComplete: "off", className: textInputClass(isPage), style: isPage ? CONTROL_STYLE_PAGE : CONTROL_STYLE }), modelOptions.length > 0 && (_jsx("datalist", { id: `model-suggestions-${selectedEngine}`, children: modelOptions.map((opt) => (_jsx("option", { value: opt.value, label: opt.label }, opt.value))) }))] }), isEndpointProvider && (_jsxs("div", { className: "border-t border-border/70 pt-2", children: [_jsxs(Button, { type: "button", onClick: () => setAdvancedOpen((v) => !v), className: "flex w-full cursor-pointer items-center justify-between gap-2 rounded px-0.5 py-1 text-left hover:text-foreground", children: [_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5 text-[11px] font-medium text-foreground", children: [advancedOpen ? (_jsx(IconChevronDown, { size: 12 })) : (_jsx(IconChevronRight, { size: 12, className: "rtl:-scale-x-100" })), "Advanced"] }), _jsx("span", { className: "truncate text-[10px] text-muted-foreground", children: selectedProvider === "ollama"
|
|
564
|
+
? "Local Ollama endpoint"
|
|
565
|
+
: "OpenAI-compatible endpoint" })] }), advancedOpen && (_jsxs("div", { className: "mt-1.5 space-y-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("p", { className: "text-[12px] font-medium text-foreground", children: "Endpoint URL" }), _jsx("span", { className: "text-[10px] text-muted-foreground", children: baseUrlConfigured ? "Configured" : "Optional" })] }), _jsx("input", { type: "url", value: baseUrl, onChange: (e) => {
|
|
564
566
|
setBaseUrl(e.target.value);
|
|
565
567
|
if (e.target.value.trim())
|
|
566
568
|
setClearBaseUrl(false);
|
|
@@ -569,14 +571,16 @@ function LLMSectionInner({ builderFlow, builderLoading, connectUrl, connected, o
|
|
|
569
571
|
handleSave();
|
|
570
572
|
}, placeholder: baseUrlConfigured
|
|
571
573
|
? "Leave blank to keep current endpoint"
|
|
572
|
-
:
|
|
574
|
+
: selectedProvider === "ollama"
|
|
575
|
+
? "http://localhost:11434"
|
|
576
|
+
: "https://gateway.example/v1", disabled: clearBaseUrl, spellCheck: false, autoComplete: "off", className: "flex h-9 w-full rounded-md border border-border bg-background px-3 text-[12px] text-foreground outline-none transition-colors hover:bg-accent/40 focus:ring-1 focus:ring-accent disabled:opacity-50 placeholder:text-muted-foreground/50", style: CONTROL_STYLE }), baseUrlConfigured && (_jsxs("label", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground", children: [_jsx(Checkbox, { checked: clearBaseUrl, onChange: (checked) => {
|
|
573
577
|
setClearBaseUrl(checked);
|
|
574
578
|
if (checked)
|
|
575
579
|
setBaseUrl("");
|
|
576
|
-
}, "aria-label": "Clear saved endpoint override", className: "shrink-0" }), "Clear saved endpoint override"] })),
|
|
580
|
+
}, "aria-label": "Clear saved endpoint override", className: "shrink-0" }), "Clear saved endpoint override"] })), endpointChanged && (_jsx(Button, { type: "button", intent: "neutral", emphasis: "solid", onClick: handleSave, disabled: saving, className: "rounded bg-accent px-2.5 py-1 text-[10px] font-medium text-foreground hover:bg-accent/80 disabled:opacity-40", children: saving ? (_jsx(IconLoader2, { size: 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: 10 })) : ("Save endpoint") }))] }))] })), envVar && envConfigured ? (_jsxs("div", { className: cn("flex items-center gap-1.5 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), envVar, " configured"] })) : envVar ? (_jsxs("div", { className: "flex gap-1.5", children: [_jsx("input", { type: "password", value: apiKey, onChange: (e) => setApiKey(e.target.value), onKeyDown: (e) => {
|
|
577
581
|
if (e.key === "Enter")
|
|
578
582
|
handleSave();
|
|
579
|
-
}, placeholder: PROVIDER_ENV_PLACEHOLDERS[envVar] ?? "...", className: cn(textInputClass(isPage), "flex-1"), style: isPage ? CONTROL_STYLE_PAGE : undefined }), _jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleSave, disabled: !providerSettingsChanged || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") })] })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { intent: "neutral", emphasis: "outline", onClick: handleTest, disabled: testing, className: pillButtonClass(isPage, "outline"), children: testing ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" }), "Testing\u2026"] })) : ("Test") }), PROVIDER_DOCS[selectedEngine] ? (_jsxs("a", { href: PROVIDER_DOCS[selectedEngine], target: "_blank", rel: "noopener noreferrer", className: cn(pillButtonClass(isPage, "outline"), "no-underline"), children: ["Get an API key", _jsx(IconExternalLink, { size: isPage ? 14 : 10 })] })) : null, engineChanged && (_jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleApply, className: pillButtonClass(isPage, "solid"), children: "Apply" })), settingsStatus != null && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { intent: "danger", emphasis: "outline", onClick: handleDisconnect, className: cn(pillButtonClass(isPage, "outline"), "ms-auto text-muted-foreground hover:bg-destructive/10 hover:text-destructive hover:border-destructive/40"), children: "Disconnect" }) }), _jsx(TooltipContent, { children: "Clear the saved engine \u2014 the app will fall back to the default until you re-apply." })] }))] }), testResult && testResult.ok && (_jsxs("p", { className: cn("flex items-center gap-1 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), "Test passed \u2014 ", testResult.latencyMs, "ms"] })), testResult && testResult.ok === false && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Test failed: ", testResult.error] })), disconnectError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Disconnect failed: ", disconnectError] })), applyNote && (_jsx("p", { className: cn("text-muted-foreground", isPage ? "text-xs" : "text-[10px]"), children: "Changes take effect on next conversation" }))] }) })
|
|
583
|
+
}, placeholder: PROVIDER_ENV_PLACEHOLDERS[envVar] ?? "...", className: cn(textInputClass(isPage), "flex-1"), style: isPage ? CONTROL_STYLE_PAGE : undefined }), _jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleSave, disabled: !providerSettingsChanged || saving, className: pillButtonClass(isPage, "solid"), children: saving ? (_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" })) : saved ? (_jsx(IconCheck, { size: isPage ? 14 : 10 })) : ("Save") })] })) : null, _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { intent: "neutral", emphasis: "outline", onClick: handleTest, disabled: testing, className: pillButtonClass(isPage, "outline"), children: testing ? (_jsxs("span", { className: "flex items-center gap-1", children: [_jsx(IconLoader2, { size: isPage ? 14 : 10, className: "animate-spin" }), "Testing\u2026"] })) : ("Test") }), PROVIDER_DOCS[selectedEngine] ? (_jsxs("a", { href: PROVIDER_DOCS[selectedEngine], target: "_blank", rel: "noopener noreferrer", className: cn(pillButtonClass(isPage, "outline"), "no-underline"), children: ["Get an API key", _jsx(IconExternalLink, { size: isPage ? 14 : 10 })] })) : null, engineChanged && (_jsx(Button, { intent: "primary", emphasis: "solid", onClick: handleApply, className: pillButtonClass(isPage, "solid"), children: "Apply" })), settingsStatus != null && (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { intent: "danger", emphasis: "outline", onClick: handleDisconnect, className: cn(pillButtonClass(isPage, "outline"), "ms-auto text-muted-foreground hover:bg-destructive/10 hover:text-destructive hover:border-destructive/40"), children: "Disconnect" }) }), _jsx(TooltipContent, { children: "Clear the saved engine \u2014 the app will fall back to the default until you re-apply." })] }))] }), testResult && testResult.ok && (_jsxs("p", { className: cn("flex items-center gap-1 text-primary", isPage ? "text-xs" : "text-[10px]"), children: [_jsx(IconCheck, { size: isPage ? 14 : 10 }), "Test passed \u2014 ", testResult.latencyMs, "ms"] })), testResult && testResult.ok === false && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Test failed: ", testResult.error] })), disconnectError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Disconnect failed: ", disconnectError] })), applyError && (_jsxs("p", { className: cn("text-destructive", isPage ? "text-xs" : "text-[10px]"), children: ["Apply failed: ", applyError] })), applyNote && (_jsx("p", { className: cn("text-muted-foreground", isPage ? "text-xs" : "text-[10px]"), children: "Changes take effect on next conversation" }))] }) })] })] })) }));
|
|
580
584
|
}
|
|
581
585
|
function friendlyAppName(appId) {
|
|
582
586
|
return appId
|
|
@@ -587,7 +591,7 @@ function friendlyAppName(appId) {
|
|
|
587
591
|
}
|
|
588
592
|
function AppDefaultModelPicker({ engines, value, disabled, onChange, }) {
|
|
589
593
|
const [open, setOpen] = useState(false);
|
|
590
|
-
const visibleEngines = engines.filter((engine) => engine.name !== "ai-sdk:anthropic"
|
|
594
|
+
const visibleEngines = engines.filter((engine) => engine.name !== "ai-sdk:anthropic");
|
|
591
595
|
const selectedModel = value.includes("::")
|
|
592
596
|
? value.slice(value.indexOf("::") + 2)
|
|
593
597
|
: null;
|
|
@@ -664,8 +668,7 @@ function AppModelDefaultsSectionInner({ grouped = false, open, onToggle, }) {
|
|
|
664
668
|
useEffect(() => load(), [load]);
|
|
665
669
|
const selectedEngineInfo = settings?.engines.find((engine) => engine.name === selectedEngine) ?? null;
|
|
666
670
|
const engineOptions = (settings?.engines ?? [])
|
|
667
|
-
.filter((engine) => engine.name === selectedEngine ||
|
|
668
|
-
(engine.name !== "ai-sdk:anthropic" && engine.name !== "ai-sdk:ollama"))
|
|
671
|
+
.filter((engine) => engine.name === selectedEngine || engine.name !== "ai-sdk:anthropic")
|
|
669
672
|
.map((engine) => ({
|
|
670
673
|
value: engine.name,
|
|
671
674
|
label: engine.name === "builder"
|
|
@@ -8,3 +8,5 @@ export { SettingsGroup, SettingsRow, type SettingsGroupProps, type SettingsRowPr
|
|
|
8
8
|
export { SettingsSection, SettingsSurfaceProvider, useSettingsSurface, type SettingsSurface, } from "./SettingsSection.js";
|
|
9
9
|
export { SettingsLoadingRow, SettingsSkeleton, type SettingsLoadingRowProps, type SettingsSkeletonProps, } from "./SettingsSkeleton.js";
|
|
10
10
|
export { normalizeSettingsSection, settingsSectionDomId, useSettingsPanelController, type SettingsPanelController, type SettingsPanelControllerOptions, } from "./useSettingsPanelController.js";
|
|
11
|
+
export { AGENT_PROVIDER_CATALOG, getAgentProviderOption, providerIdForEngine, type AgentProviderId, type AgentProviderOption, } from "../agent-provider-catalog.js";
|
|
12
|
+
export { AgentProviderPicker, AgentProviderSetupForm, type AgentProviderPickerProps, type AgentProviderSetupFormProps, } from "./ProviderSetupForm.js";
|
|
@@ -8,3 +8,5 @@ export { SettingsGroup, SettingsRow, } from "./SettingsRow.js";
|
|
|
8
8
|
export { SettingsSection, SettingsSurfaceProvider, useSettingsSurface, } from "./SettingsSection.js";
|
|
9
9
|
export { SettingsLoadingRow, SettingsSkeleton, } from "./SettingsSkeleton.js";
|
|
10
10
|
export { normalizeSettingsSection, settingsSectionDomId, useSettingsPanelController, } from "./useSettingsPanelController.js";
|
|
11
|
+
export { AGENT_PROVIDER_CATALOG, getAgentProviderOption, providerIdForEngine, } from "../agent-provider-catalog.js";
|
|
12
|
+
export { AgentProviderPicker, AgentProviderSetupForm, } from "./ProviderSetupForm.js";
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import type { ActionRunContext } from "../action.js";
|
|
32
32
|
import type { ActionEntry } from "../agent/production-agent.js";
|
|
33
|
+
export type SandboxCodeMode = "run-code" | "tool-orchestration";
|
|
33
34
|
export interface RunCodeOptions {
|
|
34
35
|
/**
|
|
35
36
|
* Extra tool names (beyond the default set) that the sandbox bridge will
|
|
@@ -54,6 +55,10 @@ export interface ExecuteSandboxCodeOptions {
|
|
|
54
55
|
getActions: () => Record<string, ActionEntry>;
|
|
55
56
|
/** Extra bridge tool names beyond the defaults. */
|
|
56
57
|
extraBridgeTools?: Set<string>;
|
|
58
|
+
/** Host policy and helper surface to use inside the sandbox. */
|
|
59
|
+
mode?: SandboxCodeMode;
|
|
60
|
+
/** Maximum number of bridged child-tool calls for orchestration mode. */
|
|
61
|
+
maxToolCalls?: number;
|
|
57
62
|
/** Request context (owner/org) applied to bridged tool calls. */
|
|
58
63
|
context?: ActionRunContext;
|
|
59
64
|
}
|
|
@@ -37,6 +37,9 @@ const DEFAULT_TIMEOUT_MS = 120_000;
|
|
|
37
37
|
const MAX_TIMEOUT_MS = 600_000;
|
|
38
38
|
const DEFAULT_MAX_OUTPUT_CHARS = 50_000;
|
|
39
39
|
const MAX_OUTPUT_CHARS = 200_000;
|
|
40
|
+
const TOOL_ORCHESTRATION_DEFAULT_MAX_CALLS = 32;
|
|
41
|
+
const TOOL_ORCHESTRATION_MAX_CALLS = 128;
|
|
42
|
+
const TOOL_ORCHESTRATION_MAX_PROVIDER_PAGES = 20;
|
|
40
43
|
/** Hard cap on bridge request bodies so sandboxed code can't exhaust parent memory. */
|
|
41
44
|
const BRIDGE_MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
42
45
|
/** Tools callable via the sandbox bridge by default. */
|
|
@@ -203,8 +206,9 @@ export function createRunCodeEntry(getActions, opts = {}) {
|
|
|
203
206
|
export async function executeSandboxCode(options) {
|
|
204
207
|
const actions = options.getActions();
|
|
205
208
|
const bridgeToken = crypto.randomBytes(32).toString("hex");
|
|
209
|
+
const mode = options.mode ?? "run-code";
|
|
206
210
|
// Start bridge server — resolves once the server is listening.
|
|
207
|
-
const { bridgePort, getUsedTools, cleanup: cleanupBridge, } = await startBridgeServer(bridgeToken, actions, options.context, DEFAULT_BRIDGE_TOOLS, options.extraBridgeTools ?? new Set());
|
|
211
|
+
const { bridgePort, getUsedTools, cleanup: cleanupBridge, } = await startBridgeServer(bridgeToken, actions, options.context, DEFAULT_BRIDGE_TOOLS, options.extraBridgeTools ?? new Set(), mode, options.maxToolCalls);
|
|
208
212
|
try {
|
|
209
213
|
// Build scrubbed env — only safe POSIX vars, no secrets. The adapter
|
|
210
214
|
// points TMPDIR/TEMP/TMP at the sandbox's own temp dir.
|
|
@@ -228,7 +232,7 @@ export async function executeSandboxCode(options) {
|
|
|
228
232
|
// bridge, env scrub, module, and output formatting stay in the parent
|
|
229
233
|
// regardless of adapter.
|
|
230
234
|
const { stdout, stderr, exitCode, timedOut } = await resolveExecutionSandboxAdapter().run({
|
|
231
|
-
moduleSource: buildSandboxModule(options.code, bridgePort, bridgeToken),
|
|
235
|
+
moduleSource: buildSandboxModule(options.code, bridgePort, bridgeToken, mode),
|
|
232
236
|
env: safeEnv,
|
|
233
237
|
timeoutMs: options.timeoutMs,
|
|
234
238
|
bridgePort,
|
|
@@ -457,8 +461,14 @@ export function createGetCodeExecutionEntry() {
|
|
|
457
461
|
},
|
|
458
462
|
};
|
|
459
463
|
}
|
|
460
|
-
async function startBridgeServer(token, actions, context, defaultTools, extraTools) {
|
|
464
|
+
async function startBridgeServer(token, actions, context, defaultTools, extraTools, mode, maxToolCalls) {
|
|
461
465
|
const usedTools = new Set();
|
|
466
|
+
const callBudget = mode === "tool-orchestration"
|
|
467
|
+
? {
|
|
468
|
+
count: 0,
|
|
469
|
+
max: normalizeToolCallBudget(maxToolCalls),
|
|
470
|
+
}
|
|
471
|
+
: undefined;
|
|
462
472
|
const server = http.createServer((req, res) => {
|
|
463
473
|
if (req.method !== "POST" || req.url !== "/tool") {
|
|
464
474
|
res.writeHead(404);
|
|
@@ -489,7 +499,7 @@ async function startBridgeServer(token, actions, context, defaultTools, extraToo
|
|
|
489
499
|
req.on("end", () => {
|
|
490
500
|
if (rejected)
|
|
491
501
|
return;
|
|
492
|
-
handleBridgeRequest(body, actions, context, defaultTools, extraTools, usedTools, res);
|
|
502
|
+
handleBridgeRequest(body, actions, context, defaultTools, extraTools, usedTools, mode, callBudget, res);
|
|
493
503
|
});
|
|
494
504
|
req.on("error", () => {
|
|
495
505
|
res.writeHead(500);
|
|
@@ -515,7 +525,7 @@ async function startBridgeServer(token, actions, context, defaultTools, extraToo
|
|
|
515
525
|
cleanup,
|
|
516
526
|
};
|
|
517
527
|
}
|
|
518
|
-
function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools, usedTools, res) {
|
|
528
|
+
function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools, usedTools, mode, callBudget, res) {
|
|
519
529
|
let parsed;
|
|
520
530
|
try {
|
|
521
531
|
parsed = JSON.parse(rawBody);
|
|
@@ -546,7 +556,27 @@ function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools
|
|
|
546
556
|
const isReadOnlyAction = entry.readOnly === true &&
|
|
547
557
|
entry.agentTool !== false &&
|
|
548
558
|
entry.toolCallable !== false;
|
|
549
|
-
|
|
559
|
+
const toolArgs = parsed.args ?? {};
|
|
560
|
+
const childArgs = mode === "tool-orchestration"
|
|
561
|
+
? boundToolOrchestrationArgs(toolName, toolArgs)
|
|
562
|
+
: toolArgs;
|
|
563
|
+
if (mode === "tool-orchestration") {
|
|
564
|
+
if (callBudget && callBudget.count >= callBudget.max) {
|
|
565
|
+
res.writeHead(429, { "Content-Type": "application/json" });
|
|
566
|
+
res.end(JSON.stringify({
|
|
567
|
+
error: `Tool-orchestration child-call budget exceeded (${callBudget.max}). Aggregate the current results or increase maxToolCalls within its limit.`,
|
|
568
|
+
}));
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (!isAllowedToolOrchestrationCall(toolName, childArgs, entry)) {
|
|
572
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
573
|
+
res.end(JSON.stringify({
|
|
574
|
+
error: `Tool "${toolName}" is not permitted by tool-orchestration. Only read-only tools and GET/HEAD provider or web reads without staging or saving files are allowed; call mutating tools directly.`,
|
|
575
|
+
}));
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
else if (!defaultTools.has(toolName) &&
|
|
550
580
|
!extraTools.has(toolName) &&
|
|
551
581
|
!isReadOnlyAction) {
|
|
552
582
|
// A registered, agent-exposed action that isn't read-only is a mutation.
|
|
@@ -563,12 +593,13 @@ function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools
|
|
|
563
593
|
}));
|
|
564
594
|
return;
|
|
565
595
|
}
|
|
566
|
-
|
|
596
|
+
if (callBudget)
|
|
597
|
+
callBudget.count += 1;
|
|
567
598
|
usedTools.add(toolName);
|
|
568
599
|
// Run the tool with the parent request context so auth/org/owner resolution
|
|
569
600
|
// works exactly as it does in the normal agent loop.
|
|
570
601
|
entry
|
|
571
|
-
.run(
|
|
602
|
+
.run(childArgs, context)
|
|
572
603
|
.then((result) => {
|
|
573
604
|
const body = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
574
605
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
@@ -580,6 +611,105 @@ function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools
|
|
|
580
611
|
res.end(JSON.stringify({ error: message }));
|
|
581
612
|
});
|
|
582
613
|
}
|
|
614
|
+
function normalizeToolCallBudget(value) {
|
|
615
|
+
if (!Number.isFinite(value) || !value || value <= 0) {
|
|
616
|
+
return TOOL_ORCHESTRATION_DEFAULT_MAX_CALLS;
|
|
617
|
+
}
|
|
618
|
+
return Math.min(Math.floor(value), TOOL_ORCHESTRATION_MAX_CALLS);
|
|
619
|
+
}
|
|
620
|
+
function isAllowedToolOrchestrationCall(toolName, args, entry) {
|
|
621
|
+
if (toolName === "workspace-files") {
|
|
622
|
+
// This is an internal bridge action by design, so agentTool/toolCallable
|
|
623
|
+
// are intentionally not required here. Its operation enum is the policy
|
|
624
|
+
// boundary for the read-only workspace surface.
|
|
625
|
+
return isCallableWithoutApproval(entry) && isReadOnlyWorkspaceRequest(args);
|
|
626
|
+
}
|
|
627
|
+
if (toolName === "provider-api-request") {
|
|
628
|
+
return isReadOnlyProviderApiRequest(args, entry);
|
|
629
|
+
}
|
|
630
|
+
return isReadOnlyOrchestrationAction(args, entry);
|
|
631
|
+
}
|
|
632
|
+
function isCallableWithoutApproval(entry) {
|
|
633
|
+
// The bridge calls ActionEntry.run directly for child reads. A predicate
|
|
634
|
+
// approval gate must therefore fail closed rather than being silently
|
|
635
|
+
// bypassed by this bounded path.
|
|
636
|
+
return entry.needsApproval === undefined || entry.needsApproval === false;
|
|
637
|
+
}
|
|
638
|
+
function isReadOnlyWorkspaceRequest(args) {
|
|
639
|
+
const action = typeof args.action === "string" ? args.action : "";
|
|
640
|
+
return action === "read" || action === "list" || action === "grep";
|
|
641
|
+
}
|
|
642
|
+
function isReadOnlyProviderApiRequest(args, entry) {
|
|
643
|
+
if (!entry.tool ||
|
|
644
|
+
!isCallableWithoutApproval(entry) ||
|
|
645
|
+
entry.agentTool === false ||
|
|
646
|
+
entry.allowInPlanMode === false) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
const method = String(args.method ?? "GET").toUpperCase();
|
|
650
|
+
return ((method === "GET" || method === "HEAD") &&
|
|
651
|
+
args.stageAs == null &&
|
|
652
|
+
args.saveToFile == null);
|
|
653
|
+
}
|
|
654
|
+
function isReadOnlyOrchestrationAction(args, entry) {
|
|
655
|
+
if (!entry.tool ||
|
|
656
|
+
!isCallableWithoutApproval(entry) ||
|
|
657
|
+
entry.agentTool === false ||
|
|
658
|
+
entry.toolCallable === false ||
|
|
659
|
+
entry.allowInPlanMode === false) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
const planMode = entry.planMode;
|
|
663
|
+
if (!planMode)
|
|
664
|
+
return entry.readOnly === true;
|
|
665
|
+
const input = args;
|
|
666
|
+
if (planMode.allowedProperties &&
|
|
667
|
+
Object.keys(args).some((key) => !planMode.allowedProperties.includes(key))) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
if (planMode.omittedProperties?.some((key) => Object.prototype.hasOwnProperty.call(args, key))) {
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
if (Object.entries(planMode.allowedValues ?? {}).some(([key, allowed]) => {
|
|
674
|
+
const value = args[key];
|
|
675
|
+
return (value !== null &&
|
|
676
|
+
value !== undefined &&
|
|
677
|
+
(typeof value !== "string" || !allowed.includes(value)));
|
|
678
|
+
})) {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
const effect = typeof planMode.effect === "function"
|
|
683
|
+
? planMode.effect(input)
|
|
684
|
+
: planMode.effect;
|
|
685
|
+
return effect === "read";
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
console.warn("[run-code] tool-orchestration read-only policy classifier failed closed:", error instanceof Error ? error.message : String(error));
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
function boundToolOrchestrationArgs(toolName, args) {
|
|
693
|
+
if (toolName !== "provider-api-request")
|
|
694
|
+
return args;
|
|
695
|
+
const fetchAllPages = args.fetchAllPages;
|
|
696
|
+
if (!fetchAllPages ||
|
|
697
|
+
typeof fetchAllPages !== "object" ||
|
|
698
|
+
Array.isArray(fetchAllPages)) {
|
|
699
|
+
return args;
|
|
700
|
+
}
|
|
701
|
+
const requestedMaxPages = Number(fetchAllPages.maxPages);
|
|
702
|
+
const maxPages = Number.isInteger(requestedMaxPages) && requestedMaxPages > 0
|
|
703
|
+
? Math.min(Math.floor(requestedMaxPages), TOOL_ORCHESTRATION_MAX_PROVIDER_PAGES)
|
|
704
|
+
: TOOL_ORCHESTRATION_MAX_PROVIDER_PAGES;
|
|
705
|
+
return {
|
|
706
|
+
...args,
|
|
707
|
+
fetchAllPages: {
|
|
708
|
+
...fetchAllPages,
|
|
709
|
+
maxPages,
|
|
710
|
+
},
|
|
711
|
+
};
|
|
712
|
+
}
|
|
583
713
|
// ---------------------------------------------------------------------------
|
|
584
714
|
// Sandbox module template
|
|
585
715
|
// ---------------------------------------------------------------------------
|
|
@@ -589,7 +719,61 @@ function handleBridgeRequest(rawBody, actions, context, defaultTools, extraTools
|
|
|
589
719
|
* `providerSearchAll`, and `webFetch` helpers via the bridge.
|
|
590
720
|
* 2. Runs the user's code as top-level await in an async IIFE.
|
|
591
721
|
*/
|
|
592
|
-
function buildSandboxModule(userCode, bridgePort, bridgeToken) {
|
|
722
|
+
function buildSandboxModule(userCode, bridgePort, bridgeToken, mode = "run-code") {
|
|
723
|
+
const orchestrationHelpers = mode === "tool-orchestration"
|
|
724
|
+
? `
|
|
725
|
+
/**
|
|
726
|
+
* Search the live registry for read-only or conditionally read-only tools, then call one
|
|
727
|
+
* of the returned names with toolCall(). The parent host enforces the same
|
|
728
|
+
* policy again, so this is discovery guidance rather than an authorization
|
|
729
|
+
* boundary.
|
|
730
|
+
*/
|
|
731
|
+
async function toolSearch(query = "", options = {}) {
|
|
732
|
+
const rawResult = await _bridgeCall("tool-search", {
|
|
733
|
+
...(query ? { query } : {}),
|
|
734
|
+
...(options.limit !== undefined ? { limit: options.limit } : {}),
|
|
735
|
+
includeSchemas: true,
|
|
736
|
+
readOnlyOnly: true,
|
|
737
|
+
});
|
|
738
|
+
return _parseBridgeResult(rawResult);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async function toolCall(name, args = {}) {
|
|
742
|
+
return _parseBridgeResult(await _bridgeCall(name, args));
|
|
743
|
+
}
|
|
744
|
+
`
|
|
745
|
+
: "";
|
|
746
|
+
const workspaceMutationHelpers = mode === "run-code"
|
|
747
|
+
? `
|
|
748
|
+
/**
|
|
749
|
+
* Write (create or overwrite) a workspace file. Use \`scratch/...\` for
|
|
750
|
+
* temporary staging files.
|
|
751
|
+
* \`content\` must be a string. Returns metadata
|
|
752
|
+
* { resourceId, path, contentType, sizeBytes, updatedAt }.
|
|
753
|
+
*/
|
|
754
|
+
async function workspaceWrite(path, content, contentType = "text/plain") {
|
|
755
|
+
const rawResult = await _bridgeCall("workspace-files", {
|
|
756
|
+
action: "write",
|
|
757
|
+
path,
|
|
758
|
+
content: typeof content === "string" ? content : JSON.stringify(content),
|
|
759
|
+
contentType,
|
|
760
|
+
});
|
|
761
|
+
try { return typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult; } catch { return rawResult; }
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Append text to a workspace file (creates if absent).
|
|
766
|
+
*/
|
|
767
|
+
async function workspaceAppend(path, content) {
|
|
768
|
+
const rawResult = await _bridgeCall("workspace-files", {
|
|
769
|
+
action: "append",
|
|
770
|
+
path,
|
|
771
|
+
content: typeof content === "string" ? content : JSON.stringify(content),
|
|
772
|
+
});
|
|
773
|
+
try { return typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult; } catch { return rawResult; }
|
|
774
|
+
}
|
|
775
|
+
`
|
|
776
|
+
: "";
|
|
593
777
|
return `
|
|
594
778
|
import { createRequire } from "node:module";
|
|
595
779
|
const require = createRequire(import.meta.url);
|
|
@@ -646,6 +830,8 @@ async function appAction(name, args = {}) {
|
|
|
646
830
|
return _parseBridgeResult(await _bridgeCall(name, args));
|
|
647
831
|
}
|
|
648
832
|
|
|
833
|
+
${orchestrationHelpers}
|
|
834
|
+
|
|
649
835
|
async function providerRequest(provider, apiPath, init = {}) {
|
|
650
836
|
const method = (init.method || "GET").toUpperCase();
|
|
651
837
|
const rawResult = await _bridgeCall("provider-api-request", {
|
|
@@ -1388,33 +1574,7 @@ async function workspaceReadMeta(path, opts = {}) {
|
|
|
1388
1574
|
return _parseBridgeResult(rawResult);
|
|
1389
1575
|
}
|
|
1390
1576
|
|
|
1391
|
-
|
|
1392
|
-
* Write (create or overwrite) a workspace file. Use \`scratch/...\` for
|
|
1393
|
-
* temporary staging files.
|
|
1394
|
-
* \`content\` must be a string. Returns metadata
|
|
1395
|
-
* { resourceId, path, contentType, sizeBytes, updatedAt }.
|
|
1396
|
-
*/
|
|
1397
|
-
async function workspaceWrite(path, content, contentType = "text/plain") {
|
|
1398
|
-
const rawResult = await _bridgeCall("workspace-files", {
|
|
1399
|
-
action: "write",
|
|
1400
|
-
path,
|
|
1401
|
-
content: typeof content === "string" ? content : JSON.stringify(content),
|
|
1402
|
-
contentType,
|
|
1403
|
-
});
|
|
1404
|
-
try { return typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult; } catch { return rawResult; }
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
/**
|
|
1408
|
-
* Append text to a workspace file (creates if absent).
|
|
1409
|
-
*/
|
|
1410
|
-
async function workspaceAppend(path, content) {
|
|
1411
|
-
const rawResult = await _bridgeCall("workspace-files", {
|
|
1412
|
-
action: "append",
|
|
1413
|
-
path,
|
|
1414
|
-
content: typeof content === "string" ? content : JSON.stringify(content),
|
|
1415
|
-
});
|
|
1416
|
-
try { return typeof rawResult === "string" ? JSON.parse(rawResult) : rawResult; } catch { return rawResult; }
|
|
1417
|
-
}
|
|
1577
|
+
${workspaceMutationHelpers}
|
|
1418
1578
|
|
|
1419
1579
|
/**
|
|
1420
1580
|
* List workspace files, optionally filtered by path prefix.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ActionEntry } from "../agent/production-agent.js";
|
|
2
|
+
/**
|
|
3
|
+
* Create the bounded read-only orchestration tool.
|
|
4
|
+
*
|
|
5
|
+
* This deliberately has a separate registry entry from run-code. The latter
|
|
6
|
+
* retains its existing workspace staging and background-execution contract;
|
|
7
|
+
* this entry is for short fan-out, reduction, and aggregation over tools that
|
|
8
|
+
* the host can prove are read-only for the supplied arguments.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createToolOrchestrationEntry(getActions: () => Record<string, ActionEntry>): ActionEntry;
|