@xopcai/xopc 0.0.318 → 0.0.319
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/browser-ext/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CuCVSoUV.js → agent-avatar-dicebear-DioXmI1B.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-D70MOJjF.js → agent-defaults-settings-panel-BC5fO4u9.js} +1 -1
- package/dist/gateway/static/root/assets/{agents-BvPp4g1p.js → agents-DCdbFcu0.js} +1 -1
- package/dist/gateway/static/root/assets/{api-Fu9gSeAI.js → api-Bly7An0d.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-bw-nrtc9.js → app-management-settings-panel-BdvzndDX.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-A6UoaC7T.js → appearance-settings-DnSQsLsS.js} +1 -1
- package/dist/gateway/static/root/assets/{apps-page-ky3W_RCl.js → apps-page-C_zoNAPB.js} +1 -1
- package/dist/gateway/static/root/assets/{archive-plugin-BH2u1E5t.js → archive-plugin-D1sosgb9.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-BYQord-c.js → attachment-load-Dq-c2Q5_.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-Ct9Lrj-E.js → automations-page-DmgKAaT7.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-workspace-CVBNAMX2.js → automations-workspace-BrqEhYKt.js} +1 -1
- package/dist/gateway/static/root/assets/{binary-plugins-B8TjGrrp.js → binary-plugins-BUoTDNRQ.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-paecvJ38.js → block-editor-yoFY7HkK.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automation-inputs-DeGdfGUg.js → browser-automation-inputs-DEAGFAVw.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-B-BM6Tg_.js → browser-automations-page-BBtYqV48.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-settings-page-B460-Vqh.js → browser-settings-page-BDEvW4D-.js} +1 -1
- package/dist/gateway/static/root/assets/{capabilities-settings-panel-DPpWDWS2.js → capabilities-settings-panel-2VSLujpj.js} +1 -1
- package/dist/gateway/static/root/assets/{channels-settings-HgKFKpET.js → channels-settings-DP-z1gs6.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-DFS1iOZv.js → chat-terminal-dock-CQUHjVPZ.js} +1 -1
- package/dist/gateway/static/root/assets/{computer-settings-page-CDF2UoSq.js → computer-settings-page-CS4ZuETk.js} +1 -1
- package/dist/gateway/static/root/assets/{connector-service-page-CKg4uafL.js → connector-service-page-DPJYn-zl.js} +1 -1
- package/dist/gateway/static/root/assets/{connectors-page-CLFwm76Q.js → connectors-page-CCnKLCgy.js} +1 -1
- package/dist/gateway/static/root/assets/{date-picker-D569kewK.js → date-picker-B1NMdhqR.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-D9nLZuGV.js → dependency-picker-ChFFtunw.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-ZfzVh2Kx.js → desktop-pet-CNsVhFJp.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-CTTPsaCL.js → desktop-pet-settings-eutzvKKT.js} +1 -1
- package/dist/gateway/static/root/assets/{device-pairing-wizard-BHa8llfD.js → device-pairing-wizard-CjDtOrHm.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CmZp7DKt.js → extension-debug-page-D-es6zh8.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CdCG_Qji.js → extension-page-Ypz64m1o.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-B7VAJYjV.js → extension-settings-page-C-hUKjFp.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-CYa7aVok.js → gateway-config-swr-fCIipSfM.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-settings-BW3KOAoB.js → gateway-settings--y8G9ILA.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-FZix2Im1.js → gateway-startup-retry-BCcckDNC.js} +1 -1
- package/dist/gateway/static/root/assets/{home-page-BmLJEvt_.js → home-page-CLq38B1d.js} +1 -1
- package/dist/gateway/static/root/assets/{image-generation-api-IDhumota.js → image-generation-api-CRPzGucy.js} +1 -1
- package/dist/gateway/static/root/assets/{imports-page-CCsDW_eB.js → imports-page-DUkhEoix.js} +1 -1
- package/dist/gateway/static/root/assets/index-CMYHurex.js +89 -0
- package/dist/gateway/static/root/assets/{index-Ca4fdHsK.css → index-ZxnWUdPv.css} +1 -1
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BEnyvzkl.js → keyboard-shortcuts-settings-L70Ciam4.js} +1 -1
- package/dist/gateway/static/root/assets/{local-app-workbench-page-C6dMiCl3.js → local-app-workbench-page-CfDldGog.js} +1 -1
- package/dist/gateway/static/root/assets/{local-apps-page-DzBneHdn.js → local-apps-page-CnJOtrAm.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-BrnrrtSS.js → locale-store-fR5cJ9bb.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-ClrOYWau.js → logs-page-vtOdc7Bb.js} +1 -1
- package/dist/gateway/static/root/assets/{management-settings-CiZYZTuG.js → management-settings-DST6JUkp.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-DUfjxG4s.js → markdown-view-D6ncpICE.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-fKRKkY7W.js → media-plugins-B4bPx7x9.js} +1 -1
- package/dist/gateway/static/root/assets/messages-BD2ZFuzA.js +3 -0
- package/dist/gateway/static/root/assets/{note-detail-panel-DIV2-sns.js → note-detail-panel-DvMuKm2q.js} +3 -3
- package/dist/gateway/static/root/assets/{notes-page-DCiOBChY.js → notes-page-AvAeLE5k.js} +1 -1
- package/dist/gateway/static/root/assets/{page-context-capture-button-C-v-_7lL.js → page-context-capture-button-Dw9WjBL0.js} +1 -1
- package/dist/gateway/static/root/assets/{page-tabs-DQ72O7xL.js → page-tabs-B8cNBKMc.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-DlO_Ap5J.js → preview-open-alternatives-YxvL9d9g.js} +1 -1
- package/dist/gateway/static/root/assets/{product-open-page-BYoaBpad.js → product-open-page-BbPRhyoH.js} +1 -1
- package/dist/gateway/static/root/assets/{project-detail-page-BaDF9vxI.js → project-detail-page-COARHPND.js} +1 -1
- package/dist/gateway/static/root/assets/{projects-page-Bo_Tgir4.js → projects-page-wILKqv2J.js} +1 -1
- package/dist/gateway/static/root/assets/{registry-api-DgKqRZl1.js → registry-api-16TjUfds.js} +1 -1
- package/dist/gateway/static/root/assets/{remote-access-hub-Cln6drW5.js → remote-access-hub-DGtvCqZx.js} +1 -1
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-8wtw5AOq.js → runtime-tools-settings-panel-Co_LUUnH.js} +1 -1
- package/dist/gateway/static/root/assets/{scenes-page-8w1c7gx5.js → scenes-page-kfZdYrD4.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-BkLzQsRu.js → schema-form-CbTLF_HM.js} +1 -1
- package/dist/gateway/static/root/assets/{sessions-page-DBb_Dif-.js → sessions-page-BHH45dMe.js} +1 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DJvTLb55.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-BXm_AXYs.js → settings-form-section-LNoXQyF4.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-IlYic2v9.js → settings-page-ClVmWX3G.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-layout-BD--CQW8.js → settings-page-layout-CB49Igqq.js} +1 -1
- package/dist/gateway/static/root/assets/{setup-status-panel-COOVP1gw.js → setup-status-panel-EXTejPLU.js} +1 -1
- package/dist/gateway/static/root/assets/{share-preview-page-zZqVlKwP.js → share-preview-page-B52OyR0V.js} +1 -1
- package/dist/gateway/static/root/assets/{shares-settings-C3nvVwkU.js → shares-settings-BLZeBj7K.js} +1 -1
- package/dist/gateway/static/root/assets/skill-reload-api-BnNM4ybJ.js +1 -0
- package/dist/gateway/static/root/assets/{skills-page-CSlXf8xJ.js → skills-page-DenebXe9.js} +1 -1
- package/dist/gateway/static/root/assets/{skills-page.utils-DBighWxW.js → skills-page.utils-BcZeRgvY.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-CKhtP7t2.js → system-settings-panel-BKWhqNff.js} +1 -1
- package/dist/gateway/static/root/assets/{task-detail-page-BiVJDXn1.js → task-detail-page-Bb5wHzN2.js} +1 -1
- package/dist/gateway/static/root/assets/{tunnel-api-Dz2z5iRH.js → tunnel-api-B0AvnJIE.js} +1 -1
- package/dist/gateway/static/root/assets/{use-autosave-D7AnGlod.js → use-autosave-C9haIbWf.js} +1 -1
- package/dist/gateway/static/root/assets/{user-model-page-vFnSEuh2.js → user-model-page-D-q-uNMx.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-api-key-field-zOuxp9Bl.js → voice-api-key-field-BU3s_NfJ.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-F6NsgxNd.js → voice-config-api-Dg811HrP.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-o7nsVHyj.js → work-discovery-overlay-BGVEGV-o.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Dnw7G5sP.js → workflow-run-setup-panel-C3etwNkI.js} +1 -1
- package/dist/gateway/static/root/assets/{workflows-page-nbri1Yf4.js → workflows-page-Ds9842Za.js} +1 -1
- package/dist/gateway/static/root/index.html +8 -8
- package/dist/package.js +1 -1
- package/dist/src/agent/embedded/run-turn.js +236 -42
- package/dist/src/agent/memory/compaction-ledger.d.ts +3 -0
- package/dist/src/agent/memory/compaction-ledger.js +128 -25
- package/dist/src/agent/memory/compaction.d.ts +12 -0
- package/dist/src/agent/memory/compaction.js +50 -9
- package/dist/src/agent/memory/context-recovery.d.ts +5 -1
- package/dist/src/agent/memory/context-recovery.js +20 -7
- package/dist/src/gateway/hono/routes/imports.js +1 -1
- package/dist/src/gateway/service.js +6 -6
- package/package.json +5 -5
- package/dist/gateway/static/root/assets/index-DZXx1pet.js +0 -89
- package/dist/gateway/static/root/assets/messages-BoEi2k2p.js +0 -3
- package/dist/gateway/static/root/assets/settings-advanced-gate-Gb5Hhv_H.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-BJCxXuYk.js +0 -1
|
@@ -16,7 +16,7 @@ import { wrapStreamFnForXopcExtensions } from "./xopc-stream-bridge.js";
|
|
|
16
16
|
import { acquireEmbeddedSessionRunner, evictEmbeddedSessionRunner } from "./session-runner.js";
|
|
17
17
|
import { projectContextForModel } from "../memory/context-budget.js";
|
|
18
18
|
import { createSqliteTranscriptRuntime } from "./transcript-runtime.js";
|
|
19
|
-
import {
|
|
19
|
+
import { assessContext, recoverContext } from "../memory/context-recovery.js";
|
|
20
20
|
import { resolveCompactionPolicy } from "../memory/compaction-policy.js";
|
|
21
21
|
import { buildPromptCacheSnapshot, observePromptCacheSnapshot } from "../../providers/prompt-cache-observability.js";
|
|
22
22
|
import { isPromptCacheExpired, recordPromptCacheTouch } from "../../providers/prompt-cache-lifecycle.js";
|
|
@@ -24,7 +24,7 @@ import { markTurnToolResult } from "../memory/turn-provenance.js";
|
|
|
24
24
|
import { RepositoryInstructions } from "../coding/repository-instructions.js";
|
|
25
25
|
import { RunVerification } from "../coding/run-verification.js";
|
|
26
26
|
import { withDelegationScope } from "../orchestration/delegation-scope.js";
|
|
27
|
-
import { getCurrentSystemPrompt, getCurrentTools } from "@earendil-works/pi-ai";
|
|
27
|
+
import { createAssistantMessageEventStream, getCurrentSystemPrompt, getCurrentTools, isRecoverableLength } from "@earendil-works/pi-ai";
|
|
28
28
|
//#region src/agent/embedded/run-turn.ts
|
|
29
29
|
init_connection_wait_repository();
|
|
30
30
|
init_logger();
|
|
@@ -88,53 +88,141 @@ function userMessageToPromptText(message) {
|
|
|
88
88
|
}).map((block) => block.text).join("");
|
|
89
89
|
return "";
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
function lastAssistantMessage(messages) {
|
|
92
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
93
|
+
const message = messages[index];
|
|
94
|
+
if (message?.role === "assistant") return message;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function discardedAttempt(message) {
|
|
98
|
+
return {
|
|
99
|
+
assistantTimestamp: message.timestamp,
|
|
100
|
+
provider: message.provider,
|
|
101
|
+
model: message.model,
|
|
102
|
+
toolCallIds: message.content.flatMap((block) => block.type === "toolCall" ? [block.id] : [])
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function isRecoverableLengthForModel(message, model) {
|
|
106
|
+
return message.provider === model.provider && message.model === model.id && isRecoverableLength(message, model.maxTokens ?? 0);
|
|
107
|
+
}
|
|
108
|
+
function withRecoveredMessages(current, recovered) {
|
|
109
|
+
return [...current.filter((message) => message.role === "system"), ...recovered.filter((message) => message.role !== "system")];
|
|
110
|
+
}
|
|
111
|
+
function createContextErrorStream(model, errorMessage) {
|
|
112
|
+
const stream = createAssistantMessageEventStream();
|
|
113
|
+
const error = {
|
|
114
|
+
role: "assistant",
|
|
115
|
+
content: [],
|
|
116
|
+
api: model.api,
|
|
117
|
+
provider: model.provider,
|
|
118
|
+
model: model.id,
|
|
119
|
+
usage: {
|
|
120
|
+
input: 0,
|
|
121
|
+
output: 0,
|
|
122
|
+
cacheRead: 0,
|
|
123
|
+
cacheWrite: 0,
|
|
124
|
+
totalTokens: 0,
|
|
125
|
+
cost: {
|
|
126
|
+
input: 0,
|
|
127
|
+
output: 0,
|
|
128
|
+
cacheRead: 0,
|
|
129
|
+
cacheWrite: 0,
|
|
130
|
+
total: 0
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
stopReason: "error",
|
|
97
134
|
errorMessage,
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
type: "
|
|
102
|
-
|
|
135
|
+
timestamp: Date.now()
|
|
136
|
+
};
|
|
137
|
+
stream.push({
|
|
138
|
+
type: "error",
|
|
139
|
+
reason: "error",
|
|
140
|
+
error
|
|
103
141
|
});
|
|
142
|
+
stream.end(error);
|
|
143
|
+
return stream;
|
|
144
|
+
}
|
|
145
|
+
async function recoverRunContext(params) {
|
|
146
|
+
let started = false;
|
|
104
147
|
const summaryModel = params.policy.model ? (await import("../../providers/index.js")).resolveModel(params.policy.model) : params.model;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
148
|
+
try {
|
|
149
|
+
const recovered = await recoverContext({
|
|
150
|
+
conversationId: params.conversationId,
|
|
151
|
+
transcript: params.transcriptRuntime,
|
|
152
|
+
policy: params.policy,
|
|
153
|
+
budget: {
|
|
154
|
+
contextWindow: params.model.contextWindow ?? 128e3,
|
|
155
|
+
systemPrompt: params.systemPrompt,
|
|
156
|
+
tools: params.agent.state.tools,
|
|
157
|
+
reserveTokens: params.policy.reserveTokens,
|
|
158
|
+
triggerThreshold: params.policy.triggerThreshold,
|
|
159
|
+
minToolResultKeepChars: params.policy.minToolResultKeepChars
|
|
160
|
+
},
|
|
161
|
+
summaryModel,
|
|
162
|
+
fallbackModels: summaryModel === params.model ? [] : [params.model],
|
|
163
|
+
signal: params.abortSignal,
|
|
164
|
+
providerRejected: params.providerRejected,
|
|
165
|
+
forceOnTrigger: true,
|
|
166
|
+
preserveLastUser: params.providerRejected,
|
|
167
|
+
discardedAttempt: params.discardedAttempt,
|
|
168
|
+
onCompactionStart: () => {
|
|
169
|
+
if (started) return;
|
|
170
|
+
started = true;
|
|
171
|
+
params.onEvent?.({
|
|
172
|
+
type: "compaction",
|
|
173
|
+
status: "started"
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (recovered.status === "compacted") params.onEvent?.({
|
|
178
|
+
type: "compaction",
|
|
179
|
+
status: "completed",
|
|
180
|
+
tokensBefore: recovered.result?.tokensBefore,
|
|
181
|
+
tokensAfter: recovered.result?.tokensAfter,
|
|
182
|
+
summary: recovered.result?.summary.slice(0, 200)
|
|
183
|
+
});
|
|
184
|
+
else if (started) params.onEvent?.({
|
|
185
|
+
type: "compaction",
|
|
186
|
+
status: "skipped"
|
|
187
|
+
});
|
|
188
|
+
return recovered;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (started) params.onEvent?.({
|
|
124
191
|
type: "compaction",
|
|
125
192
|
status: "skipped"
|
|
126
193
|
});
|
|
127
194
|
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function maybeRecoverInterruptedContext(params) {
|
|
198
|
+
const assistant = lastAssistantMessage(params.agent.state.messages);
|
|
199
|
+
const errorMessage = getAssistantTurnErrorMessage(params.agent);
|
|
200
|
+
const overflow = !!errorMessage && isContextOverflowError(errorMessage);
|
|
201
|
+
const recoverableLength = !!assistant && isRecoverableLengthForModel(assistant, params.model);
|
|
202
|
+
if (!overflow && !recoverableLength) return false;
|
|
203
|
+
if (recoverableLength && (!params.policy.enabled || params.recoveryState.interruptedAttempts >= 1)) {
|
|
204
|
+
log.warn({
|
|
205
|
+
conversationId: params.conversationId,
|
|
206
|
+
attempts: params.recoveryState.interruptedAttempts
|
|
207
|
+
}, "Truncated response recovery is unavailable or already exhausted");
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
if (recoverableLength) params.recoveryState.interruptedAttempts += 1;
|
|
211
|
+
const messages = stripTrailingErrorAssistantMessages(params.agent.state.messages);
|
|
212
|
+
log.warn({
|
|
213
|
+
conversationId: params.conversationId,
|
|
214
|
+
errorMessage,
|
|
215
|
+
messageCount: messages.length,
|
|
216
|
+
reason: recoverableLength ? "length" : "context_overflow"
|
|
217
|
+
}, recoverableLength ? "Assistant response was truncated; compacting and retrying the same turn once" : "Provider rejected the context window; compacting and retrying the same turn");
|
|
218
|
+
const recovered = await recoverRunContext({
|
|
219
|
+
...params,
|
|
220
|
+
providerRejected: true,
|
|
221
|
+
discardedAttempt: recoverableLength && assistant ? discardedAttempt(assistant) : void 0
|
|
128
222
|
});
|
|
129
223
|
params.agent.state.messages = stripTrailingErrorAssistantMessages(recovered.messages);
|
|
224
|
+
params.onContextRecovered?.();
|
|
130
225
|
params.abortSignal?.throwIfAborted();
|
|
131
|
-
params.onEvent?.({
|
|
132
|
-
type: "compaction",
|
|
133
|
-
status: "completed",
|
|
134
|
-
tokensBefore: recovered.result?.tokensBefore,
|
|
135
|
-
tokensAfter: recovered.result?.tokensAfter,
|
|
136
|
-
summary: recovered.result?.summary.slice(0, 200)
|
|
137
|
-
});
|
|
138
226
|
await params.agent.continue();
|
|
139
227
|
await params.agent.waitForIdle();
|
|
140
228
|
return true;
|
|
@@ -154,6 +242,8 @@ async function runXopcEmbeddedTurn(params) {
|
|
|
154
242
|
let unsubscribe;
|
|
155
243
|
let quarantineRunner = false;
|
|
156
244
|
let runLease;
|
|
245
|
+
let restorePrepareNextTurn;
|
|
246
|
+
let restorePrepareRequest;
|
|
157
247
|
try {
|
|
158
248
|
runLease = acquireEmbeddedRunLease({
|
|
159
249
|
conversationId,
|
|
@@ -185,6 +275,83 @@ async function runXopcEmbeddedTurn(params) {
|
|
|
185
275
|
}
|
|
186
276
|
session.agent.state.messages = stripTrailingErrorAssistantMessages(await transcriptRuntime.loadMessages());
|
|
187
277
|
runner.piSm.setActiveTurnId?.(runId);
|
|
278
|
+
const recoveryState = { interruptedAttempts: 0 };
|
|
279
|
+
let useAuthoritativeProjection = false;
|
|
280
|
+
const basePrepareNextTurn = session.agent.prepareNextTurnWithContext;
|
|
281
|
+
const inTurnPrepareNextTurn = async (turn, signal) => {
|
|
282
|
+
const prepared = await basePrepareNextTurn?.(turn, signal);
|
|
283
|
+
const currentContext = prepared?.context ?? turn.context;
|
|
284
|
+
const recoverableLength = isRecoverableLengthForModel(turn.message, resolvedModel);
|
|
285
|
+
if (recoverableLength && (!compactionPolicy.enabled || recoveryState.interruptedAttempts >= 1)) return prepared;
|
|
286
|
+
if (recoverableLength) recoveryState.interruptedAttempts += 1;
|
|
287
|
+
try {
|
|
288
|
+
const recovered = await recoverRunContext({
|
|
289
|
+
agent: session.agent,
|
|
290
|
+
model: resolvedModel,
|
|
291
|
+
conversationId,
|
|
292
|
+
transcriptRuntime,
|
|
293
|
+
policy: compactionPolicy,
|
|
294
|
+
systemPrompt: [systemPrompt, rootInstructions].filter(Boolean).join("\n\n"),
|
|
295
|
+
abortSignal: signal ?? runAbortSignal,
|
|
296
|
+
onEvent,
|
|
297
|
+
providerRejected: recoverableLength,
|
|
298
|
+
discardedAttempt: recoverableLength ? discardedAttempt(turn.message) : void 0
|
|
299
|
+
});
|
|
300
|
+
useAuthoritativeProjection ||= recovered.status === "compacted";
|
|
301
|
+
return {
|
|
302
|
+
...prepared,
|
|
303
|
+
context: {
|
|
304
|
+
...currentContext,
|
|
305
|
+
messages: withRecoveredMessages(currentContext.messages, recovered.messages)
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (signal?.aborted || runAbortSignal.aborted) throw error;
|
|
310
|
+
log.error({
|
|
311
|
+
err: error,
|
|
312
|
+
conversationId,
|
|
313
|
+
runId,
|
|
314
|
+
phase: "prepare_next_turn"
|
|
315
|
+
}, "In-turn context recovery failed; the next request will use the guarded provider path");
|
|
316
|
+
return prepared;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
session.agent.prepareNextTurnWithContext = inTurnPrepareNextTurn;
|
|
320
|
+
restorePrepareNextTurn = () => {
|
|
321
|
+
if (session.agent.prepareNextTurnWithContext === inTurnPrepareNextTurn) session.agent.prepareNextTurnWithContext = basePrepareNextTurn;
|
|
322
|
+
};
|
|
323
|
+
const basePrepareRequest = session.agent.prepareRequest;
|
|
324
|
+
const authoritativePrepareRequest = async (request, signal) => {
|
|
325
|
+
const prepared = await basePrepareRequest?.(request, signal);
|
|
326
|
+
const currentContext = prepared?.context ?? request.context;
|
|
327
|
+
if (!useAuthoritativeProjection) return {
|
|
328
|
+
...prepared,
|
|
329
|
+
context: currentContext
|
|
330
|
+
};
|
|
331
|
+
try {
|
|
332
|
+
const messages = await transcriptRuntime.loadMessages();
|
|
333
|
+
return {
|
|
334
|
+
...prepared,
|
|
335
|
+
context: {
|
|
336
|
+
...currentContext,
|
|
337
|
+
messages: withRecoveredMessages(currentContext.messages, messages)
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (signal?.aborted || runAbortSignal.aborted) throw error;
|
|
342
|
+
log.error({
|
|
343
|
+
err: error,
|
|
344
|
+
conversationId,
|
|
345
|
+
runId,
|
|
346
|
+
phase: "prepare_request"
|
|
347
|
+
}, "Failed to refresh the authoritative compacted projection");
|
|
348
|
+
return prepared;
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
session.agent.prepareRequest = authoritativePrepareRequest;
|
|
352
|
+
restorePrepareRequest = () => {
|
|
353
|
+
if (session.agent.prepareRequest === authoritativePrepareRequest) session.agent.prepareRequest = basePrepareRequest;
|
|
354
|
+
};
|
|
188
355
|
const streamFnWithXopcExtensions = wrapStreamFnForXopcExtensions(session.agent.streamFunction, params.promptCachePolicy);
|
|
189
356
|
const loggingStreamFn = (streamModel, context, options) => {
|
|
190
357
|
instructions.acknowledge();
|
|
@@ -239,7 +406,17 @@ async function runXopcEmbeddedTurn(params) {
|
|
|
239
406
|
reserveTokens: compactionPolicy.reserveTokens,
|
|
240
407
|
minToolResultKeepChars: compactionPolicy.minToolResultKeepChars
|
|
241
408
|
}, compactionPolicy.maxActiveTranscriptBytes);
|
|
242
|
-
if (!assessed.fits)
|
|
409
|
+
if (!assessed.fits) {
|
|
410
|
+
const errorMessage = `Context budget exceeded before provider request (${assessed.evaluation.estimatedTokens}/${assessed.evaluation.hardLimitTokens} tokens)`;
|
|
411
|
+
log.error({
|
|
412
|
+
conversationId,
|
|
413
|
+
runId,
|
|
414
|
+
phase: "provider_preflight",
|
|
415
|
+
estimatedTokens: assessed.evaluation.estimatedTokens,
|
|
416
|
+
hardLimitTokens: assessed.evaluation.hardLimitTokens
|
|
417
|
+
}, errorMessage);
|
|
418
|
+
return createContextErrorStream(streamModel, errorMessage);
|
|
419
|
+
}
|
|
243
420
|
effectiveContext = {
|
|
244
421
|
...effectiveContext,
|
|
245
422
|
messages: assessed.messages
|
|
@@ -350,6 +527,13 @@ async function runXopcEmbeddedTurn(params) {
|
|
|
350
527
|
session.agent.finishTurn = async (context, signal) => {
|
|
351
528
|
const baseDecision = await baseFinishTurn?.(context, signal);
|
|
352
529
|
if (baseDecision && baseDecision.action === "end") return { action: "end" };
|
|
530
|
+
if (context.message && isRecoverableLengthForModel(context.message, resolvedModel) && recoveryState.interruptedAttempts >= 1) {
|
|
531
|
+
log.warn({
|
|
532
|
+
conversationId,
|
|
533
|
+
runId
|
|
534
|
+
}, "Stopping after the bounded truncated-response recovery attempt");
|
|
535
|
+
return { action: "end" };
|
|
536
|
+
}
|
|
353
537
|
policyStopped ||= params.turnPolicy?.shouldStopAfterTurn(context) ?? false;
|
|
354
538
|
if (policyStopped || connectionStopped || clarificationStopped) return { action: "end" };
|
|
355
539
|
if (baseDecision && baseDecision.action === "continue") return { action: "continue" };
|
|
@@ -397,7 +581,7 @@ async function runXopcEmbeddedTurn(params) {
|
|
|
397
581
|
log,
|
|
398
582
|
signal: runAbortSignal
|
|
399
583
|
});
|
|
400
|
-
await
|
|
584
|
+
await maybeRecoverInterruptedContext({
|
|
401
585
|
agent: session.agent,
|
|
402
586
|
model: resolvedModel,
|
|
403
587
|
conversationId,
|
|
@@ -405,7 +589,11 @@ async function runXopcEmbeddedTurn(params) {
|
|
|
405
589
|
policy: compactionPolicy,
|
|
406
590
|
systemPrompt: [systemPrompt, rootInstructions].filter(Boolean).join("\n\n"),
|
|
407
591
|
abortSignal: runAbortSignal,
|
|
408
|
-
onEvent
|
|
592
|
+
onEvent,
|
|
593
|
+
recoveryState,
|
|
594
|
+
onContextRecovered: () => {
|
|
595
|
+
useAuthoritativeProjection = true;
|
|
596
|
+
}
|
|
409
597
|
});
|
|
410
598
|
const pending = await verification.pendingContext();
|
|
411
599
|
if ((params.verifyChanges ?? false) && pending && !policyStopped && !runAbortSignal.aborted && !isAssistantTurnFailed(session.agent) && !isAssistantTurnAborted(session.agent)) {
|
|
@@ -456,6 +644,10 @@ async function runXopcEmbeddedTurn(params) {
|
|
|
456
644
|
};
|
|
457
645
|
} finally {
|
|
458
646
|
session.agent.finishTurn = baseFinishTurn;
|
|
647
|
+
restorePrepareNextTurn();
|
|
648
|
+
restorePrepareNextTurn = void 0;
|
|
649
|
+
restorePrepareRequest();
|
|
650
|
+
restorePrepareRequest = void 0;
|
|
459
651
|
runAbortSignal.removeEventListener("abort", abortListener);
|
|
460
652
|
}
|
|
461
653
|
} catch (err) {
|
|
@@ -494,6 +686,8 @@ async function runXopcEmbeddedTurn(params) {
|
|
|
494
686
|
errorMessage: em
|
|
495
687
|
};
|
|
496
688
|
} finally {
|
|
689
|
+
restorePrepareNextTurn?.();
|
|
690
|
+
restorePrepareRequest?.();
|
|
497
691
|
runLease?.release();
|
|
498
692
|
unsubscribe?.();
|
|
499
693
|
try {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type CompactionHandover } from '../../session/compaction-types.js';
|
|
2
2
|
import type { TranscriptSourceEntry } from '../../storage/sqlite/transcript-repository.js';
|
|
3
3
|
export type { CompactionAudit, CompactionHandover, CompactionHandoverItem, HandoverItemKind, } from '../../session/compaction-types.js';
|
|
4
|
+
export declare const MAX_HANDOVER_ITEMS = 120;
|
|
5
|
+
/** Normalize legacy or model-produced ledgers into the bounded durable form. */
|
|
6
|
+
export declare function consolidateCompactionHandover(handover: CompactionHandover): CompactionHandover;
|
|
4
7
|
/** Apply a bounded model-produced delta to the trusted local handover. */
|
|
5
8
|
export declare function applyCompactionHandoverDelta(params: {
|
|
6
9
|
text: string;
|
|
@@ -4,6 +4,32 @@ import { z } from "zod";
|
|
|
4
4
|
//#region src/agent/memory/compaction-ledger.ts
|
|
5
5
|
init_compaction_types();
|
|
6
6
|
const MAX_HANDOVER_ITEMS = 120;
|
|
7
|
+
const MAX_HANDOVER_DELTA_ITEMS = 480;
|
|
8
|
+
const MAX_SOURCE_REFS_PER_ITEM = 12;
|
|
9
|
+
const COMPLETED_KIND_BUDGET = {
|
|
10
|
+
objective: 4,
|
|
11
|
+
decision: 24,
|
|
12
|
+
pending_user_ask: 2,
|
|
13
|
+
todo: 4,
|
|
14
|
+
constraint: 4,
|
|
15
|
+
file_change: 28,
|
|
16
|
+
tool_outcome: 24,
|
|
17
|
+
failure: 8,
|
|
18
|
+
current_state: 12,
|
|
19
|
+
next_action: 4
|
|
20
|
+
};
|
|
21
|
+
const ACTIVE_KIND_PRIORITY = {
|
|
22
|
+
pending_user_ask: 10,
|
|
23
|
+
objective: 9,
|
|
24
|
+
constraint: 8,
|
|
25
|
+
decision: 7,
|
|
26
|
+
failure: 6,
|
|
27
|
+
todo: 5,
|
|
28
|
+
next_action: 4,
|
|
29
|
+
current_state: 3,
|
|
30
|
+
file_change: 2,
|
|
31
|
+
tool_outcome: 1
|
|
32
|
+
};
|
|
7
33
|
const RawHandoverUpsertSchema = z.object({
|
|
8
34
|
id: z.string().min(1).max(64).optional(),
|
|
9
35
|
kind: z.enum(HANDOVER_ITEM_KINDS),
|
|
@@ -16,7 +42,7 @@ const RawHandoverUpsertSchema = z.object({
|
|
|
16
42
|
sourceSeqs: z.array(z.number().int().positive()).min(1),
|
|
17
43
|
identifiers: z.array(z.string().max(256)).max(20).default([])
|
|
18
44
|
}).strict();
|
|
19
|
-
const RawHandoverDeltaSchema = z.object({ upserts: z.array(RawHandoverUpsertSchema).max(
|
|
45
|
+
const RawHandoverDeltaSchema = z.object({ upserts: z.array(RawHandoverUpsertSchema).max(MAX_HANDOVER_DELTA_ITEMS) }).strict();
|
|
20
46
|
function extractJsonObject(text) {
|
|
21
47
|
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1]?.trim();
|
|
22
48
|
if (fenced) return fenced;
|
|
@@ -25,8 +51,86 @@ function extractJsonObject(text) {
|
|
|
25
51
|
if (start < 0 || end <= start) throw new Error("Compaction model did not return a JSON object");
|
|
26
52
|
return text.slice(start, end + 1);
|
|
27
53
|
}
|
|
28
|
-
function
|
|
29
|
-
return
|
|
54
|
+
function normalizeSemanticText(text) {
|
|
55
|
+
return text.normalize("NFKC").replace(/\s+/g, " ").trim().replace(/[.!?。!?]+$/u, "");
|
|
56
|
+
}
|
|
57
|
+
function normalizeIdentifiers(identifiers) {
|
|
58
|
+
return [...new Set(identifiers.map((value) => value.normalize("NFKC").trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right));
|
|
59
|
+
}
|
|
60
|
+
function semanticKey(item) {
|
|
61
|
+
return `${item.kind}\0${normalizeSemanticText(item.text)}\0${normalizeIdentifiers(item.identifiers).join("\0")}`;
|
|
62
|
+
}
|
|
63
|
+
function itemId(kind, text, identifiers) {
|
|
64
|
+
return createHash("sha256").update(`${kind}\0${normalizeSemanticText(text)}\0${normalizeIdentifiers(identifiers).join("\0")}`).digest("hex").slice(0, 20);
|
|
65
|
+
}
|
|
66
|
+
function newestSourceSeq(item) {
|
|
67
|
+
return item.sources.reduce((latest, source) => Math.max(latest, source.seq), 0);
|
|
68
|
+
}
|
|
69
|
+
function mergeSources(previous, incoming) {
|
|
70
|
+
const bySeq = new Map(previous.map((source) => [source.seq, source]));
|
|
71
|
+
for (const source of incoming) bySeq.set(source.seq, source);
|
|
72
|
+
const sorted = [...bySeq.values()].sort((left, right) => left.seq - right.seq);
|
|
73
|
+
if (sorted.length <= MAX_SOURCE_REFS_PER_ITEM) return sorted;
|
|
74
|
+
return [sorted[0], ...sorted.slice(-(MAX_SOURCE_REFS_PER_ITEM - 1))];
|
|
75
|
+
}
|
|
76
|
+
function mergeSemanticDuplicate(previous, incoming) {
|
|
77
|
+
return {
|
|
78
|
+
...newestSourceSeq(incoming) >= newestSourceSeq(previous) ? incoming : previous,
|
|
79
|
+
id: previous.id,
|
|
80
|
+
sources: mergeSources(previous.sources, incoming.sources),
|
|
81
|
+
identifiers: normalizeIdentifiers([...previous.identifiers, ...incoming.identifiers]).slice(0, 20)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function compareRetentionPriority(left, right) {
|
|
85
|
+
const status = Number(right.status === "active") - Number(left.status === "active");
|
|
86
|
+
if (status !== 0) return status;
|
|
87
|
+
const kind = ACTIVE_KIND_PRIORITY[right.kind] - ACTIVE_KIND_PRIORITY[left.kind];
|
|
88
|
+
if (kind !== 0) return kind;
|
|
89
|
+
const recency = newestSourceSeq(right) - newestSourceSeq(left);
|
|
90
|
+
if (recency !== 0) return recency;
|
|
91
|
+
return left.id.localeCompare(right.id);
|
|
92
|
+
}
|
|
93
|
+
function consolidateItems(items) {
|
|
94
|
+
const bySemanticKey = /* @__PURE__ */ new Map();
|
|
95
|
+
const keyOrder = [];
|
|
96
|
+
for (const item of items) {
|
|
97
|
+
if (item.status === "superseded") continue;
|
|
98
|
+
const key = semanticKey(item);
|
|
99
|
+
const existing = bySemanticKey.get(key);
|
|
100
|
+
if (existing) bySemanticKey.set(key, mergeSemanticDuplicate(existing, item));
|
|
101
|
+
else {
|
|
102
|
+
keyOrder.push(key);
|
|
103
|
+
bySemanticKey.set(key, {
|
|
104
|
+
...item,
|
|
105
|
+
sources: mergeSources([], item.sources),
|
|
106
|
+
identifiers: normalizeIdentifiers(item.identifiers).slice(0, 20)
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const deduplicated = keyOrder.map((key) => bySemanticKey.get(key));
|
|
111
|
+
const completedByKind = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const item of deduplicated) {
|
|
113
|
+
if (item.status !== "completed") continue;
|
|
114
|
+
const group = completedByKind.get(item.kind) ?? [];
|
|
115
|
+
group.push(item);
|
|
116
|
+
completedByKind.set(item.kind, group);
|
|
117
|
+
}
|
|
118
|
+
const allowedCompleted = /* @__PURE__ */ new Set();
|
|
119
|
+
for (const [kind, group] of completedByKind) {
|
|
120
|
+
group.sort((left, right) => newestSourceSeq(right) - newestSourceSeq(left) || left.id.localeCompare(right.id));
|
|
121
|
+
for (const item of group.slice(0, COMPLETED_KIND_BUDGET[kind])) allowedCompleted.add(item.id);
|
|
122
|
+
}
|
|
123
|
+
const perKindBounded = deduplicated.filter((item) => item.status === "active" || allowedCompleted.has(item.id));
|
|
124
|
+
if (perKindBounded.length <= 120) return perKindBounded;
|
|
125
|
+
const retainedIds = new Set([...perKindBounded].sort(compareRetentionPriority).slice(0, 120).map((item) => item.id));
|
|
126
|
+
return perKindBounded.filter((item) => retainedIds.has(item.id));
|
|
127
|
+
}
|
|
128
|
+
/** Normalize legacy or model-produced ledgers into the bounded durable form. */
|
|
129
|
+
function consolidateCompactionHandover(handover) {
|
|
130
|
+
return {
|
|
131
|
+
...handover,
|
|
132
|
+
items: consolidateItems(handover.items)
|
|
133
|
+
};
|
|
30
134
|
}
|
|
31
135
|
/** Apply a bounded model-produced delta to the trusted local handover. */
|
|
32
136
|
function applyCompactionHandoverDelta(params) {
|
|
@@ -37,36 +141,32 @@ function applyCompactionHandoverDelta(params) {
|
|
|
37
141
|
}]));
|
|
38
142
|
for (const item of params.current?.items ?? []) for (const source of item.sources) sourceBySeq.set(source.seq, source);
|
|
39
143
|
const items = new Map((params.current?.items ?? []).map((item) => [item.id, item]));
|
|
40
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
41
144
|
for (const item of parsed.upserts) {
|
|
42
|
-
const
|
|
43
|
-
const sources = seqs.map((seq) => {
|
|
145
|
+
const sources = [...new Set(item.sourceSeqs)].sort((a, b) => a - b).map((seq) => {
|
|
44
146
|
const source = sourceBySeq.get(seq);
|
|
45
147
|
if (!source || seq > params.sourceThroughSeq) throw new Error(`Compaction handover references unavailable source seq ${seq}`);
|
|
46
148
|
return source;
|
|
47
149
|
});
|
|
48
150
|
const text = item.text.trim();
|
|
49
|
-
const
|
|
151
|
+
const identifiers = normalizeIdentifiers(item.identifiers);
|
|
152
|
+
const id = item.id ?? itemId(item.kind, text, identifiers);
|
|
50
153
|
if (item.id && !items.has(item.id)) throw new Error(`Compaction handover updates unknown item ${item.id}`);
|
|
51
|
-
|
|
52
|
-
seenIds.add(id);
|
|
154
|
+
const previousItem = items.get(id);
|
|
53
155
|
items.set(id, {
|
|
54
156
|
id,
|
|
55
157
|
kind: item.kind,
|
|
56
158
|
text,
|
|
57
159
|
status: item.status,
|
|
58
|
-
sources,
|
|
59
|
-
identifiers: [...
|
|
160
|
+
sources: mergeSources(previousItem?.sources ?? [], sources),
|
|
161
|
+
identifiers: normalizeIdentifiers([...previousItem?.identifiers ?? [], ...identifiers]).slice(0, 20)
|
|
60
162
|
});
|
|
61
163
|
}
|
|
62
|
-
|
|
63
|
-
if (retained.length > MAX_HANDOVER_ITEMS) throw new Error(`Compaction handover exceeds ${MAX_HANDOVER_ITEMS} durable items`);
|
|
64
|
-
return {
|
|
164
|
+
return consolidateCompactionHandover({
|
|
65
165
|
version: 1,
|
|
66
166
|
sourceThroughSeq: params.sourceThroughSeq,
|
|
67
167
|
...params.previousBoundaryId ? { previousBoundaryId: params.previousBoundaryId } : {},
|
|
68
|
-
items:
|
|
69
|
-
};
|
|
168
|
+
items: [...items.values()]
|
|
169
|
+
});
|
|
70
170
|
}
|
|
71
171
|
const SUMMARY_GROUPS = [
|
|
72
172
|
{
|
|
@@ -115,14 +215,17 @@ function renderCompactionHandover(handover) {
|
|
|
115
215
|
return sections.join("\n\n");
|
|
116
216
|
}
|
|
117
217
|
function handoverForPrompt(handover) {
|
|
118
|
-
return {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
218
|
+
return {
|
|
219
|
+
maxItems: 120,
|
|
220
|
+
items: (handover ? consolidateCompactionHandover(handover) : void 0)?.items.map((item) => ({
|
|
221
|
+
id: item.id,
|
|
222
|
+
kind: item.kind,
|
|
223
|
+
text: item.text,
|
|
224
|
+
status: item.status,
|
|
225
|
+
sourceSeqs: item.sources.map((source) => source.seq),
|
|
226
|
+
identifiers: item.identifiers
|
|
227
|
+
})) ?? []
|
|
228
|
+
};
|
|
126
229
|
}
|
|
127
230
|
//#endregion
|
|
128
|
-
export { applyCompactionHandoverDelta, handoverForPrompt, renderCompactionHandover };
|
|
231
|
+
export { MAX_HANDOVER_ITEMS, applyCompactionHandoverDelta, consolidateCompactionHandover, handoverForPrompt, renderCompactionHandover };
|
|
@@ -47,6 +47,18 @@ export interface CompactionExecutionOptions {
|
|
|
47
47
|
fallbackModels?: Array<Model<Api>>;
|
|
48
48
|
signal?: AbortSignal;
|
|
49
49
|
checkpoint?: CompactionCheckpointStore;
|
|
50
|
+
/**
|
|
51
|
+
* Persist the failed attempt for audit, but replace it with a safe marker
|
|
52
|
+
* while building the handover. This prevents a truncated response and
|
|
53
|
+
* its synthetic tool results from becoming model-visible again later.
|
|
54
|
+
*/
|
|
55
|
+
discardedAttempt?: CompactionDiscardedAttempt;
|
|
56
|
+
}
|
|
57
|
+
export interface CompactionDiscardedAttempt {
|
|
58
|
+
assistantTimestamp: number;
|
|
59
|
+
provider?: string;
|
|
60
|
+
model?: string;
|
|
61
|
+
toolCallIds?: string[];
|
|
50
62
|
}
|
|
51
63
|
export interface CompactionCheckpoint {
|
|
52
64
|
version: 1;
|