@cereworker/core 26.403.2 → 26.404.2
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 +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/instance.d.ts +2 -0
- package/dist/instance.d.ts.map +1 -1
- package/dist/instance.js +10 -0
- package/dist/instance.js.map +1 -1
- package/dist/orchestrator.d.ts +30 -2
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +325 -6
- package/dist/orchestrator.js.map +1 -1
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +5 -4
- package/dist/system-prompt.js.map +1 -1
- package/dist/task-schedule.d.ts +13 -0
- package/dist/task-schedule.d.ts.map +1 -0
- package/dist/task-schedule.js +201 -0
- package/dist/task-schedule.js.map +1 -0
- package/dist/task-store.d.ts +22 -0
- package/dist/task-store.d.ts.map +1 -0
- package/dist/task-store.js +141 -0
- package/dist/task-store.js.map +1 -0
- package/dist/tool-runtime.d.ts +15 -0
- package/dist/tool-runtime.d.ts.map +1 -1
- package/dist/tool-runtime.js +3 -0
- package/dist/tool-runtime.js.map +1 -1
- package/dist/types.d.ts +143 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/orchestrator.js
CHANGED
|
@@ -12,10 +12,18 @@ const log = createLogger('orchestrator');
|
|
|
12
12
|
const TASK_COMPLETE_TOOL = 'task_complete';
|
|
13
13
|
const TASK_BLOCKED_TOOL = 'task_blocked';
|
|
14
14
|
const TASK_CHECKPOINT_TOOL = 'task_checkpoint';
|
|
15
|
+
const TASK_UPSERT_TOOL = 'task_upsert';
|
|
16
|
+
const TASK_REMOVE_TOOL = 'task_remove';
|
|
17
|
+
const TASK_LIST_TOOL = 'task_list';
|
|
18
|
+
const TASK_GET_TOOL = 'task_get';
|
|
15
19
|
const INTERNAL_TASK_TOOL_NAMES = new Set([
|
|
16
20
|
TASK_COMPLETE_TOOL,
|
|
17
21
|
TASK_BLOCKED_TOOL,
|
|
18
22
|
TASK_CHECKPOINT_TOOL,
|
|
23
|
+
TASK_UPSERT_TOOL,
|
|
24
|
+
TASK_REMOVE_TOOL,
|
|
25
|
+
TASK_LIST_TOOL,
|
|
26
|
+
TASK_GET_TOOL,
|
|
19
27
|
]);
|
|
20
28
|
const SYSTEM_FALLBACK_COMPLETION_PROMPT = '[System fallback] The last turn ended without a final answer. Continue from the last verified state and end by calling task_complete or task_blocked before your final answer.';
|
|
21
29
|
const SYSTEM_FALLBACK_STALL_PROMPT = '[System fallback] The stalled turn is being retried from the last verified state.';
|
|
@@ -89,6 +97,7 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
89
97
|
streamPhase = 'idle';
|
|
90
98
|
activeToolCall = null;
|
|
91
99
|
currentStreamTurn = null;
|
|
100
|
+
currentQuerySession = null;
|
|
92
101
|
currentAttemptCompletionState = null;
|
|
93
102
|
currentPartialContent = '';
|
|
94
103
|
currentLastContentKind = 'empty';
|
|
@@ -377,6 +386,8 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
377
386
|
conversationId: options?.conversationId,
|
|
378
387
|
sessionKey: options?.sessionKey,
|
|
379
388
|
scopeKey: options?.scopeKey,
|
|
389
|
+
turnId: options?.turnId,
|
|
390
|
+
attempt: options?.attempt,
|
|
380
391
|
});
|
|
381
392
|
}
|
|
382
393
|
unregisterTool(name) {
|
|
@@ -613,6 +624,67 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
613
624
|
return [];
|
|
614
625
|
return this.conversations.getMessages(id);
|
|
615
626
|
}
|
|
627
|
+
getQuerySession(conversationId, sessionId) {
|
|
628
|
+
return this.conversations.getQuerySession(conversationId, sessionId);
|
|
629
|
+
}
|
|
630
|
+
getSessionEvents(conversationId, sessionId) {
|
|
631
|
+
return this.conversations.getSessionEvents(conversationId, sessionId);
|
|
632
|
+
}
|
|
633
|
+
recordSessionEvent(conversationId, sessionId, type, summary, data) {
|
|
634
|
+
const session = this.conversations.getQuerySession(conversationId, sessionId);
|
|
635
|
+
if (!session)
|
|
636
|
+
return;
|
|
637
|
+
const updatedAt = Date.now();
|
|
638
|
+
const updatedSession = {
|
|
639
|
+
...session,
|
|
640
|
+
updatedAt,
|
|
641
|
+
summary: this.truncateResumeText(summary, 500),
|
|
642
|
+
};
|
|
643
|
+
this.conversations.saveQuerySession(conversationId, updatedSession);
|
|
644
|
+
const event = {
|
|
645
|
+
sessionId,
|
|
646
|
+
conversationId,
|
|
647
|
+
turnId: session.turnId,
|
|
648
|
+
attempt: session.attempt,
|
|
649
|
+
timestamp: updatedAt,
|
|
650
|
+
type,
|
|
651
|
+
state: updatedSession.state,
|
|
652
|
+
summary: this.truncateResumeText(summary, 500),
|
|
653
|
+
instanceId: updatedSession.instanceId,
|
|
654
|
+
checkpointPath: updatedSession.checkpointPath ?? null,
|
|
655
|
+
data,
|
|
656
|
+
};
|
|
657
|
+
this.conversations.appendSessionEvent(conversationId, sessionId, event);
|
|
658
|
+
}
|
|
659
|
+
recordSessionMemoryUpdate(conversationId, sessionId, snapshot) {
|
|
660
|
+
const session = this.conversations.getQuerySession(conversationId, sessionId);
|
|
661
|
+
if (!session)
|
|
662
|
+
return;
|
|
663
|
+
const updated = {
|
|
664
|
+
...session,
|
|
665
|
+
memory: snapshot,
|
|
666
|
+
updatedAt: Date.now(),
|
|
667
|
+
summary: snapshot.summary || session.summary,
|
|
668
|
+
};
|
|
669
|
+
this.conversations.saveQuerySession(conversationId, updated);
|
|
670
|
+
const event = {
|
|
671
|
+
sessionId,
|
|
672
|
+
conversationId,
|
|
673
|
+
turnId: session.turnId,
|
|
674
|
+
attempt: session.attempt,
|
|
675
|
+
timestamp: snapshot.updatedAt,
|
|
676
|
+
type: 'memory_updated',
|
|
677
|
+
state: updated.state,
|
|
678
|
+
summary: this.truncateResumeText(`Session memory updated: ${snapshot.summary}`, 500),
|
|
679
|
+
instanceId: updated.instanceId,
|
|
680
|
+
checkpointPath: updated.checkpointPath ?? null,
|
|
681
|
+
data: {
|
|
682
|
+
excerpt: snapshot.excerpt,
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
this.conversations.appendSessionEvent(conversationId, sessionId, event);
|
|
686
|
+
this.emit({ type: 'session:memory-updated', conversationId, sessionId, snapshot });
|
|
687
|
+
}
|
|
616
688
|
startConversation() {
|
|
617
689
|
const conversation = this.conversations.create();
|
|
618
690
|
this.activeConversationId = conversation.id;
|
|
@@ -749,6 +821,105 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
749
821
|
this.currentLastContentKind = 'empty';
|
|
750
822
|
this.currentJournaledContentLength = 0;
|
|
751
823
|
}
|
|
824
|
+
createQuerySession(conversationId, turnId, attempt, source, latestUserMessage, stallRetryCount, completionRetryCount, priorSession) {
|
|
825
|
+
const timestamp = Date.now();
|
|
826
|
+
const instance = this.instanceStore?.get();
|
|
827
|
+
return {
|
|
828
|
+
id: turnId,
|
|
829
|
+
conversationId,
|
|
830
|
+
turnId,
|
|
831
|
+
attempt,
|
|
832
|
+
source,
|
|
833
|
+
state: 'ready',
|
|
834
|
+
startedAt: timestamp,
|
|
835
|
+
updatedAt: timestamp,
|
|
836
|
+
summary: `Turn attempt ${attempt} started.`,
|
|
837
|
+
latestUserMessage: this.truncateResumeText(latestUserMessage, 1200),
|
|
838
|
+
stallRetryCount,
|
|
839
|
+
completionRetryCount,
|
|
840
|
+
instanceId: instance?.id,
|
|
841
|
+
checkpointPath: instance?.activeCheckpoint ?? null,
|
|
842
|
+
// Carry forward recovery state from prior attempt for crash recovery
|
|
843
|
+
...(priorSession?.latestBoundary ? { latestBoundary: priorSession.latestBoundary } : {}),
|
|
844
|
+
...(priorSession?.lastOutcome ? { lastOutcome: priorSession.lastOutcome } : {}),
|
|
845
|
+
...(priorSession?.lastError ? { lastError: priorSession.lastError } : {}),
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
saveCurrentQuerySession() {
|
|
849
|
+
if (!this.currentStreamTurn || !this.currentQuerySession)
|
|
850
|
+
return;
|
|
851
|
+
this.conversations.saveQuerySession(this.currentStreamTurn.conversationId, this.currentQuerySession);
|
|
852
|
+
}
|
|
853
|
+
updateCurrentQuerySession(type, summary, data) {
|
|
854
|
+
if (!this.currentStreamTurn || !this.currentQuerySession)
|
|
855
|
+
return;
|
|
856
|
+
const eventState = this.getQuerySessionState(type, data);
|
|
857
|
+
const updatedAt = Date.now();
|
|
858
|
+
const next = {
|
|
859
|
+
...this.currentQuerySession,
|
|
860
|
+
attempt: this.currentStreamTurn.attempt,
|
|
861
|
+
updatedAt,
|
|
862
|
+
summary: this.truncateResumeText(summary, 500),
|
|
863
|
+
state: this.resolveQuerySessionState(type, eventState, data),
|
|
864
|
+
checkpointPath: this.instanceStore?.get()?.activeCheckpoint ?? this.currentQuerySession.checkpointPath ?? null,
|
|
865
|
+
};
|
|
866
|
+
if (type === 'partial_text') {
|
|
867
|
+
const excerpt = typeof data?.excerpt === 'string' ? data.excerpt : summary;
|
|
868
|
+
next.latestAssistantMessage = this.truncateResumeText(excerpt, 1200);
|
|
869
|
+
}
|
|
870
|
+
if (type === 'tool_start') {
|
|
871
|
+
next.activeToolName = typeof data?.toolName === 'string' ? data.toolName : undefined;
|
|
872
|
+
next.activeToolCallId = typeof data?.callId === 'string' ? data.callId : undefined;
|
|
873
|
+
}
|
|
874
|
+
else if (type !== 'tool_end') {
|
|
875
|
+
next.activeToolName = undefined;
|
|
876
|
+
next.activeToolCallId = undefined;
|
|
877
|
+
}
|
|
878
|
+
if (type === 'tool_end') {
|
|
879
|
+
next.activeToolName = undefined;
|
|
880
|
+
next.activeToolCallId = undefined;
|
|
881
|
+
}
|
|
882
|
+
if (type === 'turn_finished') {
|
|
883
|
+
const finalContent = typeof data?.finalContent === 'string' ? data.finalContent.trim() : '';
|
|
884
|
+
if (finalContent) {
|
|
885
|
+
next.latestAssistantMessage = this.truncateResumeText(finalContent, 1200);
|
|
886
|
+
}
|
|
887
|
+
if (typeof data?.turnOutcome === 'string') {
|
|
888
|
+
next.lastOutcome = data.turnOutcome;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (type === 'turn_error') {
|
|
892
|
+
next.lastError = typeof data?.error === 'string' ? data.error : summary;
|
|
893
|
+
if (data?.aborted === true) {
|
|
894
|
+
next.lastOutcome = 'aborted';
|
|
895
|
+
}
|
|
896
|
+
else {
|
|
897
|
+
next.lastOutcome = 'protocol_error';
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (type === 'completion_signal' && typeof data?.signal === 'string') {
|
|
901
|
+
next.summary = this.truncateResumeText(summary, 500);
|
|
902
|
+
}
|
|
903
|
+
this.currentQuerySession = next;
|
|
904
|
+
this.saveCurrentQuerySession();
|
|
905
|
+
}
|
|
906
|
+
resolveQuerySessionState(type, defaultState, data) {
|
|
907
|
+
if (type === 'turn_finished') {
|
|
908
|
+
const turnOutcome = data?.turnOutcome;
|
|
909
|
+
if (turnOutcome === 'completed' || turnOutcome === 'completed_no_text')
|
|
910
|
+
return 'completed';
|
|
911
|
+
if (turnOutcome === 'stalled')
|
|
912
|
+
return 'stalled';
|
|
913
|
+
if (turnOutcome === 'aborted')
|
|
914
|
+
return 'aborted';
|
|
915
|
+
if (turnOutcome === 'ended_on_tool_calls' || turnOutcome === 'completion_signal_missing') {
|
|
916
|
+
return 'waiting_followup';
|
|
917
|
+
}
|
|
918
|
+
if (turnOutcome === 'protocol_error')
|
|
919
|
+
return 'failed';
|
|
920
|
+
}
|
|
921
|
+
return defaultState;
|
|
922
|
+
}
|
|
752
923
|
appendTurnJournalEntry(type, summary, data) {
|
|
753
924
|
if (!this.currentStreamTurn)
|
|
754
925
|
return;
|
|
@@ -761,6 +932,70 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
761
932
|
...(data ? { data } : {}),
|
|
762
933
|
};
|
|
763
934
|
this.conversations.appendTurnJournalEntry(this.currentStreamTurn.conversationId, this.currentStreamTurn.turnId, entry);
|
|
935
|
+
this.appendSessionEvent(type, entry.summary, data);
|
|
936
|
+
}
|
|
937
|
+
appendSessionEvent(type, summary, data) {
|
|
938
|
+
if (!this.currentStreamTurn)
|
|
939
|
+
return;
|
|
940
|
+
const instance = this.instanceStore?.get();
|
|
941
|
+
const event = {
|
|
942
|
+
sessionId: this.currentStreamTurn.sessionId,
|
|
943
|
+
conversationId: this.currentStreamTurn.conversationId,
|
|
944
|
+
turnId: this.currentStreamTurn.turnId,
|
|
945
|
+
attempt: this.currentStreamTurn.attempt,
|
|
946
|
+
timestamp: Date.now(),
|
|
947
|
+
type: this.mapJournalEntryToSessionEvent(type),
|
|
948
|
+
state: this.getQuerySessionState(type, data),
|
|
949
|
+
summary: this.truncateResumeText(summary, 500),
|
|
950
|
+
instanceId: instance?.id,
|
|
951
|
+
checkpointPath: instance?.activeCheckpoint ?? null,
|
|
952
|
+
...(data ? { data } : {}),
|
|
953
|
+
};
|
|
954
|
+
this.conversations.appendSessionEvent(this.currentStreamTurn.conversationId, this.currentStreamTurn.sessionId, event);
|
|
955
|
+
this.updateCurrentQuerySession(type, summary, data);
|
|
956
|
+
}
|
|
957
|
+
mapJournalEntryToSessionEvent(type) {
|
|
958
|
+
switch (type) {
|
|
959
|
+
case 'tool_start':
|
|
960
|
+
return 'tool_started';
|
|
961
|
+
case 'tool_end':
|
|
962
|
+
return 'tool_finished';
|
|
963
|
+
case 'checkpoint':
|
|
964
|
+
return 'checkpoint_recorded';
|
|
965
|
+
case 'boundary':
|
|
966
|
+
return 'boundary_committed';
|
|
967
|
+
case 'completion_signal':
|
|
968
|
+
return 'completion_signal_recorded';
|
|
969
|
+
case 'recovery':
|
|
970
|
+
return 'recovery_assessed';
|
|
971
|
+
case 'turn_error':
|
|
972
|
+
return 'turn_failed';
|
|
973
|
+
default:
|
|
974
|
+
return type;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
getQuerySessionState(type, data) {
|
|
978
|
+
switch (type) {
|
|
979
|
+
case 'turn_started':
|
|
980
|
+
return 'ready';
|
|
981
|
+
case 'partial_text':
|
|
982
|
+
return 'sampling';
|
|
983
|
+
case 'tool_start':
|
|
984
|
+
return 'tool_execution';
|
|
985
|
+
case 'tool_end':
|
|
986
|
+
case 'checkpoint':
|
|
987
|
+
case 'boundary':
|
|
988
|
+
case 'completion_signal':
|
|
989
|
+
return 'waiting_followup';
|
|
990
|
+
case 'recovery':
|
|
991
|
+
return data?.cause === 'stall' ? 'stalled' : 'waiting_followup';
|
|
992
|
+
case 'turn_finished':
|
|
993
|
+
return 'completed';
|
|
994
|
+
case 'turn_error':
|
|
995
|
+
return data?.aborted ? 'aborted' : 'failed';
|
|
996
|
+
default:
|
|
997
|
+
return 'waiting_followup';
|
|
998
|
+
}
|
|
764
999
|
}
|
|
765
1000
|
pruneTurnJournals(conversationId) {
|
|
766
1001
|
try {
|
|
@@ -817,6 +1052,14 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
817
1052
|
evidence: summary.evidence,
|
|
818
1053
|
browserState: summary.browserState,
|
|
819
1054
|
});
|
|
1055
|
+
if (this.currentQuerySession) {
|
|
1056
|
+
this.currentQuerySession = {
|
|
1057
|
+
...this.currentQuerySession,
|
|
1058
|
+
latestBoundary: summary,
|
|
1059
|
+
updatedAt: Date.now(),
|
|
1060
|
+
};
|
|
1061
|
+
this.saveCurrentQuerySession();
|
|
1062
|
+
}
|
|
820
1063
|
return summary;
|
|
821
1064
|
}
|
|
822
1065
|
deriveRepetitionSignals(continuity) {
|
|
@@ -1606,7 +1849,7 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1606
1849
|
}
|
|
1607
1850
|
this.resetStreamState();
|
|
1608
1851
|
}
|
|
1609
|
-
async sendMessage(content, conversationId) {
|
|
1852
|
+
async sendMessage(content, conversationId, options) {
|
|
1610
1853
|
if (!this.cerebrum)
|
|
1611
1854
|
throw new Error('Cerebrum not connected');
|
|
1612
1855
|
if (this.cerebellum && !this.cerebellum.isConnected()) {
|
|
@@ -1629,7 +1872,7 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1629
1872
|
let nextRetryContext = null;
|
|
1630
1873
|
// Track message IDs from failed attempts so they can be excluded from retries and cleaned up.
|
|
1631
1874
|
const failedAttemptMessageIds = [];
|
|
1632
|
-
const turnId = nanoid(10);
|
|
1875
|
+
const turnId = options?.turnId ?? nanoid(10);
|
|
1633
1876
|
const maxTotalAttempts = 1 + this.maxNudgeRetries + this.maxCompletionRetries;
|
|
1634
1877
|
let loopTerminated = false;
|
|
1635
1878
|
let nextRetryCause = null;
|
|
@@ -1647,11 +1890,29 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1647
1890
|
const isCurrentAttempt = () => this.abortController === abortController;
|
|
1648
1891
|
this.abortController = abortController;
|
|
1649
1892
|
this.currentAttemptCompletionState = completionState;
|
|
1893
|
+
const sessionSource = options?.source ?? 'local';
|
|
1650
1894
|
this.currentStreamTurn = {
|
|
1651
1895
|
turnId,
|
|
1652
1896
|
attempt: attemptNumber,
|
|
1653
1897
|
conversationId: convId,
|
|
1898
|
+
sessionId: turnId,
|
|
1899
|
+
source: sessionSource,
|
|
1654
1900
|
};
|
|
1901
|
+
const priorSession = this.currentQuerySession;
|
|
1902
|
+
this.currentQuerySession = this.createQuerySession(convId, turnId, attemptNumber, sessionSource, latestUserMessage, this.streamNudgeCount, completionRetryCount, priorSession);
|
|
1903
|
+
this.saveCurrentQuerySession();
|
|
1904
|
+
if (attemptNumber === 1 && sessionSource === 'channel' && options?.ingress) {
|
|
1905
|
+
this.recordSessionEvent(convId, turnId, 'channel_ingress', `Received channel message from ${options.ingress.senderName || options.ingress.senderId || 'unknown sender'}.`, {
|
|
1906
|
+
channelId: options.ingress.channelId,
|
|
1907
|
+
routeTo: options.ingress.routeTo,
|
|
1908
|
+
senderId: options.ingress.senderId,
|
|
1909
|
+
senderName: options.ingress.senderName,
|
|
1910
|
+
sessionId: options.ingress.sessionId,
|
|
1911
|
+
threadId: options.ingress.threadId,
|
|
1912
|
+
replyToId: options.ingress.replyToId,
|
|
1913
|
+
timestamp: options.ingress.timestamp,
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1655
1916
|
this.currentPartialContent = '';
|
|
1656
1917
|
this.currentLastContentKind = 'empty';
|
|
1657
1918
|
this.currentJournaledContentLength = 0;
|
|
@@ -1670,7 +1931,13 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1670
1931
|
stallRetryCount: this.streamNudgeCount,
|
|
1671
1932
|
completionRetryCount,
|
|
1672
1933
|
});
|
|
1673
|
-
this.emit({
|
|
1934
|
+
this.emit({
|
|
1935
|
+
type: 'message:cerebrum:start',
|
|
1936
|
+
conversationId: convId,
|
|
1937
|
+
turnId,
|
|
1938
|
+
sessionId: turnId,
|
|
1939
|
+
source: sessionSource,
|
|
1940
|
+
});
|
|
1674
1941
|
this.startStreamWatchdog(latestUserMessage);
|
|
1675
1942
|
let messages = this.conversations.getMessages(convId);
|
|
1676
1943
|
// On retry: exclude failed attempts' messages from history.
|
|
@@ -1774,6 +2041,7 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1774
2041
|
args: toolCall.args,
|
|
1775
2042
|
});
|
|
1776
2043
|
const isInternalTaskSignal = this.isInternalTaskSignalTool(normalizedToolName);
|
|
2044
|
+
const toolIngress = isInternalTaskSignal ? options?.ingress : undefined;
|
|
1777
2045
|
if (isInternalTaskSignal) {
|
|
1778
2046
|
completionState.internalToolCallCount++;
|
|
1779
2047
|
}
|
|
@@ -1795,8 +2063,11 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1795
2063
|
toolCall,
|
|
1796
2064
|
tools: allTools,
|
|
1797
2065
|
conversationId: convId,
|
|
1798
|
-
sessionKey:
|
|
2066
|
+
sessionKey: turnId,
|
|
1799
2067
|
scopeKey: convId,
|
|
2068
|
+
turnId,
|
|
2069
|
+
attempt: attemptNumber,
|
|
2070
|
+
ingress: toolIngress,
|
|
1800
2071
|
abortSignal: abortController.signal,
|
|
1801
2072
|
});
|
|
1802
2073
|
this.logStreamDebug('tool_callback_finished', this.buildToolDebugPayload(toolCall, result, toolName));
|
|
@@ -1839,6 +2110,16 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1839
2110
|
result.output += `\n[Cerebellum warning: ${failedChecks}]`;
|
|
1840
2111
|
}
|
|
1841
2112
|
if (verification) {
|
|
2113
|
+
result.metadata = {
|
|
2114
|
+
...(result.metadata ?? {}),
|
|
2115
|
+
verification: {
|
|
2116
|
+
passed: verification.passed,
|
|
2117
|
+
modelVerdict: verification.modelVerdict,
|
|
2118
|
+
failedChecks: verification.checks
|
|
2119
|
+
.filter((check) => !check.passed)
|
|
2120
|
+
.map((check) => check.description),
|
|
2121
|
+
},
|
|
2122
|
+
};
|
|
1842
2123
|
const vResult = {
|
|
1843
2124
|
passed: verification.passed,
|
|
1844
2125
|
checks: verification.checks,
|
|
@@ -1846,7 +2127,12 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1846
2127
|
toolCallId: toolCall.id,
|
|
1847
2128
|
toolName,
|
|
1848
2129
|
};
|
|
1849
|
-
this.emit({
|
|
2130
|
+
this.emit({
|
|
2131
|
+
type: 'verification:end',
|
|
2132
|
+
result: vResult,
|
|
2133
|
+
conversationId: convId,
|
|
2134
|
+
sessionId: turnId,
|
|
2135
|
+
});
|
|
1850
2136
|
}
|
|
1851
2137
|
}
|
|
1852
2138
|
catch {
|
|
@@ -1965,7 +2251,14 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
1965
2251
|
}
|
|
1966
2252
|
}
|
|
1967
2253
|
const cerebrumMessage = this.conversations.appendMessage(convId, 'cerebrum', displayContent, visibleToolCalls?.length ? { toolCalls: visibleToolCalls } : undefined);
|
|
1968
|
-
this.emit({
|
|
2254
|
+
this.emit({
|
|
2255
|
+
type: 'message:cerebrum:end',
|
|
2256
|
+
conversationId: convId,
|
|
2257
|
+
turnId,
|
|
2258
|
+
sessionId: turnId,
|
|
2259
|
+
source: sessionSource,
|
|
2260
|
+
message: cerebrumMessage,
|
|
2261
|
+
});
|
|
1969
2262
|
log.info('stream_finished', {
|
|
1970
2263
|
turnId,
|
|
1971
2264
|
attempt: attemptNumber,
|
|
@@ -2049,6 +2342,12 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2049
2342
|
const diagnosticMessage = this.conversations.appendMessage(convId, 'system', assessment.operatorMessage);
|
|
2050
2343
|
this.emit({ type: 'message:system', message: diagnosticMessage });
|
|
2051
2344
|
this.emitCompletionTrace('retry_failed', assessment.diagnosis, completionSignal, 'error');
|
|
2345
|
+
this.appendTurnJournalEntry('turn_error', assessment.diagnosis || 'Recovery returned stop.', {
|
|
2346
|
+
retryCause: 'completion',
|
|
2347
|
+
completionRetryCount,
|
|
2348
|
+
stallRetryCount: this.streamNudgeCount,
|
|
2349
|
+
error: assessment.diagnosis || 'Recovery returned stop.',
|
|
2350
|
+
});
|
|
2052
2351
|
this.emit({
|
|
2053
2352
|
type: 'error',
|
|
2054
2353
|
error: new Error(assessment.diagnosis ||
|
|
@@ -2076,6 +2375,12 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2076
2375
|
: '[System fallback] The turn ended repeatedly without a valid completion signal or final answer.');
|
|
2077
2376
|
this.emit({ type: 'message:system', message: diagnosticMessage });
|
|
2078
2377
|
this.emitCompletionTrace('retry_failed', `Completion retries exhausted after ${completionRetryCount}/${this.maxCompletionRetries}: ${assessment.diagnosis || completionFailure.message}`, completionSignal, 'error');
|
|
2378
|
+
this.appendTurnJournalEntry('turn_error', `Completion retries exhausted (${completionRetryCount}/${this.maxCompletionRetries}).`, {
|
|
2379
|
+
retryCause: 'completion',
|
|
2380
|
+
completionRetryCount,
|
|
2381
|
+
stallRetryCount: this.streamNudgeCount,
|
|
2382
|
+
error: assessment.diagnosis || 'Completion retries exhausted.',
|
|
2383
|
+
});
|
|
2079
2384
|
this.emit({
|
|
2080
2385
|
type: 'error',
|
|
2081
2386
|
error: new Error(assessment.diagnosis ||
|
|
@@ -2118,6 +2423,12 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2118
2423
|
stallRecovery.assessment.action === 'stop') {
|
|
2119
2424
|
const systemMessage = this.conversations.appendMessage(convId, 'system', stallRecovery.assessment.operatorMessage);
|
|
2120
2425
|
this.emit({ type: 'message:system', message: systemMessage });
|
|
2426
|
+
this.appendTurnJournalEntry('turn_error', stallRecovery.assessment.diagnosis || 'Stall recovery returned stop.', {
|
|
2427
|
+
retryCause: 'stall',
|
|
2428
|
+
stallRetryCount: this.streamNudgeCount,
|
|
2429
|
+
completionRetryCount,
|
|
2430
|
+
error: stallRecovery.assessment.diagnosis || 'Stall recovery returned stop.',
|
|
2431
|
+
});
|
|
2121
2432
|
this.emit({ type: 'error', error: new Error(stallRecovery.assessment.diagnosis) });
|
|
2122
2433
|
if (failedAttemptMessageIds.length > 0) {
|
|
2123
2434
|
this.conversations.deleteMessages(convId, failedAttemptMessageIds);
|
|
@@ -2129,6 +2440,13 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2129
2440
|
if (this.cerebellum && !this.cerebellum.isConnected() && abortController.signal.aborted) {
|
|
2130
2441
|
const err = new Error('Cerebellum disconnected during active response. Restart it with: docker compose up -d cerebellum');
|
|
2131
2442
|
log.error('Cerebellum disconnected mid-stream', { error: err.message });
|
|
2443
|
+
this.appendTurnJournalEntry('turn_error', err.message, {
|
|
2444
|
+
retryCause,
|
|
2445
|
+
stallRetryCount: this.streamNudgeCount,
|
|
2446
|
+
completionRetryCount,
|
|
2447
|
+
error: err.message,
|
|
2448
|
+
aborted: true,
|
|
2449
|
+
});
|
|
2132
2450
|
this.emit({ type: 'error', error: err });
|
|
2133
2451
|
if (failedAttemptMessageIds.length > 0) {
|
|
2134
2452
|
this.conversations.deleteMessages(convId, failedAttemptMessageIds);
|
|
@@ -2191,6 +2509,7 @@ export class Orchestrator extends TypedEventEmitter {
|
|
|
2191
2509
|
}
|
|
2192
2510
|
}
|
|
2193
2511
|
finally {
|
|
2512
|
+
this.currentQuerySession = null;
|
|
2194
2513
|
this.currentStreamTurn = null;
|
|
2195
2514
|
}
|
|
2196
2515
|
}
|