@agent-native/core 0.41.1 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +17 -56
  2. package/dist/action.d.ts +13 -1
  3. package/dist/action.d.ts.map +1 -1
  4. package/dist/action.js.map +1 -1
  5. package/dist/agent/production-agent.d.ts +8 -0
  6. package/dist/agent/production-agent.d.ts.map +1 -1
  7. package/dist/agent/production-agent.js +93 -0
  8. package/dist/agent/production-agent.js.map +1 -1
  9. package/dist/cli/app-skill.d.ts +16 -0
  10. package/dist/cli/app-skill.d.ts.map +1 -1
  11. package/dist/cli/app-skill.js +33 -3
  12. package/dist/cli/app-skill.js.map +1 -1
  13. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  14. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  15. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  16. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  17. package/dist/cli/recap.d.ts.map +1 -1
  18. package/dist/cli/recap.js +38 -16
  19. package/dist/cli/recap.js.map +1 -1
  20. package/dist/cli/skills.d.ts +30 -3
  21. package/dist/cli/skills.d.ts.map +1 -1
  22. package/dist/cli/skills.js +180 -114
  23. package/dist/cli/skills.js.map +1 -1
  24. package/dist/client/AssistantChat.d.ts.map +1 -1
  25. package/dist/client/AssistantChat.js +2 -2
  26. package/dist/client/AssistantChat.js.map +1 -1
  27. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  28. package/dist/client/agent-chat-adapter.js +172 -5
  29. package/dist/client/agent-chat-adapter.js.map +1 -1
  30. package/dist/client/blocks/index.d.ts +11 -0
  31. package/dist/client/blocks/index.d.ts.map +1 -1
  32. package/dist/client/blocks/index.js +11 -0
  33. package/dist/client/blocks/index.js.map +1 -1
  34. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts +19 -0
  35. package/dist/client/blocks/library/AnnotatedCodeBlock.d.ts.map +1 -1
  36. package/dist/client/blocks/library/AnnotatedCodeBlock.js +6 -58
  37. package/dist/client/blocks/library/AnnotatedCodeBlock.js.map +1 -1
  38. package/dist/client/blocks/library/ApiEndpointBlock.d.ts.map +1 -1
  39. package/dist/client/blocks/library/ApiEndpointBlock.js +116 -7
  40. package/dist/client/blocks/library/ApiEndpointBlock.js.map +1 -1
  41. package/dist/client/blocks/library/DataModelBlock.d.ts.map +1 -1
  42. package/dist/client/blocks/library/DataModelBlock.js +75 -9
  43. package/dist/client/blocks/library/DataModelBlock.js.map +1 -1
  44. package/dist/client/blocks/library/DiffBlock.d.ts +1 -1
  45. package/dist/client/blocks/library/DiffBlock.d.ts.map +1 -1
  46. package/dist/client/blocks/library/DiffBlock.js +265 -39
  47. package/dist/client/blocks/library/DiffBlock.js.map +1 -1
  48. package/dist/client/blocks/library/FileTreeBlock.d.ts.map +1 -1
  49. package/dist/client/blocks/library/FileTreeBlock.js +27 -4
  50. package/dist/client/blocks/library/FileTreeBlock.js.map +1 -1
  51. package/dist/client/blocks/library/HighlightedCode.d.ts +1 -1
  52. package/dist/client/blocks/library/HighlightedCode.js +1 -1
  53. package/dist/client/blocks/library/HighlightedCode.js.map +1 -1
  54. package/dist/client/blocks/library/JsonExplorerBlock.js +1 -1
  55. package/dist/client/blocks/library/JsonExplorerBlock.js.map +1 -1
  56. package/dist/client/blocks/library/MermaidBlock.js +1 -1
  57. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  58. package/dist/client/blocks/library/annotation-rail.d.ts +115 -0
  59. package/dist/client/blocks/library/annotation-rail.d.ts.map +1 -0
  60. package/dist/client/blocks/library/annotation-rail.js +139 -0
  61. package/dist/client/blocks/library/annotation-rail.js.map +1 -0
  62. package/dist/client/blocks/library/api-endpoint.config.d.ts +31 -6
  63. package/dist/client/blocks/library/api-endpoint.config.d.ts.map +1 -1
  64. package/dist/client/blocks/library/api-endpoint.config.js +30 -6
  65. package/dist/client/blocks/library/api-endpoint.config.js.map +1 -1
  66. package/dist/client/blocks/library/callout.config.d.ts +29 -0
  67. package/dist/client/blocks/library/callout.config.d.ts.map +1 -0
  68. package/dist/client/blocks/library/callout.config.js +33 -0
  69. package/dist/client/blocks/library/callout.config.js.map +1 -0
  70. package/dist/client/blocks/library/callout.d.ts +20 -0
  71. package/dist/client/blocks/library/callout.d.ts.map +1 -0
  72. package/dist/client/blocks/library/callout.js +61 -0
  73. package/dist/client/blocks/library/callout.js.map +1 -0
  74. package/dist/client/blocks/library/checklist.d.ts.map +1 -1
  75. package/dist/client/blocks/library/checklist.js +3 -3
  76. package/dist/client/blocks/library/checklist.js.map +1 -1
  77. package/dist/client/blocks/library/code.d.ts.map +1 -1
  78. package/dist/client/blocks/library/code.js +32 -15
  79. package/dist/client/blocks/library/code.js.map +1 -1
  80. package/dist/client/blocks/library/columns.d.ts.map +1 -1
  81. package/dist/client/blocks/library/columns.js +56 -35
  82. package/dist/client/blocks/library/columns.js.map +1 -1
  83. package/dist/client/blocks/library/data-model.config.d.ts +17 -0
  84. package/dist/client/blocks/library/data-model.config.d.ts.map +1 -1
  85. package/dist/client/blocks/library/data-model.config.js +15 -0
  86. package/dist/client/blocks/library/data-model.config.js.map +1 -1
  87. package/dist/client/blocks/library/decision.config.d.ts +37 -0
  88. package/dist/client/blocks/library/decision.config.d.ts.map +1 -0
  89. package/dist/client/blocks/library/decision.config.js +32 -0
  90. package/dist/client/blocks/library/decision.config.js.map +1 -0
  91. package/dist/client/blocks/library/decision.d.ts +19 -0
  92. package/dist/client/blocks/library/decision.d.ts.map +1 -0
  93. package/dist/client/blocks/library/decision.js +119 -0
  94. package/dist/client/blocks/library/decision.js.map +1 -0
  95. package/dist/client/blocks/library/diagram.config.d.ts +64 -0
  96. package/dist/client/blocks/library/diagram.config.d.ts.map +1 -0
  97. package/dist/client/blocks/library/diagram.config.js +111 -0
  98. package/dist/client/blocks/library/diagram.config.js.map +1 -0
  99. package/dist/client/blocks/library/diagram.d.ts +16 -0
  100. package/dist/client/blocks/library/diagram.d.ts.map +1 -0
  101. package/dist/client/blocks/library/diagram.js +261 -0
  102. package/dist/client/blocks/library/diagram.js.map +1 -0
  103. package/dist/client/blocks/library/diff.config.d.ts +28 -6
  104. package/dist/client/blocks/library/diff.config.d.ts.map +1 -1
  105. package/dist/client/blocks/library/diff.config.js +30 -6
  106. package/dist/client/blocks/library/diff.config.js.map +1 -1
  107. package/dist/client/blocks/library/question-form.config.d.ts +69 -0
  108. package/dist/client/blocks/library/question-form.config.d.ts.map +1 -0
  109. package/dist/client/blocks/library/question-form.config.js +58 -0
  110. package/dist/client/blocks/library/question-form.config.js.map +1 -0
  111. package/dist/client/blocks/library/question-form.d.ts +20 -0
  112. package/dist/client/blocks/library/question-form.d.ts.map +1 -0
  113. package/dist/client/blocks/library/question-form.js +286 -0
  114. package/dist/client/blocks/library/question-form.js.map +1 -0
  115. package/dist/client/blocks/library/sanitize-html.d.ts +5 -0
  116. package/dist/client/blocks/library/sanitize-html.d.ts.map +1 -0
  117. package/dist/client/blocks/library/sanitize-html.js +240 -0
  118. package/dist/client/blocks/library/sanitize-html.js.map +1 -0
  119. package/dist/client/blocks/library/server-specs.d.ts.map +1 -1
  120. package/dist/client/blocks/library/server-specs.js +59 -0
  121. package/dist/client/blocks/library/server-specs.js.map +1 -1
  122. package/dist/client/blocks/library/specs.d.ts.map +1 -1
  123. package/dist/client/blocks/library/specs.js +11 -0
  124. package/dist/client/blocks/library/specs.js.map +1 -1
  125. package/dist/client/blocks/library/tabs.d.ts.map +1 -1
  126. package/dist/client/blocks/library/tabs.js +12 -12
  127. package/dist/client/blocks/library/tabs.js.map +1 -1
  128. package/dist/client/blocks/library/wireframe-kit.d.ts +260 -0
  129. package/dist/client/blocks/library/wireframe-kit.d.ts.map +1 -0
  130. package/dist/client/blocks/library/wireframe-kit.js +920 -0
  131. package/dist/client/blocks/library/wireframe-kit.js.map +1 -0
  132. package/dist/client/blocks/library/wireframe.config.d.ts +123 -0
  133. package/dist/client/blocks/library/wireframe.config.d.ts.map +1 -0
  134. package/dist/client/blocks/library/wireframe.config.js +294 -0
  135. package/dist/client/blocks/library/wireframe.config.js.map +1 -0
  136. package/dist/client/blocks/library/wireframe.d.ts +15 -0
  137. package/dist/client/blocks/library/wireframe.d.ts.map +1 -0
  138. package/dist/client/blocks/library/wireframe.js +206 -0
  139. package/dist/client/blocks/library/wireframe.js.map +1 -0
  140. package/dist/client/blocks/registry.d.ts +9 -0
  141. package/dist/client/blocks/registry.d.ts.map +1 -1
  142. package/dist/client/blocks/registry.js +12 -5
  143. package/dist/client/blocks/registry.js.map +1 -1
  144. package/dist/client/blocks/server.d.ts +1 -0
  145. package/dist/client/blocks/server.d.ts.map +1 -1
  146. package/dist/client/blocks/server.js +1 -0
  147. package/dist/client/blocks/server.js.map +1 -1
  148. package/dist/client/blocks/types.d.ts +10 -2
  149. package/dist/client/blocks/types.d.ts.map +1 -1
  150. package/dist/client/blocks/types.js.map +1 -1
  151. package/dist/client/rich-markdown-editor/DragHandle.d.ts.map +1 -1
  152. package/dist/client/rich-markdown-editor/DragHandle.js +152 -21
  153. package/dist/client/rich-markdown-editor/DragHandle.js.map +1 -1
  154. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts +25 -1
  155. package/dist/client/rich-markdown-editor/RegistryBlockNode.d.ts.map +1 -1
  156. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +29 -6
  157. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  158. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +8 -1
  159. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  160. package/dist/client/rich-markdown-editor/SharedRichEditor.js +5 -1
  161. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  162. package/dist/extensions/actions.d.ts.map +1 -1
  163. package/dist/extensions/actions.js +159 -12
  164. package/dist/extensions/actions.js.map +1 -1
  165. package/dist/extensions/store.d.ts +21 -0
  166. package/dist/extensions/store.d.ts.map +1 -1
  167. package/dist/extensions/store.js +33 -1
  168. package/dist/extensions/store.js.map +1 -1
  169. package/dist/server/recap-image-route.d.ts.map +1 -1
  170. package/dist/server/recap-image-route.js +12 -3
  171. package/dist/server/recap-image-route.js.map +1 -1
  172. package/dist/styles/agent-native.css +1 -0
  173. package/dist/styles/blocks.css +1380 -0
  174. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
  175. package/docs/content/plan-plugin.md +107 -0
  176. package/docs/content/pr-visual-recap.md +2 -2
  177. package/docs/content/skills-guide.md +8 -0
  178. package/docs/content/template-plan.md +94 -17
  179. package/package.json +2 -1
  180. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +30 -5
  181. package/docs/content/visual-plans.md +0 -80
