@agent-native/core 0.148.1 → 0.149.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.
Files changed (165) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
  3. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
  4. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
  5. package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
  6. package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
  7. package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
  8. package/corpus/templates/calendar/vitest.config.ts +6 -1
  9. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
  10. package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
  11. package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
  12. package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
  13. package/corpus/templates/content/actions/_property-utils.ts +11 -1
  14. package/corpus/templates/content/actions/view-screen.ts +62 -12
  15. package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
  16. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
  17. package/corpus/templates/design/AGENTS.md +3 -0
  18. package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
  19. package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
  20. package/corpus/templates/design/actions/get-design-template.ts +178 -0
  21. package/corpus/templates/design/actions/list-designs.ts +3 -1
  22. package/corpus/templates/design/actions/view-screen.ts +31 -0
  23. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
  24. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  25. package/corpus/templates/design/app/i18n-data.ts +78 -0
  26. package/corpus/templates/design/app/lib/design-authors.ts +64 -0
  27. package/corpus/templates/design/app/pages/Index.tsx +114 -4
  28. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
  29. package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
  30. package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
  31. package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
  32. package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
  33. package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
  34. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
  35. package/corpus/templates/forms/AGENTS.md +2 -2
  36. package/corpus/templates/forms/actions/create-form.ts +1 -1
  37. package/corpus/templates/forms/actions/update-form.ts +5 -5
  38. package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
  39. package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
  40. package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
  41. package/corpus/templates/slides/AGENTS.md +4 -3
  42. package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
  43. package/corpus/templates/slides/actions/patch-deck.ts +18 -2
  44. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
  45. package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
  46. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +22 -75
  47. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +5 -3
  48. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
  49. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
  50. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
  51. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +27 -4
  52. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
  53. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
  54. package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
  55. package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
  56. package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
  57. package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
  58. package/corpus/templates/slides/app/pages/DeckEditor.tsx +44 -14
  59. package/corpus/templates/slides/app/pages/Index.tsx +124 -135
  60. package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
  61. package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
  62. package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
  63. package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
  64. package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
  65. package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
  66. package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
  67. package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
  68. package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
  69. package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
  70. package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
  71. package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
  72. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
  73. package/corpus/templates/slides/server/handlers/import/html-converter.ts +115 -22
  74. package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
  75. package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
  76. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
  77. package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
  78. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  79. package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
  80. package/dist/agent/engine/registry.d.ts +8 -2
  81. package/dist/agent/engine/registry.js +63 -29
  82. package/dist/agent/production-agent.js +7 -0
  83. package/dist/agent/run-manager.d.ts +5 -4
  84. package/dist/agent/run-manager.js +6 -5
  85. package/dist/cli/claude-code-participant.d.ts +2 -1
  86. package/dist/cli/claude-code-participant.js +19 -0
  87. package/dist/cli/code-agent-executor.js +223 -0
  88. package/dist/cli/create-workspace.js +3 -3
  89. package/dist/cli/create.d.ts +3 -2
  90. package/dist/cli/create.js +20 -24
  91. package/dist/client/AgentPanel.js +8 -4
  92. package/dist/client/AssistantChat.js +34 -1
  93. package/dist/client/agent-chat-adapter.d.ts +1 -0
  94. package/dist/client/agent-chat-adapter.js +10 -2
  95. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  96. package/dist/client/chat/markdown-renderer.js +4 -0
  97. package/dist/client/chat/message-components.d.ts +1 -0
  98. package/dist/client/chat/message-components.js +9 -1
  99. package/dist/client/chat/tool-call-display.d.ts +8 -0
  100. package/dist/client/chat/tool-call-display.js +30 -21
  101. package/dist/client/chat-first/app-pane.js +1 -1
  102. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  103. package/dist/client/chat-first/apps-rail.js +11 -11
  104. package/dist/client/chat-first/chat-history.d.ts +1 -1
  105. package/dist/client/chat-first/chat-history.js +2 -0
  106. package/dist/client/chat-first/primary-nav.js +1 -1
  107. package/dist/client/progress/RunsTray.js +14 -1
  108. package/dist/client/resources/McpConnectionSuggestion.js +2 -2
  109. package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
  110. package/dist/client/settings/SettingsTabsPage.js +2 -2
  111. package/dist/client/sse-event-processor.d.ts +8 -0
  112. package/dist/client/sse-event-processor.js +13 -6
  113. package/dist/collab/awareness.d.ts +2 -2
  114. package/dist/collab/routes.d.ts +1 -1
  115. package/dist/deploy/build.d.ts +5 -7
  116. package/dist/deploy/build.js +19 -12
  117. package/dist/localization/default-messages.js +1 -1
  118. package/dist/notifications/routes.d.ts +3 -3
  119. package/dist/observability/routes.d.ts +6 -6
  120. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  121. package/dist/scripts/call-agent.js +3 -3
  122. package/dist/scripts/describe-workspace-apps.js +1 -1
  123. package/dist/server/agent-capabilities.js +0 -0
  124. package/dist/server/agent-chat/context-tools.js +1 -1
  125. package/dist/server/agent-chat/prompt-resources.js +1 -1
  126. package/dist/server/agent-chat-plugin.js +1 -1
  127. package/dist/server/auth.js +190 -42
  128. package/dist/server/better-auth-migrations.js +53 -0
  129. package/dist/server/http-response-telemetry.js +7 -1
  130. package/dist/server/onboarding-html.js +119 -33
  131. package/dist/server/transcribe-voice.d.ts +1 -1
  132. package/dist/shared/password-policy.d.ts +2 -1
  133. package/dist/shared/password-policy.js +2 -1
  134. package/dist/styles/agent-conversation.css +20 -3
  135. package/dist/styles/agent-native.css +14 -2
  136. package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
  137. package/dist/templates/default/app/i18n/de-DE.ts +1 -1
  138. package/dist/templates/default/app/i18n/en-US.ts +1 -1
  139. package/dist/templates/default/app/i18n/es-ES.ts +1 -1
  140. package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
  141. package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
  142. package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
  143. package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
  144. package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
  145. package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
  146. package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
  147. package/dist/templates/default/pnpm-workspace.yaml +1 -0
  148. package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
  149. package/docs/content/cloneable-saas.mdx +6 -6
  150. package/docs/content/deployment.mdx +10 -8
  151. package/docs/content/environment-variables.mdx +4 -3
  152. package/package.json +2 -2
  153. package/src/templates/default/app/i18n/ar-SA.ts +1 -1
  154. package/src/templates/default/app/i18n/de-DE.ts +1 -1
  155. package/src/templates/default/app/i18n/en-US.ts +1 -1
  156. package/src/templates/default/app/i18n/es-ES.ts +1 -1
  157. package/src/templates/default/app/i18n/fr-FR.ts +1 -1
  158. package/src/templates/default/app/i18n/hi-IN.ts +1 -1
  159. package/src/templates/default/app/i18n/ja-JP.ts +1 -1
  160. package/src/templates/default/app/i18n/ko-KR.ts +1 -1
  161. package/src/templates/default/app/i18n/pt-BR.ts +1 -1
  162. package/src/templates/default/app/i18n/zh-CN.ts +1 -1
  163. package/src/templates/default/app/i18n/zh-TW.ts +1 -1
  164. package/src/templates/default/pnpm-workspace.yaml +1 -0
  165. package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
