@agent-native/core 0.95.1 → 0.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +25 -0
  3. package/corpus/core/package.json +3 -1
  4. package/corpus/core/src/a2a/client.ts +11 -1
  5. package/corpus/core/src/agent/production-agent.ts +87 -27
  6. package/corpus/core/src/agent/thread-data-builder.ts +7 -5
  7. package/corpus/core/src/chat-threads/store.ts +20 -1
  8. package/corpus/core/src/cli/code-agent-executor.ts +1 -0
  9. package/corpus/core/src/cli/code-agent-runs.ts +14 -0
  10. package/corpus/core/src/client/AgentPanel.tsx +32 -25
  11. package/corpus/core/src/client/AssistantChat.tsx +36 -20
  12. package/corpus/core/src/client/MultiTabAssistantChat.tsx +141 -90
  13. package/corpus/core/src/client/chat/ChatHistoryList.tsx +404 -0
  14. package/corpus/core/src/client/chat/index.ts +6 -0
  15. package/corpus/core/src/client/chat/tool-call-display.tsx +37 -3
  16. package/corpus/core/src/client/code-agent-chat-adapter.ts +72 -4
  17. package/corpus/core/src/client/composer/RealtimeVoiceMode.tsx +52 -11
  18. package/corpus/core/src/client/composer/TiptapComposer.tsx +157 -20
  19. package/corpus/core/src/client/composer/VoiceButton.tsx +28 -0
  20. package/corpus/core/src/client/composer/useRealtimeVoiceMode.tsx +5 -0
  21. package/corpus/core/src/client/conversation/code-agent-transcript.ts +4 -5
  22. package/corpus/core/src/client/guided-questions.tsx +2 -1
  23. package/corpus/core/src/client/index.ts +11 -0
  24. package/corpus/core/src/client/use-chat-threads.ts +10 -1
  25. package/corpus/core/src/client/voice-provider-status.ts +44 -0
  26. package/corpus/core/src/code-agents/transcript-normalizer.ts +76 -0
  27. package/corpus/core/src/deploy/build.ts +12 -11
  28. package/corpus/core/src/integrations/a2a-continuation-processor.ts +35 -5
  29. package/corpus/core/src/integrations/adapters/slack.ts +74 -21
  30. package/corpus/core/src/integrations/webhook-handler.ts +14 -0
  31. package/corpus/core/src/localization/default-messages.ts +6 -0
  32. package/corpus/core/src/observability/hosted-model-experiment.ts +118 -0
  33. package/corpus/core/src/observability/routes.ts +17 -0
  34. package/corpus/core/src/observability/store.ts +4 -1
  35. package/corpus/core/src/observability/traces.ts +41 -2
  36. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +19 -1
  37. package/corpus/core/src/scripts/call-agent.ts +53 -3
  38. package/corpus/core/src/scripts/chat/search-chats.ts +11 -8
  39. package/corpus/core/src/secrets/register-framework-secrets.ts +1 -1
  40. package/corpus/core/src/server/agent-chat-plugin.ts +41 -6
  41. package/corpus/core/src/server/core-routes-plugin.ts +15 -0
  42. package/corpus/core/src/server/realtime-voice.ts +71 -24
  43. package/corpus/core/src/server/voice-providers-status.ts +4 -3
  44. package/corpus/core/src/shared/cache-control.ts +2 -8
  45. package/corpus/core/src/styles/agent-native.css +1 -0
  46. package/corpus/core/src/styles/chat-history-list.css +313 -0
  47. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
  49. package/corpus/core/src/vite/client.ts +1 -0
  50. package/corpus/templates/analytics/AGENTS.md +7 -0
  51. package/corpus/templates/analytics/actions/bigquery.ts +26 -2
  52. package/corpus/templates/analytics/actions/data-source-status.ts +18 -0
  53. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +5 -4
  54. package/corpus/templates/analytics/app/global.css +10 -0
  55. package/corpus/templates/analytics/app/pages/Ask.tsx +25 -2
  56. package/corpus/templates/analytics/changelog/2026-07-10-analytics-answers-simple-time-bounded-metrics-without-unnece.md +6 -0
  57. package/corpus/templates/analytics/changelog/2026-07-10-analytics-chat-reliably-uses-connected-data-sources-across-s.md +6 -0
  58. package/corpus/templates/analytics/changelog/2026-07-10-chat-threads-now-stay-clear-of-top-actions-and-fade-smoothly.md +6 -0
  59. package/corpus/templates/analytics/changelog/2026-07-10-context-chips-can-now-be-selected-and-removed-one-at-a-time-.md +6 -0
  60. package/corpus/templates/analytics/changelog/2026-07-10-the-ask-composer-no-longer-carries-dashboard-or-panel-contex.md +6 -0
  61. package/corpus/templates/analytics/changelog/2026-07-10-the-ask-section-stays-highlighted-while-its-chat-history-is-.md +6 -0
  62. package/corpus/templates/analytics/server/lib/bigquery.ts +90 -14
  63. package/corpus/templates/analytics/server/plugins/agent-chat.ts +82 -7
  64. package/dist/a2a/client.d.ts +7 -0
  65. package/dist/a2a/client.d.ts.map +1 -1
  66. package/dist/a2a/client.js +2 -1
  67. package/dist/a2a/client.js.map +1 -1
  68. package/dist/agent/production-agent.d.ts +6 -0
  69. package/dist/agent/production-agent.d.ts.map +1 -1
  70. package/dist/agent/production-agent.js +63 -26
  71. package/dist/agent/production-agent.js.map +1 -1
  72. package/dist/agent/thread-data-builder.d.ts +1 -0
  73. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  74. package/dist/agent/thread-data-builder.js +6 -5
  75. package/dist/agent/thread-data-builder.js.map +1 -1
  76. package/dist/chat-threads/store.d.ts +10 -0
  77. package/dist/chat-threads/store.d.ts.map +1 -1
  78. package/dist/chat-threads/store.js +6 -0
  79. package/dist/chat-threads/store.js.map +1 -1
  80. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  81. package/dist/cli/code-agent-executor.js +1 -0
  82. package/dist/cli/code-agent-executor.js.map +1 -1
  83. package/dist/cli/code-agent-runs.d.ts +12 -0
  84. package/dist/cli/code-agent-runs.d.ts.map +1 -1
  85. package/dist/cli/code-agent-runs.js +1 -0
  86. package/dist/cli/code-agent-runs.js.map +1 -1
  87. package/dist/client/AgentPanel.d.ts.map +1 -1
  88. package/dist/client/AgentPanel.js +3 -3
  89. package/dist/client/AgentPanel.js.map +1 -1
  90. package/dist/client/AssistantChat.d.ts +11 -0
  91. package/dist/client/AssistantChat.d.ts.map +1 -1
  92. package/dist/client/AssistantChat.js +7 -4
  93. package/dist/client/AssistantChat.js.map +1 -1
  94. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  95. package/dist/client/MultiTabAssistantChat.js +83 -10
  96. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  97. package/dist/client/chat/ChatHistoryList.d.ts +88 -0
  98. package/dist/client/chat/ChatHistoryList.d.ts.map +1 -0
  99. package/dist/client/chat/ChatHistoryList.js +83 -0
  100. package/dist/client/chat/ChatHistoryList.js.map +1 -0
  101. package/dist/client/chat/index.d.ts +1 -0
  102. package/dist/client/chat/index.d.ts.map +1 -1
  103. package/dist/client/chat/index.js +1 -0
  104. package/dist/client/chat/index.js.map +1 -1
  105. package/dist/client/chat/tool-call-display.d.ts +13 -1
  106. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  107. package/dist/client/chat/tool-call-display.js +10 -2
  108. package/dist/client/chat/tool-call-display.js.map +1 -1
  109. package/dist/client/code-agent-chat-adapter.d.ts +17 -1
  110. package/dist/client/code-agent-chat-adapter.d.ts.map +1 -1
  111. package/dist/client/code-agent-chat-adapter.js +47 -3
  112. package/dist/client/code-agent-chat-adapter.js.map +1 -1
  113. package/dist/client/composer/RealtimeVoiceMode.d.ts +11 -1
  114. package/dist/client/composer/RealtimeVoiceMode.d.ts.map +1 -1
  115. package/dist/client/composer/RealtimeVoiceMode.js +5 -3
  116. package/dist/client/composer/RealtimeVoiceMode.js.map +1 -1
  117. package/dist/client/composer/TiptapComposer.d.ts +25 -2
  118. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  119. package/dist/client/composer/TiptapComposer.js +96 -5
  120. package/dist/client/composer/TiptapComposer.js.map +1 -1
  121. package/dist/client/composer/VoiceButton.d.ts.map +1 -1
  122. package/dist/client/composer/VoiceButton.js +21 -1
  123. package/dist/client/composer/VoiceButton.js.map +1 -1
  124. package/dist/client/composer/useRealtimeVoiceMode.d.ts.map +1 -1
  125. package/dist/client/composer/useRealtimeVoiceMode.js +5 -0
  126. package/dist/client/composer/useRealtimeVoiceMode.js.map +1 -1
  127. package/dist/client/conversation/code-agent-transcript.d.ts +2 -0
  128. package/dist/client/conversation/code-agent-transcript.d.ts.map +1 -1
  129. package/dist/client/conversation/code-agent-transcript.js +3 -5
  130. package/dist/client/conversation/code-agent-transcript.js.map +1 -1
  131. package/dist/client/guided-questions.d.ts.map +1 -1
  132. package/dist/client/guided-questions.js +2 -1
  133. package/dist/client/guided-questions.js.map +1 -1
  134. package/dist/client/index.d.ts +3 -1
  135. package/dist/client/index.d.ts.map +1 -1
  136. package/dist/client/index.js +3 -1
  137. package/dist/client/index.js.map +1 -1
  138. package/dist/client/use-chat-threads.d.ts.map +1 -1
  139. package/dist/client/use-chat-threads.js +9 -1
  140. package/dist/client/use-chat-threads.js.map +1 -1
  141. package/dist/client/voice-provider-status.d.ts +10 -0
  142. package/dist/client/voice-provider-status.d.ts.map +1 -0
  143. package/dist/client/voice-provider-status.js +32 -0
  144. package/dist/client/voice-provider-status.js.map +1 -0
  145. package/dist/code-agents/transcript-normalizer.d.ts +29 -0
  146. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
  147. package/dist/code-agents/transcript-normalizer.js +55 -0
  148. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  149. package/dist/collab/routes.d.ts +1 -1
  150. package/dist/collab/struct-routes.d.ts +1 -1
  151. package/dist/deploy/build.d.ts.map +1 -1
  152. package/dist/deploy/build.js +13 -11
  153. package/dist/deploy/build.js.map +1 -1
  154. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  155. package/dist/integrations/a2a-continuation-processor.js +25 -5
  156. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  157. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  158. package/dist/integrations/adapters/slack.js +55 -21
  159. package/dist/integrations/adapters/slack.js.map +1 -1
  160. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  161. package/dist/integrations/webhook-handler.js +11 -0
  162. package/dist/integrations/webhook-handler.js.map +1 -1
  163. package/dist/localization/default-messages.d.ts +5 -0
  164. package/dist/localization/default-messages.d.ts.map +1 -1
  165. package/dist/localization/default-messages.js +5 -0
  166. package/dist/localization/default-messages.js.map +1 -1
  167. package/dist/notifications/routes.d.ts +2 -2
  168. package/dist/observability/hosted-model-experiment.d.ts +39 -0
  169. package/dist/observability/hosted-model-experiment.d.ts.map +1 -0
  170. package/dist/observability/hosted-model-experiment.js +90 -0
  171. package/dist/observability/hosted-model-experiment.js.map +1 -0
  172. package/dist/observability/routes.d.ts +1 -1
  173. package/dist/observability/routes.d.ts.map +1 -1
  174. package/dist/observability/routes.js +16 -0
  175. package/dist/observability/routes.js.map +1 -1
  176. package/dist/observability/store.d.ts.map +1 -1
  177. package/dist/observability/store.js +4 -1
  178. package/dist/observability/store.js.map +1 -1
  179. package/dist/observability/traces.d.ts +6 -0
  180. package/dist/observability/traces.d.ts.map +1 -1
  181. package/dist/observability/traces.js +28 -2
  182. package/dist/observability/traces.js.map +1 -1
  183. package/dist/resources/handlers.d.ts +1 -1
  184. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  185. package/dist/scripts/agent-engines/list-agent-engines.js +19 -1
  186. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  187. package/dist/scripts/call-agent.d.ts.map +1 -1
  188. package/dist/scripts/call-agent.js +43 -3
  189. package/dist/scripts/call-agent.js.map +1 -1
  190. package/dist/scripts/chat/search-chats.d.ts +1 -1
  191. package/dist/scripts/chat/search-chats.d.ts.map +1 -1
  192. package/dist/scripts/chat/search-chats.js +11 -8
  193. package/dist/scripts/chat/search-chats.js.map +1 -1
  194. package/dist/secrets/register-framework-secrets.js +1 -1
  195. package/dist/secrets/register-framework-secrets.js.map +1 -1
  196. package/dist/server/agent-chat-plugin.d.ts +11 -0
  197. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  198. package/dist/server/agent-chat-plugin.js +27 -6
  199. package/dist/server/agent-chat-plugin.js.map +1 -1
  200. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  201. package/dist/server/core-routes-plugin.d.ts +3 -0
  202. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  203. package/dist/server/core-routes-plugin.js +13 -0
  204. package/dist/server/core-routes-plugin.js.map +1 -1
  205. package/dist/server/realtime-voice.d.ts.map +1 -1
  206. package/dist/server/realtime-voice.js +55 -21
  207. package/dist/server/realtime-voice.js.map +1 -1
  208. package/dist/server/voice-providers-status.d.ts.map +1 -1
  209. package/dist/server/voice-providers-status.js +2 -3
  210. package/dist/server/voice-providers-status.js.map +1 -1
  211. package/dist/shared/cache-control.d.ts +4 -6
  212. package/dist/shared/cache-control.d.ts.map +1 -1
  213. package/dist/shared/cache-control.js +2 -7
  214. package/dist/shared/cache-control.js.map +1 -1
  215. package/dist/styles/agent-native.css +1 -0
  216. package/dist/styles/chat-history-list.css +313 -0
  217. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  218. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
  219. package/dist/vite/client.d.ts.map +1 -1
  220. package/dist/vite/client.js +1 -0
  221. package/dist/vite/client.js.map +1 -1
  222. package/package.json +3 -1
  223. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +20 -0
  224. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +8 -4
