@pasko70/pibo 1.7.12 → 1.8.1
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/apps/chat/workflow-manual-trigger-runtime.js +13 -4
- package/dist/core/compaction-prompt.js +87 -52
- package/dist/core/context-guard.js +172 -5
- package/dist/core/provider-recovery.js +64 -0
- package/dist/core/provider-telemetry.js +97 -4
- package/dist/core/routed-session.js +181 -16
- package/dist/core/runtime-telemetry.js +251 -91
- package/dist/core/runtime.js +53 -12
- package/dist/core/session-errors.js +14 -1
- package/dist/core/session-router.js +127 -26
- package/dist/data/telemetry.js +115 -26
- package/dist/gateway/request.js +17 -8
- package/dist/ralph/service.js +3 -5
- package/package.json +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.7.12.vsix +0 -0
|
@@ -2,6 +2,8 @@ import { SessionManager, shouldCompact } from "@earendil-works/pi-coding-agent";
|
|
|
2
2
|
import { getOpenAiCodexProviderUsageForActiveModel } from "../auth/openai-codex-usage.js";
|
|
3
3
|
import { normalizeSessionErrorDetails, runtimeSessionErrorDetails } from "./session-errors.js";
|
|
4
4
|
import { expandInlineSkills } from "./skill-expansion.js";
|
|
5
|
+
import { PIBO_CONTEXT_GUARD_RESUME_MESSAGE_TYPE, PIBO_CONTEXT_GUARD_RESUME_PROMPT, cancelPiboAssistantContextGuardRecovery, claimPiboAssistantContextGuardRecovery, waitForPiboAssistantContextGuardRecovery, } from "./context-guard.js";
|
|
6
|
+
import { PIBO_PROVIDER_RECOVERY_MESSAGE_TYPE, PIBO_PROVIDER_RECOVERY_PROMPT, PiboProviderRecoveryCancelledError, isRetryablePiboAssistantError, isRetryablePiboProviderError, resolvePiboProviderRecoverySettings, waitForPiboProviderRecovery, } from "./provider-recovery.js";
|
|
5
7
|
const FAST_SERVICE_TIER = "priority";
|
|
6
8
|
function modelSupportsFastServiceTier(model) {
|
|
7
9
|
if (!model)
|
|
@@ -21,6 +23,9 @@ function withFastServiceTierOption(options) {
|
|
|
21
23
|
function errorMessage(error) {
|
|
22
24
|
return error instanceof Error ? error.message : String(error);
|
|
23
25
|
}
|
|
26
|
+
function isAssistantMessage(message) {
|
|
27
|
+
return Boolean(message && typeof message === "object" && message.role === "assistant");
|
|
28
|
+
}
|
|
24
29
|
function numberValue(value) {
|
|
25
30
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
26
31
|
}
|
|
@@ -388,6 +393,7 @@ export class RoutedSession {
|
|
|
388
393
|
onSessionOperation;
|
|
389
394
|
onKillChildren;
|
|
390
395
|
onStateChange;
|
|
396
|
+
onMessagesInterrupted;
|
|
391
397
|
queue = [];
|
|
392
398
|
processing = false;
|
|
393
399
|
disposed = false;
|
|
@@ -398,9 +404,15 @@ export class RoutedSession {
|
|
|
398
404
|
nextAssistantIndex = 0;
|
|
399
405
|
activeThinkingIndex;
|
|
400
406
|
nextThinkingIndex = 0;
|
|
407
|
+
pendingAssistantError;
|
|
408
|
+
pendingAssistantErrorRetryable = false;
|
|
409
|
+
activeMessageFailed = false;
|
|
410
|
+
providerRecoveryCancelled = false;
|
|
411
|
+
providerRecoveryAbortController;
|
|
401
412
|
unsubscribe;
|
|
413
|
+
recoverySession;
|
|
402
414
|
isContinuePatched = false;
|
|
403
|
-
constructor(piboSessionId, runtime, emit, pluginRegistry, forwardPiEvents, onPiEventTelemetry, initialFastMode, onSessionOperation, onKillChildren, onStateChange) {
|
|
415
|
+
constructor(piboSessionId, runtime, emit, pluginRegistry, forwardPiEvents, onPiEventTelemetry, initialFastMode, onSessionOperation, onKillChildren, onStateChange, onMessagesInterrupted) {
|
|
404
416
|
this.piboSessionId = piboSessionId;
|
|
405
417
|
this.runtime = runtime;
|
|
406
418
|
this.emit = emit;
|
|
@@ -410,6 +422,7 @@ export class RoutedSession {
|
|
|
410
422
|
this.onSessionOperation = onSessionOperation;
|
|
411
423
|
this.onKillChildren = onKillChildren;
|
|
412
424
|
this.onStateChange = onStateChange;
|
|
425
|
+
this.onMessagesInterrupted = onMessagesInterrupted;
|
|
413
426
|
this.fastMode = initialFastMode && this.fastModeSupported();
|
|
414
427
|
this.bindRuntimeSession();
|
|
415
428
|
this.patchFastModeProviderRequest();
|
|
@@ -485,12 +498,32 @@ export class RoutedSession {
|
|
|
485
498
|
}
|
|
486
499
|
bindRuntimeSession() {
|
|
487
500
|
this.unsubscribe?.();
|
|
488
|
-
|
|
501
|
+
const session = this.runtime.session;
|
|
502
|
+
if (this.recoverySession && this.recoverySession !== session) {
|
|
503
|
+
this.cancelProviderRecovery();
|
|
504
|
+
cancelPiboAssistantContextGuardRecovery(this.recoverySession, new Error("Context guard recovery cancelled because the Pi session changed"));
|
|
505
|
+
}
|
|
506
|
+
this.recoverySession = session;
|
|
507
|
+
claimPiboAssistantContextGuardRecovery(session);
|
|
508
|
+
this.unsubscribe = session.subscribe((event) => {
|
|
489
509
|
this.onPiEventTelemetry?.(this.piboSessionId, event, { status: this.getStatus(), activeEventId: this.activeMessage?.id });
|
|
490
510
|
const model = this.runtime.session.model;
|
|
491
511
|
const normalized = normalizePiEvent(this.piboSessionId, event, { contextWindow: numberValue(model?.contextWindow) });
|
|
492
|
-
|
|
493
|
-
|
|
512
|
+
const candidate = event && typeof event === "object" ? event : undefined;
|
|
513
|
+
const assistantMessageEnded = candidate?.type === "message_end" && isAssistantMessage(candidate.message);
|
|
514
|
+
// Pi gets the first chance to recover through its short retry/compaction loop.
|
|
515
|
+
// Keep the final error pending so the routed turn can continue durable recovery.
|
|
516
|
+
if (assistantMessageEnded && normalized?.type === "session_error") {
|
|
517
|
+
this.pendingAssistantError = this.withActiveMessage(normalized);
|
|
518
|
+
this.pendingAssistantErrorRetryable = isRetryablePiboAssistantError(candidate.message);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
if (assistantMessageEnded) {
|
|
522
|
+
this.pendingAssistantError = undefined;
|
|
523
|
+
this.pendingAssistantErrorRetryable = false;
|
|
524
|
+
}
|
|
525
|
+
if (normalized)
|
|
526
|
+
this.emit(this.withActiveMessage(normalized));
|
|
494
527
|
}
|
|
495
528
|
if (this.forwardPiEvents) {
|
|
496
529
|
this.emit({ type: "pi_event", piboSessionId: this.piboSessionId, event });
|
|
@@ -498,6 +531,87 @@ export class RoutedSession {
|
|
|
498
531
|
this.handleCompactionEvent(event);
|
|
499
532
|
});
|
|
500
533
|
}
|
|
534
|
+
flushPendingAssistantError() {
|
|
535
|
+
if (!this.pendingAssistantError)
|
|
536
|
+
return;
|
|
537
|
+
this.activeMessageFailed = true;
|
|
538
|
+
this.emit(this.pendingAssistantError);
|
|
539
|
+
this.pendingAssistantError = undefined;
|
|
540
|
+
this.pendingAssistantErrorRetryable = false;
|
|
541
|
+
}
|
|
542
|
+
cancelProviderRecovery() {
|
|
543
|
+
this.providerRecoveryCancelled = true;
|
|
544
|
+
this.providerRecoveryAbortController?.abort();
|
|
545
|
+
this.providerRecoveryAbortController = undefined;
|
|
546
|
+
}
|
|
547
|
+
async resumeContextGuardRecovery(session) {
|
|
548
|
+
while (await waitForPiboAssistantContextGuardRecovery(session)) {
|
|
549
|
+
try {
|
|
550
|
+
await session.sendCustomMessage({
|
|
551
|
+
customType: PIBO_CONTEXT_GUARD_RESUME_MESSAGE_TYPE,
|
|
552
|
+
content: [{ type: "text", text: PIBO_CONTEXT_GUARD_RESUME_PROMPT }],
|
|
553
|
+
display: false,
|
|
554
|
+
}, { triggerTurn: true });
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
const resumeError = error instanceof Error ? error : new Error(String(error));
|
|
558
|
+
cancelPiboAssistantContextGuardRecovery(session, resumeError);
|
|
559
|
+
throw resumeError;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async recoverTransientProviderErrors(session) {
|
|
564
|
+
let attempt = 0;
|
|
565
|
+
while (this.pendingAssistantError && this.pendingAssistantErrorRetryable) {
|
|
566
|
+
if (this.providerRecoveryCancelled)
|
|
567
|
+
throw new PiboProviderRecoveryCancelledError();
|
|
568
|
+
const settings = resolvePiboProviderRecoverySettings(session.settingsManager);
|
|
569
|
+
if (!settings.enabled)
|
|
570
|
+
return;
|
|
571
|
+
attempt += 1;
|
|
572
|
+
this.pendingAssistantError = undefined;
|
|
573
|
+
this.pendingAssistantErrorRetryable = false;
|
|
574
|
+
const controller = new AbortController();
|
|
575
|
+
this.providerRecoveryAbortController = controller;
|
|
576
|
+
try {
|
|
577
|
+
await waitForPiboProviderRecovery(attempt, settings, controller.signal);
|
|
578
|
+
}
|
|
579
|
+
finally {
|
|
580
|
+
if (this.providerRecoveryAbortController === controller) {
|
|
581
|
+
this.providerRecoveryAbortController = undefined;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (this.providerRecoveryCancelled || this.disposed || this.runtime.session !== session || !this.activeMessage) {
|
|
585
|
+
throw new PiboProviderRecoveryCancelledError();
|
|
586
|
+
}
|
|
587
|
+
try {
|
|
588
|
+
await session.sendCustomMessage({
|
|
589
|
+
customType: PIBO_PROVIDER_RECOVERY_MESSAGE_TYPE,
|
|
590
|
+
content: [{ type: "text", text: PIBO_PROVIDER_RECOVERY_PROMPT }],
|
|
591
|
+
display: false,
|
|
592
|
+
details: { attempt },
|
|
593
|
+
}, { triggerTurn: true });
|
|
594
|
+
await this.resumeContextGuardRecovery(session);
|
|
595
|
+
if (this.providerRecoveryCancelled)
|
|
596
|
+
throw new PiboProviderRecoveryCancelledError();
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
if (error instanceof PiboProviderRecoveryCancelledError)
|
|
600
|
+
throw error;
|
|
601
|
+
if (!isRetryablePiboProviderError(error))
|
|
602
|
+
throw error;
|
|
603
|
+
const message = errorMessage(error);
|
|
604
|
+
this.pendingAssistantError = {
|
|
605
|
+
type: "session_error",
|
|
606
|
+
piboSessionId: this.piboSessionId,
|
|
607
|
+
eventId: this.activeMessage.id,
|
|
608
|
+
error: message,
|
|
609
|
+
errorDetails: runtimeSessionErrorDetails(message),
|
|
610
|
+
};
|
|
611
|
+
this.pendingAssistantErrorRetryable = true;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
501
615
|
handleCompactionEvent(event) {
|
|
502
616
|
if (!event || typeof event !== "object")
|
|
503
617
|
return;
|
|
@@ -602,15 +716,17 @@ export class RoutedSession {
|
|
|
602
716
|
}
|
|
603
717
|
removeQueuedMessages(predicate) {
|
|
604
718
|
this.assertActive();
|
|
605
|
-
|
|
719
|
+
const removedMessages = [];
|
|
606
720
|
for (let index = this.queue.length - 1; index >= 0; index -= 1) {
|
|
607
721
|
const item = this.queue[index];
|
|
608
722
|
if (item.kind !== "message" || !predicate(item.event))
|
|
609
723
|
continue;
|
|
610
724
|
this.queue.splice(index, 1);
|
|
611
|
-
|
|
725
|
+
removedMessages.push(item.event);
|
|
612
726
|
}
|
|
613
|
-
|
|
727
|
+
removedMessages.reverse();
|
|
728
|
+
this.notifyMessagesInterrupted(removedMessages, "queued message removed");
|
|
729
|
+
return removedMessages.length;
|
|
614
730
|
}
|
|
615
731
|
getCurrentSession() {
|
|
616
732
|
return this.createSessionSnapshot();
|
|
@@ -733,16 +849,25 @@ export class RoutedSession {
|
|
|
733
849
|
async dispose() {
|
|
734
850
|
if (this.disposed)
|
|
735
851
|
return;
|
|
852
|
+
this.notifyMessagesInterrupted(this.activeAndQueuedMessages(), "session disposed");
|
|
853
|
+
this.cancelProviderRecovery();
|
|
736
854
|
this.queue.length = 0;
|
|
737
855
|
this.onStateChange?.({ processing: this.processing, queuedMessages: this.queue.length, disposed: true });
|
|
738
856
|
this.unsubscribe?.();
|
|
739
857
|
this.unsubscribe = undefined;
|
|
858
|
+
if (this.recoverySession) {
|
|
859
|
+
this.cancelContextGuardRecovery("Context guard recovery cancelled because the routed session was disposed");
|
|
860
|
+
this.recoverySession = undefined;
|
|
861
|
+
}
|
|
740
862
|
this.disposed = true;
|
|
741
863
|
await this.runtime.dispose();
|
|
742
864
|
}
|
|
743
865
|
async kill() {
|
|
866
|
+
this.notifyMessagesInterrupted(this.activeAndQueuedMessages(), "session killed");
|
|
867
|
+
this.cancelProviderRecovery();
|
|
744
868
|
this.queue.length = 0;
|
|
745
869
|
this.onStateChange?.({ processing: this.processing, queuedMessages: this.queue.length, disposed: this.disposed });
|
|
870
|
+
this.cancelContextGuardRecovery("Context guard recovery cancelled because the routed session was killed");
|
|
746
871
|
await this.runtime.session.abort();
|
|
747
872
|
return this.piboSessionId;
|
|
748
873
|
}
|
|
@@ -750,11 +875,16 @@ export class RoutedSession {
|
|
|
750
875
|
this.assertActive();
|
|
751
876
|
const queuedIndex = this.queue.findIndex((item) => item.event.id === eventId);
|
|
752
877
|
if (queuedIndex >= 0) {
|
|
753
|
-
this.queue.splice(queuedIndex, 1);
|
|
878
|
+
const [removed] = this.queue.splice(queuedIndex, 1);
|
|
879
|
+
if (removed?.kind === "message")
|
|
880
|
+
this.notifyMessagesInterrupted([removed.event], "message cancelled");
|
|
754
881
|
this.onStateChange?.({ processing: this.processing, queuedMessages: this.queue.length, disposed: this.disposed });
|
|
755
882
|
return true;
|
|
756
883
|
}
|
|
757
884
|
if (this.activeMessage?.id === eventId) {
|
|
885
|
+
this.notifyMessagesInterrupted([this.activeMessage], "message cancelled");
|
|
886
|
+
this.cancelProviderRecovery();
|
|
887
|
+
this.cancelContextGuardRecovery("Context guard recovery cancelled with the active message");
|
|
758
888
|
await this.runtime.session.abort();
|
|
759
889
|
return true;
|
|
760
890
|
}
|
|
@@ -792,20 +922,32 @@ export class RoutedSession {
|
|
|
792
922
|
});
|
|
793
923
|
try {
|
|
794
924
|
this.activeMessage = event;
|
|
925
|
+
this.providerRecoveryCancelled = false;
|
|
926
|
+
this.pendingAssistantError = undefined;
|
|
927
|
+
this.pendingAssistantErrorRetryable = false;
|
|
928
|
+
this.activeMessageFailed = false;
|
|
795
929
|
this.activeAssistantIndex = undefined;
|
|
796
930
|
this.nextAssistantIndex = 0;
|
|
797
931
|
this.activeThinkingIndex = undefined;
|
|
798
932
|
this.nextThinkingIndex = 0;
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
933
|
+
const session = this.runtime.session;
|
|
934
|
+
const expandedText = expandInlineSkills(event.text, session.resourceLoader.getSkills().skills);
|
|
935
|
+
await session.prompt(expandedText, { source: promptSource(event.source) });
|
|
936
|
+
await this.resumeContextGuardRecovery(session);
|
|
937
|
+
await this.recoverTransientProviderErrors(session);
|
|
938
|
+
this.flushPendingAssistantError();
|
|
939
|
+
if (!this.activeMessageFailed) {
|
|
940
|
+
this.emit({
|
|
941
|
+
type: "message_finished",
|
|
942
|
+
piboSessionId: this.piboSessionId,
|
|
943
|
+
eventId: event.id,
|
|
944
|
+
source: event.source,
|
|
945
|
+
});
|
|
946
|
+
}
|
|
807
947
|
}
|
|
808
948
|
catch (error) {
|
|
949
|
+
if (error instanceof PiboProviderRecoveryCancelledError)
|
|
950
|
+
return;
|
|
809
951
|
const message = errorMessage(error);
|
|
810
952
|
this.emit({
|
|
811
953
|
type: "session_error",
|
|
@@ -817,6 +959,10 @@ export class RoutedSession {
|
|
|
817
959
|
}
|
|
818
960
|
finally {
|
|
819
961
|
this.activeMessage = undefined;
|
|
962
|
+
this.providerRecoveryCancelled = false;
|
|
963
|
+
this.pendingAssistantError = undefined;
|
|
964
|
+
this.pendingAssistantErrorRetryable = false;
|
|
965
|
+
this.activeMessageFailed = false;
|
|
820
966
|
this.activeAssistantIndex = undefined;
|
|
821
967
|
this.nextAssistantIndex = 0;
|
|
822
968
|
this.activeThinkingIndex = undefined;
|
|
@@ -873,6 +1019,9 @@ export class RoutedSession {
|
|
|
873
1019
|
getProviderUsage: () => this.getProviderUsage(),
|
|
874
1020
|
clearQueue: () => this.clearQueue(),
|
|
875
1021
|
abort: async () => {
|
|
1022
|
+
if (this.activeMessage)
|
|
1023
|
+
this.notifyMessagesInterrupted([this.activeMessage], "abort requested");
|
|
1024
|
+
this.cancelContextGuardRecovery("Context guard recovery cancelled by abort");
|
|
876
1025
|
await this.runtime.session.abort();
|
|
877
1026
|
},
|
|
878
1027
|
dispose: () => this.dispose(),
|
|
@@ -913,6 +1062,11 @@ export class RoutedSession {
|
|
|
913
1062
|
},
|
|
914
1063
|
}, event);
|
|
915
1064
|
}
|
|
1065
|
+
cancelContextGuardRecovery(message) {
|
|
1066
|
+
const session = this.recoverySession ?? this.runtime.session;
|
|
1067
|
+
cancelPiboAssistantContextGuardRecovery(session, new Error(message));
|
|
1068
|
+
session.abortCompaction?.();
|
|
1069
|
+
}
|
|
916
1070
|
assertActive() {
|
|
917
1071
|
if (this.disposed) {
|
|
918
1072
|
throw new Error(`Session "${this.piboSessionId}" has been disposed`);
|
|
@@ -934,10 +1088,21 @@ export class RoutedSession {
|
|
|
934
1088
|
}
|
|
935
1089
|
clearQueue() {
|
|
936
1090
|
const cleared = this.queue.length;
|
|
1091
|
+
const removedMessages = this.queue.flatMap((item) => item.kind === "message" ? [item.event] : []);
|
|
937
1092
|
this.queue.length = 0;
|
|
1093
|
+
this.notifyMessagesInterrupted(removedMessages, "queue cleared");
|
|
938
1094
|
this.onStateChange?.({ processing: this.processing, queuedMessages: this.queue.length, disposed: this.disposed });
|
|
939
1095
|
return cleared;
|
|
940
1096
|
}
|
|
1097
|
+
activeAndQueuedMessages() {
|
|
1098
|
+
const messages = this.queue.flatMap((item) => item.kind === "message" ? [item.event] : []);
|
|
1099
|
+
return this.activeMessage ? [this.activeMessage, ...messages] : messages;
|
|
1100
|
+
}
|
|
1101
|
+
notifyMessagesInterrupted(messages, reason) {
|
|
1102
|
+
if (messages.length === 0)
|
|
1103
|
+
return;
|
|
1104
|
+
this.onMessagesInterrupted?.(messages, reason);
|
|
1105
|
+
}
|
|
941
1106
|
createSessionSnapshot() {
|
|
942
1107
|
const session = this.runtime.session;
|
|
943
1108
|
const manager = session.sessionManager;
|