@iaforged/context-code 1.2.9 → 1.2.10

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 (172) hide show
  1. package/README.md +119 -119
  2. package/context-bootstrap.js +26 -26
  3. package/dist/src/QueryEngine.js +394 -327
  4. package/dist/src/bridge/bridgeUI.js +1 -1
  5. package/dist/src/buddy/prompt.js +4 -4
  6. package/dist/src/cli/handlers/auth.js +126 -9
  7. package/dist/src/cli/print.js +35 -1
  8. package/dist/src/commands/agent/agent.js +28 -2
  9. package/dist/src/commands/agent/agentStore.js +8 -1
  10. package/dist/src/commands/agent/index.js +1 -1
  11. package/dist/src/commands/bridge-kick.js +9 -9
  12. package/dist/src/commands/commit.js +34 -34
  13. package/dist/src/commands/init-verifiers.js +3 -3
  14. package/dist/src/commands/init.js +88 -88
  15. package/dist/src/commands/insights.js +787 -787
  16. package/dist/src/commands/install.js +19 -19
  17. package/dist/src/commands/login/login.js +21 -12
  18. package/dist/src/commands/logout/logout.js +9 -0
  19. package/dist/src/commands/model/model.js +9 -4
  20. package/dist/src/commands/orchestrate/SwarmUI.js +50 -0
  21. package/dist/src/commands/orchestrate/index.js +2 -2
  22. package/dist/src/commands/orchestrate/orchestrate.js +708 -12
  23. package/dist/src/commands/pr_comments/index.js +33 -33
  24. package/dist/src/commands/profile/index.js +1 -1
  25. package/dist/src/commands/profile/profile.js +52 -3
  26. package/dist/src/commands/provider/index.js +1 -1
  27. package/dist/src/commands/provider/provider.js +117 -45
  28. package/dist/src/commands/resumen/index.js +9 -0
  29. package/dist/src/commands/resumen/resumen.js +29 -0
  30. package/dist/src/commands/security-review.js +190 -190
  31. package/dist/src/commands/swarm-auto/index.js +9 -0
  32. package/dist/src/commands/swarm-auto/swarmAuto.js +111 -0
  33. package/dist/src/commands/swarm-init/index.js +9 -0
  34. package/dist/src/commands/swarm-init/swarmInit.js +72 -0
  35. package/dist/src/commands/team/team.js +39 -6
  36. package/dist/src/commands.js +14 -0
  37. package/dist/src/components/LogoV2/CondensedLogo.js +2 -2
  38. package/dist/src/components/PromptInput/PromptInputQueuedCommands.js +3 -3
  39. package/dist/src/components/agents/agentFileUtils.js +6 -6
  40. package/dist/src/components/permissions/hooks.js +5 -5
  41. package/dist/src/constants/outputStyles.js +83 -83
  42. package/dist/src/core/agents/blueprints.js +58 -0
  43. package/dist/src/core/agents/cliAdapter.js +61 -0
  44. package/dist/src/core/agents/registry.js +93 -0
  45. package/dist/src/core/agents/runtime.js +4 -0
  46. package/dist/src/core/agents/runtime.smoke.js +42 -0
  47. package/dist/src/core/agents/swarm.smoke.js +48 -0
  48. package/dist/src/core/agents/swarmTools.js +38 -0
  49. package/dist/src/core/auth/index.js +2 -0
  50. package/dist/src/core/auth/loginCliAdapter.js +24 -0
  51. package/dist/src/core/auth/loginCore.js +67 -0
  52. package/dist/src/core/auth/logoutCliAdapter.js +34 -0
  53. package/dist/src/core/auth/logoutCore.js +52 -0
  54. package/dist/src/core/auth/preflight.smoke.js +151 -0
  55. package/dist/src/core/index.js +21 -0
  56. package/dist/src/core/mcp/blueprints.js +27 -0
  57. package/dist/src/core/mcp/common.js +14 -0
  58. package/dist/src/core/mcp/runtime.js +67 -0
  59. package/dist/src/core/mcp/runtime.smoke.js +50 -0
  60. package/dist/src/core/mcp/swarmClient.js +40 -0
  61. package/dist/src/core/mcp/swarmSetup.js +43 -0
  62. package/dist/src/core/providers/cliAdapter.js +39 -0
  63. package/dist/src/core/providers/contracts.js +1 -0
  64. package/dist/src/core/providers/index.js +3 -0
  65. package/dist/src/core/providers/llmCore.js +123 -0
  66. package/dist/src/core/providers/providerCore.js +141 -0
  67. package/dist/src/core/providers/providerModelCompatibility.js +98 -0
  68. package/dist/src/core/providers/providerParitySmoke.js +83 -0
  69. package/dist/src/core/providers/providerProfileModelSmoke.js +80 -0
  70. package/dist/src/core/query/contracts.js +1 -0
  71. package/dist/src/core/query/runtime.js +117 -0
  72. package/dist/src/core/query/runtime.smoke.js +39 -0
  73. package/dist/src/core/query/timelineThinking.smoke.js +25 -0
  74. package/dist/src/core/query/wiring.smoke.js +76 -0
  75. package/dist/src/core/skills/cliAdapter.js +38 -0
  76. package/dist/src/core/skills/index.js +52 -0
  77. package/dist/src/core/skills/runtime.smoke.js +53 -0
  78. package/dist/src/core/tasks/runtime.js +205 -0
  79. package/dist/src/core/tasks/runtime.smoke.js +63 -0
  80. package/dist/src/core/tasks/sdkAdapter.js +4 -0
  81. package/dist/src/core/tools/contracts.js +3 -0
  82. package/dist/src/core/tools/fileResolution.js +112 -0
  83. package/dist/src/core/tools/fileResolution.smoke.js +33 -0
  84. package/dist/src/core/tools/filesCore.js +51 -0
  85. package/dist/src/core/tools/filesCore.smoke.js +108 -0
  86. package/dist/src/core/tools/gitCore.js +20 -0
  87. package/dist/src/core/tools/imageParity.smoke.js +36 -0
  88. package/dist/src/core/tools/notebookParity.smoke.js +68 -0
  89. package/dist/src/core/tools/registry.js +22 -0
  90. package/dist/src/core/tools/runtime.smoke.js +32 -0
  91. package/dist/src/core/tools/shellCore.js +60 -0
  92. package/dist/src/core/types/agentContext.js +9 -0
  93. package/dist/src/core/types/auth.js +3 -0
  94. package/dist/src/core/types/command.js +13 -0
  95. package/dist/src/core/types/provider.js +3 -0
  96. package/dist/src/core/types/sdkEvent.js +10 -0
  97. package/dist/src/core/types/swarm.js +1 -0
  98. package/dist/src/cost-tracker.js +3 -3
  99. package/dist/src/hooks/useAwaySummary.js +22 -9
  100. package/dist/src/main.js +32 -2
  101. package/dist/src/screens/REPL.js +9 -0
  102. package/dist/src/services/AgentSummary/agentSummary.js +10 -10
  103. package/dist/src/services/autoDream/autoDream.js +5 -5
  104. package/dist/src/services/autoDream/consolidationPrompt.js +49 -49
  105. package/dist/src/services/compact/prompt.js +238 -238
  106. package/dist/src/services/limits/sessionCounter.js +17 -17
  107. package/dist/src/services/mcp/client.js +27 -1
  108. package/dist/src/services/orchestration/execution/AgentTaskExecutor.js +39 -20
  109. package/dist/src/services/orchestration/execution/OrchestrationExecutionRuntime.js +65 -58
  110. package/dist/src/skills/bundled/loop.js +57 -57
  111. package/dist/src/skills/bundled/remember.js +53 -53
  112. package/dist/src/skills/bundled/simplify.js +49 -49
  113. package/dist/src/skills/bundled/skillify.js +2 -2
  114. package/dist/src/state/onChangeAppState.js +6 -0
  115. package/dist/src/tasks/LocalAgentTask/LocalAgentTask.js +5 -5
  116. package/dist/src/tasks/LocalMainSessionTask.js +5 -5
  117. package/dist/src/tasks/LocalShellTask/LocalShellTask.js +13 -13
  118. package/dist/src/tools/AgentTool/forkSubagent.js +25 -25
  119. package/dist/src/tools/AskUserQuestionTool/prompt.js +29 -29
  120. package/dist/src/tools/BashTool/BashTool.js +27 -2
  121. package/dist/src/tools/BriefTool/prompt.js +14 -14
  122. package/dist/src/tools/EnterPlanModeTool/EnterPlanModeTool.js +12 -12
  123. package/dist/src/tools/EnterPlanModeTool/prompt.js +140 -140
  124. package/dist/src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.js +18 -18
  125. package/dist/src/tools/ExitPlanModeTool/prompt.js +23 -23
  126. package/dist/src/tools/ExitWorktreeTool/prompt.js +29 -29
  127. package/dist/src/tools/FileEditTool/prompt.js +7 -7
  128. package/dist/src/tools/FileReadTool/FileReadTool.js +18 -1
  129. package/dist/src/tools/FileWriteTool/prompt.js +6 -6
  130. package/dist/src/tools/GlobTool/prompt.js +4 -4
  131. package/dist/src/tools/GrepTool/prompt.js +10 -10
  132. package/dist/src/tools/LSPTool/prompt.js +18 -18
  133. package/dist/src/tools/ListMcpResourcesTool/prompt.js +15 -15
  134. package/dist/src/tools/PowerShellTool/PowerShellTool.js +25 -2
  135. package/dist/src/tools/ReadMcpResourceTool/prompt.js +13 -13
  136. package/dist/src/tools/SendMessageTool/prompt.js +36 -36
  137. package/dist/src/tools/SkillTool/prompt.js +21 -21
  138. package/dist/src/tools/SleepTool/prompt.js +10 -10
  139. package/dist/src/tools/TaskCreateTool/prompt.js +41 -41
  140. package/dist/src/tools/TaskGetTool/prompt.js +21 -21
  141. package/dist/src/tools/TaskListTool/prompt.js +30 -30
  142. package/dist/src/tools/TaskOutputTool/TaskOutputTool.js +8 -8
  143. package/dist/src/tools/TaskStopTool/prompt.js +5 -5
  144. package/dist/src/tools/TaskUpdateTool/prompt.js +74 -74
  145. package/dist/src/tools/TodoWriteTool/prompt.js +178 -178
  146. package/dist/src/tools/ToolSearchTool/prompt.js +9 -9
  147. package/dist/src/tools/WebFetchTool/WebFetchTool.js +9 -9
  148. package/dist/src/tools/WebFetchTool/prompt.js +31 -31
  149. package/dist/src/tools/WebSearchTool/prompt.js +26 -26
  150. package/dist/src/utils/agentContext.js +2 -0
  151. package/dist/src/utils/agenticSessionSearch.js +38 -38
  152. package/dist/src/utils/config.js +2 -0
  153. package/dist/src/utils/genericProcessUtils.js +21 -21
  154. package/dist/src/utils/heapDumpService.js +4 -4
  155. package/dist/src/utils/mcpValidation.js +2 -2
  156. package/dist/src/utils/model/modelStrings.js +1 -1
  157. package/dist/src/utils/model/providers.js +5 -0
  158. package/dist/src/utils/orchestration/store/providerAgentStore.js +22 -22
  159. package/dist/src/utils/orchestration/store/providerWorkspaceStore.js +10 -10
  160. package/dist/src/utils/orchestration/store/runStore.js +68 -68
  161. package/dist/src/utils/orchestration/store/teamStore.js +28 -28
  162. package/dist/src/utils/permissions/permissionExplainer.js +6 -6
  163. package/dist/src/utils/permissions/permissionsDb.js +43 -43
  164. package/dist/src/utils/sdkEventQueue.js +2 -0
  165. package/dist/src/utils/secureStorage/sqliteStorage.js +12 -12
  166. package/dist/src/utils/standardMcp/common.js +15 -0
  167. package/dist/src/utils/standardMcp/setup.js +52 -0
  168. package/dist/src/utils/swarm/teammatePromptAddendum.js +10 -10
  169. package/dist/src/utils/task/framework.js +6 -6
  170. package/package.json +1 -1
  171. package/dist/src/commands/usage/index.js +0 -7
  172. package/dist/src/commands/usage/usage.js +0 -5
