@ccpocket/bridge 1.72.2 → 1.72.3
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/dist/auto-rename.d.ts +0 -1
- package/dist/auto-rename.js +2 -18
- package/dist/auto-rename.js.map +1 -1
- package/dist/codex-process.js +33 -5
- package/dist/codex-process.js.map +1 -1
- package/dist/parser.d.ts +15 -0
- package/dist/parser.js +72 -0
- package/dist/parser.js.map +1 -1
- package/dist/sdk-process.d.ts +11 -0
- package/dist/sdk-process.js +298 -80
- package/dist/sdk-process.js.map +1 -1
- package/dist/websocket.d.ts +3 -1
- package/dist/websocket.js +60 -10
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/sdk-process.js
CHANGED
|
@@ -89,6 +89,7 @@ export function hasExplicitClaudeCredential(env = process.env) {
|
|
|
89
89
|
function canStartClaudeSdk(env = process.env) {
|
|
90
90
|
return hasExplicitClaudeCredential(env) || isClaudeOAuthOptInEnabled(env);
|
|
91
91
|
}
|
|
92
|
+
const CLAUDE_AUTH_RESOLUTION_TIMEOUT_MS = 500;
|
|
92
93
|
export async function listAvailableClaudeModels(projectPath) {
|
|
93
94
|
if (!canStartClaudeSdk()) {
|
|
94
95
|
throw new Error(CLAUDE_OAUTH_OPT_IN_MESSAGE);
|
|
@@ -260,6 +261,63 @@ export function buildAskUserAnswers(input, result) {
|
|
|
260
261
|
export function resolvePermissionMode(current, requested) {
|
|
261
262
|
return requested ?? current;
|
|
262
263
|
}
|
|
264
|
+
const CLAUDE_SYSTEM_INJECTED_USER_TEXT = /^<(?:local-command-caveat|local-command-std(?:err|out)|task-notification|teammate-message|bash-(?:input|stdout))>/;
|
|
265
|
+
const UNKNOWN_CLAUDE_ASSISTANT_ERROR = {
|
|
266
|
+
message: "Claude stopped because of an unknown request error.",
|
|
267
|
+
errorCode: "claude_assistant_error",
|
|
268
|
+
};
|
|
269
|
+
const CLAUDE_ASSISTANT_ERRORS = {
|
|
270
|
+
authentication_failed: {
|
|
271
|
+
message: "Claude authentication failed. Sign in again on the Bridge machine.",
|
|
272
|
+
errorCode: "auth_token_expired",
|
|
273
|
+
},
|
|
274
|
+
oauth_org_not_allowed: {
|
|
275
|
+
message: "This Claude subscription organization cannot be used by the Agent SDK.",
|
|
276
|
+
errorCode: "claude_oauth_org_not_allowed",
|
|
277
|
+
},
|
|
278
|
+
billing_error: {
|
|
279
|
+
message: "Claude could not continue because of an account billing error.",
|
|
280
|
+
errorCode: "claude_billing_error",
|
|
281
|
+
},
|
|
282
|
+
rate_limit: {
|
|
283
|
+
message: "Claude is temporarily rate limited. Try again shortly.",
|
|
284
|
+
errorCode: "claude_rate_limit",
|
|
285
|
+
},
|
|
286
|
+
invalid_request: {
|
|
287
|
+
message: "Claude rejected this request as invalid.",
|
|
288
|
+
errorCode: "claude_invalid_request",
|
|
289
|
+
},
|
|
290
|
+
model_not_found: {
|
|
291
|
+
message: "The selected Claude model is not available for this account.",
|
|
292
|
+
errorCode: "claude_model_not_found",
|
|
293
|
+
},
|
|
294
|
+
server_error: {
|
|
295
|
+
message: "Claude encountered a server error.",
|
|
296
|
+
errorCode: "claude_server_error",
|
|
297
|
+
},
|
|
298
|
+
unknown: UNKNOWN_CLAUDE_ASSISTANT_ERROR,
|
|
299
|
+
max_output_tokens: {
|
|
300
|
+
message: "Claude reached the maximum response length before finishing.",
|
|
301
|
+
errorCode: "claude_max_output_tokens",
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
function claudeAssistantErrorMessage(error) {
|
|
305
|
+
if (typeof error !== "string" || error.length === 0)
|
|
306
|
+
return null;
|
|
307
|
+
const details = Object.hasOwn(CLAUDE_ASSISTANT_ERRORS, error)
|
|
308
|
+
? CLAUDE_ASSISTANT_ERRORS[error]
|
|
309
|
+
: UNKNOWN_CLAUDE_ASSISTANT_ERROR;
|
|
310
|
+
return { type: "error", ...details };
|
|
311
|
+
}
|
|
312
|
+
function isInternalClaudeResultError(error) {
|
|
313
|
+
return (error.startsWith("[ede_diagnostic]") ||
|
|
314
|
+
error.includes("Bun is not defined"));
|
|
315
|
+
}
|
|
316
|
+
function isClaudeSystemInjectedUserText(text) {
|
|
317
|
+
const normalized = text.trimStart();
|
|
318
|
+
return (CLAUDE_SYSTEM_INJECTED_USER_TEXT.test(normalized) ||
|
|
319
|
+
normalized.startsWith("Base directory for this skill:"));
|
|
320
|
+
}
|
|
263
321
|
/**
|
|
264
322
|
* Convert SDK messages to the ServerMessage format used by the WebSocket protocol.
|
|
265
323
|
* Exported for testing.
|
|
@@ -281,13 +339,39 @@ export function sdkMessageToServerMessage(msg) {
|
|
|
281
339
|
if (sys.subtype === "compact_boundary") {
|
|
282
340
|
return { type: "status", status: "compacting" };
|
|
283
341
|
}
|
|
342
|
+
if (sys.subtype === "status" && sys.compact_result === "failed") {
|
|
343
|
+
const compactError = typeof sys.compact_error === "string" ? sys.compact_error.trim() : "";
|
|
344
|
+
return {
|
|
345
|
+
type: "error",
|
|
346
|
+
message: compactError
|
|
347
|
+
? `Claude context compaction failed: ${compactError}`
|
|
348
|
+
: "Claude context compaction failed.",
|
|
349
|
+
errorCode: "claude_compaction_failed",
|
|
350
|
+
};
|
|
351
|
+
}
|
|
284
352
|
return null;
|
|
285
353
|
}
|
|
286
354
|
case "assistant": {
|
|
287
355
|
const ast = msg;
|
|
356
|
+
const rawContent = ast.message.content;
|
|
357
|
+
const content = Array.isArray(rawContent)
|
|
358
|
+
? rawContent.filter((block) => {
|
|
359
|
+
if (!block || typeof block !== "object")
|
|
360
|
+
return true;
|
|
361
|
+
const candidate = block;
|
|
362
|
+
return !(candidate.type === "thinking" &&
|
|
363
|
+
typeof candidate.thinking === "string" &&
|
|
364
|
+
candidate.thinking.trim().length === 0);
|
|
365
|
+
})
|
|
366
|
+
: rawContent;
|
|
367
|
+
if (Array.isArray(content) && content.length === 0)
|
|
368
|
+
return null;
|
|
288
369
|
return {
|
|
289
370
|
type: "assistant",
|
|
290
|
-
message:
|
|
371
|
+
message: {
|
|
372
|
+
...ast.message,
|
|
373
|
+
content,
|
|
374
|
+
},
|
|
291
375
|
...(ast.uuid ? { messageUuid: ast.uuid } : {}),
|
|
292
376
|
};
|
|
293
377
|
}
|
|
@@ -317,11 +401,13 @@ export function sdkMessageToServerMessage(msg) {
|
|
|
317
401
|
.filter((c) => c.type === "text")
|
|
318
402
|
.map((c) => c.text);
|
|
319
403
|
if (texts.length > 0) {
|
|
404
|
+
const isSynthetic = usr.isSynthetic === true ||
|
|
405
|
+
texts.every(isClaudeSystemInjectedUserText);
|
|
320
406
|
return {
|
|
321
407
|
type: "user_input",
|
|
322
408
|
text: texts.join("\n"),
|
|
323
409
|
...(usr.uuid ? { userMessageUuid: usr.uuid } : {}),
|
|
324
|
-
...(
|
|
410
|
+
...(isSynthetic ? { isSynthetic: true } : {}),
|
|
325
411
|
...(usr.isMeta ? { isMeta: true } : {}),
|
|
326
412
|
};
|
|
327
413
|
}
|
|
@@ -342,12 +428,19 @@ export function sdkMessageToServerMessage(msg) {
|
|
|
342
428
|
...tokenUsage,
|
|
343
429
|
};
|
|
344
430
|
}
|
|
345
|
-
//
|
|
346
|
-
|
|
347
|
-
//
|
|
348
|
-
|
|
431
|
+
// Claude Code can misclassify a routine interrupt as
|
|
432
|
+
// error_during_execution and prepend an internal-only EDE diagnostic.
|
|
433
|
+
// Its own terminal renderer filters these records, so keep the same
|
|
434
|
+
// boundary here while preserving any accompanying real error.
|
|
435
|
+
const rawErrors = Array.isArray(res.errors)
|
|
436
|
+
? res.errors.filter((error) => typeof error === "string")
|
|
437
|
+
: [];
|
|
438
|
+
const visibleErrors = rawErrors.filter((error) => !isInternalClaudeResultError(error));
|
|
439
|
+
if (rawErrors.length > 0 && visibleErrors.length === 0) {
|
|
349
440
|
return null;
|
|
350
441
|
}
|
|
442
|
+
// All other result subtypes are errors
|
|
443
|
+
const errorText = visibleErrors.length > 0 ? visibleErrors.join("\n") : "Unknown error";
|
|
351
444
|
return {
|
|
352
445
|
type: "result",
|
|
353
446
|
subtype: "error",
|
|
@@ -394,6 +487,10 @@ export class SdkProcess extends EventEmitter {
|
|
|
394
487
|
get permissionMode() { return this._permissionMode; }
|
|
395
488
|
_model;
|
|
396
489
|
get model() { return this._model; }
|
|
490
|
+
authClassification = "unknown";
|
|
491
|
+
authGeneration = 0;
|
|
492
|
+
authResolution = Promise.resolve();
|
|
493
|
+
authResolutionPending = false;
|
|
397
494
|
sessionAllowRules = new Set();
|
|
398
495
|
initTimeoutId = null;
|
|
399
496
|
sessionEndEmitted = false;
|
|
@@ -404,6 +501,7 @@ export class SdkProcess extends EventEmitter {
|
|
|
404
501
|
_projectPath = null;
|
|
405
502
|
toolCallsSinceLastResult = 0;
|
|
406
503
|
fileEditsSinceLastResult = 0;
|
|
504
|
+
pendingAssistantError = null;
|
|
407
505
|
launchStartedAt = 0;
|
|
408
506
|
get status() {
|
|
409
507
|
return this._status;
|
|
@@ -432,6 +530,10 @@ export class SdkProcess extends EventEmitter {
|
|
|
432
530
|
}
|
|
433
531
|
this.stopped = false;
|
|
434
532
|
this._sessionId = null;
|
|
533
|
+
this.authGeneration += 1;
|
|
534
|
+
this.authClassification = "unknown";
|
|
535
|
+
this.authResolution = Promise.resolve();
|
|
536
|
+
this.authResolutionPending = false;
|
|
435
537
|
this.sessionEndEmitted = false;
|
|
436
538
|
this.pendingPermissions.clear();
|
|
437
539
|
this.permissionModeGeneration += 1;
|
|
@@ -440,6 +542,7 @@ export class SdkProcess extends EventEmitter {
|
|
|
440
542
|
this.sessionAllowRules.clear();
|
|
441
543
|
this.toolCallsSinceLastResult = 0;
|
|
442
544
|
this.fileEditsSinceLastResult = 0;
|
|
545
|
+
this.pendingAssistantError = null;
|
|
443
546
|
this.launchStartedAt = Date.now();
|
|
444
547
|
if (options?.initialInput) {
|
|
445
548
|
this.pendingInputQueue.push({ text: options.initialInput });
|
|
@@ -528,14 +631,19 @@ export class SdkProcess extends EventEmitter {
|
|
|
528
631
|
},
|
|
529
632
|
},
|
|
530
633
|
});
|
|
634
|
+
const queryInstance = this.queryInstance;
|
|
635
|
+
const authGeneration = this.authGeneration;
|
|
636
|
+
this.authResolution = this.resolveAuthClassification(queryInstance, authGeneration);
|
|
531
637
|
// Background message processing
|
|
532
|
-
this.processMessages().catch((err) => {
|
|
533
|
-
if (this.stopped) {
|
|
638
|
+
this.processMessages(authGeneration, this.authResolution).catch((err) => {
|
|
639
|
+
if (this.stopped || authGeneration !== this.authGeneration) {
|
|
534
640
|
// Suppress errors from intentional stop (SDK bug: Bun API referenced on Node.js)
|
|
535
641
|
return;
|
|
536
642
|
}
|
|
537
643
|
console.error("[sdk-process] Message processing error:", err);
|
|
538
|
-
this.
|
|
644
|
+
if (!this.flushPendingAssistantError()) {
|
|
645
|
+
this.emitMessage({ type: "error", message: `SDK error: ${err instanceof Error ? err.message : String(err)}` });
|
|
646
|
+
}
|
|
539
647
|
this.stop();
|
|
540
648
|
this.emit("exit", 1);
|
|
541
649
|
});
|
|
@@ -548,6 +656,10 @@ export class SdkProcess extends EventEmitter {
|
|
|
548
656
|
this.initTimeoutId = null;
|
|
549
657
|
}
|
|
550
658
|
this.stopped = true;
|
|
659
|
+
this.authGeneration += 1;
|
|
660
|
+
this.authClassification = "unknown";
|
|
661
|
+
this.authResolution = Promise.resolve();
|
|
662
|
+
this.authResolutionPending = false;
|
|
551
663
|
this.pendingInputQueue = [];
|
|
552
664
|
if (this.queryInstance) {
|
|
553
665
|
console.log("[sdk-process] Stopping query");
|
|
@@ -558,6 +670,7 @@ export class SdkProcess extends EventEmitter {
|
|
|
558
670
|
this.userMessageResolve = null;
|
|
559
671
|
this.toolCallsSinceLastResult = 0;
|
|
560
672
|
this.fileEditsSinceLastResult = 0;
|
|
673
|
+
this.pendingAssistantError = null;
|
|
561
674
|
// Emit session_end so listeners can re-persist metadata before cleanup.
|
|
562
675
|
// processMessages() won't reach its session_end emit because close()
|
|
563
676
|
// causes the iterator to throw and the error is suppressed.
|
|
@@ -596,15 +709,7 @@ export class SdkProcess extends EventEmitter {
|
|
|
596
709
|
}
|
|
597
710
|
const resolve = this.userMessageResolve;
|
|
598
711
|
this.userMessageResolve = null;
|
|
599
|
-
resolve
|
|
600
|
-
type: "user",
|
|
601
|
-
session_id: this._sessionId ?? "",
|
|
602
|
-
message: {
|
|
603
|
-
role: "user",
|
|
604
|
-
content: [{ type: "text", text }],
|
|
605
|
-
},
|
|
606
|
-
parent_tool_use_id: null,
|
|
607
|
-
});
|
|
712
|
+
this.resolveUserMessage(resolve, text);
|
|
608
713
|
return { queued: false, shouldInterrupt: false };
|
|
609
714
|
}
|
|
610
715
|
sendInput(text) {
|
|
@@ -625,31 +730,9 @@ export class SdkProcess extends EventEmitter {
|
|
|
625
730
|
}
|
|
626
731
|
const resolve = this.userMessageResolve;
|
|
627
732
|
this.userMessageResolve = null;
|
|
628
|
-
const content = [];
|
|
629
|
-
// Add image blocks first (Claude processes images before text)
|
|
630
|
-
for (const image of images) {
|
|
631
|
-
content.push({
|
|
632
|
-
type: "image",
|
|
633
|
-
source: {
|
|
634
|
-
type: "base64",
|
|
635
|
-
media_type: image.mimeType,
|
|
636
|
-
data: image.base64,
|
|
637
|
-
},
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
// Add text block
|
|
641
|
-
content.push({ type: "text", text });
|
|
642
733
|
const totalKB = images.reduce((sum, img) => sum + Math.round(img.base64.length / 1024), 0);
|
|
643
734
|
console.log(`[sdk-process] Sending message with ${images.length} image(s) (${totalKB}KB base64 total)`);
|
|
644
|
-
resolve
|
|
645
|
-
type: "user",
|
|
646
|
-
session_id: this._sessionId ?? "",
|
|
647
|
-
message: {
|
|
648
|
-
role: "user",
|
|
649
|
-
content,
|
|
650
|
-
},
|
|
651
|
-
parent_tool_use_id: null,
|
|
652
|
-
});
|
|
735
|
+
this.resolveUserMessage(resolve, text, images);
|
|
653
736
|
return { queued: false, shouldInterrupt: false };
|
|
654
737
|
}
|
|
655
738
|
sendInputWithImages(text, images) {
|
|
@@ -886,33 +969,16 @@ export class SdkProcess extends EventEmitter {
|
|
|
886
969
|
}
|
|
887
970
|
async *createUserMessageStream() {
|
|
888
971
|
while (!this.stopped) {
|
|
889
|
-
//
|
|
890
|
-
|
|
972
|
+
// A queued mid-turn input must wait for result so it cannot overtake
|
|
973
|
+
// the interrupted turn. Once idle, each consumer request drains FIFO.
|
|
974
|
+
const turnInProgress = this._status === "running" ||
|
|
975
|
+
this._status === "compacting" ||
|
|
976
|
+
this._status === "waiting_approval";
|
|
977
|
+
if (this.pendingInputQueue.length > 0 && !turnInProgress) {
|
|
891
978
|
const { text, images } = this.pendingInputQueue.shift();
|
|
892
979
|
console.log(`[sdk-process] Sending queued input${images ? ` with ${images.length} image(s)` : ""} (remaining: ${this.pendingInputQueue.length})`);
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
for (const image of images) {
|
|
896
|
-
content.push({
|
|
897
|
-
type: "image",
|
|
898
|
-
source: {
|
|
899
|
-
type: "base64",
|
|
900
|
-
media_type: image.mimeType,
|
|
901
|
-
data: image.base64,
|
|
902
|
-
},
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
content.push({ type: "text", text });
|
|
907
|
-
yield {
|
|
908
|
-
type: "user",
|
|
909
|
-
session_id: this._sessionId ?? "",
|
|
910
|
-
message: {
|
|
911
|
-
role: "user",
|
|
912
|
-
content,
|
|
913
|
-
},
|
|
914
|
-
parent_tool_use_id: null,
|
|
915
|
-
};
|
|
980
|
+
this.setStatus("running");
|
|
981
|
+
yield this.buildUserMessage(text, images);
|
|
916
982
|
continue;
|
|
917
983
|
}
|
|
918
984
|
const msg = await new Promise((resolve) => {
|
|
@@ -923,30 +989,99 @@ export class SdkProcess extends EventEmitter {
|
|
|
923
989
|
yield msg;
|
|
924
990
|
}
|
|
925
991
|
}
|
|
926
|
-
async
|
|
992
|
+
async resolveAuthClassification(queryInstance, generation) {
|
|
993
|
+
if (!queryInstance)
|
|
994
|
+
return;
|
|
995
|
+
const initializationResult = queryInstance.initializationResult;
|
|
996
|
+
if (typeof initializationResult !== "function")
|
|
997
|
+
return;
|
|
998
|
+
this.authResolutionPending = true;
|
|
999
|
+
const initializationPromise = Promise.resolve().then(() => initializationResult.call(queryInstance));
|
|
1000
|
+
const settlement = (async () => {
|
|
1001
|
+
try {
|
|
1002
|
+
const initialization = await initializationPromise;
|
|
1003
|
+
if (generation !== this.authGeneration)
|
|
1004
|
+
return;
|
|
1005
|
+
this.authResolutionPending = false;
|
|
1006
|
+
this.applyAuthSource(initialization.account?.apiKeySource, generation);
|
|
1007
|
+
}
|
|
1008
|
+
catch (error) {
|
|
1009
|
+
if (generation !== this.authGeneration)
|
|
1010
|
+
return;
|
|
1011
|
+
this.authResolutionPending = false;
|
|
1012
|
+
console.warn(`[sdk-process] Could not resolve Claude auth source: ${error instanceof Error ? error.message : String(error)}`);
|
|
1013
|
+
}
|
|
1014
|
+
})();
|
|
1015
|
+
let timeoutId;
|
|
1016
|
+
try {
|
|
1017
|
+
const timeout = new Promise((resolve) => {
|
|
1018
|
+
timeoutId = setTimeout(() => resolve(false), CLAUDE_AUTH_RESOLUTION_TIMEOUT_MS);
|
|
1019
|
+
});
|
|
1020
|
+
const settledBeforeTimeout = await Promise.race([
|
|
1021
|
+
settlement.then(() => true),
|
|
1022
|
+
timeout,
|
|
1023
|
+
]);
|
|
1024
|
+
if (!settledBeforeTimeout && generation === this.authGeneration) {
|
|
1025
|
+
console.warn("[sdk-process] Timed out resolving Claude auth source");
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
finally {
|
|
1029
|
+
if (timeoutId)
|
|
1030
|
+
clearTimeout(timeoutId);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
applyAuthSource(source, generation) {
|
|
1034
|
+
if (generation !== this.authGeneration)
|
|
1035
|
+
return true;
|
|
1036
|
+
if (source === "oauth") {
|
|
1037
|
+
this.authClassification = "subscription";
|
|
1038
|
+
}
|
|
1039
|
+
else if (typeof source === "string" &&
|
|
1040
|
+
this.authClassification === "unknown") {
|
|
1041
|
+
this.authClassification = "api_key";
|
|
1042
|
+
}
|
|
1043
|
+
if (this.authClassification === "subscription" &&
|
|
1044
|
+
!isClaudeOAuthOptInEnabled()) {
|
|
1045
|
+
console.log("[sdk-process] OAuth auth source requires explicit opt-in");
|
|
1046
|
+
this.emitMessage({
|
|
1047
|
+
type: "error",
|
|
1048
|
+
message: CLAUDE_OAUTH_OPT_IN_MESSAGE,
|
|
1049
|
+
errorCode: CLAUDE_OAUTH_OPT_IN_ERROR_CODE,
|
|
1050
|
+
});
|
|
1051
|
+
this.stop();
|
|
1052
|
+
this.emit("exit", 1);
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
return true;
|
|
1056
|
+
}
|
|
1057
|
+
async processMessages(authGeneration, authResolution) {
|
|
927
1058
|
if (!this.queryInstance)
|
|
928
1059
|
return;
|
|
929
1060
|
for await (const message of this.queryInstance) {
|
|
930
|
-
if (this.stopped)
|
|
1061
|
+
if (this.stopped || authGeneration !== this.authGeneration)
|
|
931
1062
|
break;
|
|
932
1063
|
if (message.type === "system" &&
|
|
933
1064
|
"subtype" in message &&
|
|
934
1065
|
message.subtype === "init" &&
|
|
935
|
-
message.apiKeySource
|
|
936
|
-
!isClaudeOAuthOptInEnabled()) {
|
|
937
|
-
console.log("[sdk-process] OAuth auth source requires explicit opt-in");
|
|
938
|
-
this.emitMessage({
|
|
939
|
-
type: "error",
|
|
940
|
-
message: CLAUDE_OAUTH_OPT_IN_MESSAGE,
|
|
941
|
-
errorCode: CLAUDE_OAUTH_OPT_IN_ERROR_CODE,
|
|
942
|
-
});
|
|
943
|
-
this.stop();
|
|
944
|
-
this.emit("exit", 1);
|
|
1066
|
+
!this.applyAuthSource(message.apiKeySource, authGeneration)) {
|
|
945
1067
|
return;
|
|
946
1068
|
}
|
|
947
1069
|
// Convert SDK message to ServerMessage
|
|
948
1070
|
let serverMsg = sdkMessageToServerMessage(message);
|
|
1071
|
+
if (message.type === "assistant" && this.pendingAssistantError === null) {
|
|
1072
|
+
this.pendingAssistantError = claudeAssistantErrorMessage(message.error);
|
|
1073
|
+
}
|
|
949
1074
|
if (serverMsg?.type === "result") {
|
|
1075
|
+
if (this.authClassification !== "subscription") {
|
|
1076
|
+
await authResolution;
|
|
1077
|
+
}
|
|
1078
|
+
if (this.stopped || authGeneration !== this.authGeneration)
|
|
1079
|
+
return;
|
|
1080
|
+
if (this.authClassification !== "api_key" ||
|
|
1081
|
+
this.authResolutionPending) {
|
|
1082
|
+
const { cost: _estimatedApiCost, ...withoutCost } = serverMsg;
|
|
1083
|
+
serverMsg = withoutCost;
|
|
1084
|
+
}
|
|
950
1085
|
if (this.toolCallsSinceLastResult > 0 || this.fileEditsSinceLastResult > 0) {
|
|
951
1086
|
serverMsg = {
|
|
952
1087
|
...serverMsg,
|
|
@@ -961,6 +1096,23 @@ export class SdkProcess extends EventEmitter {
|
|
|
961
1096
|
this.toolCallsSinceLastResult = 0;
|
|
962
1097
|
this.fileEditsSinceLastResult = 0;
|
|
963
1098
|
}
|
|
1099
|
+
if (message.type === "result" && this.pendingAssistantError) {
|
|
1100
|
+
const result = message;
|
|
1101
|
+
const hasRealResultError = serverMsg?.type === "result" &&
|
|
1102
|
+
serverMsg.subtype === "error" &&
|
|
1103
|
+
Array.isArray(result.errors) &&
|
|
1104
|
+
result.errors.some((error) => typeof error === "string" &&
|
|
1105
|
+
!isInternalClaudeResultError(error));
|
|
1106
|
+
if (!hasRealResultError) {
|
|
1107
|
+
this.flushPendingAssistantError();
|
|
1108
|
+
if (serverMsg?.type === "result" && serverMsg.subtype === "error") {
|
|
1109
|
+
serverMsg = null;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
this.pendingAssistantError = null;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
964
1116
|
if (serverMsg) {
|
|
965
1117
|
this.emitMessage(serverMsg);
|
|
966
1118
|
}
|
|
@@ -1000,7 +1152,17 @@ export class SdkProcess extends EventEmitter {
|
|
|
1000
1152
|
// Update status from message type
|
|
1001
1153
|
this.updateStatusFromMessage(message);
|
|
1002
1154
|
}
|
|
1155
|
+
await authResolution;
|
|
1156
|
+
if (this.stopped || authGeneration !== this.authGeneration)
|
|
1157
|
+
return;
|
|
1158
|
+
this.flushPendingAssistantError();
|
|
1003
1159
|
// Query finished — CLI has completed shutdown including file writes.
|
|
1160
|
+
// Treat natural completion as the end of this auth generation so an
|
|
1161
|
+
// initializationResult that settles after the timeout cannot emit a
|
|
1162
|
+
// second, contradictory terminal event for an already-finished query.
|
|
1163
|
+
this.authGeneration += 1;
|
|
1164
|
+
this.authResolutionPending = false;
|
|
1165
|
+
this.authResolution = Promise.resolve();
|
|
1004
1166
|
this.queryInstance = null;
|
|
1005
1167
|
// Emit session_end before exit so listeners can re-persist metadata
|
|
1006
1168
|
// (e.g. customTitle) that the CLI may have overwritten during shutdown.
|
|
@@ -1008,6 +1170,14 @@ export class SdkProcess extends EventEmitter {
|
|
|
1008
1170
|
this.setStatus("idle");
|
|
1009
1171
|
this.emit("exit", 0);
|
|
1010
1172
|
}
|
|
1173
|
+
flushPendingAssistantError() {
|
|
1174
|
+
if (!this.pendingAssistantError)
|
|
1175
|
+
return false;
|
|
1176
|
+
const message = this.pendingAssistantError;
|
|
1177
|
+
this.pendingAssistantError = null;
|
|
1178
|
+
this.emitMessage(message);
|
|
1179
|
+
return true;
|
|
1180
|
+
}
|
|
1011
1181
|
/**
|
|
1012
1182
|
* Core permission handler: called by SDK before each tool execution.
|
|
1013
1183
|
* Returns a Promise that resolves when the user approves/rejects.
|
|
@@ -1053,9 +1223,21 @@ export class SdkProcess extends EventEmitter {
|
|
|
1053
1223
|
}
|
|
1054
1224
|
updateStatusFromMessage(msg) {
|
|
1055
1225
|
switch (msg.type) {
|
|
1056
|
-
case "system":
|
|
1057
|
-
|
|
1226
|
+
case "system": {
|
|
1227
|
+
const system = msg;
|
|
1228
|
+
if (system.subtype === "status") {
|
|
1229
|
+
if (system.status === "compacting") {
|
|
1230
|
+
this.setStatus("compacting");
|
|
1231
|
+
}
|
|
1232
|
+
else if (system.status === "requesting") {
|
|
1233
|
+
this.setStatus("running");
|
|
1234
|
+
}
|
|
1235
|
+
else if (system.status === null && this._status === "compacting") {
|
|
1236
|
+
this.setStatus("running");
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1058
1239
|
break;
|
|
1240
|
+
}
|
|
1059
1241
|
case "assistant":
|
|
1060
1242
|
if (this.pendingPermissions.size === 0) {
|
|
1061
1243
|
this.setStatus("running");
|
|
@@ -1069,9 +1251,45 @@ export class SdkProcess extends EventEmitter {
|
|
|
1069
1251
|
case "result":
|
|
1070
1252
|
this.pendingPermissions.clear();
|
|
1071
1253
|
this.setStatus("idle");
|
|
1254
|
+
this.deliverQueuedInputIfWaiting();
|
|
1072
1255
|
break;
|
|
1073
1256
|
}
|
|
1074
1257
|
}
|
|
1258
|
+
buildUserMessage(text, images) {
|
|
1259
|
+
const content = [];
|
|
1260
|
+
if (images) {
|
|
1261
|
+
for (const image of images) {
|
|
1262
|
+
content.push({
|
|
1263
|
+
type: "image",
|
|
1264
|
+
source: {
|
|
1265
|
+
type: "base64",
|
|
1266
|
+
media_type: image.mimeType,
|
|
1267
|
+
data: image.base64,
|
|
1268
|
+
},
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
content.push({ type: "text", text });
|
|
1273
|
+
return {
|
|
1274
|
+
type: "user",
|
|
1275
|
+
session_id: this._sessionId ?? "",
|
|
1276
|
+
message: { role: "user", content },
|
|
1277
|
+
parent_tool_use_id: null,
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
deliverQueuedInputIfWaiting() {
|
|
1281
|
+
const resolve = this.userMessageResolve;
|
|
1282
|
+
const queued = this.pendingInputQueue[0];
|
|
1283
|
+
if (!resolve || !queued)
|
|
1284
|
+
return;
|
|
1285
|
+
this.userMessageResolve = null;
|
|
1286
|
+
this.pendingInputQueue.shift();
|
|
1287
|
+
this.resolveUserMessage(resolve, queued.text, queued.images);
|
|
1288
|
+
}
|
|
1289
|
+
resolveUserMessage(resolve, text, images) {
|
|
1290
|
+
this.setStatus("running");
|
|
1291
|
+
resolve(this.buildUserMessage(text, images));
|
|
1292
|
+
}
|
|
1075
1293
|
handlePostToolUseHook(input) {
|
|
1076
1294
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
1077
1295
|
return;
|