@@ -14,6 +14,7 @@ import { readAgentsBundleFromFs, generateDevelopmentSkillsPromptBlock, } from ".
14
14
  import { getAmbientOrgId, getAmbientUserEmail, runWithRequestContext, } from "../server/request-context.js";
15
15
  import { isReasoningEffort, } from "../shared/reasoning-effort.js";
16
16
  import { createAgentNativeConfigContext, loadResolvedAgentNativeConfig, } from "../vite/agent-native-config-loader.js";
17
+ import { runClaudeCodeParticipant, } from "./claude-code-participant.js";
17
18
  import { codexMcpConfigArgs, mergeCodeAgentMcpConfig, restrictCodeAgentMcpConfig, } from "./code-agent-mcp-config.js";
18
19
  import { createCodeAgentOutputSmoother } from "./code-agent-output-smoother.js";
19
20
  import { addCodeAgentCommandToAllowlist, appendCodeAgentTranscriptEvent, dequeueCodeAgentFollowUp, getCodeAgentRunRecord, isCodeAgentCommandAllowed, listCodeAgentTranscriptEvents, updateCodeAgentRunRecord, } from "./code-agent-runs.js";
@@ -21,6 +22,8 @@ const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
21
22
  const MAX_TOOL_OUTPUT_CHARS = 50_000;
22
23
  const MAX_FILE_READ_CHARS = 120_000;
23
24
  const CODEX_CLI_ENGINE_NAME = "codex-cli";
25
+ const CLAUDE_CLI_ENGINE_NAME = "claude-cli";
26
+ const CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5";
24
27
  const RECAP_SOURCE_TOOL_PROFILE = "recap-source";
25
28
  const RECAP_SOURCE_OUTPUT_FILE = "recap-source.json";
