@gajae-code/coding-agent 0.12.21 → 0.13.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 (222) hide show
  1. package/CHANGELOG.md +67 -2
  2. package/dist/types/cli/args.d.ts +5 -2
  3. package/dist/types/cli/root-flags.d.ts +246 -0
  4. package/dist/types/cli.d.ts +11 -19
  5. package/dist/types/commands/daemon.d.ts +0 -30
  6. package/dist/types/commands/launch.d.ts +11 -19
  7. package/dist/types/commands/quick-lane.d.ts +32 -0
  8. package/dist/types/commands/sdk.d.ts +21 -15
  9. package/dist/types/config/file-lock-gc.d.ts +2 -0
  10. package/dist/types/config/model-equivalence.d.ts +20 -0
  11. package/dist/types/config/model-profile-activation.d.ts +62 -17
  12. package/dist/types/config/model-profiles.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +68 -1
  14. package/dist/types/config/model-resolver.d.ts +15 -4
  15. package/dist/types/config/models-config-schema.d.ts +7 -2
  16. package/dist/types/config/provider-selection-policy.d.ts +62 -0
  17. package/dist/types/config/settings-schema.d.ts +36 -78
  18. package/dist/types/config/settings.d.ts +2 -0
  19. package/dist/types/edit/renderer.d.ts +5 -2
  20. package/dist/types/edit/streaming.d.ts +13 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
  22. package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
  23. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  24. package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
  25. package/dist/types/internal-urls/local-protocol.d.ts +1 -0
  26. package/dist/types/modes/components/model-selector.d.ts +0 -2
  27. package/dist/types/modes/components/provider-order-context.d.ts +45 -0
  28. package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
  29. package/dist/types/modes/components/settings-defs.d.ts +5 -1
  30. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  31. package/dist/types/quick-lane/classify.d.ts +40 -0
  32. package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
  33. package/dist/types/sdk/broker/broker.d.ts +56 -2
  34. package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
  35. package/dist/types/sdk/broker/session-index.d.ts +83 -2
  36. package/dist/types/sdk/broker/state-version.d.ts +1 -1
  37. package/dist/types/sdk/bus/notification-service.d.ts +6 -0
  38. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
  39. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
  40. package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
  41. package/dist/types/sdk/cli/rows.d.ts +98 -0
  42. package/dist/types/sdk/cli/session-cli.d.ts +35 -3
  43. package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
  44. package/dist/types/sdk/elevation/audit.d.ts +93 -0
  45. package/dist/types/sdk/elevation/capability.d.ts +3 -0
  46. package/dist/types/sdk/elevation/digest.d.ts +72 -0
  47. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
  48. package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
  49. package/dist/types/sdk/elevation/owner.d.ts +48 -0
  50. package/dist/types/sdk/guides/cache.d.ts +98 -0
  51. package/dist/types/sdk/guides/catalog.d.ts +156 -0
  52. package/dist/types/sdk/guides/cli.d.ts +39 -0
  53. package/dist/types/sdk/guides/manifest.d.ts +66 -0
  54. package/dist/types/sdk/guides/verify.d.ts +66 -0
  55. package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
  56. package/dist/types/sdk/host/control/index.d.ts +1 -1
  57. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  58. package/dist/types/sdk/host/query/cursor.d.ts +10 -0
  59. package/dist/types/sdk/host/query/handlers.d.ts +21 -0
  60. package/dist/types/sdk/host/session-runtime.d.ts +9 -0
  61. package/dist/types/sdk/mcp/server.d.ts +2 -2
  62. package/dist/types/sdk/prompt-status.d.ts +11 -0
  63. package/dist/types/sdk/session.d.ts +4 -0
  64. package/dist/types/sdk/transport/index.d.ts +1 -0
  65. package/dist/types/sdk/transport/relay.d.ts +1 -0
  66. package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
  67. package/dist/types/session/agent-session.d.ts +32 -18
  68. package/dist/types/session/blob-store.d.ts +13 -7
  69. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  70. package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
  71. package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
  72. package/dist/types/session/session-manager.d.ts +139 -10
  73. package/dist/types/session/session-storage.d.ts +48 -0
  74. package/dist/types/setup/provider-onboarding.d.ts +7 -1
  75. package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
  76. package/dist/types/stt/setup.d.ts +2 -0
  77. package/dist/types/task/executor.d.ts +10 -2
  78. package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
  79. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  80. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  81. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  82. package/dist/types/tools/browser.d.ts +2 -0
  83. package/dist/types/tools/image-gen.d.ts +14 -18
  84. package/dist/types/tools/index.d.ts +2 -0
  85. package/dist/types/tools/resource-gc.d.ts +2 -0
  86. package/dist/types/utils/pasted-image-path.d.ts +6 -0
  87. package/package.json +9 -9
  88. package/scripts/generate-sdk-operation-inventory.ts +8 -0
  89. package/scripts/run-test-manifest.ts +61 -21
  90. package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
  91. package/src/cli/args.ts +62 -9
  92. package/src/cli/completion-cli.ts +1 -0
  93. package/src/cli/fast-help.ts +1 -0
  94. package/src/cli/root-flags.ts +100 -0
  95. package/src/cli/setup-cli.ts +6 -2
  96. package/src/cli.ts +42 -65
  97. package/src/commands/acp.ts +1 -1
  98. package/src/commands/daemon.ts +2 -31
  99. package/src/commands/gc.ts +1 -1
  100. package/src/commands/launch.ts +8 -129
  101. package/src/commands/quick-lane.ts +51 -0
  102. package/src/commands/sdk.ts +200 -26
  103. package/src/commands/setup.ts +4 -2
  104. package/src/config/file-lock-gc.ts +4 -1
  105. package/src/config/model-equivalence.ts +76 -7
  106. package/src/config/model-profile-activation.ts +1115 -265
  107. package/src/config/model-profiles.ts +134 -2
  108. package/src/config/model-registry.ts +352 -50
  109. package/src/config/model-resolver.ts +289 -38
  110. package/src/config/models-config-schema.ts +11 -4
  111. package/src/config/provider-selection-policy.ts +146 -0
  112. package/src/config/settings-schema.ts +50 -77
  113. package/src/config/settings.ts +40 -0
  114. package/src/coordinator-mcp/server.ts +32 -2
  115. package/src/edit/renderer.ts +175 -223
  116. package/src/edit/streaming.ts +108 -1
  117. package/src/export/html/index.ts +57 -7
  118. package/src/extensibility/custom-tools/types.ts +2 -0
  119. package/src/extensibility/extensions/compact-handler.ts +3 -2
  120. package/src/extensibility/extensions/runner.ts +6 -0
  121. package/src/extensibility/extensions/types.ts +3 -0
  122. package/src/gjc-runtime/gc-runtime.ts +379 -20
  123. package/src/gjc-runtime/launch-worktree.ts +5 -1
  124. package/src/gjc-runtime/team-runtime.ts +59 -2
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
  126. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
  127. package/src/internal-urls/docs-index.generated.ts +17 -13
  128. package/src/internal-urls/local-protocol.ts +1 -0
  129. package/src/main.ts +40 -3
  130. package/src/memories/index.ts +4 -2
  131. package/src/modes/acp/acp-agent.ts +235 -91
  132. package/src/modes/components/model-selector.ts +206 -42
  133. package/src/modes/components/provider-order-context.ts +197 -0
  134. package/src/modes/components/provider-order-editor.ts +441 -0
  135. package/src/modes/components/settings-defs.ts +21 -1
  136. package/src/modes/components/settings-selector.ts +45 -2
  137. package/src/modes/components/tool-execution.ts +47 -15
  138. package/src/modes/controllers/command-controller.ts +6 -3
  139. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  140. package/src/modes/controllers/input-controller.ts +73 -2
  141. package/src/modes/controllers/selector-controller.ts +260 -153
  142. package/src/modes/prompt-suggestion-controller.ts +1 -1
  143. package/src/modes/runtime-init.ts +1 -0
  144. package/src/modes/utils/ui-helpers.ts +18 -0
  145. package/src/prompts/tools/browser.md +1 -0
  146. package/src/quick-lane/classify.ts +257 -0
  147. package/src/rlm/index.ts +1 -1
  148. package/src/runtime/stt-settings-setup.ts +57 -0
  149. package/src/sdk/acp/adapter.ts +2 -1
  150. package/src/sdk/broker/broker.ts +780 -12
  151. package/src/sdk/broker/lifecycle.ts +70 -29
  152. package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
  153. package/src/sdk/broker/session-index.ts +463 -48
  154. package/src/sdk/broker/state-version.ts +13 -9
  155. package/src/sdk/broker/transport.ts +14 -1
  156. package/src/sdk/bus/index.ts +34 -13
  157. package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
  158. package/src/sdk/bus/notification-service.ts +47 -1
  159. package/src/sdk/bus/prompt-reconciliation.ts +22 -3
  160. package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
  161. package/src/sdk/bus/telegram-daemon.ts +54 -3
  162. package/src/sdk/cli/rows.ts +247 -0
  163. package/src/sdk/cli/session-cli.ts +930 -79
  164. package/src/sdk/elevation/allowlist.ts +54 -0
  165. package/src/sdk/elevation/audit.ts +208 -0
  166. package/src/sdk/elevation/capability.ts +41 -0
  167. package/src/sdk/elevation/digest.ts +129 -0
  168. package/src/sdk/elevation/dispatch-receipt.ts +184 -0
  169. package/src/sdk/elevation/grant-ledger.ts +1142 -0
  170. package/src/sdk/elevation/owner.ts +109 -0
  171. package/src/sdk/guides/cache.ts +363 -0
  172. package/src/sdk/guides/catalog.ts +554 -0
  173. package/src/sdk/guides/cli.ts +235 -0
  174. package/src/sdk/guides/manifest.ts +152 -0
  175. package/src/sdk/guides/verify.ts +178 -0
  176. package/src/sdk/host/control/dispatch.ts +49 -1
  177. package/src/sdk/host/control/index.ts +1 -0
  178. package/src/sdk/host/control/operations.ts +8 -1
  179. package/src/sdk/host/query/cursor.ts +47 -3
  180. package/src/sdk/host/query/handlers.ts +155 -2
  181. package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
  182. package/src/sdk/host/session-runtime.test.ts +391 -1
  183. package/src/sdk/host/session-runtime.ts +308 -73
  184. package/src/sdk/host/surface-policy.ts +3 -0
  185. package/src/sdk/host/websocket-transport.ts +7 -1
  186. package/src/sdk/mcp/server.ts +88 -15
  187. package/src/sdk/prompt-status.ts +8 -0
  188. package/src/sdk/protocol/operation-inventory.generated.json +62 -0
  189. package/src/sdk/protocol/operation-registry.ts +3 -2
  190. package/src/sdk/session.ts +87 -39
  191. package/src/sdk/transport/index.ts +1 -0
  192. package/src/sdk/transport/relay.ts +3 -0
  193. package/src/sdk/transport/serve-cli.ts +111 -24
  194. package/src/secrets/obfuscator.ts +92 -12
  195. package/src/session/agent-session.ts +322 -93
  196. package/src/session/blob-store.ts +60 -8
  197. package/src/session/fallback-chain-controller.ts +35 -0
  198. package/src/session/internal/managed-session-scope.ts +25 -3
  199. package/src/session/internal/managed-session-storage.ts +574 -73
  200. package/src/session/internal/session-memory-sidecar.ts +19 -29
  201. package/src/session/session-manager.ts +2166 -743
  202. package/src/session/session-storage.ts +715 -48
  203. package/src/setup/provider-onboarding.ts +19 -3
  204. package/src/setup/provider-presets.json +3 -1
  205. package/src/slash-commands/builtin-registry.ts +85 -3
  206. package/src/stt/downloader.ts +2 -3
  207. package/src/stt/setup.ts +29 -4
  208. package/src/task/executor.ts +22 -18
  209. package/src/task/index.ts +24 -1
  210. package/src/tools/archive-reader.ts +11 -2
  211. package/src/tools/browser/runtime-diagnostics.ts +249 -0
  212. package/src/tools/browser/tab-protocol.ts +4 -0
  213. package/src/tools/browser/tab-supervisor.ts +5 -0
  214. package/src/tools/browser/tab-worker.ts +36 -1
  215. package/src/tools/browser.ts +7 -0
  216. package/src/tools/image-gen.ts +110 -166
  217. package/src/tools/index.ts +2 -0
  218. package/src/tools/read.ts +1 -1
  219. package/src/tools/resource-gc.ts +6 -1
  220. package/src/tools/tool-catalog.generated.ts +5 -1
  221. package/src/utils/pasted-image-path.ts +93 -0
  222. package/src/web/search/index.ts +3 -3
