@agent-native/core 0.157.5 → 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.
Files changed (109) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/agent-native.config.ts +5 -0
  3. package/corpus/templates/analytics/netlify.toml +1 -0
  4. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +23 -5
  5. package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
  6. package/corpus/templates/assets/netlify.toml +3 -0
  7. package/corpus/templates/brain/netlify.toml +3 -0
  8. package/corpus/templates/calendar/agent-native.config.ts +5 -0
  9. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -1
  10. package/corpus/templates/calendar/netlify.toml +3 -0
  11. package/corpus/templates/chat/agent-native.config.ts +5 -0
  12. package/corpus/templates/chat/netlify.toml +3 -0
  13. package/corpus/templates/clips/actions/create-meeting.ts +19 -1
  14. package/corpus/templates/clips/actions/lib/meeting-content.ts +28 -0
  15. package/corpus/templates/clips/actions/list-meetings.ts +33 -11
  16. package/corpus/templates/clips/app/components/meetings/bullet-link.tsx +48 -25
  17. package/corpus/templates/clips/app/components/meetings/canvas-editor.tsx +7 -6
  18. package/corpus/templates/clips/app/components/meetings/transcript-bubbles.tsx +388 -178
  19. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +7 -13
  20. package/corpus/templates/clips/app/components/transcript/transcript-segment-row.tsx +98 -0
  21. package/corpus/templates/clips/app/i18n/en-US.ts +5 -0
  22. package/corpus/templates/clips/app/lib/meeting-history-query.ts +11 -0
  23. package/corpus/templates/clips/app/lib/public-meeting.ts +4 -0
  24. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +49 -31
  25. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +47 -21
  26. package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +15 -1
  27. package/corpus/templates/clips/netlify.toml +8 -4
  28. package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +10 -0
  29. package/corpus/templates/clips/shared/transcript-segments.ts +20 -2
  30. package/corpus/templates/content/netlify.toml +3 -0
  31. package/corpus/templates/design/app/i18n-data.ts +2 -0
  32. package/corpus/templates/design/netlify.toml +3 -0
  33. package/corpus/templates/dispatch/netlify.toml +3 -0
  34. package/corpus/templates/factory/netlify.toml +3 -0
  35. package/corpus/templates/forms/netlify.toml +3 -0
  36. package/corpus/templates/macros/netlify.toml +3 -0
  37. package/corpus/templates/mail/agent-native.config.ts +5 -0
  38. package/corpus/templates/mail/netlify.toml +3 -0
  39. package/corpus/templates/plan/netlify.toml +3 -0
  40. package/corpus/templates/slides/netlify.toml +3 -0
  41. package/dist/agent/harness/acp-adapter.js +17 -2
  42. package/dist/agent/harness/ai-sdk-adapter.js +14 -3
  43. package/dist/agent/harness/background.js +10 -5
  44. package/dist/agent/harness/hosted.d.ts +20 -0
  45. package/dist/agent/harness/hosted.js +100 -0
  46. package/dist/agent/harness/runner.js +100 -77
  47. package/dist/agent/observational-memory/observer.js +12 -0
  48. package/dist/agent/production-agent.d.ts +3 -0
  49. package/dist/agent/production-agent.js +74 -4
  50. package/dist/agent/thread-data-builder.d.ts +12 -0
  51. package/dist/agent/thread-data-builder.js +23 -0
  52. package/dist/agent/types.d.ts +6 -0
  53. package/dist/client/AgentPanel.d.ts +5 -1
  54. package/dist/client/AgentPanel.js +113 -17
  55. package/dist/client/AgentTaskCard.d.ts +2 -2
  56. package/dist/client/AgentTaskCard.js +8 -22
  57. package/dist/client/AssistantChat.d.ts +8 -0
  58. package/dist/client/AssistantChat.js +106 -59
  59. package/dist/client/MultiTabAssistantChat.js +50 -6
  60. package/dist/client/NewWorkspaceAppFlow.js +7 -1
  61. package/dist/client/agent-chat-adapter.d.ts +6 -0
  62. package/dist/client/agent-chat-adapter.js +36 -8
  63. package/dist/client/app-chat-sidebar.d.ts +5 -0
  64. package/dist/client/app-chat-sidebar.js +53 -9
  65. package/dist/client/chat/composer-draft.d.ts +4 -0
  66. package/dist/client/chat/composer-draft.js +63 -0
  67. package/dist/client/chat-first/apps-rail.js +10 -1
  68. package/dist/client/chat-first/primary-nav.js +10 -5
  69. package/dist/client/frame-protocol.d.ts +1 -0
  70. package/dist/client/hooks/index.d.ts +1 -1
  71. package/dist/client/hooks/index.js +1 -1
  72. package/dist/client/i18n.js +5 -2
  73. package/dist/client/progress/RunsTray.js +129 -16
  74. package/dist/client/settings/SettingsRow.js +1 -1
  75. package/dist/client/use-chat-threads.js +11 -6
  76. package/dist/collab/routes.d.ts +1 -1
  77. package/dist/collab/struct-routes.d.ts +1 -1
  78. package/dist/config.d.ts +11 -0
  79. package/dist/config.js +52 -0
  80. package/dist/deploy/build.js +28 -11
  81. package/dist/extensions/actions.js +22 -1
  82. package/dist/hosted-harness/actions/get-hosted-harness-config.d.ts +2 -0
  83. package/dist/hosted-harness/actions/get-hosted-harness-config.js +13 -0
  84. package/dist/hosted-harness/actions/set-hosted-harness-enabled.d.ts +4 -0
  85. package/dist/hosted-harness/actions/set-hosted-harness-enabled.js +32 -0
  86. package/dist/localization/default-messages.d.ts +4 -1
  87. package/dist/localization/default-messages.js +5 -2
  88. package/dist/observability/routes.d.ts +5 -5
  89. package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
  90. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  91. package/dist/resources/handlers.d.ts +1 -1
  92. package/dist/secrets/routes.d.ts +9 -9
  93. package/dist/server/action-discovery.js +12 -0
  94. package/dist/server/agent-chat-plugin.js +24 -4
  95. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  96. package/dist/server/agent-teams.d.ts +20 -12
  97. package/dist/server/agent-teams.js +69 -35
  98. package/dist/server/hosted-harness-policy.d.ts +17 -0
  99. package/dist/server/hosted-harness-policy.js +38 -0
  100. package/dist/server/realtime-token.d.ts +1 -1
  101. package/dist/server/request-context.d.ts +4 -0
  102. package/dist/server/transcribe-voice.d.ts +1 -1
  103. package/dist/styles/agent-native.css +39 -0
  104. package/dist/templates/chat/agent-native.config.ts +5 -0
  105. package/dist/templates/chat/netlify.toml +3 -0
  106. package/dist/vite/action-types-plugin.js +8 -0
  107. package/package.json +4 -2
  108. package/src/templates/chat/agent-native.config.ts +5 -0
  109. package/src/templates/chat/netlify.toml +3 -0
