@agent-native/core 0.157.6 → 0.157.7
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/agent-native.config.ts +5 -0
- package/corpus/templates/analytics/netlify.toml +1 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +23 -5
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/assets/netlify.toml +3 -0
- package/corpus/templates/brain/netlify.toml +3 -0
- package/corpus/templates/calendar/agent-native.config.ts +5 -0
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -1
- package/corpus/templates/calendar/netlify.toml +3 -0
- package/corpus/templates/chat/agent-native.config.ts +5 -0
- package/corpus/templates/chat/netlify.toml +3 -0
- package/corpus/templates/clips/actions/lib/meeting-content.ts +28 -0
- package/corpus/templates/clips/actions/list-meetings.ts +33 -11
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/clips/app/lib/meeting-history-query.ts +11 -0
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +47 -21
- package/corpus/templates/clips/netlify.toml +4 -0
- package/corpus/templates/content/netlify.toml +3 -0
- package/corpus/templates/design/app/i18n-data.ts +2 -0
- package/corpus/templates/design/netlify.toml +3 -0
- package/corpus/templates/dispatch/netlify.toml +3 -0
- package/corpus/templates/factory/netlify.toml +3 -0
- package/corpus/templates/forms/netlify.toml +3 -0
- package/corpus/templates/macros/netlify.toml +3 -0
- package/corpus/templates/mail/agent-native.config.ts +5 -0
- package/corpus/templates/mail/netlify.toml +3 -0
- package/corpus/templates/plan/netlify.toml +3 -0
- package/corpus/templates/slides/netlify.toml +3 -0
- package/dist/agent/harness/acp-adapter.js +17 -2
- package/dist/agent/harness/ai-sdk-adapter.js +14 -3
- package/dist/agent/harness/background.js +10 -5
- package/dist/agent/harness/hosted.d.ts +20 -0
- package/dist/agent/harness/hosted.js +100 -0
- package/dist/agent/harness/runner.js +100 -77
- package/dist/agent/observational-memory/observer.js +12 -0
- package/dist/agent/production-agent.d.ts +3 -0
- package/dist/agent/production-agent.js +74 -4
- package/dist/agent/thread-data-builder.d.ts +12 -0
- package/dist/agent/thread-data-builder.js +23 -0
- package/dist/agent/types.d.ts +6 -0
- package/dist/client/AgentPanel.d.ts +5 -1
- package/dist/client/AgentPanel.js +113 -17
- package/dist/client/AgentTaskCard.d.ts +2 -2
- package/dist/client/AgentTaskCard.js +8 -22
- package/dist/client/AssistantChat.d.ts +8 -0
- package/dist/client/AssistantChat.js +106 -59
- package/dist/client/MultiTabAssistantChat.js +50 -6
- package/dist/client/NewWorkspaceAppFlow.js +7 -1
- package/dist/client/agent-chat-adapter.d.ts +6 -0
- package/dist/client/agent-chat-adapter.js +36 -8
- package/dist/client/app-chat-sidebar.d.ts +5 -0
- package/dist/client/app-chat-sidebar.js +53 -9
- package/dist/client/chat/composer-draft.d.ts +4 -0
- package/dist/client/chat/composer-draft.js +63 -0
- package/dist/client/chat-first/apps-rail.js +10 -1
- package/dist/client/chat-first/primary-nav.js +10 -5
- package/dist/client/frame-protocol.d.ts +1 -0
- package/dist/client/hooks/index.d.ts +1 -1
- package/dist/client/hooks/index.js +1 -1
- package/dist/client/i18n.js +5 -2
- package/dist/client/progress/RunsTray.js +129 -16
- package/dist/client/settings/SettingsRow.js +1 -1
- package/dist/client/use-chat-threads.js +11 -6
- package/dist/config.d.ts +11 -0
- package/dist/config.js +52 -0
- package/dist/extensions/actions.js +22 -1
- package/dist/hosted-harness/actions/get-hosted-harness-config.d.ts +2 -0
- package/dist/hosted-harness/actions/get-hosted-harness-config.js +13 -0
- package/dist/hosted-harness/actions/set-hosted-harness-enabled.d.ts +4 -0
- package/dist/hosted-harness/actions/set-hosted-harness-enabled.js +32 -0
- package/dist/localization/default-messages.d.ts +4 -1
- package/dist/localization/default-messages.js +5 -2
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.js +12 -0
- package/dist/server/agent-chat-plugin.js +24 -4
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-teams.d.ts +20 -12
- package/dist/server/agent-teams.js +69 -35
- package/dist/server/hosted-harness-policy.d.ts +17 -0
- package/dist/server/hosted-harness-policy.js +38 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/request-context.d.ts +4 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +39 -0
- package/dist/templates/chat/agent-native.config.ts +5 -0
- package/dist/templates/chat/netlify.toml +3 -0
- package/dist/vite/action-types-plugin.js +8 -0
- package/package.json +5 -3
- package/src/templates/chat/agent-native.config.ts +5 -0
- package/src/templates/chat/netlify.toml +3 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
+
import { getFramePostMessageTargetOrigin } from "./frame.js";
|
|
2
3
|
/** Custom event used by top-level hosts such as Electron webviews. */
|
|
3
4
|
export const APP_CHAT_SIDEBAR_STATE_EVENT = "agent-native:per-app-chat-state";
|
|
4
5
|
/** postMessage sent from a per-app chat host to its embedded app. */
|
|
@@ -8,7 +9,7 @@ export const APP_CHAT_SIDEBAR_STATE_REQUEST_MESSAGE = "agentNative.perAppChatSta
|
|
|
8
9
|
export function buildAppChatSidebarStateMessage(open) {
|
|
9
10
|
return {
|
|
10
11
|
type: APP_CHAT_SIDEBAR_STATE_MESSAGE,
|
|
11
|
-
data: { open },
|
|
12
|
+
data: { open, hosted: true },
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
export function buildAppChatSidebarStateRequest() {
|
|
@@ -22,22 +23,62 @@ export function buildAppChatSidebarStateRequest() {
|
|
|
22
23
|
export function isPerAppChatStorageKey(storageKey) {
|
|
23
24
|
return (storageKey === "desktop-app-chat" || storageKey === "dispatch-app-chat");
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
function readDesktopChatBridge() {
|
|
27
|
+
if (typeof window === "undefined")
|
|
28
|
+
return null;
|
|
29
|
+
const bridge = window.agentNativeDesktop;
|
|
30
|
+
return bridge?.chat ?? null;
|
|
31
|
+
}
|
|
32
|
+
function readInitialPerAppChatState() {
|
|
33
|
+
return {
|
|
34
|
+
open: false,
|
|
35
|
+
hosted: readDesktopChatBridge() !== null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function requestPerAppChatCommand(command) {
|
|
39
|
+
if (typeof window === "undefined")
|
|
40
|
+
return false;
|
|
41
|
+
const desktopCommand = readDesktopChatBridge()?.[command];
|
|
42
|
+
if (desktopCommand) {
|
|
43
|
+
desktopCommand();
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (window.parent === window)
|
|
47
|
+
return false;
|
|
48
|
+
const data = command === "toggle" ? undefined : { open: command === "open" };
|
|
49
|
+
window.parent.postMessage({
|
|
50
|
+
type: "agentNative.toggleSidebar",
|
|
51
|
+
...(data ? { data } : {}),
|
|
52
|
+
}, getFramePostMessageTargetOrigin() ?? "*");
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
export function usePerAppChatState(enabled = true) {
|
|
56
|
+
const [state, setState] = useState(() => enabled ? readInitialPerAppChatState() : { open: false, hosted: false });
|
|
27
57
|
useEffect(() => {
|
|
58
|
+
if (!enabled)
|
|
59
|
+
return;
|
|
28
60
|
const applyState = (value) => {
|
|
29
|
-
if (typeof value
|
|
30
|
-
|
|
61
|
+
if (!value || typeof value !== "object")
|
|
62
|
+
return;
|
|
63
|
+
const next = value;
|
|
64
|
+
if (typeof next.open !== "boolean")
|
|
65
|
+
return;
|
|
66
|
+
setState({
|
|
67
|
+
open: next.open,
|
|
68
|
+
// Older hosts sent only `open`; a typed host-state message is still
|
|
69
|
+
// enough to establish that the parent owns this app's chat.
|
|
70
|
+
hosted: next.hosted !== false,
|
|
71
|
+
});
|
|
31
72
|
};
|
|
32
73
|
const handleCustomEvent = (event) => {
|
|
33
|
-
applyState(event.detail
|
|
74
|
+
applyState(event.detail);
|
|
34
75
|
};
|
|
35
76
|
const handleMessage = (event) => {
|
|
36
77
|
if (window.parent === window || event.source !== window.parent)
|
|
37
78
|
return;
|
|
38
79
|
if (event.data?.type !== APP_CHAT_SIDEBAR_STATE_MESSAGE)
|
|
39
80
|
return;
|
|
40
|
-
applyState(event.data.data
|
|
81
|
+
applyState(event.data.data);
|
|
41
82
|
};
|
|
42
83
|
window.addEventListener(APP_CHAT_SIDEBAR_STATE_EVENT, handleCustomEvent);
|
|
43
84
|
window.addEventListener("message", handleMessage);
|
|
@@ -48,6 +89,9 @@ export function usePerAppChatOpen() {
|
|
|
48
89
|
window.removeEventListener(APP_CHAT_SIDEBAR_STATE_EVENT, handleCustomEvent);
|
|
49
90
|
window.removeEventListener("message", handleMessage);
|
|
50
91
|
};
|
|
51
|
-
}, []);
|
|
52
|
-
return
|
|
92
|
+
}, [enabled]);
|
|
93
|
+
return state;
|
|
94
|
+
}
|
|
95
|
+
export function usePerAppChatOpen() {
|
|
96
|
+
return usePerAppChatState().open;
|
|
53
97
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function assistantChatComposerDraftKey(scope?: string | null): string | null;
|
|
2
|
+
export declare function readAssistantChatComposerDraft(scope?: string | null): string | null;
|
|
3
|
+
export declare function writeAssistantChatComposerDraft(scope: string | null | undefined, text: string): void;
|
|
4
|
+
export declare function clearAssistantChatComposerDraft(scope?: string | null): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const ASSISTANT_CHAT_COMPOSER_DRAFT_PREFIX = "agent-chat-composer-text:";
|
|
2
|
+
export function assistantChatComposerDraftKey(scope) {
|
|
3
|
+
const normalizedScope = scope?.trim();
|
|
4
|
+
return normalizedScope
|
|
5
|
+
? `${ASSISTANT_CHAT_COMPOSER_DRAFT_PREFIX}${encodeURIComponent(normalizedScope)}`
|
|
6
|
+
: null;
|
|
7
|
+
}
|
|
8
|
+
function getComposerDraftStorage() {
|
|
9
|
+
if (typeof window === "undefined")
|
|
10
|
+
return null;
|
|
11
|
+
try {
|
|
12
|
+
return window.localStorage;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// coercion-ok: browser storage may be unavailable; treat it as absent.
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function readAssistantChatComposerDraft(scope) {
|
|
20
|
+
const key = assistantChatComposerDraftKey(scope);
|
|
21
|
+
const storage = getComposerDraftStorage();
|
|
22
|
+
if (!key || !storage)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
const draft = storage.getItem(key);
|
|
26
|
+
return draft && draft.trim().length > 0 ? draft : null;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// coercion-ok: browser storage may be unavailable; treat it as absent.
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function writeAssistantChatComposerDraft(scope, text) {
|
|
34
|
+
const key = assistantChatComposerDraftKey(scope);
|
|
35
|
+
const storage = getComposerDraftStorage();
|
|
36
|
+
if (!key || !storage)
|
|
37
|
+
return;
|
|
38
|
+
try {
|
|
39
|
+
if (text.trim().length > 0) {
|
|
40
|
+
storage.setItem(key, text);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
storage.removeItem(key);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// coercion-ok: browser storage may be unavailable; keep the live editor authoritative.
|
|
48
|
+
// The live editor remains the source of truth when browser storage is unavailable.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function clearAssistantChatComposerDraft(scope) {
|
|
52
|
+
const key = assistantChatComposerDraftKey(scope);
|
|
53
|
+
const storage = getComposerDraftStorage();
|
|
54
|
+
if (!key || !storage)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
storage.removeItem(key);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// coercion-ok: browser storage may be unavailable; keep the live editor authoritative.
|
|
61
|
+
// The live editor remains the source of truth when browser storage is unavailable.
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -53,7 +53,16 @@ export const ChatFirstAppsRail = memo(function ChatFirstAppsRail({ apps, activeA
|
|
|
53
53
|
.map((id) => appsById.get(id))
|
|
54
54
|
.filter((app) => Boolean(app));
|
|
55
55
|
}, [apps, layout]);
|
|
56
|
-
const visibleApps =
|
|
56
|
+
const visibleApps = useMemo(() => {
|
|
57
|
+
if (showAllApps)
|
|
58
|
+
return orderedApps;
|
|
59
|
+
const defaultApps = orderedApps.slice(0, 5);
|
|
60
|
+
if (!activeAppId || defaultApps.some((app) => app.id === activeAppId)) {
|
|
61
|
+
return defaultApps;
|
|
62
|
+
}
|
|
63
|
+
const activeApp = orderedApps.find((app) => app.id === activeAppId);
|
|
64
|
+
return activeApp ? [...defaultApps, activeApp] : defaultApps;
|
|
65
|
+
}, [activeAppId, orderedApps, showAllApps]);
|
|
57
66
|
const hasMoreApps = orderedApps.length > visibleApps.length;
|
|
58
67
|
const createTrigger = createAppTrigger ??
|
|
59
68
|
(onCreateApp ? (_jsx("button", { type: "button", className: "flex size-6 items-center justify-center rounded text-sidebar-foreground/55 hover:bg-sidebar-accent hover:text-sidebar-foreground", onClick: onCreateApp, "aria-label": copy("createWorkspaceApp"), title: copy("createWorkspaceApp"), children: _jsx(IconPlus, { size: 14, "aria-hidden": "true" }) })) : null);
|
|
@@ -2,16 +2,21 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { IconClock, IconPlus, IconPlugConnected, IconSearch, } from "@tabler/icons-react";
|
|
3
3
|
import { defaultChatFirstCopy } from "./copy.js";
|
|
4
4
|
export function ChatFirstPrimaryNavigation({ onNewChat, onOpenIntegrations, onOpenScheduled, onSearch, activeTab, collapsed = false, stickyNewChat = false, copy = defaultChatFirstCopy, }) {
|
|
5
|
-
const tabClassName = (tab) => `flex h-8 w-full items-center gap-2 rounded-md
|
|
5
|
+
const tabClassName = (tab) => `flex h-8 w-full items-center gap-2 rounded-md text-[13px] font-medium transition-[background-color,color] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${collapsed ? "justify-center px-0" : "px-2"} ${activeTab === tab
|
|
6
6
|
? "bg-sidebar-accent text-sidebar-accent-foreground"
|
|
7
7
|
: "text-sidebar-foreground/80 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"}`;
|
|
8
|
-
const
|
|
8
|
+
const tabLabel = (tab) => tab === "new-chat"
|
|
9
|
+
? copy("newChat")
|
|
10
|
+
: tab === "integrations"
|
|
11
|
+
? copy("integrations")
|
|
12
|
+
: copy("scheduled");
|
|
13
|
+
const renderTab = (tab, content, onSelect, className) => (_jsx("button", { type: "button", role: "tab", "aria-selected": activeTab === tab, "aria-label": collapsed || tab === "new-chat" ? tabLabel(tab) : undefined, title: collapsed ? tabLabel(tab) : undefined, className: [tabClassName(tab), className].filter(Boolean).join(" "), onClick: onSelect, children: content }));
|
|
9
14
|
const newChatTab = onNewChat
|
|
10
15
|
? renderTab("new-chat", _jsxs(_Fragment, { children: [_jsx(IconPlus, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: copy("newChat") })] }), onNewChat, "code-agents-primary-new-chat")
|
|
11
16
|
: null;
|
|
12
|
-
const integrationsTab = renderTab("integrations", _jsxs(_Fragment, { children: [_jsx(IconPlugConnected, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("integrations") })] }), onOpenIntegrations);
|
|
13
|
-
const scheduledTab = renderTab("scheduled", _jsxs(_Fragment, { children: [_jsx(IconClock, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { children: copy("scheduled") })] }), onOpenScheduled);
|
|
14
|
-
const searchAction = onSearch ? (_jsxs("button", { type: "button", className:
|
|
17
|
+
const integrationsTab = renderTab("integrations", _jsxs(_Fragment, { children: [_jsx(IconPlugConnected, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: copy("integrations") })] }), onOpenIntegrations);
|
|
18
|
+
const scheduledTab = renderTab("scheduled", _jsxs(_Fragment, { children: [_jsx(IconClock, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: copy("scheduled") })] }), onOpenScheduled);
|
|
19
|
+
const searchAction = onSearch ? (_jsxs("button", { type: "button", "aria-label": collapsed ? copy("search") : undefined, title: collapsed ? copy("search") : undefined, className: `mt-px flex h-8 w-full items-center gap-2 rounded-md text-[13px] font-medium text-sidebar-foreground/80 transition-[background-color,color] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${collapsed ? "justify-center px-0" : "px-2"}`, onClick: onSearch, children: [_jsx(IconSearch, { size: 15, className: "shrink-0", "aria-hidden": "true" }), _jsx("span", { className: collapsed ? "sr-only" : undefined, children: copy("search") })] })) : null;
|
|
15
20
|
if (stickyNewChat) {
|
|
16
21
|
return (_jsxs(_Fragment, { children: [newChatTab ? (_jsx("div", { className: "code-agents-primary-new-chat-shell", children: newChatTab })) : null, _jsxs("div", { className: "code-agents-nav-list", "aria-label": "Agent navigation", children: [_jsxs("div", { role: "tablist", "aria-label": "Primary navigation", className: "grid gap-px", children: [integrationsTab, scheduledTab] }), searchAction] })] }));
|
|
17
22
|
}
|
|
@@ -8,7 +8,7 @@ export { notifySessionInvalidated, useSession, type AuthSession, } from "../use-
|
|
|
8
8
|
export { ACTION_KEEPALIVE_BODY_BUDGET_BYTES, callAction, tryCallActionKeepalive, useActionQuery, useActionMutation, type ActionRegistry, type ClientActionCallOptions, type ClientActionMethod, type KeepaliveActionCallRejectionReason, type KeepaliveActionCallResult, } from "../use-action.js";
|
|
9
9
|
export { createAgentNativeQueryClient } from "../create-query-client.js";
|
|
10
10
|
export { AppProviders, type AppProvidersProps } from "../app-providers.js";
|
|
11
|
-
export { APP_CHAT_SIDEBAR_STATE_EVENT, APP_CHAT_SIDEBAR_STATE_MESSAGE, APP_CHAT_SIDEBAR_STATE_REQUEST_MESSAGE, buildAppChatSidebarStateMessage, buildAppChatSidebarStateRequest, isPerAppChatStorageKey, usePerAppChatOpen, type AppChatSidebarState, } from "../app-chat-sidebar.js";
|
|
11
|
+
export { APP_CHAT_SIDEBAR_STATE_EVENT, APP_CHAT_SIDEBAR_STATE_MESSAGE, APP_CHAT_SIDEBAR_STATE_REQUEST_MESSAGE, buildAppChatSidebarStateMessage, buildAppChatSidebarStateRequest, isPerAppChatStorageKey, requestPerAppChatCommand, usePerAppChatState, usePerAppChatOpen, type AppChatSidebarCommand, type AppChatSidebarState, } from "../app-chat-sidebar.js";
|
|
12
12
|
export { usePinchZoom, type UsePinchZoomOptions } from "../use-pinch-zoom.js";
|
|
13
13
|
export { useAvatarUrl, uploadAvatar, invalidateAvatarCache, } from "../use-avatar.js";
|
|
14
14
|
export { usePollLoop, type UsePollLoopOptions, type UsePollLoopHandle, } from "../use-poll-loop.js";
|
|
@@ -8,7 +8,7 @@ export { notifySessionInvalidated, useSession, } from "../use-session.js";
|
|
|
8
8
|
export { ACTION_KEEPALIVE_BODY_BUDGET_BYTES, callAction, tryCallActionKeepalive, useActionQuery, useActionMutation, } from "../use-action.js";
|
|
9
9
|
export { createAgentNativeQueryClient } from "../create-query-client.js";
|
|
10
10
|
export { AppProviders } from "../app-providers.js";
|
|
11
|
-
export { APP_CHAT_SIDEBAR_STATE_EVENT, APP_CHAT_SIDEBAR_STATE_MESSAGE, APP_CHAT_SIDEBAR_STATE_REQUEST_MESSAGE, buildAppChatSidebarStateMessage, buildAppChatSidebarStateRequest, isPerAppChatStorageKey, usePerAppChatOpen, } from "../app-chat-sidebar.js";
|
|
11
|
+
export { APP_CHAT_SIDEBAR_STATE_EVENT, APP_CHAT_SIDEBAR_STATE_MESSAGE, APP_CHAT_SIDEBAR_STATE_REQUEST_MESSAGE, buildAppChatSidebarStateMessage, buildAppChatSidebarStateRequest, isPerAppChatStorageKey, requestPerAppChatCommand, usePerAppChatState, usePerAppChatOpen, } from "../app-chat-sidebar.js";
|
|
12
12
|
export { usePinchZoom } from "../use-pinch-zoom.js";
|
|
13
13
|
export { useAvatarUrl, uploadAvatar, invalidateAvatarCache, } from "../use-avatar.js";
|
|
14
14
|
export { usePollLoop, } from "../use-poll-loop.js";
|
package/dist/client/i18n.js
CHANGED
|
@@ -451,6 +451,9 @@ const CORE_FALLBACK_MESSAGES = {
|
|
|
451
451
|
"runsTray.statusDone": "Done",
|
|
452
452
|
"runsTray.statusFailed": "Failed",
|
|
453
453
|
"runsTray.statusStopped": "Stopped",
|
|
454
|
+
"runsTray.statusNeedsApproval": "Needs approval",
|
|
455
|
+
"runsTray.statusNeedsInput": "Needs input",
|
|
456
|
+
"runsTray.statusPaused": "Paused",
|
|
454
457
|
"runsTray.updatedJustNow": "Updated just now",
|
|
455
458
|
"runsTray.finishedJustNow": "Finished just now",
|
|
456
459
|
"runsTray.updatedMinutes": "Updated {{count}}m ago",
|
|
@@ -459,8 +462,8 @@ const CORE_FALLBACK_MESSAGES = {
|
|
|
459
462
|
"runsTray.finishedHours": "Finished {{count}}h ago",
|
|
460
463
|
"runsTray.updatedDate": "Updated {{date}}",
|
|
461
464
|
"runsTray.finishedDate": "Finished {{date}}",
|
|
462
|
-
"agentTask.
|
|
463
|
-
"agentTask.stop": "Stop
|
|
465
|
+
"agentTask.spawnedAgent": "Spawned agent",
|
|
466
|
+
"agentTask.stop": "Stop spawned agent",
|
|
464
467
|
"agentTask.openThread": "Open task thread",
|
|
465
468
|
"codeRequired.fallbackDetail": "Edit locally or use Builder.io to edit this code in the cloud and continue customizing the app any way you like.",
|
|
466
469
|
"codeRequired.defaultFeature": "Make the requested code changes to this app",
|
|
@@ -24,20 +24,37 @@ function useRunsTrayState({ pollMs = 3000, limit = 5, hideWhenIdle = true, showR
|
|
|
24
24
|
const includeRecent = showRecent ?? !hideWhenIdle;
|
|
25
25
|
const runsVersion = useChangeVersion("runs");
|
|
26
26
|
const refresh = useCallback(async (signal) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
const query = new URLSearchParams({ limit: String(limit) });
|
|
28
|
+
if (!includeRecent)
|
|
29
|
+
query.set("active", "true");
|
|
30
|
+
const [legacyResult, backgroundResult] = await Promise.allSettled([
|
|
31
|
+
fetch(agentNativePath(`/_agent-native/runs?${query.toString()}`), {
|
|
32
|
+
signal,
|
|
33
|
+
}),
|
|
34
|
+
fetch(agentNativePath(`/_agent-native/agent-chat/runs/list?${new URLSearchParams({ limit: String(limit) }).toString()}`), { signal }),
|
|
35
|
+
]);
|
|
36
|
+
const rows = legacyResult.status === "fulfilled" && legacyResult.value.ok
|
|
37
|
+
? await readLegacyRuns(legacyResult.value)
|
|
38
|
+
: null;
|
|
39
|
+
const backgroundRows = backgroundResult.status === "fulfilled" && backgroundResult.value.ok
|
|
40
|
+
? await readBackgroundRuns(backgroundResult.value)
|
|
41
|
+
: null;
|
|
42
|
+
const hasSuccessfulResponse = rows !== null || backgroundRows !== null;
|
|
43
|
+
if (!hasSuccessfulResponse) {
|
|
44
|
+
return;
|
|
36
45
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
const merged = new Map();
|
|
47
|
+
for (const row of rows ?? [])
|
|
48
|
+
merged.set(row.id, row);
|
|
49
|
+
for (const row of backgroundRows ?? []) {
|
|
50
|
+
const normalized = normalizeBackgroundRun(row);
|
|
51
|
+
if (includeRecent || normalized.status === "running") {
|
|
52
|
+
merged.set(normalized.id, normalized);
|
|
53
|
+
}
|
|
40
54
|
}
|
|
55
|
+
setRuns([...merged.values()]
|
|
56
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
|
57
|
+
.slice(0, limit));
|
|
41
58
|
}, [includeRecent, limit]);
|
|
42
59
|
useEffect(() => {
|
|
43
60
|
void refresh();
|
|
@@ -54,7 +71,10 @@ function useRunsTrayState({ pollMs = 3000, limit = 5, hideWhenIdle = true, showR
|
|
|
54
71
|
enabled: pollMs > 0 || hasActiveRun,
|
|
55
72
|
});
|
|
56
73
|
const dismissRun = useCallback(async (runId) => {
|
|
74
|
+
const existing = runs.find((run) => run.id === runId);
|
|
57
75
|
setRuns((current) => current.filter((run) => run.id !== runId));
|
|
76
|
+
if (existing && isBackgroundRun(existing))
|
|
77
|
+
return;
|
|
58
78
|
try {
|
|
59
79
|
const res = await fetch(agentNativePath(`/_agent-native/runs/${runId}`), {
|
|
60
80
|
method: "DELETE",
|
|
@@ -66,7 +86,7 @@ function useRunsTrayState({ pollMs = 3000, limit = 5, hideWhenIdle = true, showR
|
|
|
66
86
|
catch {
|
|
67
87
|
refresh();
|
|
68
88
|
}
|
|
69
|
-
}, [refresh]);
|
|
89
|
+
}, [refresh, runs]);
|
|
70
90
|
const stopRun = useCallback(async (runId) => {
|
|
71
91
|
// Optimistic: mark as cancelled immediately so the UI is responsive.
|
|
72
92
|
setRuns((current) => current.map((run) => run.id === runId
|
|
@@ -179,6 +199,67 @@ function RunsTrayContent({ runs, hasRuns, activeCount, terminalCount, onDismiss,
|
|
|
179
199
|
? t("runsTray.summaryRecent", { count: runs.length })
|
|
180
200
|
: t("runsTray.noTrackedWorkYet") })] }), hasRuns ? (_jsx("div", { className: "max-h-96 divide-y divide-border overflow-y-auto", children: runs.map((run) => (_jsx(RunRow, { run: run, onDismiss: onDismiss, onStop: onStop, onOpenThread: onOpenThread }, run.id))) })) : (_jsxs("div", { className: "px-3 py-6 text-center", children: [_jsx(IconClock, { size: 22, className: "mx-auto text-muted-foreground/50", "aria-hidden": true }), _jsx("div", { className: "mt-2 text-sm font-medium text-foreground", children: t("runsTray.noRecentRuns") }), _jsx("p", { className: "mt-1 text-xs leading-relaxed text-muted-foreground", children: t("runsTray.emptyDescription") })] }))] }));
|
|
181
201
|
}
|
|
202
|
+
async function readLegacyRuns(response) {
|
|
203
|
+
try {
|
|
204
|
+
const body = await response.json();
|
|
205
|
+
return Array.isArray(body) ? body : null;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// coercion-ok: malformed responses return null, distinct from an empty run list.
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async function readBackgroundRuns(response) {
|
|
213
|
+
try {
|
|
214
|
+
const body = await response.json();
|
|
215
|
+
if (!body || typeof body !== "object" || !("runs" in body))
|
|
216
|
+
return null;
|
|
217
|
+
const runs = body.runs;
|
|
218
|
+
return Array.isArray(runs) ? runs : null;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// coercion-ok: malformed responses return null, distinct from an empty run list.
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function normalizeBackgroundRun(run) {
|
|
226
|
+
const metadata = run.metadata ?? {};
|
|
227
|
+
const threadId = run.sourceRecord?.threadId ??
|
|
228
|
+
(typeof metadata.threadId === "string" ? metadata.threadId : undefined);
|
|
229
|
+
const status = run.status === "errored"
|
|
230
|
+
? "failed"
|
|
231
|
+
: run.status === "queued" ||
|
|
232
|
+
run.status === "running" ||
|
|
233
|
+
run.status === "needs-input" ||
|
|
234
|
+
run.status === "needs-approval"
|
|
235
|
+
? "running"
|
|
236
|
+
: "succeeded";
|
|
237
|
+
return {
|
|
238
|
+
id: run.id,
|
|
239
|
+
owner: "",
|
|
240
|
+
title: run.title,
|
|
241
|
+
step: run.needsApproval
|
|
242
|
+
? "Needs approval"
|
|
243
|
+
: (run.subtitle ?? (run.status === "paused" ? "Paused" : run.phase)),
|
|
244
|
+
percent: null,
|
|
245
|
+
status,
|
|
246
|
+
startedAt: run.createdAt,
|
|
247
|
+
updatedAt: run.updatedAt,
|
|
248
|
+
completedAt: status === "running" ? null : run.updatedAt,
|
|
249
|
+
metadata: {
|
|
250
|
+
...metadata,
|
|
251
|
+
kind: run.kind,
|
|
252
|
+
source: run.source,
|
|
253
|
+
sourceLabel: run.sourceLabel,
|
|
254
|
+
backgroundStatus: run.status,
|
|
255
|
+
backgroundGoalId: run.goalId,
|
|
256
|
+
needsApproval: run.needsApproval,
|
|
257
|
+
needsInput: run.needsInput,
|
|
258
|
+
...(run.surfaceUrl ? { surfaceUrl: run.surfaceUrl } : {}),
|
|
259
|
+
...(threadId ? { threadId } : {}),
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
182
263
|
function getRunThreadId(run) {
|
|
183
264
|
const metadata = run.metadata ?? {};
|
|
184
265
|
const direct = typeof metadata.threadId === "string"
|
|
@@ -223,13 +304,20 @@ function isAgentTeamRun(run) {
|
|
|
223
304
|
run.metadata !== null &&
|
|
224
305
|
run.metadata.kind === "agent-team");
|
|
225
306
|
}
|
|
307
|
+
function isBackgroundRun(run) {
|
|
308
|
+
return (typeof run.metadata === "object" &&
|
|
309
|
+
run.metadata !== null &&
|
|
310
|
+
typeof run.metadata.backgroundStatus ===
|
|
311
|
+
"string");
|
|
312
|
+
}
|
|
226
313
|
function RunRow({ run, onDismiss, onStop, onOpenThread, }) {
|
|
227
314
|
const t = useT();
|
|
228
315
|
const { formatDate } = useFormatters();
|
|
229
316
|
const threadId = getRunThreadId(run);
|
|
230
317
|
const isRunning = run.status === "running";
|
|
231
|
-
const canStop = isRunning && isAgentTeamRun(run);
|
|
232
|
-
|
|
318
|
+
const canStop = isRunning && (isAgentTeamRun(run) || isBackgroundRun(run));
|
|
319
|
+
const backgroundStatus = getBackgroundStatus(run);
|
|
320
|
+
return (_jsxs("div", { className: "flex flex-col gap-1.5 px-3 py-2.5 text-sm", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate font-medium text-foreground", children: run.title }), run.step ? (_jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: run.step })) : null] }), _jsx(StatusPill, { status: run.status, backgroundStatus: backgroundStatus })] }), run.percent != null || isRunning ? (_jsx("div", { className: "h-1 w-full overflow-hidden rounded bg-muted", children: run.percent != null ? (_jsx("div", { className: cn("h-full origin-left transition-transform duration-200 ease-[var(--ease-collapse)]", run.status === "failed"
|
|
233
321
|
? "bg-destructive"
|
|
234
322
|
: run.status === "cancelled"
|
|
235
323
|
? "bg-muted-foreground/50"
|
|
@@ -250,12 +338,37 @@ const STATUS_PILL_STYLES = {
|
|
|
250
338
|
failed: "bg-destructive/10 text-destructive",
|
|
251
339
|
cancelled: "bg-muted text-muted-foreground",
|
|
252
340
|
};
|
|
253
|
-
function StatusPill({ status }) {
|
|
341
|
+
function StatusPill({ status, backgroundStatus, }) {
|
|
254
342
|
const t = useT();
|
|
343
|
+
if (backgroundStatus === "needs-approval") {
|
|
344
|
+
return (_jsxs("span", { className: "inline-flex h-5 shrink-0 items-center gap-1 rounded-md bg-primary/10 px-1.5 text-[10px] font-medium text-primary", children: [_jsx(IconAlertCircle, { size: 12, "aria-hidden": true }), t("runsTray.statusNeedsApproval", { defaultValue: "Needs approval" })] }));
|
|
345
|
+
}
|
|
346
|
+
if (backgroundStatus === "needs-input") {
|
|
347
|
+
return (_jsxs("span", { className: "inline-flex h-5 shrink-0 items-center gap-1 rounded-md bg-primary/10 px-1.5 text-[10px] font-medium text-primary", children: [_jsx(IconAlertCircle, { size: 12, "aria-hidden": true }), t("runsTray.statusNeedsInput", { defaultValue: "Needs input" })] }));
|
|
348
|
+
}
|
|
349
|
+
if (backgroundStatus === "paused") {
|
|
350
|
+
return (_jsxs("span", { className: "inline-flex h-5 shrink-0 items-center gap-1 rounded-md bg-muted px-1.5 text-[10px] font-medium text-muted-foreground", children: [_jsx(IconClock, { size: 12, "aria-hidden": true }), t("runsTray.statusPaused", { defaultValue: "Paused" })] }));
|
|
351
|
+
}
|
|
255
352
|
const { Icon, className } = STATUS_GLYPHS[status];
|
|
256
353
|
const spinClass = status === "running" ? " animate-spin" : "";
|
|
257
354
|
return (_jsxs("span", { className: cn("inline-flex h-5 shrink-0 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium", STATUS_PILL_STYLES[status]), children: [_jsx(Icon, { size: 12, className: `${className}${spinClass}`, "aria-hidden": true }), t(STATUS_COPY_KEYS[status])] }));
|
|
258
355
|
}
|
|
356
|
+
function getBackgroundStatus(run) {
|
|
357
|
+
const value = run.metadata?.backgroundStatus;
|
|
358
|
+
return typeof value === "string" && isBackgroundStatus(value)
|
|
359
|
+
? value
|
|
360
|
+
: undefined;
|
|
361
|
+
}
|
|
362
|
+
function isBackgroundStatus(value) {
|
|
363
|
+
return (value === "queued" ||
|
|
364
|
+
value === "running" ||
|
|
365
|
+
value === "paused" ||
|
|
366
|
+
value === "needs-input" ||
|
|
367
|
+
value === "needs-approval" ||
|
|
368
|
+
value === "completed" ||
|
|
369
|
+
value === "errored" ||
|
|
370
|
+
value === "unknown");
|
|
371
|
+
}
|
|
259
372
|
// dark: variants only where there's no semantic token for the colour
|
|
260
373
|
// (e.g. success green isn't in shadcn's default palette).
|
|
261
374
|
const STATUS_GLYPHS = {
|
|
@@ -12,5 +12,5 @@ export function SettingsGroup({ id, title, description, variant = "default", cla
|
|
|
12
12
|
}
|
|
13
13
|
/** One setting: label and description on the start side, control on the end. */
|
|
14
14
|
export function SettingsRow({ id, label, description, icon, status, control, children, className, }) {
|
|
15
|
-
return (_jsxs("div", { id: id, className: cn("scroll-mt-16 px-5 py-4 sm:px-6", className), children: [_jsxs("div", { className: "flex flex-col gap-3
|
|
15
|
+
return (_jsxs("div", { id: id, className: cn("agent-native-settings-row scroll-mt-16 px-5 py-4 sm:px-6", className), children: [_jsxs("div", { className: "agent-native-settings-row__layout flex flex-col gap-3", children: [_jsxs("div", { className: "agent-native-settings-row__main flex min-w-0 flex-1 gap-3", children: [icon && (_jsx("span", { className: "mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-md border border-border bg-background text-muted-foreground [&>svg]:size-[18px]", children: icon })), _jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-sm font-medium text-foreground", children: label }), status] }), description && (_jsx("p", { className: "mt-1 max-w-xl text-sm leading-6 text-muted-foreground", children: description }))] })] }), control && (_jsx("div", { className: "agent-native-settings-row__control max-w-full shrink-0", children: control }))] }), children && _jsx("div", { className: "mt-4", children: children })] }));
|
|
16
16
|
}
|
|
@@ -467,10 +467,9 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
467
467
|
// it; the server hasn't seen it yet because there's no POST anymore,
|
|
468
468
|
// the row gets written when the user sends a message.
|
|
469
469
|
// - savedId is set but not on the current page → look it up directly. A
|
|
470
|
-
// found thread stays active. An
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
// never-posted tab remains selectable after reload.
|
|
470
|
+
// found thread stays active. An unavailable lookup keeps the saved id so
|
|
471
|
+
// the detail surface can preserve cached state and offer recovery instead
|
|
472
|
+
// of replacing a shared/reopened conversation with a blank local tab.
|
|
474
473
|
// - No savedId → synthesize a fresh local id (no POST; server creates the
|
|
475
474
|
// row on first message). The server may contain chats from another
|
|
476
475
|
// branch, preview, or project that shares the same user/database, so
|
|
@@ -512,7 +511,12 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
512
511
|
const restoredIsUnavailable = restoredThread === null && lookupRestored && !restoredOnPage;
|
|
513
512
|
const restoredBelongsElsewhere = Boolean(restoredThread &&
|
|
514
513
|
!threadCanStayVisibleInScope(restoredThread.scope ?? null, scopeRef.current));
|
|
515
|
-
|
|
514
|
+
// Keep the saved id when the direct lookup says 404. AssistantChat owns
|
|
515
|
+
// the detail restore and can show a retryable error while preserving any
|
|
516
|
+
// cached transcript or composer draft. Replacing the id here silently
|
|
517
|
+
// turns a shared/reopened conversation into a new blank chat after the
|
|
518
|
+
// slower lookup finishes.
|
|
519
|
+
const restoredNeedsReplacement = restoredBelongsElsewhere;
|
|
516
520
|
if (restoredNeedsReplacement)
|
|
517
521
|
setActiveThreadId(null);
|
|
518
522
|
const savedId = restoredNeedsReplacement ? null : restoredId;
|
|
@@ -534,7 +538,8 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
|
|
|
534
538
|
}
|
|
535
539
|
else if (savedId &&
|
|
536
540
|
!newlyCreatedRef.current.has(savedId) &&
|
|
537
|
-
!loadedHasSavedId
|
|
541
|
+
!loadedHasSavedId &&
|
|
542
|
+
!restoredIsUnavailable) {
|
|
538
543
|
// The tab the user left open isn't a server thread and we didn't
|
|
539
544
|
// create it this session (newlyCreatedRef was wiped by the
|
|
540
545
|
// reload). Treat it as the empty tab it is — keep its id and
|
package/dist/config.d.ts
CHANGED
|
@@ -59,6 +59,16 @@ export interface AgentNativeChangelogConfig {
|
|
|
59
59
|
/** Whether agents should create and surface user-facing changelog entries. */
|
|
60
60
|
enabled?: boolean;
|
|
61
61
|
}
|
|
62
|
+
export type AgentNativeHarnessRuntime = "claude-code" | "codex" | "pi" | "opencode";
|
|
63
|
+
export interface AgentNativeHarnessConfig {
|
|
64
|
+
/** Optionally narrow the hosted harness picker to these runtimes. */
|
|
65
|
+
runtimes?: AgentNativeHarnessRuntime[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The intentionally small app-level switch for hosted tools-only harnesses.
|
|
69
|
+
* `true` enables every supported runtime; an object narrows the picker.
|
|
70
|
+
*/
|
|
71
|
+
export type AgentNativeHarnessSetting = boolean | AgentNativeHarnessConfig;
|
|
62
72
|
export interface AgentNativeConfig {
|
|
63
73
|
version?: typeof AGENT_NATIVE_CONFIG_VERSION;
|
|
64
74
|
onboarding?: AgentNativeOnboardingConfig;
|
|
@@ -67,6 +77,7 @@ export interface AgentNativeConfig {
|
|
|
67
77
|
instructions?: AgentNativeInstructionsConfig;
|
|
68
78
|
translations?: AgentNativeTranslationsConfig;
|
|
69
79
|
changelog?: AgentNativeChangelogConfig;
|
|
80
|
+
harness?: AgentNativeHarnessSetting;
|
|
70
81
|
}
|
|
71
82
|
export interface AgentNativeConfigContext {
|
|
72
83
|
command: "serve" | "build";
|
package/dist/config.js
CHANGED
|
@@ -30,6 +30,7 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
|
|
|
30
30
|
const instructionsValue = input.instructions;
|
|
31
31
|
const translationsValue = input.translations;
|
|
32
32
|
const changelogValue = input.changelog;
|
|
33
|
+
const harnessValue = input.harness;
|
|
33
34
|
const normalized = {
|
|
34
35
|
...(input.version === undefined
|
|
35
36
|
? {}
|
|
@@ -57,6 +58,9 @@ export function normalizeAgentNativeConfig(input, source = "agent-native config"
|
|
|
57
58
|
if (changelogValue !== undefined) {
|
|
58
59
|
normalized.changelog = normalizeChangelogConfig(changelogValue, `${source}.changelog`);
|
|
59
60
|
}
|
|
61
|
+
if (harnessValue !== undefined) {
|
|
62
|
+
normalized.harness = normalizeHarnessConfig(harnessValue, `${source}.harness`);
|
|
63
|
+
}
|
|
60
64
|
return normalized;
|
|
61
65
|
}
|
|
62
66
|
export function mergeAgentNativeConfigs(base, override) {
|
|
@@ -128,6 +132,7 @@ export function mergeAgentNativeConfigs(base, override) {
|
|
|
128
132
|
...override.changelog,
|
|
129
133
|
}
|
|
130
134
|
: undefined,
|
|
135
|
+
harness: mergeHarnessSettings(base.harness, override.harness),
|
|
131
136
|
};
|
|
132
137
|
}
|
|
133
138
|
export function resolveAgentNativeConfig(input, context) {
|
|
@@ -254,6 +259,47 @@ function normalizeChangelogConfig(value, source) {
|
|
|
254
259
|
}
|
|
255
260
|
return value.enabled === undefined ? {} : { enabled: value.enabled };
|
|
256
261
|
}
|
|
262
|
+
function normalizeHarnessConfig(value, source) {
|
|
263
|
+
if (typeof value === "boolean")
|
|
264
|
+
return value;
|
|
265
|
+
if (!isRecord(value)) {
|
|
266
|
+
throw new Error(`${source} must be a boolean or object`);
|
|
267
|
+
}
|
|
268
|
+
if ("enabled" in value || "ui" in value) {
|
|
269
|
+
throw new Error(`${source} must be true or an object with runtimes`);
|
|
270
|
+
}
|
|
271
|
+
const runtimes = value.runtimes;
|
|
272
|
+
if (runtimes !== undefined) {
|
|
273
|
+
if (!Array.isArray(runtimes) ||
|
|
274
|
+
runtimes.some((runtime) => !isAgentNativeHarnessRuntime(runtime))) {
|
|
275
|
+
throw new Error(`${source}.runtimes must contain only "claude-code", "codex", "pi", or "opencode"`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
...(runtimes === undefined
|
|
280
|
+
? {}
|
|
281
|
+
: { runtimes: [...new Set(runtimes)] }),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function mergeHarnessSettings(base, override) {
|
|
285
|
+
if (override === undefined)
|
|
286
|
+
return base;
|
|
287
|
+
if (typeof override === "boolean")
|
|
288
|
+
return override;
|
|
289
|
+
if (typeof base !== "object" || base === null)
|
|
290
|
+
return override;
|
|
291
|
+
if (base.runtimes === undefined && override.runtimes === undefined) {
|
|
292
|
+
return {};
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
runtimes: [
|
|
296
|
+
...new Set([
|
|
297
|
+
...(base.runtimes ?? []),
|
|
298
|
+
...(override.runtimes ?? []),
|
|
299
|
+
]),
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
}
|
|
257
303
|
function normalizeRelativeFilePath(value, source) {
|
|
258
304
|
const normalized = value.trim().replaceAll("\\", "/");
|
|
259
305
|
if (!normalized ||
|
|
@@ -286,6 +332,12 @@ function isFirstRunMode(value) {
|
|
|
286
332
|
value === "connect" ||
|
|
287
333
|
value === "connect-and-integrations");
|
|
288
334
|
}
|
|
335
|
+
function isAgentNativeHarnessRuntime(value) {
|
|
336
|
+
return (value === "claude-code" ||
|
|
337
|
+
value === "codex" ||
|
|
338
|
+
value === "pi" ||
|
|
339
|
+
value === "opencode");
|
|
340
|
+
}
|
|
289
341
|
function isRecord(value) {
|
|
290
342
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
291
343
|
}
|