package/corpus/README.md CHANGED
@@ -27,5 +27,5 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
27
27
 
28
28
  ## Generated Counts
29
29
 
30
- - core files: 2167
31
- - template files: 5375
30
+ - core files: 2171
31
+ - template files: 5381
@@ -1,5 +1,30 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.96.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 270d85f: Use Builder Connect and metered Builder credits automatically for realtime voice, with an OpenAI API key as the secondary fallback.
8
+
9
+ ### Patch Changes
10
+
11
+ - 270d85f: Run a sticky 80/20 Sonnet 5 versus GPT-5.6 Luna default-model experiment across first-party hosted templates while preserving explicit model choices.
12
+ - 270d85f: Add a shared, presentational `ChatHistoryList` component (`@agent-native/core/client`) for rendering chat/run history lists with optional grouped sections, active-item highlight, search box, loading/empty/error states, and optional pin/rename/delete row actions. Core's `HistoryPopover` now renders through it instead of bespoke row markup, with no behavior change.
13
+ - 270d85f: Allow app final-response guards to grant models additional bounded corrective attempts before emitting a fallback.
14
+ - 270d85f: Improve Slack agent progress and diagnostics, and continue long-running delegated tasks reliably.
15
+ - 270d85f: Keep page-level chat actions clear of conversation content and expose scroll state so hosts can fade older messages beneath header controls.
16
+ - 270d85f: Reduce the public SSR edge-cache freshness window from one hour to ten minutes while retaining long stale-while-revalidate coverage.
17
+ - 270d85f: Attach a pending approval key to the Agent-Native Code tool-call transcript item so the shared inline `ApprovalAffordance` (Approve/Deny) can render directly under the paused bash call, instead of only through a separate host banner. `ApprovalContext` gains optional `onDeny` and `onAlwaysAllow` hooks (both additive; existing consumers keep today's Approve-only, local-Deny behavior when they don't supply them), and `AssistantChat` accepts an optional `approvalActions` prop to wire them. `createCodeAgentChatAdapter` now resolves an approved Code tool call through `controller.control("approve")` instead of treating the approval message as a new prompt.
18
+ - 270d85f: Export `./styles/agent-conversation.css` from the package so consumers outside the monorepo (like the desktop app) can import it by package specifier instead of a fragile source-relative path.
19
+ - 270d85f: Export `./styles/chat-history-list.css` from the package so non-Tailwind hosts (like `@agent-native/code-agents-ui`) can import the shared `ChatHistoryList` stylesheet by package specifier instead of a fragile source-relative path.
20
+ - 270d85f: Export the reasoning hook from browser-only SSR stubs so Cloudflare template builds remain deployable.
21
+ - 270d85f: Archived chat threads are now excluded from thread lists and search by default, so archiving a thread actually removes it from the sidebar/history and the `chat-history` agent tool. Pass `includeArchived: true` (store options, `chat-history` search action, or `search-chats` script) to see archived threads explicitly.
22
+ - 270d85f: Use one primary composer action while a response is running: show stop for an empty draft, restore send when the user types, and keep the microphone immediately to its left.
23
+ - 270d85f: Preserve template and use-case context in Builder waitlist form submissions and lifecycle events.
24
+ - 270d85f: Select composer context chips with the first Backspace at the start of a prompt, then remove the selected chip with the next Backspace.
25
+ - 270d85f: Show compact GPT-5.6 model and reasoning names in the collapsed chat composer control.
26
+ - 270d85f: Add a structured `signal: "credential-gap"` marker to code-agent transcript events and export a shared `isCredentialGapCodeAgentEvent` helper so history builders (`thread-data-builder.ts`, `code-agent-transcript.ts`) detect the "no LLM provider key" condition from the executor's structured field instead of regex-matching the hint text. The regex remains only as a fallback for already-persisted transcripts that predate the field.
27
+
3
28
  ## 0.95.1