26
29
  /**
@@ -110,6 +113,18 @@ export async function executeCodeAgentRun(options) {
110
113
  signal: options.signal,
111
114
  });
112
115
  }
116
+ if (requestedEngine === CLAUDE_CLI_ENGINE_NAME) {
117
+ return executeClaudeCliRun({
118
+ run: existing,
119
+ prompt: executionPrompt,
120
+ model: options.model ?? metadataString(existing, "model"),
121
+ reasoningEffort: options.reasoningEffort ?? metadataReasoningEffort(existing),
122
+ permissionMode: existing.permissionMode ?? "full-auto",
123
+ stdout: options.stdout,
124
+ streamToolOutputToStdout,
125
+ signal: options.signal,
126
+ });
127
+ }
113
128
  const engine = options.engine ?? (await resolveExecutorEngine(requestedEngine));
114
129
  if (!engine) {
115
130
  const message = "No LLM provider key was found. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, another supported provider key, or run `codex login` to use Codex CLI.";
@@ -437,6 +452,214 @@ export function codeAgentMcpInvocationPolicy(permissionMode) {
437
452
  mode: permissionMode === "read-only" ? "read-only" : "allow-all",
438
453
  };
439
454
  }
455
+ async function executeClaudeCliRun(options) {
456
+ const cwd = options.run.cwd || process.cwd();
457
+ const model = normalizeClaudeCliModel(options.model);
458
+ const reasoningEffort = normalizeClaudeCliEffort(options.reasoningEffort);
459
+ const streamToolOutputToStdout = options.streamToolOutputToStdout ??
460
+ process.env.AGENT_NATIVE_CODE_AGENT_STRUCTURED_STDOUT !== "1";
461
+ const assistantText = [];
462
+ appendCodeAgentTranscriptEvent({
463
+ runId: options.run.id,
464
+ kind: "status",
465
+ message: "Starting Claude Code with local Claude authentication.",
466
+ metadata: {
467
+ status: "running",
468
+ phase: "executing",
469
+ engine: CLAUDE_CLI_ENGINE_NAME,
470
+ },
471
+ });
472
+ try {
473
+ const result = await runClaudeCodeParticipant({
474
+ role: options.permissionMode === "full-auto" ? "driver" : "watchdog",
475
+ prompt: buildClaudeCliPrompt(options.run, options.prompt),
476
+ cwd,
477
+ model,
478
+ effort: reasoningEffort,
479
+ signal: options.signal,
480
+ onEvent: (event) => {
481
+ const text = readClaudeParticipantAssistantText(event);
482
+ if (!text)
483
+ return;
484
+ assistantText.push(text);
485
+ if (streamToolOutputToStdout)
486
+ options.stdout?.write(text);
487
+ },
488
+ });
489
+ const finalMessage = readClaudeParticipantResultText(result.events) ??
490
+ (assistantText.join("\n\n").trim() || "Claude Code run completed.");
491
+ appendCodeAgentTranscriptEvent({
492
+ runId: options.run.id,
493
+ kind: "system",
494
+ message: finalMessage,
495
+ metadata: {
496
+ role: "assistant",
497
+ engine: CLAUDE_CLI_ENGINE_NAME,
498
+ model,
499
+ ...(reasoningEffort ? { reasoningEffort } : {}),
500
+ },
501
+ });
502
+ const pendingFollowUp = dequeueCodeAgentFollowUp(options.run.id);
503
+ if (pendingFollowUp) {
504
+ const message = pendingFollowUp.mode === "queued"
505
+ ? "Claude Code run completed; running queued follow-up."
506
+ : "Claude Code run completed; applying steering follow-up.";
507
+ appendCodeAgentTranscriptEvent({
508
+ runId: options.run.id,
509
+ kind: "status",
510
+ message,
511
+ metadata: {
512
+ status: "running",
513
+ phase: "follow-up",
514
+ followUpId: pendingFollowUp.id,
515
+ followUpMode: pendingFollowUp.mode,
516
+ engine: CLAUDE_CLI_ENGINE_NAME,
517
+ },
518
+ });
519
+ if (pendingFollowUp.permissionMode) {
520
+ updateCodeAgentRunRecord(options.run.id, {
521
+ permissionMode: pendingFollowUp.permissionMode,
522
+ });
523
+ }
524
+ return executeCodeAgentRun({
525
+ runId: options.run.id,
526
+ prompt: pendingFollowUp.prompt,
527
+ attachments: pendingFollowUp.attachments ??
528
+ userPromptAttachmentsForEvent(options.run.id, pendingFollowUp.eventId),
529
+ appendUserEvent: false,
530
+ stdout: options.stdout,
531
+ streamToolOutputToStdout: options.streamToolOutputToStdout,
532
+ signal: options.signal,
533
+ });
534
+ }
535
+ appendCodeAgentTranscriptEvent({
536
+ runId: options.run.id,
537
+ kind: "status",
538
+ message: "Claude Code run completed.",
539
+ metadata: {
540
+ status: "completed",
541
+ phase: "complete",
542
+ engine: CLAUDE_CLI_ENGINE_NAME,
543
+ },
544
+ });
545
+ return updateCodeAgentRunRecord(options.run.id, {
546
+ status: "completed",
547
+ phase: "complete",
548
+ needsApproval: false,
549
+ progress: {
550
+ label: "Complete",
551
+ completed: 1,
552
+ total: 1,
553
+ percent: 100,
554
+ },
555
+ metadata: {
556
+ executionCompletedAt: new Date().toISOString(),
557
+ engine: CLAUDE_CLI_ENGINE_NAME,
558
+ model,
559
+ ...(reasoningEffort ? { reasoningEffort } : {}),
560
+ permissionMode: options.permissionMode,
561
+ },
562
+ });
563
+ }
564
+ catch (error) {
565
+ const interrupted = options.signal?.aborted === true;
566
+ const message = error instanceof Error ? error.message : String(error);
567
+ const summary = interrupted
568
+ ? "Claude Code run paused."
569
+ : `Claude Code run failed: ${message}`;
570
+ if (streamToolOutputToStdout)
571
+ options.stdout?.write(`\n${summary}\n`);
572
+ appendCodeAgentTranscriptEvent({
573
+ runId: options.run.id,
574
+ kind: "status",
575
+ message: summary,
576
+ metadata: {
577
+ status: interrupted ? "paused" : "errored",
578
+ phase: interrupted ? "paused" : "error",
579
+ engine: CLAUDE_CLI_ENGINE_NAME,
580
+ },
581
+ });
582
+ return updateCodeAgentRunRecord(options.run.id, {
583
+ status: interrupted ? "paused" : "errored",
584
+ phase: interrupted ? "paused" : "error",
585
+ progress: {
586
+ label: interrupted ? "Paused" : "Error",
587
+ completed: 0,
588
+ total: 1,
589
+ failed: interrupted ? 0 : 1,
590
+ percent: 0,
591
+ },
592
+ metadata: {
593
+ ...(interrupted
594
+ ? { executionPausedAt: new Date().toISOString() }
595
+ : {
596
+ executionError: message,
597
+ executionErroredAt: new Date().toISOString(),
598
+ }),
599
+ engine: CLAUDE_CLI_ENGINE_NAME,
600
+ model,
601
+ },
602
+ });
603
+ }
604
+ }
605
+ function buildClaudeCliPrompt(run, prompt) {
606
+ const permissionMode = run.permissionMode ?? "full-auto";
607
+ const mode = permissionMode === "full-auto" ? "Auto" : "Plan";
608
+ const modeInstruction = mode === "Plan"
609
+ ? "Inspect and explain only. Do not edit files or run mutating commands."
610
+ : "Edit and verify as needed. Do not create, switch, reset, rebase, or stash git branches.";
611
+ const additionalSkillsRoot = process.env.AGENT_NATIVE_CODE_AGENT_SKILLS_ROOT?.trim();
612
+ return [
613
+ `You are running from Agent-Native Code in ${run.cwd || process.cwd()}.`,
614
+ "Follow the repository AGENTS.md and any relevant skill instructions.",
615
+ ...(additionalSkillsRoot
616
+ ? [
617
+ `Additional installed Agent Plugin skills are available under ${additionalSkillsRoot}. Read any relevant SKILL.md files before acting.`,
618
+ ]
619
+ : []),
620
+ `Run mode: ${mode} (${permissionMode}). ${modeInstruction}`,
621
+ "",
622
+ "# User request",
623
+ prompt,
624
+ ].join("\n");
625
+ }
626
+ function normalizeClaudeCliModel(model) {
627
+ const trimmed = model?.trim();
628
+ return !trimmed || trimmed === "auto" || trimmed === CLAUDE_CLI_ENGINE_NAME
629
+ ? CLAUDE_CLI_DEFAULT_MODEL
630
+ : trimmed;
631
+ }
632
+ function normalizeClaudeCliEffort(effort) {
633
+ return effort && effort !== "auto" ? effort : undefined;
634
+ }
635
+ function readClaudeParticipantAssistantText(event) {
636
+ if (event.type !== "assistant")
637
+ return null;
638
+ const message = asRecordValue(event.message);
639
+ const content = message?.content;
640
+ if (!Array.isArray(content))
641
+ return null;
642
+ const text = content
643
+ .filter((part) => isRecordValue(part))
644
+ .map((part) => (typeof part.text === "string" ? part.text : ""))
645
+ .filter(Boolean)
646
+ .join("");
647
+ return text.trim() || null;
648
+ }
649
+ function readClaudeParticipantResultText(events) {
650
+ for (let index = events.length - 1; index >= 0; index -= 1) {
651
+ const result = events[index]?.result;
652
+ if (typeof result === "string" && result.trim())
653
+ return result.trim();
654
+ }
655
+ return null;
656
+ }
657
+ function isRecordValue(value) {
658
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
659
+ }
660
+ function asRecordValue(value) {
661
+ return isRecordValue(value) ? value : null;
662
+ }
440
663
  async function executeCodexCliRun(options) {
441
664
  const cwd = options.run.cwd || process.cwd();
442
665
  const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-native-code-codex-"));
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * @deprecated `create-workspace` is now an alias for `create`. In current
3
- * versions, `agent-native create <name>` defaults to scaffolding a workspace
4
- * with a multi-select template picker. Use `--standalone` for a single-app
5
- * standalone scaffold.
3
+ * versions, `agent-native create <name>` asks for a starting shape and its
4
+ * Chat/first-party template paths scaffold a workspace. Use `--standalone`
5
+ * for a single-app standalone scaffold.
6
6
  *
7
7
  * This module is kept for backwards compatibility with older docs and
8
8
  * scripts that still invoke `agent-native create-workspace`.
@@ -32,8 +32,9 @@ export interface CreateAppOptions {
32
32
  /**
33
33
  * Main entry for `agent-native create [name]`.
34
34
  *
35
- * Default behavior: scaffold a workspace at <name>/ with a multi-select
36
- * template picker. Use --standalone for the single-app standalone flow.
35
+ * Default behavior: ask for a starting shape, with Chat and first-party
36
+ * templates creating a workspace at <name>/. Use --standalone for the
37
+ * single-app standalone flow.
37
38
  *
38
39
  * If run *inside* an existing workspace, falls through to the add-app
39
40
  * flow that scaffolds one new app under apps/<name>/.
@@ -93,8 +93,9 @@ const COMMUNITY_OPTION = {
93
93
  /**
94
94
  * Main entry for `agent-native create [name]`.
95
95
  *
96
- * Default behavior: scaffold a workspace at <name>/ with a multi-select
97
- * template picker. Use --standalone for the single-app standalone flow.
96
+ * Default behavior: ask for a starting shape, with Chat and first-party
97
+ * templates creating a workspace at <name>/. Use --standalone for the
98
+ * single-app standalone flow.
98
99
  *
99
100
  * If run *inside* an existing workspace, falls through to the add-app
100
101
  * flow that scaffolds one new app under apps/<name>/.
@@ -126,8 +127,8 @@ export async function createApp(name, opts) {
126
127
  }
127
128
  // When exactly one template is specified explicitly, treat it as a
128
129
  // standalone scaffold (script-friendly, matches historic behavior).
129
- // Use `--template a,b` or pass no --template to opt into the workspace
130
- // flow with the multi-select picker.
130
+ // Use `--template a,b` to opt into the workspace flow with the multi-select
131
+ // picker. Bare `create` asks for the starting shape first.
131
132
  const parsed = parseTemplateList(opts?.template);
132
133
  // Headless can't live in a workspace, so reject it when more than one
133
134
  // template is requested or when workspace semantics are forced.
@@ -146,9 +147,8 @@ export async function createApp(name, opts) {
146
147
  // No template specified: ask what shape to start from before diving into
147
148
  // "which templates?". The on-ramp choice implies the project structure, so
148
149
  // we don't ask a separate "workspace or standalone?" question — Chat and
149
- // Headless scaffold a single standalone app (the lightest starts; headless
150
- // cannot live in a workspace), while Template continues into the workspace
151
- // multi-select.
150
+ // Template creates a workspace, while Community and Headless scaffold a
151
+ // single standalone app.
152
152
  if (parsed.length === 0) {
153
153
  // The deprecated `create-workspace` alias forces workspace semantics, so
154
154
  // it must skip the start-shape prompt and scaffold a workspace directly.
@@ -157,10 +157,17 @@ export async function createApp(name, opts) {
157
157
  return;
158
158
  }
159
159
  const shape = await promptStartShape(clack);
160
- if (shape === "headless" || shape === "chat") {
160
+ if (shape === "headless") {
161
161
  await createStandaloneApp(name, { ...opts, template: shape }, clack);
162
162
  return;
163
163
  }
164
+ if (shape === "chat") {
165
+ // Chat is the default workspace on-ramp. Keep the minimal chat app in
166
+ // the same workspace shape as every other first-party app so the next
167
+ // documented step, `add-app`, works immediately.
168
+ await createWorkspaceInteractive(name, { ...opts, template: shape }, clack);
169
+ return;
170
+ }
164
171
  if (shape === "community") {
165
172
  const template = await promptCommunityTemplate(clack);
166
173
  await createStandaloneApp(name, { ...opts, template }, clack);
@@ -178,12 +185,11 @@ export async function createApp(name, opts) {
178
185
  * choice made here implies the project structure, so we deliberately avoid a
179
186
  * separate "workspace or standalone?" question:
180
187
  * - "template" → full app(s) in a workspace (the multi-select picker)
188
+ * - "chat" → a minimal Chat app in a workspace with Dispatch
181
189
  * - "community" → a single standalone app from a public GitHub repository
182
- * - "chat" → a single standalone chat UI app
183
190
  * - "headless" → a single standalone action-first app with no UI shell
184
- * Chat and headless are standalone on purpose: a monorepo is unnecessary
185
- * ceremony for the lightest on-ramps, and headless cannot be a workspace
186
- * member. Either can grow into a workspace later via `add-app`.
191
+ * Headless cannot be a workspace member. Use `--standalone --template chat`
192
+ * when a standalone Chat app is the intended shape.
187
193
  */
