@agent-native/core 0.125.0 → 0.127.1
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 +2 -2
- package/corpus/core/CHANGELOG.md +79 -0
- package/corpus/core/docs/content/external-agents-catalog.mdx +14 -11
- package/corpus/core/docs/content/mcp-protocol.mdx +9 -4
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/index.ts +2 -0
- package/corpus/core/src/agent/production-agent.ts +6 -1
- package/corpus/core/src/agent/run-manager.ts +37 -0
- package/corpus/core/src/agent/run-store.ts +18 -0
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +75 -43
- package/corpus/core/src/client/agent-chat.ts +19 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +25 -0
- package/corpus/core/src/client/resources/mcp-integration-catalog.ts +23 -0
- package/corpus/core/src/client/settings/AgentsSection.tsx +565 -71
- package/corpus/core/src/client/settings/SecretsSection.tsx +71 -36
- package/corpus/core/src/client/use-chat-models.ts +54 -37
- package/corpus/core/src/localization/default-messages.ts +11 -0
- package/corpus/core/src/mcp/ask-app-task-handle.ts +183 -0
- package/corpus/core/src/mcp/builtin-tools.ts +146 -34
- package/corpus/core/src/server/agent-capabilities.ts +8 -1
- package/corpus/core/src/server/agent-chat-plugin.ts +17 -0
- package/corpus/core/src/server/agent-discovery.ts +1 -1
- package/corpus/core/src/server/agent-peer-probe.ts +133 -0
- package/corpus/core/src/server/core-routes-plugin.ts +67 -0
- package/corpus/core/src/server/email-template.ts +13 -0
- package/corpus/core/src/server/email.ts +40 -4
- package/corpus/core/src/shared/mcp-embed-headers.ts +4 -0
- package/corpus/core/src/sharing/actions/share-resource.ts +55 -5
- package/corpus/core/src/sharing/registry.ts +41 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +159 -119
- package/corpus/templates/assets/actions/edit-image.ts +31 -18
- package/corpus/templates/assets/actions/refine-image.ts +43 -29
- package/corpus/templates/assets/actions/restyle-image.ts +30 -19
- package/corpus/templates/assets/actions/variant-slots.ts +26 -0
- package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +85 -17
- package/corpus/templates/clips/changelog/2026-07-27-share-notification-emails-now-include-a-clickable-recording-.md +6 -0
- package/corpus/templates/clips/server/db/index.ts +28 -6
- package/corpus/templates/clips/server/lib/share-email-hero.ts +105 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +100 -12
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +17 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +169 -22
- package/corpus/templates/design/app/hooks/use-question-flow.ts +21 -1
- package/corpus/templates/design/changelog/2026-07-28-the-model-you-pick-when-starting-a-new-design-is-now-used-fo.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-28-pending-apps-are-hidden-by-default-and-app-ownership-details.md +6 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +5 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +5 -0
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +31 -8
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +15 -0
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +78 -8
- package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +179 -0
- package/corpus/templates/slides/app/components/presentation/present-channel.ts +15 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +10 -1
- package/corpus/templates/slides/app/lib/export-google-slides-client.ts +67 -0
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +16 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +11 -0
- package/corpus/templates/slides/app/pages/Presentation.tsx +11 -3
- package/corpus/templates/slides/changelog/2026-07-28-export-to-google-slides-now-creates-the-deck-directly-in-you.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-28-opening-a-presentation-link-directly-no-longer-bounces-back-.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-28-presenter-view-open-a-second-window-while-presenting-to-see-.md +6 -0
- package/corpus/templates/slides/server/routes/api/exports/google-slides.post.ts +90 -0
- package/corpus/toolkit/CHANGELOG.md +6 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/composer/TiptapComposer.tsx +143 -111
- package/corpus/toolkit/src/composer/runtime-adapters.tsx +2 -0
- package/dist/a2a/index.d.ts +2 -0
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -0
- package/dist/a2a/index.js.map +1 -1
- package/dist/agent/production-agent.d.ts +2 -2
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +6 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +11 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +34 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +8 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +16 -0
- package/dist/agent/run-store.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +60 -31
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts +6 -0
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +13 -1
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +14 -0
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.d.ts +6 -0
- package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.js +16 -0
- package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
- package/dist/client/settings/AgentsSection.d.ts.map +1 -1
- package/dist/client/settings/AgentsSection.js +250 -30
- package/dist/client/settings/AgentsSection.js.map +1 -1
- package/dist/client/settings/SecretsSection.d.ts.map +1 -1
- package/dist/client/settings/SecretsSection.js +16 -2
- package/dist/client/settings/SecretsSection.js.map +1 -1
- package/dist/client/use-chat-models.d.ts.map +1 -1
- package/dist/client/use-chat-models.js +46 -27
- package/dist/client/use-chat-models.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +9 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +9 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp/ask-app-task-handle.d.ts +25 -0
- package/dist/mcp/ask-app-task-handle.d.ts.map +1 -0
- package/dist/mcp/ask-app-task-handle.js +136 -0
- package/dist/mcp/ask-app-task-handle.js.map +1 -0
- package/dist/mcp/builtin-tools.d.ts +3 -3
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +115 -38
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-capabilities.d.ts +7 -1
- package/dist/server/agent-capabilities.d.ts.map +1 -1
- package/dist/server/agent-capabilities.js +1 -0
- package/dist/server/agent-capabilities.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +7 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +16 -1
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-discovery.d.ts +1 -0
- package/dist/server/agent-discovery.d.ts.map +1 -1
- package/dist/server/agent-discovery.js +1 -1
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/agent-peer-probe.d.ts +37 -0
- package/dist/server/agent-peer-probe.d.ts.map +1 -0
- package/dist/server/agent-peer-probe.js +90 -0
- package/dist/server/agent-peer-probe.js.map +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +53 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/email-template.d.ts +6 -0
- package/dist/server/email-template.d.ts.map +1 -1
- package/dist/server/email-template.js +6 -0
- package/dist/server/email-template.js.map +1 -1
- package/dist/server/email.d.ts +7 -0
- package/dist/server/email.d.ts.map +1 -1
- package/dist/server/email.js +31 -6
- package/dist/server/email.js.map +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -0
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +4 -0
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/sharing/actions/share-resource.d.ts.map +1 -1
- package/dist/sharing/actions/share-resource.js +49 -5
- package/dist/sharing/actions/share-resource.js.map +1 -1
- package/dist/sharing/registry.d.ts +40 -1
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +159 -119
- package/docs/content/external-agents-catalog.mdx +14 -11
- package/docs/content/mcp-protocol.mdx +9 -4
- package/package.json +2 -2
- package/src/a2a/index.ts +2 -0
- package/src/agent/production-agent.ts +6 -1
- package/src/agent/run-manager.ts +37 -0
- package/src/agent/run-store.ts +18 -0
- package/src/client/MultiTabAssistantChat.tsx +75 -43
- package/src/client/agent-chat.ts +19 -1
- package/src/client/chat/tool-call-display.tsx +25 -0
- package/src/client/resources/mcp-integration-catalog.ts +23 -0
- package/src/client/settings/AgentsSection.tsx +565 -71
- package/src/client/settings/SecretsSection.tsx +71 -36
- package/src/client/use-chat-models.ts +54 -37
- package/src/localization/default-messages.ts +11 -0
- package/src/mcp/ask-app-task-handle.ts +183 -0
- package/src/mcp/builtin-tools.ts +146 -34
- package/src/server/agent-capabilities.ts +8 -1
- package/src/server/agent-chat-plugin.ts +17 -0
- package/src/server/agent-discovery.ts +1 -1
- package/src/server/agent-peer-probe.ts +133 -0
- package/src/server/core-routes-plugin.ts +67 -0
- package/src/server/email-template.ts +13 -0
- package/src/server/email.ts +40 -4
- package/src/shared/mcp-embed-headers.ts +4 -0
- package/src/sharing/actions/share-resource.ts +55 -5
- package/src/sharing/registry.ts +41 -1
- package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +159 -119
package/src/agent/run-manager.ts
CHANGED
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
getRunEventsSince,
|
|
16
16
|
getRunById,
|
|
17
17
|
getRunByThread,
|
|
18
|
+
getRunTurnRef,
|
|
19
|
+
markTurnAborted,
|
|
18
20
|
cleanupOldRuns,
|
|
19
21
|
updateRunHeartbeat,
|
|
20
22
|
bumpRunProgress,
|
|
@@ -2095,5 +2097,40 @@ export async function abortRunDurably(
|
|
|
2095
2097
|
return abortedInMemory;
|
|
2096
2098
|
}
|
|
2097
2099
|
|
|
2100
|
+
/**
|
|
2101
|
+
* Stop the whole turn `runId` belongs to, not just that run.
|
|
2102
|
+
*
|
|
2103
|
+
* A turn is executed as a chain of runs: every `loop_limit` / `auto_continue`
|
|
2104
|
+
* boundary and every background handoff starts a successor run with a NEW run
|
|
2105
|
+
* id under the same turn id. Aborting one run therefore only ends the current
|
|
2106
|
+
* chunk — the successor claims itself and the turn keeps going, which is what
|
|
2107
|
+
* users see as "Stop didn't stop it". The durable turn-abort marker is the only
|
|
2108
|
+
* thing the successor-claim path (`isTurnAborted`) consults.
|
|
2109
|
+
*/
|
|
2110
|
+
export async function abortTurnDurably(
|
|
2111
|
+
runId: string,
|
|
2112
|
+
reason: string = "user",
|
|
2113
|
+
): Promise<void> {
|
|
2114
|
+
const memRun = activeRuns.get(runId);
|
|
2115
|
+
// In-memory first: a foreground run's SQL insert is async, so the row may not
|
|
2116
|
+
// exist yet when Stop lands moments after send.
|
|
2117
|
+
const ref = memRun
|
|
2118
|
+
? { threadId: memRun.threadId, turnId: memRun.turnId }
|
|
2119
|
+
: await getRunTurnRef(runId).catch(() => null);
|
|
2120
|
+
if (!ref) return;
|
|
2121
|
+
try {
|
|
2122
|
+
await markTurnAborted(ref.threadId, ref.turnId, reason);
|
|
2123
|
+
} catch (error) {
|
|
2124
|
+
// The current run is already stopped; a failed marker write must not turn
|
|
2125
|
+
// Stop into a 500. Successors will keep running — capture it so that is
|
|
2126
|
+
// visible rather than silent.
|
|
2127
|
+
captureError(error, {
|
|
2128
|
+
route: "/_agent-native/agent-chat/runs/:id/abort",
|
|
2129
|
+
tags: { source: "agent-run-manager", phase: "abort-turn" },
|
|
2130
|
+
extra: { runId, reason, ...ref },
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2098
2135
|
// Re-export so callers can avoid importing from run-store directly.
|
|
2099
2136
|
export { tryClaimRunSlot } from "./run-store.js";
|
package/src/agent/run-store.ts
CHANGED
|
@@ -2053,6 +2053,24 @@ export async function markTurnAborted(
|
|
|
2053
2053
|
);
|
|
2054
2054
|
}
|
|
2055
2055
|
|
|
2056
|
+
/**
|
|
2057
|
+
* Turn identity for a run, or null when the row is unknown. `turn_id` is null
|
|
2058
|
+
* for rows written before the column existed; those runs are their own turn.
|
|
2059
|
+
*/
|
|
2060
|
+
export async function getRunTurnRef(
|
|
2061
|
+
runId: string,
|
|
2062
|
+
): Promise<{ threadId: string; turnId: string } | null> {
|
|
2063
|
+
await ensureRunTables();
|
|
2064
|
+
const { rows } = await getDbExec().execute({
|
|
2065
|
+
sql: `SELECT thread_id, turn_id FROM agent_runs WHERE id = ?`,
|
|
2066
|
+
args: [runId],
|
|
2067
|
+
});
|
|
2068
|
+
const row = rows[0] as { thread_id?: unknown; turn_id?: unknown } | undefined;
|
|
2069
|
+
const threadId = row?.thread_id ? String(row.thread_id) : "";
|
|
2070
|
+
if (!threadId) return null;
|
|
2071
|
+
return { threadId, turnId: row?.turn_id ? String(row.turn_id) : runId };
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2056
2074
|
export async function isTurnAborted(
|
|
2057
2075
|
threadId: string,
|
|
2058
2076
|
turnId: string,
|
|
@@ -88,6 +88,8 @@ interface PendingSend {
|
|
|
88
88
|
interface PendingDelivery {
|
|
89
89
|
threadId: string | null;
|
|
90
90
|
send: PendingSend;
|
|
91
|
+
/** Applied to whichever thread this send lands on; a queued send outlives the handler that parsed it. */
|
|
92
|
+
modelOverride?: ModelSelection;
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
/** The single path that hands a queued send to a mounted chat ref. */
|
|
@@ -154,28 +156,28 @@ function resolveModelSelection(
|
|
|
154
156
|
groups: EngineModelGroup[],
|
|
155
157
|
): ModelSelection | undefined {
|
|
156
158
|
if (!selection?.model) return undefined;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
);
|
|
174
|
-
if (groups.length > 0 && !preferredGroup && !fallbackGroup) {
|
|
175
|
-
return undefined;
|
|
176
|
-
}
|
|
159
|
+
// Engine precedence turns on whether the catalog OFFERS the supplied engine,
|
|
160
|
+
// not on whether that engine advertises this model:
|
|
161
|
+
// offered → honor it. A gateway's advertised list is its built-in
|
|
162
|
+
// catalog, not what the endpoint serves, and one model id
|
|
163
|
+
// sits under several groups (claude-sonnet-5 under both
|
|
164
|
+
// anthropic and builder) — so a model-only match would
|
|
165
|
+
// reroute the turn to a different provider and bill it there.
|
|
166
|
+
// not offered → heal to a group that serves the model. A selection left
|
|
167
|
+
// on `builder` after disconnecting it must still run on the
|
|
168
|
+
// user's own key; the same model through another configured
|
|
169
|
+
// provider is the point of bring-your-own-key.
|
|
170
|
+
// `builder` is in the catalog exactly when Builder is connected, so this is a
|
|
171
|
+
// real availability signal rather than a guess.
|
|
172
|
+
const suppliedEngine = selection.engine?.trim()
|
|
173
|
+
? selection.engine
|
|
174
|
+
: undefined;
|
|
177
175
|
const engine =
|
|
178
|
-
|
|
176
|
+
(groups.some((group) => group.engine === suppliedEngine)
|
|
177
|
+
? suppliedEngine
|
|
178
|
+
: undefined) ??
|
|
179
|
+
groups.find((group) => group.models.includes(selection.model))?.engine ??
|
|
180
|
+
suppliedEngine;
|
|
179
181
|
if (!engine && groups.length > 0) return undefined;
|
|
180
182
|
|
|
181
183
|
const effort = resolveReasoningEffortSelection(
|
|
@@ -1120,7 +1122,14 @@ export function MultiTabAssistantChat({
|
|
|
1120
1122
|
.catch(() => null),
|
|
1121
1123
|
])
|
|
1122
1124
|
.then(([enginesData, envKeys, builderStatus]) => {
|
|
1123
|
-
if (!enginesData?.engines)
|
|
1125
|
+
if (!enginesData?.engines) {
|
|
1126
|
+
// Leaves `availableModels` empty for the session, so an override with
|
|
1127
|
+
// no engine of its own has nothing to resolve against.
|
|
1128
|
+
console.warn(
|
|
1129
|
+
"[agent-chat] no engine list; model overrides cannot be catalog-resolved",
|
|
1130
|
+
);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1124
1133
|
const configuredKeys = new Set(
|
|
1125
1134
|
(envKeys as Array<{ key: string; configured: boolean }>)
|
|
1126
1135
|
.filter((k) => k.configured)
|
|
@@ -1598,6 +1607,7 @@ export function MultiTabAssistantChat({
|
|
|
1598
1607
|
context,
|
|
1599
1608
|
openSidebar,
|
|
1600
1609
|
model,
|
|
1610
|
+
engine,
|
|
1601
1611
|
effort,
|
|
1602
1612
|
newTab,
|
|
1603
1613
|
background,
|
|
@@ -1638,32 +1648,44 @@ export function MultiTabAssistantChat({
|
|
|
1638
1648
|
...(submitMessageId ? { submitMessageId } : {}),
|
|
1639
1649
|
};
|
|
1640
1650
|
|
|
1651
|
+
// Resolved once, up front, and carried with the send until a thread
|
|
1652
|
+
// exists to key it by. Applying it only when `model` matched
|
|
1653
|
+
// `availableModels` dropped every override that arrived before the
|
|
1654
|
+
// engines fetch resolved — and the cold-start replay below runs at mount,
|
|
1655
|
+
// when that list is always still empty.
|
|
1656
|
+
let modelOverride: ModelSelection | undefined;
|
|
1657
|
+
if (model) {
|
|
1658
|
+
const catalogEngine = availableModels.find((g) =>
|
|
1659
|
+
g.models.includes(model),
|
|
1660
|
+
)?.engine;
|
|
1661
|
+
const overrideEngine = engine ?? catalogEngine;
|
|
1662
|
+
if (!overrideEngine && availableModels.length > 0) {
|
|
1663
|
+
console.warn(
|
|
1664
|
+
`[agent-chat] model override "${model}" is in no engine group and carries no engine; the server will substitute its default`,
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
modelOverride = {
|
|
1668
|
+
model,
|
|
1669
|
+
...(overrideEngine ? { engine: overrideEngine } : {}),
|
|
1670
|
+
effort: resolveReasoningEffortSelection(
|
|
1671
|
+
model,
|
|
1672
|
+
isReasoningEffort(effort) ? effort : undefined,
|
|
1673
|
+
),
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1641
1677
|
const sendToTab = (threadId: string) => {
|
|
1642
1678
|
if (isAgentChatSubmitCancelled(submitMessageId)) return;
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
g.models.includes(model),
|
|
1647
|
-
);
|
|
1648
|
-
if (matchedGroup) {
|
|
1649
|
-
const selectedEffort = resolveReasoningEffortSelection(
|
|
1650
|
-
model,
|
|
1651
|
-
isReasoningEffort(effort) ? effort : undefined,
|
|
1652
|
-
);
|
|
1653
|
-
threadModelRef.current.set(threadId, {
|
|
1654
|
-
model,
|
|
1655
|
-
engine: matchedGroup.engine,
|
|
1656
|
-
effort: selectedEffort,
|
|
1657
|
-
});
|
|
1658
|
-
bumpModelSelectionVersion();
|
|
1659
|
-
}
|
|
1679
|
+
if (modelOverride) {
|
|
1680
|
+
threadModelRef.current.set(threadId, modelOverride);
|
|
1681
|
+
bumpModelSelectionVersion();
|
|
1660
1682
|
}
|
|
1661
1683
|
|
|
1662
1684
|
const ref = chatRefs.current.get(threadId);
|
|
1663
1685
|
if (ref) {
|
|
1664
1686
|
deliverPendingSend(ref, send);
|
|
1665
1687
|
} else {
|
|
1666
|
-
pendingDeliveries.current.push({ threadId, send });
|
|
1688
|
+
pendingDeliveries.current.push({ threadId, send, modelOverride });
|
|
1667
1689
|
}
|
|
1668
1690
|
};
|
|
1669
1691
|
|
|
@@ -1709,7 +1731,11 @@ export function MultiTabAssistantChat({
|
|
|
1709
1731
|
} else {
|
|
1710
1732
|
// Cold start: no thread yet. Queue for the first active thread (the
|
|
1711
1733
|
// bootstrap effect creates it) rather than racing a second create.
|
|
1712
|
-
pendingDeliveries.current.push({
|
|
1734
|
+
pendingDeliveries.current.push({
|
|
1735
|
+
threadId: null,
|
|
1736
|
+
send,
|
|
1737
|
+
modelOverride,
|
|
1738
|
+
});
|
|
1713
1739
|
}
|
|
1714
1740
|
}
|
|
1715
1741
|
};
|
|
@@ -1762,16 +1788,22 @@ export function MultiTabAssistantChat({
|
|
|
1762
1788
|
if (isAgentChatSubmitCancelled(delivery.send.submitMessageId)) continue;
|
|
1763
1789
|
const threadId = delivery.threadId ?? active ?? null;
|
|
1764
1790
|
const ref = threadId ? chatRefs.current.get(threadId) : null;
|
|
1791
|
+
if (threadId && delivery.modelOverride) {
|
|
1792
|
+
threadModelRef.current.set(threadId, delivery.modelOverride);
|
|
1793
|
+
bumpModelSelectionVersion();
|
|
1794
|
+
}
|
|
1765
1795
|
if (threadId && ref) {
|
|
1766
1796
|
const { send } = delivery;
|
|
1767
1797
|
setTimeout(() => deliverPendingSend(ref, send), 50);
|
|
1768
1798
|
} else {
|
|
1769
1799
|
// Not ready — keep it, pinning the resolved threadId once known.
|
|
1770
|
-
remaining.push(
|
|
1800
|
+
remaining.push(
|
|
1801
|
+
threadId ? { ...delivery, threadId, send: delivery.send } : delivery,
|
|
1802
|
+
);
|
|
1771
1803
|
}
|
|
1772
1804
|
}
|
|
1773
1805
|
pendingDeliveries.current = remaining;
|
|
1774
|
-
}, [openTabIds, activeThreadId]);
|
|
1806
|
+
}, [openTabIds, activeThreadId, bumpModelSelectionVersion]);
|
|
1775
1807
|
|
|
1776
1808
|
// Listen for chatRunning completion events
|
|
1777
1809
|
useEffect(() => {
|
package/src/client/agent-chat.ts
CHANGED
|
@@ -858,6 +858,17 @@ function normalizeAgentChatRequestMode(
|
|
|
858
858
|
return value === "act" || value === "plan" ? value : undefined;
|
|
859
859
|
}
|
|
860
860
|
|
|
861
|
+
/**
|
|
862
|
+
* Composers submit `engine: ""` whenever the engines list failed to load. An
|
|
863
|
+
* empty string is not nullish, so it survives `??` yet reads as falsy — one
|
|
864
|
+
* value meaning both "specified" and "absent". Absent is decided here, once.
|
|
865
|
+
*/
|
|
866
|
+
function nonEmptyString(value: unknown): string | undefined {
|
|
867
|
+
if (typeof value !== "string") return undefined;
|
|
868
|
+
const trimmed = value.trim();
|
|
869
|
+
return trimmed ? trimmed : undefined;
|
|
870
|
+
}
|
|
871
|
+
|
|
861
872
|
/** A normalized `agentNative.submitChat` payload — decode via {@link parseSubmitChatMessage}. */
|
|
862
873
|
export interface ParsedSubmitChat {
|
|
863
874
|
/** Visible prompt text (non-empty). */
|
|
@@ -867,6 +878,12 @@ export interface ParsedSubmitChat {
|
|
|
867
878
|
submit: boolean;
|
|
868
879
|
openSidebar?: boolean;
|
|
869
880
|
model?: string;
|
|
881
|
+
/**
|
|
882
|
+
* Engine paired with `model`. The receiver cannot re-derive it for an id the
|
|
883
|
+
* catalog omits, and a model sent without one is normalized to the resolved
|
|
884
|
+
* engine's default server-side.
|
|
885
|
+
*/
|
|
886
|
+
engine?: string;
|
|
870
887
|
/** Raw effort hint; the receiver validates it against the model. */
|
|
871
888
|
effort?: unknown;
|
|
872
889
|
newTab?: boolean;
|
|
@@ -907,7 +924,8 @@ export function parseSubmitChatMessage(
|
|
|
907
924
|
submit: raw.submit !== false,
|
|
908
925
|
openSidebar:
|
|
909
926
|
typeof raw.openSidebar === "boolean" ? raw.openSidebar : undefined,
|
|
910
|
-
model:
|
|
927
|
+
model: nonEmptyString(raw.model),
|
|
928
|
+
engine: nonEmptyString(raw.engine),
|
|
911
929
|
effort: raw.effort,
|
|
912
930
|
newTab: typeof raw.newTab === "boolean" ? raw.newTab : undefined,
|
|
913
931
|
background:
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
import { ConnectBuilderCard } from "../ConnectBuilderCard.js";
|
|
42
42
|
import { useT } from "../i18n.js";
|
|
43
43
|
import { McpAppRenderer } from "../mcp-apps/McpAppRenderer.js";
|
|
44
|
+
import { findMcpIntegrationForToolName } from "../resources/mcp-integration-catalog.js";
|
|
44
45
|
import type { AgentCallProgress, ContentPart } from "../sse-event-processor.js";
|
|
45
46
|
import {
|
|
46
47
|
BashCell,
|
|
@@ -279,7 +280,31 @@ type ToolIconComponent = React.ComponentType<{
|
|
|
279
280
|
size?: number | string;
|
|
280
281
|
}>;
|
|
281
282
|
|
|
283
|
+
const brandIcons = new Map<string, ToolIconComponent>();
|
|
284
|
+
|
|
285
|
+
function brandToolIcon(logoUrl: string, name: string): ToolIconComponent {
|
|
286
|
+
const cached = brandIcons.get(logoUrl);
|
|
287
|
+
if (cached) return cached;
|
|
288
|
+
const Icon: ToolIconComponent = ({ className, size }) => (
|
|
289
|
+
<img
|
|
290
|
+
src={logoUrl}
|
|
291
|
+
alt=""
|
|
292
|
+
aria-hidden
|
|
293
|
+
width={size}
|
|
294
|
+
height={size}
|
|
295
|
+
title={name}
|
|
296
|
+
className={cn("rounded-[3px] object-contain", className)}
|
|
297
|
+
/>
|
|
298
|
+
);
|
|
299
|
+
brandIcons.set(logoUrl, Icon);
|
|
300
|
+
return Icon;
|
|
301
|
+
}
|
|
302
|
+
|
|
282
303
|
function resolveToolIcon(toolName: string): ToolIconComponent {
|
|
304
|
+
const integration = findMcpIntegrationForToolName(toolName);
|
|
305
|
+
if (integration) {
|
|
306
|
+
return brandToolIcon(integration.logoUrl, integration.name);
|
|
307
|
+
}
|
|
283
308
|
const name = toolName.toLowerCase();
|
|
284
309
|
if (name.includes("slack")) return IconBrandSlack;
|
|
285
310
|
if (
|
|
@@ -803,6 +803,29 @@ export function findMcpIntegrationForText(
|
|
|
803
803
|
return null;
|
|
804
804
|
}
|
|
805
805
|
|
|
806
|
+
/**
|
|
807
|
+
* Matches the server segment of an `mcp__<server>__<tool>` name, not prose, so
|
|
808
|
+
* ambiguous brand words ("box", "monday", "linear") are safe here in a way they
|
|
809
|
+
* are not in `findMcpIntegrationForText`.
|
|
810
|
+
*/
|
|
811
|
+
export function findMcpIntegrationForToolName(
|
|
812
|
+
toolName: string,
|
|
813
|
+
integrations: readonly DefaultMcpIntegration[] = DEFAULT_MCP_INTEGRATIONS,
|
|
814
|
+
): DefaultMcpIntegration | null {
|
|
815
|
+
const server = /^mcp__(.+?)__/.exec(toolName.toLowerCase())?.[1];
|
|
816
|
+
if (!server) return null;
|
|
817
|
+
return (
|
|
818
|
+
integrations.find((integration) =>
|
|
819
|
+
[
|
|
820
|
+
integration.id,
|
|
821
|
+
integration.provider,
|
|
822
|
+
...(integration.brandAliases ?? []),
|
|
823
|
+
...(integration.aliases ?? []),
|
|
824
|
+
].some((alias) => textContainsTerm(server, alias)),
|
|
825
|
+
) ?? null
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
|
|
806
829
|
export function isMcpConnectionFailureText(text: string): boolean {
|
|
807
830
|
return /\b(?:can(?:not|'t|’t)|could(?: not|n't|n’t)|unable|failed|don't have access|don’t have access|not connected|not able)\b[\s\S]{0,80}\b(?:read|access|open|see|fetch|connect)\b/i.test(
|
|
808
831
|
text,
|