4
29
 
5
30
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.95.1",
3
+ "version": "0.96.0",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -219,6 +219,8 @@
219
219
  "./terminal/server": "./dist/terminal/index.js",
220
220
  "./tailwind": "./dist/tailwind.preset.js",
221
221
  "./styles/agent-native.css": "./dist/styles/agent-native.css",
222
+ "./styles/agent-conversation.css": "./dist/styles/agent-conversation.css",
223
+ "./styles/chat-history-list.css": "./dist/styles/chat-history-list.css",
222
224
  "./agent/engine": "./dist/agent/engine/index.js",
223
225
  "./agent/harness": "./dist/agent/harness/index.js",
224
226
  "./eval": "./dist/eval/index.js",
@@ -550,6 +550,13 @@ export async function callAgent(
550
550
  timeoutMs?: number;
551
551
  /** Poll interval for async calls. Primarily useful for tests/retries. */
552
552
  pollIntervalMs?: number;
553
+ /**
554
+ * Return receiver-verified artifact text from the last polled task when
555
+ * the call times out. Defaults to true for backwards compatibility.
556
+ * Callers that can continue polling the remote task separately should set
557
+ * this to false so the A2ATaskTimeoutError (and its taskId) is preserved.
558
+ */
559
+ returnRecoverableArtifactsOnTimeout?: boolean;
553
560
  /**
554
561
  * Called with each successfully polled task while an async call is still
555
562
  * in flight (see `A2AClient.sendAndWait`). Fires once per real poll
@@ -614,7 +621,10 @@ export async function callAgent(
614
621
 
615
622
  return "";
616
623
  } catch (err) {
617
- if (err instanceof A2ATaskTimeoutError) {
624
+ if (
625
+ opts?.returnRecoverableArtifactsOnTimeout !== false &&
626
+ err instanceof A2ATaskTimeoutError
627
+ ) {
618
628
  const recoverableText = extractRecoverableArtifactText(err.lastTask);
619
629
  if (recoverableText) return recoverableText;
620
630
  }
@@ -1762,6 +1762,12 @@ export type AgentLoopFinalResponseGuardResult =
1762
1762
  | {
1763
1763
  retryMessage: string;
1764
1764
  fallbackMessage?: string;
1765
+ /**
1766
+ * Number of rejected text-only answers the model may correct before the
1767
+ * fallback is emitted. Defaults to one and is capped to keep a broken
1768
+ * guard/model combination from looping indefinitely.
1769
+ */
1770
+ maxRetries?: number;
1765
1771
  };
