@agent-native/core 0.227.0 → 0.228.0-nightly-20261111004142

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 (190) hide show
  1. package/dist/action.d.ts +6 -2
  2. package/dist/agent/engine/builder-engine.js +6 -1
  3. package/dist/agent/engine/tool-call-journal-seed.d.ts +2 -0
  4. package/dist/agent/engine/tool-call-journal-seed.js +14 -0
  5. package/dist/agent/harness/store.js +14 -0
  6. package/dist/agent/production-agent.js +257 -17
  7. package/dist/agent/reaped-run-thread.js +6 -0
  8. package/dist/agent/run-manager.js +6 -0
  9. package/dist/agent/tool-call-journal.d.ts +4 -0
  10. package/dist/agent/tool-call-journal.js +12 -0
  11. package/dist/agent/types.d.ts +7 -1
  12. package/dist/app-config/agent.d.ts +3 -0
  13. package/dist/app-config/agent.js +7 -0
  14. package/dist/app-config/schema.d.ts +3 -0
  15. package/dist/application-state/handlers.d.ts +3 -3
  16. package/dist/application-state/handlers.js +6 -1
  17. package/dist/application-state/index.d.ts +3 -2
  18. package/dist/application-state/index.js +3 -2
  19. package/dist/application-state/last-used-tab.d.ts +11 -0
  20. package/dist/application-state/last-used-tab.js +43 -0
  21. package/dist/application-state/script-helpers.d.ts +26 -0
  22. package/dist/application-state/script-helpers.js +83 -15
  23. package/dist/application-state/store.d.ts +8 -0
  24. package/dist/application-state/store.js +16 -0
  25. package/dist/cli/agent.js +7 -3
  26. package/dist/cli/code-agent-executor.d.ts +1 -0
  27. package/dist/cli/code-agent-executor.js +37 -2
  28. package/dist/cli/code.js +3 -1
  29. package/dist/client/agent-sidebar-state.d.ts +2 -0
  30. package/dist/client/browser-tab-use.d.ts +9 -0
  31. package/dist/client/browser-tab-use.js +112 -0
  32. package/dist/client/chat/runtime.js +3 -1
  33. package/dist/client/error-capture.js +33 -1
  34. package/dist/client/navigation/index.d.ts +1 -0
  35. package/dist/client/navigation/index.js +1 -0
  36. package/dist/client/resources/mcp-integration-logos.js +16 -0
  37. package/dist/client/review/use-review.d.ts +28 -0
  38. package/dist/client/review/use-review.js +41 -12
  39. package/dist/client/route-state.js +2 -0
  40. package/dist/client/sse-event-processor.d.ts +1 -0
  41. package/dist/client/sse-event-processor.js +7 -1
  42. package/dist/client/use-db-sync.d.ts +8 -1
  43. package/dist/client/use-db-sync.js +6 -5
  44. package/dist/coding-tools/run-code.js +12 -2
  45. package/dist/coding-tools/sandbox/executions-store.d.ts +1 -1
  46. package/dist/coding-tools/sandbox/executions-store.js +9 -3
  47. package/dist/collab/routes.d.ts +1 -1
  48. package/dist/data-programs/execute.js +1 -1
  49. package/dist/db/schema-snapshot.generated.js +17 -3
  50. package/dist/extensions/url-safety.d.ts +1 -3
  51. package/dist/extensions/url-safety.js +23 -23
  52. package/dist/file-upload/actions/upload-image.d.ts +5 -5
  53. package/dist/file-upload/index.d.ts +1 -1
  54. package/dist/file-upload/index.js +1 -1
  55. package/dist/file-upload/s3-client.d.ts +1 -0
  56. package/dist/file-upload/s3-client.js +70 -13
  57. package/dist/file-upload/s3.d.ts +9 -0
  58. package/dist/file-upload/s3.js +14 -1
  59. package/dist/framework-package-versions.json +8 -8
  60. package/dist/jobs/run-history.js +10 -3
  61. package/dist/jobs/scheduler.js +5 -36
  62. package/dist/localization/core-messages/supplemental/ar-SA.js +35 -0
  63. package/dist/localization/core-messages/supplemental/de-DE.js +35 -0
  64. package/dist/localization/core-messages/supplemental/en-US.js +35 -0
  65. package/dist/localization/core-messages/supplemental/es-ES.js +35 -0
  66. package/dist/localization/core-messages/supplemental/fr-FR.js +35 -0
  67. package/dist/localization/core-messages/supplemental/hi-IN.js +35 -0
  68. package/dist/localization/core-messages/supplemental/ja-JP.js +35 -0
  69. package/dist/localization/core-messages/supplemental/ko-KR.js +35 -0
  70. package/dist/localization/core-messages/supplemental/pt-BR.js +35 -0
  71. package/dist/localization/core-messages/supplemental/zh-CN.js +35 -0
  72. package/dist/localization/core-messages/supplemental/zh-TW.js +35 -0
  73. package/dist/localization/default-messages.d.ts +35 -0
  74. package/dist/localization/default-messages.js +35 -0
  75. package/dist/localization/mcp-settings-messages.d.ts +35 -0
  76. package/dist/mcp/actions/list-mcp-client-connections.d.ts +8 -0
  77. package/dist/mcp/actions/list-mcp-client-connections.js +39 -0
  78. package/dist/mcp/browser-tab-target.d.ts +16 -0
  79. package/dist/mcp/browser-tab-target.js +58 -0
  80. package/dist/mcp/build-server.d.ts +1 -0
  81. package/dist/mcp/build-server.js +38 -7
  82. package/dist/mcp/call-attribution.d.ts +4 -1
  83. package/dist/mcp/call-attribution.js +3 -1
  84. package/dist/mcp/client-connections.d.ts +6 -0
  85. package/dist/mcp/client-connections.js +91 -0
  86. package/dist/mcp/connect-route.js +12 -2
  87. package/dist/mcp/connect-store.d.ts +3 -0
  88. package/dist/mcp/connect-store.js +10 -1
  89. package/dist/mcp/direct-action-call.js +1 -0
  90. package/dist/mcp/oauth-store.d.ts +6 -0
  91. package/dist/mcp/oauth-store.js +23 -0
  92. package/dist/mcp/oauth-token.d.ts +1 -0
  93. package/dist/mcp/oauth-token.js +1 -0
  94. package/dist/mcp/server.js +9 -20
  95. package/dist/mcp-client/config.d.ts +2 -0
  96. package/dist/mcp-client/manager.js +23 -0
  97. package/dist/mcp-client/workspace-servers.js +42 -10
  98. package/dist/notifications/routes.d.ts +3 -3
  99. package/dist/org/handlers.d.ts +1 -1
  100. package/dist/org/handlers.js +21 -17
  101. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  102. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  103. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  104. package/dist/provider-api/index.js +17 -36
  105. package/dist/review/actions/create-review-comment.d.ts +7 -0
  106. package/dist/review/actions/create-review-comment.js +29 -3
  107. package/dist/review/actions/delete-review-comment.js +2 -2
  108. package/dist/review/actions/list-review-comments.d.ts +3 -0
  109. package/dist/review/actions/list-review-comments.js +41 -17
  110. package/dist/review/actions/react-to-review-comment.js +2 -2
  111. package/dist/review/actions/reply-review-comment.d.ts +4 -0
  112. package/dist/review/actions/reply-review-comment.js +15 -6
  113. package/dist/review/actions/update-review-comment-anchor.d.ts +6 -0
  114. package/dist/review/actions/update-review-comment-anchor.js +39 -10
  115. package/dist/review/actions/update-review-comment.d.ts +3 -0
  116. package/dist/review/actions/update-review-comment.js +28 -3
  117. package/dist/review/errors.d.ts +33 -0
  118. package/dist/review/errors.js +51 -0
  119. package/dist/review/index.d.ts +5 -4
  120. package/dist/review/index.js +3 -2
  121. package/dist/review/registry.d.ts +51 -1
  122. package/dist/review/registry.js +117 -0
  123. package/dist/review/store.d.ts +59 -14
  124. package/dist/review/store.js +622 -101
  125. package/dist/review/suggestions/actions.js +65 -10
  126. package/dist/review/suggestions/actor-kind.d.ts +5 -0
  127. package/dist/review/suggestions/actor-kind.js +20 -0
  128. package/dist/review/suggestions/store.js +16 -2
  129. package/dist/review/suggestions/types.d.ts +13 -0
  130. package/dist/review/types.d.ts +89 -0
  131. package/dist/scripts/db/dev-query-proxy.d.ts +2 -0
  132. package/dist/scripts/db/dev-query-proxy.js +4 -1
  133. package/dist/scripts/db/query.js +3 -0
  134. package/dist/scripts/dev/shell.js +4 -0
  135. package/dist/scripts/dev-session.d.ts +31 -0
  136. package/dist/scripts/dev-session.js +58 -0
  137. package/dist/scripts/resources/list.js +53 -4
  138. package/dist/scripts/runner.js +22 -4
  139. package/dist/search/query.d.ts +11 -5
  140. package/dist/search/query.js +52 -15
  141. package/dist/search/tokenize.d.ts +1 -0
  142. package/dist/search/tokenize.js +3 -0
  143. package/dist/secrets/api-keys.d.ts +4 -0
  144. package/dist/secrets/api-keys.js +5 -0
  145. package/dist/secrets/register.d.ts +2 -0
  146. package/dist/secrets/routes.d.ts +2 -2
  147. package/dist/secrets/routes.js +85 -6
  148. package/dist/secrets/storage.d.ts +2 -0
  149. package/dist/secrets/storage.js +34 -9
  150. package/dist/secrets/substitution.d.ts +3 -2
  151. package/dist/secrets/substitution.js +56 -7
  152. package/dist/server/action-discovery.js +5 -0
  153. package/dist/server/action-routes.js +8 -6
  154. package/dist/server/agent-chat/prompt-resources.js +117 -79
  155. package/dist/server/agent-chat/script-entries.js +15 -8
  156. package/dist/server/agent-chat-plugin.js +9 -4
  157. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  158. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  159. package/dist/server/agents-bundle.d.ts +3 -1
  160. package/dist/server/agents-bundle.js +4 -4
  161. package/dist/server/csrf.js +1 -0
  162. package/dist/server/dev-action-bridge.d.ts +2 -0
  163. package/dist/server/dev-action-bridge.js +34 -5
  164. package/dist/server/first-touch-route.d.ts +1 -1
  165. package/dist/server/http-response-telemetry.js +4 -0
  166. package/dist/server/loopback.js +3 -1
  167. package/dist/server/request-context.d.ts +26 -0
  168. package/dist/shared/app-state-routes.d.ts +7 -0
  169. package/dist/shared/app-state-routes.js +7 -0
  170. package/dist/shared/mcp-client-connections.d.ts +41 -0
  171. package/dist/shared/mcp-client-connections.js +18 -0
  172. package/dist/shared/mcp-connect-content.d.ts +18 -0
  173. package/dist/shared/mcp-connect-content.js +200 -172
  174. package/dist/shared/runtime-config.js +5 -1
  175. package/dist/templates/chat/.agents/skills/context-awareness/SKILL.md +13 -0
  176. package/dist/templates/default/.agents/skills/context-awareness/SKILL.md +13 -0
  177. package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +13 -0
  178. package/dist/tracking/providers.d.ts +7 -0
  179. package/dist/tracking/providers.js +39 -17
  180. package/dist/triggers/condition-evaluator.d.ts +2 -0
  181. package/dist/triggers/condition-evaluator.js +93 -3
  182. package/dist/triggers/dispatcher.js +24 -6
  183. package/docs/AGENTS.md +16 -16
  184. package/docs/content/create-with-an-agent.mdx +10 -4
  185. package/docs/content/durable-resume.mdx +11 -2
  186. package/docs/content/toolkit-comments-review.mdx +54 -1
  187. package/package.json +5 -4
  188. package/src/templates/chat/.agents/skills/context-awareness/SKILL.md +13 -0
  189. package/src/templates/default/.agents/skills/context-awareness/SKILL.md +13 -0
  190. package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +13 -0