@@ -16,6 +16,7 @@ import { completeRun as completeProgressRun, startRun as startProgressRun, updat
16
16
  import { isRuntimeVisibleScope, parseSkillFrontmatter, } from "../server/agent-chat/skill-frontmatter.js";
17
17
  import { canUseDeployCredentialFallbackForRequest, getProviderCredentialAuthFailure, readDeployCredentialEnv, } from "../server/credential-provider.js";
18
18
  import { readBody } from "../server/h3-helpers.js";
19
+ import { resolveHostedHarnessPolicy } from "../server/hosted-harness-policy.js";
19
20
  import { assertRequestActionSurfaceIsolation, getRequestRunContext, ensureRequestRunContext, getRequestContext, getRequestOrgId, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
20
21
  import { fireInternalDispatch } from "../server/self-dispatch.js";
21
22
  import { isReasoningEffort, normalizeReasoningEffortForRequest, stepDownReasoningEffort, } from "../shared/reasoning-effort.js";
@@ -33,6 +34,7 @@ import { PROVIDER_TO_ENV } from "./engine/provider-env-vars.js";
33
34
  import { loadPriorTurnToolCallJournal } from "./engine/tool-call-journal-seed.js";
34
35
  import { backfillEngineMessagesToolResults, stringifyToolUseInputForGateway, unmatchedToolResultReplayText, } from "./engine/translate-anthropic.js";
35
36
  import { EngineError } from "./engine/types.js";
37
+ import { filterHostedHarnessToolNames, normalizeHostedHarnessRuntime, } from "./harness/hosted.js";
36
38
  import { getDefaultMaxIterations, getDefaultMaxRunInputTokens, MAX_AGENT_MAX_ITERATIONS, MIN_AGENT_MAX_ITERATIONS, normalizeMaxIterations, normalizeMaxRunInputTokens, readAgentLoopSettings, } from "./loop-settings.js";
37
39
  import { maybeCompactThread, buildObservationalContext, hasObservationalMemory, serializeObservationalMemoryBlock, } from "./observational-memory/index.js";
38
40
  import { ProcessorChain, TripWire, toolCallsFromContent, } from "./processors.js";
@@ -5056,7 +5058,7 @@ export async function runAgentLoop(opts) {
5056
5058
  // the user-visible turn.
5057
5059
  if (opts.ownerEmail) {
5058
5060
  const compactThreadId = opts.threadId;
5059
- void maybeCompactThread({
5061
+ const compaction = maybeCompactThread({
5060
5062
  threadId: compactThreadId,
5061
5063
  ownerEmail: opts.ownerEmail,
5062
5064
  orgId: opts.orgId ?? null,
@@ -5064,6 +5066,17 @@ export async function runAgentLoop(opts) {
5064
5066
  }).catch((err) => {
5065
5067
  console.warn("[observational-memory] post-turn compaction skipped:", err instanceof Error ? err.message : String(err));
5066
5068
  });
5069
+ // This pass has to finish a streaming model call before it writes, and
5070
+ // it starts after the turn's `done` event — on a host that freezes the
5071
+ // isolate once the response settles, an unregistered promise is simply
5072
+ // killed, so the thread never accrues the memory that would spare the
5073
+ // next turn. Hand it to the platform keep-alive where there is one;
5074
+ // long-lived hosts keep the existing fire-and-forget behavior.
5075
+ const waitUntil = getRequestRunContext()?.waitUntil;
5076
+ if (waitUntil)
5077
+ waitUntil(compaction);
5078
+ else
5079
+ void compaction;
5067
5080
  }
5068
5081
  }
5069
5082
  }
@@ -6135,7 +6148,7 @@ export function createProductionAgentHandler(options) {
6135
6148
  return { error: "Invalid request body" };
6136
6149
  }
6137
6150
  }
6138
- const { message, history = [], structuredHistory, references = [], threadId, attachments, displayMessage, queuedMessageId, internalContinuation, turnId: requestTurnId, model: requestModel, engine: requestEngine, effort: requestEffort, browserTabId, scope, trackInRunsTray, } = body;
6151
+ const { message, history = [], structuredHistory, references = [], threadId, attachments, displayMessage, queuedMessageId, internalContinuation, turnId: requestTurnId, model: requestModel, engine: requestEngine, effort: requestEffort, browserTabId, scope, harness: requestHarness, trackInRunsTray, } = body;
6139
6152
  setupMark("bodyParsed");
6140
6153
  // Durable-background marker. Present ONLY when this handler was re-entered
6141
6154
  // as the Netlify background worker via the `_process-run` self-dispatch
@@ -6258,7 +6271,36 @@ export function createProductionAgentHandler(options) {
6258
6271
  requestAttachments = preparedRequest.attachments;
6259
6272
  }
6260
6273
  }
6261
- const availableRequestActions = getRequestActions();
6274
+ const requestedHostedHarness = normalizeHostedHarnessRuntime(requestHarness?.runtime);
6275
+ if (requestHarness !== undefined && !requestedHostedHarness) {
6276
+ setResponseStatus(event, 400);
6277
+ return { error: "Unsupported hosted harness runtime" };
6278
+ }
6279
+ const hostedHarnessPolicy = requestedHostedHarness
6280
+ ? await resolveHostedHarnessPolicy({
6281
+ config: options.hostedHarnessConfig,
6282
+ orgId: getRequestOrgId() ?? null,
6283
+ userEmail: ownerEmail,
6284
+ })
6285
+ : null;
6286
+ if (requestedHostedHarness &&
6287
+ (!hostedHarnessPolicy?.enabled ||
6288
+ !hostedHarnessPolicy.runtimes.includes(requestedHostedHarness))) {
6289
+ setResponseStatus(event, hostedHarnessPolicy?.configEnabled ? 403 : 404);
6290
+ return {
6291
+ error: hostedHarnessPolicy?.configEnabled
6292
+ ? "Hosted harness is not enabled for this organization"
6293
+ : "Hosted harness is not configured for this app",
6294
+ };
6295
+ }
6296
+ const runContext = ensureRequestRunContext();
6297
+ if (runContext && requestedHostedHarness) {
6298
+ runContext.hostedHarnessRuntime = requestedHostedHarness;
6299
+ }
6300
+ let availableRequestActions = getRequestActions();
6301
+ if (requestedHostedHarness) {
6302
+ availableRequestActions = filterActionsByAllowedNames(availableRequestActions, filterHostedHarnessToolNames(Object.keys(availableRequestActions)));
6303
+ }
6262
6304
  let surfacedRequestActions = availableRequestActions;
6263
6305
  if (options.resolveActionSurface) {
6264
6306
  const persistedSurface = isBackgroundWorker
@@ -6276,6 +6318,9 @@ export function createProductionAgentHandler(options) {
6276
6318
  availableActionNames: Object.keys(availableRequestActions),
6277
6319
  });
6278
6320
  surfacedRequestActions = filterActionsByAllowedNames(availableRequestActions, surface.allowedActionNames);
6321
+ if (requestedHostedHarness) {
6322
+ surfacedRequestActions = filterActionsByAllowedNames(surfacedRequestActions, filterHostedHarnessToolNames(Object.keys(surfacedRequestActions)));
6323
+ }
6279
6324
  const allowedNames = Object.keys(surfacedRequestActions);
6280
6325
  const runCtx = ensureRequestRunContext();
6281
6326
  if (runCtx)
@@ -6604,8 +6649,9 @@ export function createProductionAgentHandler(options) {
6604
6649
  // is unrelated to the app's job (e.g. a voice-first macro tracker).
6605
6650
  const filesContextThunk = () => (async () => {
6606
6651
  let filesContext = "";
6607
- if (options.skipFilesContext)
6652
+ if (options.skipFilesContext || requestedHostedHarness) {
6608
6653
  return filesContext;
6654
+ }
6609
6655
  if (history.length === 0) {
6610
6656
  try {
6611
6657
  const { resourceListAccessible, SHARED_OWNER, WORKSPACE_OWNER, resourceGet, } = await import("../resources/store.js");
@@ -6963,6 +7009,30 @@ export function createProductionAgentHandler(options) {
6963
7009
  // Keep the body-derived messages — never drop the run.
6964
7010
  }
6965
7011
  }
7012
+ // A foreground turn on an existing thread that arrives with NO history is
7013
+ // amnesia, not a new conversation — the client trims history against a size
7014
+ // budget and one tool-heavy turn could zero it out, which reads downstream
7015
+ // as an ordinary first message. Recover what was said from the stored
7016
+ // thread. Runs only on the foreground path, before `onRunPrepared` persists
7017
+ // this turn, so the recovered window cannot contain the current message.
7018
+ if (!isBackgroundWorker &&
7019
+ !internalContinuation &&
7020
+ threadId &&
7021
+ historyMessages.length === 0) {
7022
+ try {
7023
+ const { getThread } = await import("../chat-threads/store.js");
7024
+ const { recoverThreadHistoryForRequest } = await import("./thread-data-builder.js");
7025
+ const recovered = recoverThreadHistoryForRequest((await getThread(threadId))?.threadData);
7026
+ if (recovered.length > 0)
7027
+ messages.unshift(...recovered);
7028
+ }
7029
+ catch (err) {
7030
+ // Never drop the run over this — but never let it pass for "the thread
7031
+ // had nothing to recover" either. That silence is the same failure this
7032
+ // block exists to fix, one layer down.
7033
+ console.warn(`[agent-chat] history recovery failed for thread ${threadId}; continuing with no prior context:`, err);
7034
+ }
7035
+ }
6966
7036
  setupMark("depsThread");
6967
7037
  // DIAGNOSTIC-ONLY: owner/thread resolution + runId/effectiveThreadId +
6968
7038
  // chained-continuation thread fetch finished.
@@ -73,6 +73,18 @@ export declare function normalizeThreadRepository(repo: any): any;
73
73
  * Originally inlined in `integrations/webhook-handler.ts`.
74
74
  */
75
75
  export declare function threadDataToEngineMessages(threadData: string | Record<string, unknown> | null | undefined): EngineMessage[];
76
+ /**
77
+ * The trailing window of what was actually said in a thread, for a request that
78
+ * arrived carrying no history of its own. The client trims history against a
79
+ * size budget, so one tool-heavy turn can zero it out; without this floor the
80
+ * model re-derives answers it already gave and re-asks questions the user
81
+ * already answered. Contiguous and bounded on purpose — this restores the
82
+ * conversation, not the tool transcript.
83
+ */
84
+ export declare function recoverThreadHistoryForRequest(threadData: string | Record<string, unknown> | null | undefined, limits?: {
85
+ maxMessages?: number;
86
+ maxChars?: number;
87
+ }): EngineMessage[];
76
88
  /**
77
89
  * Select the participant-visible delivery for integration turns while keeping
78
90
  * a compact, trusted resource ledger available to the agent. Raw tool results
@@ -671,6 +671,29 @@ export function threadDataToEngineMessages(threadData) {
671
671
  }
672
672
  return messages;
673
673
  }
674
+ const MAX_RECOVERED_HISTORY_MESSAGES = 12;
675
+ const MAX_RECOVERED_HISTORY_CHARS = 32_000;
676
+ function engineMessageTextLength(message) {
677
+ return message.content.reduce((total, part) => total + (part.type === "text" ? (part.text?.length ?? 0) : 0), 0);
678
+ }
679
+ /**
680
+ * The trailing window of what was actually said in a thread, for a request that
681
+ * arrived carrying no history of its own. The client trims history against a
682
+ * size budget, so one tool-heavy turn can zero it out; without this floor the
683
+ * model re-derives answers it already gave and re-asks questions the user
684
+ * already answered. Contiguous and bounded on purpose — this restores the
685
+ * conversation, not the tool transcript.
686
+ */
687
+ export function recoverThreadHistoryForRequest(threadData, limits) {
688
+ const maxMessages = limits?.maxMessages ?? MAX_RECOVERED_HISTORY_MESSAGES;
689
+ const maxChars = limits?.maxChars ?? MAX_RECOVERED_HISTORY_CHARS;
690
+ const window = threadDataToEngineMessages(threadData).slice(-maxMessages);
691
+ let total = window.reduce((sum, message) => sum + engineMessageTextLength(message), 0);
692
+ while (window.length > 1 && total > maxChars) {
693
+ total -= engineMessageTextLength(window.shift());
694
+ }
695
+ return window;
696
+ }
674
697
  const MAX_INTEGRATION_ARTIFACTS_IN_CONTEXT = 12;
675
698
  const MAX_INTEGRATION_ARTIFACT_FIELD_CHARS = 500;
676
699
  function boundedString(value) {
@@ -129,6 +129,10 @@ export interface AgentChatScope {
129
129
  id: string;
130
130
  label?: string;
131
131
  }
132
+ export interface AgentChatHarnessRequest {
133
+ /** Hosted tools-only runtime selected in the production app picker. */
134
+ runtime: "claude-code" | "codex" | "pi" | "opencode";
135
+ }
132
136
  export interface AgentChatRequest {
133
137
  message: string;
134
138
  /** Stable identity of a durable queued message, used to reject replayed delivery. */
@@ -217,6 +221,8 @@ export interface AgentChatRequest {
217
221
  browserTabId?: string;
218
222
  /** Resource scope for this chat thread, e.g. the deck currently bound to the tab. */
219
223
  scope?: AgentChatScope | null;
224
+ /** Optional hosted tools-only harness selection. */
225
+ harness?: AgentChatHarnessRequest;
220
226
  /** When true, expose this chat turn as a user-visible run in RunsTray. */
221
227
  trackInRunsTray?: boolean;
222
228
  /**
@@ -175,6 +175,10 @@ export interface AgentSidebarProps {
175
175
  imageModelMenu?: AssistantChatProps["imageModelMenu"];
176
176
  /** Local or hosted agent runtimes shown above the model list. */
177
177
  availableAgents?: AssistantChatProps["availableAgents"];
178
+ /** Host-provided model catalog used by native chat surfaces. */
179
+ availableModels?: AssistantChatProps["availableModels"];
180
+ /** Whether the host-provided model catalog is still loading. */
181
+ modelListLoading?: AssistantChatProps["modelListLoading"];
178
182
  /** Selected agent runtime identifier. */
179
183
  selectedAgent?: AssistantChatProps["selectedAgent"];
180
184
  /** Callback when the user picks an agent runtime. */
@@ -246,7 +250,7 @@ export interface AgentSidebarProps {
246
250
  * Wraps app content with a toggleable agent sidebar.
247
251
  * Use AgentToggleButton in your header to open/close it.
248
252
  */
249
- export declare function AgentSidebar({ children, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, selectedAgent, onAgentChange, onConnectLocalRuntime, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, apiUrl, agentChatSurface, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
253
+ export declare function AgentSidebar({ children, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, apiUrl, agentChatSurface, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
250
254
  /**
251
255
  * Focus the agent chat composer input.
252
256
  * Opens the sidebar if closed, then focuses the text input.
@@ -25,6 +25,7 @@ import { Tooltip as DesignSystemTooltip } from "@agent-native/toolkit/design-sys
25
25
  import { IconMessageCircle, IconMessageDots, IconTerminal2, IconSettings, IconLayoutSidebarRightCollapse, IconLayoutGrid, IconCheck, IconPlus, IconX, IconDotsVertical, IconHistory, IconArrowsHorizontal, IconArrowsMaximize, IconExternalLink, IconShare3, } from "@tabler/icons-react";
26
26
  import React, { useState, useEffect, useRef, useCallback, useMemo, lazy, Suspense, startTransition, } from "react";
27
27
  import { flushSync } from "react-dom";
28
+ import { hostedHarnessAgentOption, isHostedHarnessConfigured, isHostedHarnessRuntime, normalizeHostedHarnessRuntimes, } from "../agent/harness/hosted.js";
28
29
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger, } from "./components/ui/dropdown-menu.js";
29
30
  import { normalizeTooltipText } from "./components/ui/tooltip.js";
30
31
  import { ErrorReportActions } from "./ErrorReportActions.js";
@@ -44,7 +45,8 @@ import { withBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js
44
45
  import { AGENT_SIDEBAR_MIN_WIDTH, consumeAgentSidebarUrlOpenOverride, clampAgentSidebarWidth, dispatchAgentSidebarStateChange, getAgentSidebarMaxWidth, getInitialAgentSidebarOpen, getAgentSidebarWideWidth, setAgentSidebarOpenPreference, subscribeAgentSidebarUrlChanges, SIDEBAR_STATE_CHANGE_EVENT, } from "./agent-sidebar-state.js";
45
46
  import { trackEvent } from "./analytics.js";
46
47
  import { agentNativePath, appPath, isWorkspaceAppPath } from "./api-path.js";
47
- import { APP_CHAT_SIDEBAR_STATE_EVENT, APP_CHAT_SIDEBAR_STATE_REQUEST_MESSAGE, buildAppChatSidebarStateMessage, isPerAppChatStorageKey, } from "./app-chat-sidebar.js";
48
+ import { APP_CHAT_SIDEBAR_STATE_EVENT, APP_CHAT_SIDEBAR_STATE_REQUEST_MESSAGE, buildAppChatSidebarStateMessage, isPerAppChatStorageKey, requestPerAppChatCommand, usePerAppChatState, } from "./app-chat-sidebar.js";
49
+ import { injectedAgentNativeConfig } from "./app-config.js";
48
50
  import { readClientAppState } from "./application-state.js";
49
51
  import { assistantUiRecoverableRenderErrorKind } from "./assistant-ui-recovery.js";
50
52
  import { shouldParentFrameOwnAgentPanel } from "./builder-frame.js";
@@ -58,6 +60,7 @@ import { useFirstRunOnboardingGateOwnsSurface } from "./onboarding/first-run-sta
58
60
  import { useOnboardingPreviewMode } from "./onboarding/use-preview-mode.js";
59
61
  import { recoverFromStaleChunkError } from "./route-chunk-recovery.js";
60
62
  import { withBuilderConnectTrackingParams } from "./settings/useBuilderStatus.js";
63
+ import { useActionQuery } from "./use-action.js";
61
64
  import { useScreenRefreshKey } from "./use-db-sync.js";
62
65
  import { useDevMode } from "./use-dev-mode.js";
63
66
  import { cn } from "./utils.js";
@@ -957,6 +960,7 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
957
960
  __html: "@media (hover:hover) and (pointer:fine){" +
958
961
  ".agent-sidebar-chat-header[data-agent-sidebar-chat-header]{opacity:0;pointer-events:none;transition:opacity 150ms ease-out;}" +
959
962
  ".agent-panel-root:hover .agent-sidebar-chat-header[data-agent-sidebar-chat-header],.agent-panel-root:focus-within .agent-sidebar-chat-header[data-agent-sidebar-chat-header],.agent-sidebar-chat-header[data-agent-sidebar-chat-header][data-agent-sidebar-chat-header-active]{opacity:1;pointer-events:auto;}" +
963
+ ".agent-sidebar-panel[data-agent-sidebar-per-app-chat='true'] .agent-sidebar-chat-header[data-agent-sidebar-chat-header]{opacity:1;pointer-events:auto;transition:none;}" +
960
964
  "}" +
961
965
  ".agent-tab-close{opacity:0}.agent-tab:hover .agent-tab-close{opacity:1}" +
962
966
  ".agent-tabs-scroll{scrollbar-width:none;-ms-overflow-style:none;}" +
@@ -1414,9 +1418,43 @@ export function AgentChatSurface({ mode = "panel", className, defaultMode = "cha
1414
1418
  * Wraps app content with a toggleable agent sidebar.
1415
1419
  * Use AgentToggleButton in your header to open/close it.
1416
1420
  */
1417
- export function AgentSidebar({ children, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, selectedAgent, onAgentChange, onConnectLocalRuntime, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, apiUrl, agentChatSurface, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
1421
+ export function AgentSidebar({ children, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, apiUrl, agentChatSurface, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
1422
+ const staticHostedHarnessEnabled = isHostedHarnessConfigured(injectedAgentNativeConfig().harness);
1423
+ const hostedHarnessQuery = useActionQuery("get-hosted-harness-config", undefined, { enabled: staticHostedHarnessEnabled });
1424
+ const hostedHarnessStatus = hostedHarnessQuery.data;
1425
+ const hostedHarnessEnabled = hostedHarnessStatus?.enabled === true;
1426
+ const hostedHarnessRuntimes = useMemo(() => hostedHarnessEnabled
1427
+ ? normalizeHostedHarnessRuntimes(hostedHarnessStatus?.runtimes)
1428
+ : [], [hostedHarnessEnabled, hostedHarnessStatus?.runtimes]);
1429
+ const hostedHarnessUi = hostedHarnessEnabled;
1430
+ const hostedHarnessStorageKey = `agent-native-hosted-harness${storageKey ? `:${storageKey}` : ""}`;
1431
+ const [hostedHarnessRuntime, setHostedHarnessRuntime] = useState("claude-code");
1432
+ const hostedHarnessAgentOptions = useMemo(() => hostedHarnessRuntimes.map(hostedHarnessAgentOption), [hostedHarnessRuntimes]);
1433
+ const effectiveAvailableAgents = hostedHarnessEnabled
1434
+ ? [
1435
+ ...hostedHarnessAgentOptions,
1436
+ ...(availableAgents ?? []).filter((agent) => !isHostedHarnessRuntime(agent.id)),
1437
+ ]
1438
+ : availableAgents;
1439
+ const effectiveSelectedAgent = hostedHarnessEnabled
1440
+ ? hostedHarnessRuntime
1441
+ : selectedAgent;
1442
+ const effectiveOnAgentChange = hostedHarnessEnabled
1443
+ ? (agent) => {
1444
+ if (isHostedHarnessRuntime(agent)) {
1445
+ setHostedHarnessRuntime(agent);
1446
+ }
1447
+ onAgentChange?.(agent);
1448
+ }
1449
+ : onAgentChange;
1450
+ const effectivePosition = hostedHarnessUi ? "left" : position;
1451
+ const effectiveDefaultOpen = hostedHarnessUi || defaultOpen;
1452
+ const effectiveShowTabBar = hostedHarnessUi || showTabBar;
1453
+ const effectiveAnimateDesktop = hostedHarnessUi ? false : animateDesktop;
1418
1454
  const sidebarOpenStorageKey = openStorageKey ?? storageKey;
1419
1455
  const isPerAppChatSidebar = isPerAppChatStorageKey(sidebarOpenStorageKey);
1456
+ const perAppChatState = usePerAppChatState(!isPerAppChatSidebar);
1457
+ const isPerAppChatHosted = !isPerAppChatSidebar && perAppChatState.hosted === true;
1420
1458
  const onboardingPreviewMode = useOnboardingPreviewMode();
1421
1459
  const firstRunOnboardingGateOwnsSurface = useFirstRunOnboardingGateOwnsSurface();
1422
1460
  const showFirstRunOnboarding = !firstRunOnboardingGateOwnsSurface &&
@@ -1424,7 +1462,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1424
1462
  (SHOW_FIRST_RUN_ONBOARDING || onboardingPreviewMode);
1425
1463
  const initialWidth = defaultSidebarWidth ?? sidebarWidth ?? 380;
1426
1464
  const [open, setOpen] = useState(() => openOnChatRunning ||
1427
- getInitialAgentSidebarOpen(defaultOpen, sidebarOpenStorageKey));
1465
+ getInitialAgentSidebarOpen(effectiveDefaultOpen, sidebarOpenStorageKey));
1428
1466
  const [presentationMode, setPresentationMode] = useState(false);
1429
1467
  const [width, setWidth] = useState(initialWidth);
1430
1468
  const [isWideDrawer, setIsWideDrawer] = useState(() => {
@@ -1485,6 +1523,45 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1485
1523
  return value;
1486
1524
  });
1487
1525
  }, [sidebarOpenStorageKey]);
1526
+ useEffect(() => {
1527
+ try {
1528
+ const saved = localStorage.getItem(hostedHarnessStorageKey);
1529
+ if (saved && isHostedHarnessRuntime(saved)) {
1530
+ setHostedHarnessRuntime(saved);
1531
+ }
1532
+ }
1533
+ catch {
1534
+ // coercion-ok: localStorage is optional persistence; memory state remains authoritative.
1535
+ // The picker falls back to Claude Code when storage is unavailable.
1536
+ }
1537
+ }, [hostedHarnessStorageKey]);
1538
+ useEffect(() => {
1539
+ if (!hostedHarnessEnabled || hostedHarnessRuntimes.length === 0)
1540
+ return;
1541
+ const next = hostedHarnessRuntimes.includes(hostedHarnessRuntime)
1542
+ ? hostedHarnessRuntime
1543
+ : hostedHarnessRuntimes[0];
1544
+ if (!next)
1545
+ return;
1546
+ if (next !== hostedHarnessRuntime)
1547
+ setHostedHarnessRuntime(next);
1548
+ try {
1549
+ localStorage.setItem(hostedHarnessStorageKey, next);
1550
+ }
1551
+ catch {
1552
+ // coercion-ok: localStorage is optional persistence; memory state remains authoritative.
1553
+ // The selected runtime remains in memory for this tab.
1554
+ }
1555
+ }, [
1556
+ hostedHarnessEnabled,
1557
+ hostedHarnessRuntimes,
1558
+ hostedHarnessRuntime,
1559
+ hostedHarnessStorageKey,
1560
+ ]);
1561
+ useEffect(() => {
1562
+ if (hostedHarnessUi)
1563
+ setOpenPersisted(true);
1564
+ }, [hostedHarnessUi, setOpenPersisted]);
1488
1565
  const applyUrlOpenOverride = useCallback(() => {
1489
1566
  const override = consumeAgentSidebarUrlOpenOverride(sidebarOpenStorageKey);
1490
1567
  if (override !== null)
@@ -1495,9 +1572,9 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1495
1572
  return subscribeAgentSidebarUrlChanges(applyUrlOpenOverride);
1496
1573
  }, [applyUrlOpenOverride]);
1497
1574
  useEffect(() => {
1498
- if (openOnChatRunning)
1575
+ if (openOnChatRunning && !isPerAppChatHosted)
1499
1576
  setOpen(true);
1500
- }, [openOnChatRunning]);
1577
+ }, [isPerAppChatHosted, openOnChatRunning]);
1501
1578
  // Track whether the frame is controlling the sidebar (code mode = frame active).
1502
1579
  // Default to true when inside an iframe — assume the frame sidebar is active
1503
1580
  // until told otherwise. This prevents both sidebars flashing after hot reloads.
@@ -1519,7 +1596,8 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1519
1596
  const [hasFrameSidebarState, setHasFrameSidebarState] = useState(false);
1520
1597
  const [backgroundPanelActive, setBackgroundPanelActive] = useState(false);
1521
1598
  const [runningTabIds, setRunningTabIds] = useState(() => new Set());
1522
- const shouldMountPanel = !presentationMode &&
1599
+ const shouldMountPanel = !isPerAppChatHosted &&
1600
+ !presentationMode &&
1523
1601
  (!frameCodeMode || !shouldParentFrameOwnAgentPanel()) &&
1524
1602
  (open || backgroundPanelActive || runningTabIds.size > 0);
1525
1603
  const shouldMountPanelRef = useRef(shouldMountPanel);
@@ -1531,10 +1609,12 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1531
1609
  // Skip the initial emit in frame-owned mode — wait until the frame has
1532
1610
  // sent us its real sidebar state. Once we know, this effect re-runs and
1533
1611
  // dispatches the correct value.
1534
- if (frameOwned && !hasFrameSidebarState)
1612
+ if (frameOwned && !hasFrameSidebarState && !isPerAppChatHosted)
1535
1613
  return;
1536
1614
  dispatchAgentSidebarStateChange({
1537
- open: !presentationMode && (frameOwned ? frameSidebarOpen : open),
1615
+ open: isPerAppChatHosted
1616
+ ? perAppChatState.open
1617
+ : !presentationMode && (frameOwned ? frameSidebarOpen : open),
1538
1618
  source: frameOwned ? "frame" : "app",
1539
1619
  mode: frameOwned ? "code" : "app",
1540
1620
  });
@@ -1544,6 +1624,8 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1544
1624
  open,
1545
1625
  presentationMode,
1546
1626
  hasFrameSidebarState,
1627
+ isPerAppChatHosted,
1628
+ perAppChatState.open,
1547
1629
  ]);
1548
1630
  useEffect(() => {
1549
1631
  if (!isPerAppChatSidebar)
@@ -1583,7 +1665,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1583
1665
  ? detail.tabId
1584
1666
  : "__default__";
1585
1667
  if (detail?.isRunning === true) {
1586
- if (openOnChatRunning)
1668
+ if (openOnChatRunning && !isPerAppChatHosted)
1587
1669
  setOpen(true);
1588
1670
  setRunningTabIds((prev) => {
1589
1671
  const next = new Set(prev);
@@ -1609,7 +1691,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1609
1691
  window.removeEventListener(AGENT_PANEL_PREPARE_EVENT, preparePanel);
1610
1692
  window.removeEventListener(AGENT_CHAT_RUNNING_EVENT, handleChatRunning);
1611
1693
  };
1612
- }, [openOnChatRunning, setOpenPersisted]);
1694
+ }, [isPerAppChatHosted, openOnChatRunning, setOpenPersisted]);
1613
1695
  useEffect(() => {
1614
1696
  const replayAfterMount = (type, event) => {
1615
1697
  if (shouldMountPanelRef.current)
@@ -1639,6 +1721,10 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1639
1721
  }, [setOpenPersisted]);
1640
1722
  useEffect(() => {
1641
1723
  const toggleHandler = () => {
1724
+ if (isPerAppChatHosted) {
1725
+ requestPerAppChatCommand("toggle");
1726
+ return;
1727
+ }
1642
1728
  if (frameCodeMode && shouldParentFrameOwnAgentPanel()) {
1643
1729
  // Forward toggle to frame parent — the frame sidebar handles it
1644
1730
  window.parent.postMessage({ type: "agentNative.toggleSidebar" }, parentFrameTargetOrigin());
@@ -1648,6 +1734,10 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1648
1734
  }
1649
1735
  };
1650
1736
  const openHandler = () => {
1737
+ if (isPerAppChatHosted) {
1738
+ requestPerAppChatCommand("open");
1739
+ return;
1740
+ }
1651
1741
  if (frameCodeMode && shouldParentFrameOwnAgentPanel()) {
1652
1742
  window.parent.postMessage({ type: "agentNative.toggleSidebar", data: { open: true } }, parentFrameTargetOrigin());
1653
1743
  }
@@ -1656,6 +1746,10 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1656
1746
  }
1657
1747
  };
1658
1748
  const closeHandler = () => {
1749
+ if (isPerAppChatHosted) {
1750
+ requestPerAppChatCommand("close");
1751
+ return;
1752
+ }
1659
1753
  if (frameCodeMode && shouldParentFrameOwnAgentPanel()) {
1660
1754
  window.parent.postMessage({ type: "agentNative.toggleSidebar", data: { open: false } }, parentFrameTargetOrigin());
1661
1755
  }
@@ -1671,7 +1765,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1671
1765
  window.removeEventListener("agent-panel:open", openHandler);
1672
1766
  window.removeEventListener("agent-panel:close", closeHandler);
1673
1767
  };
1674
- }, [setOpenPersisted, frameCodeMode]);
1768
+ }, [setOpenPersisted, frameCodeMode, isPerAppChatHosted]);
1675
1769
  // Listen for sidebar mode commands from the frame parent.
1676
1770
  // When frame is in "code" mode, hide the app sidebar.
1677
1771
  // When frame is in "app" mode, show the app sidebar, sync width and panel mode.
@@ -1848,10 +1942,10 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1848
1942
  }, [runDrawerMorph, drawerPlaceholderWidth, isWideDrawer]);
1849
1943
  const handleResizeStart = useCallback(() => setIsResizing(true), []);
1850
1944
  const handleResizeEnd = useCallback(() => setIsResizing(false), []);
1851
- const isLeft = position === "left";
1945
+ const isLeft = effectivePosition === "left";
1852
1946
  const wideDrawerEnabled = isWideDrawer && !isMobile;
1853
1947
  const mobileAnimationEnabled = !presentationMode && isMobile && animateMobile;
1854
- const desktopAnimationEnabled = !presentationMode && !isMobile && animateDesktop;
1948
+ const desktopAnimationEnabled = !presentationMode && !isMobile && effectiveAnimateDesktop;
1855
1949
  const sidebarAnimationEnabled = mobileAnimationEnabled || desktopAnimationEnabled;
1856
1950
  const [renderAnimatedPanel, setRenderAnimatedPanel] = useState(shouldMountPanel);
1857
1951
  useEffect(() => {
@@ -1940,6 +2034,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1940
2034
  background: "var(--agent-sidebar-background)",
1941
2035
  width: desktopAnimationEnabled ? undefined : width,
1942
2036
  maxHeight: "var(--agent-native-viewport-height, 100vh)",
2037
+ zIndex: hostedHarnessUi ? SIDEBAR_OVERLAY_Z_INDEX : undefined,
1943
2038
  borderLeft: !panelOpen || isLeft || showResizeHandle
1944
2039
  ? "none"
1945
2040
  : "1px solid hsl(var(--border))",
@@ -1956,17 +2051,18 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1956
2051
  }
1957
2052
  // Mount the live chat surface only while visible or actively needed. Keeping
1958
2053
  // it mounted while closed starts app-state polling on every public page view.
1959
- const sidebar = shouldRenderPanel ? (_jsxs(_Fragment, { children: [showResizeHandle && !isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd })), _jsx("div", { className: cn("agent-sidebar-panel flex shrink-0 flex-col overflow-hidden text-[13px] leading-[1.2] antialiased", chatViewTransition && AGENT_CHAT_VIEW_TRANSITION_CLASS), "data-agent-sidebar-animation": wideDrawerEnabled
2054
+ const sidebar = shouldRenderPanel ? (_jsxs(_Fragment, { children: [showResizeHandle && !isLeft && (_jsx(ResizeHandle, { position: effectivePosition, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd })), _jsx("div", { className: cn("agent-sidebar-panel flex shrink-0 flex-col overflow-hidden text-[13px] leading-[1.2] antialiased", chatViewTransition && AGENT_CHAT_VIEW_TRANSITION_CLASS), "data-agent-sidebar-animation": wideDrawerEnabled
1960
2055
  ? "drawer"
1961
2056
  : mobileAnimationEnabled
1962
2057
  ? "mobile"
1963
2058
  : desktopAnimationEnabled
1964
2059
  ? "desktop"
1965
- : undefined, "data-agent-sidebar-layout": panelLayout, "data-agent-sidebar-position": position, "data-agent-sidebar-state": panelOpen ? "open" : "closed", "data-agent-sidebar-per-app-chat": isPerAppChatSidebar ? "true" : undefined, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, "data-agent-sidebar-chat-handoff": chatViewTransitionHandoff ? "true" : undefined, style: chatViewTransition
2060
+ : undefined, "data-agent-sidebar-layout": panelLayout, "data-agent-sidebar-position": effectivePosition, "data-agent-native-hosted-harness-ui": hostedHarnessUi ? "desktop" : undefined, "data-agent-sidebar-state": panelOpen ? "open" : "closed", "data-agent-sidebar-per-app-chat": isPerAppChatSidebar ? "true" : undefined, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, "data-agent-sidebar-chat-handoff": chatViewTransitionHandoff ? "true" : undefined, style: chatViewTransition
1966
2061
  ? getAgentChatViewTransitionStyle(panelStyle)
1967
- : panelStyle, inert: sidebarAnimationEnabled && !panelOpen ? true : undefined, "aria-hidden": sidebarAnimationEnabled && !panelOpen ? true : undefined, children: _jsx("div", { className: "agent-sidebar-panel-inner flex min-h-0 flex-1 flex-col", children: _jsx(AgentPanel, { emptyStateText: emptyStateText, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, composerToolbarSlot: composerToolbarSlot, composerSlot: composerSlot, onComposerTextChange: onComposerTextChange, imageModelMenu: imageModelMenu, availableAgents: availableAgents, selectedAgent: selectedAgent, onAgentChange: onAgentChange, onConnectLocalRuntime: onConnectLocalRuntime, runtime: runtime, adapterReloadKey: adapterReloadKey, threadFooterSlot: threadFooterSlot, apiUrl: apiUrl, agentChatSurface: agentChatSurface, showTabBar: showTabBar, suppressInlineOpenApp: suppressInlineOpenApp, composerPlaceholder: composerPlaceholder, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, onFullViewRequest: onFullscreenRequest, storageKey: storageKey, scope: scope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
2062
+ : panelStyle, inert: sidebarAnimationEnabled && !panelOpen ? true : undefined, "aria-hidden": sidebarAnimationEnabled && !panelOpen ? true : undefined, children: _jsx("div", { className: "agent-sidebar-panel-inner flex min-h-0 flex-1 flex-col", children: _jsx(AgentPanel, { emptyStateText: emptyStateText, suggestions: suggestions, dynamicSuggestions: dynamicSuggestions, composerToolbarSlot: composerToolbarSlot, composerSlot: composerSlot, onComposerTextChange: onComposerTextChange, imageModelMenu: imageModelMenu, availableAgents: effectiveAvailableAgents, availableModels: availableModels, modelListLoading: modelListLoading, selectedAgent: effectiveSelectedAgent, onAgentChange: effectiveOnAgentChange, hostedHarness: hostedHarnessEnabled, onConnectLocalRuntime: onConnectLocalRuntime, runtime: runtime, adapterReloadKey: adapterReloadKey, threadFooterSlot: threadFooterSlot, apiUrl: apiUrl, agentChatSurface: agentChatSurface, showTabBar: effectiveShowTabBar, suppressInlineOpenApp: suppressInlineOpenApp, composerPlaceholder: composerPlaceholder, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, onFullViewRequest: onFullscreenRequest, storageKey: storageKey, scope: scope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: effectivePosition, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
1968
2063
  const drawerPlaceholder = wideDrawerEnabled && !presentationMode && panelOpen ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-drawer-placeholder shrink-0", "data-agent-sidebar-placeholder": "true", style: { width: drawerPlaceholderWidth + 1 } })) : null;
1969
- return (_jsx(AgentSidebarOnboardingContext.Provider, { value: true, children: _jsxs(RealtimeVoiceModeProvider, { browserTabId: browserTabId, children: [showFirstRunOnboarding && (_jsx(Suspense, { fallback: null, children: _jsx(FirstRunOnboarding, {}) })), _jsxs("div", { className: "agent-sidebar-shell flex min-w-0 flex-1 h-screen overflow-hidden", "data-agent-sidebar-position": position, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: [isMobile &&
2064
+ return (_jsx(AgentSidebarOnboardingContext.Provider, { value: true, children: _jsxs(RealtimeVoiceModeProvider, { browserTabId: browserTabId, children: [showFirstRunOnboarding && (_jsx(Suspense, { fallback: null, children: _jsx(FirstRunOnboarding, {}) })), _jsxs("div", { className: "agent-sidebar-shell flex min-w-0 flex-1 h-screen overflow-hidden", "data-agent-sidebar-position": effectivePosition, "data-agent-native-hosted-harness-ui": hostedHarnessUi ? "desktop" : undefined, "data-agent-native-hosted-chat": isPerAppChatHosted ? "true" : undefined, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: [isMobile &&
2065
+ !isPerAppChatHosted &&
1970
2066
  !presentationMode &&
1971
2067
  (mobileAnimationEnabled ? shouldRenderPanel : open) && (_jsx("div", { className: cn("agent-sidebar-backdrop fixed inset-0 bg-foreground/40", mobileAnimationEnabled && !panelOpen && "pointer-events-none"), "data-agent-sidebar-animation": mobileAnimationEnabled ? "mobile" : undefined, "data-agent-sidebar-state": panelOpen ? "open" : "closed", style: { zIndex: SIDEBAR_OVERLAY_Z_INDEX - 1 }, onClick: () => setOpenPersisted(false) })), shouldMountPanel ? _jsx(URLSync, { browserTabId: browserTabId }) : null, isResizing ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-resize-overlay" })) : null, isLeft && !presentationMode ? sidebar : null, isLeft && !presentationMode ? drawerPlaceholder : null, _jsx("div", { className: "agent-sidebar-main-surface flex flex-1 flex-col overflow-auto min-w-0", "data-agent-sidebar-main-position": position, "data-agent-sidebar-main-state": !isMobile && !presentationMode && panelOpen ? "open" : "closed", "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: _jsx(ScreenRefreshBoundary, { children: children }) }), !isLeft && !presentationMode ? drawerPlaceholder : null, !isLeft && !presentationMode ? sidebar : null] })] }) }));
1972
2068
  }
@@ -6,7 +6,7 @@ export interface AgentTaskCardProps {
6
6
  onOpen?: (threadId: string) => void;
7
7
  }
8
8
  /**
9
- * Rich preview card for a sub-agent task. Listens for agent-task-event
10
- * CustomEvents to update its state in real-time.
9
+ * Inline tool-call presentation for a spawned sub-agent. Listens for
10
+ * agent-task-event CustomEvents to update its streamed output in real-time.
11
11
  */
12
12
  export declare function AgentTaskCard({ taskId, threadId, description, onOpen, }: AgentTaskCardProps): React.JSX.Element;
@@ -1,5 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { IconLoader2, IconCheck, IconChevronRight, IconExternalLink, IconAlertCircle, IconPlayerStop, IconSubtask, } from "@tabler/icons-react";
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { IconLoader2, IconChevronRight, IconExternalLink, IconAlertCircle, IconPlayerStop, IconSubtask, } from "@tabler/icons-react";
3
3
  import { useState, useEffect, useRef, useCallback } from "react";
4
4
  import ReactMarkdown from "react-markdown";
5
5
  import remarkGfm from "remark-gfm";
@@ -7,8 +7,8 @@ import { agentNativePath } from "./api-path.js";
7
7
  import { useT } from "./i18n.js";
8
8
  import { cn } from "./utils.js";
9
9
  /**
10
- * Rich preview card for a sub-agent task. Listens for agent-task-event
11
- * CustomEvents to update its state in real-time.
10
+ * Inline tool-call presentation for a spawned sub-agent. Listens for
11
+ * agent-task-event CustomEvents to update its streamed output in real-time.
12
12
  */
13
13
  export function AgentTaskCard({ taskId, threadId, description, onOpen, }) {
14
14
  const t = useT();
@@ -127,24 +127,10 @@ export function AgentTaskCard({ taskId, threadId, description, onOpen, }) {
127
127
  const isError = status === "errored";
128
128
  const taskTitle = description.trim() || "Task";
129
129
  const currentStepText = currentStep.trim();
130
- const statusLabel = isRunning ? "Running" : isError ? "Error" : "Done";
131
- const statusClassName = cn("inline-flex h-5 shrink-0 items-center gap-1 rounded-md px-1.5 text-[10px] font-medium", isError
132
- ? "bg-destructive/10 text-destructive"
133
- : isComplete
134
- ? "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"
135
- : "bg-muted text-muted-foreground");
136
130
  const displayText = isComplete && summary ? summary : preview;
137
131
  const hasContent = displayText.length > 0;
138
- const emptyMessage = isRunning
139
- ? "Waiting for updates"
140
- : isError
141
- ? "No error details yet"
142
- : "No summary available";
143
- return (_jsxs("div", { className: cn("my-2 overflow-hidden rounded-lg border bg-background/50 transition-colors", isError
144
- ? "border-destructive/30"
145
- : isComplete
146
- ? "border-emerald-500/20"
147
- : "border-border"), children: [_jsxs("button", { type: "button", "aria-expanded": expanded, className: "flex w-full items-center gap-2 px-3 py-2 text-start transition-colors hover:bg-muted/45", onClick: () => setExpanded(!expanded), children: [_jsxs("span", { className: "inline-flex h-5 shrink-0 items-center gap-1 rounded-md border border-border/60 bg-background/70 px-1.5 text-[10px] font-medium text-muted-foreground", children: [_jsx(IconSubtask, { className: "h-3 w-3" }), t("agentTask.backgroundTask")] }), _jsx("span", { className: "min-w-0 flex-1 truncate text-xs font-medium text-foreground", children: taskTitle }), _jsxs("span", { className: statusClassName, children: [isRunning ? (_jsx(IconLoader2, { className: "h-3 w-3 animate-spin" })) : isError ? (_jsx(IconAlertCircle, { className: "h-3 w-3" })) : (_jsx(IconCheck, { className: "h-3 w-3" })), statusLabel] }), _jsx(IconChevronRight, { className: cn("h-3 w-3 shrink-0 text-muted-foreground/40 transition-transform duration-150", expanded && "rotate-90", "rtl:-scale-x-100") })] }), expanded && isRunning && currentStepText && (_jsxs("div", { className: "flex gap-1.5 border-t border-border/60 px-3 py-1.5 text-[11px] text-muted-foreground", children: [_jsx("span", { className: "shrink-0 font-medium text-foreground/70", children: "Now:" }), _jsx("span", { className: "min-w-0 break-words", children: currentStepText })] })), expanded && hasContent && (_jsx("div", { className: "px-3 pb-2 pt-1", children: _jsx("div", { ref: previewRef, className: "agent-markdown prose prose-sm prose-invert max-h-48 max-w-none overflow-y-auto break-words rounded-md border border-border/50 bg-muted/25 px-3 py-2 text-xs text-muted-foreground", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: displayText.length > 800
148
- ? "..." + displayText.slice(-800)
149
- : displayText }) }) })), expanded && (_jsxs("div", { className: "flex items-center justify-between gap-2 px-3 pb-2", children: [_jsx("span", { className: "min-w-0 flex-1 truncate text-[10px] text-muted-foreground/60", children: !hasContent ? emptyMessage : "" }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [isRunning && (_jsxs("button", { onClick: handleStop, className: "inline-flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-muted hover:text-destructive", "aria-label": t("agentTask.stop"), children: [_jsx(IconPlayerStop, { className: "h-3 w-3" }), "Stop"] })), _jsxs("button", { onClick: handleOpen, className: "inline-flex cursor-pointer items-center gap-1 rounded-md px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-muted hover:text-foreground", children: [t("agentTask.openThread"), _jsx(IconExternalLink, { className: "h-3 w-3" })] })] })] }))] }));
132
+ const statusLabel = isRunning ? "Running" : isError ? "Error" : "Done";
133
+ return (_jsxs("div", { className: "group/agent my-0.5 w-full", children: [_jsxs("button", { type: "button", "aria-expanded": expanded, className: "flex w-full items-center gap-1.5 rounded-md py-0.5 text-left text-[13px] text-muted-foreground transition-colors hover:text-foreground", onClick: () => setExpanded(!expanded), children: [_jsx("span", { className: "relative flex size-4 shrink-0 items-center justify-center", children: isRunning ? (_jsx(IconLoader2, { className: "size-3.5 animate-spin" })) : isError ? (_jsx(IconAlertCircle, { className: "size-3.5 text-destructive" })) : (_jsxs(_Fragment, { children: [_jsx(IconSubtask, { className: cn("size-3.5 transition-opacity", "group-hover/agent:opacity-0") }), _jsx(IconChevronRight, { className: cn("absolute size-3.5 opacity-0 transition-[opacity,transform] group-hover/agent:opacity-100", expanded && "rotate-90") })] })) }), _jsxs("span", { className: cn("min-w-0 truncate font-normal", isRunning && "agent-running-shimmer"), children: [t("agentTask.spawnedAgent"), ": ", taskTitle] }), _jsx("span", { className: "sr-only", children: statusLabel })] }), expanded && (_jsxs("div", { className: "ms-1 border-s border-border/50 ps-2 pt-1", children: [isRunning && currentStepText && (_jsx("p", { className: "ps-5 pb-1 text-xs text-muted-foreground", "aria-live": "polite", children: currentStepText })), hasContent && (_jsx("div", { className: "ps-5 pb-1", children: _jsx("div", { ref: previewRef, "aria-live": isRunning ? "polite" : undefined, "data-streaming": isRunning ? "true" : undefined, className: "agent-markdown prose prose-sm prose-invert max-h-48 max-w-none overflow-y-auto break-words text-xs text-muted-foreground", children: _jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: displayText.length > 800
134
+ ? "..." + displayText.slice(-800)
135
+ : displayText }) }) })), _jsxs("div", { className: "flex items-center gap-1 ps-5 pb-1", children: [isRunning && (_jsxs("button", { onClick: handleStop, className: "inline-flex cursor-pointer items-center gap-1 rounded-md px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-destructive", "aria-label": t("agentTask.stop"), children: [_jsx(IconPlayerStop, { className: "size-3" }), "Stop"] })), _jsxs("button", { onClick: handleOpen, className: "inline-flex cursor-pointer items-center gap-1 rounded-md px-1.5 py-0.5 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground", children: [t("agentTask.openThread"), _jsx(IconExternalLink, { className: "size-3" })] })] })] }))] }));
150
136
  }
@@ -242,6 +242,12 @@ export interface AssistantChatAdapterContext {
242
242
  effortRef: {
243
243
  current: ReasoningEffort | undefined;
244
244
  };
245
+ harnessRef?: {
246
+ current: string | undefined;
247
+ };
248
+ hostedHarnessRef?: {
249
+ current: boolean;
250
+ };
245
251
  execModeRef: {
246
252
  current: "build" | "plan" | undefined;
247
253
  };
@@ -369,6 +375,8 @@ export interface AssistantChatProps {
369
375
  availableAgents?: ComposerAgentOption[];
370
376
  /** Selected agent runtime identifier. */
371
377
  selectedAgent?: string;
378
+ /** Mark the selected runtime as the hosted tools-only harness mode. */
379
+ hostedHarness?: boolean;
372
380
  /** Callback when the user picks an agent runtime. */
373
381
  onAgentChange?: (agent: string) => void;
374
382
  /**