@cereworker/core 26.403.2 → 26.404.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/conversation.d.ts +9 -1
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +51 -0
- package/dist/conversation.js.map +1 -1
- package/dist/events.d.ts +15 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/orchestrator.d.ts +28 -2
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +313 -6
- package/dist/orchestrator.js.map +1 -1
- package/dist/tool-runtime.d.ts +4 -0
- package/dist/tool-runtime.d.ts.map +1 -1
- package/dist/tool-runtime.js +2 -0
- package/dist/tool-runtime.js.map +1 -1
- package/dist/types.d.ts +70 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/orchestrator.js
CHANGED
|
@@ -89,6 +89,7 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
89
89
|
streamPhase = 'idle';
|
|
90
90
|
activeToolCall = null;
|
|
91
91
|
currentStreamTurn = null;
|
|
92
|
+
currentQuerySession = null;
|
|
92
93
|
currentAttemptCompletionState = null;
|
|
93
94
|
currentPartialContent = '';
|
|
94
95
|
currentLastContentKind = 'empty';
|
|
@@ -377,6 +378,8 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
377
378
|
conversationId: options?.conversationId,
|
|
378
379
|
sessionKey: options?.sessionKey,
|
|
379
380
|
scopeKey: options?.scopeKey,
|
|
381
|
+
turnId: options?.turnId,
|
|
382
|
+
attempt: options?.attempt,
|
|
380
383
|
});
|
|
381
384
|
}
|
|
382
385
|
unregisterTool(name) {
|
|
@@ -613,6 +616,67 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
613
616
|
return [];
|
|
614
617
|
return this.conversations.getMessages(id);
|
|
615
618
|
}
|
|
619
|
+
getQuerySession(conversationId, sessionId) {
|
|
620
|
+
return this.conversations.getQuerySession(conversationId, sessionId);
|
|
621
|
+
}
|
|
622
|
+
getSessionEvents(conversationId, sessionId) {
|
|
623
|
+
return this.conversations.getSessionEvents(conversationId, sessionId);
|
|
624
|
+
}
|
|
625
|
+
recordSessionEvent(conversationId, sessionId, type, summary, data) {
|
|
626
|
+
const session = this.conversations.getQuerySession(conversationId, sessionId);
|
|
627
|
+
if (!session)
|
|
628
|
+
return;
|
|
629
|
+
const updatedAt = Date.now();
|
|
630
|
+
const updatedSession = {
|
|
631
|
+
...session,
|
|
632
|
+
updatedAt,
|
|
633
|
+
summary: this.truncateResumeText(summary, 500),
|
|
634
|
+
};
|
|
635
|
+
this.conversations.saveQuerySession(conversationId, updatedSession);
|
|
636
|
+
const event = {
|
|
637
|
+
sessionId,
|
|
638
|
+
conversationId,
|
|
639
|
+
turnId: session.turnId,
|
|
640
|
+
attempt: session.attempt,
|
|
641
|
+
timestamp: updatedAt,
|
|
642
|
+
type,
|
|
643
|
+
state: updatedSession.state,
|
|
644
|
+
summary: this.truncateResumeText(summary, 500),
|
|
645
|
+
instanceId: updatedSession.instanceId,
|
|
646
|
+
checkpointPath: updatedSession.checkpointPath ?? null,
|
|
647
|
+
data,
|
|
648
|
+
};
|
|
649
|
+
this.conversations.appendSessionEvent(conversationId, sessionId, event);
|
|
650
|
+
}
|
|
651
|
+
recordSessionMemoryUpdate(conversationId, sessionId, snapshot) {
|
|
652
|
+
const session = this.conversations.getQuerySession(conversationId, sessionId);
|
|
653
|
+
if (!session)
|
|
654
|
+
return;
|
|
655
|
+
const updated = {
|
|
656
|
+
...session,
|
|
657
|
+
memory: snapshot,
|
|
658
|
+
updatedAt: Date.now(),
|
|
659
|
+
summary: snapshot.summary || session.summary,
|
|
660
|
+
};
|
|
661
|
+
this.conversations.saveQuerySession(conversationId, updated);
|
|
662
|
+
const event = {
|
|
663
|
+
sessionId,
|
|
664
|
+
conversationId,
|
|
665
|
+
turnId: session.turnId,
|
|
666
|
+
attempt: session.attempt,
|
|
667
|
+
timestamp: snapshot.updatedAt,
|
|
668
|
+
type: 'memory_updated',
|
|
669
|
+
state: updated.state,
|
|
670
|
+
summary: this.truncateResumeText(`Session memory updated: ${snapshot.summary}`, 500),
|
|
671
|
+
instanceId: updated.instanceId,
|
|
672
|
+
checkpointPath: updated.checkpointPath ?? null,
|
|
673
|
+
data: {
|
|
674
|
+
excerpt: snapshot.excerpt,
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
this.conversations.appendSessionEvent(conversationId, sessionId, event);
|
|
678
|
+
this.emit({ type: 'session:memory-updated', conversationId, sessionId, snapshot });
|
|
679
|
+
}
|
|
616
680
|
startConversation() {
|
|
617
681
|
const conversation = this.conversations.create();
|
|
618
682
|
this.activeConversationId = conversation.id;
|
|
@@ -749,6 +813,105 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
749
813
|
this.currentLastContentKind = 'empty';
|
|
750
814
|
this.currentJournaledContentLength = 0;
|
|
751
815
|
}
|
|
816
|
+
createQuerySession(conversationId, turnId, attempt, source, latestUserMessage, stallRetryCount, completionRetryCount, priorSession) {
|
|
817
|
+
const timestamp = Date.now();
|
|
818
|
+
const instance = this.instanceStore?.get();
|
|
819
|
+
return {
|
|
820
|
+
id: turnId,
|
|
821
|
+
conversationId,
|
|
822
|
+
turnId,
|
|
823
|
+
attempt,
|
|
824
|
+
source,
|
|
825
|
+
state: 'ready',
|
|
826
|
+
startedAt: timestamp,
|
|
827
|
+
updatedAt: timestamp,
|
|
828
|
+
summary: `Turn attempt ${attempt} started.`,
|
|
829
|
+
latestUserMessage: this.truncateResumeText(latestUserMessage, 1200),
|
|
830
|
+
stallRetryCount,
|
|
831
|
+
completionRetryCount,
|
|
832
|
+
instanceId: instance?.id,
|
|
833
|
+
checkpointPath: instance?.activeCheckpoint ?? null,
|
|
834
|
+
// Carry forward recovery state from prior attempt for crash recovery
|
|
835
|
+
...(priorSession?.latestBoundary ? { latestBoundary: priorSession.latestBoundary } : {}),
|
|
836
|
+
...(priorSession?.lastOutcome ? { lastOutcome: priorSession.lastOutcome } : {}),
|
|
837
|
+
...(priorSession?.lastError ? { lastError: priorSession.lastError } : {}),
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
saveCurrentQuerySession() {
|
|
841
|
+
if (!this.currentStreamTurn || !this.currentQuerySession)
|
|
842
|
+
return;
|
|
843
|
+
this.conversations.saveQuerySession(this.currentStreamTurn.conversationId, this.currentQuerySession);
|
|
844
|
+
}
|
|
845
|
+
updateCurrentQuerySession(type, summary, data) {
|
|
846
|
+
if (!this.currentStreamTurn || !this.currentQuerySession)
|
|
847
|
+
return;
|
|
848
|
+
const eventState = this.getQuerySessionState(type, data);
|
|
849
|
+
const updatedAt = Date.now();
|
|
850
|
+
const next = {
|
|
851
|
+
...this.currentQuerySession,
|
|
852
|
+
attempt: this.currentStreamTurn.attempt,
|
|
853
|
+
updatedAt,
|
|
854
|
+
summary: this.truncateResumeText(summary, 500),
|
|
855
|
+
state: this.resolveQuerySessionState(type, eventState, data),
|
|
856
|
+
checkpointPath: this.instanceStore?.get()?.activeCheckpoint ?? this.currentQuerySession.checkpointPath ?? null,
|
|
857
|
+
};
|
|
858
|
+
if (type === 'partial_text') {
|
|
859
|
+
const excerpt = typeof data?.excerpt === 'string' ? data.excerpt : summary;
|
|
860
|
+
next.latestAssistantMessage = this.truncateResumeText(excerpt, 1200);
|
|
861
|
+
}
|
|
862
|
+
if (type === 'tool_start') {
|
|
863
|
+
next.activeToolName = typeof data?.toolName === 'string' ? data.toolName : undefined;
|
|
864
|
+
next.activeToolCallId = typeof data?.callId === 'string' ? data.callId : undefined;
|
|
865
|
+
}
|
|
866
|
+
else if (type !== 'tool_end') {
|
|
867
|
+
next.activeToolName = undefined;
|
|
868
|
+
next.activeToolCallId = undefined;
|
|
869
|
+
}
|
|
870
|
+
if (type === 'tool_end') {
|
|
871
|
+
next.activeToolName = undefined;
|
|
872
|
+
next.activeToolCallId = undefined;
|
|
873
|
+
}
|
|
874
|
+
if (type === 'turn_finished') {
|
|
875
|
+
const finalContent = typeof data?.finalContent === 'string' ? data.finalContent.trim() : '';
|
|
876
|
+
if (finalContent) {
|
|
877
|
+
next.latestAssistantMessage = this.truncateResumeText(finalContent, 1200);
|
|
878
|
+
}
|
|
879
|
+
if (typeof data?.turnOutcome === 'string') {
|
|
880
|
+
next.lastOutcome = data.turnOutcome;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (type === 'turn_error') {
|
|
884
|
+
next.lastError = typeof data?.error === 'string' ? data.error : summary;
|
|
885
|
+
if (data?.aborted === true) {
|
|
886
|
+
next.lastOutcome = 'aborted';
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
next.lastOutcome = 'protocol_error';
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (type === 'completion_signal' && typeof data?.signal === 'string') {
|
|
893
|
+
next.summary = this.truncateResumeText(summary, 500);
|
|
894
|
+
}
|
|
895
|
+
this.currentQuerySession = next;
|
|
896
|
+
this.saveCurrentQuerySession();
|
|
897
|
+
}
|
|
898
|
+
resolveQuerySessionState(type, defaultState, data) {
|
|
899
|
+
if (type === 'turn_finished') {
|
|
900
|
+
const turnOutcome = data?.turnOutcome;
|
|
901
|
+
if (turnOutcome === 'completed' || turnOutcome === 'completed_no_text')
|
|
902
|
+
return 'completed';
|
|
903
|
+
if (turnOutcome === 'stalled')
|
|
904
|
+
return 'stalled';
|
|
905
|
+
if (turnOutcome === 'aborted')
|
|
906
|
+
return 'aborted';
|
|
907
|
+
if (turnOutcome === 'ended_on_tool_calls' || turnOutcome === 'completion_signal_missing') {
|
|
908
|
+
return 'waiting_followup';
|
|
909
|
+
}
|
|
910
|
+
if (turnOutcome === 'protocol_error')
|
|
911
|
+
return 'failed';
|
|
912
|
+
}
|
|
913
|
+
return defaultState;
|
|
914
|
+
}
|
|
752
915
|
appendTurnJournalEntry(type, summary, data) {
|
|
753
916
|
if (!this.currentStreamTurn)
|
|
754
917
|
return;
|
|
@@ -761,6 +924,70 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
761
924
|
...(data ? { data } : {}),
|
|
762
925
|
};
|
|
763
926
|
this.conversations.appendTurnJournalEntry(this.currentStreamTurn.conversationId, this.currentStreamTurn.turnId, entry);
|
|
927
|
+
this.appendSessionEvent(type, entry.summary, data);
|
|
928
|
+
}
|
|
929
|
+
appendSessionEvent(type, summary, data) {
|
|
930
|
+
if (!this.currentStreamTurn)
|
|
931
|
+
return;
|
|
932
|
+
const instance = this.instanceStore?.get();
|
|
933
|
+
const event = {
|
|
934
|
+
sessionId: this.currentStreamTurn.sessionId,
|
|
935
|
+
conversationId: this.currentStreamTurn.conversationId,
|
|
936
|
+
turnId: this.currentStreamTurn.turnId,
|
|
937
|
+
attempt: this.currentStreamTurn.attempt,
|
|
938
|
+
timestamp: Date.now(),
|
|
939
|
+
type: this.mapJournalEntryToSessionEvent(type),
|
|
940
|
+
state: this.getQuerySessionState(type, data),
|
|
941
|
+
summary: this.truncateResumeText(summary, 500),
|
|
942
|
+
instanceId: instance?.id,
|
|
943
|
+
checkpointPath: instance?.activeCheckpoint ?? null,
|
|
944
|
+
...(data ? { data } : {}),
|
|
945
|
+
};
|
|
946
|
+
this.conversations.appendSessionEvent(this.currentStreamTurn.conversationId, this.currentStreamTurn.sessionId, event);
|
|
947
|
+
this.updateCurrentQuerySession(type, summary, data);
|
|
948
|
+
}
|
|
949
|
+
mapJournalEntryToSessionEvent(type) {
|
|
950
|
+
switch (type) {
|
|
951
|
+
case 'tool_start':
|
|
952
|
+
return 'tool_started';
|
|
953
|
+
case 'tool_end':
|
|
954
|
+
return 'tool_finished';
|
|
955
|
+
case 'checkpoint':
|
|
956
|
+
return 'checkpoint_recorded';
|
|
957
|
+
case 'boundary':
|
|
958
|
+
return 'boundary_committed';
|
|
959
|
+
case 'completion_signal':
|
|
960
|
+
return 'completion_signal_recorded';
|
|
961
|
+
case 'recovery':
|
|
962
|
+
return 'recovery_assessed';
|
|
963
|
+
case 'turn_error':
|
|
964
|
+
return 'turn_failed';
|
|
965
|
+
default:
|
|
966
|
+
return type;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
getQuerySessionState(type, data) {
|
|
970
|
+
switch (type) {
|
|
971
|
+
case 'turn_started':
|
|
972
|
+
return 'ready';
|
|
973
|
+
case 'partial_text':
|
|
974
|
+
return 'sampling';
|
|
975
|
+
case 'tool_start':
|
|
976
|
+
return 'tool_execution';
|
|
977
|
+
case 'tool_end':
|
|
978
|
+
case 'checkpoint':
|
|
979
|
+
case 'boundary':
|
|
980
|
+
case 'completion_signal':
|
|
981
|
+
return 'waiting_followup';
|
|
982
|
+
case 'recovery':
|
|
983
|
+
return data?.cause === 'stall' ? 'stalled' : 'waiting_followup';
|
|
984
|
+
case 'turn_finished':
|
|
985
|
+
return 'completed';
|
|
986
|
+
case 'turn_error':
|
|
987
|
+
return data?.aborted ? 'aborted' : 'failed';
|
|
988
|
+
default:
|
|
989
|
+
return 'waiting_followup';
|
|
990
|
+
}
|
|
764
991
|
}
|
|
765
992
|
pruneTurnJournals(conversationId) {
|
|
766
993
|
try {
|
|
@@ -817,6 +1044,14 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
817
1044
|
evidence: summary.evidence,
|
|
818
1045
|
browserState: summary.browserState,
|
|
819
1046
|
});
|
|
1047
|
+
if (this.currentQuerySession) {
|
|
1048
|
+
this.currentQuerySession = {
|
|
1049
|
+
...this.currentQuerySession,
|
|
1050
|
+
latestBoundary: summary,
|
|
1051
|
+
updatedAt: Date.now(),
|
|
1052
|
+
};
|
|
1053
|
+
this.saveCurrentQuerySession();
|
|
1054
|
+
}
|
|
820
1055
|
return summary;
|
|
821
1056
|
}
|
|
822
1057
|
deriveRepetitionSignals(continuity) {
|
|
@@ -1606,7 +1841,7 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1606
1841
|
}
|
|
1607
1842
|
this.resetStreamState();
|
|
1608
1843
|
}
|
|
1609
|
-
async sendMessage(content, conversationId) {
|
|
1844
|
+
async sendMessage(content, conversationId, options) {
|
|
1610
1845
|
if (!this.cerebrum)
|
|
1611
1846
|
throw new Error('Cerebrum not connected');
|
|
1612
1847
|
if (this.cerebellum && !this.cerebellum.isConnected()) {
|
|
@@ -1629,7 +1864,7 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1629
1864
|
let nextRetryContext = null;
|
|
1630
1865
|
// Track message IDs from failed attempts so they can be excluded from retries and cleaned up.
|
|
1631
1866
|
const failedAttemptMessageIds = [];
|
|
1632
|
-
const turnId = nanoid(10);
|
|
1867
|
+
const turnId = options?.turnId ?? nanoid(10);
|
|
1633
1868
|
const maxTotalAttempts = 1 + this.maxNudgeRetries + this.maxCompletionRetries;
|
|
1634
1869
|
let loopTerminated = false;
|
|
1635
1870
|
let nextRetryCause = null;
|
|
@@ -1647,11 +1882,27 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1647
1882
|
const isCurrentAttempt = () => this.abortController === abortController;
|
|
1648
1883
|
this.abortController = abortController;
|
|
1649
1884
|
this.currentAttemptCompletionState = completionState;
|
|
1885
|
+
const sessionSource = options?.source ?? 'local';
|
|
1650
1886
|
this.currentStreamTurn = {
|
|
1651
1887
|
turnId,
|
|
1652
1888
|
attempt: attemptNumber,
|
|
1653
1889
|
conversationId: convId,
|
|
1890
|
+
sessionId: turnId,
|
|
1891
|
+
source: sessionSource,
|
|
1654
1892
|
};
|
|
1893
|
+
const priorSession = this.currentQuerySession;
|
|
1894
|
+
this.currentQuerySession = this.createQuerySession(convId, turnId, attemptNumber, sessionSource, latestUserMessage, this.streamNudgeCount, completionRetryCount, priorSession);
|
|
1895
|
+
this.saveCurrentQuerySession();
|
|
1896
|
+
if (attemptNumber === 1 && sessionSource === 'channel' && options?.ingress) {
|
|
1897
|
+
this.recordSessionEvent(convId, turnId, 'channel_ingress', `Received channel message from ${options.ingress.senderName || options.ingress.senderId || 'unknown sender'}.`, {
|
|
1898
|
+
channelId: options.ingress.channelId,
|
|
1899
|
+
senderId: options.ingress.senderId,
|
|
1900
|
+
senderName: options.ingress.senderName,
|
|
1901
|
+
threadId: options.ingress.threadId,
|
|
1902
|
+
replyToId: options.ingress.replyToId,
|
|
1903
|
+
timestamp: options.ingress.timestamp,
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1655
1906
|
this.currentPartialContent = '';
|
|
1656
1907
|
this.currentLastContentKind = 'empty';
|
|
1657
1908
|
this.currentJournaledContentLength = 0;
|
|
@@ -1670,7 +1921,13 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1670
1921
|
stallRetryCount: this.streamNudgeCount,
|
|
1671
1922
|
completionRetryCount,
|
|
1672
1923
|
});
|
|
1673
|
-
this.emit({
|
|
1924
|
+
this.emit({
|
|
1925
|
+
type: 'message:cerebrum:start',
|
|
1926
|
+
conversationId: convId,
|
|
1927
|
+
turnId,
|
|
1928
|
+
sessionId: turnId,
|
|
1929
|
+
source: sessionSource,
|
|
1930
|
+
});
|
|
1674
1931
|
this.startStreamWatchdog(latestUserMessage);
|
|
1675
1932
|
let messages = this.conversations.getMessages(convId);
|
|
1676
1933
|
// On retry: exclude failed attempts' messages from history.
|
|
@@ -1795,8 +2052,10 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1795
2052
|
toolCall,
|
|
1796
2053
|
tools: allTools,
|
|
1797
2054
|
conversationId: convId,
|
|
1798
|
-
sessionKey:
|
|
2055
|
+
sessionKey: turnId,
|
|
1799
2056
|
scopeKey: convId,
|
|
2057
|
+
turnId,
|
|
2058
|
+
attempt: attemptNumber,
|
|
1800
2059
|
abortSignal: abortController.signal,
|
|
1801
2060
|
});
|
|
1802
2061
|
this.logStreamDebug('tool_callback_finished', this.buildToolDebugPayload(toolCall, result, toolName));
|
|
@@ -1839,6 +2098,16 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1839
2098
|
result.output += `\n[Cerebellum warning: ${failedChecks}]`;
|
|
1840
2099
|
}
|
|
1841
2100
|
if (verification) {
|
|
2101
|
+
result.metadata = {
|
|
2102
|
+
...(result.metadata ?? {}),
|
|
2103
|
+
verification: {
|
|
2104
|
+
passed: verification.passed,
|
|
2105
|
+
modelVerdict: verification.modelVerdict,
|
|
2106
|
+
failedChecks: verification.checks
|
|
2107
|
+
.filter((check) => !check.passed)
|
|
2108
|
+
.map((check) => check.description),
|
|
2109
|
+
},
|
|
2110
|
+
};
|
|
1842
2111
|
const vResult = {
|
|
1843
2112
|
passed: verification.passed,
|
|
1844
2113
|
checks: verification.checks,
|
|
@@ -1846,7 +2115,12 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1846
2115
|
toolCallId: toolCall.id,
|
|
1847
2116
|
toolName,
|
|
1848
2117
|
};
|
|
1849
|
-
this.emit({
|
|
2118
|
+
this.emit({
|
|
2119
|
+
type: 'verification:end',
|
|
2120
|
+
result: vResult,
|
|
2121
|
+
conversationId: convId,
|
|
2122
|
+
sessionId: turnId,
|
|
2123
|
+
});
|
|
1850
2124
|
}
|
|
1851
2125
|
}
|
|
1852
2126
|
catch {
|
|
@@ -1965,7 +2239,14 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1965
2239
|
}
|
|
1966
2240
|
}
|
|
1967
2241
|
const cerebrumMessage = this.conversations.appendMessage(convId, 'cerebrum', displayContent, visibleToolCalls?.length ? { toolCalls: visibleToolCalls } : undefined);
|
|
1968
|
-
this.emit({
|
|
2242
|
+
this.emit({
|
|
2243
|
+
type: 'message:cerebrum:end',
|
|
2244
|
+
conversationId: convId,
|
|
2245
|
+
turnId,
|
|
2246
|
+
sessionId: turnId,
|
|
2247
|
+
source: sessionSource,
|
|
2248
|
+
message: cerebrumMessage,
|
|
2249
|
+
});
|
|
1969
2250
|
log.info('stream_finished', {
|
|
1970
2251
|
turnId,
|
|
1971
2252
|
attempt: attemptNumber,
|
|
@@ -2049,6 +2330,12 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2049
2330
|
const diagnosticMessage = this.conversations.appendMessage(convId, 'system', assessment.operatorMessage);
|
|
2050
2331
|
this.emit({ type: 'message:system', message: diagnosticMessage });
|
|
2051
2332
|
this.emitCompletionTrace('retry_failed', assessment.diagnosis, completionSignal, 'error');
|
|
2333
|
+
this.appendTurnJournalEntry('turn_error', assessment.diagnosis || 'Recovery returned stop.', {
|
|
2334
|
+
retryCause: 'completion',
|
|
2335
|
+
completionRetryCount,
|
|
2336
|
+
stallRetryCount: this.streamNudgeCount,
|
|
2337
|
+
error: assessment.diagnosis || 'Recovery returned stop.',
|
|
2338
|
+
});
|
|
2052
2339
|
this.emit({
|
|
2053
2340
|
type: 'error',
|
|
2054
2341
|
error: new Error(assessment.diagnosis ||
|
|
@@ -2076,6 +2363,12 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2076
2363
|
: '[System fallback] The turn ended repeatedly without a valid completion signal or final answer.');
|
|
2077
2364
|
this.emit({ type: 'message:system', message: diagnosticMessage });
|
|
2078
2365
|
this.emitCompletionTrace('retry_failed', `Completion retries exhausted after ${completionRetryCount}/${this.maxCompletionRetries}: ${assessment.diagnosis || completionFailure.message}`, completionSignal, 'error');
|
|
2366
|
+
this.appendTurnJournalEntry('turn_error', `Completion retries exhausted (${completionRetryCount}/${this.maxCompletionRetries}).`, {
|
|
2367
|
+
retryCause: 'completion',
|
|
2368
|
+
completionRetryCount,
|
|
2369
|
+
stallRetryCount: this.streamNudgeCount,
|
|
2370
|
+
error: assessment.diagnosis || 'Completion retries exhausted.',
|
|
2371
|
+
});
|
|
2079
2372
|
this.emit({
|
|
2080
2373
|
type: 'error',
|
|
2081
2374
|
error: new Error(assessment.diagnosis ||
|
|
@@ -2118,6 +2411,12 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2118
2411
|
stallRecovery.assessment.action === 'stop') {
|
|
2119
2412
|
const systemMessage = this.conversations.appendMessage(convId, 'system', stallRecovery.assessment.operatorMessage);
|
|
2120
2413
|
this.emit({ type: 'message:system', message: systemMessage });
|
|
2414
|
+
this.appendTurnJournalEntry('turn_error', stallRecovery.assessment.diagnosis || 'Stall recovery returned stop.', {
|
|
2415
|
+
retryCause: 'stall',
|
|
2416
|
+
stallRetryCount: this.streamNudgeCount,
|
|
2417
|
+
completionRetryCount,
|
|
2418
|
+
error: stallRecovery.assessment.diagnosis || 'Stall recovery returned stop.',
|
|
2419
|
+
});
|
|
2121
2420
|
this.emit({ type: 'error', error: new Error(stallRecovery.assessment.diagnosis) });
|
|
2122
2421
|
if (failedAttemptMessageIds.length > 0) {
|
|
2123
2422
|
this.conversations.deleteMessages(convId, failedAttemptMessageIds);
|
|
@@ -2129,6 +2428,13 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2129
2428
|
if (this.cerebellum && !this.cerebellum.isConnected() && abortController.signal.aborted) {
|
|
2130
2429
|
const err = new Error('Cerebellum disconnected during active response. Restart it with: docker compose up -d cerebellum');
|
|
2131
2430
|
log.error('Cerebellum disconnected mid-stream', { error: err.message });
|
|
2431
|
+
this.appendTurnJournalEntry('turn_error', err.message, {
|
|
2432
|
+
retryCause,
|
|
2433
|
+
stallRetryCount: this.streamNudgeCount,
|
|
2434
|
+
completionRetryCount,
|
|
2435
|
+
error: err.message,
|
|
2436
|
+
aborted: true,
|
|
2437
|
+
});
|
|
2132
2438
|
this.emit({ type: 'error', error: err });
|
|
2133
2439
|
if (failedAttemptMessageIds.length > 0) {
|
|
2134
2440
|
this.conversations.deleteMessages(convId, failedAttemptMessageIds);
|
|
@@ -2191,6 +2497,7 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2191
2497
|
}
|
|
2192
2498
|
}
|
|
2193
2499
|
finally {
|
|
2500
|
+
this.currentQuerySession = null;
|
|
2194
2501
|
this.currentStreamTurn = null;
|
|
2195
2502
|
}
|
|
2196
2503
|
}
|