@@ -1 +1 @@
1
- {"version":3,"file":"agent-chat-adapter.d.ts","sourceRoot":"","sources":["../../src/client/agent-chat-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAsB,MAAM,qBAAqB,CAAC;AAgBhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAKrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,oBAAoB;AAC9B;;;;GAIG;AACD,KAAK;AACP,0EAA0E;GACxE,WAAW,CAAC;AAm6BhB;;;;GAIG;AACH;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3C,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,eAAe,GAAG,SAAS,CAAA;KAAE,CAAC;IACrD,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3D,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC,GAAG,gBAAgB,CAygCnB"}
1
+ {"version":3,"file":"agent-chat-adapter.d.ts","sourceRoot":"","sources":["../../src/client/agent-chat-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAsB,MAAM,qBAAqB,CAAC;AAgBhF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAKrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,oBAAoB;AAC9B;;;;GAIG;AACD,KAAK;AACP,0EAA0E;GACxE,WAAW,CAAC;AAm+BhB;;;;GAIG;AACH;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3C,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,eAAe,GAAG,SAAS,CAAA;KAAE,CAAC;IACrD,WAAW,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,CAAA;KAAE,CAAC;IAC3D,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC,GAAG,gBAAgB,CA4nCnB"}
@@ -29,9 +29,29 @@ const MAX_TOTAL_TRANSIENT_CONTINUATIONS = 32;
29
29
  // times lets a transient slow start recover, while the cap still terminates a
30
30
  // genuinely stuck turn with a clear message instead of looping forever.
31
31
  const MAX_EMPTY_TRANSIENT_CONTINUATIONS = 3;
32
+ // How many consecutive continuations that re-stream the SAME narration without
33
+ // advancing (no in-flight tool, no completed tool) we tolerate before giving
34
+ // up. A model that degenerates into repeating one phrase ("I have the full
35
+ // HTML. Creating the extension now!") emits "new" text every continuation,
36
+ // which keeps resetting the stalled/empty budgets — so without this guard the
37
+ // stuck turn burns the entire MAX_TOTAL_TRANSIENT_CONTINUATIONS budget (each
38
+ // round re-sending any large pasted payload) before bailing. Catching the
39
+ // repeat ends it in a few rounds with a clear, actionable message instead.
40
+ const MAX_REPEATED_TRANSIENT_CONTINUATIONS = 3;
32
41
  const RETRY_BASE_DELAY_MS = 500;
33
42
  const RETRY_MAX_DELAY_MS = 8_000;
34
43
  const MAX_HISTORY_ATTACHMENT_CHARS = 60_000;
44
+ // The attachment submitted with the CURRENT turn gets a much larger cap than
45
+ // prior-history embedding. The server threads this turn's attachments into each
46
+ // action's ActionRunContext, and `create-extension`/`update-extension` host a
47
+ // pasted file verbatim from it via `contentFromAttachment` — a feature whose
48
+ // whole point is large pastes. Truncating the outbound payload to the 60K
49
+ // history cap would silently cut a >60K HTML/Alpine file before the server ever
50
+ // reads it, hosting a broken extension. Mirror the large-input tool-arg cap
51
+ // (MAX_HISTORY_LARGE_TOOL_ARGS_CHARS) so realistic pasted files survive intact;
52
+ // the trailing truncation notice still makes a pathological multi-MB paste
53
+ // visibly (not silently) capped.
54
+ const MAX_OUTBOUND_ATTACHMENT_CHARS = 200_000;
35
55
  const MAX_HISTORY_MESSAGES = 24;
36
56
  const MAX_HISTORY_TOTAL_CHARS = 64_000;
37
57
  const MAX_HISTORY_MESSAGE_CHARS = 12_000;
@@ -216,10 +236,10 @@ function truncateHistoryAttachment(text) {
216
236
  return `${text.slice(0, MAX_HISTORY_ATTACHMENT_CHARS)}\n\n[Attachment truncated after ${MAX_HISTORY_ATTACHMENT_CHARS.toLocaleString()} characters; ${omitted.toLocaleString()} characters omitted from prior chat history.]`;
217
237
  }
218
238
  function truncateOutboundAttachment(text) {
219
- if (text.length <= MAX_HISTORY_ATTACHMENT_CHARS)
239
+ if (text.length <= MAX_OUTBOUND_ATTACHMENT_CHARS)
220
240
  return text;
221
- const omitted = text.length - MAX_HISTORY_ATTACHMENT_CHARS;
222
- return `${text.slice(0, MAX_HISTORY_ATTACHMENT_CHARS)}\n\n[Attachment truncated after ${MAX_HISTORY_ATTACHMENT_CHARS.toLocaleString()} characters; ${omitted.toLocaleString()} characters omitted from the submitted attachment.]`;
241
+ const omitted = text.length - MAX_OUTBOUND_ATTACHMENT_CHARS;
242
+ return `${text.slice(0, MAX_OUTBOUND_ATTACHMENT_CHARS)}\n\n[Attachment truncated after ${MAX_OUTBOUND_ATTACHMENT_CHARS.toLocaleString()} characters; ${omitted.toLocaleString()} characters omitted from the submitted attachment.]`;
223
243
  }
224
244
  function attachmentHistoryText(attachment) {
225
245
  if (typeof attachment.text === "string" && attachment.text.length > 0) {
@@ -448,6 +468,30 @@ function hasContinuationProgress(content) {
448
468
  ? part.text.trim().length > 0
449
469
  : part.result !== undefined);
450
470
  }
471
+ /**
472
+ * Signature of the *unique* sentence-like segments in a continuation's newly
473
+ * streamed text, used to detect a degenerate repetition loop. A stuck model
474
+ * re-emits the same phrase ("I have the full HTML. Creating the extension
475
+ * now!") an arbitrary number of times per run, so the set of unique segments
476
+ * is small and stable across continuations regardless of how many times any
477
+ * single run repeated it. An empty signature (no visible text) is never a
478
+ * repeat — the stalled/empty budgets handle no-output stalls instead.
479
+ */
480
+ function continuationRepeatSignature(content) {
481
+ const text = content
482
+ .map((part) => (part.type === "text" ? part.text : ""))
483
+ .join(" ")
484
+ .toLowerCase();
485
+ const segments = text
486
+ // Split after sentence punctuation even when runs are concatenated without
487
+ // a following space ("...now!I have..."), and on newlines.
488
+ .split(/(?<=[.!?])|\n+/)
489
+ .map((segment) => segment.replace(/[^a-z0-9]+/g, " ").trim())
490
+ .filter((segment) => segment.length > 0);
491
+ if (segments.length === 0)
492
+ return "";
493
+ return Array.from(new Set(segments)).sort().join("");
494
+ }
451
495
  /**
452
496
  * True when an action was streamed but never returned a result yet — i.e. a
453
497
  * `tool_start` with no matching `tool_done`. The server is still executing it,
@@ -479,6 +523,17 @@ function stableJson(value) {
479
523
  return String(value ?? "");
480
524
  }
481
525
  }
526
+ // Bounded signature of an in-flight tool call's input, used by the stall guard
527
+ // to tell a genuinely-stuck retry (same tool, same payload, connection keeps
528
+ // dropping) apart from a legitimate retry with a CHANGED payload (e.g. the
529
+ // `create-extension` cutoff nudge tells the model to re-send a smaller body).
530
+ // A changed payload yields a different signature, so the guard resets that
531
+ // tool's stall budget instead of aborting the new attempt as a repeat. Bounded
532
+ // to length + head so we never retain a large pasted payload across rounds.
533
+ function inFlightToolInputSignature(part) {
534
+ const raw = part.argsText ?? stableJson(part.args);
535
+ return `${raw.length}${raw.slice(0, 256)}`;
536
+ }
482
537
  function toolContinuationKey(part) {
483
538
  return [
484
539
  part.toolCallId,
@@ -535,8 +590,13 @@ function autoContinueMessage(signal) {
535
590
  : signal.reason === "stream_ended"
536
591
  ? "The previous stream ended before the agent sent a final completion signal."
537
592
  : "The previous run reached an internal execution budget.";
538
- const actionInputNote = signal.reason === "run_timeout" && tool
539
- ? `\n\nThe previous run timed out while preparing the \`${tool}\` action input before the action could run. Avoid spending another whole run assembling one large tool payload. If this is \`create-extension\`, create a compact working v1 first, then use focused \`update-extension\` edits for refinements.`
593
+ // A run_timeout or a mid-stream cutoff while assembling one large action
594
+ // payload (e.g. inlining a big pasted HTML file into `create-extension`) is
595
+ // the classic trigger for the repetition/cutoff loop. Nudge the model toward
596
+ // a compact first version it can actually finish in a single run.
597
+ const cutoffPreparingAction = signal.reason === "run_timeout" || signal.reason === "stream_ended";
598
+ const actionInputNote = cutoffPreparingAction && tool
599
+ ? `\n\nThe previous run was cut off while preparing the \`${tool}\` action input before the action could finish. Avoid spending another whole run assembling one large tool payload. If this is \`create-extension\`, create a compact working v1 first, then use focused \`update-extension\` edits for refinements.`
540
600
  : "";
541
601
  return `${AUTO_CONTINUE_PROMPT}\n\nInternal note: ${reason}${actionInputNote}`;
542
602
  }
@@ -841,6 +901,22 @@ export function createAgentChatAdapter(options) {
841
901
  let stalledTransientContinuationAttempts = 0;
842
902
  let emptyTransientContinuationAttempts = 0;
843
903
  let totalTransientContinuationAttempts = 0;
904
+ let repeatedTransientContinuationAttempts = 0;
905
+ let lastContinuationRepeatSignature = null;
906
+ let recoveryGaveUpOnRepetition = false;
907
+ // Track when the same write tool is stuck in-flight across consecutive
908
+ // continuations (connection keeps dropping mid-execution). This is
909
+ // orthogonal to the text-repeat guard — in-flight tools reset
910
+ // madeProgress=true so the stalled/empty budgets never fire, but the tool
911
+ // never actually completes. After MAX_REPEATED_INFLIGHT_TOOL_STALLS
912
+ // consecutive interruptions of the same tool, bail with a clear message.
913
+ let lastInFlightToolName;
914
+ // Signature of the stuck tool's input. A retry with a changed payload
915
+ // (different signature) resets the stall count so the new attempt gets
916
+ // its own budget rather than inheriting the prior payload's.
917
+ let lastInFlightToolSignature;
918
+ let repeatedInFlightToolCount = 0;
919
+ const MAX_REPEATED_INFLIGHT_TOOL_STALLS = 3;
844
920
  const continuationHistoryFragments = [];
845
921
  const structuredContinuationFragments = [];
846
922
  let visibleContinuationPrefix = [];
@@ -857,6 +933,11 @@ export function createAgentChatAdapter(options) {
857
933
  `stale_run_continuations: ${staleRunContinuationAttempts}`,
858
934
  `stalled_transient_continuations: ${stalledTransientContinuationAttempts}`,
859
935
  `empty_transient_continuations: ${emptyTransientContinuationAttempts}`,
936
+ `repeated_transient_continuations: ${repeatedTransientContinuationAttempts}`,
937
+ `repeated_inflight_tool_stalls: ${repeatedInFlightToolCount}`,
938
+ lastInFlightToolName
939
+ ? `last_inflight_tool: ${lastInFlightToolName}`
940
+ : "",
860
941
  `total_transient_continuations: ${totalTransientContinuationAttempts}`,
861
942
  attemptedRunIds.length > 0
862
943
  ? `attempted_runs: ${attemptedRunIds.join(", ")}`
@@ -866,6 +947,9 @@ export function createAgentChatAdapter(options) {
866
947
  .join("\n");
867
948
  };
868
949
  const exhaustedRecoveryMessage = (reason) => {
950
+ if (recoveryGaveUpOnRepetition) {
951
+ 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.";
952
+ }
869
953
  if (content.length === 0 &&
870
954
  (reason === "run_timeout" ||
871
955
  reason === "no_progress" ||
@@ -927,6 +1011,9 @@ export function createAgentChatAdapter(options) {
927
1011
  staleRunContinuationAttempts,
928
1012
  stalledTransientContinuationAttempts,
929
1013
  emptyTransientContinuationAttempts,
1014
+ repeatedTransientContinuationAttempts,
1015
+ repeatedInFlightToolCount,
1016
+ lastInFlightToolName,
930
1017
  totalTransientContinuationAttempts,
931
1018
  ...extra,
932
1019
  },
@@ -941,6 +1028,9 @@ export function createAgentChatAdapter(options) {
941
1028
  staleRunContinuationAttempts,
942
1029
  stalledTransientContinuationAttempts,
943
1030
  emptyTransientContinuationAttempts,
1031
+ repeatedTransientContinuationAttempts,
1032
+ repeatedInFlightToolCount,
1033
+ lastInFlightToolName,
944
1034
  totalTransientContinuationAttempts,
945
1035
  },
946
1036
  },
@@ -1104,12 +1194,89 @@ export function createAgentChatAdapter(options) {
1104
1194
  // for the stalled/empty caps.
1105
1195
  const madeProgress = madeContentProgress || hasInFlightTool;
1106
1196
  const madeDurableToolProgress = visibleContent.some((part) => part.type === "tool-call" && part.result !== undefined);
1197
+ // In-flight tool stall guard. When the same write tool is stuck
1198
+ // in-flight because the connection keeps dropping (stream_ended),
1199
+ // hasInFlightTool=true keeps madeProgress=true and completely
1200
+ // bypasses the stalled/empty budgets. Track the last in-flight tool
1201
+ // name; when the same tool is still unresolved after
1202
+ // MAX_REPEATED_INFLIGHT_TOOL_STALLS consecutive stream_ended events,
1203
+ // bail with a clear message.
1204
+ //
1205
+ // Only count stream_ended (connection drop / reconnect failed), NOT
1206
+ // run_timeout (server legitimately still executing a slow tool). A
1207
+ // run_timeout with an in-flight tool means the server is actively
1208
+ // working and reconnection may still recover the result; a repeated
1209
+ // stream_ended means the connection keeps breaking under that payload.
1210
+ const currentInFlightToolPart = visibleContent.find((p) => p.type === "tool-call" &&
1211
+ p.result === undefined &&
1212
+ p.activity !== true);
1213
+ const currentInFlightToolName = currentInFlightToolPart?.toolName;
1214
+ const currentInFlightToolSignature = currentInFlightToolPart
1215
+ ? inFlightToolInputSignature(currentInFlightToolPart)
1216
+ : undefined;
1217
+ const isConnectionDrop = signal.reason === "stream_ended";
1218
+ if (currentInFlightToolName && isConnectionDrop) {
1219
+ if (currentInFlightToolName === lastInFlightToolName &&
1220
+ currentInFlightToolSignature === lastInFlightToolSignature) {
1221
+ repeatedInFlightToolCount += 1;
1222
+ }
1223
+ else {
1224
+ // New tool, or the same tool retried with a CHANGED (e.g.
1225
+ // smaller) payload — give the changed input a fresh stall budget
1226
+ // instead of aborting it as a repeat of the prior payload.
1227
+ repeatedInFlightToolCount = 0;
1228
+ lastInFlightToolName = currentInFlightToolName;
1229
+ lastInFlightToolSignature = currentInFlightToolSignature;
1230
+ }
1231
+ }
1232
+ else if (!currentInFlightToolName) {
1233
+ repeatedInFlightToolCount = 0;
1234
+ }
1235
+ // Degenerate repetition guard. When the model gets stuck re-streaming
1236
+ // the SAME narration every continuation without ever starting or
1237
+ // finishing a tool, each round is "new" text — so madeProgress stays
1238
+ // true and the stalled/empty budgets never trip. Compare this round's
1239
+ // unique-sentence signature to the previous round's; a match with no
1240
+ // tool progress is a non-advancing loop. Tracked by its own counter
1241
+ // so it bails after a few rounds instead of the full transient budget,
1242
+ // without perturbing the stalled/empty/stale accounting.
1243
+ const repeatSignature = continuationRepeatSignature(visibleContent);
1244
+ const isNonAdvancingRepeat = signal.reason !== "loop_limit" &&
1245
+ repeatSignature !== "" &&
1246
+ repeatSignature === lastContinuationRepeatSignature &&
1247
+ !hasInFlightTool &&
1248
+ !madeDurableToolProgress;
1107
1249
  if (signal.reason === "loop_limit") {
1108
1250
  stalledTransientContinuationAttempts = 0;
1109
1251
  emptyTransientContinuationAttempts = 0;
1110
1252
  }
1111
1253
  else {
1112
1254
  totalTransientContinuationAttempts += 1;
1255
+ // Bail when the same write tool is stuck in-flight across too many
1256
+ // consecutive continuations. Checked before the text-repeat guard
1257
+ // because hasInFlightTool=true would mask the repeat as progress.
1258
+ if (repeatedInFlightToolCount >= MAX_REPEATED_INFLIGHT_TOOL_STALLS) {
1259
+ recoveryGaveUpOnRepetition = true;
1260
+ return { ok: false, resetVisibleContent: false };
1261
+ }
1262
+ // Bail fast on a non-advancing repetition loop, well before the
1263
+ // stalled/empty/total budgets would (each round otherwise re-sends
1264
+ // the whole pasted payload). Tracked separately so it never trips
1265
+ // on legitimately-progressing runs that happen to be slow.
1266
+ if (isNonAdvancingRepeat) {
1267
+ repeatedTransientContinuationAttempts += 1;
1268
+ if (repeatedTransientContinuationAttempts >
1269
+ MAX_REPEATED_TRANSIENT_CONTINUATIONS) {
1270
+ recoveryGaveUpOnRepetition = true;
1271
+ return { ok: false, resetVisibleContent: false };
1272
+ }
1273
+ }
1274
+ else {
1275
+ repeatedTransientContinuationAttempts = 0;
1276
+ }
1277
+ if (repeatSignature) {
1278
+ lastContinuationRepeatSignature = repeatSignature;
1279
+ }
1113
1280
  // A run_timeout that produced nothing visible and no activity (the
1114
1281
  // model spent the whole soft-timeout window thinking before its
1115
1282
  // first output) is NOT an immediate give-up: a transient slow start