1766
1772
 
1767
1773
  export type AgentLoopFinalResponseGuard = (
@@ -3641,7 +3647,11 @@ export async function runAgentLoop(opts: {
3641
3647
  typeof guard === "string" ? guard : guard.retryMessage;
3642
3648
  const fallbackMessage =
3643
3649
  typeof guard === "string" ? guard : guard.fallbackMessage;
3644
- if (finalGuardRetries < 1) {
3650
+ const maxGuardRetries =
3651
+ typeof guard === "string"
3652
+ ? 1
3653
+ : Math.max(0, Math.min(3, Math.trunc(guard.maxRetries ?? 1)));
3654
+ if (finalGuardRetries < maxGuardRetries) {
3645
3655
  finalGuardRetries += 1;
3646
3656
  send({ type: "clear" });
3647
3657
  messages.push({
@@ -6160,21 +6170,82 @@ export function createProductionAgentHandler(
6160
6170
  // DIAGNOSTIC-ONLY: bracket stored-model resolution (getStoredModelForEngine
6161
6171
  // settings read).
6162
6172
  workerStep("model_start");
6173
+ const storedModel =
6174
+ requestModel == null && configuredModel == null
6175
+ ? await getStoredModelForEngine(engine, { appId: options.appId })
6176
+ : undefined;
6163
6177
  const modelCandidate =
6164
- requestModel ??
6165
- configuredModel ??
6166
- (await getStoredModelForEngine(engine, { appId: options.appId })) ??
6167
- engine.defaultModel;
6178
+ requestModel ?? configuredModel ?? storedModel ?? engine.defaultModel;
6168
6179
  // DIAGNOSTIC-ONLY: stored-model resolution finished.
6169
6180
  workerStep("model_done");
6170
6181
  const model = normalizeModelForEngine(engine, modelCandidate);
6182
+ let effectiveModel = model;
6183
+ let modelSelectionSource:
6184
+ | "request"
6185
+ | "configured"
6186
+ | "stored"
6187
+ | "default"
6188
+ | "experiment" =
6189
+ requestModel != null
6190
+ ? "request"
6191
+ : configuredModel != null
6192
+ ? "configured"
6193
+ : storedModel != null
6194
+ ? "stored"
6195
+ : "default";
6196
+ let experimentAssignments: Array<{
6197
+ experimentId: string;
6198
+ variantId: string;
6199
+ }> = [];
6200
+
6201
+ // Database-backed experiments remain available to app operators. A
6202
+ // platform-wide hosted rollout is layered in only when no explicit or
6203
+ // persisted model selection exists, and only on the cross-provider Builder
6204
+ // engine. This preserves the model picker's user intent.
6205
+ try {
6206
+ if (ownerEmail) {
6207
+ const { resolveActiveExperimentConfig } =
6208
+ await import("../observability/experiments.js");
6209
+ const expConfig = await resolveActiveExperimentConfig(ownerEmail);
6210
+ if (expConfig) {
6211
+ experimentAssignments = [...expConfig.assignments];
6212
+ if (typeof expConfig.configs.model === "string") {
6213
+ effectiveModel = normalizeModelForEngine(
6214
+ engine,
6215
+ expConfig.configs.model,
6216
+ );
6217
+ modelSelectionSource = "experiment";
6218
+ }
6219
+ }
6220
+
6221
+ if (modelSelectionSource === "default") {
6222
+ const { resolveHostedDefaultModelExperiment } =
6223
+ await import("../observability/hosted-model-experiment.js");
6224
+ const hostedExperiment = resolveHostedDefaultModelExperiment({
6225
+ userId: ownerEmail,
6226
+ engineName: engine.name,
6227
+ isDefaultModelSelection: true,
6228
+ supportedModels: engine.supportedModels,
6229
+ });
6230
+ if (hostedExperiment) {
6231
+ effectiveModel = hostedExperiment.model;
6232
+ experimentAssignments.push(hostedExperiment.assignment);
6233
+ modelSelectionSource = "experiment";
6234
+ }
6235
+ }
6236
+ }
6237
+ } catch {
6238
+ // Experiments are best-effort. Model resolution must keep working if the
6239
+ // observability tables are unavailable during startup or migration.
6240
+ }
6241
+
6171
6242
  const reasoningEffort = resolveAgentRequestReasoningEffort({
6172
- model,
6243
+ model: effectiveModel,
6173
6244
  requestEffort,
6174
6245
  configuredEffort: options.reasoningEffort,
6175
6246
  });
6176
6247
 
6177
- options.onEngineResolved?.(engine, model);
6248
+ options.onEngineResolved?.(engine, effectiveModel);
6178
6249
 
6179
6250
  // One-line per-turn resolution log so it's obvious in dev which engine
6180
6251
  // is actually handling the request. `requestEngine` is what the client
@@ -6182,7 +6253,7 @@ export function createProductionAgentHandler(
6182
6253
  // Divergence between them is the usual cause of "status says builder but
6183
6254
  // no [builder-engine] log lines appear" confusion.
6184
6255
  console.log(
6185
- `[agent-chat] resolved engine=${engine.name} model=${model} requestEngine=${requestEngine ?? "(none)"}`,
6256
+ `[agent-chat] resolved engine=${engine.name} model=${effectiveModel} requestEngine=${requestEngine ?? "(none)"} modelSource=${modelSelectionSource}`,
6186
6257
  );
6187
6258
 
6188
6259
  if (
@@ -7487,25 +7558,6 @@ export function createProductionAgentHandler(
7487
7558
  }
7488
7559
  }
7489
7560
 
7490
- // Apply experiment variant overrides (A/B testing)
7491
- let effectiveModel = model;
7492
- try {
7493
- const { resolveActiveExperimentConfig } =
7494
- await import("../observability/experiments.js");
7495
- if (!ownerEmail) {
7496
- // Without an authenticated owner we can't resolve user-scoped experiments.
7497
- throw new Error("no authenticated user");
7498
- }
7499
- const expConfig = await resolveActiveExperimentConfig(ownerEmail);
7500
- if (expConfig) {
7501
- if (typeof expConfig.configs.model === "string") {
7502
- effectiveModel = expConfig.configs.model;
7503
- }
7504
- }
7505
- } catch {
7506
- // Experiments module unavailable — use default model
7507
- }
7508
-
7509
7561
  // TODO(processor-seam): thread `processors` from ProductionAgentOptions
7510
7562
  // through to runAgentLoop here once the handler exposes a way to
7511
7563
  // configure them (e.g. a `processors` field on ProductionAgentOptions
@@ -7592,6 +7644,14 @@ export function createProductionAgentHandler(
7592
7644
  threadId: threadId ?? null,
7593
7645
  userId: ownerEmail,
7594
7646
  config: obsConfig,
7647
+ metadata: {
7648
+ modelSelectionSource,
7649
+ ...(experimentAssignments.length > 0
7650
+ ? { experimentAssignments }
7651
+ : {}),
7652
+ },
7653
+ experimentAssignments,
7654
+ modelSelectionSource,
7595
7655
  classifyError: () => {
7596
7656
  if (
7597
7657
  agentLoopOpts.signal.aborted &&
@@ -1,5 +1,6 @@
1
1
  import type { ActionChatUIConfig } from "../action-ui.js";
2
2
  import {
3
+ isCredentialGapCodeAgentEvent,
3
4
  normalizeCodeAgentTranscript,
4
5
  type CodeAgentTranscriptEvent as CoreCodeAgentTranscriptEvent,
5
6
  type NormalizedCodeAgentStatusEvent,
@@ -664,6 +665,7 @@ export interface CodeAgentThreadTranscriptEvent {
664
665
  metadata?: Record<string, unknown>;
665
666
  artifactPath?: string;
666
667
  artifactUrl?: string;
668
+ signal?: CoreCodeAgentTranscriptEvent["signal"];
667
669
  }
668
670
 
669
671
  export interface BuildRepositoryFromCodeAgentTranscriptOptions {
@@ -1094,6 +1096,7 @@ function toCoreCodeAgentTranscriptEvent(
1094
1096
  ...(event.artifactPath ? { artifactPath: event.artifactPath } : {}),
1095
1097
  ...(event.artifactUrl ? { artifactUrl: event.artifactUrl } : {}),
1096
1098
  },
1099
+ ...(event.signal ? { signal: event.signal } : {}),
1097
1100
  };
1098
1101
  }
1099
1102
 
@@ -1137,6 +1140,9 @@ function toolContentPartForCodeAgentTranscriptItem(
1137
1140
  ? { result: previewCodeAgentTranscriptValue(item.result) ?? "" }
1138
1141
  : {}),
1139
1142
  ...(item.structuredMeta ? { structuredMeta: item.structuredMeta } : {}),
1143
+ ...(item.pendingApprovalKey
1144
+ ? { approval: { approvalKey: item.pendingApprovalKey } }
1145
+ : {}),
1140
1146
  };
1141
1147
  }
1142
1148
 
@@ -1144,7 +1150,7 @@ function statusTextForCodeAgentTranscriptItem(
1144
1150
  item: NormalizedCodeAgentStatusEvent,
1145
1151
  options: BuildRepositoryFromCodeAgentTranscriptOptions,
1146
1152
  ): string | null {
1147
- if (options.hideCredentialMessages && isCredentialCodeAgentText(item.text)) {
1153
+ if (options.hideCredentialMessages && isCredentialGapCodeAgentEvent(item)) {
1148
1154
  return null;
1149
1155
  }
1150
1156
  if (item.statusKind === "artifact") {
@@ -1219,10 +1225,6 @@ function stringRecordValue(
1219
1225
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
1220
1226
  }
1221
1227
 
1222
- function isCredentialCodeAgentText(value: string): boolean {
1223
- return /No LLM provider key was found|Missing credentials/i.test(value);
1224
- }
1225
-
1226
1228
  export function upsertUserMessage(repo: any, userMsg: UserMessage): any {
1227
1229
  const nextRepo = normalizeThreadRepository(repo);
1228
1230
 
@@ -639,6 +639,14 @@ export interface ListThreadsOptions {
639
639
  /** When true, returns only threads with no scope (general chats). */
640
640
  unscopedOnly?: boolean;
641
641
  orgId?: string | null;
642
+ /**
643
+ * Include archived threads in the results. Defaults to false: archived
644
+ * threads (`archived_at` set via `setThreadArchived`) are hidden from the
645
+ * ordinary chat list/search so archiving actually removes a thread from
646
+ * view. Pass true for surfaces that explicitly need to see archived chats
647
+ * (e.g. an "Archived" filter or restoring one via `setThreadArchived`).
648
+ */
649
+ includeArchived?: boolean;
642
650
  }
643
651
 
644
652
  function chatThreadAccessSql(
@@ -686,6 +694,9 @@ export async function listThreads(
686
694
  );
687
695
  const filters: string[] = [access.sql, `message_count > 0`];
688
696
  const args: (string | number)[] = [...access.args];
697
+ if (!opts.includeArchived) {
698
+ filters.push(`archived_at IS NULL`);
699
+ }
689
700
  if (opts.scope) {
690
701
  filters.push(`scope_type = ? AND scope_id = ?`);
691
702
  args.push(opts.scope.type, opts.scope.id);
@@ -710,7 +721,12 @@ export async function searchThreads(
710
721
  ownerEmail: string,
711
722
  query: string,
712
723
  limit = 50,
713
- options: { scope?: { type: string; id: string }; orgId?: string | null } = {},
724
+ options: {
725
+ scope?: { type: string; id: string };
726
+ orgId?: string | null;
727
+ /** See `ListThreadsOptions.includeArchived` — defaults to false. */
728
+ includeArchived?: boolean;
729
+ } = {},
714
730
  ): Promise<ChatThreadSummary[]> {
715
731
  await ensureTable();
716
732
  const client = getDbExec();
@@ -728,6 +744,9 @@ export async function searchThreads(
728
744
  `(title LIKE ? ESCAPE '!' OR preview LIKE ? ESCAPE '!' OR thread_data LIKE ? ESCAPE '!')`,
729
745
  ];
730
746
  const args: (string | number)[] = [...access.args, pattern, pattern, pattern];
747
+ if (!options.includeArchived) {
748
+ filters.push(`archived_at IS NULL`);
749
+ }
731
750
  if (options.scope) {
732
751
  filters.push(`scope_type = ? AND scope_id = ?`);
733
752
  args.push(options.scope.type, options.scope.id);
@@ -215,6 +215,7 @@ export async function executeCodeAgentRun(
215
215
  runId: existing.id,
216
216
  kind: "status",
217
217
  message,
218
+ signal: "credential-gap",
218
219
  metadata: { status: "paused", phase: "missing-credentials" },
219
220
  });
220
221
  return updateCodeAgentRunRecord(existing.id, {
@@ -77,6 +77,17 @@ export type CodeAgentTranscriptEventKind =
77
77
  | "artifact"
78
78
  | "status";
79
79
 
80
+ /**
81
+ * Structured, machine-checkable marker for transcript events that need
82
+ * special handling in the UI beyond free-text matching. `"credential-gap"`
83
+ * marks the status event the executor appends when no LLM provider key (or
84
+ * Codex CLI login) is available; consumers should prefer this field over
85
+ * regex-matching `message` (see `isCredentialGapCodeAgentEvent` in
86
+ * `../code-agents/transcript-normalizer.js`). Optional so older, already
87
+ * persisted JSONL transcripts without the field keep parsing unchanged.
88
+ */
89
+ export type CodeAgentTranscriptEventSignal = "credential-gap";
90
+
80
91
  export interface CodeAgentTranscriptEvent {
81
92
  schemaVersion: 1;
82
93
  id: string;
@@ -85,6 +96,7 @@ export interface CodeAgentTranscriptEvent {
85
96
  message: string;
86
97
  createdAt: string;
87
98
  metadata?: Record<string, unknown>;
99
+ signal?: CodeAgentTranscriptEventSignal;
88
100
  }
89
101
 
90
102
  export interface CreateCodeAgentRunInput {
@@ -109,6 +121,7 @@ export interface AppendCodeAgentTranscriptEventInput {
109
121
  message: string;
110
122
  createdAt?: string;
111
123
  metadata?: Record<string, unknown>;
124
+ signal?: CodeAgentTranscriptEventSignal;
112
125
  }
113
126
 
114
127
  export interface QueueCodeAgentFollowUpInput {
@@ -294,6 +307,7 @@ export function appendCodeAgentTranscriptEvent(
294
307
  message: input.message,
295
308
  createdAt,
296
309
  metadata: input.metadata,
310
+ ...(input.signal ? { signal: input.signal } : {}),
297
311
  };
298
312
 
299
313
  fs.mkdirSync(codeAgentTranscriptsDir(), { recursive: true });
@@ -1458,32 +1458,39 @@ function AgentPanelInner({
1458
1458
  activeTab && (activeTabMessageCount > 0 || activeTab.status !== "idle");
1459
1459
 
1460
1460
  return (
1461
- <div className="pointer-events-none absolute inset-x-0 top-3 z-[60] flex justify-end px-3 sm:top-4 sm:px-4">
1462
- <div className="pointer-events-auto flex items-center gap-1">
1463
- {canShareActiveTab ? (
1464
- <ShareButton
1465
- resourceType="chat_thread"
1466
- resourceId={activeTab.id}
1467
- resourceTitle={activeTab.label || "Chat"}
1468
- shareUrl={getChatThreadShareUrl(activeTab.id)}
1469
- trigger="icon"
1470
- triggerClassName="h-8 w-8 border border-border bg-background/95 shadow-sm backdrop-blur hover:bg-accent"
1471
- />
1472
- ) : null}
1473
- <button
1474
- type="button"
1475
- data-agent-page-new-chat=""
1476
- aria-label={t("agentPanel.newChat")}
1477
- onClick={() => {
1478
- addTab();
1479
- }}
1480
- className="inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background/95 px-2.5 text-xs font-medium text-foreground shadow-sm backdrop-blur transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1481
- >
1482
- <IconPlus size={14} />
1483
- <span>{t("agentPanel.newChat")}</span>
1484
- </button>
1461
+ <>
1462
+ <div
1463
+ aria-hidden="true"
1464
+ data-agent-page-chat-fade=""
1465
+ className="pointer-events-none absolute inset-x-0 top-0 z-50 h-16 bg-gradient-to-b from-background via-background/90 to-transparent opacity-0 transition-opacity duration-150"
1466
+ />
1467
+ <div className="pointer-events-none absolute inset-x-0 top-3 z-[60] flex justify-end px-3 sm:top-4 sm:px-4">
1468
+ <div className="pointer-events-auto flex items-center gap-1">
1469
+ {canShareActiveTab ? (
1470
+ <ShareButton
1471
+ resourceType="chat_thread"
1472
+ resourceId={activeTab.id}
1473
+ resourceTitle={activeTab.label || "Chat"}
1474
+ shareUrl={getChatThreadShareUrl(activeTab.id)}
1475
+ trigger="icon"
1476
+ triggerClassName="h-8 w-8 border border-border bg-background/95 shadow-sm backdrop-blur hover:bg-accent"
1477
+ />
1478
+ ) : null}
1479
+ <button
1480
+ type="button"
1481
+ data-agent-page-new-chat=""
1482
+ aria-label={t("agentPanel.newChat")}
1483
+ onClick={() => {
1484
+ addTab();
1485
+ }}
1486
+ className="inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background/95 px-2.5 text-xs font-medium text-foreground shadow-sm backdrop-blur transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1487
+ >
1488
+ <IconPlus size={14} />
1489
+ <span>{t("agentPanel.newChat")}</span>
1490
+ </button>
1491
+ </div>
1485
1492
  </div>
1486
- </div>
1493
+ </>
1487
1494
  );
1488
1495
  },
1489
1496
  [getChatThreadShareUrl, t],
@@ -1629,6 +1629,17 @@ export interface AssistantChatProps {
1629
1629
  historyReloadKey?: string | number | null;
1630
1630
  /** Smooth the last assistant message while an external transcript is updating. */
1631
1631
  externalStreaming?: boolean;
1632
+ /**
1633
+ * Optional host hooks for the inline `needsApproval` affordance beyond the
1634
+ * built-in Approve. Additive: omit entirely to keep today's default
1635
+ * behavior (Deny is local-only, no "Always allow" button). Code sessions
1636
+ * pass these through to the same `host.controlRun` commands their
1637
+ * standalone approval banner already uses (see CodeAgentsApp).
1638
+ */
1639
+ approvalActions?: {
1640
+ onDeny?: (approvalKey: string) => void;
1641
+ onAlwaysAllow?: (approvalKey: string) => void;
1642
+ };
1632
1643
  }
1633
1644
 
1634
1645
  export const CHAT_STORAGE_PREFIX = "agent-chat:";
@@ -1852,6 +1863,7 @@ const AssistantChatInner = forwardRef<
1852
1863
  onSlashCommand,
1853
1864
  execMode,
1854
1865
  onExecModeChange,
1866
+ approvalActions,
1855
1867
  planModeDisabled,
1856
1868
  planModeDisabledReason,
1857
1869
  selectedModel,
@@ -4640,8 +4652,12 @@ const AssistantChatInner = forwardRef<
4640
4652
  ),
4641
4653
  } as Parameters<typeof threadRuntime.append>[0]);
4642
4654
  },
4655
+ ...(approvalActions?.onDeny ? { onDeny: approvalActions.onDeny } : {}),
4656
+ ...(approvalActions?.onAlwaysAllow
4657
+ ? { onAlwaysAllow: approvalActions.onAlwaysAllow }
4658
+ : {}),
4643
4659
  }),
4644
- [appendThreadMessage, execMode, markOptimisticRunning],
4660
+ [appendThreadMessage, execMode, markOptimisticRunning, approvalActions],
4645
4661
  );
4646
4662
 
4647
4663
  return (
@@ -5189,33 +5205,33 @@ const AssistantChatInner = forwardRef<
5189
5205
  onAttachmentError={setComposerError}
5190
5206
  extraActionButton={
5191
5207
  contextXRayEnabled ||
5192
- composerExtraActionButton ||
5193
- showRunningInUI ? (
5208
+ composerExtraActionButton ? (
5194
5209
  <>
5195
5210
  {contextXRayEnabled && (
5196
5211
  <ContextMeter threadId={threadId} />
5197
5212
  )}
5198
5213
  {composerExtraActionButton}
5199
- {showRunningInUI && (
5200
- <Tooltip>
5201
- <TooltipTrigger asChild>
5202
- <button
5203
- type="button"
5204
- onClick={() => stopActiveRun()}
5205
- aria-label="Stop response"
5206
- className="shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
5207
- >
5208
- <IconPlayerStopFilled className="h-3 w-3" />
5209
- </button>
5210
- </TooltipTrigger>
5211
- <TooltipContent>
5212
- Stop response
5213
- </TooltipContent>
5214
- </Tooltip>
5215
- )}
5216
5214
  </>
5217
5215
  ) : undefined
5218
5216
  }
5217
+ stopButton={
5218
+ showRunningInUI ? (
5219
+ <Tooltip>
5220
+ <TooltipTrigger asChild>
5221
+ <button
5222
+ type="button"
5223
+ onClick={() => stopActiveRun()}
5224
+ aria-label="Stop response"
5225
+ data-agent-composer-slot="stop-button"
5226
+ className="shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
5227
+ >
5228
+ <IconPlayerStopFilled className="h-3 w-3" />
5229
+ </button>
5230
+ </TooltipTrigger>
5231
+ <TooltipContent>Stop response</TooltipContent>
5232
+ </Tooltip>
5233
+ ) : undefined
5234
+ }
5219
5235
  />
5220
5236
  </>
5221
5237
  )}