@@ -36,6 +36,7 @@ import { flushSessionStorage, recordTranscript, } from './utils/sessionStorage.j
36
36
  import { asSystemPrompt } from './utils/systemPromptType.js';
37
37
  import { resolveThemeSetting } from './utils/systemTheme.js';
38
38
  import { shouldEnableThinkingByDefault, } from './utils/thinking.js';
39
+ import { CoreQueryRuntimeEmitter } from './core/query/runtime.js';
39
40
  // Lazy: MessageSelector.tsx pulls React/ink; only needed for message filtering at query time
40
41
  /* eslint-disable @typescript-eslint/no-require-imports */
41
42
  const messageSelector = () => require('./components/MessageSelector.js');
@@ -44,6 +45,7 @@ import { buildSystemInitMessage, sdkCompatToolName, } from './utils/messages/sys
44
45
  import { getScratchpadDir, isScratchpadEnabled, } from './utils/permissions/filesystem.js';
45
46
  /* eslint-enable @typescript-eslint/no-require-imports */
46
47
  import { handleOrphanedPermission, isResultSuccessful, normalizeMessage, } from './utils/queryHelpers.js';
48
+ import { isAbortError } from './utils/errors.js';
47
49
  // Dead code elimination: conditional import for coordinator mode
48
50
  /* eslint-disable @typescript-eslint/no-require-imports */