@@ -45,6 +45,7 @@ export interface LocalProtocolOptions {
45
45
  getArtifactsDir?: () => string | null;
46
46
  isManagedDestination?: () => boolean;
47
47
  getManagedLegacyLocalMigrationSource?: () => ManagedLegacyLocalMigrationSource | null;
48
+ getCredentialSessionId?: () => string | null;
48
49
  getSessionId?: () => string | null;
49
50
  }
50
51
 
package/src/main.ts CHANGED
@@ -54,6 +54,9 @@ import {
54
54
  createAgentSession,
55
55
  discoverAuthStorage,
56
56
  } from "./sdk";
57
+ import { processIncarnation } from "./sdk/broker/process-incarnation";
58
+ import { SessionIndex } from "./sdk/broker/session-index";
59
+
57
60
  import type { AgentSession } from "./session/agent-session";
58
61
  import { SessionMigrationBusyError } from "./session/internal/session-open-errors";
59
62
  import {
@@ -264,7 +267,7 @@ export function resolveAcpStartupOptions(
264
267
  ...(parsed.plan ? ["--plan"] : []),
265
268
  ...(parsed.systemPrompt ? ["--system-prompt"] : []),
266
269
  ...(parsed.tmux ? ["--tmux"] : []),
267
- ...(parsed.tools?.length ? ["--tools"] : []),
270
+ ...(parsed.tools !== undefined ? ["--tools"] : []),
268
271
  ...(parsed.extensions?.length ? ["--extension"] : []),
269
272
  ...(parsed.unknownFlags.size > 0 ? [`unknown flags: ${[...parsed.unknownFlags.keys()].join(" ")}`] : []),
270
273
  ];
@@ -997,8 +1000,14 @@ export async function createSessionManager(
997
1000
  // buildSessionOptions restores the session's model/thinking instead of
998
1001
  // overriding them with CLI defaults.
999
1002
  if (activeSettings.get("autoResume")) {
1000
- const manager = await SessionManager.continueRecent(cwd, sessionDestination(), undefined, migrationPolicy);
1001
- if (manager.getEntries().length > 0) {
1003
+ const manager = await SessionManager.continueRecent(
1004
+ cwd,
1005
+ sessionDestination(),
1006
+ undefined,
1007
+ migrationPolicy,
1008
+ sessionMemoryMode,
1009
+ );
1010
+ if (manager.hasHistoryEntries()) {
1002
1011
  parsed.continue = true;
1003
1012
  }
1004
1013
  return manager;
@@ -1720,6 +1729,34 @@ export async function runRootCommand(
1720
1729
  await postmortem.cleanup();
1721
1730
  return;
1722
1731
  }
1732
+ // Register a resumed direct session before constructing the agent: GC holds the
1733
+ // same index lock while deleting artifacts, so startup and deletion are fenced.
1734
+ const directSessionId = process.env.GJC_LIFECYCLE_REQUEST_ID ? undefined : sessionManager?.getSessionId();
1735
+ if (directSessionId) {
1736
+ const sessionIndex = new SessionIndex(settingsInstance.getAgentDir());
1737
+ const locator = { repo: sessionManager?.getCwd() ?? cwd, stateRoot: settingsInstance.getAgentDir() };
1738
+ // A pid is reusable, so the broker's teardown fence needs this session's OS
1739
+ // start incarnation recorded alongside the pid it publishes.
1740
+ const directSessionIncarnation = processIncarnation(process.pid);
1741
+ await sessionIndex.append({
1742
+ type: "host_registered",
1743
+ sessionId: directSessionId,
1744
+ locator,
1745
+ endpointGeneration: 0,
1746
+ pid: process.pid,
1747
+ ...(directSessionIncarnation ? { processIncarnation: directSessionIncarnation } : {}),
1748
+ });
1749
+ postmortem.register("direct-session-index", async () => {
1750
+ await sessionIndex.append({
1751
+ type: "host_unregistered",
1752
+ sessionId: directSessionId,
1753
+ locator,
1754
+ endpointGeneration: 0,
1755
+ pid: process.pid,
1756
+ });
1757
+ });
1758
+ }
1759
+
1723
1760
  const createAgentSessionImpl = deps.createAgentSession ?? createAgentSession;
1724
1761
  const createSession: CreateSessionForMain = async (options, context): Promise<CreateAgentSessionResult> => {
1725
1762
  const result = await logger.time("createAgentSession", createAgentSessionImpl, options);
@@ -238,7 +238,7 @@ async function runPhase1(options: {
238
238
  logger.debug("Phase1 skipped: no model available");
239
239
  return;
240
240
  }
241
- const phase1ApiKey = await modelRegistry.getApiKey(phase1Model, session.sessionId);
241
+ const phase1ApiKey = await modelRegistry.getApiKey(phase1Model, session.credentialSessionId);
242
242
  if (!phase1ApiKey) {
243
243
  logger.debug("Phase1 skipped: no API key for phase1 model", {
244
244
  provider: phase1Model.provider,
@@ -401,7 +401,7 @@ async function runPhase2(options: {
401
401
  });
402
402
  return;
403
403
  }
404
- const phase2ApiKey = await modelRegistry.getApiKey(phase2Model, session.sessionId);
404
+ const phase2ApiKey = await modelRegistry.getApiKey(phase2Model, session.credentialSessionId);
405
405
  if (!phase2ApiKey) {
406
406
  markPhase2FailureWithFallback(db, {
407
407
  claim,
@@ -1091,6 +1091,8 @@ async function resolveMemoryModel(options: {
1091
1091
  settings: session.settings,
1092
1092
  matchPreferences: { usageOrder: session.settings.getStorage()?.getModelUsageOrder() },
1093
1093
  modelRegistry,
1094
+ sessionId: session.sessionId,
1095
+ credentialSessionId: session.credentialSessionId,
1094
1096
  });
1095
1097
  if (resolved.model) return resolved.model;
1096
1098
  }
@@ -70,6 +70,7 @@ const ACP_CUSTOM_MODEL_PRESET = "__custom__";
70
70
  const THINKING_CONFIG_ID = "thinking";
71
71
  const SESSION_PAGE_SIZE = 50;
72
72
  const MAX_ACP_REPLAY_PAGES = 10_000;
73
+ const MAX_ACP_SESSION_LIST_PAGES = 10_000;
73
74
  /** Bounded retention of settled prompt correlations so late duplicates stay closed. */
74
75
  const SETTLED_PROMPT_CORRELATION_RETENTION = 16;
75
76
  /**
@@ -108,11 +109,11 @@ interface PromptWaiter {
108
109
  deferredFrames: JsonObject[];
109
110
  /** Activity frames held until acknowledgement establishes exact prompt ownership. */
110
111
  deferredActivityFrames: JsonObject[];
111
- /** Clock reading of the last inbound frame for this prompt; baseline of the inactivity watchdog. */
112
+ /** Clock reading of the last frame proven to belong to this prompt; watchdog silence baseline. */
112
113
  lastFrameAt: number;
113
- /** Frame (or wire event) type of that last frame, reported when the watchdog expires. */
114
+ /** Type of that prompt-owned frame, reported when the watchdog expires. */
114
115
  lastFrameType: string;
115
- /** Cancels the armed inactivity watchdog; re-armed by every inbound frame for this prompt. */
116
+ /** Cancels the armed inactivity watchdog; re-armed by prompt-owned frames. */
116
117
  cancelWatchdog?: () => void;
117
118
  /** What the host is observably doing — a tool running, a model call unanswered — and the bound that follows from it. */
118
119
  activity: PromptActivity;
@@ -139,6 +140,8 @@ type SessionRecord = {
139
140
  busy: boolean;
140
141
  /** Start/update args retained because tool_execution_end does not carry them. */
141
142
  toolArgs: Map<string, unknown>;
143
+ /** Message projection state for correlationless session-scoped assistant events. */
144
+ sessionMessageProgress?: { textEmitted: boolean; thoughtEmitted: boolean };
142
145
  /** Actionable model-profile authentication failure detected before prompt dispatch. */
143
146
  connectionId?: string;
144
147
  /** Bounded set of correlations already settled; they stay closed for publication. */
@@ -148,6 +151,11 @@ type SessionRecord = {
148
151
  /** Set by `session/cancel` so an in-flight prompt settles as `cancelled`, never as an error. */
149
152
  cancelRequested?: boolean;
150
153
  };
154
+
155
+ function promptWaiterRetired(record: SessionRecord, waiter: PromptWaiter): boolean {
156
+ return waiter.settled || record.activePrompt !== waiter;
157
+ }
158
+
151
159
  type Endpoint = { url: string; token: string };
152
160
 
153
161
  type BrokerSession = {
@@ -187,6 +195,42 @@ function object(value: unknown): JsonObject | undefined {
187
195
  return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as JsonObject) : undefined;
188
196
  }
189
197
 
198
+ async function collectAcpSessionList(
199
+ request: (input: JsonObject) => Promise<unknown>,
200
+ input: JsonObject = {},
201
+ ): Promise<JsonObject> {
202
+ const aggregate: JsonObject = {};
203
+ const sessions: unknown[] = [];
204
+ let cursor: string | undefined;
205
+ for (let pageCount = 0; pageCount < MAX_ACP_SESSION_LIST_PAGES; pageCount++) {
206
+ const response = object(await request({ ...input, ...(cursor === undefined ? {} : { cursor }) }));
207
+ const listing = object(response?.result) ?? response;
208
+ if (response?.ok === false) {
209
+ const failure = object(response.error);
210
+ throw new AcpSdkAdapterError(
211
+ typeof failure?.code === "string" ? failure.code : "broker_error",
212
+ typeof failure?.message === "string" ? failure.message : "session.list failed",
213
+ );
214
+ }
215
+ if (listing) {
216
+ for (const [key, value] of Object.entries(listing)) {
217
+ if (key !== "sessions" && key !== "continuationCursor") aggregate[key] = value;
218
+ }
219
+ if (Array.isArray(listing.sessions)) sessions.push(...listing.sessions);
220
+ const nextCursor =
221
+ typeof listing.continuationCursor === "string" && listing.continuationCursor.length > 0
222
+ ? listing.continuationCursor
223
+ : undefined;
224
+ if (nextCursor) {
225
+ cursor = nextCursor;
226
+ continue;
227
+ }
228
+ }
229
+ return { ...aggregate, sessions };
230
+ }
231
+ throw new AcpSdkAdapterError("protocol_error", "session.list exceeded the page budget.");
232
+ }
233
+
190
234
  function aggregateAcpFailure(code: string, message: string, failures: unknown[]): AcpSdkAdapterError {
191
235
  const aggregate = new AggregateError(failures, message);
192
236
  return Object.assign(new AcpSdkAdapterError(code, aggregate.message), {
@@ -281,25 +325,6 @@ export function acpAvailableCommandsFromSkills(query: unknown): AvailableCommand
281
325
  return [...commands.values()];
282
326
  }
283
327
 
284
- function correlationFrom(...values: unknown[]): PromptCorrelation {
285
- const correlation: PromptCorrelation = {};
286
- for (const value of values) {
287
- const candidate = object(value);
288
- for (const record of [candidate, object(candidate?.result)]) {
289
- if (!record) continue;
290
- if (!correlation.commandId) {
291
- const commandId = record.commandId ?? record.command_id;
292
- if (typeof commandId === "string" && commandId) correlation.commandId = commandId;
293
- }
294
- if (!correlation.turnId) {
295
- const turnId = record.turnId ?? record.turn_id;
296
- if (typeof turnId === "string" && turnId) correlation.turnId = turnId;
297
- }
298
- }
299
- }
300
- return correlation;
301
- }
302
-
303
328
  function hasCorrelation(correlation: PromptCorrelation): boolean {
304
329
  return correlation.commandId !== undefined || correlation.turnId !== undefined;
305
330
  }
@@ -379,6 +404,36 @@ function strictCorrelationFrom(...values: unknown[]): PromptCorrelation | undefi
379
404
  return malformed ? undefined : correlation;
380
405
  }
381
406
 
407
+ function sdkFrameCorrelation(frame: JsonObject, event?: JsonObject): PromptCorrelation | undefined {
408
+ return strictCorrelationFrom(frame, event);
409
+ }
410
+
411
+ /**
412
+ * Conflicting envelope/event identities own no prompt and therefore cannot
413
+ * refresh a watchdog or publish into either turn.
414
+ */
415
+ function watchdogCorrelationFrom(frame: JsonObject, event?: JsonObject): PromptCorrelation {
416
+ return strictCorrelationFrom(frame, event) ?? {};
417
+ }
418
+
419
+ function logDroppedPromptTerminal(
420
+ sessionId: string,
421
+ event: JsonObject,
422
+ reason: "incomplete_correlation" | "correlation_mismatch",
423
+ actual: PromptCorrelation,
424
+ expected?: PromptCorrelation,
425
+ ): void {
426
+ logger.error("acp_prompt_terminal_dropped", {
427
+ sessionId,
428
+ terminalType: event.type,
429
+ reason,
430
+ ...(actual.commandId ? { commandId: actual.commandId } : {}),
431
+ ...(actual.turnId ? { turnId: actual.turnId } : {}),
432
+ ...(expected?.commandId ? { expectedCommandId: expected.commandId } : {}),
433
+ ...(expected?.turnId ? { expectedTurnId: expected.turnId } : {}),
434
+ });
435
+ }
436
+
382
437
  function terminalOutcome(event: JsonObject): SdkPromptTerminalOutcome | undefined {
383
438
  const outcome = object(event.outcome);
384
439
  if (!outcome) return undefined;
@@ -1179,9 +1234,10 @@ export class AcpAgent implements Agent {
1179
1234
 
1180
1235
  async listSessions(params: ListSessionsRequest): Promise<ListSessionsResponse> {
1181
1236
  if (params.cwd) this.#assertAbsoluteCwd(params.cwd);
1182
- const result = object(await (await this.#brokerAdapter()).global("session.list"));
1183
- const listing = object(result?.result) ?? result;
1184
- const listed = Array.isArray(listing?.sessions) ? listing.sessions : [];
1237
+ const adapter = await this.#brokerAdapter();
1238
+ const listing = await collectAcpSessionList(input => adapter.global("session.list", input));
1239
+ const listed = Array.isArray(listing.sessions) ? listing.sessions : [];
1240
+
1185
1241
  if (params.cwd) {
1186
1242
  const discovered = new Set<string>();
1187
1243
  for (const session of listed) {
@@ -1368,6 +1424,10 @@ export class AcpAgent implements Agent {
1368
1424
  };
1369
1425
  record.activePrompt = waiter;
1370
1426
  });
1427
+ // The watchdog may reject this waiter while the SDK acknowledgement request is still
1428
+ // pending. Retain the original promise for the caller, but mark that delayed rejection
1429
+ // as observed until prompt() can resume and await it.
1430
+ void response.catch(() => undefined);
1371
1431
  // Silence has to be bounded from the moment the prompt owns the session: a host that
1372
1432
  // dies before it ever answers is exactly the failure that leaves the client running.
1373
1433
  this.#armPromptWatchdog(params.sessionId, record, waiter);
@@ -1388,7 +1448,7 @@ export class AcpAgent implements Agent {
1388
1448
  record.adapter,
1389
1449
  );
1390
1450
  }
1391
- try {
1451
+ const acknowledgementTask = (async (): Promise<PromptResponse> => {
1392
1452
  const acknowledgement = await record.adapter.prompt({
1393
1453
  text: payload.text,
1394
1454
  ...(payload.images.length ? { images: payload.images } : {}),
@@ -1403,22 +1463,80 @@ export class AcpAgent implements Agent {
1403
1463
  waiter.boundary = record.inboundSequence;
1404
1464
  waiter.correlation = acknowledgementCorrelation;
1405
1465
  waiter.acknowledged = true;
1466
+ if (promptWaiterRetired(record, waiter)) {
1467
+ if (
1468
+ !record.settledPromptCorrelations.some(settled =>
1469
+ correlationsExactlyMatch(settled, acknowledgementCorrelation),
1470
+ )
1471
+ ) {
1472
+ record.settledPromptCorrelations.push(acknowledgementCorrelation);
1473
+ while (record.settledPromptCorrelations.length > SETTLED_PROMPT_CORRELATION_RETENTION)
1474
+ record.settledPromptCorrelations.shift();
1475
+ }
1476
+ return await response;
1477
+ }
1406
1478
  // Frames held while ownership was unknown belong to this prompt only when the
1407
1479
  // acknowledgement proves their complete correlation matches exactly.
1408
1480
  const deferredActivityFrames = waiter.deferredActivityFrames.splice(0);
1481
+ let observedDeferredActivity = false;
1409
1482
  for (const deferredFrame of deferredActivityFrames) {
1410
1483
  const deferredEvent = receivedSdkEvent(deferredFrame)?.event;
1411
- if (correlationsExactlyMatch(waiter.correlation, correlationFrom(deferredFrame, deferredEvent)))
1484
+ if (correlationsExactlyMatch(waiter.correlation, watchdogCorrelationFrom(deferredFrame, deferredEvent))) {
1412
1485
  this.#observePromptActivity(waiter, deferredFrame);
1486
+ observedDeferredActivity = true;
1487
+ }
1413
1488
  }
1414
- if (deferredActivityFrames.length > 0) this.#armPromptWatchdog(params.sessionId, record, waiter);
1489
+ if (observedDeferredActivity) this.#armPromptWatchdog(params.sessionId, record, waiter);
1415
1490
  const deferred = waiter.deferredFrames.splice(0);
1416
- for (const deferredFrame of deferred)
1417
- if (correlationsExactlyMatch(waiter.correlation, correlationFrom(deferredFrame)))
1418
- record.frameTail = record.frameTail.then(
1419
- async () => await this.#handleSdkFrame(params.sessionId, record.adapter, deferredFrame),
1420
- );
1491
+ for (const deferredFrame of deferred) {
1492
+ const deferredEvent = receivedSdkEvent(deferredFrame)?.event;
1493
+ if (!deferredEvent) continue;
1494
+ const deferredCorrelation = sdkFrameCorrelation(deferredFrame, deferredEvent) ?? {};
1495
+ const deferredIsTerminal = deferredEvent.type === "agent_end" || deferredEvent.type === "agent_failed";
1496
+ if (!hasCompleteCorrelation(deferredCorrelation)) {
1497
+ if (deferredIsTerminal)
1498
+ logDroppedPromptTerminal(
1499
+ params.sessionId,
1500
+ deferredEvent,
1501
+ "incomplete_correlation",
1502
+ deferredCorrelation,
1503
+ waiter.correlation,
1504
+ );
1505
+ continue;
1506
+ }
1507
+ const matchesPrompt = correlationsExactlyMatch(waiter.correlation, deferredCorrelation);
1508
+ if (
1509
+ !matchesPrompt &&
1510
+ record.settledPromptCorrelations.some(settled => correlationsExactlyMatch(settled, deferredCorrelation))
1511
+ )
1512
+ continue;
1513
+ if (!matchesPrompt) {
1514
+ if (deferredIsTerminal)
1515
+ logDroppedPromptTerminal(
1516
+ params.sessionId,
1517
+ deferredEvent,
1518
+ "correlation_mismatch",
1519
+ deferredCorrelation,
1520
+ waiter.correlation,
1521
+ );
1522
+ continue;
1523
+ }
1524
+ const task = record.frameTail.then(
1525
+ async () => await this.#handleSdkFrame(params.sessionId, record.adapter, deferredFrame),
1526
+ );
1527
+ record.frameTail = task.catch(
1528
+ async error =>
1529
+ await this.#failSession(params.sessionId, record.adapter, this.#frameProcessingFailure(error)),
1530
+ );
1531
+ }
1421
1532
  this.#settlePrompt(record, waiter);
1533
+ return await response;
1534
+ })();
1535
+ // Settlement can win before the SDK answers `turn.prompt`; acknowledgement processing
1536
+ // must remain alive to tombstone its eventual correlation without holding the ACP caller.
1537
+ void acknowledgementTask.catch(() => undefined);
1538
+ try {
1539
+ return await Promise.race([response, acknowledgementTask]);
1422
1540
  } catch (error) {
1423
1541
  waiter.deferredFrames.length = 0;
1424
1542
  waiter.deferredActivityFrames.length = 0;
@@ -1437,7 +1555,6 @@ export class AcpAgent implements Agent {
1437
1555
  }
1438
1556
  throw error;
1439
1557
  }
1440
- return await response;
1441
1558
  }
1442
1559
 
1443
1560
  async cancel(params: { sessionId: string }): Promise<void> {
@@ -1655,8 +1772,9 @@ export class AcpAgent implements Agent {
1655
1772
  }
1656
1773
 
1657
1774
  async #scopedBrokerSession(id: string, cwd: string): Promise<BrokerSession | undefined> {
1658
- const response = object(await (await this.#brokerAdapter()).global("session.list", { cwd }));
1659
- const result = object(response?.result) ?? response;
1775
+ const adapter = await this.#brokerAdapter();
1776
+ const result = await collectAcpSessionList(input => adapter.global("session.list", input), { cwd });
1777
+
1660
1778
  const matches: BrokerSession[] = [];
1661
1779
  for (const item of Array.isArray(result?.sessions) ? result.sessions : []) {
1662
1780
  const session = object(item) as BrokerSession | undefined;
@@ -1926,10 +2044,11 @@ export class AcpAgent implements Agent {
1926
2044
  }
1927
2045
 
1928
2046
  async #resolveSavedSession(id: string, cwd: string): Promise<string> {
1929
- const response = object(
1930
- await (await this.#brokerAdapter()).global("session.list", { resolveSessionId: id, cwd }),
1931
- );
1932
- const result = object(response?.result) ?? response;
2047
+ const adapter = await this.#brokerAdapter();
2048
+ const result = await collectAcpSessionList(input => adapter.global("session.list", input), {
2049
+ resolveSessionId: id,
2050
+ cwd,
2051
+ });
1933
2052
  const saved = object(result?.savedSession);
1934
2053
  if (saved?.id !== id || typeof saved.path !== "string")
1935
2054
  throw new AcpSdkAdapterError("not_found", `Saved ACP session does not exist: ${id}`);
@@ -1979,38 +2098,33 @@ export class AcpAgent implements Agent {
1979
2098
  }
1980
2099
 
1981
2100
  /**
1982
- * Any frame proves the producer is alive, so every one of them restarts the bound: liveness
1983
- * is a property of the session host process, not of one turn, and a host still flushing an
1984
- * abandoned turn's backlog is exactly a producer that has not stopped producing. Which bound
1985
- * is restarted is a different question, and that one is correlation-owned; see below.
2101
+ * A frame only restarts this prompt's watchdog after its complete correlation proves
2102
+ * that it belongs to the prompt. Correlationless or foreign traffic still proves the
2103
+ * session host process is alive, but not that it is making progress on this turn; using
2104
+ * process liveness as turn liveness lets an otherwise healthy host keep a wedged prompt
2105
+ * open forever. Matching frames retain the per-gap behavior for hosts demonstrably
2106
+ * working on this turn, while pre-acknowledgement activity is replayed after ownership
2107
+ * becomes known.
1986
2108
  */
1987
2109
  #refreshPromptWatchdog(id: string, record: SessionRecord, frame: JsonObject): void {
1988
2110
  const waiter = record.activePrompt;
1989
2111
  if (!waiter || waiter.settled) return;
1990
2112
  const event = receivedSdkEvent(frame)?.event;
1991
- waiter.lastFrameAt = this.#promptWatchdogClock.now();
1992
- waiter.lastFrameType =
1993
- typeof event?.type === "string" ? event.type : typeof frame.type === "string" ? frame.type : "unknown";
1994
- // Which bound applies is per-turn state, so it is fenced by the same identity that gates
1995
- // settlement: only a frame carrying exactly the acknowledged prompt's command/turn may
1996
- // start, clear, or extend a bound. Without this fence an already-settled turn's flushed
1997
- // frames mutate the live turn — a stale `message_update` clears `awaitingModel` and
1998
- // narrows it off the inference bound, a stale `tool_execution_start` pins it to the tool
1999
- // bound with no tool running. Frames with no correlation at all (heartbeats,
2000
- // session-scoped events) are refresh-only for the same reason: they prove the producer
2001
- // lives but say nothing about what this turn is doing, so they leave the bound as is.
2002
- // Before acknowledgement the prompt has no identity, so correlated activity is retained
2003
- // and folded in only after the acknowledgement proves exact ownership.
2113
+ const correlation = watchdogCorrelationFrom(frame, event);
2004
2114
  if (!waiter.acknowledged) {
2005
- if (hasCorrelation(correlationFrom(frame, event))) waiter.deferredActivityFrames.push(frame);
2006
- } else if (correlationsExactlyMatch(waiter.correlation, correlationFrom(frame, event))) {
2007
- this.#observePromptActivity(waiter, frame);
2115
+ if (hasCorrelation(correlation)) waiter.deferredActivityFrames.push(frame);
2116
+ return;
2008
2117
  }
2118
+ if (!correlationsExactlyMatch(waiter.correlation, correlation)) return;
2119
+ this.#observePromptActivity(waiter, frame);
2009
2120
  this.#armPromptWatchdog(id, record, waiter);
2010
2121
  }
2011
2122
 
2012
2123
  #observePromptActivity(waiter: PromptWaiter, frame: JsonObject): void {
2013
2124
  const event = receivedSdkEvent(frame)?.event;
2125
+ waiter.lastFrameAt = this.#promptWatchdogClock.now();
2126
+ waiter.lastFrameType =
2127
+ typeof event?.type === "string" ? event.type : typeof frame.type === "string" ? frame.type : "unknown";
2014
2128
  waiter.activity.observe(
2015
2129
  typeof event?.type === "string" ? event.type : undefined,
2016
2130
  typeof event?.toolCallId === "string" ? event.toolCallId : undefined,
@@ -2062,8 +2176,8 @@ export class AcpAgent implements Agent {
2062
2176
  if (!record || record.adapter !== adapter) return;
2063
2177
  // Ingress ordering is recorded before queued work begins.
2064
2178
  this.#observeSessionActivity(record, frame);
2065
- // Any frame at all proves the producer is still alive, so it refreshes the
2066
- // inactivity watchdog before the queued work that may take a long time to run.
2179
+ // Correlation is checked at ingress before a prompt-owned frame may refresh the
2180
+ // watchdog, so queued processing cannot turn unrelated host traffic into turn liveness.
2067
2181
  this.#refreshPromptWatchdog(id, record, frame);
2068
2182
  ++record.inboundSequence;
2069
2183
  const task = record.frameTail.then(async () => await this.#handleSdkFrame(id, adapter, frame));
@@ -2103,19 +2217,33 @@ export class AcpAgent implements Agent {
2103
2217
  if (!received) return;
2104
2218
  const { event, wirePayload } = received;
2105
2219
  const isTerminal = event.type === "agent_end" || event.type === "agent_failed";
2106
- const correlation = (isTerminal ? strictCorrelationFrom(frame, event) : correlationFrom(frame, event)) ?? {};
2220
+ const derivedCorrelation = sdkFrameCorrelation(frame, event);
2221
+ const correlation = derivedCorrelation ?? {};
2107
2222
  const activePrompt = record.activePrompt;
2108
2223
  const outcome = isTerminal ? terminalOutcome(event) : undefined;
2224
+ const settledCorrelation = record.settledPromptCorrelations.some(settled =>
2225
+ correlationsMatch(settled, correlation),
2226
+ );
2109
2227
  if (isTerminal) {
2110
2228
  // Terminal ownership requires a complete identity. Unowned, partial, and
2111
2229
  // duplicate terminals are never allowed to publish or query anything.
2112
- if (!hasCompleteCorrelation(correlation) || !activePrompt || activePrompt.settled) return;
2230
+ if (!activePrompt || activePrompt.settled) return;
2231
+ if (!hasCompleteCorrelation(correlation)) {
2232
+ logDroppedPromptTerminal(id, event, "incomplete_correlation", correlation, activePrompt.correlation);
2233
+ return;
2234
+ }
2113
2235
  if (!activePrompt.acknowledged) {
2114
2236
  // Hold the entire frame until the prompt acknowledgement proves ownership.
2115
2237
  activePrompt.deferredFrames.push(frame);
2116
2238
  return;
2117
2239
  }
2118
- if (!correlationsExactlyMatch(activePrompt.correlation, correlation) || activePrompt.terminal) return;
2240
+ const matchesPrompt = correlationsExactlyMatch(activePrompt.correlation, correlation);
2241
+ if (settledCorrelation && !matchesPrompt) return;
2242
+ if (!matchesPrompt) {
2243
+ logDroppedPromptTerminal(id, event, "correlation_mismatch", correlation, activePrompt.correlation);
2244
+ return;
2245
+ }
2246
+ if (activePrompt.terminal) return;
2119
2247
  if (!outcome) {
2120
2248
  const detail =
2121
2249
  typeof (event as { error?: { message?: unknown } }).error?.message === "string"
@@ -2131,17 +2259,15 @@ export class AcpAgent implements Agent {
2131
2259
  }
2132
2260
  activePrompt.terminal = { outcome, correlation };
2133
2261
  }
2134
- const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : undefined;
2135
- if (
2136
- toolCallId &&
2137
- (event.type === "tool_execution_start" || event.type === "tool_execution_update") &&
2138
- "args" in event
2139
- ) {
2140
- record.toolArgs.set(toolCallId, event.args);
2262
+ if (!isTerminal && derivedCorrelation === undefined) return;
2263
+ if (!isTerminal && hasCorrelation(correlation)) {
2264
+ if (!activePrompt || activePrompt.settled) return;
2265
+ if (!activePrompt.acknowledged) {
2266
+ activePrompt.deferredFrames.push(frame);
2267
+ return;
2268
+ }
2269
+ if (!correlationsExactlyMatch(activePrompt.correlation, correlation)) return;
2141
2270
  }
2142
- const settledCorrelation = record.settledPromptCorrelations.some(settled =>
2143
- correlationsMatch(settled, correlation),
2144
- );
2145
2271
  if (settledCorrelation) {
2146
2272
  // Frames for an already-settled correlation stay closed until an active prompt
2147
2273
  // acknowledges the exact same identity.
@@ -2151,25 +2277,41 @@ export class AcpAgent implements Agent {
2151
2277
  }
2152
2278
  if (!activePrompt || activePrompt.settled || !correlationsMatch(activePrompt.correlation, correlation)) return;
2153
2279
  }
2154
- // After a correlated settlement with no active prompt, correlationless wire frames
2155
- // have no prompt to belong to and must not publish further updates.
2156
- if (!record.activePrompt && record.settledPromptCorrelations.length > 0 && !hasCorrelation(correlation)) return;
2280
+ const promptOwner =
2281
+ activePrompt &&
2282
+ !activePrompt.settled &&
2283
+ activePrompt.acknowledged &&
2284
+ correlationsExactlyMatch(activePrompt.correlation, correlation)
2285
+ ? activePrompt
2286
+ : undefined;
2287
+ const toolCallId = typeof event.toolCallId === "string" ? event.toolCallId : undefined;
2288
+ if (
2289
+ toolCallId &&
2290
+ (event.type === "tool_execution_start" || event.type === "tool_execution_update") &&
2291
+ "args" in event
2292
+ ) {
2293
+ record.toolArgs.set(toolCallId, event.args);
2294
+ }
2157
2295
  if (wirePayload) {
2158
2296
  for (const notification of mapAgentWireEventPayloadToAcpSessionUpdates(wirePayload as never, id, {
2159
2297
  cwd: record.cwd,
2160
2298
  getToolArgs: id => record.toolArgs.get(id),
2161
2299
  getMessageProgress: message => {
2162
- if (!activePrompt || !object(message)) return undefined;
2163
- activePrompt.messageProgress ??= { textEmitted: false, thoughtEmitted: false };
2164
- return activePrompt.messageProgress;
2300
+ if (!object(message)) return undefined;
2301
+ if (promptOwner) {
2302
+ promptOwner.messageProgress ??= { textEmitted: false, thoughtEmitted: false };
2303
+ return promptOwner.messageProgress;
2304
+ }
2305
+ record.sessionMessageProgress ??= { textEmitted: false, thoughtEmitted: false };
2306
+ return record.sessionMessageProgress;
2165
2307
  },
2166
2308
  })) {
2167
2309
  if (
2168
- activePrompt &&
2310
+ promptOwner &&
2169
2311
  notification.update.sessionUpdate === "agent_message_chunk" &&
2170
2312
  notification.update.content.type === "text"
2171
2313
  )
2172
- activePrompt.emittedAssistantText += notification.update.content.text;
2314
+ promptOwner.emittedAssistantText += notification.update.content.text;
2173
2315
  await this.#publishSessionUpdate(id, notification, adapter);
2174
2316
  }
2175
2317
  }
@@ -2188,14 +2330,16 @@ export class AcpAgent implements Agent {
2188
2330
  adapter,
2189
2331
  );
2190
2332
  }
2191
- if (event.type === "message_end" && object(event.message)?.role === "assistant" && activePrompt)
2192
- activePrompt.messageProgress = undefined;
2333
+ if (event.type === "message_end" && object(event.message)?.role === "assistant") {
2334
+ if (promptOwner) promptOwner.messageProgress = undefined;
2335
+ else record.sessionMessageProgress = undefined;
2336
+ }
2193
2337
  if (event.type === "agent_end") {
2194
2338
  const finalText = typeof event.finalText === "string" ? event.finalText : "";
2195
- if (activePrompt && finalText) {
2196
- const resolution = resolveAcpFinalText(activePrompt.emittedAssistantText, finalText);
2339
+ if (promptOwner && finalText) {
2340
+ const resolution = resolveAcpFinalText(promptOwner.emittedAssistantText, finalText);
2197
2341
  if (resolution.kind === "emit") {
2198
- activePrompt.emittedAssistantText += resolution.text;
2342
+ promptOwner.emittedAssistantText += resolution.text;
2199
2343
  await this.#publishSessionUpdate(
2200
2344
  id,
2201
2345
  {
@@ -2211,9 +2355,9 @@ export class AcpAgent implements Agent {
2211
2355
  } else if (resolution.kind === "divergent") {
2212
2356
  logger.warn("acp_final_text_diverged", {
2213
2357
  sessionId: id,
2214
- ...(activePrompt.correlation.commandId ? { commandId: activePrompt.correlation.commandId } : {}),
2215
- ...(activePrompt.correlation.turnId ? { turnId: activePrompt.correlation.turnId } : {}),
2216
- streamedLength: activePrompt.emittedAssistantText.length,
2358
+ ...(promptOwner.correlation.commandId ? { commandId: promptOwner.correlation.commandId } : {}),
2359
+ ...(promptOwner.correlation.turnId ? { turnId: promptOwner.correlation.turnId } : {}),
2360
+ streamedLength: promptOwner.emittedAssistantText.length,
2217
2361
  finalLength: resolution.final.text.length,
2218
2362
  });
2219
2363
  }