188
194
  async function promptStartShape(clack) {
189
195
  const choice = await clack.select(startShapePromptOptions());
@@ -201,7 +207,7 @@ function startShapePromptOptions() {
201
207
  {
202
208
  value: "chat",
203
209
  label: "Chat",
204
- hint: "A single app with a minimal chat UI and the browser shell wired up",
210
+ hint: "A minimal chat app in a workspace with the browser shell wired up",
205
211
  },
206
212
  {
207
213
  value: "template",
@@ -1420,20 +1426,9 @@ function postProcessStandalone(name, targetDir, templateName, resolution) {
1420
1426
  }
1421
1427
  }
1422
1428
  }
1423
- // Ensure pnpm.onlyBuiltDependencies is set so native packages
1424
- // (better-sqlite3, esbuild, node-pty) compile their postinstall scripts
1425
- // under pnpm 10+ without prompting for `pnpm approve-builds`.
1426
1429
  pkg.dependencies = pkg.dependencies ?? {};
1427
1430
  pkg.dependencies.postgres ??= POSTGRES_DEPENDENCY_VERSION;
1428
1431
  ensureReactRouterBuildDependencies(pkg);
1429
- const requiredBuilt = ["better-sqlite3", "esbuild", "node-pty"];
1430
- if (!pkg.pnpm || typeof pkg.pnpm !== "object") {
1431
- pkg.pnpm = {};
1432
- }
1433
- const existing = Array.isArray(pkg.pnpm.onlyBuiltDependencies)
1434
- ? pkg.pnpm.onlyBuiltDependencies
1435
- : [];
1436
- pkg.pnpm.onlyBuiltDependencies = Array.from(new Set([...existing, ...requiredBuilt]));
1437
1432
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1438
1433
  }
