@agent-native/core 0.84.2 → 0.84.3

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 (41) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +8 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +9 -2
  5. package/corpus/core/src/agent/run-loop-with-resume.ts +38 -8
  6. package/corpus/core/src/agent/thread-data-builder.ts +6 -0
  7. package/corpus/core/src/client/agent-chat-adapter.ts +208 -9
  8. package/corpus/core/src/client/sharing/ShareButton.tsx +70 -21
  9. package/corpus/core/src/client/sse-event-processor.ts +15 -0
  10. package/corpus/templates/analytics/AGENTS.md +1 -1
  11. package/corpus/templates/design/app/pages/DesignEditor.tsx +129 -100
  12. package/corpus/templates/design/changelog/2026-06-30-agent-chat-now-reports-saved-design-generations-clearly-when.md +6 -0
  13. package/corpus/templates/design/changelog/2026-06-30-apply-styles-now-appears-only-for-localhost-visual-edit-scre.md +6 -0
  14. package/corpus/templates/design/changelog/2026-06-30-share-options-now-make-export-and-agent-handoff-easier-to-no.md +6 -0
  15. package/corpus/templates/design/changelog/2026-07-01-share-general-access-menu-stays-open-when-choosing-organization.md +6 -0
  16. package/dist/agent/production-agent.d.ts.map +1 -1
  17. package/dist/agent/production-agent.js +9 -2
  18. package/dist/agent/production-agent.js.map +1 -1
  19. package/dist/agent/run-loop-with-resume.d.ts +2 -2
  20. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  21. package/dist/agent/run-loop-with-resume.js +31 -8
  22. package/dist/agent/run-loop-with-resume.js.map +1 -1
  23. package/dist/agent/thread-data-builder.d.ts +2 -0
  24. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  25. package/dist/agent/thread-data-builder.js +5 -0
  26. package/dist/agent/thread-data-builder.js.map +1 -1
  27. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  28. package/dist/client/agent-chat-adapter.js +179 -8
  29. package/dist/client/agent-chat-adapter.js.map +1 -1
  30. package/dist/client/sharing/ShareButton.d.ts +6 -0
  31. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  32. package/dist/client/sharing/ShareButton.js +25 -11
  33. package/dist/client/sharing/ShareButton.js.map +1 -1
  34. package/dist/client/sse-event-processor.d.ts +4 -0
  35. package/dist/client/sse-event-processor.d.ts.map +1 -1
  36. package/dist/client/sse-event-processor.js +13 -0
  37. package/dist/client/sse-event-processor.js.map +1 -1
  38. package/dist/notifications/routes.d.ts +1 -1
  39. package/dist/observability/routes.d.ts +5 -5
  40. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  41. package/package.json +1 -1
@@ -529,6 +529,34 @@ function hasContinuationProgress(content) {
529
529
  ? part.text.trim().length > 0
530
530
  : part.result !== undefined);
531
531
  }