package/dist/action.d.ts CHANGED
@@ -38,6 +38,8 @@ export declare function resolveToolMaxResultChars(action: {
38
38
  }): number;
39
39
  export interface ActionRunContext {
40
40
  send?: (event: AgentChatEvent) => void;
41
+ /** Request a clean empty final after this invocation succeeds. Available only to agent tool calls; does not stop the model or override errors, receipts, or later instructions. */
42
+ settle?: () => void;
41
43
  /**
42
44
  * Result cap the agent loop enforces on this call, from
43
45
  * `resolveToolMaxResultChars`. Absent outside the agent loop.
@@ -92,12 +94,14 @@ export interface ActionRunContext {
92
94
  approvedToolCallKey?: string;
93
95
  /**
94
96
  * The client behind a `caller: "mcp"` call. `name` and `version` are its
95
- * self-reported `clientInfo`. `oauthClientId` is the OAuth client its
96
- * credential was issued to.
97
+ * self-reported `clientInfo`, and `userAgent` its self-reported HTTP
98
+ * User-Agent. `oauthClientId` is the OAuth client its credential was issued
99
+ * to.
97
100
  */
98
101
  mcpClient?: {
99
102
  name?: string;
100
103
  version?: string;
104
+ userAgent?: string;
101
105
  oauthClientId?: string;
102
106
  };
103
107
  }