1439
1434
  catch { }
@@ -1452,6 +1447,7 @@ function postProcessStandalone(name, targetDir, templateName, resolution) {
1452
1447
  "better-sqlite3": "true",
1453
1448
  esbuild: "true",
1454
1449
  "node-pty": "true",
1450
+ "tesseract.js": "true",
1455
1451
  },
1456
1452
  };
1457
1453
  if (templateName !== "headless") {
@@ -689,7 +689,11 @@ function AgentPanelInner({ defaultMode = "chat", className, style, apiUrl, empty
689
689
  return (_jsx(ShareButton, { resourceType: "chat_thread", resourceId: activeTab.id, resourceTitle: activeTab.label || "Chat", shareUrl: getChatThreadShareUrl(activeTab.id), trigger: "icon", triggerClassName: "h-7 w-7" }));
690
690
  })(), feedbackEnabled ? (_jsx(FeedbackButton, { variant: "icon", side: "bottom", align: "end", chatSessionId: activeChatSessionId, chatStorageKey: storageKey, open: feedbackOpen, onOpenChange: setFeedbackOpen, trigger: _jsx("button", { type: "button", tabIndex: -1, "aria-hidden": "true", className: "pointer-events-none absolute end-0 top-full h-px w-px opacity-0" }) })) : null, mode === "chat" && (_jsx(IconTooltip, { content: t("agentPanel.newChat"), children: _jsx("button", { onClick: addTab, "aria-label": t("agentPanel.newChat"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), !onCollapse && mode === "cli" && canUseCodeTools && (_jsx(IconTooltip, { content: t("agentPanel.newTerminal"), children: _jsx("button", { onClick: addCliTab, "aria-label": t("agentPanel.newTerminal"), className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", children: _jsx(IconPlus, { size: 14 }) }) })), _jsxs(DropdownMenu, { open: headerMenuOpen, onOpenChange: setHeaderMenuOpen, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx("button", { className: cn("flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-accent/50", (headerMenuOpen || mode === "settings") &&
691
691
  "bg-accent text-foreground"), "aria-label": t("agentPanel.panelOptions"), children: _jsx(IconDotsVertical, { size: 14 }) }) }), _jsxs(DropdownMenuContent, { align: "end", sideOffset: 6, className: "w-48", children: [onCollapse && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenuItem, { onSelect: onCollapse, children: [_jsx(IconLayoutSidebarRightCollapse, { size: 14, className: "shrink-0" }), t("agentPanel.collapseSidebar"), _jsx(DropdownMenuShortcut, { children: toggleSidebarHint })] }), _jsx(DropdownMenuSeparator, {})] })), onCollapse && mode === "chat" && wideDrawerAction ? (_jsxs(DropdownMenuItem, { onSelect: wideDrawerAction, children: [_jsx(IconArrowsHorizontal, { size: 14, className: "shrink-0" }), wideDrawerLabel, _jsx(DropdownMenuShortcut, { children: widenChatHint })] })) : null, showFullViewAction && agentPageHref ? (_jsx(DropdownMenuItem, { asChild: true, children: _jsxs(Link, { to: agentPageHref, "aria-label": t("agentPanel.openFullView"), children: [_jsx(IconArrowsMaximize, { size: 14, className: "shrink-0" }), t("agentPanel.openFullView")] }) })) : null, (onCollapse && mode === "chat" && wideDrawerAction) ||
692
- showFullViewAction ? (_jsx(DropdownMenuSeparator, {})) : null, onCollapse && mode === "chat" && (_jsxs(DropdownMenuItem, { onSelect: addTab, children: [_jsx(IconPlus, { size: 14, className: "shrink-0" }), t("agentPanel.newChat")] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: toggleHistory, children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
692
+ showFullViewAction ? (_jsx(DropdownMenuSeparator, {})) : null, onCollapse && mode === "chat" && (_jsxs(DropdownMenuItem, { onSelect: addTab, children: [_jsx(IconPlus, { size: 14, className: "shrink-0" }), t("agentPanel.newChat")] })), mode === "chat" && toggleHistory && (_jsxs(DropdownMenuItem, { onSelect: () => {
693
+ // Let the menu finish restoring focus before mounting the
694
+ // history popover; otherwise Radix dismisses the new overlay.
695
+ setTimeout(() => toggleHistory(), 0);
696
+ }, children: [_jsx(IconHistory, { size: 14, className: "shrink-0" }), showHistory
693
697
  ? t("agentPanel.hideChats")
694
698
  : t("agentPanel.allChats")] })), mode === "chat" && (_jsx(RunsTrayMenuItem, { pollMs: 0, limit: 12, showRecent: true, onOpenThread: openRunThread })), mode === "chat" && _jsx(DropdownMenuSeparator, {}), mode === "cli" && availableClis.length > 0 && (_jsxs(_Fragment, { children: [availableClis.map((cli) => (_jsxs(DropdownMenuItem, { onSelect: () => selectCli(cli.command), className: cn(cli.command === selectedCli
695
699
  ? "font-medium"
@@ -942,7 +946,7 @@ const SIDEBAR_OVERLAY_Z_INDEX = 70;
942
946
  const SIDEBAR_DRAWER_Z_INDEX = 80;
943
947
  const SIDEBAR_DRAWER_VIEW_TRANSITION_NAME = "agent-native-sidebar-drawer";
944
948
  /** Shared max width of the centered fullscreen chat column and composer. */
945
- const FULLSCREEN_CHAT_COLUMN_MAX_PX = 684;
949
+ const FULLSCREEN_CHAT_COLUMN_MAX_PX = 750;
946
950
  export function getActiveTabScrollDelta(containerRect, tabRect, margin = 24) {
947
951
  if (tabRect.left < containerRect.left + margin) {
948
952
  return tabRect.left - containerRect.left - margin;
@@ -1842,7 +1846,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1842
1846
  }
1843
1847
  // Mount the live chat surface only while visible or actively needed. Keeping
1844
1848
  // it mounted while closed starts app-state polling on every public page view.
1845
- const sidebar = shouldRenderPanel ? (_jsxs(_Fragment, { children: [showResizeHandle && !isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd })), _jsx("div", { className: cn("agent-sidebar-panel flex shrink-0 flex-col overflow-hidden text-[13px] leading-[1.2] antialiased", chatViewTransition && AGENT_CHAT_VIEW_TRANSITION_CLASS, mobileAnimationEnabled && "shadow-2xl"), "data-agent-sidebar-animation": wideDrawerEnabled
1849
+ const sidebar = shouldRenderPanel ? (_jsxs(_Fragment, { children: [showResizeHandle && !isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd })), _jsx("div", { className: cn("agent-sidebar-panel flex shrink-0 flex-col overflow-hidden text-[13px] leading-[1.2] antialiased", chatViewTransition && AGENT_CHAT_VIEW_TRANSITION_CLASS), "data-agent-sidebar-animation": wideDrawerEnabled
1846
1850
  ? "drawer"
1847
1851
  : mobileAnimationEnabled
1848
1852
  ? "mobile"
@@ -1851,7 +1855,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
1851
1855
  : undefined, "data-agent-sidebar-layout": panelLayout, "data-agent-sidebar-position": position, "data-agent-sidebar-state": panelOpen ? "open" : "closed", "data-agent-sidebar-resizing": isResizing ? "true" : undefined, "data-agent-sidebar-chat-handoff": chatViewTransitionHandoff ? "true" : undefined, style: chatViewTransition
1852
1856
  ? getAgentChatViewTransitionStyle(panelStyle)
1853
1857
  : 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, threadFooterSlot: threadFooterSlot, missingApiKeySetupLayout: "sidebar", onCollapse: () => setOpenPersisted(false), onSnapTo75Percent: isMobile ? undefined : snapTo75Percent, isWideDrawer: isMobile ? false : isWideDrawer, onExitWideDrawer: isMobile ? undefined : exitWideDrawer, storageKey: storageKey, scope: scope, showScopeBadge: showScopeBadge, browserTabId: browserTabId, threadUrlSync: threadUrlSync, agentPageHref: agentPageHref, allowSettingsMode: false, chatOnly: true }) }) }), showResizeHandle && isLeft && (_jsx(ResizeHandle, { position: position, onDrag: handleDrag, onResizeStart: handleResizeStart, onResizeEnd: handleResizeEnd }))] })) : null;
1854
- const drawerPlaceholder = wideDrawerEnabled && !presentationMode && shouldRenderPanel ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-drawer-placeholder shrink-0", "data-agent-sidebar-placeholder": "true", style: { width: drawerPlaceholderWidth + 1 } })) : null;
1858
+ 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;
1855
1859
  return (_jsx(AgentSidebarOnboardingContext.Provider, { value: true, children: _jsxs(RealtimeVoiceModeProvider, { browserTabId: browserTabId, children: [showFirstRunOnboarding && (_jsx(Suspense, { fallback: null, children: _jsx(FirstRunOnboarding, {}) })), _jsxs("div", { className: "agent-sidebar-shell flex min-w-0 flex-1 h-screen overflow-hidden", "data-agent-sidebar-position": position, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: [isMobile &&
1856
1860
  !presentationMode &&
1857
1861
  (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, 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] })] }) }));
@@ -1346,6 +1346,9 @@ export function useAutoResumeStatus(tabId, forceStopped) {
1346
1346
  }, [clearAutoResume, forceStopped]);
1347
1347
  return { isAutoResuming, clearAutoResume };
1348
1348
  }
1349
+ function approvalResolutionIdentity(approvalKey, toolCallId) {
1350
+ return `${toolCallId ?? ""}\u0000${approvalKey}`;
1351
+ }
1349
1352
  const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateText, suggestions, dynamicSuggestions, threadFooterSlot, emptyStateAddon, showHeader = true, onSwitchToCli, className, apiUrl, tabId, browserTabId, threadId, contextScope, isActiveComposer = true, onMessageCountChange, onSaveThread, onGenerateTitle, composerSlot, onComposerTextChange, composerAreaClassName, composerPlaceholder, missingApiKeySetupLayout = "default", composerLayoutVariant = "default", centerComposerWhenEmpty = false, emptyStateDisplay = "default", composerToolbarSlot, composerExtraActionButton, composerDisabled = false, composerDisabledPlaceholder, isNewThread, isThreadStateLoading, onSlashCommand, execMode, onExecModeChange, approvalActions, planModeDisabled, planModeDisabledReason, selectedModel, defaultModel, selectedEngine, selectedEffort, availableModels, modelListLoading, onModelChange, onEffortChange, imageModelMenu, onForkChat, onConnectProvider, onConnectLocalRuntime, plusMenuMode = "full", providerStatusChecksEnabled = true, loadHistoryRepository, historyReloadKey, externalStreaming = false, agentChatSurface = "app", }, ref) {
1350
1353
  const thread = useThread();
1351
1354
  const threadRuntime = useThreadRuntime();
@@ -3974,11 +3977,34 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3974
3977
  composerContextItems.length > 0 ||
3975
3978
  showPlanModeCallout ||
3976
3979
  showInlineMissingKeySetup);
3980
+ const approvalResolutionScope = threadId ?? tabId ?? "default";
3981
+ const [approvalResolutionState, setApprovalResolutionState] = useState(() => ({
3982
+ scope: approvalResolutionScope,
3983
+ byIdentity: new Map(),
3984
+ }));
3985
+ const getApprovalResolution = useCallback((approvalKey, toolCallId) => {
3986
+ if (approvalResolutionState.scope !== approvalResolutionScope) {
3987
+ return null;
3988
+ }
3989
+ return (approvalResolutionState.byIdentity.get(approvalResolutionIdentity(approvalKey, toolCallId)) ?? null);
3990
+ }, [approvalResolutionScope, approvalResolutionState]);
3991
+ const recordApprovalResolution = useCallback((approvalKey, resolution, toolCallId) => {
3992
+ setApprovalResolutionState((previous) => {
3993
+ const byIdentity = previous.scope === approvalResolutionScope
3994
+ ? previous.byIdentity
3995
+ : new Map();
3996
+ const next = new Map(byIdentity);
3997
+ next.set(approvalResolutionIdentity(approvalKey, toolCallId), resolution);
3998
+ return { scope: approvalResolutionScope, byIdentity: next };
3999
+ });
4000
+ }, [approvalResolutionScope]);
3977
4001
  // Human-in-the-loop approvals: when the user approves a paused `needsApproval`
3978
4002
  // tool call, re-issue the turn carrying the call's approval key so the server
3979
4003
  // gate lets that specific call run. Reuses the same append path as recovery /
3980
4004
  // queued messages (no hand-written fetch).
3981
4005
  const approvalCtx = useMemo(() => ({
4006
+ getApprovalResolution,
4007
+ onApprovalResolved: recordApprovalResolution,
3982
4008
  onApprove: (approvalKey) => {
3983
4009
  markOptimisticRunning();
3984
4010
  appendThreadMessage({
@@ -4000,7 +4026,14 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4000
4026
  ...(approvalActions?.onAlwaysAllow
4001
4027
  ? { onAlwaysAllow: approvalActions.onAlwaysAllow }
4002
4028
  : {}),
4003
- }), [appendThreadMessage, execMode, markOptimisticRunning, approvalActions]);
4029
+ }), [
4030
+ appendThreadMessage,
4031
+ execMode,
4032
+ getApprovalResolution,
4033
+ markOptimisticRunning,
4034
+ approvalActions,
4035
+ recordApprovalResolution,
4036
+ ]);
4004
4037
  return (_jsx(CheckpointContext.Provider, { value: checkpointCtx, children: _jsx(MessageActionsContext.Provider, { value: messageActionsCtx, children: _jsx(ApprovalContext.Provider, { value: approvalCtx, children: _jsx(ChatRunDurationContext.Provider, { value: lastChatRunDurationMs, children: _jsx(ServerRunActiveContext.Provider, { value: serverRunActive, children: _jsx(ChatRunningContext.Provider, { value: showRunningInUI, children: _jsx(TextStreamingContext.Provider, { value: textStreaming, children: _jsxs("div", { "data-agent-empty-state": centeredEmptyState
4005
4038
  ? "centered"
4006
4039
  : compactMissingKeyEmptyState
@@ -13,6 +13,7 @@ export type AgentChatSurfaceKind =
13
13
  | "dev-frame"
14
14
  /** Chat hosted by Desktop for a local app with explicit code access. */
15
15
  | "desktop";
16
+ export declare const BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS = 90000;
16
17
  export declare const BACKGROUND_FOLLOW_IDLE_TIMEOUT_MS = 210000;
17
18
  export declare const MAX_FOLLOWED_BACKGROUND_RUNS = 24;
18
19
  export declare const MAX_BACKGROUND_FOLLOW_WALL_TIME_MS: number;
@@ -96,6 +96,10 @@ const STARTUP_RESPONSE_TIMEOUT_MS = 45_000;
96
96
  // reports that the self-dispatch itself failed before a successor claimed the
97
97
  // run, the existing client auto-continue POST remains the fallback.
98
98
  const BACKGROUND_FOLLOW_POLL_INTERVAL_MS = 1_000;
99
+ // This watchdog belongs to a read-only reattach, not to the server run. A
100
+ // shorter reader window lets the follow loop re-poll `/runs/active` while a
101
+ // completed run is still inside the server's terminal replay window.
102
+ export const BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS = 90_000;
99
103
  // How long the follow loop tolerates seeing NO active run for this turn before
100
104
  // treating the turn as ended. The server pre-inserts the successor row before
101
105
  // the old chunk completes, so a healthy chain never shows an idle gap; allow a
@@ -1691,11 +1695,12 @@ export function createAgentChatAdapter(options) {
1691
1695
  preparingActionStatesByRun.set(id, state);
1692
1696
  return state;
1693
1697
  };
1694
- const currentSSEOptions = () => ({
1698
+ const currentSSEOptions = (overrides = {}) => ({
1695
1699
  durableBackgroundRun: currentRunDispatchMode?.startsWith("background") === true,
1696
1700
  ...(runId
1697
1701
  ? { preparingActionState: preparingActionStateForRun(runId) }
1698
1702
  : {}),
1703
+ ...overrides,
1699
1704
  });
1700
1705
  const captureChatClientError = (error, phase, extra = {}) => {
1701
1706
  captureError(error, {
@@ -2102,7 +2107,10 @@ export function createAgentChatAdapter(options) {
2102
2107
  rememberRunSeq(seq);
2103
2108
  if (threadId)
2104
2109
  updateActiveRunSeq(seq);
2105
- }, runId, currentSSEOptions())) {
2110
+ }, runId, currentSSEOptions({
2111
+ noProgressTimeoutMs: BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS,
2112
+ actionPreparationStallTimeoutMs: BACKGROUND_FOLLOW_ATTACH_WATCHDOG_MS,
2113
+ }))) {
2106
2114
  yield withRequestModeMetadata(result);
2107
2115
  }
2108
2116
  // readSSEStream returned normally: a terminal done/error was
@@ -31,6 +31,7 @@ export declare function markdownUrlTransform(value: string): string;
31
31
  export declare function extractCodeText(child: React.ReactNode): string;
32
32
  export declare const markdownComponents: {
33
33
  a(props: React.AnchorHTMLAttributes<HTMLAnchorElement>): React.JSX.Element;
34
+ table(props: React.TableHTMLAttributes<HTMLTableElement>): React.JSX.Element;
34
35
  pre(props: React.HTMLAttributes<HTMLPreElement>): React.JSX.Element;
35
36
  };
36
37
  export declare function renderMarkdownToClipboardHtml(markdown: string): string | null;
@@ -158,6 +158,10 @@ export const markdownComponents = {
158
158
  }
159
159
  return (_jsxs("a", { href: href, target: "_blank", rel: "noreferrer", className: cn(CTA_BUTTON_CLASSES, className), ...rest, children: [_jsx("span", { children: children }), _jsx(IconExternalLink, { size: 13, strokeWidth: 2, "aria-hidden": "true" })] }));
160
160
  },
161
+ table(props) {
162
+ const { children, ...rest } = props;
163
+ return (_jsx("div", { className: "agent-markdown-table-wrap", children: _jsx("table", { ...rest, children: children }) }));
164
+ },
161
165
  pre(props) {
162
166
  const { children, ...rest } = props;
163
167
  if (React.isValidElement(children)) {
@@ -97,6 +97,7 @@ export declare function shouldShowAssistantWorkSummary({ isLast, isComplete, has
97
97
  hasUnresolvedTool: boolean;
98
98
  chatRunning: boolean;
99
99
  }): boolean;
100
+ export declare function getAssistantWorkSummaryDurationMs(durationMs: number | null | undefined, groupStartIndex: number, firstWorkPartIndex: number): number | null | undefined;
100
101
  export declare function isAlwaysVisibleAssistantTool(part: {
101
102
  type?: unknown;
102
103
  toolName?: unknown;
@@ -667,6 +667,9 @@ export function shouldShowAssistantWorkSummary({ isLast, isComplete, hasCollapsi
667
667
  // disclosure state to the default-open value on every new submission.
668
668
  return isComplete || !isLast;
669
669
  }
670
+ export function getAssistantWorkSummaryDurationMs(durationMs, groupStartIndex, firstWorkPartIndex) {
671
+ return groupStartIndex === firstWorkPartIndex ? durationMs : null;
672
+ }
670
673
  function ReasoningMessagePart() {
671
674
  const part = useMessagePartReasoning();
672
675
  const partRuntime = useMessagePartRuntime();
@@ -958,6 +961,11 @@ export function AssistantMessage() {
958
961
  msgContent.some((p, index) => !isCallAgentToolCallShadowed(msgContent, index) &&
959
962
  (p.type !== "tool-call" || p.activity !== true) &&
960
963
  isCollapsibleAssistantWorkPart(p));
964
+ const firstWorkPartIndex = Array.isArray(msgContent)
965
+ ? msgContent.findIndex((p, index) => !isCallAgentToolCallShadowed(msgContent, index) &&
966
+ (p.type !== "tool-call" || p.activity !== true) &&
967
+ isCollapsibleAssistantWorkPart(p))
968
+ : -1;
961
969
  const shadowedToolCallIds = Array.isArray(msgContent)
962
970
  ? shadowedCallAgentToolCallIds(msgContent)
963
971
  : new Set();
@@ -979,7 +987,7 @@ export function AssistantMessage() {
979
987
  });
980
988
  if (!showSummary)
981
989
  return _jsx(_Fragment, { children: children });
982
- return (_jsx(WorkedForSummary, { durationMs: capturedDurationMs ?? persistedDurationMs, defaultOpen: hasCustomUi, autoCollapse: animateCollapse && !hasCustomUi, children: children }));
990
+ return (_jsx(WorkedForSummary, { durationMs: getAssistantWorkSummaryDurationMs(capturedDurationMs ?? persistedDurationMs, part.indices[0] ?? -1, firstWorkPartIndex), defaultOpen: hasCustomUi, autoCollapse: animateCollapse && !hasCustomUi, children: children }));
983
991
  }
984
992
  case "group-ran-tools": {
985
993
  const toolCount = part.indices.filter((index) => msgContent?.[index]?.type === "tool-call").length;
@@ -24,9 +24,17 @@ export declare function ToolCallStackMotion({ children, className, }: {
24
24
  * provided, and "Always allow" only renders when `onAlwaysAllow` is provided
25
25
  * — both are additive so existing action-approval consumers are unaffected.
26
26
  */
27
+ export type ApprovalResolution = "approved" | "denied";
27
28
  export type ApprovalContextValue = {
28
29
  /** Re-issue the turn so the server runs the approved call. */
29
30
  onApprove: (approvalKey: string) => void;
31
+ /**
32
+ * Keep the visible resolution stable while the chat repository refreshes or
33
+ * remounts the message containing this approval card.
34
+ */
35
+ onApprovalResolved?: (approvalKey: string, resolution: ApprovalResolution, toolCallId?: string) => void;
36
+ /** Read a resolution retained by the owning chat surface. */
37
+ getApprovalResolution?: (approvalKey: string, toolCallId?: string) => ApprovalResolution | null;
30
38
  /**
31
39
  * Optional host hook invoked in addition to the local "denied" state, e.g.
32
40
  * so a Code session can also resolve its own pending approval as denied.