@iaforged/context-code 1.2.9 → 1.2.12
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.
- package/README.md +119 -119
- package/context-bootstrap.js +28 -26
- package/dist/src/QueryEngine.js +394 -327
- package/dist/src/bridge/bridgeUI.js +1 -1
- package/dist/src/buddy/prompt.js +4 -4
- package/dist/src/cli/handlers/auth.js +126 -9
- package/dist/src/cli/print.js +35 -1
- package/dist/src/commands/agent/agent.js +28 -2
- package/dist/src/commands/agent/agentStore.js +8 -1
- package/dist/src/commands/agent/index.js +1 -1
- package/dist/src/commands/bridge-kick.js +9 -9
- package/dist/src/commands/commit.js +34 -34
- package/dist/src/commands/init-verifiers.js +3 -3
- package/dist/src/commands/init.js +88 -88
- package/dist/src/commands/insights.js +787 -787
- package/dist/src/commands/install.js +19 -19
- package/dist/src/commands/login/login.js +21 -12
- package/dist/src/commands/logout/logout.js +9 -0
- package/dist/src/commands/model/model.js +9 -4
- package/dist/src/commands/orchestrate/SwarmUI.js +50 -0
- package/dist/src/commands/orchestrate/index.js +2 -2
- package/dist/src/commands/orchestrate/orchestrate.js +708 -12
- package/dist/src/commands/pr_comments/index.js +33 -33
- package/dist/src/commands/profile/index.js +1 -1
- package/dist/src/commands/profile/profile.js +52 -3
- package/dist/src/commands/provider/index.js +1 -1
- package/dist/src/commands/provider/provider.js +117 -45
- package/dist/src/commands/resumen/index.js +9 -0
- package/dist/src/commands/resumen/resumen.js +29 -0
- package/dist/src/commands/security-review.js +190 -190
- package/dist/src/commands/swarm-auto/index.js +9 -0
- package/dist/src/commands/swarm-auto/swarmAuto.js +111 -0
- package/dist/src/commands/swarm-init/index.js +9 -0
- package/dist/src/commands/swarm-init/swarmInit.js +72 -0
- package/dist/src/commands/team/team.js +39 -6
- package/dist/src/commands.js +14 -0
- package/dist/src/components/LogoV2/CondensedLogo.js +2 -2
- package/dist/src/components/PromptInput/PromptInputQueuedCommands.js +3 -3
- package/dist/src/components/agents/agentFileUtils.js +6 -6
- package/dist/src/components/permissions/hooks.js +5 -5
- package/dist/src/constants/outputStyles.js +83 -83
- package/dist/src/core/agents/blueprints.js +58 -0
- package/dist/src/core/agents/cliAdapter.js +61 -0
- package/dist/src/core/agents/registry.js +93 -0
- package/dist/src/core/agents/runtime.js +4 -0
- package/dist/src/core/agents/runtime.smoke.js +42 -0
- package/dist/src/core/agents/swarm.smoke.js +48 -0
- package/dist/src/core/agents/swarmTools.js +38 -0
- package/dist/src/core/auth/index.js +2 -0
- package/dist/src/core/auth/loginCliAdapter.js +24 -0
- package/dist/src/core/auth/loginCore.js +67 -0
- package/dist/src/core/auth/logoutCliAdapter.js +34 -0
- package/dist/src/core/auth/logoutCore.js +52 -0
- package/dist/src/core/auth/preflight.smoke.js +151 -0
- package/dist/src/core/index.js +21 -0
- package/dist/src/core/mcp/blueprints.js +27 -0
- package/dist/src/core/mcp/common.js +14 -0
- package/dist/src/core/mcp/runtime.js +67 -0
- package/dist/src/core/mcp/runtime.smoke.js +50 -0
- package/dist/src/core/mcp/swarmClient.js +40 -0
- package/dist/src/core/mcp/swarmSetup.js +43 -0
- package/dist/src/core/providers/cliAdapter.js +39 -0
- package/dist/src/core/providers/contracts.js +1 -0
- package/dist/src/core/providers/index.js +3 -0
- package/dist/src/core/providers/llmCore.js +123 -0
- package/dist/src/core/providers/providerCore.js +141 -0
- package/dist/src/core/providers/providerModelCompatibility.js +98 -0
- package/dist/src/core/providers/providerParitySmoke.js +83 -0
- package/dist/src/core/providers/providerProfileModelSmoke.js +80 -0
- package/dist/src/core/query/contracts.js +1 -0
- package/dist/src/core/query/runtime.js +117 -0
- package/dist/src/core/query/runtime.smoke.js +39 -0
- package/dist/src/core/query/timelineThinking.smoke.js +25 -0
- package/dist/src/core/query/wiring.smoke.js +76 -0
- package/dist/src/core/skills/cliAdapter.js +38 -0
- package/dist/src/core/skills/index.js +52 -0
- package/dist/src/core/skills/runtime.smoke.js +53 -0
- package/dist/src/core/tasks/runtime.js +205 -0
- package/dist/src/core/tasks/runtime.smoke.js +63 -0
- package/dist/src/core/tasks/sdkAdapter.js +4 -0
- package/dist/src/core/tools/contracts.js +3 -0
- package/dist/src/core/tools/fileResolution.js +112 -0
- package/dist/src/core/tools/fileResolution.smoke.js +33 -0
- package/dist/src/core/tools/filesCore.js +51 -0
- package/dist/src/core/tools/filesCore.smoke.js +108 -0
- package/dist/src/core/tools/gitCore.js +20 -0
- package/dist/src/core/tools/imageParity.smoke.js +36 -0
- package/dist/src/core/tools/notebookParity.smoke.js +68 -0
- package/dist/src/core/tools/registry.js +22 -0
- package/dist/src/core/tools/runtime.smoke.js +32 -0
- package/dist/src/core/tools/shellCore.js +60 -0
- package/dist/src/core/types/agentContext.js +9 -0
- package/dist/src/core/types/auth.js +3 -0
- package/dist/src/core/types/command.js +13 -0
- package/dist/src/core/types/provider.js +3 -0
- package/dist/src/core/types/sdkEvent.js +10 -0
- package/dist/src/core/types/swarm.js +1 -0
- package/dist/src/cost-tracker.js +3 -3
- package/dist/src/hooks/useAwaySummary.js +22 -9
- package/dist/src/main.js +32 -2
- package/dist/src/screens/REPL.js +9 -0
- package/dist/src/services/AgentSummary/agentSummary.js +10 -10
- package/dist/src/services/autoDream/autoDream.js +5 -5
- package/dist/src/services/autoDream/consolidationPrompt.js +49 -49
- package/dist/src/services/compact/prompt.js +238 -238
- package/dist/src/services/limits/sessionCounter.js +17 -17
- package/dist/src/services/mcp/client.js +27 -1
- package/dist/src/services/orchestration/execution/AgentTaskExecutor.js +39 -20
- package/dist/src/services/orchestration/execution/OrchestrationExecutionRuntime.js +65 -58
- package/dist/src/skills/bundled/loop.js +57 -57
- package/dist/src/skills/bundled/remember.js +53 -53
- package/dist/src/skills/bundled/simplify.js +49 -49
- package/dist/src/skills/bundled/skillify.js +2 -2
- package/dist/src/state/onChangeAppState.js +6 -0
- package/dist/src/tasks/LocalAgentTask/LocalAgentTask.js +5 -5
- package/dist/src/tasks/LocalMainSessionTask.js +5 -5
- package/dist/src/tasks/LocalShellTask/LocalShellTask.js +13 -13
- package/dist/src/tools/AgentTool/forkSubagent.js +25 -25
- package/dist/src/tools/AskUserQuestionTool/prompt.js +29 -29
- package/dist/src/tools/BashTool/BashTool.js +27 -2
- package/dist/src/tools/BriefTool/prompt.js +14 -14
- package/dist/src/tools/EnterPlanModeTool/EnterPlanModeTool.js +12 -12
- package/dist/src/tools/EnterPlanModeTool/prompt.js +140 -140
- package/dist/src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.js +18 -18
- package/dist/src/tools/ExitPlanModeTool/prompt.js +23 -23
- package/dist/src/tools/ExitWorktreeTool/prompt.js +29 -29
- package/dist/src/tools/FileEditTool/prompt.js +7 -7
- package/dist/src/tools/FileReadTool/FileReadTool.js +18 -1
- package/dist/src/tools/FileWriteTool/prompt.js +6 -6
- package/dist/src/tools/GlobTool/prompt.js +4 -4
- package/dist/src/tools/GrepTool/prompt.js +10 -10
- package/dist/src/tools/LSPTool/prompt.js +18 -18
- package/dist/src/tools/ListMcpResourcesTool/prompt.js +15 -15
- package/dist/src/tools/PowerShellTool/PowerShellTool.js +25 -2
- package/dist/src/tools/ReadMcpResourceTool/prompt.js +13 -13
- package/dist/src/tools/SendMessageTool/prompt.js +36 -36
- package/dist/src/tools/SkillTool/prompt.js +21 -21
- package/dist/src/tools/SleepTool/prompt.js +10 -10
- package/dist/src/tools/TaskCreateTool/prompt.js +41 -41
- package/dist/src/tools/TaskGetTool/prompt.js +21 -21
- package/dist/src/tools/TaskListTool/prompt.js +30 -30
- package/dist/src/tools/TaskOutputTool/TaskOutputTool.js +8 -8
- package/dist/src/tools/TaskStopTool/prompt.js +5 -5
- package/dist/src/tools/TaskUpdateTool/prompt.js +74 -74
- package/dist/src/tools/TodoWriteTool/prompt.js +178 -178
- package/dist/src/tools/ToolSearchTool/prompt.js +9 -9
- package/dist/src/tools/WebFetchTool/WebFetchTool.js +9 -9
- package/dist/src/tools/WebFetchTool/prompt.js +31 -31
- package/dist/src/tools/WebSearchTool/prompt.js +26 -26
- package/dist/src/utils/agentContext.js +2 -0
- package/dist/src/utils/agenticSessionSearch.js +38 -38
- package/dist/src/utils/config.js +2 -0
- package/dist/src/utils/genericProcessUtils.js +21 -21
- package/dist/src/utils/heapDumpService.js +4 -4
- package/dist/src/utils/mcpValidation.js +2 -2
- package/dist/src/utils/model/modelStrings.js +1 -1
- package/dist/src/utils/model/providers.js +5 -0
- package/dist/src/utils/orchestration/store/providerAgentStore.js +22 -22
- package/dist/src/utils/orchestration/store/providerWorkspaceStore.js +10 -10
- package/dist/src/utils/orchestration/store/runStore.js +68 -68
- package/dist/src/utils/orchestration/store/teamStore.js +28 -28
- package/dist/src/utils/permissions/permissionExplainer.js +6 -6
- package/dist/src/utils/permissions/permissionsDb.js +43 -43
- package/dist/src/utils/sdkEventQueue.js +2 -0
- package/dist/src/utils/secureStorage/sqliteStorage.js +12 -12
- package/dist/src/utils/standardMcp/common.js +15 -0
- package/dist/src/utils/standardMcp/setup.js +52 -0
- package/dist/src/utils/swarm/teammatePromptAddendum.js +10 -10
- package/dist/src/utils/task/framework.js +6 -6
- package/package.json +1 -1
- package/dist/src/commands/usage/index.js +0 -7
- package/dist/src/commands/usage/usage.js +0 -5
package/dist/src/QueryEngine.js
CHANGED
|
@@ -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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
496
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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:
|
|
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:
|
|
510
|
-
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
568
|
-
//
|
|
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: '
|
|
574
|
-
|
|
721
|
+
type: 'tool_use_summary',
|
|
722
|
+
summary: message.summary,
|
|
723
|
+
preceding_tool_use_ids: message.precedingToolUseIds,
|
|
575
724
|
session_id: getSessionId(),
|
|
576
|
-
|
|
577
|
-
uuid: randomUUID(),
|
|
725
|
+
uuid: message.uuid,
|
|
578
726
|
};
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
|
|
593
|
-
|
|
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: '
|
|
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:
|
|
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
|
-
`
|
|
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
|
-
//
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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: '
|
|
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
|
-
|
|
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
|
-
//
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
if (
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
|
|
771
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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) => {
|