@@ -194,7 +194,12 @@ class BuilderEngine {
194
194
  const toolNameMap = createProviderToolNameMap(opts.tools, opts.messages);
195
195
  const providerTools = limitProviderTools(opts.tools);
196
196
  const messages = engineMessagesToBuilderGatewayAnthropic(await inlineOwnedToolResultImages(opts.messages), toolNameMap);
197
- const tools = engineToolsToAnthropic(providerTools, toolNameMap);
197
+ const anthropicTools = engineToolsToAnthropic(providerTools, toolNameMap);
198
+ // The gateway sends GPT tools in OpenAI strict mode unless told otherwise,
199
+ // and strict mode makes the model fill every optional action argument.
200
+ const tools = /^gpt-/i.test(model)
201
+ ? anthropicTools.map((tool) => ({ ...tool, strict: false }))
202
+ : anthropicTools;
198
203
  const thinkingBudget = opts.providerOptions?.anthropic?.thinking?.budgetTokens;
199
204
  const reasoningEffort = normalizeReasoningEffortForModel(model, opts.reasoningEffort ??
200
205
  (typeof thinkingBudget === "number"
@@ -1,3 +1,4 @@
1
+ import type { WriteReceipt } from "../../action.js";
1
2
  import { type ArtifactReceipt } from "../../artifacts/detect.js";
2
3
  import { type ToolCallJournal } from "../tool-call-journal.js";
3
4
  import type { AgentChatEvent } from "../types.js";
@@ -25,6 +26,7 @@ export interface PriorTurnToolResultSummary {
25
26
  content: string;
26
27
  isError: boolean;
27
28
  artifacts?: ArtifactReceipt[];
29
+ receipt?: WriteReceipt;
28
30
  }
29
31
  export type PriorTurnToolCallJournalRead = {
30
32
  status: "read";
@@ -1,6 +1,7 @@
1
1
  import { isArtifactReceipt, } from "../../artifacts/detect.js";
2
2
  import { AgentRunJournalUnreadableError, getCompletedAgentExecutionSteps, getCurrentTurnEventsForThread, } from "../run-store.js";
3
3
  import { classifyToolCallJournal, isConsistentToolCallInputFingerprint, toolCallInputFingerprint, } from "../tool-call-journal.js";
4
+ import { readWriteReceipt } from "../write-receipts.js";
4
5
  const LEDGER_READ_RETRY_MS = 250;
5
6
  function isRecord(value) {
6
7
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -75,6 +76,7 @@ function durableStepToolDone(event, row, unreadable) {
75
76
  const mcpApp = durableStepMcpApp(outcome.mcpApp);
76
77
  const chatUI = durableStepChatUI(outcome.chatUI);
77
78
  const fileMutation = durableStepFileMutation(outcome.fileMutation);
79
+ const receipt = readWriteReceipt({ _receipt: outcome.receipt });
78
80
  return {
79
81
  type: "tool_done",
80
82
  tool: event.tool,
@@ -95,6 +97,16 @@ function durableStepToolDone(event, row, unreadable) {
95
97
  ? { errorCode: outcome.errorCode }
96
98
  : {}),
97
99
  completedSideEffect: outcome.completedSideEffect,
100
+ ...(outcome.hasAgentWarnings === true
101
+ ? { hasAgentWarnings: true }
102
+ : {}),
103
+ ...(receipt ? { receipt } : {}),
104
+ ...(!outcome.isError &&
105
+ outcome.actionSettled === true &&
106
+ outcome.settledOnCleanToolStop === true &&
107
+ outcome.replayTruncated !== true
108
+ ? { actionSettled: true, settledOnCleanToolStop: true }
109
+ : {}),
98
110
  ...(outcome.replayed === true ? { replayed: true } : {}),
99
111
  argumentsHash: input.argumentsHash,
100
112
  ...(artifacts.length > 0 ? { artifacts } : {}),
@@ -351,11 +363,13 @@ export async function loadPriorTurnToolCallJournal(threadId, turnId, options = {
351
363
  matchedStart: call !== undefined,
352
364
  });
353
365
  const artifacts = event.artifacts?.filter(isArtifactReceipt);
366
+ const receipt = readWriteReceipt({ _receipt: event.receipt });
354
367
  priorToolResults.push({
355
368
  name: event.tool,
356
369
  input: event.input ?? call?.input,
357
370
  content: event.result,
358
371
  isError: event.isError === true,
372
+ ...(receipt ? { receipt } : {}),
359
373
  ...(artifacts && artifacts.length > 0 ? { artifacts } : {}),
360
374
  });
361
375
  }
@@ -151,6 +151,20 @@ export const agentHarnessSessionsStore = defineStore({
151
151
  name: "agent-harness-sessions-run-policy",
152
152
  sql: "ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS run_policy TEXT",
153
153
  },
154
+ {
155
+ // Local runtimes skip legacy v6/v7 and never re-run a recorded baseline.
156
+ // The injected client keeps a rolled-back caller transaction's catalog
157
+ // view from marking this done without the columns.
158
+ name: "agent-harness-sessions-approval-and-expiry-columns",
159
+ run: async (exec) => {
160
+ for (const column of [
161
+ "approval_resolution_id",
162
+ "expiry_cleanup_id",
163
+ ]) {
164
+ await ensureColumnExists("agent_harness_sessions", column, `ALTER TABLE agent_harness_sessions ADD COLUMN IF NOT EXISTS ${column} TEXT`, { injectedClient: exec });
165
+ }
166
+ },
167
+ },
154
168
  ],
155
169
  });
156
170
  export function ensureAgentHarnessSessionTables() {
@@ -7,7 +7,7 @@ import { defineEventHandler, getHeader, setResponseHeader, setResponseStatus, ge
7
7
  import { parseA2AAgentActivityPart } from "../a2a/activity.js";
8
8
  import "../authorization/check-action.js";
9
9
  import { actionCallIsRetrySafe, actionChangeResource, classifyActionCall, } from "../action-call-classification.js";
10
- import { runOutsideActionInvocation, vouchedOutcome } from "../action-frame.js";
10
+ import { actionExecutionStorage, runOutsideActionInvocation, vouchedOutcome, } from "../action-frame.js";
11
11
  import { ACTION_CHAT_UI_RECORD_CHANGE_RENDERER, normalizeActionChangeResult, } from "../action-ui.js";
12
12
  import { AgentConnectionRequiredError, describeToolParameterSignature, isActionContractError, isActionHiddenFromEveryAgentSurface, isAgentActionStopError, isAgentConnectionRequiredError, actionCallIntent, ActionInputValidationError, telemetryInvalidPaths, invocationEffect, runPreparedActionCall, boundActionCallIntent, preparedCallArguments, preparedCallNeedsApproval, recordInvalidActionCall, resolveToolMaxResultChars, stripUnsupportedSchemaKeywords, } from "../action.js";
13
13
  import { resolveAgentKitIntegrationReferences } from "../agentkit/capabilities.js";
@@ -109,7 +109,7 @@ import { getStrictToolInputValidator } from "./strict-tool-input.js";
109
109
  import { normalizeSubmissionDeadlineMetadata } from "./submission-deadline.js";
110
110
  import { consumeAgentToolApproval, createAgentToolApproval, ensureAgentToolApprovalTable, hasPendingCurrentTurnApproval, isAgentToolAlwaysAllowed, resolveAgentToolApprovalTurnId, } from "./tool-approval-store.js";
111
111
  import { toolCallInputSignature } from "./tool-call-input-signature.js";
112
- import { ambiguousLegacyCallEffectKey, findAmbiguousLegacyJournalEntryForCall, findUnresolvedJournalStepForCall, findInterruptedLegacyJournalEntryForCall, findReplayableJournalEntry, hasJournalStepForCall, isUnavailableToolCallInput, journalEntriesAmbiguousInHistory, journalEntriesRepresentedInHistory, journalCallEffects, journalCallIdentities, matchToolCallResultsInHistory, toolCallIdentityKey, toolCallInputFingerprint, unmatchedAmbiguousLegacyToolCallResultsInHistory, } from "./tool-call-journal.js";
112
+ import { ambiguousLegacyCallEffectKey, buildResumeJournalNote, findAmbiguousLegacyJournalEntryForCall, findUnresolvedJournalStepForCall, findInterruptedLegacyJournalEntryForCall, findReplayableJournalEntry, hasJournalStepForCall, isUnavailableToolCallInput, journalEntriesAmbiguousInHistory, journalEntriesRepresentedInHistory, journalCallEffects, journalCallIdentities, matchToolCallResultsInHistory, toolCallIdentityKey, toolCallInputFingerprint, unmatchedAmbiguousLegacyToolCallResultsInHistory, } from "./tool-call-journal.js";
113
113
  import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
114
114
  import { formatToolIndex } from "./tool-index.js";
115
115
  import { describeToolResultImages, extractAgentImagesFromActionResult, } from "./tool-result-images.js";
@@ -2875,6 +2875,7 @@ function serializeDurableExecutionStepOutcome(outcome) {
2875
2875
  replayTruncated: true,
2876
2876
  replayOmittedFields: omittedFields,
2877
2877
  ...(outcome.errorCode ? { errorCode: outcome.errorCode } : {}),
2878
+ ...(outcome.hasAgentWarnings ? { hasAgentWarnings: true } : {}),
2878
2879
  ...(outcome.receipt ? { receipt: outcome.receipt } : {}),
2879
2880
  };
2880
2881
  let serialized;
@@ -5028,6 +5029,9 @@ export async function runAgentLoop(opts) {
5028
5029
  if (followUpRunId)
5029
5030
  send({ type: "suggestions", suggestions: [] });
5030
5031
  let model = normalizeModelForEngine(engine, opts.model);
5032
+ let settlementCandidate = false;
5033
+ let settledEmptyFinal = false;
5034
+ let turnHasActionWarnings = false;
5031
5035
  let outcomeReported = false;
5032
5036
  const reportOutcome = (outcome) => {
5033
5037
  if (outcomeReported)
@@ -5045,11 +5049,11 @@ export async function runAgentLoop(opts) {
5045
5049
  // provider reads alongside those results.
5046
5050
  const deliverSteeredMessagesAtToolBoundary = async (toolResultParts) => {
5047
5051
  if (signal.aborted)
5048
- return;
5052
+ return 0;
5049
5053
  const threadId = opts.threadId;
5050
5054
  const runId = opts.runId;
5051
5055
  if (!threadId || !runId)
5052
- return;
5056
+ return 0;
5053
5057
  const { claimSteeredQueuedMessages } = await import("../chat-threads/store.js");
5054
5058
  let delivered;
5055
5059
  try {
@@ -5059,7 +5063,7 @@ export async function runAgentLoop(opts) {
5059
5063
  // The claim is one transaction, so a failure leaves the message queued
5060
5064
  // and on screen; it sends at a later boundary or after this reply.
5061
5065
  console.warn("[agent-chat] Send now delivery skipped at a tool boundary:", error instanceof Error ? error.message : String(error));
5062
- return;
5066
+ return 0;
5063
5067
  }
5064
5068
  for (const { queued, record } of delivered) {
5065
5069
  toolResultParts.push(...buildUserContentWithAttachments({
@@ -5073,6 +5077,7 @@ export async function runAgentLoop(opts) {
5073
5077
  continuationMessageId: record.continuationMessageId,
5074
5078
  });
5075
5079
  }
5080
+ return delivered.length;
5076
5081
  };
5077
5082
  const deferSteeredMessagesPastFinalResponse = async () => {
5078
5083
  if (!opts.threadId || !opts.runId)
@@ -5260,6 +5265,11 @@ export async function runAgentLoop(opts) {
5260
5265
  recoverCompletedExecutionSteps: opts.recoverCompletedExecutionSteps === true,
5261
5266
  });
5262
5267
  const toolCallJournal = journalRead.status === "read" ? journalRead.toolCallJournal : null;
5268
+ turnHasActionWarnings =
5269
+ journalRead.status === "read" &&
5270
+ journalRead.events.some((event) => event.type === "tool_done" &&
5271
+ (event.hasAgentWarnings === true ||
5272
+ event.result.includes("<agent-warning ")));
5263
5273
  const journalHistory = messages.slice(findCurrentTurnStartForContinuation(messages));
5264
5274
  const journalEntriesAlreadyRepresented = toolCallJournal
5265
5275
  ? journalEntriesRepresentedInHistory(toolCallJournal, journalHistory)
@@ -5308,6 +5318,111 @@ export async function runAgentLoop(opts) {
5308
5318
  };
5309
5319
  toolCallHistory.push(...journaledPriorToolCalls);
5310
5320
  toolResultHistory.push(...journaledPriorToolResults);
5321
+ if ((internalContinuationTurn ||
5322
+ opts.recoverCompletedExecutionSteps === true) &&
5323
+ toolCallJournal &&
5324
+ toolCallJournal.interrupted.length === 0) {
5325
+ const tail = journalHistory.at(-1);
5326
+ const continuationText = tail?.role === "user" &&
5327
+ tail.content.length === 1 &&
5328
+ tail.content[0]?.type === "text"
5329
+ ? tail.content[0].text
5330
+ : undefined;
5331
+ const journalNote = buildResumeJournalNote(toolCallJournal);
5332
+ const safeContinuation = continuationText !== undefined &&
5333
+ [
5334
+ "run_timeout",
5335
+ "loop_limit",
5336
+ "stream_ended",
5337
+ "gateway_timeout",
5338
+ "rate_limited",
5339
+ "network_interrupted",
5340
+ "no_progress",
5341
+ ].some((reason) => [undefined, journalNote ?? undefined].some((note) => {
5342
+ const expected = [];
5343
+ appendAgentLoopContinuation(expected, reason, note ? { journalNote: note } : {});
5344
+ return (expected[0]?.content[0]?.type === "text" &&
5345
+ expected[0].content[0].text === continuationText);
5346
+ }));
5347
+ const lastMessage = journalHistory.at(safeContinuation ? -2 : -1);
5348
+ const previousMessage = journalHistory.at(safeContinuation ? -3 : -2);
5349
+ const pairs = matchToolCallResultsInHistory(journalHistory);
5350
+ const calls = previousMessage?.role === "assistant"
5351
+ ? previousMessage.content.filter((part) => part.type === "tool-call")
5352
+ : [];
5353
+ const events = journalRead.status === "read" ? journalRead.events : [];
5354
+ const batchIndex = events
5355
+ .map((event) => event.type === "rich_event" &&
5356
+ event.event.namespace === "agent-native.tool-batch" &&
5357
+ event.event.name === "declared" &&
5358
+ event.event.version === 1)
5359
+ .lastIndexOf(true);
5360
+ const batchEvent = batchIndex < 0 ? undefined : events.at(batchIndex);
5361
+ const batchData = batchEvent?.type === "rich_event" ? batchEvent.event.data : undefined;
5362
+ const batchCalls = isRecord(batchData) && Array.isArray(batchData.calls)
5363
+ ? batchData.calls
5364
+ : [];
5365
+ const afterBatch = events.slice(batchIndex + 1);
5366
+ const starts = afterBatch.filter((event) => event.type === "tool_start");
5367
+ const dones = afterBatch.filter((event) => event.type === "tool_done");
5368
+ if (batchIndex >= 0 &&
5369
+ batchCalls.length > 0 &&
5370
+ batchCalls.every((call) => isRecord(call) &&
5371
+ typeof call.id === "string" &&
5372
+ call.id.length > 0 &&
5373
+ typeof call.name === "string" &&
5374
+ call.name.length > 0) &&
5375
+ new Set(batchCalls.map((call) => call.id)).size === batchCalls.length &&
5376
+ starts.length === batchCalls.length &&
5377
+ dones.length === batchCalls.length &&
5378
+ !afterBatch.some((event) => event.type === "user_message" ||
5379
+ event.type === "tool_input_start" ||
5380
+ event.type === "tool_input_delta" ||
5381
+ (event.type === "model_stream" &&
5382
+ event.status === "end" &&
5383
+ event.reason !== "end_turn")) &&
5384
+ lastMessage?.role === "user" &&
5385
+ lastMessage.content.length > 0 &&
5386
+ lastMessage.content.every((part) => part.type === "tool-result" &&
5387
+ !part.isError &&
5388
+ part.outcome !== "unknown" &&
5389
+ !part.approvalPause) &&
5390
+ batchCalls.every((call) => {
5391
+ if (!isRecord(call) ||
5392
+ typeof call.id !== "string" ||
5393
+ typeof call.name !== "string")
5394
+ return false;
5395
+ const matchingStarts = starts.filter((start) => start.id === call.id && start.tool === call.name);
5396
+ if (matchingStarts.length !== 1 || !matchingStarts[0]?.executionStepId)
5397
+ return false;
5398
+ const stepId = matchingStarts[0].executionStepId;
5399
+ const matchingDones = dones.filter((done) => done.executionStepId === stepId &&
5400
+ done.tool === call.name &&
5401
+ done.id === call.id);
5402
+ const matchingPairs = pairs.filter((pair) => calls.includes(pair.call) &&
5403
+ lastMessage.content.includes(pair.result) &&
5404
+ pair.call.executionStepId === stepId &&
5405
+ pair.result.executionStepId === stepId &&
5406
+ pair.call.name === call.name &&
5407
+ pair.result.toolName === call.name);
5408
+ const entries = toolCallJournal.completed.filter((entry) => entry.executionStepId === stepId && entry.tool === call.name);
5409
+ return (matchingDones.length === 1 &&
5410
+ matchingDones[0]?.actionSettled === true &&
5411
+ matchingDones[0].settledOnCleanToolStop === true &&
5412
+ !matchingDones[0].isError &&
5413
+ matchingDones[0].outcomeUnknown !== true &&
5414
+ matchingPairs.length === 1 &&
5415
+ entries.length === 1 &&
5416
+ entries[0]?.actionSettled === true &&
5417
+ entries[0].settledOnCleanToolStop === true &&
5418
+ !entries[0].isError);
5419
+ }) &&
5420
+ !toolResultHistory.some((result) => result.isError) &&
5421
+ !turnHasActionWarnings &&
5422
+ !writeReceiptGuard(turnReceipts(), false)) {
5423
+ settlementCandidate = true;
5424
+ }
5425
+ }
5311
5426
  const unreadableJournalStop = journalRead.status === "unreadable" &&
5312
5427
  (journalRead.code === "private_history_unrecoverable" ||
5313
5428
  internalContinuationTurn ||
@@ -6181,6 +6296,9 @@ export async function runAgentLoop(opts) {
6181
6296
  }
6182
6297
  if (followUpResults.length > 0)
6183
6298
  messages.push({ role: "user", content: followUpResults });
6299
+ if (terminalStopReason === "max_tokens" || followUpResults.length > 0) {
6300
+ settlementCandidate = false;
6301
+ }
6184
6302
  if (terminalStopReason === "max_tokens") {
6185
6303
  if (hasEmptyFinalResponse) {
6186
6304
  preserveReasoningEffortForEmptyResponseRetries = true;
@@ -6222,7 +6340,13 @@ export async function runAgentLoop(opts) {
6222
6340
  flushUnstreamedAssistantText();
6223
6341
  continue;
6224
6342
  }
6225
- if (hasEmptyFinalResponse) {
6343
+ const canSettleEmptyFinal = hasEmptyFinalResponse &&
6344
+ settlementCandidate &&
6345
+ terminalStopReason === "end_turn" &&
6346
+ followUpResults.length === 0 &&
6347
+ !isSignalAborted(signal) &&
6348
+ !writeReceiptGuard(turnReceipts(), receiptGuardRetried);
6349
+ if (hasEmptyFinalResponse && !canSettleEmptyFinal) {
6226
6350
  completedFollowUpSuggestions = undefined;
6227
6351
  if (emptyFinalResponseRetries < EMPTY_FINAL_RESPONSE_RETRY_LIMIT) {
6228
6352
  emptyFinalResponseRetries += 1;
@@ -6277,6 +6401,7 @@ export async function runAgentLoop(opts) {
6277
6401
  : receiptGuard;
6278
6402
  }
6279
6403
  if (guard) {
6404
+ settlementCandidate = false;
6280
6405
  completedFollowUpSuggestions = undefined;
6281
6406
  const retryMessage = typeof guard === "string" ? guard : guard.retryMessage;
6282
6407
  const fallbackMessage = typeof guard === "string" ? guard : guard.fallbackMessage;
@@ -6318,7 +6443,8 @@ export async function runAgentLoop(opts) {
6318
6443
  }
6319
6444
  else {
6320
6445
  flushUnstreamedAssistantText();
6321
- if (followUpRunId &&
6446
+ if (!canSettleEmptyFinal &&
6447
+ followUpRunId &&
6322
6448
  completedFollowUpSuggestions === undefined &&
6323
6449
  (terminalStopReason === "end_turn" || followUpResults.length > 0)) {
6324
6450
  completingFollowUpSuggestions = true;
@@ -6331,6 +6457,7 @@ export async function runAgentLoop(opts) {
6331
6457
  continue;
6332
6458
  }
6333
6459
  }
6460
+ settledEmptyFinal = canSettleEmptyFinal && !guard;
6334
6461
  emptyFinalResponseRetries = 0;
6335
6462
  preserveReasoningEffortForEmptyResponseRetries = false;
6336
6463
  consecutiveMaxTokensContinuations = 0;
@@ -6346,6 +6473,7 @@ export async function runAgentLoop(opts) {
6346
6473
  preserveReasoningEffortForEmptyResponseRetries = false;
6347
6474
  consecutiveMaxTokensContinuations = 0;
6348
6475
  const toolCallTruncatedByOutputCap = terminalStopReason === "max_tokens";
6476
+ const toolCallHasCleanStop = terminalStopReason === "tool_use";
6349
6477
  if (toolCallTruncatedByOutputCap) {
6350
6478
  if (truncatedToolCallRetries < TRUNCATED_TOOL_CALL_RETRY_LIMIT) {
6351
6479
  truncatedToolCallRetries += 1;
@@ -6361,6 +6489,25 @@ export async function runAgentLoop(opts) {
6361
6489
  effectiveMaxOutputTokens = opts.maxOutputTokens;
6362
6490
  }
6363
6491
  flushUnstreamedAssistantText();
6492
+ settlementCandidate = false;
6493
+ const batchCallIds = toolCallParts.map((call) => call.id);
6494
+ if (currentExecutionFence() &&
6495
+ toolCallHasCleanStop &&
6496
+ followUpResults.length === 0 &&
6497
+ !send.isPrivateTurn() &&
6498
+ batchCallIds.every(Boolean) &&
6499
+ new Set(batchCallIds).size === batchCallIds.length) {
6500
+ await send({
6501
+ type: "rich_event",
6502
+ event: {
6503
+ namespace: "agent-native.tool-batch",
6504
+ name: "declared",
6505
+ version: 1,
6506
+ data: { calls: toolCallParts.map(({ id, name }) => ({ id, name })) },
6507
+ },
6508
+ });
6509
+ }
6510
+ const settledToolCalls = new Set();
6364
6511
  let requestedActionStop = null;
6365
6512
  // Tool closures set the stop, so control flow still reads it as null here.
6366
6513
  const actionStopRequested = () => requestedActionStop !== null;
@@ -7103,6 +7250,8 @@ export async function runAgentLoop(opts) {
7103
7250
  throw new Error(`Execution step ${toolCall.id} has no stored result text`);
7104
7251
  }
7105
7252
  const isError = outcome.isError === true;
7253
+ if (outcome.hasAgentWarnings === true)
7254
+ turnHasActionWarnings = true;
7106
7255
  const completedSideEffect = outcome.completedSideEffect === true;
7107
7256
  const artifacts = Array.isArray(outcome.artifacts)
7108
7257
  ? outcome.artifacts.filter(isArtifactReceipt)
@@ -7130,8 +7279,21 @@ export async function runAgentLoop(opts) {
7130
7279
  ...(errorCode ? { errorCode } : {}),
7131
7280
  completedSideEffect,
7132
7281
  replayed: true,
7282
+ ...(outcome.hasAgentWarnings === true
7283
+ ? { hasAgentWarnings: true }
7284
+ : {}),
7133
7285
  ...(artifacts.length > 0 ? { artifacts } : {}),
7134
7286
  ...(receipt ? { receipt } : {}),
7287
+ ...(outcome.isError === false &&
7288
+ outcome.actionSettled === true &&
7289
+ outcome.settledOnCleanToolStop === true &&
7290
+ toolCallHasCleanStop &&
7291
+ outcome.replayTruncated !== true
7292
+ ? {
7293
+ actionSettled: true,
7294
+ settledOnCleanToolStop: true,
7295
+ }
7296
+ : {}),
7135
7297
  ...(mcpApp ? { mcpApp } : {}),
7136
7298
  ...(chatUI ? { chatUI } : {}),
7137
7299
  ...(chatUI && outcome.chatUIResult !== undefined
@@ -7140,6 +7302,14 @@ export async function runAgentLoop(opts) {
7140
7302
  ...(fileMutation ? { fileMutation } : {}),
7141
7303
  });
7142
7304
  recordToolResult(result, isError, artifacts, receipt, false);
7305
+ if (!isError &&
7306
+ outcome.isError === false &&
7307
+ typeof outcome.completedSideEffect === "boolean" &&
7308
+ outcome.actionSettled === true &&
7309
+ outcome.settledOnCleanToolStop === true &&
7310
+ outcome.replayTruncated !== true) {
7311
+ settledToolCalls.add(originalToolCall);
7312
+ }
7143
7313
  if (!isError) {
7144
7314
  noteToolCallSucceeded(actionEntry, toolCall.name, toolCall.input);
7145
7315
  }
@@ -7251,6 +7421,16 @@ export async function runAgentLoop(opts) {
7251
7421
  result,
7252
7422
  ...(replayedFailure ? { isError: true } : {}),
7253
7423
  ...(journaled.errorCode ? { errorCode: journaled.errorCode } : {}),
7424
+ ...(journaled.receipt ? { receipt: journaled.receipt } : {}),
7425
+ ...(!replayedFailure &&
7426
+ journaled.actionSettled &&
7427
+ journaled.settledOnCleanToolStop &&
7428
+ toolCallHasCleanStop
7429
+ ? {
7430
+ actionSettled: true,
7431
+ settledOnCleanToolStop: true,
7432
+ }
7433
+ : {}),
7254
7434
  completedSideEffect: true,
7255
7435
  replayed: true,
7256
7436
  ...(journaled.artifacts?.length
@@ -7267,7 +7447,11 @@ export async function runAgentLoop(opts) {
7267
7447
  }
7268
7448
  : {}),
7269
7449
  });
7270
- recordToolResult(result, replayedFailure, journaled.artifacts, undefined, false);
7450
+ recordToolResult(result, replayedFailure, journaled.artifacts, journaled.receipt, false);
7451
+ if (!replayedFailure &&
7452
+ journaled.actionSettled &&
7453
+ journaled.settledOnCleanToolStop)
7454
+ settledToolCalls.add(originalToolCall);
7271
7455
  if (!replayedFailure) {
7272
7456
  noteToolCallSucceeded(actionEntry, toolCall.name, toolCall.input);
7273
7457
  }
@@ -7548,6 +7732,10 @@ export async function runAgentLoop(opts) {
7548
7732
  let toolArtifacts = [];
7549
7733
  let fileMutation;
7550
7734
  let receipt;
7735
+ let requestedSettlement = false;
7736
+ const settlementWasRequested = () => requestedSettlement;
7737
+ let settlementOnReturn = false;
7738
+ let requiresNextAction = false;
7551
7739
  try {
7552
7740
  // The run may have been aborted while we waited above for an
7553
7741
  // interrupted tool's ledger result (the wait can poll for minutes).
@@ -7597,6 +7785,13 @@ export async function runAgentLoop(opts) {
7597
7785
  }
7598
7786
  const actionContext = {
7599
7787
  send,
7788
+ settle: () => {
7789
+ if (actionExecutionOutcome.evidence &&
7790
+ actionExecutionStorage?.getStore()?.frame ===
7791
+ actionExecutionOutcome.evidence) {
7792
+ requestedSettlement = true;
7793
+ }
7794
+ },
7600
7795
  userEmail: actionUserEmail ?? undefined,
7601
7796
  orgId: actionOrgId,
7602
7797
  appId: opts.appId,
@@ -7750,8 +7945,12 @@ export async function runAgentLoop(opts) {
7750
7945
  signal.addEventListener("abort", () => reject(new Error("Run aborted")), { once: true });
7751
7946
  }),
7752
7947
  ]);
7948
+ settlementOnReturn = settlementWasRequested();
7753
7949
  const mcpResult = isMcpActionResult(raw) ? raw : null;
7754
7950
  const rawForAgent = mcpResult ? mcpResult.text : raw;
7951
+ requiresNextAction =
7952
+ isRecord(rawForAgent) &&
7953
+ typeof rawForAgent.nextRequiredAction === "string";
7755
7954
  if (mcpResult &&
7756
7955
  mcpResult.raw &&
7757
7956
  typeof mcpResult.raw === "object" &&
@@ -7942,8 +8141,18 @@ export async function runAgentLoop(opts) {
7942
8141
  }
7943
8142
  const agentWarnings = drainAgentWarnings();
7944
8143
  if (agentWarnings.length > 0) {
8144
+ turnHasActionWarnings = true;
7945
8145
  result = `${result}\n\n${formatAgentWarningsForToolResult(agentWarnings)}`;
7946
8146
  }
8147
+ const actionSettled = Boolean(!isError &&
8148
+ !toolResultLost &&
8149
+ !isSignalAborted(signal) &&
8150
+ settlementOnReturn &&
8151
+ !requiresNextAction &&
8152
+ callEffect !== "unknown" &&
8153
+ toolCallHasCleanStop &&
8154
+ agentWarnings.length === 0 &&
8155
+ !writeReceiptGuard(receipt ? [{ tool: toolCall.name, ...receipt }] : [], false));
7947
8156
  const resolvedChatUI = actionChatUIForResult(toolCall.name, actionEntry, toolCall.input, chatUIResult, isError);
7948
8157
  // A write refreshes other sessions, including one that failed after
7949
8158
  // it ran: its changes are applied all the same.
@@ -7989,6 +8198,15 @@ export async function runAgentLoop(opts) {
7989
8198
  ? { errorCode: toolErrorCode }
7990
8199
  : {}),
7991
8200
  completedSideEffect: callEffect === "committed",
8201
+ ...(!isPrivateCall() && actionSettled
8202
+ ? {
8203
+ actionSettled: true,
8204
+ settledOnCleanToolStop: true,
8205
+ }
8206
+ : {}),
8207
+ ...(agentWarnings.length > 0
8208
+ ? { hasAgentWarnings: true }
8209
+ : {}),
7992
8210
  ...(isPrivateCall() ? {} : { artifacts: toolArtifacts }),
7993
8211
  ...(!isPrivateCall() && receipt ? { receipt } : {}),
7994
8212
  ...(!isPrivateCall() && mcpApp ? { mcpApp } : {}),
@@ -8033,9 +8251,21 @@ export async function runAgentLoop(opts) {
8033
8251
  ...(fileMutation ? { fileMutation } : {}),
8034
8252
  ...(toolArtifacts.length > 0 ? { artifacts: toolArtifacts } : {}),
8035
8253
  ...(receipt ? { receipt } : {}),
8254
+ ...(actionSettled
8255
+ ? {
8256
+ actionSettled: true,
8257
+ settledOnCleanToolStop: true,
8258
+ }
8259
+ : {}),
8260
+ ...(agentWarnings.length > 0
8261
+ ? { hasAgentWarnings: true }
8262
+ : {}),
8036
8263
  });
8037
8264
  toolDonePersistenceFailed = false;
8038
8265
  recordToolResult(result, isError, toolArtifacts, receipt);
8266
+ if (actionSettled) {
8267
+ settledToolCalls.add(originalToolCall);
8268
+ }
8039
8269
  if (isError) {
8040
8270
  if (callEffect === "committed") {
8041
8271
  resetAfterSuccessfulWrite(toolCall.name, toolCall.input);
@@ -8222,7 +8452,15 @@ export async function runAgentLoop(opts) {
8222
8452
  : toolResultParts,
8223
8453
  });
8224
8454
  if (!actionStopRequested()) {
8225
- await deliverSteeredMessagesAtToolBoundary(toolResultParts);
8455
+ const delivered = await deliverSteeredMessagesAtToolBoundary(toolResultParts);
8456
+ settlementCandidate =
8457
+ delivered === 0 &&
8458
+ toolCallHasCleanStop &&
8459
+ followUpResults.length === 0 &&
8460
+ toolCallParts.length > 0 &&
8461
+ toolCallParts.every((call) => settledToolCalls.has(call)) &&
8462
+ !turnHasActionWarnings &&
8463
+ !toolResultHistory.some((result) => result.isError);
8226
8464
  }
8227
8465
  if (requestedActionStop) {
8228
8466
  const stop = requestedActionStop;
@@ -8294,14 +8532,16 @@ export async function runAgentLoop(opts) {
8294
8532
  send({ type: "suggestions", suggestions: completedFollowUpSuggestions });
8295
8533
  send({
8296
8534
  type: "done",
8297
- ...(loopBreakerStopped
8298
- ? {
8299
- reason: "loop_breaker",
8300
- ...(loopBreakerCloseout
8301
- ? { message: loopBreakerCloseout.message }
8302
- : {}),
8303
- }
8304
- : {}),
8535
+ ...(settledEmptyFinal
8536
+ ? { reason: "action_settled" }
8537
+ : loopBreakerStopped
8538
+ ? {
8539
+ reason: "loop_breaker",
8540
+ ...(loopBreakerCloseout
8541
+ ? { message: loopBreakerCloseout.message }
8542
+ : {}),
8543
+ }
8544
+ : {}),
8305
8545
  });
8306
8546
  if (opts.threadId) {
8307
8547
  void retainRequestDbPoolScope(clearLedgerForThread(opts.threadId).catch(() => { }));
@@ -87,6 +87,12 @@ export async function foldReapedRunIntoThread(runId, terminalError) {
87
87
  if (!thread)
88
88
  return "superseded";
89
89
  const repo = readThreadRepository(run.threadId, run.turnId, thread.threadData);
90
+ // A thread with no messages has no tail for the retry check below to wait
91
+ // on, so retrying would defer forever and fail every sweep.
92
+ if (repo.messages.length === 0) {
93
+ console.warn(`[agent-chat] dropping stopped run ${runId}: thread ${run.threadId} has no messages (turn ${run.turnId})`);
94
+ return "superseded";
95
+ }
90
96
  const folded = foldAssistantTurnWithSteers(repo, assistant, steered, {
91
97
  runId,
92
98
  turnId: run.turnId,
@@ -320,6 +320,10 @@ function isBatchableRunEvent(event) {
320
320
  export function endsAfterToolResultWithoutAssistantFinal(run, privateCompletedUiTools = new Set()) {
321
321
  let toolResultAfterLastAssistantText = false;
322
322
  for (const { event, seq } of run.events) {
323
+ if (event.type === "done" && event.reason === "action_settled") {
324
+ toolResultAfterLastAssistantText = false;
325
+ continue;
326
+ }
323
327
  if (event.type === "text" && event.text.trim().length > 0) {
324
328
  toolResultAfterLastAssistantText = false;
325
329
  continue;
@@ -1907,6 +1911,8 @@ export function startRun(runId, threadId, runFn, onComplete, options) {
1907
1911
  const shouldAutoContinueAfterUnfinishedTurn = finalStatus === "completed" &&
1908
1912
  !checkpointStatusReadFailed &&
1909
1913
  checkpointUnknownStatus === null &&
1914
+ !(terminalEventForCompletion?.event.type === "done" &&
1915
+ terminalEventForCompletion.event.reason === "action_settled") &&
1910
1916
  (endsAfterToolResultWithoutAssistantFinal(runForCompletionChecks, privateCompletedUiTools) ||
1911
1917
  endsDuringActionPreparation(runForCompletionChecks)) &&
1912
1918
  (!terminalEventForCompletion ||
@@ -1,4 +1,5 @@
1
1
  import type { ActionChatUIConfig } from "../action-ui.js";
2
+ import type { WriteReceipt } from "../action.js";
2
3
  import { type ArtifactReceipt } from "../artifacts/detect.js";
3
4
  import type { AgentMcpAppPayload } from "../mcp-client/app-result.js";
4
5
  import type { EngineMessage, EngineToolCallPart, EngineToolResultPart } from "./engine/types.js";
@@ -16,6 +17,9 @@ export interface ToolCallJournalEntry {
16
17
  /** The call failed after its side effect completed. */
17
18
  isError?: true;
18
19
  errorCode?: string;
20
+ receipt?: WriteReceipt;
21
+ actionSettled?: true;
22
+ settledOnCleanToolStop?: true;
19
23
  /** The call recorded a completed side effect, its own or a nested call's. */
20
24
  committed?: true;
21
25
  /** Exact identity of the arguments the call ran with, defaults applied. */