@agent-native/core 0.84.12 → 0.84.14

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 (112) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/durable-background.ts +42 -5
  5. package/corpus/core/src/agent/production-agent.ts +72 -10
  6. package/corpus/core/src/agent/run-manager.ts +84 -4
  7. package/corpus/core/src/agent/run-store.ts +55 -11
  8. package/corpus/core/src/chat-threads/store.ts +2 -0
  9. package/corpus/core/src/cli/skills.ts +10 -6
  10. package/corpus/core/src/client/AssistantChat.tsx +40 -9
  11. package/corpus/core/src/client/agent-chat-adapter.ts +44 -5
  12. package/corpus/core/src/client/blocks/library/diagram.tsx +3 -3
  13. package/corpus/core/src/client/chat/message-components.tsx +17 -0
  14. package/corpus/core/src/client/chat/run-recovery.tsx +52 -44
  15. package/corpus/core/src/client/chat/tool-call-display.tsx +7 -2
  16. package/corpus/core/src/observability/traces.ts +17 -3
  17. package/corpus/core/src/server/agent-chat-plugin.ts +12 -1
  18. package/corpus/core/src/shared/streaming-text-smoothing.ts +10 -5
  19. package/corpus/core/src/styles/blocks.css +44 -1
  20. package/corpus/templates/clips/app/components/library/recording-card.tsx +14 -4
  21. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +10 -12
  22. package/corpus/templates/clips/app/hooks/use-library.ts +7 -4
  23. package/corpus/templates/clips/app/lib/recording-status.ts +32 -0
  24. package/corpus/templates/clips/changelog/2026-07-01-chrome-extension-recordings-stream-uploads-while-recording.md +6 -0
  25. package/corpus/templates/clips/changelog/2026-07-01-dictation-keeps-listening-through-natural-pauses-and-gives-c.md +6 -0
  26. package/corpus/templates/clips/changelog/2026-07-01-redoing-a-paused-desktop-recording-no-longer-leaves-the-recording-controls-disabled.md +6 -0
  27. package/corpus/templates/clips/changelog/2026-07-01-stalled-clip-uploads-now-update-in-the-library-and-surface-as-failed.md +6 -0
  28. package/corpus/templates/clips/chrome-extension/src/background.ts +8 -0
  29. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +61 -16
  30. package/corpus/templates/clips/desktop/src/lib/recorder.ts +36 -18
  31. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +99 -27
  32. package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +4 -43
  33. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +3 -0
  34. package/corpus/templates/clips/desktop/src/styles.css +5 -48
  35. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +4 -5
  36. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +15 -6
  37. package/corpus/templates/design/AGENTS.md +8 -4
  38. package/corpus/templates/design/actions/create-design.ts +1 -0
  39. package/corpus/templates/design/actions/duplicate-design.ts +3 -1
  40. package/corpus/templates/design/actions/edit-design.ts +43 -13
  41. package/corpus/templates/design/actions/generate-design.ts +5 -1
  42. package/corpus/templates/design/actions/present-design-variants.ts +17 -12
  43. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  44. package/corpus/templates/design/app/pages/DesignEditor.tsx +2 -2
  45. package/corpus/templates/design/changelog/2026-07-01-org-members-can-see-design-projects-created-in-their-workspace.md +6 -0
  46. package/corpus/templates/design/server/plugins/db.ts +9 -0
  47. package/corpus/templates/plan/app/components/plan/wireframe/html-artboard.css +44 -1
  48. package/corpus/templates/plan/changelog/2026-07-01-visual-plan-and-recap-diagram-labels-now-wrap-inside-their-b.md +6 -0
  49. package/dist/agent/durable-background.d.ts +3 -0
  50. package/dist/agent/durable-background.d.ts.map +1 -1
  51. package/dist/agent/durable-background.js +33 -3
  52. package/dist/agent/durable-background.js.map +1 -1
  53. package/dist/agent/production-agent.d.ts +1 -0
  54. package/dist/agent/production-agent.d.ts.map +1 -1
  55. package/dist/agent/production-agent.js +55 -9
  56. package/dist/agent/production-agent.js.map +1 -1
  57. package/dist/agent/run-manager.d.ts +2 -0
  58. package/dist/agent/run-manager.d.ts.map +1 -1
  59. package/dist/agent/run-manager.js +68 -5
  60. package/dist/agent/run-manager.js.map +1 -1
  61. package/dist/agent/run-store.d.ts +9 -0
  62. package/dist/agent/run-store.d.ts.map +1 -1
  63. package/dist/agent/run-store.js +47 -11
  64. package/dist/agent/run-store.js.map +1 -1
  65. package/dist/chat-threads/store.d.ts +1 -0
  66. package/dist/chat-threads/store.d.ts.map +1 -1
  67. package/dist/chat-threads/store.js +2 -0
  68. package/dist/chat-threads/store.js.map +1 -1
  69. package/dist/cli/skills.d.ts.map +1 -1
  70. package/dist/cli/skills.js +10 -6
  71. package/dist/cli/skills.js.map +1 -1
  72. package/dist/client/AssistantChat.d.ts +10 -0
  73. package/dist/client/AssistantChat.d.ts.map +1 -1
  74. package/dist/client/AssistantChat.js +26 -8
  75. package/dist/client/AssistantChat.js.map +1 -1
  76. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  77. package/dist/client/agent-chat-adapter.js +40 -5
  78. package/dist/client/agent-chat-adapter.js.map +1 -1
  79. package/dist/client/blocks/library/diagram.js +3 -3
  80. package/dist/client/blocks/library/diagram.js.map +1 -1
  81. package/dist/client/chat/message-components.d.ts +3 -1
  82. package/dist/client/chat/message-components.d.ts.map +1 -1
  83. package/dist/client/chat/message-components.js +16 -2
  84. package/dist/client/chat/message-components.js.map +1 -1
  85. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  86. package/dist/client/chat/run-recovery.js +15 -10
  87. package/dist/client/chat/run-recovery.js.map +1 -1
  88. package/dist/client/chat/tool-call-display.d.ts +1 -0
  89. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  90. package/dist/client/chat/tool-call-display.js +3 -2
  91. package/dist/client/chat/tool-call-display.js.map +1 -1
  92. package/dist/collab/awareness.d.ts +2 -2
  93. package/dist/collab/awareness.d.ts.map +1 -1
  94. package/dist/collab/routes.d.ts +1 -1
  95. package/dist/notifications/routes.d.ts +3 -3
  96. package/dist/observability/routes.d.ts +6 -6
  97. package/dist/observability/traces.d.ts +5 -0
  98. package/dist/observability/traces.d.ts.map +1 -1
  99. package/dist/observability/traces.js +9 -3
  100. package/dist/observability/traces.js.map +1 -1
  101. package/dist/resources/handlers.d.ts +2 -2
  102. package/dist/server/agent-chat-plugin.d.ts +2 -1
  103. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  104. package/dist/server/agent-chat-plugin.js +8 -3
  105. package/dist/server/agent-chat-plugin.js.map +1 -1
  106. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  107. package/dist/server/transcribe-voice.d.ts +1 -1
  108. package/dist/shared/streaming-text-smoothing.d.ts.map +1 -1
  109. package/dist/shared/streaming-text-smoothing.js +8 -5
  110. package/dist/shared/streaming-text-smoothing.js.map +1 -1
  111. package/dist/styles/blocks.css +44 -1
  112. package/package.json +1 -1