49
51
  const getCoordinatorUserContext = feature('COORDINATOR_MODE')
@@ -443,154 +445,321 @@ export class QueryEngine {
443
445
  const initialStructuredOutputCalls = jsonSchema
444
446
  ? countToolCalls(this.mutableMessages, SYNTHETIC_OUTPUT_TOOL_NAME)
445
447
  : 0;
446
- for await (const message of query({
447
- messages,
448
- systemPrompt,
449
- userContext,
450
- systemContext,
451
- canUseTool: wrappedCanUseTool,
452
- toolUseContext: processUserInputContext,
453
- fallbackModel,
454
- querySource: 'sdk',
455
- maxTurns,
456
- taskBudget,
457
- })) {
458
- // Record assistant, user, and compact boundary messages
459
- if (message.type === 'assistant' ||
460
- message.type === 'user' ||
461
- (message.type === 'system' && message.subtype === 'compact_boundary')) {
462
- // Before writing a compact boundary, flush any in-memory-only
463
- // messages up through the preservedSegment tail. Attachments and
464
- // progress are now recorded inline (their switch cases below), but
465
- // this flush still matters for the preservedSegment tail walk.
466
- // If the SDK subprocess restarts before then (claude-desktop kills
467
- // between turns), tailUuid points to a never-written message →
468
- // applyPreservedSegmentRelinks fails its tail→head walk → returns
469
- // without pruning → resume loads full pre-compact history.
470
- if (persistSession &&
471
- message.type === 'system' &&
472
- message.subtype === 'compact_boundary') {
473
- const tailUuid = message.compactMetadata?.preservedSegment?.tailUuid;
474
- if (tailUuid) {
475
- const tailIdx = this.mutableMessages.findLastIndex(m => m.uuid === tailUuid);
476
- if (tailIdx !== -1) {
477
- await recordTranscript(this.mutableMessages.slice(0, tailIdx + 1));
448
+ const coreQueryEnabled = process.env.CORE_QUERY_ENABLED === '1';
449
+ const coreEmitter = coreQueryEnabled
450
+ ? new CoreQueryRuntimeEmitter(this.config.onCoreQueryEvent)
451
+ : null;
452
+ coreEmitter?.emitSessionState('started');
453
+ const emitCoreTerminalFailure = (reason) => {
454
+ coreEmitter?.emitResult(true, lastStopReason);
455
+ coreEmitter?.emitSessionState(reason === 'cancelled' || reason === 'canceled' ? 'cancelled' : 'failed', reason);
456
+ };
457
+ const emitCoreTerminalSuccess = (isError, result) => {
458
+ coreEmitter?.emitResult(isError, lastStopReason, result);
459
+ coreEmitter?.emitSessionState(isError ? 'failed' : 'completed');
460
+ };
461
+ let queryLoopError = null;
462
+ try {
463
+ for await (const message of query({
464
+ messages,
465
+ systemPrompt,
466
+ userContext,
467
+ systemContext,
468
+ canUseTool: wrappedCanUseTool,
469
+ toolUseContext: processUserInputContext,
470
+ fallbackModel,
471
+ querySource: 'sdk',
472
+ maxTurns,
473
+ taskBudget,
474
+ })) {
475
+ coreEmitter?.emitRunning();
476
+ // Record assistant, user, and compact boundary messages
477
+ if (message.type === 'assistant' ||
478
+ message.type === 'user' ||
479
+ (message.type === 'system' && message.subtype === 'compact_boundary')) {
480
+ // Before writing a compact boundary, flush any in-memory-only
481
+ // messages up through the preservedSegment tail. Attachments and
482
+ // progress are now recorded inline (their switch cases below), but
483
+ // this flush still matters for the preservedSegment tail walk.
484
+ // If the SDK subprocess restarts before then (claude-desktop kills
485
+ // between turns), tailUuid points to a never-written message →
486
+ // applyPreservedSegmentRelinks fails its tail→head walk → returns
487
+ // without pruning → resume loads full pre-compact history.
488
+ if (persistSession &&
489
+ message.type === 'system' &&
490
+ message.subtype === 'compact_boundary') {
491
+ const tailUuid = message.compactMetadata?.preservedSegment?.tailUuid;
492
+ if (tailUuid) {
493
+ const tailIdx = this.mutableMessages.findLastIndex(m => m.uuid === tailUuid);
494
+ if (tailIdx !== -1) {
495
+ await recordTranscript(this.mutableMessages.slice(0, tailIdx + 1));
496
+ }
478
497
  }
479
498
  }
480
- }
481
- messages.push(message);
482
- if (persistSession) {
483
- // Fire-and-forget for assistant messages. claude.ts yields one
484
- // assistant message per content block, then mutates the last
485
- // one's message.usage/stop_reason on message_delta relying on
486
- // the write queue's 100ms lazy jsonStringify. Awaiting here
487
- // blocks ask()'s generator, so message_delta can't run until
488
- // every block is consumed; the drain timer (started at block 1)
489
- // elapses first. Interactive CC doesn't hit this because
490
- // useLogMessages.ts fire-and-forgets. enqueueWrite is
491
- // order-preserving so fire-and-forget here is safe.
492
- if (message.type === 'assistant') {
493
- void recordTranscript(messages);
499
+ messages.push(message);
500
+ if (persistSession) {
501
+ // Fire-and-forget for assistant messages. claude.ts yields one
502
+ // assistant message per content block, then mutates the last
503
+ // one's message.usage/stop_reason on message_delta relying on
504
+ // the write queue's 100ms lazy jsonStringify. Awaiting here
505
+ // blocks ask()'s generator, so message_delta can't run until
506
+ // every block is consumed; the drain timer (started at block 1)
507
+ // elapses first. Interactive CC doesn't hit this because
508
+ // useLogMessages.ts fire-and-forgets. enqueueWrite is
509
+ // order-preserving so fire-and-forget here is safe.
510
+ if (message.type === 'assistant') {
511
+ void recordTranscript(messages);
512
+ }
513
+ else {
514
+ await recordTranscript(messages);
515
+ }
494
516
  }
495
- else {
496
- await recordTranscript(messages);
517
+ // Acknowledge initial user messages after first transcript recording
518
+ if (!hasAcknowledgedInitialMessages && messagesToAck.length > 0) {
519
+ hasAcknowledgedInitialMessages = true;
520
+ for (const msgToAck of messagesToAck) {
521
+ if (msgToAck.type === 'user') {
522
+ yield {
523
+ type: 'user',
524
+ message: msgToAck.message,
525
+ session_id: getSessionId(),
526
+ parent_tool_use_id: null,
527
+ uuid: msgToAck.uuid,
528
+ timestamp: msgToAck.timestamp,
529
+ isReplay: true,
530
+ };
531
+ }
532
+ }
497
533
  }
498
534
  }
499
- // Acknowledge initial user messages after first transcript recording
500
- if (!hasAcknowledgedInitialMessages && messagesToAck.length > 0) {
501
- hasAcknowledgedInitialMessages = true;
502
- for (const msgToAck of messagesToAck) {
503
- if (msgToAck.type === 'user') {
535
+ if (message.type === 'user') {
536
+ turnCount++;
537
+ coreEmitter?.setTurn(turnCount);
538
+ }
539
+ coreEmitter?.emitFromQueryMessage(message);
540
+ switch (message.type) {
541
+ case 'tombstone':
542
+ // Tombstone messages are control signals for removing messages, skip them
543
+ break;
544
+ case 'assistant':
545
+ // Capture stop_reason if already set (synthetic messages). For
546
+ // streamed responses, this is null at content_block_stop time;
547
+ // the real value arrives via message_delta (handled below).
548
+ if (message.message.stop_reason != null) {
549
+ lastStopReason = message.message.stop_reason;
550
+ }
551
+ this.mutableMessages.push(message);
552
+ yield* normalizeMessage(message);
553
+ break;
554
+ case 'progress':
555
+ this.mutableMessages.push(message);
556
+ // Record inline so the dedup loop in the next ask() call sees it
557
+ // as already-recorded. Without this, deferred progress interleaves
558
+ // with already-recorded tool_results in mutableMessages, and the
559
+ // dedup walk freezes startingParentUuid at the wrong message —
560
+ // forking the chain and orphaning the conversation on resume.
561
+ if (persistSession) {
562
+ messages.push(message);
563
+ void recordTranscript(messages);
564
+ }
565
+ yield* normalizeMessage(message);
566
+ break;
567
+ case 'user':
568
+ this.mutableMessages.push(message);
569
+ yield* normalizeMessage(message);
570
+ break;
571
+ case 'stream_event':
572
+ if (message.event.type === 'message_start') {
573
+ // Reset current message usage for new message
574
+ currentMessageUsage = EMPTY_USAGE;
575
+ currentMessageUsage = updateUsage(currentMessageUsage, message.event.message.usage);
576
+ }
577
+ if (message.event.type === 'message_delta') {
578
+ currentMessageUsage = updateUsage(currentMessageUsage, message.event.usage);
579
+ // Capture stop_reason from message_delta. The assistant message
580
+ // is yielded at content_block_stop with stop_reason=null; the
581
+ // real value only arrives here (see claude.ts message_delta
582
+ // handler). Without this, result.stop_reason is always null.
583
+ if (message.event.delta.stop_reason != null) {
584
+ lastStopReason = message.event.delta.stop_reason;
585
+ }
586
+ }
587
+ if (message.event.type === 'message_stop') {
588
+ // Accumulate current message usage into total
589
+ this.totalUsage = accumulateUsage(this.totalUsage, currentMessageUsage);
590
+ }
591
+ if (includePartialMessages) {
592
+ yield {
593
+ type: 'stream_event',
594
+ event: message.event,
595
+ session_id: getSessionId(),
596
+ parent_tool_use_id: null,
597
+ uuid: randomUUID(),
598
+ };
599
+ }
600
+ break;
601
+ case 'attachment':
602
+ this.mutableMessages.push(message);
603
+ // Record inline (same reason as progress above).
604
+ if (persistSession) {
605
+ messages.push(message);
606
+ void recordTranscript(messages);
607
+ }
608
+ // Extract structured output from StructuredOutput tool calls
609
+ if (message.attachment.type === 'structured_output') {
610
+ structuredOutputFromTool = message.attachment.data;
611
+ }
612
+ // Handle max turns reached signal from query.ts
613
+ else if (message.attachment.type === 'max_turns_reached') {
614
+ if (persistSession) {
615
+ if (isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) ||
616
+ isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK)) {
617
+ await flushSessionStorage();
618
+ }
619
+ }
620
+ yield {
621
+ type: 'result',
622
+ subtype: 'error_max_turns',
623
+ duration_ms: Date.now() - startTime,
624
+ duration_api_ms: getTotalAPIDuration(),
625
+ is_error: true,
626
+ num_turns: message.attachment.turnCount,
627
+ stop_reason: lastStopReason,
628
+ session_id: getSessionId(),
629
+ total_cost_usd: getTotalCost(),
630
+ usage: this.totalUsage,
631
+ modelUsage: getModelUsage(),
632
+ permission_denials: this.permissionDenials,
633
+ fast_mode_state: getFastModeState(mainLoopModel, initialAppState.fastMode),
634
+ uuid: randomUUID(),
635
+ errors: [
636
+ `Reached maximum number of turns (${message.attachment.maxTurns})`,
637
+ ],
638
+ };
639
+ emitCoreTerminalFailure('error_max_turns');
640
+ return;
641
+ }
642
+ // Yield queued_command attachments as SDK user message replays
643
+ else if (replayUserMessages &&
644
+ message.attachment.type === 'queued_command') {
504
645
  yield {
505
646
  type: 'user',
506
- message: msgToAck.message,
647
+ message: {
648
+ role: 'user',
649
+ content: message.attachment.prompt,
650
+ },
507
651
  session_id: getSessionId(),
508
652
  parent_tool_use_id: null,
509
- uuid: msgToAck.uuid,
510
- timestamp: msgToAck.timestamp,
653
+ uuid: message.attachment.source_uuid || message.uuid,
654
+ timestamp: message.timestamp,
511
655
  isReplay: true,
512
656
  };
513
657
  }
514
- }
515
- }
516
- }
517
- if (message.type === 'user') {
518
- turnCount++;
519
- }
520
- switch (message.type) {
521
- case 'tombstone':
522
- // Tombstone messages are control signals for removing messages, skip them
523
- break;
524
- case 'assistant':
525
- // Capture stop_reason if already set (synthetic messages). For
526
- // streamed responses, this is null at content_block_stop time;
527
- // the real value arrives via message_delta (handled below).
528
- if (message.message.stop_reason != null) {
529
- lastStopReason = message.message.stop_reason;
530
- }
531
- this.mutableMessages.push(message);
532
- yield* normalizeMessage(message);
533
- break;
534
- case 'progress':
535
- this.mutableMessages.push(message);
536
- // Record inline so the dedup loop in the next ask() call sees it
537
- // as already-recorded. Without this, deferred progress interleaves
538
- // with already-recorded tool_results in mutableMessages, and the
539
- // dedup walk freezes startingParentUuid at the wrong message —
540
- // forking the chain and orphaning the conversation on resume.
541
- if (persistSession) {
542
- messages.push(message);
543
- void recordTranscript(messages);
544
- }
545
- yield* normalizeMessage(message);
546
- break;
547
- case 'user':
548
- this.mutableMessages.push(message);
549
- yield* normalizeMessage(message);
550
- break;
551
- case 'stream_event':
552
- if (message.event.type === 'message_start') {
553
- // Reset current message usage for new message
554
- currentMessageUsage = EMPTY_USAGE;
555
- currentMessageUsage = updateUsage(currentMessageUsage, message.event.message.usage);
556
- }
557
- if (message.event.type === 'message_delta') {
558
- currentMessageUsage = updateUsage(currentMessageUsage, message.event.usage);
559
- // Capture stop_reason from message_delta. The assistant message
560
- // is yielded at content_block_stop with stop_reason=null; the
561
- // real value only arrives here (see claude.ts message_delta
562
- // handler). Without this, result.stop_reason is always null.
563
- if (message.event.delta.stop_reason != null) {
564
- lastStopReason = message.event.delta.stop_reason;
658
+ break;
659
+ case 'stream_request_start':
660
+ // Don't yield stream request start messages
661
+ break;
662
+ case 'system': {
663
+ // Snip boundary: replay on our store to remove zombie messages and
664
+ // stale markers. The yielded boundary is a signal, not data to push —
665
+ // the replay produces its own equivalent boundary. Without this,
666
+ // markers persist and re-trigger on every turn, and mutableMessages
667
+ // never shrinks (memory leak in long SDK sessions). The subtype
668
+ // check lives inside the injected callback so feature-gated strings
669
+ // stay out of this file (excluded-strings check).
670
+ const snipResult = this.config.snipReplay?.(message, this.mutableMessages);
671
+ if (snipResult !== undefined) {
672
+ if (snipResult.executed) {
673
+ this.mutableMessages.length = 0;
674
+ this.mutableMessages.push(...snipResult.messages);
675
+ }
676
+ break;
565
677
  }
678
+ this.mutableMessages.push(message);
679
+ // Yield compact boundary messages to SDK
680
+ if (message.subtype === 'compact_boundary' &&
681
+ message.compactMetadata) {
682
+ // Release pre-compaction messages for GC. The boundary was just
683
+ // pushed so it's the last element. query.ts already uses
684
+ // getMessagesAfterCompactBoundary() internally, so only
685
+ // post-boundary messages are needed going forward.
686
+ const mutableBoundaryIdx = this.mutableMessages.length - 1;
687
+ if (mutableBoundaryIdx > 0) {
688
+ this.mutableMessages.splice(0, mutableBoundaryIdx);
689
+ }
690
+ const localBoundaryIdx = messages.length - 1;
691
+ if (localBoundaryIdx > 0) {
692
+ messages.splice(0, localBoundaryIdx);
693
+ }
694
+ yield {
695
+ type: 'system',
696
+ subtype: 'compact_boundary',
697
+ session_id: getSessionId(),
698
+ uuid: message.uuid,
699
+ compact_metadata: toSDKCompactMetadata(message.compactMetadata),
700
+ };
701
+ }
702
+ if (message.subtype === 'api_error') {
703
+ yield {
704
+ type: 'system',
705
+ subtype: 'api_retry',
706
+ attempt: message.retryAttempt,
707
+ max_retries: message.maxRetries,
708
+ retry_delay_ms: message.retryInMs,
709
+ error_status: message.error.status ?? null,
710
+ error: categorizeRetryableAPIError(message.error),
711
+ session_id: getSessionId(),
712
+ uuid: message.uuid,
713
+ };
714
+ }
715
+ // Don't yield other system messages in headless mode
716
+ break;
566
717
  }
567
- if (message.event.type === 'message_stop') {
568
- // Accumulate current message usage into total
569
- this.totalUsage = accumulateUsage(this.totalUsage, currentMessageUsage);
570
- }
571
- if (includePartialMessages) {
718
+ case 'tool_use_summary':
719
+ // Yield tool use summary messages to SDK
572
720
  yield {
573
- type: 'stream_event',
574
- event: message.event,
721
+ type: 'tool_use_summary',
722
+ summary: message.summary,
723
+ preceding_tool_use_ids: message.precedingToolUseIds,
575
724
  session_id: getSessionId(),
576
- parent_tool_use_id: null,
577
- uuid: randomUUID(),
725
+ uuid: message.uuid,
578
726
  };
579
- }
580
- break;
581
- case 'attachment':
582
- this.mutableMessages.push(message);
583
- // Record inline (same reason as progress above).
727
+ break;
728
+ }
729
+ // Check if USD budget has been exceeded
730
+ if (maxBudgetUsd !== undefined && getTotalCost() >= maxBudgetUsd) {
584
731
  if (persistSession) {
585
- messages.push(message);
586
- void recordTranscript(messages);
587
- }
588
- // Extract structured output from StructuredOutput tool calls
589
- if (message.attachment.type === 'structured_output') {
590
- structuredOutputFromTool = message.attachment.data;
732
+ if (isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) ||
733
+ isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK)) {
734
+ await flushSessionStorage();
735
+ }
591
736
  }
592
- // Handle max turns reached signal from query.ts
593
- else if (message.attachment.type === 'max_turns_reached') {
737
+ yield {
738
+ type: 'result',
739
+ subtype: 'error_max_budget_usd',
740
+ duration_ms: Date.now() - startTime,
741
+ duration_api_ms: getTotalAPIDuration(),
742
+ is_error: true,
743
+ num_turns: turnCount,
744
+ stop_reason: lastStopReason,
745
+ session_id: getSessionId(),
746
+ total_cost_usd: getTotalCost(),
747
+ usage: this.totalUsage,
748
+ modelUsage: getModelUsage(),
749
+ permission_denials: this.permissionDenials,
750
+ fast_mode_state: getFastModeState(mainLoopModel, initialAppState.fastMode),
751
+ uuid: randomUUID(),
752
+ errors: [`Reached maximum budget ($${maxBudgetUsd})`],
753
+ };
754
+ emitCoreTerminalFailure('error_max_budget_usd');
755
+ return;
756
+ }
757
+ // Check if structured output retry limit exceeded (only on user messages)
758
+ if (message.type === 'user' && jsonSchema) {
759
+ const currentCalls = countToolCalls(this.mutableMessages, SYNTHETIC_OUTPUT_TOOL_NAME);
760
+ const callsThisQuery = currentCalls - initialStructuredOutputCalls;
761
+ const maxRetries = parseInt(process.env.MAX_STRUCTURED_OUTPUT_RETRIES || '5', 10);
762
+ if (callsThisQuery >= maxRetries) {
594
763
  if (persistSession) {
595
764
  if (isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) ||
596
765
  isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK)) {
@@ -599,11 +768,11 @@ export class QueryEngine {
599
768
  }
600
769
  yield {
601
770
  type: 'result',
602
- subtype: 'error_max_turns',
771
+ subtype: 'error_max_structured_output_retries',
603
772
  duration_ms: Date.now() - startTime,
604
773
  duration_api_ms: getTotalAPIDuration(),
605
774
  is_error: true,
606
- num_turns: message.attachment.turnCount,
775
+ num_turns: turnCount,
607
776
  stop_reason: lastStopReason,
608
777
  session_id: getSessionId(),
609
778
  total_cost_usd: getTotalCost(),
@@ -613,109 +782,42 @@ export class QueryEngine {
613
782
  fast_mode_state: getFastModeState(mainLoopModel, initialAppState.fastMode),
614
783
  uuid: randomUUID(),
615
784
  errors: [
616
- `Reached maximum number of turns (${message.attachment.maxTurns})`,
785
+ `Failed to provide valid structured output after ${maxRetries} attempts`,
617
786
  ],
618
787
  };
788
+ emitCoreTerminalFailure('error_max_structured_output_retries');
619
789
  return;
620
790
  }
621
- // Yield queued_command attachments as SDK user message replays
622
- else if (replayUserMessages &&
623
- message.attachment.type === 'queued_command') {
624
- yield {
625
- type: 'user',
626
- message: {
627
- role: 'user',
628
- content: message.attachment.prompt,
629
- },
630
- session_id: getSessionId(),
631
- parent_tool_use_id: null,
632
- uuid: message.attachment.source_uuid || message.uuid,
633
- timestamp: message.timestamp,
634
- isReplay: true,
635
- };
636
- }
637
- break;
638
- case 'stream_request_start':
639
- // Don't yield stream request start messages
640
- break;
641
- case 'system': {
642
- // Snip boundary: replay on our store to remove zombie messages and
643
- // stale markers. The yielded boundary is a signal, not data to push —
644
- // the replay produces its own equivalent boundary. Without this,
645
- // markers persist and re-trigger on every turn, and mutableMessages
646
- // never shrinks (memory leak in long SDK sessions). The subtype
647
- // check lives inside the injected callback so feature-gated strings
648
- // stay out of this file (excluded-strings check).
649
- const snipResult = this.config.snipReplay?.(message, this.mutableMessages);
650
- if (snipResult !== undefined) {
651
- if (snipResult.executed) {
652
- this.mutableMessages.length = 0;
653
- this.mutableMessages.push(...snipResult.messages);
654
- }
655
- break;
656
- }
657
- this.mutableMessages.push(message);
658
- // Yield compact boundary messages to SDK
659
- if (message.subtype === 'compact_boundary' &&
660
- message.compactMetadata) {
661
- // Release pre-compaction messages for GC. The boundary was just
662
- // pushed so it's the last element. query.ts already uses
663
- // getMessagesAfterCompactBoundary() internally, so only
664
- // post-boundary messages are needed going forward.
665
- const mutableBoundaryIdx = this.mutableMessages.length - 1;
666
- if (mutableBoundaryIdx > 0) {
667
- this.mutableMessages.splice(0, mutableBoundaryIdx);
668
- }
669
- const localBoundaryIdx = messages.length - 1;
670
- if (localBoundaryIdx > 0) {
671
- messages.splice(0, localBoundaryIdx);
672
- }
673
- yield {
674
- type: 'system',
675
- subtype: 'compact_boundary',
676
- session_id: getSessionId(),
677
- uuid: message.uuid,
678
- compact_metadata: toSDKCompactMetadata(message.compactMetadata),
679
- };
680
- }
681
- if (message.subtype === 'api_error') {
682
- yield {
683
- type: 'system',
684
- subtype: 'api_retry',
685
- attempt: message.retryAttempt,
686
- max_retries: message.maxRetries,
687
- retry_delay_ms: message.retryInMs,
688
- error_status: message.error.status ?? null,
689
- error: categorizeRetryableAPIError(message.error),
690
- session_id: getSessionId(),
691
- uuid: message.uuid,
692
- };
693
- }
694
- // Don't yield other system messages in headless mode
695
- break;
696
791
  }
697
- case 'tool_use_summary':
698
- // Yield tool use summary messages to SDK
699
- yield {
700
- type: 'tool_use_summary',
701
- summary: message.summary,
702
- preceding_tool_use_ids: message.precedingToolUseIds,
703
- session_id: getSessionId(),
704
- uuid: message.uuid,
705
- };
706
- break;
707
792
  }
708
- // Check if USD budget has been exceeded
709
- if (maxBudgetUsd !== undefined && getTotalCost() >= maxBudgetUsd) {
710
- if (persistSession) {
711
- if (isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) ||
712
- isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK)) {
713
- await flushSessionStorage();
714
- }
793
+ // Stop hooks yield progress/attachment messages AFTER the assistant
794
+ // response (via yield* handleStopHooks in query.ts). Since #23537 pushes
795
+ // those to `messages` inline, last(messages) can be a progress/attachment
796
+ // instead of the assistant — which makes textResult extraction below
797
+ // return '' and -p mode emit a blank line. Allowlist to assistant|user:
798
+ // isResultSuccessful handles both (user with all tool_result blocks is a
799
+ // valid successful terminal state).
800
+ const result = messages.findLast(m => m.type === 'assistant' || m.type === 'user');
801
+ // Capture for the error_during_execution diagnostic — isResultSuccessful
802
+ // is a type predicate (message is Message), so inside the false branch
803
+ // `result` narrows to never and these accesses don't typecheck.
804
+ const edeResultType = result?.type ?? 'undefined';
805
+ const edeLastContentType = result?.type === 'assistant'
806
+ ? (last(result.message.content)?.type ?? 'none')
807
+ : 'n/a';
808
+ // Flush buffered transcript writes before yielding result.
809
+ // The desktop app kills the CLI process immediately after receiving the
810
+ // result message, so any unflushed writes would be lost.
811
+ if (persistSession) {
812
+ if (isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) ||
813
+ isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK)) {
814
+ await flushSessionStorage();
715
815
  }
816
+ }
817
+ if (!isResultSuccessful(result, lastStopReason)) {
716
818
  yield {
717
819
  type: 'result',
718
- subtype: 'error_max_budget_usd',
820
+ subtype: 'error_during_execution',
719
821
  duration_ms: Date.now() - startTime,
720
822
  duration_api_ms: getTotalAPIDuration(),
721
823
  is_error: true,
@@ -728,70 +830,76 @@ export class QueryEngine {
728
830
  permission_denials: this.permissionDenials,
729
831
  fast_mode_state: getFastModeState(mainLoopModel, initialAppState.fastMode),
730
832
  uuid: randomUUID(),
731
- errors: [`Reached maximum budget ($${maxBudgetUsd})`],
833
+ // Diagnostic prefix: these are what isResultSuccessful() checks — if
834
+ // the result type isn't assistant-with-text/thinking or user-with-
835
+ // tool_result, and stop_reason isn't end_turn, that's why this fired.
836
+ // errors[] is turn-scoped via the watermark; previously it dumped the
837
+ // entire process's logError buffer (ripgrep timeouts, ENOENT, etc).
838
+ errors: (() => {
839
+ const all = getInMemoryErrors();
840
+ const start = errorLogWatermark
841
+ ? all.lastIndexOf(errorLogWatermark) + 1
842
+ : 0;
843
+ return [
844
+ `[ede_diagnostic] result_type=${edeResultType} last_content_type=${edeLastContentType} stop_reason=${lastStopReason}`,
845
+ ...all.slice(start).map(_ => _.error),
846
+ ];
847
+ })(),
732
848
  };
849
+ emitCoreTerminalFailure('error_during_execution');
733
850
  return;
734
851
  }
735
- // Check if structured output retry limit exceeded (only on user messages)
736
- if (message.type === 'user' && jsonSchema) {
737
- const currentCalls = countToolCalls(this.mutableMessages, SYNTHETIC_OUTPUT_TOOL_NAME);
738
- const callsThisQuery = currentCalls - initialStructuredOutputCalls;
739
- const maxRetries = parseInt(process.env.MAX_STRUCTURED_OUTPUT_RETRIES || '5', 10);
740
- if (callsThisQuery >= maxRetries) {
741
- if (persistSession) {
742
- if (isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) ||
743
- isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK)) {
744
- await flushSessionStorage();
745
- }
746
- }
747
- yield {
748
- type: 'result',
749
- subtype: 'error_max_structured_output_retries',
750
- duration_ms: Date.now() - startTime,
751
- duration_api_ms: getTotalAPIDuration(),
752
- is_error: true,
753
- num_turns: turnCount,
754
- stop_reason: lastStopReason,
755
- session_id: getSessionId(),
756
- total_cost_usd: getTotalCost(),
757
- usage: this.totalUsage,
758
- modelUsage: getModelUsage(),
759
- permission_denials: this.permissionDenials,
760
- fast_mode_state: getFastModeState(mainLoopModel, initialAppState.fastMode),
761
- uuid: randomUUID(),
762
- errors: [
763
- `Failed to provide valid structured output after ${maxRetries} attempts`,
764
- ],
765
- };
766
- return;
852
+ // Extract the text result based on message type
853
+ let textResult = '';
854
+ let isApiError = false;
855
+ if (result.type === 'assistant') {
856
+ const lastContent = last(result.message.content);
857
+ if (lastContent?.type === 'text' &&
858
+ !SYNTHETIC_MESSAGES.has(lastContent.text)) {
859
+ textResult = lastContent.text;
767
860
  }
861
+ isApiError = Boolean(result.isApiErrorMessage);
768
862
  }
863
+ yield {
864
+ type: 'result',
865
+ subtype: 'success',
866
+ is_error: isApiError,
867
+ duration_ms: Date.now() - startTime,
868
+ duration_api_ms: getTotalAPIDuration(),
869
+ num_turns: turnCount,
870
+ result: textResult,
871
+ stop_reason: lastStopReason,
872
+ session_id: getSessionId(),
873
+ total_cost_usd: getTotalCost(),
874
+ usage: this.totalUsage,
875
+ modelUsage: getModelUsage(),
876
+ permission_denials: this.permissionDenials,
877
+ structured_output: structuredOutputFromTool,
878
+ fast_mode_state: getFastModeState(mainLoopModel, initialAppState.fastMode),
879
+ uuid: randomUUID(),
880
+ };
769
881
  }
770
- // Stop hooks yield progress/attachment messages AFTER the assistant
771
- // response (via yield* handleStopHooks in query.ts). Since #23537 pushes
772
- // those to `messages` inline, last(messages) can be a progress/attachment
773
- // instead of the assistant — which makes textResult extraction below
774
- // return '' and -p mode emit a blank line. Allowlist to assistant|user:
775
- // isResultSuccessful handles both (user with all tool_result blocks is a
776
- // valid successful terminal state).
777
- const result = messages.findLast(m => m.type === 'assistant' || m.type === 'user');
778
- // Capture for the error_during_execution diagnostic — isResultSuccessful
779
- // is a type predicate (message is Message), so inside the false branch
780
- // `result` narrows to never and these accesses don't typecheck.
781
- const edeResultType = result?.type ?? 'undefined';
782
- const edeLastContentType = result?.type === 'assistant'
783
- ? (last(result.message.content)?.type ?? 'none')
784
- : 'n/a';
785
- // Flush buffered transcript writes before yielding result.
786
- // The desktop app kills the CLI process immediately after receiving the
787
- // result message, so any unflushed writes would be lost.
788
- if (persistSession) {
789
- if (isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) ||
790
- isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK)) {
791
- await flushSessionStorage();
792
- }
882
+ catch (error) {
883
+ queryLoopError = error;
793
884
  }
794
- if (!isResultSuccessful(result, lastStopReason)) {
885
+ if (queryLoopError !== null) {
886
+ const error = queryLoopError;
887
+ const isTimeout = error?.name === 'TimeoutError' ||
888
+ error?.message?.toLowerCase().includes('timeout') ||
889
+ error?.message?.toLowerCase().includes('timed out');
890
+ const failureReason = isAbortError(error)
891
+ ? 'cancelled'
892
+ : isTimeout
893
+ ? 'timeout'
894
+ : 'error';
895
+ coreEmitter?.emitError(error?.message || String(error), failureReason);
896
+ emitCoreTerminalFailure(failureReason);
897
+ if (persistSession) {
898
+ if (isEnvTruthy(process.env.CLAUDE_CODE_EAGER_FLUSH) ||
899
+ isEnvTruthy(process.env.CLAUDE_CODE_IS_COWORK)) {
900
+ await flushSessionStorage();
901
+ }
902
+ }
795
903
  yield {
796
904
  type: 'result',
797
905
  subtype: 'error_during_execution',
@@ -807,53 +915,11 @@ export class QueryEngine {
807
915
  permission_denials: this.permissionDenials,
808
916
  fast_mode_state: getFastModeState(mainLoopModel, initialAppState.fastMode),
809
917
  uuid: randomUUID(),
810
- // Diagnostic prefix: these are what isResultSuccessful() checks — if
811
- // the result type isn't assistant-with-text/thinking or user-with-
812
- // tool_result, and stop_reason isn't end_turn, that's why this fired.
813
- // errors[] is turn-scoped via the watermark; previously it dumped the
814
- // entire process's logError buffer (ripgrep timeouts, ENOENT, etc).
815
- errors: (() => {
816
- const all = getInMemoryErrors();
817
- const start = errorLogWatermark
818
- ? all.lastIndexOf(errorLogWatermark) + 1
819
- : 0;
820
- return [
821
- `[ede_diagnostic] result_type=${edeResultType} last_content_type=${edeLastContentType} stop_reason=${lastStopReason}`,
822
- ...all.slice(start).map(_ => _.error),
823
- ];
824
- })(),
918
+ errors: [error?.message || String(error)],
825
919
  };
826
920
  return;
827
921
  }
828
- // Extract the text result based on message type
829
- let textResult = '';
830
- let isApiError = false;
831
- if (result.type === 'assistant') {
832
- const lastContent = last(result.message.content);
833
- if (lastContent?.type === 'text' &&
834
- !SYNTHETIC_MESSAGES.has(lastContent.text)) {
835
- textResult = lastContent.text;
836
- }
837
- isApiError = Boolean(result.isApiErrorMessage);
838
- }
839
- yield {
840
- type: 'result',
841
- subtype: 'success',
842
- is_error: isApiError,
843
- duration_ms: Date.now() - startTime,
844
- duration_api_ms: getTotalAPIDuration(),
845
- num_turns: turnCount,
846
- result: textResult,
847
- stop_reason: lastStopReason,
848
- session_id: getSessionId(),
849
- total_cost_usd: getTotalCost(),
850
- usage: this.totalUsage,
851
- modelUsage: getModelUsage(),
852
- permission_denials: this.permissionDenials,
853
- structured_output: structuredOutputFromTool,
854
- fast_mode_state: getFastModeState(mainLoopModel, initialAppState.fastMode),
855
- uuid: randomUUID(),
856
- };
922
+ emitCoreTerminalSuccess(isApiError, textResult);
857
923
  }
858
924
  interrupt() {
859
925
  this.abortController.abort();
@@ -878,7 +944,7 @@ export class QueryEngine {
878
944
  *
879
945
  * Convenience wrapper around QueryEngine for one-shot usage.
880
946
  */
881
- export async function* ask({ commands, prompt, promptUuid, isMeta, cwd, tools, mcpClients, verbose = false, thinkingConfig, maxTurns, maxBudgetUsd, taskBudget, canUseTool, mutableMessages = [], getReadFileCache, setReadFileCache, customSystemPrompt, appendSystemPrompt, userSpecifiedModel, fallbackModel, jsonSchema, getAppState, setAppState, abortController, replayUserMessages = false, includePartialMessages = false, handleElicitation, agents = [], setSDKStatus, orphanedPermission, }) {
947
+ export async function* ask({ commands, prompt, promptUuid, isMeta, cwd, tools, mcpClients, verbose = false, thinkingConfig, maxTurns, maxBudgetUsd, taskBudget, canUseTool, mutableMessages = [], getReadFileCache, setReadFileCache, customSystemPrompt, appendSystemPrompt, userSpecifiedModel, fallbackModel, jsonSchema, getAppState, setAppState, abortController, replayUserMessages = false, includePartialMessages = false, handleElicitation, agents = [], setSDKStatus, orphanedPermission, onCoreQueryEvent, }) {
882
948
  const engine = new QueryEngine({
883
949
  cwd,
884
950
  tools,
@@ -906,6 +972,7 @@ export async function* ask({ commands, prompt, promptUuid, isMeta, cwd, tools, m
906
972
  setSDKStatus,
907
973
  abortController,
908
974
  orphanedPermission,
975
+ onCoreQueryEvent,
909
976
  ...(feature('HISTORY_SNIP')
910
977
  ? {
911
978
  snipReplay: (yielded, store) => {