@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.
- 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/create-meeting.ts +19 -1
- 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/components/meetings/bullet-link.tsx +48 -25
- package/corpus/templates/clips/app/components/meetings/canvas-editor.tsx +7 -6
- package/corpus/templates/clips/app/components/meetings/transcript-bubbles.tsx +388 -178
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +7 -13
- package/corpus/templates/clips/app/components/transcript/transcript-segment-row.tsx +98 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +5 -0
- package/corpus/templates/clips/app/lib/meeting-history-query.ts +11 -0
- package/corpus/templates/clips/app/lib/public-meeting.ts +4 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +49 -31
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +47 -21
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +15 -1
- package/corpus/templates/clips/netlify.toml +8 -4
- package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +10 -0
- package/corpus/templates/clips/shared/transcript-segments.ts +20 -2
- 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/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +52 -0
- package/dist/deploy/build.js +28 -11
- 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/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 +4 -2
- package/src/templates/chat/agent-native.config.ts +5 -0
- 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
|
-
|
|
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
|
|
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) {
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -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(
|
|
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 = !
|
|
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:
|
|
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 =
|
|
1945
|
+
const isLeft = effectivePosition === "left";
|
|
1852
1946
|
const wideDrawerEnabled = isWideDrawer && !isMobile;
|
|
1853
1947
|
const mobileAnimationEnabled = !presentationMode && isMobile && animateMobile;
|
|
1854
|
-
const desktopAnimationEnabled = !presentationMode && !isMobile &&
|
|
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:
|
|
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":
|
|
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:
|
|
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":
|
|
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
|
-
*
|
|
10
|
-
* CustomEvents to update its
|
|
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,
|
|
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
|
-
*
|
|
11
|
-
* CustomEvents to update its
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
/**
|