@@ -116,6 +116,7 @@ async function ensureRunTables(): Promise<void> {
116
116
  turn_id TEXT,
117
117
  error_code TEXT,
118
118
  error_detail TEXT,
119
+ terminal_reason TEXT,
119
120
  dispatch_mode TEXT,
120
121
  diag_stage TEXT
121
122
  )
@@ -187,6 +188,7 @@ async function ensureRunTables(): Promise<void> {
187
188
  ["turn_id", "TEXT"],
188
189
  ["error_code", "TEXT"],
189
190
  ["error_detail", "TEXT"],
191
+ ["terminal_reason", "TEXT"],
190
192
  ["dispatch_mode", "TEXT"],
191
193
  ["diag_stage", "TEXT"],
192
194
  ["worker_stage", "TEXT"],
@@ -264,6 +266,7 @@ async function ensureRunTables(): Promise<void> {
264
266
  "turn_id",
265
267
  "error_code",
266
268
  "error_detail",
269
+ "terminal_reason",
267
270
  "dispatch_mode",
268
271
  "diag_stage",
269
272
  "worker_stage",
@@ -621,6 +624,28 @@ export async function setRunError(
621
624
  }
622
625
  }
623
626
 
627
+ /**
628
+ * Record why a run reached its terminal status. Unlike error_code/error_detail,
629
+ * this is set for successful checkpoint boundaries too (for example
630
+ * status='completed' + terminal_reason='run_timeout').
631
+ */
632
+ export async function setRunTerminalReason(
633
+ runId: string,
634
+ terminalReason: string | undefined,
635
+ ): Promise<void> {
636
+ if (!terminalReason) return;
637
+ try {
638
+ await ensureRunTables();
639
+ const client = getDbExec();
640
+ await client.execute({
641
+ sql: `UPDATE agent_runs SET terminal_reason = ? WHERE id = ?`,
642
+ args: [terminalReason.slice(0, 200), runId],
643
+ });
644
+ } catch {
645
+ // Diagnostics are best-effort; never let them break completion.
646
+ }
647
+ }
648
+
624
649
  /**
625
650
  * Diagnostic stage names recorded onto a background run as it moves through the
626
651
  * `_process-run` worker pipeline. Each value is the LAST stage successfully
@@ -765,7 +790,8 @@ export async function reapIfStale(
765
790
  SET status = 'errored',
766
791
  completed_at = ?,
767
792
  error_code = ?,
768
- error_detail = ?
793
+ error_detail = ?,
794
+ terminal_reason = ?
769
795
  WHERE id = ?
770
796
  AND status = 'running'
771
797
  AND ${staleClause}`,
@@ -773,6 +799,7 @@ export async function reapIfStale(
773
799
  completedAt,
774
800
  STALE_RUN_ERROR_EVENT.errorCode,
775
801
  STALE_RUN_ERROR_EVENT.details,
802
+ STALE_RUN_ERROR_EVENT.errorCode,
776
803
  runId,
777
804
  ...staleArgs,
778
805
  ],
@@ -821,7 +848,8 @@ export async function reapUnclaimedBackgroundRun(
821
848
  SET status = 'errored',
822
849
  completed_at = ?,
823
850
  error_code = ?,
824
- error_detail = ?
851
+ error_detail = ?,
852
+ terminal_reason = ?
825
853
  WHERE id = ?
826
854
  AND status = 'running'
827
855
  AND dispatch_mode = 'background'
@@ -830,6 +858,7 @@ export async function reapUnclaimedBackgroundRun(
830
858
  completedAt,
831
859
  UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.errorCode,
832
860
  UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.details,
861
+ UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.errorCode,
833
862
  runId,
834
863
  cutoff,
835
864
  ],
@@ -902,8 +931,8 @@ export async function markRunAborted(
902
931
  await ensureRunTables();
903
932
  const client = getDbExec();
904
933
  await client.execute({
905
- sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ? WHERE id = ?`,
906
- args: [reason ?? "user", Date.now(), runId],
934
+ sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ?, terminal_reason = ? WHERE id = ?`,
935
+ args: [reason ?? "user", Date.now(), `aborted:${reason ?? "user"}`, runId],
907
936
  });
908
937
  await safeAppendTerminalRunEvent(runId, { type: "done" }, "mark-aborted");
909
938
  }
@@ -1005,13 +1034,14 @@ export async function getRunByThread(
1005
1034
  completedAt: number | null;
1006
1035
  lastProgressAt: number | null;
1007
1036
  dispatchMode: string | null;
1037
+ terminalReason: string | null;
1008
1038
  diagStage: string | null;
1009
1039
  } | null> {
1010
1040
  await ensureRunTables();
1011
1041
  const client = getDbExec();
1012
1042
  const sql = options?.includeTerminal
1013
- ? `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, diag_stage FROM agent_runs WHERE thread_id = ? ORDER BY started_at DESC LIMIT 1`
1014
- : `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, diag_stage FROM agent_runs WHERE thread_id = ? AND status = 'running' ORDER BY started_at DESC LIMIT 1`;
1043
+ ? `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, terminal_reason, diag_stage FROM agent_runs WHERE thread_id = ? ORDER BY started_at DESC LIMIT 1`
1044
+ : `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, dispatch_mode, terminal_reason, diag_stage FROM agent_runs WHERE thread_id = ? AND status = 'running' ORDER BY started_at DESC LIMIT 1`;
1015
1045
  const { rows } = await client.execute({ sql, args: [threadId] });
1016
1046
  if (rows.length === 0) return null;
1017
1047
  const r = rows[0] as {
@@ -1024,6 +1054,7 @@ export async function getRunByThread(
1024
1054
  completed_at: number | string | null;
1025
1055
  last_progress_at: number | string | null;
1026
1056
  dispatch_mode?: string | null;
1057
+ terminal_reason?: string | null;
1027
1058
  diag_stage?: string | null;
1028
1059
  };
1029
1060
  return {
@@ -1037,6 +1068,7 @@ export async function getRunByThread(
1037
1068
  lastProgressAt:
1038
1069
  r.last_progress_at == null ? null : Number(r.last_progress_at),
1039
1070
  dispatchMode: r.dispatch_mode ?? null,
1071
+ terminalReason: r.terminal_reason ?? null,
1040
1072
  diagStage: r.diag_stage ?? null,
1041
1073
  };
1042
1074
  }
@@ -1053,6 +1085,7 @@ export interface AgentRunSummary {
1053
1085
  errorCode: string | null;
1054
1086
  abortReason: string | null;
1055
1087
  dispatchMode: string | null;
1088
+ terminalReason: string | null;
1056
1089
  /** Last reached `_process-run` worker stage (JSON `{stage,detail?,at}`). */
1057
1090
  diagStage: string | null;
1058
1091
  }
