@agent-native/toolkit 0.198.4 → 0.198.6

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 (96) hide show
  1. package/dist/app/agentkit/react/components.d.ts +6 -3
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +98 -68
  4. package/dist/app/agentkit/react/components.js.map +1 -1
  5. package/dist/app/agentkit/react/styles.css +36 -24
  6. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  7. package/dist/app/chat/AgentKitAssistantChat.js +32 -15
  8. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  9. package/dist/app/chat/AgentPanel.d.ts +1 -1
  10. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  11. package/dist/app/chat/AgentPanel.js +11 -38
  12. package/dist/app/chat/AgentPanel.js.map +1 -1
  13. package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
  14. package/dist/app/chat/AgentSidebar.js +3 -11
  15. package/dist/app/chat/AgentSidebar.js.map +1 -1
  16. package/dist/app/chat/chat/tool-call-display.d.ts +2 -1
  17. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  18. package/dist/app/chat/chat/tool-call-display.js +83 -55
  19. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  20. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  21. package/dist/app/i18n/catalogs/ar-SA.js +1 -0
  22. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  23. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  24. package/dist/app/i18n/catalogs/de-DE.js +1 -0
  25. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  26. package/dist/app/i18n/catalogs/en-US.d.ts +1 -0
  27. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  28. package/dist/app/i18n/catalogs/en-US.js +1 -0
  29. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  30. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  31. package/dist/app/i18n/catalogs/es-ES.js +1 -0
  32. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  33. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  34. package/dist/app/i18n/catalogs/fr-FR.js +1 -0
  35. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  36. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  37. package/dist/app/i18n/catalogs/hi-IN.js +1 -0
  38. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  39. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  40. package/dist/app/i18n/catalogs/ja-JP.js +1 -0
  41. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  42. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  43. package/dist/app/i18n/catalogs/ko-KR.js +1 -0
  44. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  45. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  46. package/dist/app/i18n/catalogs/pt-BR.js +1 -0
  47. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  48. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  49. package/dist/app/i18n/catalogs/zh-CN.js +1 -0
  50. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  51. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  52. package/dist/app/i18n/catalogs/zh-TW.js +1 -0
  53. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  54. package/dist/composer/PromptComposer.d.ts +3 -0
  55. package/dist/composer/PromptComposer.d.ts.map +1 -1
  56. package/dist/composer/PromptComposer.js +2 -2
  57. package/dist/composer/PromptComposer.js.map +1 -1
  58. package/dist/composer/TiptapComposer.d.ts +4 -1
  59. package/dist/composer/TiptapComposer.d.ts.map +1 -1
  60. package/dist/composer/TiptapComposer.js +32 -25
  61. package/dist/composer/TiptapComposer.js.map +1 -1
  62. package/dist/ui/sonner.js +1 -1
  63. package/dist/ui/sonner.js.map +1 -1
  64. package/package.json +3 -3
  65. package/src/app/agentkit/react/components.interaction.spec.tsx +93 -0
  66. package/src/app/agentkit/react/components.spec.tsx +1 -2
  67. package/src/app/agentkit/react/components.tsx +185 -132
  68. package/src/app/agentkit/react/composer-context.spec.tsx +7 -0
  69. package/src/app/agentkit/react/lifecycle.spec.tsx +556 -39
  70. package/src/app/agentkit/react/styles.css +36 -24
  71. package/src/app/chat/AgentKitAssistantChat.spec.tsx +60 -1
  72. package/src/app/chat/AgentKitAssistantChat.tsx +45 -14
  73. package/src/app/chat/AgentPanel.overlay.spec.tsx +28 -4
  74. package/src/app/chat/AgentPanel.tsx +51 -168
  75. package/src/app/chat/AgentSidebar.spec.tsx +19 -19
  76. package/src/app/chat/AgentSidebar.tsx +59 -94
  77. package/src/app/chat/chat/tool-call-display.spec.tsx +371 -15
  78. package/src/app/chat/chat/tool-call-display.tsx +154 -80
  79. package/src/app/i18n/catalogs/ar-SA.ts +1 -0
  80. package/src/app/i18n/catalogs/de-DE.ts +1 -0
  81. package/src/app/i18n/catalogs/en-US.ts +1 -0
  82. package/src/app/i18n/catalogs/es-ES.ts +1 -0
  83. package/src/app/i18n/catalogs/fr-FR.ts +1 -0
  84. package/src/app/i18n/catalogs/hi-IN.ts +1 -0
  85. package/src/app/i18n/catalogs/ja-JP.ts +1 -0
  86. package/src/app/i18n/catalogs/ko-KR.ts +1 -0
  87. package/src/app/i18n/catalogs/pt-BR.ts +1 -0
  88. package/src/app/i18n/catalogs/zh-CN.ts +1 -0
  89. package/src/app/i18n/catalogs/zh-TW.ts +1 -0
  90. package/src/app-shell/sidebar-overflow.spec.tsx +2 -1
  91. package/src/composer/PromptComposer.context.spec.tsx +28 -15
  92. package/src/composer/PromptComposer.tsx +7 -0
  93. package/src/composer/TiptapComposer.spec.ts +40 -6
  94. package/src/composer/TiptapComposer.tsx +89 -89
  95. package/src/ui/sonner.spec.ts +15 -0
  96. package/src/ui/sonner.tsx +1 -1