532
+ function lastCompletedSideEffectTool(content) {
533
+ for (let i = content.length - 1; i >= 0; i--) {
534
+ const part = content[i];
535
+ if (part.type === "tool-call" &&
536
+ part.activity !== true &&
537
+ part.result !== undefined &&
538
+ part.isError !== true &&
539
+ part.completedSideEffect === true) {
540
+ return part;
541
+ }
542
+ }
543
+ return undefined;
544
+ }
545
+ function humanizeActionName(toolName) {
546
+ return toolName
547
+ .replace(/^agent:/, "")
548
+ .replace(/[-_]+/g, " ")
549
+ .trim();
550
+ }
551
+ function completedToolTimeoutMessage(toolName) {
552
+ if (toolName === "generate-design" || toolName === "update-design") {
553
+ return "The design was saved, but the assistant timed out before sending its final note. You can open the generated design from the completed tool card above.";
554
+ }
555
+ if (toolName === "present-design-variants") {
556
+ return "The design variants were saved, but the assistant timed out before sending its final note. You can review the completed variants from the tool card above.";
557
+ }
558
+ return `The ${humanizeActionName(toolName)} action completed, but the assistant timed out before sending its final response. The saved result is in the completed tool card above.`;
559
+ }
532
560
  /**
533
561
  * Signature of the *unique* sentence-like segments in a continuation's newly
534
562
  * streamed text, used to detect a degenerate repetition loop. A stuck model
@@ -603,6 +631,8 @@ function toolContinuationKey(part) {
603
631
  stableJson(part.args),
604
632
  part.result === undefined ? "pending" : "done",
605
633
  part.result ?? "",
634
+ part.isError === true ? "error" : "",
635
+ part.completedSideEffect === true ? "side-effect" : "",
606
636
  part.activity === true ? "activity" : "tool",
607
637
  part.mcpApp ? "mcp-app" : "",
608
638
  ].join("\u0000");
@@ -657,6 +687,8 @@ function incrementalActionGuidance(tool) {
657
687
  case "generate-design":
658
688
  case "update-design":
659
689
  return "persist a minimal first version (fewer files) with `generate-design`, then refine individual files with `edit-design` search/replace instead of resending everything";
690
+ case "present-design-variants":
691
+ return "save compact but complete variant screens with `present-design-variants` first, keeping each HTML direction focused enough to finish, then refine the chosen direction with `generate-design` or `edit-design`";
660
692
  case "create-visual-plan":
661
693
  case "create-ui-plan":
662
694
  case "create-plan-design":
@@ -1039,6 +1071,7 @@ export function createAgentChatAdapter(options) {
1039
1071
  // its own budget rather than inheriting the prior payload's.
1040
1072
  let lastInFlightToolSignature;
1041
1073
  let repeatedInFlightToolCount = 0;
1074
+ let recoveryGaveUpOnInFlightTool = false;
1042
1075
  const MAX_REPEATED_INFLIGHT_TOOL_STALLS = 3;
1043
1076
  const continuationHistoryFragments = [];
1044
1077
  const structuredContinuationFragments = [];
@@ -1077,6 +1110,9 @@ export function createAgentChatAdapter(options) {
1077
1110
  .join("\n");
1078
1111
  };
1079
1112
  const exhaustedRecoveryMessage = (reason) => {
1113
+ if (recoveryGaveUpOnInFlightTool) {
1114
+ return "The agent got stuck waiting for the same tool to finish, so I stopped the automatic retries. The tool did not report a completed result.";
1115
+ }
1080
1116
  if (recoveryGaveUpOnRepetition) {
1081
1117
  return "The agent got stuck repeating the same response without finishing, so I stopped the automatic retries. This often happens when it tries to re-type a large pasted file into one action — starting a new chat, or asking for a smaller first step, usually gets it unstuck.";
1082
1118
  }
@@ -1309,6 +1345,73 @@ export function createAgentChatAdapter(options) {
1309
1345
  }
1310
1346
  return false;
1311
1347
  };
1348
+ const reconnectBackgroundContinuationForRunTimeout = async function* () {
1349
+ if (!threadId || !runId)
1350
+ return false;
1351
+ const interruptedRunId = runId;
1352
+ const interruptedLastSeq = lastSeq;
1353
+ let lastActiveRunError = null;
1354
+ for (let attempt = 0; attempt < 3; attempt++) {
1355
+ if (attempt > 0) {
1356
+ await delay(500, abortSignal);
1357
+ }
1358
+ if (abortSignal.aborted)
1359
+ return true;
1360
+ try {
1361
+ const activeRes = await fetch(`${apiUrl}/runs/active?threadId=${encodeURIComponent(threadId)}`, { signal: abortSignal });
1362
+ if (!activeRes.ok) {
1363
+ if (activeRes.status === 404)
1364
+ return false;
1365
+ lastActiveRunError = new Error(`Active run lookup failed: ${activeRes.status}`);
1366
+ continue;
1367
+ }
1368
+ const active = await activeRes.json();
1369
+ if (!active?.active || !active.runId)
1370
+ return false;
1371
+ const activeRunId = String(active.runId);
1372
+ const dispatchMode = typeof active.dispatchMode === "string"
1373
+ ? active.dispatchMode
1374
+ : "";
1375
+ if (activeRunId === interruptedRunId) {
1376
+ if (dispatchMode.startsWith("background"))
1377
+ continue;
1378
+ return false;
1379
+ }
1380
+ const activeTurnId = typeof active.turnId === "string" ? active.turnId : "";
1381
+ const activeStatus = typeof active.status === "string" ? active.status : "";
1382
+ if (!dispatchMode.startsWith("background"))
1383
+ return false;
1384
+ if (activeTurnId && activeTurnId !== turnId)
1385
+ return false;
1386
+ if (activeStatus !== "running" && activeStatus !== "starting") {
1387
+ return false;
1388
+ }
1389
+ runId = activeRunId;
1390
+ if (!attemptedRunIds.includes(activeRunId)) {
1391
+ attemptedRunIds.push(activeRunId);
1392
+ }
1393
+ lastSeq = -1;
1394
+ setActiveRun({ threadId, runId: activeRunId, lastSeq: -1 });
1395
+ const reconnected = yield* reconnectCurrentRun();
1396
+ if (reconnected)
1397
+ return true;
1398
+ }
1399
+ catch (activeErr) {
1400
+ if (activeErr instanceof Error &&
1401
+ activeErr.name === "AbortError") {
1402
+ clearActiveRun();
1403
+ return true;
1404
+ }
1405
+ lastActiveRunError = activeErr;
1406
+ }
1407
+ }
1408
+ if (lastActiveRunError) {
1409
+ captureChatClientError(lastActiveRunError, "reconnect-background-continuation-failed");
1410
+ }
1411
+ runId = interruptedRunId;
1412
+ lastSeq = interruptedLastSeq;
1413
+ return false;
1414
+ };
1312
1415
  const visibleContentForContinuation = () => {
1313
1416
  return contentAfterContinuationPrefix(content, visibleContinuationPrefix);
1314
1417
  };
@@ -1331,6 +1434,7 @@ export function createAgentChatAdapter(options) {
1331
1434
  // before tool_start; treating it as progress caused silent retry
1332
1435
  // loops when the LLM timed out while assembling a large tool input.
1333
1436
  const hasInFlightTool = hasInFlightToolCall(visibleContent);
1437
+ const completedSideEffectTool = lastCompletedSideEffectTool(content);
1334
1438
  // Either real output or an actively-running tool counts as progress
1335
1439
  // for the stalled/empty caps.
1336
1440
  const madeProgress = madeContentProgress || hasInFlightTool;
@@ -1343,11 +1447,11 @@ export function createAgentChatAdapter(options) {
1343
1447
  // MAX_REPEATED_INFLIGHT_TOOL_STALLS consecutive stream_ended events,
1344
1448
  // bail with a clear message.
1345
1449
  //
1346
- // Only count stream_ended (connection drop / reconnect failed), NOT
1347
- // run_timeout (server legitimately still executing a slow tool). A
1348
- // run_timeout with an in-flight tool means the server is actively
1349
- // working and reconnection may still recover the result; a repeated
1350
- // stream_ended means the connection keeps breaking under that payload.
1450
+ // Count broken streams (connection drop / reconnect failed) and
1451
+ // no_progress stalls (the client aborts a stream that stayed open but
1452
+ // stopped producing events). Do NOT count run_timeout: with an
1453
+ // in-flight tool that means the server is still actively executing a
1454
+ // slow action and reconnection may recover the result.
1351
1455
  const currentInFlightToolPart = visibleContent.find((p) => p.type === "tool-call" &&
1352
1456
  p.result === undefined &&
1353
1457
  p.activity !== true);
@@ -1355,8 +1459,8 @@ export function createAgentChatAdapter(options) {
1355
1459
  const currentInFlightToolSignature = currentInFlightToolPart
1356
1460
  ? inFlightToolInputSignature(currentInFlightToolPart)
1357
1461
  : undefined;
1358
- const isConnectionDrop = signal.reason === "stream_ended";
1359
- if (currentInFlightToolName && isConnectionDrop) {
1462
+ const isBrokenInFlightTool = signal.reason === "stream_ended" || signal.reason === "no_progress";
1463
+ if (currentInFlightToolName && isBrokenInFlightTool) {
1360
1464
  if (currentInFlightToolName === lastInFlightToolName &&
1361
1465
  currentInFlightToolSignature === lastInFlightToolSignature) {
1362
1466
  repeatedInFlightToolCount += 1;
@@ -1393,11 +1497,27 @@ export function createAgentChatAdapter(options) {
1393
1497
  }
1394
1498
  else {
1395
1499
  totalTransientContinuationAttempts += 1;
1500
+ // If a mutating action already completed, do not turn a missing
1501
+ // closing sentence into a scary connection failure. Give the model
1502
+ // one continuation opportunity (the completed tool itself counts
1503
+ // as progress on the first timeout); if the follow-up produces no
1504
+ // new content, stop locally with a clear "saved, final note timed
1505
+ // out" message.
1506
+ if (completedSideEffectTool &&
1507
+ !hasInFlightToolCall(content) &&
1508
+ !madeContentProgress &&
1509
+ !hasInFlightTool) {
1510
+ return {
1511
+ ok: false,
1512
+ resetVisibleContent: false,
1513
+ completedToolName: completedSideEffectTool.toolName,
1514
+ };
1515
+ }
1396
1516
  // Bail when the same write tool is stuck in-flight across too many
1397
1517
  // consecutive continuations. Checked before the text-repeat guard
1398
1518
  // because hasInFlightTool=true would mask the repeat as progress.
1399
1519
  if (repeatedInFlightToolCount >= MAX_REPEATED_INFLIGHT_TOOL_STALLS) {
1400
- recoveryGaveUpOnRepetition = true;
1520
+ recoveryGaveUpOnInFlightTool = true;
1401
1521
  return { ok: false, resetVisibleContent: false };
1402
1522
  }
1403
1523
  // Bail fast on a non-advancing repetition loop, well before the
@@ -1760,6 +1880,11 @@ export function createAgentChatAdapter(options) {
1760
1880
  if (err.reason === "no_progress") {
1761
1881
  await abortCurrentRun();
1762
1882
  }
1883
+ if (err.reason === "run_timeout" && !err.errorInfo) {
1884
+ const reconnected = yield* reconnectBackgroundContinuationForRunTimeout();
1885
+ if (reconnected)
1886
+ return;
1887
+ }
1763
1888
  if (err.reason === "stream_ended") {
1764
1889
  const reconnected = yield* reconnectCurrentRun();
1765
1890
  if (reconnected)
@@ -1770,6 +1895,29 @@ export function createAgentChatAdapter(options) {
1770
1895
  }
1771
1896
  const continuation = prepareAutoContinuation(err);
1772
1897
  if (!continuation.ok) {
1898
+ if (continuation.completedToolName) {
1899
+ const message = completedToolTimeoutMessage(continuation.completedToolName);
1900
+ content.push({ type: "text", text: message });
1901
+ yield {
1902
+ content: [...content],
1903
+ status: {
1904
+ type: "complete",
1905
+ reason: "stop",
1906
+ },
1907
+ metadata: {
1908
+ custom: {
1909
+ ...(runId ? { runId } : {}),
1910
+ runWarning: {
1911
+ message,
1912
+ errorCode: "final_response_timeout_after_tool",
1913
+ recoverable: true,
1914
+ },
1915
+ },
1916
+ },
1917
+ };
1918
+ clearActiveRun();
1919
+ return;
1920
+ }
1773
1921
  const preservedError = err.errorInfo ?? lastRecoverableRunError ?? null;
1774
1922
  const message = preservedError?.message ??
1775
1923
  exhaustedRecoveryMessage(err.reason);
@@ -1920,6 +2068,29 @@ export function createAgentChatAdapter(options) {
1920
2068
  if (content.length > 0) {
1921
2069
  const continuation = prepareAutoContinuation(new AgentAutoContinueSignal({ reason: "stream_ended" }));
1922
2070
  if (!continuation.ok) {
2071
+ if (continuation.completedToolName) {
2072
+ const message = completedToolTimeoutMessage(continuation.completedToolName);
2073
+ content.push({ type: "text", text: message });
2074
+ yield {
2075
+ content: [...content],
2076
+ status: {
2077
+ type: "complete",
2078
+ reason: "stop",
2079
+ },
2080
+ metadata: {
2081
+ custom: {
2082
+ ...(runId ? { runId } : {}),
2083
+ runWarning: {
2084
+ message,
2085
+ errorCode: "final_response_timeout_after_tool",
2086
+ recoverable: true,
2087
+ },
2088
+ },
2089
+ },
2090
+ };
2091
+ clearActiveRun();
2092
+ return;
2093
+ }
1923
2094
  const message = exhaustedRecoveryMessage("stream_ended");
1924
2095
  captureChatClientError(err, "recovery-exhausted");
1925
2096
  const runError = {