@@ -1065,7 +1098,7 @@ export async function listRunsForThread(
1065
1098
  const limit = Math.min(Math.max(options.limit ?? 10, 1), 50);
1066
1099
  const client = getDbExec();
1067
1100
  const { rows } = await client.execute({
1068
- sql: `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, error_code, abort_reason, dispatch_mode, diag_stage
1101
+ sql: `SELECT id, thread_id, turn_id, status, started_at, heartbeat_at, completed_at, last_progress_at, error_code, abort_reason, dispatch_mode, terminal_reason, diag_stage
1069
1102
  FROM agent_runs
1070
1103
  WHERE thread_id = ?
1071
1104
  ORDER BY started_at DESC
@@ -1085,6 +1118,7 @@ export async function listRunsForThread(
1085
1118
  error_code?: string | null;
1086
1119
  abort_reason?: string | null;
1087
1120
  dispatch_mode?: string | null;
1121
+ terminal_reason?: string | null;
1088
1122
  diag_stage?: string | null;
1089
1123
  };
1090
1124
  return {
@@ -1100,6 +1134,7 @@ export async function listRunsForThread(
1100
1134
  errorCode: row.error_code ?? null,
1101
1135
  abortReason: row.abort_reason ?? null,
1102
1136
  dispatchMode: row.dispatch_mode ?? null,
1137
+ terminalReason: row.terminal_reason ?? null,
1103
1138
  diagStage: row.diag_stage ?? null,
1104
1139
  };
1105
1140
  });
@@ -1183,13 +1218,15 @@ export async function reapAllStaleRuns(): Promise<number> {
1183
1218
  SET status = 'errored',
1184
1219
  completed_at = ?,
1185
1220
  error_code = ?,
1186
- error_detail = ?
1221
+ error_detail = ?,
1222
+ terminal_reason = ?
1187
1223
  WHERE status = 'running'
1188
1224
  AND ${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()}`,
1189
1225
  args: [
1190
1226
  completedAt,
1191
1227
  STALE_RUN_ERROR_EVENT.errorCode,
1192
1228
  STALE_RUN_ERROR_EVENT.details,
1229
+ STALE_RUN_ERROR_EVENT.errorCode,
1193
1230
  completedAt,
1194
1231
  ],
1195
1232
  });
@@ -1241,12 +1278,14 @@ export async function cleanupOldRuns(
1241
1278
  SET status = 'errored',
1242
1279
  completed_at = ?,
1243
1280
  error_code = ?,
1244
- error_detail = ?
1281
+ error_detail = ?,
1282
+ terminal_reason = ?
1245
1283
  WHERE status = 'running' AND started_at < ?`,
1246
1284
  args: [
1247
1285
  completedAt,
1248
1286
  STALE_RUN_ERROR_EVENT.errorCode,
1249
1287
  STALE_RUN_ERROR_EVENT.details,
1288
+ STALE_RUN_ERROR_EVENT.errorCode,
1250
1289
  cutoff,
1251
1290
  ],
1252
1291
  });
@@ -1257,13 +1296,15 @@ export async function cleanupOldRuns(
1257
1296
  SET status = 'errored',
1258
1297
  completed_at = ?,
1259
1298
  error_code = ?,
1260
- error_detail = ?
1299
+ error_detail = ?,
1300
+ terminal_reason = ?
1261
1301
  WHERE status = 'running'
1262
1302
  AND ${livenessBasisSql()} < ${backgroundAwareStaleCutoffSql()}`,
1263
1303
  args: [
1264
1304
  completedAt,
1265
1305
  STALE_RUN_ERROR_EVENT.errorCode,
1266
1306
  STALE_RUN_ERROR_EVENT.details,
1307
+ STALE_RUN_ERROR_EVENT.errorCode,
1267
1308
  completedAt,
1268
1309
  ],
1269
1310
  });
@@ -1312,6 +1353,7 @@ export async function listErroredRuns(options?: {
1312
1353
  status: string;
1313
1354
  errorCode: string | null;
1314
1355
  errorDetail: string | null;
1356
+ terminalReason: string | null;
1315
1357
  startedAt: number;
1316
1358
  completedAt: number | null;
1317
1359
  durationMs: number | null;
@@ -1323,7 +1365,7 @@ export async function listErroredRuns(options?: {
1323
1365
  const since =
1324
1366
  options?.sinceMs && options.sinceMs > 0 ? Date.now() - options.sinceMs : 0;
1325
1367
  const { rows } = await client.execute({
1326
- sql: `SELECT id, thread_id, turn_id, status, error_code, error_detail, started_at, completed_at
1368
+ sql: `SELECT id, thread_id, turn_id, status, error_code, error_detail, terminal_reason, started_at, completed_at
1327
1369
  FROM agent_runs
1328
1370
  WHERE status IN ('errored', 'aborted')
1329
1371
  AND COALESCE(completed_at, started_at) >= ?
@@ -1339,6 +1381,7 @@ export async function listErroredRuns(options?: {
1339
1381
  status: string;
1340
1382
  error_code: string | null;
1341
1383
  error_detail: string | null;
1384
+ terminal_reason: string | null;
1342
1385
  started_at: number | string;
1343
1386
  completed_at: number | string | null;
1344
1387
  };
@@ -1352,6 +1395,7 @@ export async function listErroredRuns(options?: {
1352
1395
  status: row.status,
1353
1396
  errorCode: row.error_code ?? null,
1354
1397
  errorDetail: row.error_detail ?? null,
1398
+ terminalReason: row.terminal_reason ?? null,
1355
1399
  startedAt,
1356
1400
  completedAt,
1357
1401
  durationMs: completedAt == null ? null : completedAt - startedAt,
@@ -712,6 +712,7 @@ export interface UpdateThreadDataOptions {
712
712
  preserveExistingQueuedMessages?: boolean;
713
713
  preserveExistingTopLevelKeys?: boolean;
714
714
  maxAttempts?: number;
715
+ ignoreConflicts?: boolean;
715
716
  }
716
717
 
717
718
  function parseThreadData(value: string): any {
@@ -792,6 +793,7 @@ export async function updateThreadData(
792
793
  }
793
794
 
794
795
  if (lastConflict) {
796
+ if (options.ignoreConflicts) return;
795
797
  throw new Error(
796
798
  `Failed to update chat thread ${id} after concurrent write conflicts.`,
797
799
  );
@@ -329,15 +329,18 @@ iteration, or a human-in-the-loop choice among design directions.
329
329
  overview board and renders an inline chat choice with one button per screen
330
330
  name. After the user picks, delete the unchosen variant screens and continue
331
331
  from the kept screen by first calling \`get-design-snapshot\` with that
332
- screen's \`fileId\`, then making a bounded single-file pass.
332
+ screen's \`fileId\`, then calling \`edit-design\` on that same \`fileId\` in a
333
+ bounded single-file pass. Use \`mode: "replace-file"\` when expanding the
334
+ representative placeholder into the full chosen direction. Do not call
335
+ \`generate-design\` after a variant pick.
333
336
  - If the chat choice buttons are not available in the host, ask the user to
334
337
  tell you the screen name they prefer. The variants are already real screens
335
338
  on the board, so do not ask them to paste HTML or copy a generated handoff
336
339
  summary.
337
340
  - For direct refinements to an already chosen direction, call
338
341
  \`get-design-snapshot\`, edit from the current tuned HTML, and use
339
- \`edit-design\` for surgical changes. Use \`generate-design\` for new files
340
- or larger structural rewrites.
342
+ \`edit-design\` for surgical changes or \`mode: "replace-file"\` for a bounded
343
+ selected-file replacement. Use \`generate-design\` for new files only.
341
344
  - Use \`export-coding-handoff\` when the user wants to implement the chosen
342
345
  design in a codebase.
343
346
 
@@ -353,9 +356,10 @@ iteration, or a human-in-the-loop choice among design directions.
353
356
  5. After \`present-design-variants\`, wait for the user's pick before
354
357
  generating the next version. Keep the chosen screen, delete the other
355
358
  variant screens, call \`get-design-snapshot\` with \`fileId\` for the kept
356
- screen, then continue from that single file in a bounded pass. Prefer
357
- \`edit-design\`; if \`generate-design\` is needed, update only the kept file
358
- compactly and stop after the first successful save.
359
+ screen, then call \`edit-design\` on that same \`fileId\` in a bounded pass.
360
+ Use \`mode: "replace-file"\` when expanding the representative placeholder
361
+ into the full chosen direction. Do not call \`generate-design\` after a
362
+ variant pick. Stop after the first successful \`edit-design\` save.
359
363
 
360
364
  ## Design Quality Bar
361
365
 
@@ -601,6 +601,31 @@ export function resolveAssistantChatSubmitIntent({
601
601
  return requestedIntent ?? "immediate";
602
602
  }
603
603
 
604
+ export function resolveAssistantChatRunningState({
605
+ forceStopped,
606
+ isRuntimeRunning,
607
+ isReconnecting,
608
+ optimisticRunning,
609
+ isAutoResuming,
610
+ }: {
611
+ forceStopped: boolean;
612
+ isRuntimeRunning: boolean;
613
+ isReconnecting: boolean;
614
+ optimisticRunning: boolean;
615
+ isAutoResuming: boolean;
616
+ }): { isRunning: boolean; showRunningInUI: boolean } {
617
+ const isRunning =
618
+ !forceStopped && (isRuntimeRunning || isReconnecting || optimisticRunning);
619
+ return {
620
+ isRunning,
621
+ // During auto-continuation, assistant-ui can briefly mark the message done
622
+ // between chunks even though the adapter is about to POST the next run.
623
+ // Keep the visible chat state running so the latest assistant message shows
624
+ // Thinking/Resuming and does not expose footer actions prematurely.
625
+ showRunningInUI: !forceStopped && (isRunning || isAutoResuming),
626
+ };
627
+ }
628
+
604
629
  type QueuedMessage = {
605
630
  id: string;
606
631
  text: string;
@@ -1378,14 +1403,17 @@ const AssistantChatInner = forwardRef<
1378
1403
  // queueing forever" state where isReconnecting or isRuntimeRunning gets
1379
1404
  // wedged (e.g. after a tab refresh + stop during reconnect).
1380
1405
  const [forceStopped, setForceStopped] = useState(false);
1381
- // Real running state drives submission/queue gating. Treat reconnecting
1382
- // to an active run the same as running, UNLESS the user has explicitly
1383
- // clicked stop (forceStopped).
1384
- const isRunning =
1385
- !forceStopped && (isRuntimeRunning || isReconnecting || optimisticRunning);
1386
- const textStreaming = isRunning || externalStreaming;
1387
- // UI-only running state — drives the stop button and thinking indicator.
1388
- const showRunningInUI = isRunning;
1406
+ // Real running state drives submission/queue gating; UI running also covers
1407
+ // short auto-continuation gaps so the latest assistant message does not flash
1408
+ // into a done state while the agent is still working.
1409
+ const { isRunning, showRunningInUI } = resolveAssistantChatRunningState({
1410
+ forceStopped,
1411
+ isRuntimeRunning,
1412
+ isReconnecting,
1413
+ optimisticRunning,
1414
+ isAutoResuming,
1415
+ });
1416
+ const textStreaming = showRunningInUI || externalStreaming;
1389
1417
  // A revealed activity label wins; otherwise stay a steady "Thinking" by
1390
1418
  // default. We only surface "Reconnecting" while we are actively replaying
1391
1419
  // recovered content (reconnectContent populated). A bare reconnect with no
@@ -2265,6 +2293,8 @@ const AssistantChatInner = forwardRef<
2265
2293
 
2266
2294
  useEffect(() => {
2267
2295
  if (!authError) return;
2296
+ const shouldCaptureStuckAuthCard =
2297
+ authSessionAvailable || authError.sessionExpired;
2268
2298
  // Auto-recovery (`checkAuthSession`) runs immediately + at 250ms. If the
2269
2299
  // card is still showing 3 seconds later, recovery failed and the user
2270
2300
  // is about to hit "Refresh chat" — that's the "Reload UI required"
@@ -2273,6 +2303,7 @@ const AssistantChatInner = forwardRef<
2273
2303
  void (async () => {
2274
2304
  const hasSession = await checkAuthSession();
2275
2305
  if (hasSession) return;
2306
+ if (!shouldCaptureStuckAuthCard) return;
2276
2307
  captureError(new Error("agent-chat:auth_error_card_stuck"), {
2277
2308
  tags: {
2278
2309
  context: "agent-native-chat",
@@ -3211,7 +3242,7 @@ const AssistantChatInner = forwardRef<
3211
3242
  <CheckpointContext.Provider value={checkpointCtx}>
3212
3243
  <MessageActionsContext.Provider value={messageActionsCtx}>
3213
3244
  <ApprovalContext.Provider value={approvalCtx}>
3214
- <ChatRunningContext.Provider value={isRunning}>
3245
+ <ChatRunningContext.Provider value={showRunningInUI}>
3215
3246
  <TextStreamingContext.Provider value={textStreaming}>
3216
3247
  <div
3217
3248
  data-agent-empty-state={
@@ -807,6 +807,21 @@ function lastActivityTool(
807
807
  return undefined;
808
808
  }
809
809
 
810
+ function formatActivityTrail(
811
+ trail: readonly AgentActivityTrailEntry[],
812
+ ): string | undefined {
813
+ const items = trail
814
+ .slice(-8)
815
+ .map((entry) => {
816
+ const label = entry.label.replace(/\s+/g, " ").trim();
817
+ const tool = entry.tool?.replace(/\s+/g, " ").trim();
818
+ if (label && tool && label !== tool) return `${label} (${tool})`;
819
+ return label || tool || "";
820
+ })
821
+ .filter(Boolean);
822
+ return items.length > 0 ? items.join(" > ") : undefined;
823
+ }
824
+
810
825
  function lastUnresolvedToolActivity(
811
826
  content: ContentPart[],
812
827
  ): string | undefined {
@@ -928,9 +943,9 @@ function incrementalActionGuidance(tool: string): string | undefined {
928
943
  return "create a compact working v1 with `create-extension`, then use focused `update-extension` edits for refinements";
929
944
  case "generate-design":
930
945
  case "update-design":
931
- return "persist a minimal first version (fewer files) with `generate-design`, then refine individual files with `edit-design` search/replace instead of resending everything";
946
+ return 'if an existing design file or snapshot is already in history, especially after a Design variant pick, stop retrying `generate-design`: call `get-design-snapshot` for the selected file, then call `edit-design` once on that same `fileId` (`mode: "replace-file"` for a compact full-file replacement, or search/replace for smaller edits). Use `generate-design` only for a brand-new compact first file when no target file exists yet';
932
947
  case "present-design-variants":
933
- return "call `present-design-variants` with concise labels, descriptions, accent colors, and feature bullets; omit large `content` HTML when needed so the action can render compact representative screens, then refine the chosen direction with `generate-design` or `edit-design`";
948
+ return 'call `present-design-variants` with concise labels, descriptions, accent colors, and feature bullets; omit large `content` HTML when needed so the action can render compact representative screens. After the user picks a direction, delete unchosen screens, snapshot the selected `fileId`, and refine that same file with `edit-design` (`mode: "replace-file"` for placeholder expansion). Do not call `generate-design` after the variant pick';
934
949
  case "create-visual-plan":
935
950
  case "create-ui-plan":
936
951
  case "create-plan-design":
@@ -1439,14 +1454,36 @@ export function createAgentChatAdapter(
1439
1454
  let visibleContinuationPrefix: ContentPart[] = [];
1440
1455
  let lastAutoContinueReason: string | null = null;
1441
1456
  let lastRecoverableRunError: AgentAutoContinueErrorInfo | null = null;
1457
+ let lastActivityTrail: AgentActivityTrailEntry[] = [];
1442
1458
  const attemptedRunIds: string[] = [];
1443
1459
  let authRecoveryAttempted = false;
1444
1460
  let continuationToolCallCounter = 0;
1445
1461
  const nextContinuationToolCallId = () =>
1446
1462
  `continuation_tc_${++continuationToolCallCounter}`;
1447
1463
 
1464
+ const runDebugContextDetails = (): string => {
1465
+ const pageOrigin =
1466
+ typeof window !== "undefined" && window.location?.origin
1467
+ ? window.location.origin
1468
+ : "";
1469
+ return [
1470
+ `api_url: ${apiUrl}`,
1471
+ pageOrigin ? `page_origin: ${pageOrigin}` : "",
1472
+ tabId ? `tab_id: ${tabId}` : "",
1473
+ threadId ? `thread_id: ${threadId}` : "",
1474
+ `turn_id: ${turnId}`,
1475
+ runId ? `current_run: ${runId}` : "",
1476
+ attemptedRunIds.length > 0
1477
+ ? `attempted_runs: ${attemptedRunIds.join(", ")}`
1478
+ : "",
1479
+ ]
1480
+ .filter(Boolean)
1481
+ .join("\n");
1482
+ };
1483
+
1448
1484
  const connectionRecoveryDetails = (): string => {
1449
1485
  return [
1486
+ runDebugContextDetails(),
1450
1487
  lastAutoContinueReason
1451
1488
  ? `last_auto_continue_reason: ${lastAutoContinueReason}`
1452
1489
  : "",
@@ -1468,10 +1505,10 @@ export function createAgentChatAdapter(
1468
1505
  lastPreparingToolName
1469
1506
  ? `last_preparing_tool: ${lastPreparingToolName}`
1470
1507
  : "",
1471
- `total_transient_continuations: ${totalTransientContinuationAttempts}`,
1472
- attemptedRunIds.length > 0
1473
- ? `attempted_runs: ${attemptedRunIds.join(", ")}`
1508
+ formatActivityTrail(lastActivityTrail)
1509
+ ? `activity_trail: ${formatActivityTrail(lastActivityTrail)}`
1474
1510
  : "",
1511
+ `total_transient_continuations: ${totalTransientContinuationAttempts}`,
1475
1512
  ]
1476
1513
  .filter(Boolean)
1477
1514
  .join("\n");
@@ -1559,6 +1596,7 @@ export function createAgentChatAdapter(
1559
1596
  lastSeq,
1560
1597
  contentParts: content.length,
1561
1598
  attemptedRunIds: [...attemptedRunIds],
1599
+ activityTrail: [...lastActivityTrail],
1562
1600
  startupRecoveryAttempts,
1563
1601
  staleRunContinuationAttempts,
1564
1602
  stalledTransientContinuationAttempts,
@@ -1867,6 +1905,7 @@ export function createAgentChatAdapter(
1867
1905
  completedToolName?: string;
1868
1906
  } => {
1869
1907
  lastAutoContinueReason = signal.reason;
1908
+ lastActivityTrail = [...signal.activityTrail];
1870
1909
  if (signal.errorInfo) {
1871
1910
  lastRecoverableRunError = signal.errorInfo;
1872
1911
  }
@@ -291,7 +291,7 @@ function PositionedDiagram({
291
291
  return (
292
292
  <span
293
293
  key={`${edge.from}-${edge.to}-${index}-label`}
294
- className="absolute z-10 max-w-[130px] -translate-x-1/2 -translate-y-1/2 rounded-full border border-border bg-background px-2 py-0.5 text-center text-[11px] font-semibold text-muted-foreground shadow-sm"
294
+ className="absolute z-10 max-w-[130px] -translate-x-1/2 -translate-y-1/2 break-words rounded-full border border-border bg-background px-2 py-0.5 text-center text-[11px] font-semibold text-muted-foreground shadow-sm [overflow-wrap:anywhere]"
295
295
  style={{
296
296
  left: `${(from.displayX + to.displayX) / 2}%`,
297
297
  top: `${(from.displayY + to.displayY) / 2}%`,
@@ -305,7 +305,7 @@ function PositionedDiagram({
305
305
  {displayNodesForDirection.map((node, index) => (
306
306
  <article
307
307
  key={node.id}
308
- className="absolute z-20 -translate-x-1/2 -translate-y-1/2 rounded-xl border-2 border-border bg-background p-3 text-foreground shadow-sm"
308
+ className="absolute z-20 -translate-x-1/2 -translate-y-1/2 break-words rounded-xl border-2 border-border bg-background p-3 text-foreground shadow-sm [overflow-wrap:anywhere]"
309
309
  style={{
310
310
  left: `${node.displayX}%`,
311
311
  top: `${node.displayY}%`,
@@ -485,7 +485,7 @@ function SequenceDiagram({
485
485
  {next && (
486
486
  <div className="grid min-w-[72px] justify-items-center gap-1 text-muted-foreground">
487
487
  {edge?.label && (
488
- <span className="max-w-[96px] truncate rounded-full border border-border px-2 py-0.5 text-[11px] font-semibold">
488
+ <span className="max-w-[96px] whitespace-normal break-words rounded-full border border-border px-2 py-0.5 text-center text-[11px] font-semibold [overflow-wrap:anywhere]">
489
489
  {edge.label}
490
490
  </span>
491
491
  )}
@@ -784,19 +784,31 @@ function assistantMessageStatusIsTerminal(message: {
784
784
  return statusType === "complete" || statusType === "incomplete";
785
785
  }
786
786
 
787
+ export function assistantMessageHasUnresolvedTool(content: unknown): boolean {
788
+ if (!Array.isArray(content)) return false;
789
+ return content.some((part): boolean => {
790
+ if (!part || typeof part !== "object") return false;
791
+ const record = part as { type?: unknown; result?: unknown };
792
+ return record.type === "tool-call" && record.result === undefined;
793
+ });
794
+ }
795
+
787
796
  export function shouldShowAssistantMessageFooter({
788
797
  isLast,
789
798
  chatRunning,
790
799
  hasRenderableContent,
791
800
  statusIsTerminal,
801
+ hasUnresolvedTool,
792
802
  }: {
793
803
  isLast: boolean;
794
804
  chatRunning: boolean;
795
805
  hasRenderableContent: boolean;
796
806
  statusIsTerminal: boolean;
807
+ hasUnresolvedTool?: boolean;
797
808
  }): boolean {
798
809
  if (!hasRenderableContent) return false;
799
810
  if (!isLast) return true;
811
+ if (hasUnresolvedTool) return false;
800
812
  return !chatRunning && statusIsTerminal;
801
813
  }
802
814
 
@@ -813,11 +825,13 @@ export function AssistantMessage() {
813
825
  thread.messages.length > 0 &&
814
826
  thread.messages[thread.messages.length - 1].id === msg.id;
815
827
  const hasRenderableContent = assistantMessageHasRenderableContent(msg);
828
+ const hasUnresolvedTool = assistantMessageHasUnresolvedTool(msg.content);
816
829
  const isComplete = shouldShowAssistantMessageFooter({
817
830
  isLast,
818
831
  chatRunning,
819
832
  hasRenderableContent,
820
833
  statusIsTerminal: assistantMessageStatusIsTerminal(msg),
834
+ hasUnresolvedTool,
821
835
  });
822
836
  const cpCtx = React.useContext(CheckpointContext);
823
837
 
@@ -907,6 +921,9 @@ export function AssistantMessage() {
907
921
  {isComplete && hasCodeAgentTools && msgContent && (
908
922
  <FilesChangedSummary parts={msgContent} />
909
923
  )}
924
+ {isLast && hasUnresolvedTool && !chatRunning && (
925
+ <RunningActivityStatus label="Thinking" />
926
+ )}
910
927
  </div>
911
928
  {isComplete && (
912
929
  <div className="mt-1 flex items-center justify-between">