@@ -763,7 +763,23 @@ function RepeatedActivityCluster({
763
763
  );
764
764
  }
765
765
 
766
+ function formatToolDiagnostic(value: unknown): string | undefined {
767
+ if (value === undefined) return undefined;
768
+ if (typeof value === "string") return value || undefined;
769
+ try {
770
+ return JSON.stringify(value, null, 2) ?? String(value);
771
+ } catch {
772
+ return String(value);
773
+ }
774
+ }
775
+
766
776
  function toolToActivity(tool: AgentToolCall): AgentActivity {
777
+ const failed = tool.status === "failed";
778
+ const errorMessage = failed
779
+ ? ((tool.error?.message?.trim() ? tool.error.message : undefined) ??
780
+ formatToolDiagnostic(tool.output) ??
781
+ formatToolDiagnostic(tool.error?.details))
782
+ : undefined;
767
783
  return {
768
784
  id: tool.id,
769
785
  kind: inferAgentActivityKind(tool.name),
@@ -774,12 +790,17 @@ function toolToActivity(tool: AgentToolCall): AgentActivity {
774
790
  : tool.status === "running"
775
791
  ? "running"
776
792
  : tool.status,
777
- detail:
778
- typeof tool.output === "string"
793
+ detail: failed
794
+ ? undefined
795
+ : typeof tool.output === "string"
779
796
  ? tool.output
780
797
  : typeof tool.input === "string"
781
798
  ? tool.input
782
799
  : undefined,
800
+ summary:
801
+ failed && errorMessage
802
+ ? [{ type: "text", text: errorMessage }]
803
+ : undefined,
783
804
  };
784
805
  }
785
806
 
@@ -904,11 +925,13 @@ export function AgentActivityGroup({
904
925
  afterSequence,
905
926
  throughSequence,
906
927
  excludeAgentActivities = false,
928
+ isCurrentSegment = true,
907
929
  }: {
908
930
  runId?: RunId;
909
931
  afterSequence?: number;
910
932
  throughSequence?: number;
911
933
  excludeAgentActivities?: boolean;
934
+ isCurrentSegment?: boolean;
912
935
  }) {
913
936
  const { threadId, slots, registry, labels } = useAgentKit();
914
937
  const thread = useAgentThread();
@@ -1011,7 +1034,7 @@ export function AgentActivityGroup({
1011
1034
  const activityItems = items.filter(
1012
1035
  (activity) => !durableToolResultIds.has(activity.id),
1013
1036
  );
1014
- const latestActivity = activityItems.reduce<AgentActivity | undefined>(
1037
+ const latestRunningActivity = items.reduce<AgentActivity | undefined>(
1015
1038
  (current, activity) => {
1016
1039
  if (activity.status !== "running") return current;
1017
1040
  if (!current) return activity;
@@ -1046,38 +1069,37 @@ export function AgentActivityGroup({
1046
1069
  Number.isFinite(startedAt) && Number.isFinite(completedAt)
1047
1070
  ? Math.max(0, completedAt - startedAt)
1048
1071
  : undefined;
1072
+ const hasTerminalRunEvent = runEvents.some(
1073
+ (event) =>
1074
+ event.runId === runId &&
1075
+ (event.type === "run.completed" ||
1076
+ event.type === "run.failed" ||
1077
+ event.type === "run.cancelled"),
1078
+ );
1049
1079
  const activelyWorking =
1050
- throughSequence === undefined &&
1051
- (run === undefined ? running : run.status === "running");
1052
- const currentActivity = activelyWorking ? latestActivity : undefined;
1053
- const visiblyRunning =
1054
- throughSequence === undefined && activelyWorking && running;
1055
- const [elapsedAt, setElapsedAt] = useState<number>();
1056
- useEffect(() => {
1057
- if (!activelyWorking || !Number.isFinite(startedAt)) return;
1058
- const update = () => setElapsedAt(Date.now());
1059
- update();
1060
- const interval = globalThis.setInterval(update, 1_000);
1061
- return () => globalThis.clearInterval(interval);
1062
- }, [activelyWorking, startedAt]);
1063
- const activeDurationMs =
1064
- activelyWorking && elapsedAt !== undefined && Number.isFinite(startedAt)
1065
- ? Math.max(0, elapsedAt - startedAt)
1066
- : undefined;
1080
+ isCurrentSegment &&
1081
+ (run ? run.status === "running" : running && !hasTerminalRunEvent);
1067
1082
  const completedRunSummary =
1068
- throughSequence !== undefined ||
1069
- (items.length > 0 && !running) ||
1070
- runEvents.some(
1071
- (event) =>
1072
- event.runId === runId &&
1073
- (event.type === "run.completed" ||
1074
- event.type === "run.failed" ||
1075
- event.type === "run.cancelled"),
1076
- ) ||
1077
- (afterSequence === undefined &&
1078
- run !== undefined &&
1079
- ["completed", "failed", "cancelled"].includes(run.status));
1080
- if (items.length === 0) return null;
1083
+ !activelyWorking &&
1084
+ (throughSequence !== undefined ||
1085
+ (items.length > 0 && !running) ||
1086
+ hasTerminalRunEvent ||
1087
+ (afterSequence === undefined &&
1088
+ run !== undefined &&
1089
+ ["completed", "failed", "cancelled"].includes(run.status)));
1090
+ if (items.length === 0 && !activelyWorking) return null;
1091
+ const currentActivity = activelyWorking
1092
+ ? (latestRunningActivity ?? {
1093
+ id: `thinking:${runId ?? thread.id}`,
1094
+ kind: "model",
1095
+ label: labels.reasoning,
1096
+ status: "running",
1097
+ })
1098
+ : undefined;
1099
+ const currentActivityLabel =
1100
+ currentActivity?.label === "Contacting model"
1101
+ ? labels.reasoning
1102
+ : currentActivity?.label;
1081
1103
  const displayGroups = new Map<ActivityBucket, AgentActivity[][]>(
1082
1104
  ACTIVITY_BUCKET_ORDER.map((bucket) => [bucket, []]),
1083
1105
  );
@@ -1114,7 +1136,9 @@ export function AgentActivityGroup({
1114
1136
  }
1115
1137
  }
1116
1138
  const hasExpandableActivity = ACTIVITY_BUCKET_ORDER.some(
1117
- (bucket) => displayGroups.get(bucket)!.length > 0,
1139
+ (bucket) =>
1140
+ (!completedRunSummary || bucket !== "thinking") &&
1141
+ displayGroups.get(bucket)!.length > 0,
1118
1142
  );
1119
1143
  const activityBucketLabels = {
1120
1144
  thinking: "Thinking",
@@ -1130,34 +1154,15 @@ export function AgentActivityGroup({
1130
1154
  second: labels.durationSecondShort,
1131
1155
  });
1132
1156
  const summaryLabel = activelyWorking
1133
- ? activeDurationMs !== undefined && activeDurationMs >= 1_000
1134
- ? labels.workingFor.replace(
1135
- "{{duration}}",
1136
- formatDuration(activeDurationMs),
1137
- )
1138
- : labels.working
1157
+ ? ""
1139
1158
  : completedRunSummary
1140
1159
  ? durationMs !== undefined && durationMs >= 1_000
1141
1160
  ? labels.workedFor.replace("{{duration}}", formatDuration(durationMs))
1142
1161
  : labels.worked
1143
1162
  : labels.activities;
1163
+ const visiblyRunning = activelyWorking;
1144
1164
  return (
1145
1165
  <>
1146
- {items
1147
- .filter((activity) => activity.status === "failed")
1148
- .map((activity) => (
1149
- <div key={activity.id} className="agentkit-run-failure" role="alert">
1150
- <IconAlertCircle aria-hidden="true" className="agentkit-icon" />
1151
- <div className="agentkit-run-failure-copy">
1152
- <strong>{activity.label}</strong>
1153
- <span>
1154
- {toolMap.get(activity.id)?.error?.message ??
1155
- activity.detail ??
1156
- labels.error}
1157
- </span>
1158
- </div>
1159
- </div>
1160
- ))}
1161
1166
  {durableToolResults.length ? (
1162
1167
  <div data-agentkit-tool-results="true">
1163
1168
  {durableToolResults.map(({ tool, Renderer }) => (
@@ -1165,10 +1170,11 @@ export function AgentActivityGroup({
1165
1170
  ))}
1166
1171
  </div>
1167
1172
  ) : null}
1168
- {hasExpandableActivity ? (
1173
+ {hasExpandableActivity || activelyWorking ? (
1169
1174
  <AgentWorkDisclosure
1170
1175
  label={summaryLabel}
1171
1176
  running={visiblyRunning}
1177
+ expandable={hasExpandableActivity}
1172
1178
  current={
1173
1179
  currentActivity ? (
1174
1180
  <span
@@ -1182,8 +1188,8 @@ export function AgentActivityGroup({
1182
1188
  threadId={threadId}
1183
1189
  compact
1184
1190
  />
1185
- <span className="agentkit-activities-current-label">
1186
- {currentActivity.label}
1191
+ <span className="agentkit-activities-current-label agent-running-shimmer">
1192
+ {currentActivityLabel}
1187
1193
  </span>
1188
1194
  </span>
1189
1195
  ) : null
@@ -1191,6 +1197,7 @@ export function AgentActivityGroup({
1191
1197
  >
1192
1198
  <div className="agentkit-activities-list">
1193
1199
  {ACTIVITY_BUCKET_ORDER.map((bucket) => {
1200
+ if (completedRunSummary && bucket === "thinking") return null;
1194
1201
  const groups = displayGroups.get(bucket)!;
1195
1202
  if (groups.length === 0) return null;
1196
1203
  return (
@@ -1262,7 +1269,9 @@ export function AgentActivityGroup({
1262
1269
  </AgentWorkDisclosure>
1263
1270
  ) : (
1264
1271
  <div className="agentkit-activities-static">
1265
- <span className="agentkit-activities-label">{summaryLabel}</span>
1272
+ {summaryLabel ? (
1273
+ <span className="agentkit-activities-label">{summaryLabel}</span>
1274
+ ) : null}
1266
1275
  {currentActivity ? (
1267
1276
  <span
1268
1277
  className="agentkit-activities-current"
@@ -1275,8 +1284,8 @@ export function AgentActivityGroup({
1275
1284
  threadId={threadId}
1276
1285
  compact
1277
1286
  />
1278
- <span className="agentkit-activities-current-label">
1279
- {currentActivity.label}
1287
+ <span className="agentkit-activities-current-label agent-running-shimmer">
1288
+ {currentActivityLabel}
1280
1289
  </span>
1281
1290
  </span>
1282
1291
  ) : null}
@@ -1289,11 +1298,13 @@ export function AgentActivityGroup({
1289
1298
  function AgentWorkDisclosure({
1290
1299
  label,
1291
1300
  running,
1301
+ expandable = true,
1292
1302
  current,
1293
1303
  children,
1294
1304
  }: {
1295
1305
  label: string;
1296
1306
  running?: boolean;
1307
+ expandable?: boolean;
1297
1308
  current?: ReactNode;
1298
1309
  children: ReactNode;
1299
1310
  }) {
@@ -1302,23 +1313,30 @@ function AgentWorkDisclosure({
1302
1313
  <details
1303
1314
  className="agentkit-activities"
1304
1315
  data-running={running ? "true" : undefined}
1316
+ data-expandable={expandable ? "true" : "false"}
1305
1317
  open={open}
1306
1318
  >
1307
1319
  <summary
1308
1320
  className="agentkit-activities-summary"
1321
+ aria-disabled={!expandable}
1309
1322
  onClick={(event) => {
1310
1323
  event.preventDefault();
1324
+ if (!expandable) return;
1311
1325
  setOpen((value) => !value);
1312
1326
  }}
1313
1327
  >
1314
- <span className="agentkit-activities-heading">
1315
- <span className="agentkit-activities-label">{label}</span>
1316
- <IconChevronRight
1317
- aria-hidden="true"
1318
- className="agentkit-icon agentkit-summary-chevron"
1319
- />
1328
+ <span className="agentkit-activities-summary-content">
1329
+ {label ? (
1330
+ <span className="agentkit-activities-heading">
1331
+ <span className="agentkit-activities-label">{label}</span>
1332
+ </span>
1333
+ ) : null}
1334
+ {current}
1320
1335
  </span>
1321
- {current}
1336
+ <IconChevronRight
1337
+ aria-hidden="true"
1338
+ className="agentkit-icon agentkit-summary-chevron"
1339
+ />
1322
1340
  </summary>
1323
1341
  {children}
1324
1342
  </details>
@@ -1359,9 +1377,7 @@ const ACTIVITY_BUCKET_ORDER: ActivityBucket[] = [
1359
1377
  ];
1360
1378
 
1361
1379
  function activityBucketForKind(kind: string): ActivityBucket {
1362
- if (kind === "reasoning" || kind === "model" || kind === "status") {
1363
- return "thinking";
1364
- }
1380
+ if (kind === "reasoning") return "thinking";
1365
1381
  if (kind === "search" || kind === "read") return "research";
1366
1382
  if (
1367
1383
  kind === "write" ||
@@ -2451,6 +2467,9 @@ export function AgentMessageActions({
2451
2467
  () => resolveAgentMessageRequestId(message, thread.events),
2452
2468
  [message, thread.events],
2453
2469
  );
2470
+ const hasMessageMenuActions = Boolean(
2471
+ requestId || (forkingCapability.visible && onThreadForked),
2472
+ );
2454
2473
  const runId = useMemo(
2455
2474
  () => resolveAgentMessageRunId(message, thread.events),
2456
2475
  [message, thread.events],
@@ -2569,7 +2588,11 @@ export function AgentMessageActions({
2569
2588
  forkAction.error ??
2570
2589
  regenerateAction.error;
2571
2590
  return (
2572
- <div className="agentkit-message-actions" data-role={message.role}>
2591
+ <div
2592
+ className="agentkit-message-actions"
2593
+ data-role={message.role}
2594
+ data-streaming={message.status === "streaming" ? "true" : undefined}
2595
+ >
2573
2596
  {message.role === "assistant" ? (
2574
2597
  <>
2575
2598
  <div className="agentkit-message-actions-leading">
@@ -2752,63 +2775,67 @@ export function AgentMessageActions({
2752
2775
  threadId={threadId}
2753
2776
  />
2754
2777
  ) : null}
2755
- <Menu
2756
- open={actionsMenuOpen}
2757
- onOpenChange={(open) => {
2758
- setActionsMenuOpen(open);
2759
- if (!open) setRequestIdCopied(false);
2760
- }}
2761
- placement="bottom"
2762
- align="end"
2763
- className="agentkit-message-menu w-48"
2764
- trigger={
2765
- <IconButton
2766
- label={labels.messageActions}
2767
- icon={<IconDotsVertical aria-hidden="true" />}
2768
- size="compact"
2769
- aria-expanded={actionsMenuOpen}
2770
- title={labels.messageActions}
2771
- />
2772
- }
2773
- items={[
2774
- {
2775
- id: "copy-request-id",
2776
- label: requestIdCopied
2777
- ? labels.copied
2778
- : requestId
2779
- ? labels.copyRequestId
2780
- : labels.requestIdUnavailable,
2781
- icon: requestIdCopied ? (
2782
- <IconCircleCheck size={14} aria-hidden="true" />
2783
- ) : (
2784
- <IconId size={14} aria-hidden="true" />
2785
- ),
2786
- disabled: !requestId || requestIdAction.pending,
2787
- },
2788
- ...(forkingCapability.visible && onThreadForked
2789
- ? [
2790
- {
2791
- id: "fork-chat",
2792
- label: (
2793
- <span title={forkingCapability.reason}>
2794
- {labels.fork}
2795
- </span>
2796
- ),
2797
- icon: <IconGitBranch size={14} aria-hidden="true" />,
2798
- disabled:
2799
- !forkingCapability.enabled || forkAction.pending,
2800
- },
2801
- ]
2802
- : []),
2803
- ]}
2804
- onAction={(id) => {
2805
- if (id === "copy-request-id") {
2806
- void requestIdAction.execute().catch(() => undefined);
2807
- } else if (id === "fork-chat") {
2808
- void forkAction.execute().catch(() => undefined);
2778
+ {hasMessageMenuActions ? (
2779
+ <Menu
2780
+ open={actionsMenuOpen}
2781
+ onOpenChange={(open) => {
2782
+ setActionsMenuOpen(open);
2783
+ if (!open) setRequestIdCopied(false);
2784
+ }}
2785
+ placement="bottom"
2786
+ align="end"
2787
+ className="agentkit-message-menu w-48"
2788
+ trigger={
2789
+ <IconButton
2790
+ label={labels.messageActions}
2791
+ icon={<IconDotsVertical aria-hidden="true" />}
2792
+ size="compact"
2793
+ aria-expanded={actionsMenuOpen}
2794
+ title={labels.messageActions}
2795
+ />
2809
2796
  }
2810
- }}
2811
- />
2797
+ items={[
2798
+ ...(requestId
2799
+ ? [
2800
+ {
2801
+ id: "copy-request-id",
2802
+ label: requestIdCopied
2803
+ ? labels.copied
2804
+ : labels.copyRequestId,
2805
+ icon: requestIdCopied ? (
2806
+ <IconCircleCheck size={14} aria-hidden="true" />
2807
+ ) : (
2808
+ <IconId size={14} aria-hidden="true" />
2809
+ ),
2810
+ disabled: requestIdAction.pending,
2811
+ },
2812
+ ]
2813
+ : []),
2814
+ ...(forkingCapability.visible && onThreadForked
2815
+ ? [
2816
+ {
2817
+ id: "fork-chat",
2818
+ label: (
2819
+ <span title={forkingCapability.reason}>
2820
+ {labels.fork}
2821
+ </span>
2822
+ ),
2823
+ icon: <IconGitBranch size={14} aria-hidden="true" />,
2824
+ disabled:
2825
+ !forkingCapability.enabled || forkAction.pending,
2826
+ },
2827
+ ]
2828
+ : []),
2829
+ ]}
2830
+ onAction={(id) => {
2831
+ if (id === "copy-request-id") {
2832
+ void requestIdAction.execute().catch(() => undefined);
2833
+ } else if (id === "fork-chat") {
2834
+ void forkAction.execute().catch(() => undefined);
2835
+ }
2836
+ }}
2837
+ />
2838
+ ) : null}
2812
2839
  </div>
2813
2840
  </>
2814
2841
  ) : (
@@ -3054,6 +3081,7 @@ export interface AgentKitComposerProps extends Omit<
3054
3081
  | "extraActionButton"
3055
3082
  | "onSubmit"
3056
3083
  | "onBeforeSubmit"
3084
+ | "onSubmissionPendingChange"
3057
3085
  | "onAttachmentError"
3058
3086
  | "interceptBuildRequestsForBuilder"
3059
3087
  | "planModeDisabled"
@@ -3068,6 +3096,8 @@ export interface AgentKitComposerProps extends Omit<
3068
3096
  composerRef?: { current: TiptapComposerHandle | null };
3069
3097
  threadId?: string;
3070
3098
  className?: string;
3099
+ /** The host transcript renders its own accessible submission status. */
3100
+ announcePendingSubmission?: boolean;
3071
3101
  queueWhileRunning?: boolean;
3072
3102
  showModelSelector?: boolean;
3073
3103
  /** Controlled execution mode for agent-native act/plan workflows. */
@@ -3087,6 +3117,7 @@ function hasActiveRuns(thread: AgentThreadState): boolean {
3087
3117
  export function AgentKitComposer({
3088
3118
  threadId: requestedThreadId,
3089
3119
  className,
3120
+ announcePendingSubmission = true,
3090
3121
  queueWhileRunning = true,
3091
3122
  showModelSelector = true,
3092
3123
  slashCommands,
@@ -3135,6 +3166,7 @@ export function AgentKitComposer({
3135
3166
  extraActionButton,
3136
3167
  onSubmit: onSubmitOverride,
3137
3168
  onBeforeSubmit,
3169
+ onSubmissionPendingChange,
3138
3170
  onAttachmentError,
3139
3171
  interceptBuildRequestsForBuilder,
3140
3172
  planModeDisabled,
@@ -3191,6 +3223,15 @@ export function AgentKitComposer({
3191
3223
  threadId,
3192
3224
  );
3193
3225
  const submissionBlocked = Boolean(submissionDisabled) || command.pending;
3226
+ const [submissionPending, setSubmissionPending] = useState(false);
3227
+ const handleSubmissionPendingChange = useCallback(
3228
+ (pending: boolean) => {
3229
+ setSubmissionPending(pending);
3230
+ onSubmissionPendingChange?.(pending);
3231
+ },
3232
+ [onSubmissionPendingChange],
3233
+ );
3234
+ const suggestionSubmitBlocked = submissionBlocked || submissionPending;
3194
3235
  const localComposerRef = useRef<TiptapComposerHandle>(null);
3195
3236
  const composerRef = hostComposerRef ?? localComposerRef;
3196
3237
  const selectedSuggestionRef = useRef<
@@ -3513,7 +3554,7 @@ export function AgentKitComposer({
3513
3554
  ) => {
3514
3555
  if (
3515
3556
  disabled ||
3516
- submissionBlocked ||
3557
+ suggestionSubmitBlocked ||
3517
3558
  !suggestionsCapability.enabled ||
3518
3559
  selectedSuggestionRef.current ||
3519
3560
  !isCurrentAgentSuggestion(controller.getThread(threadId), suggestion)
@@ -3536,6 +3577,15 @@ export function AgentKitComposer({
3536
3577
  const suggestions = selectAgentSuggestions(thread);
3537
3578
  return (
3538
3579
  <div className={`agentkit-composer-stack ${className ?? ""}`}>
3580
+ {announcePendingSubmission && submissionPending ? (
3581
+ <span
3582
+ className="agentkit-visually-hidden"
3583
+ role="status"
3584
+ aria-live="polite"
3585
+ >
3586
+ {labels.activityBuckets?.thinking ?? "Thinking"}
3587
+ </span>
3588
+ ) : null}
3539
3589
  {queueCapability.visible ? (
3540
3590
  Queue ? (
3541
3591
  <Queue
@@ -3611,14 +3661,14 @@ export function AgentKitComposer({
3611
3661
  <Suggestions
3612
3662
  suggestions={suggestions}
3613
3663
  threadId={threadId}
3614
- pending={submissionBlocked}
3664
+ pending={suggestionSubmitBlocked}
3615
3665
  onSelect={selectSuggestion}
3616
3666
  />
3617
3667
  ) : (
3618
3668
  <AgentSuggestionBar
3619
3669
  suggestions={suggestions.map((suggestion) => ({
3620
3670
  ...suggestion,
3621
- disabled: submissionBlocked || Boolean(disabled),
3671
+ disabled: suggestionSubmitBlocked || Boolean(disabled),
3622
3672
  }))}
3623
3673
  ariaLabel={labels.suggestions}
3624
3674
  onSelect={selectSuggestion}
@@ -3646,7 +3696,6 @@ export function AgentKitComposer({
3646
3696
  imageModelMenu={imageModelMenu}
3647
3697
  autoFocus={autoFocus}
3648
3698
  composerRef={composerRef}
3649
- submitting={command.pending}
3650
3699
  willQueue={active && queueWhileRunning && canQueue}
3651
3700
  showModelSelector={showModelSelector && canSelectModel}
3652
3701
  availableModels={availableModels}
@@ -3669,8 +3718,11 @@ export function AgentKitComposer({
3669
3718
  initialTextKey={composerInitialTextKey}
3670
3719
  onTextChange={onTextChange}
3671
3720
  extraActionButton={extraActionButton}
3721
+ sendButtonDisabled={submissionPending}
3672
3722
  onBeforeSubmit={onBeforeSubmit}
3723
+ onSubmissionPendingChange={handleSubmissionPendingChange}
3673
3724
  getSubmitFailureDraftScope={getSubmitFailureDraftScope}
3725
+ clearOnSubmitImmediately
3674
3726
  onAttachmentError={reportAttachmentError}
3675
3727
  interceptBuildRequestsForBuilder={interceptBuildRequestsForBuilder}
3676
3728
  planModeDisabled={planModeDisabled}
@@ -4190,6 +4242,7 @@ export function AgentKitChat({
4190
4242
  afterSequence={afterSequence}
4191
4243
  throughSequence={throughSequence}
4192
4244
  excludeAgentActivities
4245
+ isCurrentSegment={throughSequence === undefined}
4193
4246
  />
4194
4247
  </AgentKitSurfaceBoundary>
4195
4248
  );
@@ -317,6 +317,7 @@ describe("AgentKit composer context submission", () => {
317
317
  expect(capture.props).toMatchObject({
318
318
  contextItems: source,
319
319
  contextMenuItems,
320
+ clearOnSubmitImmediately: true,
320
321
  onRemoveContextItem,
321
322
  onInspectContextItem,
322
323
  onRetryContextItem,
@@ -332,6 +333,7 @@ describe("AgentKit composer context submission", () => {
332
333
  ];
333
334
  let pending: void | Promise<void>;
334
335
  await act(async () => {
336
+ capture.props!.onSubmissionPendingChange?.(true);
335
337
  pending = capture.props!.onSubmit("Review", [], references, {
336
338
  contextItems: source,
337
339
  composerModeContext: "Use scheduling tools for this request.",
@@ -341,6 +343,9 @@ describe("AgentKit composer context submission", () => {
341
343
  expect(beforeSend).toHaveBeenCalledOnce();
342
344
  expect(runtime.startRun).not.toHaveBeenCalled();
343
345
  expect(runtime.queueMessage).not.toHaveBeenCalled();
346
+ expect(container.querySelector('[role="status"]')?.textContent).toBe(
347
+ "Thinking",
348
+ );
344
349
  expect(Object.isFrozen(saved)).toBe(true);
345
350
  expect(Object.isFrozen(saved!.contextItems![0])).toBe(true);
346
351
  expect(Object.isFrozen(saved!.references[0].metadata)).toBe(true);
@@ -349,7 +354,9 @@ describe("AgentKit composer context submission", () => {
349
354
  await act(async () => {
350
355
  release();
351
356
  await pending;
357
+ capture.props!.onSubmissionPendingChange?.(false);
352
358
  });
359
+ expect(container.querySelector('[role="status"]')).toBeNull();
353
360
  const input =
354
361
  intent === "queued"
355
362
  ? runtime.queueMessage.mock.calls[0][0]