neoagent 2.5.2-beta.0 → 2.5.2-beta.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/flutter_app/lib/main_chat.dart +0 -3
- package/flutter_app/lib/main_shared.dart +40 -29
- package/package.json +1 -1
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +19016 -19016
- package/server/services/ai/engine.js +545 -46
- package/server/services/ai/tools.js +25 -15
|
@@ -112,6 +112,80 @@ function buildInitialRunMetadata(options = {}) {
|
|
|
112
112
|
return metadata;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
const MESSAGING_PROGRESS_FIRST_UPDATE_MS = 60 * 1000;
|
|
116
|
+
const MESSAGING_PROGRESS_REPEAT_MS = 90 * 1000;
|
|
117
|
+
const MESSAGING_PROGRESS_STALL_MS = 240 * 1000;
|
|
118
|
+
const MESSAGING_PROGRESS_TICK_MS = 15 * 1000;
|
|
119
|
+
|
|
120
|
+
function isoNow() {
|
|
121
|
+
return new Date().toISOString();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function timestampMs(value, fallback = 0) {
|
|
125
|
+
const resolved = value ? Date.parse(value) : NaN;
|
|
126
|
+
return Number.isFinite(resolved) ? resolved : fallback;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function formatElapsedDuration(durationMs) {
|
|
130
|
+
const totalSeconds = Math.max(1, Math.floor(Number(durationMs || 0) / 1000));
|
|
131
|
+
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
132
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
133
|
+
const seconds = totalSeconds % 60;
|
|
134
|
+
if (seconds === 0) return `${minutes}m`;
|
|
135
|
+
return `${minutes}m ${seconds}s`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function cloneInterimHistory(history = []) {
|
|
139
|
+
if (!Array.isArray(history)) return [];
|
|
140
|
+
return history.map((item) => ({
|
|
141
|
+
content: String(item?.content || '').trim(),
|
|
142
|
+
kind: normalizeInterimKind(item?.kind),
|
|
143
|
+
expectsReply: item?.expectsReply === true,
|
|
144
|
+
deferFollowUp: item?.deferFollowUp === true,
|
|
145
|
+
createdAt: item?.createdAt || isoNow(),
|
|
146
|
+
})).filter((item) => item.content);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function createInterimSignatureSet(history = [], platform = null) {
|
|
150
|
+
const signatures = new Set();
|
|
151
|
+
for (const item of cloneInterimHistory(history)) {
|
|
152
|
+
signatures.add(buildInterimSignature({
|
|
153
|
+
content: item.content,
|
|
154
|
+
kind: item.kind,
|
|
155
|
+
expectsReply: item.expectsReply === true,
|
|
156
|
+
platform,
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
return signatures;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function buildInitialProgressLedger({ startedAt, retryState = {} } = {}) {
|
|
163
|
+
const startedAtIso = startedAt || isoNow();
|
|
164
|
+
const interimHistory = cloneInterimHistory(retryState.interimHistory);
|
|
165
|
+
const lastInterimMessage = interimHistory[interimHistory.length - 1]?.content || '';
|
|
166
|
+
const lastVisibleAt = retryState.lastUserVisibleUpdateAt || (lastInterimMessage ? startedAtIso : null);
|
|
167
|
+
return {
|
|
168
|
+
currentStep: retryState.currentStep || null,
|
|
169
|
+
currentTool: retryState.currentTool || null,
|
|
170
|
+
currentStepStartedAt: retryState.currentStepStartedAt || null,
|
|
171
|
+
lastVerifiedProgressAt: retryState.lastVerifiedProgressAt || startedAtIso,
|
|
172
|
+
lastUserVisibleUpdateAt: lastVisibleAt,
|
|
173
|
+
lastFinalDeliveryAt: retryState.lastFinalDeliveryAt || null,
|
|
174
|
+
heartbeatCount: Number(retryState.heartbeatCount || 0),
|
|
175
|
+
stallNotifiedAt: retryState.stallNotifiedAt || null,
|
|
176
|
+
progressState: retryState.progressState || 'active',
|
|
177
|
+
currentPhase: retryState.currentPhase || 'idle',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function hasVisibleInterimActivity(runMeta) {
|
|
182
|
+
return Boolean(
|
|
183
|
+
runMeta?.lastInterimMessage
|
|
184
|
+
|| (Array.isArray(runMeta?.interimMessages) && runMeta.interimMessages.length > 0)
|
|
185
|
+
|| Number(runMeta?.progressLedger?.heartbeatCount || 0) > 0
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
115
189
|
function planningDepthForForceMode(forceMode) {
|
|
116
190
|
return forceMode === 'plan_execute' ? 'deep' : 'light';
|
|
117
191
|
}
|
|
@@ -555,6 +629,97 @@ class AgentEngine {
|
|
|
555
629
|
.run(JSON.stringify(next), runId);
|
|
556
630
|
}
|
|
557
631
|
|
|
632
|
+
buildProgressLedgerSnapshot(runMeta) {
|
|
633
|
+
if (!runMeta?.progressLedger) return null;
|
|
634
|
+
return {
|
|
635
|
+
currentStep: runMeta.progressLedger.currentStep || null,
|
|
636
|
+
currentTool: runMeta.progressLedger.currentTool || null,
|
|
637
|
+
currentStepStartedAt: runMeta.progressLedger.currentStepStartedAt || null,
|
|
638
|
+
lastVerifiedProgressAt: runMeta.progressLedger.lastVerifiedProgressAt || null,
|
|
639
|
+
lastUserVisibleUpdateAt: runMeta.progressLedger.lastUserVisibleUpdateAt || null,
|
|
640
|
+
lastFinalDeliveryAt: runMeta.progressLedger.lastFinalDeliveryAt || null,
|
|
641
|
+
heartbeatCount: Number(runMeta.progressLedger.heartbeatCount || 0),
|
|
642
|
+
stallNotifiedAt: runMeta.progressLedger.stallNotifiedAt || null,
|
|
643
|
+
progressState: runMeta.progressLedger.progressState || 'active',
|
|
644
|
+
currentPhase: runMeta.progressLedger.currentPhase || 'idle',
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
persistProgressLedger(runId) {
|
|
649
|
+
const runMeta = this.getRunMeta(runId);
|
|
650
|
+
if (!runMeta?.progressLedger) return;
|
|
651
|
+
this.persistRunMetadata(runId, {
|
|
652
|
+
progressLedger: this.buildProgressLedgerSnapshot(runMeta),
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
updateRunProgress(runId, patch = {}, options = {}) {
|
|
657
|
+
const runMeta = this.getRunMeta(runId);
|
|
658
|
+
if (!runMeta) return null;
|
|
659
|
+
if (!runMeta.progressLedger) {
|
|
660
|
+
runMeta.progressLedger = buildInitialProgressLedger({
|
|
661
|
+
startedAt: runMeta.startedAtIso || isoNow(),
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const previousState = runMeta.progressLedger.progressState || 'active';
|
|
666
|
+
runMeta.progressLedger = {
|
|
667
|
+
...runMeta.progressLedger,
|
|
668
|
+
...patch,
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
if (options.verified === true) {
|
|
672
|
+
runMeta.progressLedger.lastVerifiedProgressAt = options.timestamp || isoNow();
|
|
673
|
+
runMeta.progressLedger.progressState = 'active';
|
|
674
|
+
runMeta.progressLedger.stallNotifiedAt = null;
|
|
675
|
+
this.recordRunEvent(runMeta.userId, runId, 'progress_verified', {
|
|
676
|
+
phase: runMeta.progressLedger.currentPhase || 'idle',
|
|
677
|
+
currentStep: runMeta.progressLedger.currentStep || null,
|
|
678
|
+
currentTool: runMeta.progressLedger.currentTool || null,
|
|
679
|
+
}, { agentId: runMeta.agentId, stepId: options.stepId || null });
|
|
680
|
+
if (previousState === 'stalled') {
|
|
681
|
+
this.recordRunEvent(runMeta.userId, runId, 'progress_resumed', {
|
|
682
|
+
phase: runMeta.progressLedger.currentPhase || 'idle',
|
|
683
|
+
currentStep: runMeta.progressLedger.currentStep || null,
|
|
684
|
+
currentTool: runMeta.progressLedger.currentTool || null,
|
|
685
|
+
}, { agentId: runMeta.agentId, stepId: options.stepId || null });
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (options.persist !== false) {
|
|
690
|
+
this.persistProgressLedger(runId);
|
|
691
|
+
}
|
|
692
|
+
return runMeta.progressLedger;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
markRunVisibleProgress(runId, timestamp = isoNow()) {
|
|
696
|
+
const runMeta = this.getRunMeta(runId);
|
|
697
|
+
if (!runMeta) return null;
|
|
698
|
+
const ledger = this.updateRunProgress(runId, {
|
|
699
|
+
lastUserVisibleUpdateAt: timestamp,
|
|
700
|
+
}, {
|
|
701
|
+
persist: false,
|
|
702
|
+
});
|
|
703
|
+
this.persistProgressLedger(runId);
|
|
704
|
+
return ledger;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
markRunFinalDelivery(runId, content = '', timestamp = isoNow()) {
|
|
708
|
+
const runMeta = this.getRunMeta(runId);
|
|
709
|
+
if (!runMeta) return null;
|
|
710
|
+
runMeta.finalDeliverySent = true;
|
|
711
|
+
runMeta.lastSentMessage = String(content || '').trim() || runMeta.lastSentMessage || '';
|
|
712
|
+
const ledger = this.updateRunProgress(runId, {
|
|
713
|
+
lastUserVisibleUpdateAt: timestamp,
|
|
714
|
+
lastFinalDeliveryAt: timestamp,
|
|
715
|
+
progressState: 'complete',
|
|
716
|
+
}, {
|
|
717
|
+
persist: false,
|
|
718
|
+
});
|
|
719
|
+
this.persistProgressLedger(runId);
|
|
720
|
+
return ledger;
|
|
721
|
+
}
|
|
722
|
+
|
|
558
723
|
recordRunEvent(userId, runId, eventType, payload = {}, options = {}) {
|
|
559
724
|
try {
|
|
560
725
|
return recordRunEvent({
|
|
@@ -695,6 +860,7 @@ class AgentEngine {
|
|
|
695
860
|
createdAt,
|
|
696
861
|
});
|
|
697
862
|
runMeta.lastInterimMessage = normalizedContent;
|
|
863
|
+
this.markRunVisibleProgress(runId, createdAt);
|
|
698
864
|
|
|
699
865
|
this.emit(userId, 'run:assistant_interim', {
|
|
700
866
|
runId,
|
|
@@ -722,6 +888,7 @@ class AgentEngine {
|
|
|
722
888
|
content: normalizedContent,
|
|
723
889
|
createdAt,
|
|
724
890
|
},
|
|
891
|
+
progressLedger: this.buildProgressLedgerSnapshot(runMeta),
|
|
725
892
|
terminalInterim: terminalInterim
|
|
726
893
|
? { kind: normalizedKind, content: normalizedContent, createdAt }
|
|
727
894
|
: null,
|
|
@@ -1584,6 +1751,45 @@ class AgentEngine {
|
|
|
1584
1751
|
};
|
|
1585
1752
|
}
|
|
1586
1753
|
|
|
1754
|
+
enqueueSystemSteering(runId, content, metadata = {}) {
|
|
1755
|
+
const runMeta = this.getRunMeta(runId);
|
|
1756
|
+
const trimmed = typeof content === 'string' ? content.trim() : '';
|
|
1757
|
+
if (!runMeta || runMeta.aborted || !trimmed) return null;
|
|
1758
|
+
if (!Array.isArray(runMeta.systemSteeringQueue)) {
|
|
1759
|
+
runMeta.systemSteeringQueue = [];
|
|
1760
|
+
}
|
|
1761
|
+
const signature = JSON.stringify({
|
|
1762
|
+
content: trimmed,
|
|
1763
|
+
reason: metadata.reason || '',
|
|
1764
|
+
});
|
|
1765
|
+
if (runMeta.systemSteeringQueue.some((item) => item.signature === signature)) {
|
|
1766
|
+
return null;
|
|
1767
|
+
}
|
|
1768
|
+
const item = {
|
|
1769
|
+
id: uuidv4(),
|
|
1770
|
+
content: trimmed,
|
|
1771
|
+
metadata,
|
|
1772
|
+
signature,
|
|
1773
|
+
createdAt: isoNow(),
|
|
1774
|
+
};
|
|
1775
|
+
runMeta.systemSteeringQueue.push(item);
|
|
1776
|
+
return item;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
applyQueuedSystemSteering(runId, messages) {
|
|
1780
|
+
const runMeta = this.getRunMeta(runId);
|
|
1781
|
+
if (!runMeta?.systemSteeringQueue?.length) {
|
|
1782
|
+
return { messages, appliedCount: 0 };
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
const queued = runMeta.systemSteeringQueue.splice(0, runMeta.systemSteeringQueue.length);
|
|
1786
|
+
for (const entry of queued) {
|
|
1787
|
+
messages.push({ role: 'system', content: entry.content });
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
return { messages, appliedCount: queued.length };
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1587
1793
|
applyQueuedSteering(runId, messages, { userId, conversationId }) {
|
|
1588
1794
|
const runMeta = this.getRunMeta(runId);
|
|
1589
1795
|
if (!runMeta?.steeringQueue?.length) {
|
|
@@ -1619,6 +1825,242 @@ class AgentEngine {
|
|
|
1619
1825
|
return { messages, appliedCount: queued.length };
|
|
1620
1826
|
}
|
|
1621
1827
|
|
|
1828
|
+
buildMessagingHeartbeatText(runMeta, options = {}) {
|
|
1829
|
+
const stalled = options.stalled === true;
|
|
1830
|
+
const fallbackStartedAtMs = Number.isFinite(runMeta?.startedAt) ? runMeta.startedAt : Date.now();
|
|
1831
|
+
const startedAtMs = timestampMs(
|
|
1832
|
+
runMeta?.progressLedger?.currentStepStartedAt,
|
|
1833
|
+
fallbackStartedAtMs,
|
|
1834
|
+
);
|
|
1835
|
+
const elapsed = formatElapsedDuration(Date.now() - startedAtMs);
|
|
1836
|
+
const currentTool = String(runMeta?.progressLedger?.currentTool || '').trim();
|
|
1837
|
+
if (currentTool) {
|
|
1838
|
+
return stalled
|
|
1839
|
+
? `Still working on ${currentTool}. This run has not made verified progress for ${elapsed}.`
|
|
1840
|
+
: `Still working on ${currentTool}. ${elapsed} elapsed so far.`;
|
|
1841
|
+
}
|
|
1842
|
+
return stalled
|
|
1843
|
+
? `Still working on this. This run has not made verified progress for ${elapsed}.`
|
|
1844
|
+
: `Still working on this. ${elapsed} elapsed so far.`;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
async sendRuntimeMessagingHeartbeat(runId, options = {}) {
|
|
1848
|
+
const runMeta = this.getRunMeta(runId);
|
|
1849
|
+
if (!runMeta || runMeta.aborted) return { sent: false, skipped: true };
|
|
1850
|
+
if (runMeta.triggerSource !== 'messaging' || !runMeta.messagingContext?.platform || !runMeta.messagingContext?.chatId) {
|
|
1851
|
+
return { sent: false, skipped: true };
|
|
1852
|
+
}
|
|
1853
|
+
if (!this.messagingManager) {
|
|
1854
|
+
return { sent: false, skipped: true };
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
const createdAt = isoNow();
|
|
1858
|
+
const content = this.buildMessagingHeartbeatText(runMeta, options);
|
|
1859
|
+
await this.messagingManager.sendMessage(
|
|
1860
|
+
runMeta.userId,
|
|
1861
|
+
runMeta.messagingContext.platform,
|
|
1862
|
+
runMeta.messagingContext.chatId,
|
|
1863
|
+
content,
|
|
1864
|
+
{
|
|
1865
|
+
agentId: runMeta.agentId,
|
|
1866
|
+
runId,
|
|
1867
|
+
persistConversation: true,
|
|
1868
|
+
metadata: {
|
|
1869
|
+
interim: true,
|
|
1870
|
+
interim_kind: options.stalled === true ? 'blocker' : 'progress',
|
|
1871
|
+
runtime_heartbeat: true,
|
|
1872
|
+
expects_reply: false,
|
|
1873
|
+
},
|
|
1874
|
+
deliveryKind: 'interim',
|
|
1875
|
+
},
|
|
1876
|
+
);
|
|
1877
|
+
|
|
1878
|
+
runMeta.lastInterimMessage = content;
|
|
1879
|
+
if (!Array.isArray(runMeta.interimMessages)) {
|
|
1880
|
+
runMeta.interimMessages = [];
|
|
1881
|
+
}
|
|
1882
|
+
runMeta.interimMessages.push({
|
|
1883
|
+
content,
|
|
1884
|
+
kind: options.stalled === true ? 'blocker' : 'progress',
|
|
1885
|
+
expectsReply: false,
|
|
1886
|
+
deferFollowUp: false,
|
|
1887
|
+
createdAt,
|
|
1888
|
+
});
|
|
1889
|
+
const heartbeatCount = Number(runMeta.progressLedger?.heartbeatCount || 0) + 1;
|
|
1890
|
+
this.updateRunProgress(runId, {
|
|
1891
|
+
heartbeatCount,
|
|
1892
|
+
lastUserVisibleUpdateAt: createdAt,
|
|
1893
|
+
});
|
|
1894
|
+
this.recordRunEvent(runMeta.userId, runId, 'progress_heartbeat_sent', {
|
|
1895
|
+
stalled: options.stalled === true,
|
|
1896
|
+
currentTool: runMeta.progressLedger?.currentTool || null,
|
|
1897
|
+
currentStep: runMeta.progressLedger?.currentStep || null,
|
|
1898
|
+
}, { agentId: runMeta.agentId });
|
|
1899
|
+
this.enqueueSystemSteering(
|
|
1900
|
+
runId,
|
|
1901
|
+
'A runtime-generated progress update was already sent while the run was blocked. Do not repeat that same status. When control returns, either keep working silently, send a materially new update, or finish with the actual result.',
|
|
1902
|
+
{ reason: 'runtime_heartbeat' },
|
|
1903
|
+
);
|
|
1904
|
+
return { sent: true, content };
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
shouldSendMessagingFinalFallback(runMeta, content, platform = null) {
|
|
1908
|
+
const cleanedContent = normalizeOutgoingMessage(content || '', platform, {
|
|
1909
|
+
collapseWhitespace: false,
|
|
1910
|
+
});
|
|
1911
|
+
const lastFinalDeliveryMessage = normalizeOutgoingMessage(
|
|
1912
|
+
runMeta?.lastSentMessage
|
|
1913
|
+
|| (Array.isArray(runMeta?.sentMessages) ? runMeta.sentMessages[runMeta.sentMessages.length - 1] : '')
|
|
1914
|
+
|| '',
|
|
1915
|
+
platform,
|
|
1916
|
+
);
|
|
1917
|
+
return Boolean(
|
|
1918
|
+
cleanedContent
|
|
1919
|
+
&& !runMeta?.terminalInterim
|
|
1920
|
+
&& runMeta?.explicitMessageSent !== true
|
|
1921
|
+
&& runMeta?.finalDeliverySent !== true
|
|
1922
|
+
&& !lastFinalDeliveryMessage
|
|
1923
|
+
);
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
async deliverMessagingFinalFallback({
|
|
1927
|
+
runId,
|
|
1928
|
+
userId,
|
|
1929
|
+
agentId,
|
|
1930
|
+
platform,
|
|
1931
|
+
chatId,
|
|
1932
|
+
content,
|
|
1933
|
+
}) {
|
|
1934
|
+
const runMeta = this.getRunMeta(runId);
|
|
1935
|
+
if (!runMeta || !this.messagingManager) return { sent: false, skipped: true };
|
|
1936
|
+
const cleanedContent = normalizeOutgoingMessage(content || '', platform, {
|
|
1937
|
+
collapseWhitespace: false,
|
|
1938
|
+
});
|
|
1939
|
+
if (!this.shouldSendMessagingFinalFallback(runMeta, cleanedContent, platform)) {
|
|
1940
|
+
return { sent: false, skipped: true };
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
const chunks = splitOutgoingMessageForPlatform(platform, cleanedContent);
|
|
1944
|
+
console.info(
|
|
1945
|
+
`[Run ${shortenRunId(runId)}] messaging_fallback chunks=${chunks.length} to=${summarizeForLog(chatId, 80)}`
|
|
1946
|
+
);
|
|
1947
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
1948
|
+
if (i > 0) {
|
|
1949
|
+
const delay = Math.max(1000, Math.min(chunks[i].length * 30, 4000));
|
|
1950
|
+
await this.messagingManager.sendTyping(userId, platform, chatId, true, { agentId }).catch(() => {});
|
|
1951
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1952
|
+
}
|
|
1953
|
+
await this.messagingManager.sendMessage(userId, platform, chatId, chunks[i], { runId, agentId }).catch((err) =>
|
|
1954
|
+
console.error('[Engine] Auto-reply fallback failed:', err.message)
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
runMeta.lastSentMessage = chunks[chunks.length - 1] || cleanedContent;
|
|
1959
|
+
runMeta.sentMessages = Array.isArray(runMeta.sentMessages)
|
|
1960
|
+
? [...runMeta.sentMessages, ...chunks]
|
|
1961
|
+
: chunks.slice();
|
|
1962
|
+
this.markRunFinalDelivery(runId, runMeta.lastSentMessage);
|
|
1963
|
+
return { sent: true, chunks };
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
async tickMessagingProgressSupervisor(runId) {
|
|
1967
|
+
const runMeta = this.getRunMeta(runId);
|
|
1968
|
+
if (!runMeta || runMeta.aborted || runMeta.triggerSource !== 'messaging') {
|
|
1969
|
+
return { sent: false, skipped: true };
|
|
1970
|
+
}
|
|
1971
|
+
if (runMeta.finalDeliverySent === true || runMeta.terminalInterim) {
|
|
1972
|
+
return { sent: false, skipped: true };
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
const now = Date.now();
|
|
1976
|
+
const ledger = runMeta.progressLedger || buildInitialProgressLedger({
|
|
1977
|
+
startedAt: runMeta.startedAtIso || isoNow(),
|
|
1978
|
+
});
|
|
1979
|
+
runMeta.progressLedger = ledger;
|
|
1980
|
+
const startedAtMs = Number.isFinite(runMeta.startedAt) ? runMeta.startedAt : now;
|
|
1981
|
+
|
|
1982
|
+
const lastVerifiedAtMs = timestampMs(ledger.lastVerifiedProgressAt, startedAtMs);
|
|
1983
|
+
const lastVisibleAtMs = timestampMs(ledger.lastUserVisibleUpdateAt, 0);
|
|
1984
|
+
const heartbeatThresholdMs = lastVisibleAtMs > 0
|
|
1985
|
+
? MESSAGING_PROGRESS_REPEAT_MS
|
|
1986
|
+
: MESSAGING_PROGRESS_FIRST_UPDATE_MS;
|
|
1987
|
+
const comparisonVisibleAtMs = lastVisibleAtMs > 0 ? lastVisibleAtMs : startedAtMs;
|
|
1988
|
+
const stalled = (now - lastVerifiedAtMs) >= MESSAGING_PROGRESS_STALL_MS;
|
|
1989
|
+
|
|
1990
|
+
if (stalled && !ledger.stallNotifiedAt) {
|
|
1991
|
+
this.updateRunProgress(runId, {
|
|
1992
|
+
stallNotifiedAt: isoNow(),
|
|
1993
|
+
progressState: 'stalled',
|
|
1994
|
+
});
|
|
1995
|
+
this.recordRunEvent(runMeta.userId, runId, 'progress_stalled', {
|
|
1996
|
+
currentTool: ledger.currentTool || null,
|
|
1997
|
+
currentStep: ledger.currentStep || null,
|
|
1998
|
+
phase: ledger.currentPhase || 'idle',
|
|
1999
|
+
}, { agentId: runMeta.agentId });
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
if ((now - comparisonVisibleAtMs) < heartbeatThresholdMs) {
|
|
2003
|
+
return { sent: false, skipped: true };
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
if (ledger.currentPhase === 'tool' && ledger.currentStepStartedAt) {
|
|
2007
|
+
return this.sendRuntimeMessagingHeartbeat(runId, { stalled });
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
if (ledger.currentPhase !== 'idle') {
|
|
2011
|
+
return { sent: false, skipped: true };
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
const lastSupervisorNudgeAtMs = timestampMs(runMeta.lastSupervisorNudgeAt, 0);
|
|
2015
|
+
if (lastSupervisorNudgeAtMs > 0 && (now - lastSupervisorNudgeAtMs) < heartbeatThresholdMs) {
|
|
2016
|
+
return { sent: false, skipped: true };
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
const nudge = stalled
|
|
2020
|
+
? 'The messaging user has only seen progress updates so far, and the run now appears stalled. Decide explicitly whether to continue, send one concise blocker update, or finish with the final answer. Do not leave the run with only an interim status.'
|
|
2021
|
+
: 'The messaging user has not received a final answer yet. Decide explicitly whether to keep working, send one concise progress update, or finish with the final answer. Do not stop with only an interim status.';
|
|
2022
|
+
const queued = this.enqueueSystemSteering(runId, nudge, {
|
|
2023
|
+
reason: stalled ? 'stalled_progress_check' : 'progress_check',
|
|
2024
|
+
});
|
|
2025
|
+
if (!queued) {
|
|
2026
|
+
return { sent: false, skipped: true };
|
|
2027
|
+
}
|
|
2028
|
+
runMeta.lastSupervisorNudgeAt = isoNow();
|
|
2029
|
+
this.updateRunProgress(runId, {
|
|
2030
|
+
lastUserVisibleUpdateAt: ledger.lastUserVisibleUpdateAt || null,
|
|
2031
|
+
});
|
|
2032
|
+
return { sent: false, queued: true };
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
startMessagingProgressSupervisor(runId) {
|
|
2036
|
+
const runMeta = this.getRunMeta(runId);
|
|
2037
|
+
if (!runMeta || runMeta.triggerSource !== 'messaging' || !runMeta.messagingContext?.platform || !runMeta.messagingContext?.chatId) {
|
|
2038
|
+
return false;
|
|
2039
|
+
}
|
|
2040
|
+
if (runMeta.messagingProgressSupervisor?.timer) {
|
|
2041
|
+
return true;
|
|
2042
|
+
}
|
|
2043
|
+
const timer = setInterval(() => {
|
|
2044
|
+
this.tickMessagingProgressSupervisor(runId).catch((error) => {
|
|
2045
|
+
console.warn('[Engine] Messaging progress supervisor failed:', error?.message || error);
|
|
2046
|
+
});
|
|
2047
|
+
}, MESSAGING_PROGRESS_TICK_MS);
|
|
2048
|
+
timer.unref?.();
|
|
2049
|
+
runMeta.messagingProgressSupervisor = { timer };
|
|
2050
|
+
return true;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
stopMessagingProgressSupervisor(runId) {
|
|
2054
|
+
const runMeta = this.getRunMeta(runId);
|
|
2055
|
+
const timer = runMeta?.messagingProgressSupervisor?.timer || null;
|
|
2056
|
+
if (timer) {
|
|
2057
|
+
clearInterval(timer);
|
|
2058
|
+
}
|
|
2059
|
+
if (runMeta?.messagingProgressSupervisor) {
|
|
2060
|
+
runMeta.messagingProgressSupervisor = null;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1622
2064
|
isRunStopped(runId) {
|
|
1623
2065
|
return this.getRunMeta(runId)?.aborted === true;
|
|
1624
2066
|
}
|
|
@@ -1871,8 +2313,15 @@ class AgentEngine {
|
|
|
1871
2313
|
);
|
|
1872
2314
|
|
|
1873
2315
|
const retryMessagingState = options.messagingRetryState || {};
|
|
1874
|
-
const
|
|
2316
|
+
const carriedFinalMessage = String(retryMessagingState.lastFinalMessage || '').trim();
|
|
1875
2317
|
const carriedExplicitMessageSent = retryMessagingState.explicitMessageSent === true;
|
|
2318
|
+
const carriedInterimHistory = cloneInterimHistory(retryMessagingState.interimHistory);
|
|
2319
|
+
const carriedLastInterimMessage = carriedInterimHistory[carriedInterimHistory.length - 1]?.content || '';
|
|
2320
|
+
const startedAtIso = isoNow();
|
|
2321
|
+
const progressLedger = buildInitialProgressLedger({
|
|
2322
|
+
startedAt: startedAtIso,
|
|
2323
|
+
retryState: retryMessagingState,
|
|
2324
|
+
});
|
|
1876
2325
|
|
|
1877
2326
|
this.activeRuns.set(runId, {
|
|
1878
2327
|
userId,
|
|
@@ -1882,23 +2331,39 @@ class AgentEngine {
|
|
|
1882
2331
|
messagingSent: false,
|
|
1883
2332
|
noResponse: false,
|
|
1884
2333
|
explicitMessageSent: carriedExplicitMessageSent,
|
|
1885
|
-
|
|
2334
|
+
finalDeliverySent: carriedExplicitMessageSent,
|
|
2335
|
+
lastSentMessage: carriedExplicitMessageSent ? carriedFinalMessage : '',
|
|
1886
2336
|
sentMessages: [],
|
|
1887
2337
|
widgetSnapshotSaved: false,
|
|
1888
2338
|
triggerType,
|
|
1889
2339
|
triggerSource,
|
|
1890
2340
|
startedAt: Date.now(),
|
|
2341
|
+
startedAtIso,
|
|
1891
2342
|
lastToolName: null,
|
|
1892
2343
|
lastToolTarget: null,
|
|
1893
|
-
lastInterimMessage: carriedExplicitMessageSent ? '' :
|
|
1894
|
-
interimMessages: [],
|
|
1895
|
-
interimSignatures:
|
|
2344
|
+
lastInterimMessage: carriedExplicitMessageSent ? '' : carriedLastInterimMessage,
|
|
2345
|
+
interimMessages: carriedExplicitMessageSent ? [] : carriedInterimHistory,
|
|
2346
|
+
interimSignatures: carriedExplicitMessageSent
|
|
2347
|
+
? new Set()
|
|
2348
|
+
: createInterimSignatureSet(carriedInterimHistory, options.source || null),
|
|
1896
2349
|
terminalInterim: null,
|
|
1897
2350
|
voiceSessionId: options.voiceSessionId || null,
|
|
1898
2351
|
steeringQueue: [],
|
|
2352
|
+
systemSteeringQueue: [],
|
|
1899
2353
|
toolPids: new Set(),
|
|
1900
2354
|
repetitionGuard: new ToolRepetitionGuard(),
|
|
2355
|
+
messagingContext: triggerSource === 'messaging'
|
|
2356
|
+
? {
|
|
2357
|
+
platform: options.source || null,
|
|
2358
|
+
chatId: options.chatId || null,
|
|
2359
|
+
}
|
|
2360
|
+
: null,
|
|
2361
|
+
progressLedger,
|
|
1901
2362
|
});
|
|
2363
|
+
this.persistRunMetadata(runId, {
|
|
2364
|
+
progressLedger,
|
|
2365
|
+
});
|
|
2366
|
+
this.startMessagingProgressSupervisor(runId);
|
|
1902
2367
|
this.emit(userId, 'run:start', { runId, agentId, title: runTitle, model, triggerType, triggerSource });
|
|
1903
2368
|
this.recordRunEvent(userId, runId, 'run_started', {
|
|
1904
2369
|
title: runTitle,
|
|
@@ -2278,12 +2743,22 @@ class AgentEngine {
|
|
|
2278
2743
|
if (this.isRunStopped(runId)) break;
|
|
2279
2744
|
iteration++;
|
|
2280
2745
|
|
|
2746
|
+
const systemSteeringAtLoopStart = this.applyQueuedSystemSteering(runId, messages);
|
|
2747
|
+
messages = systemSteeringAtLoopStart.messages;
|
|
2281
2748
|
const steeringAtLoopStart = this.applyQueuedSteering(runId, messages, {
|
|
2282
2749
|
userId,
|
|
2283
2750
|
conversationId
|
|
2284
2751
|
});
|
|
2285
2752
|
messages = steeringAtLoopStart.messages;
|
|
2286
2753
|
messages = sanitizeConversationMessages(messages);
|
|
2754
|
+
this.updateRunProgress(runId, {
|
|
2755
|
+
currentPhase: 'model',
|
|
2756
|
+
currentStep: `model:${iteration}`,
|
|
2757
|
+
currentTool: null,
|
|
2758
|
+
currentStepStartedAt: isoNow(),
|
|
2759
|
+
}, {
|
|
2760
|
+
verified: true,
|
|
2761
|
+
});
|
|
2287
2762
|
|
|
2288
2763
|
let metrics = this.estimatePromptMetrics(messages, tools);
|
|
2289
2764
|
const contextWindow = provider.getContextWindow(model);
|
|
@@ -2444,6 +2919,21 @@ class AgentEngine {
|
|
|
2444
2919
|
}
|
|
2445
2920
|
|
|
2446
2921
|
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
2922
|
+
this.updateRunProgress(runId, {
|
|
2923
|
+
currentPhase: 'idle',
|
|
2924
|
+
currentStep: null,
|
|
2925
|
+
currentTool: null,
|
|
2926
|
+
currentStepStartedAt: null,
|
|
2927
|
+
}, {
|
|
2928
|
+
verified: true,
|
|
2929
|
+
});
|
|
2930
|
+
const systemSteeringAfterResponse = this.applyQueuedSystemSteering(runId, messages);
|
|
2931
|
+
messages = systemSteeringAfterResponse.messages;
|
|
2932
|
+
if (systemSteeringAfterResponse.appliedCount > 0) {
|
|
2933
|
+
iteration = Math.max(0, iteration - 1);
|
|
2934
|
+
lastContent = '';
|
|
2935
|
+
continue;
|
|
2936
|
+
}
|
|
2447
2937
|
const steeringAfterResponse = this.applyQueuedSteering(runId, messages, {
|
|
2448
2938
|
userId,
|
|
2449
2939
|
conversationId
|
|
@@ -2470,11 +2960,13 @@ class AgentEngine {
|
|
|
2470
2960
|
&& this.activeRuns.get(runId)?.noResponse !== true
|
|
2471
2961
|
&& options.deliveryState?.noResponse !== true
|
|
2472
2962
|
);
|
|
2963
|
+
const visibleInterimActivity = hasVisibleInterimActivity(this.activeRuns.get(runId));
|
|
2473
2964
|
const fallbackStatus = (
|
|
2474
2965
|
proactiveRunNeedsDecision
|
|
2475
2966
|
|| toolExecutions.length > 0
|
|
2476
2967
|
|| failedStepCount > 0
|
|
2477
2968
|
|| messagingSent
|
|
2969
|
+
|| visibleInterimActivity
|
|
2478
2970
|
) ? 'continue' : 'complete';
|
|
2479
2971
|
const loopState = await runWithModelFallback('loop decision', () => this.decideLoopState({
|
|
2480
2972
|
provider,
|
|
@@ -2685,6 +3177,15 @@ class AgentEngine {
|
|
|
2685
3177
|
|
|
2686
3178
|
db.prepare('INSERT INTO agent_steps (id, run_id, step_index, type, description, status, tool_name, tool_input, started_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime(\'now\'))')
|
|
2687
3179
|
.run(stepId, runId, stepIndex, this.getStepType(toolName), `${toolName}: ${JSON.stringify(toolArgs).slice(0, 200)} `, 'running', toolName, JSON.stringify(toolArgs));
|
|
3180
|
+
this.updateRunProgress(runId, {
|
|
3181
|
+
currentPhase: 'tool',
|
|
3182
|
+
currentStep: stepId,
|
|
3183
|
+
currentTool: toolName,
|
|
3184
|
+
currentStepStartedAt: isoNow(),
|
|
3185
|
+
}, {
|
|
3186
|
+
verified: true,
|
|
3187
|
+
stepId,
|
|
3188
|
+
});
|
|
2688
3189
|
|
|
2689
3190
|
this.emit(userId, 'run:tool_start', {
|
|
2690
3191
|
runId, stepId, stepIndex, toolName, toolArgs,
|
|
@@ -2885,6 +3386,16 @@ class AgentEngine {
|
|
|
2885
3386
|
.run(conversationId, 'tool', toolMessage.content, toolCall.id, toolName);
|
|
2886
3387
|
}
|
|
2887
3388
|
|
|
3389
|
+
this.updateRunProgress(runId, {
|
|
3390
|
+
currentPhase: 'idle',
|
|
3391
|
+
currentStep: null,
|
|
3392
|
+
currentTool: null,
|
|
3393
|
+
currentStepStartedAt: null,
|
|
3394
|
+
}, {
|
|
3395
|
+
verified: true,
|
|
3396
|
+
stepId,
|
|
3397
|
+
});
|
|
3398
|
+
|
|
2888
3399
|
const runMeta = this.activeRuns.get(runId);
|
|
2889
3400
|
if (runMeta) {
|
|
2890
3401
|
runMeta.lastToolName = toolName;
|
|
@@ -2916,6 +3427,7 @@ class AgentEngine {
|
|
|
2916
3427
|
console.warn(
|
|
2917
3428
|
`[Run ${shortenRunId(runId)}] stopped trigger=${triggerSource} steps=${stepIndex} tokens=${totalTokens}`
|
|
2918
3429
|
);
|
|
3430
|
+
this.stopMessagingProgressSupervisor(runId);
|
|
2919
3431
|
this.activeRuns.delete(runId);
|
|
2920
3432
|
this.emit(userId, 'run:stopped', { runId, triggerSource });
|
|
2921
3433
|
this.recordRunEvent(userId, runId, 'run_stopped', {
|
|
@@ -2991,9 +3503,8 @@ class AgentEngine {
|
|
|
2991
3503
|
let finalResponseText = messagingSent
|
|
2992
3504
|
? (sentMessageText || (normalizedLastContent ? lastContent.trim() : ''))
|
|
2993
3505
|
: (normalizedLastContent ? lastContent.trim() : sentMessageText);
|
|
2994
|
-
const
|
|
3506
|
+
const lastFinalDeliveryMessage = normalizeOutgoingMessage(
|
|
2995
3507
|
runMeta?.lastSentMessage
|
|
2996
|
-
|| runMeta?.lastInterimMessage
|
|
2997
3508
|
|| (Array.isArray(runMeta?.sentMessages) ? runMeta.sentMessages[runMeta.sentMessages.length - 1] : '')
|
|
2998
3509
|
|| '',
|
|
2999
3510
|
options?.source || null
|
|
@@ -3130,39 +3641,19 @@ class AgentEngine {
|
|
|
3130
3641
|
skipPersistence: options.skipRunContextPersistence === true
|
|
3131
3642
|
});
|
|
3132
3643
|
|
|
3133
|
-
// Fallback: if this was a messaging-triggered run and no
|
|
3134
|
-
//
|
|
3135
|
-
//
|
|
3136
|
-
// must be sent explicitly via send_message.
|
|
3644
|
+
// Fallback: if this was a messaging-triggered run and no final delivery
|
|
3645
|
+
// was already sent in this run, auto-send the final assistant text.
|
|
3646
|
+
// Interim progress updates do not suppress this final delivery.
|
|
3137
3647
|
if (triggerSource === 'messaging' && options.source && options.chatId) {
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
);
|
|
3148
|
-
if (shouldSendFallback) {
|
|
3149
|
-
const manager = this.messagingManager;
|
|
3150
|
-
if (manager) {
|
|
3151
|
-
const chunks = splitOutgoingMessageForPlatform(options.source, cleanedContent);
|
|
3152
|
-
console.info(
|
|
3153
|
-
`[Run ${shortenRunId(runId)}] messaging_fallback chunks=${chunks.length} to=${summarizeForLog(options.chatId, 80)}`
|
|
3154
|
-
);
|
|
3155
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
3156
|
-
if (i > 0) {
|
|
3157
|
-
const delay = Math.max(1000, Math.min(chunks[i].length * 30, 4000));
|
|
3158
|
-
await manager.sendTyping(userId, options.source, options.chatId, true, { agentId }).catch(() => { });
|
|
3159
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3160
|
-
}
|
|
3161
|
-
await manager.sendMessage(userId, options.source, options.chatId, chunks[i], { runId, agentId }).catch((err) =>
|
|
3162
|
-
console.error('[Engine] Auto-reply fallback failed:', err.message)
|
|
3163
|
-
);
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3648
|
+
if (this.shouldSendMessagingFinalFallback(runMeta, lastContent || '', options.source) && !lastFinalDeliveryMessage) {
|
|
3649
|
+
await this.deliverMessagingFinalFallback({
|
|
3650
|
+
runId,
|
|
3651
|
+
userId,
|
|
3652
|
+
agentId,
|
|
3653
|
+
platform: options.source,
|
|
3654
|
+
chatId: options.chatId,
|
|
3655
|
+
content: lastContent || '',
|
|
3656
|
+
});
|
|
3166
3657
|
}
|
|
3167
3658
|
}
|
|
3168
3659
|
|
|
@@ -3170,6 +3661,7 @@ class AgentEngine {
|
|
|
3170
3661
|
`[Run ${shortenRunId(runId)}] completed trigger=${triggerSource} steps=${stepIndex} tokens=${totalTokens} durationMs=${runMeta?.startedAt ? Date.now() - runMeta.startedAt : 0} finalResponse=${finalResponseText ? 'yes' : 'no'} sentMessages=${runMeta?.sentMessages?.length || 0}`
|
|
3171
3662
|
);
|
|
3172
3663
|
this.cleanupSubagentsForRun(runId, { cancelRunning: true });
|
|
3664
|
+
this.stopMessagingProgressSupervisor(runId);
|
|
3173
3665
|
this.activeRuns.delete(runId);
|
|
3174
3666
|
this.emit(userId, 'run:complete', {
|
|
3175
3667
|
runId,
|
|
@@ -3230,6 +3722,7 @@ class AgentEngine {
|
|
|
3230
3722
|
`[Run ${shortenRunId(runId)}] stopped trigger=${triggerSource} steps=${stepIndex} tokens=${totalTokens}`
|
|
3231
3723
|
);
|
|
3232
3724
|
this.cleanupSubagentsForRun(runId, { cancelRunning: true });
|
|
3725
|
+
this.stopMessagingProgressSupervisor(runId);
|
|
3233
3726
|
this.activeRuns.delete(runId);
|
|
3234
3727
|
this.emit(userId, 'run:stopped', { runId, triggerSource });
|
|
3235
3728
|
this.recordRunEvent(userId, runId, 'run_stopped', {
|
|
@@ -3267,18 +3760,13 @@ class AgentEngine {
|
|
|
3267
3760
|
|| runMeta?.messagingSent === true
|
|
3268
3761
|
),
|
|
3269
3762
|
});
|
|
3270
|
-
const lastVisibleMessage = normalizeOutgoingMessage(
|
|
3271
|
-
runMeta?.lastSentMessage
|
|
3272
|
-
|| runMeta?.lastInterimMessage
|
|
3273
|
-
|| '',
|
|
3274
|
-
options?.source || null
|
|
3275
|
-
);
|
|
3276
3763
|
db.prepare('UPDATE agent_runs SET status = ?, error = ?, updated_at = datetime(\'now\') WHERE id = ?')
|
|
3277
3764
|
.run('retrying', err.message, runId);
|
|
3278
3765
|
console.warn(
|
|
3279
3766
|
`[Run ${shortenRunId(runId)}] retrying_messaging_attempt=${retryCount + 1} reason=${summarizeForLog(err.message, 140)}`
|
|
3280
3767
|
);
|
|
3281
3768
|
this.cleanupSubagentsForRun(runId, { cancelRunning: true });
|
|
3769
|
+
this.stopMessagingProgressSupervisor(runId);
|
|
3282
3770
|
this.activeRuns.delete(runId);
|
|
3283
3771
|
this.emit(userId, 'run:interim', {
|
|
3284
3772
|
runId,
|
|
@@ -3290,8 +3778,17 @@ class AgentEngine {
|
|
|
3290
3778
|
...options,
|
|
3291
3779
|
messagingAutonomousRetryCount: retryCount + 1,
|
|
3292
3780
|
messagingRetryState: {
|
|
3293
|
-
|
|
3781
|
+
lastFinalMessage: String(runMeta?.lastSentMessage || options?.messagingRetryState?.lastFinalMessage || '').trim(),
|
|
3294
3782
|
explicitMessageSent: runMeta?.explicitMessageSent === true || options?.messagingRetryState?.explicitMessageSent === true,
|
|
3783
|
+
interimHistory: cloneInterimHistory([
|
|
3784
|
+
...(Array.isArray(options?.messagingRetryState?.interimHistory) ? options.messagingRetryState.interimHistory : []),
|
|
3785
|
+
...(Array.isArray(runMeta?.interimMessages) ? runMeta.interimMessages : []),
|
|
3786
|
+
]),
|
|
3787
|
+
lastUserVisibleUpdateAt: runMeta?.progressLedger?.lastUserVisibleUpdateAt || options?.messagingRetryState?.lastUserVisibleUpdateAt || null,
|
|
3788
|
+
lastFinalDeliveryAt: runMeta?.progressLedger?.lastFinalDeliveryAt || options?.messagingRetryState?.lastFinalDeliveryAt || null,
|
|
3789
|
+
heartbeatCount: Number(runMeta?.progressLedger?.heartbeatCount || options?.messagingRetryState?.heartbeatCount || 0),
|
|
3790
|
+
progressState: runMeta?.progressLedger?.progressState || options?.messagingRetryState?.progressState || 'active',
|
|
3791
|
+
lastVerifiedProgressAt: runMeta?.progressLedger?.lastVerifiedProgressAt || options?.messagingRetryState?.lastVerifiedProgressAt || null,
|
|
3295
3792
|
},
|
|
3296
3793
|
context: {
|
|
3297
3794
|
...(options.context || {}),
|
|
@@ -3358,6 +3855,7 @@ class AgentEngine {
|
|
|
3358
3855
|
if (!Array.isArray(runMeta.sentMessages)) runMeta.sentMessages = [];
|
|
3359
3856
|
runMeta.sentMessages.push(messagingFailureContent);
|
|
3360
3857
|
}
|
|
3858
|
+
this.markRunFinalDelivery(runId, messagingFailureContent);
|
|
3361
3859
|
} catch (sendErr) {
|
|
3362
3860
|
console.error('[Engine] Messaging error fallback failed:', sendErr.message);
|
|
3363
3861
|
messagingFailureContent = '';
|
|
@@ -3380,6 +3878,7 @@ class AgentEngine {
|
|
|
3380
3878
|
);
|
|
3381
3879
|
|
|
3382
3880
|
this.cleanupSubagentsForRun(runId, { cancelRunning: true });
|
|
3881
|
+
this.stopMessagingProgressSupervisor(runId);
|
|
3383
3882
|
this.activeRuns.delete(runId);
|
|
3384
3883
|
this.emit(userId, 'run:error', { runId, error: err.message });
|
|
3385
3884
|
this.recordRunEvent(userId, runId, 'run_failed', {
|