@xopcai/xopc 0.0.318 → 0.0.320

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.
Files changed (145) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CuCVSoUV.js → agent-avatar-dicebear-BXLU0HJa.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-defaults-settings-panel-D70MOJjF.js → agent-defaults-settings-panel-BGCqlTcR.js} +1 -1
  5. package/dist/gateway/static/root/assets/{agents-BvPp4g1p.js → agents-hrbGiOHn.js} +1 -1
  6. package/dist/gateway/static/root/assets/{api-Fu9gSeAI.js → api-DEneywgz.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-bw-nrtc9.js → app-management-settings-panel-C0jEvonH.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-A6UoaC7T.js → appearance-settings-ByVRv2Oe.js} +1 -1
  9. package/dist/gateway/static/root/assets/{apps-page-ky3W_RCl.js → apps-page-vC87gAyb.js} +1 -1
  10. package/dist/gateway/static/root/assets/{archive-plugin-BH2u1E5t.js → archive-plugin-CMITc1PQ.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-BYQord-c.js → attachment-load-B5Z1PKHm.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automations-page-Ct9Lrj-E.js → automations-page-BOcXqVar.js} +1 -1
  13. package/dist/gateway/static/root/assets/{automations-workspace-CVBNAMX2.js → automations-workspace-CzYmREol.js} +1 -1
  14. package/dist/gateway/static/root/assets/{binary-plugins-B8TjGrrp.js → binary-plugins-BkRb9adx.js} +1 -1
  15. package/dist/gateway/static/root/assets/{block-editor-paecvJ38.js → block-editor-DsmDlkC2.js} +1 -1
  16. package/dist/gateway/static/root/assets/{browser-automation-inputs-DeGdfGUg.js → browser-automation-inputs-BgBH3eFk.js} +1 -1
  17. package/dist/gateway/static/root/assets/{browser-automations-page-B-BM6Tg_.js → browser-automations-page-e3KpurLo.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-settings-page-B460-Vqh.js → browser-settings-page-BxaSzUIU.js} +1 -1
  19. package/dist/gateway/static/root/assets/{capabilities-settings-panel-DPpWDWS2.js → capabilities-settings-panel-CFCu-8da.js} +1 -1
  20. package/dist/gateway/static/root/assets/{channels-settings-HgKFKpET.js → channels-settings-CENp82yt.js} +1 -1
  21. package/dist/gateway/static/root/assets/{chat-terminal-dock-DFS1iOZv.js → chat-terminal-dock-DUdkU1C5.js} +1 -1
  22. package/dist/gateway/static/root/assets/{computer-settings-page-CDF2UoSq.js → computer-settings-page-C6WR9Syy.js} +1 -1
  23. package/dist/gateway/static/root/assets/{connector-service-page-CKg4uafL.js → connector-service-page-SVKcySx0.js} +1 -1
  24. package/dist/gateway/static/root/assets/{connectors-page-CLFwm76Q.js → connectors-page-g_KTZWCb.js} +1 -1
  25. package/dist/gateway/static/root/assets/{date-picker-D569kewK.js → date-picker-DBD5yedi.js} +1 -1
  26. package/dist/gateway/static/root/assets/{dependency-picker-D9nLZuGV.js → dependency-picker-HE41Co5W.js} +1 -1
  27. package/dist/gateway/static/root/assets/{desktop-pet-ZfzVh2Kx.js → desktop-pet-DH9EU9O3.js} +1 -1
  28. package/dist/gateway/static/root/assets/{desktop-pet-settings-CTTPsaCL.js → desktop-pet-settings-Br6Z1Vau.js} +1 -1
  29. package/dist/gateway/static/root/assets/{device-pairing-wizard-BHa8llfD.js → device-pairing-wizard-DO7jp0ep.js} +1 -1
  30. package/dist/gateway/static/root/assets/{extension-debug-page-CmZp7DKt.js → extension-debug-page-CFjVNb1m.js} +1 -1
  31. package/dist/gateway/static/root/assets/{extension-page-CdCG_Qji.js → extension-page-BQ3ldBhh.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-settings-page-B7VAJYjV.js → extension-settings-page-CaXnWbRI.js} +1 -1
  33. package/dist/gateway/static/root/assets/{gateway-config-swr-CYa7aVok.js → gateway-config-swr-DgFmBSH9.js} +1 -1
  34. package/dist/gateway/static/root/assets/{gateway-settings-BW3KOAoB.js → gateway-settings-BkeSEBxk.js} +1 -1
  35. package/dist/gateway/static/root/assets/{gateway-startup-retry-FZix2Im1.js → gateway-startup-retry-C7h3xef0.js} +1 -1
  36. package/dist/gateway/static/root/assets/{home-page-BmLJEvt_.js → home-page-KoQXV4hp.js} +1 -1
  37. package/dist/gateway/static/root/assets/{image-generation-api-IDhumota.js → image-generation-api-DJ1xaz2p.js} +1 -1
  38. package/dist/gateway/static/root/assets/{imports-page-CCsDW_eB.js → imports-page-CaB54QIp.js} +1 -1
  39. package/dist/gateway/static/root/assets/{index-Ca4fdHsK.css → index-ZxnWUdPv.css} +1 -1
  40. package/dist/gateway/static/root/assets/index-u4709aCa.js +89 -0
  41. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BEnyvzkl.js → keyboard-shortcuts-settings-DnvHvKQJ.js} +1 -1
  42. package/dist/gateway/static/root/assets/{local-app-workbench-page-C6dMiCl3.js → local-app-workbench-page-7QX0YFa7.js} +1 -1
  43. package/dist/gateway/static/root/assets/{local-apps-page-DzBneHdn.js → local-apps-page-RSAl-NhO.js} +1 -1
  44. package/dist/gateway/static/root/assets/{locale-store-BrnrrtSS.js → locale-store-C4RjTd0C.js} +1 -1
  45. package/dist/gateway/static/root/assets/{logs-page-ClrOYWau.js → logs-page-N0q5DxnI.js} +1 -1
  46. package/dist/gateway/static/root/assets/{management-settings-CiZYZTuG.js → management-settings-C_cb0Kue.js} +1 -1
  47. package/dist/gateway/static/root/assets/{markdown-view-DUfjxG4s.js → markdown-view-DkPzc50Z.js} +1 -1
  48. package/dist/gateway/static/root/assets/{media-plugins-fKRKkY7W.js → media-plugins-BzLYE9lj.js} +1 -1
  49. package/dist/gateway/static/root/assets/messages-M5xl1fHE.js +3 -0
  50. package/dist/gateway/static/root/assets/{note-detail-panel-DIV2-sns.js → note-detail-panel-CVWst-Er.js} +3 -3
  51. package/dist/gateway/static/root/assets/{notes-page-DCiOBChY.js → notes-page-BFwCgIls.js} +1 -1
  52. package/dist/gateway/static/root/assets/{page-context-capture-button-C-v-_7lL.js → page-context-capture-button-DVnGa7Lu.js} +1 -1
  53. package/dist/gateway/static/root/assets/{page-tabs-DQ72O7xL.js → page-tabs-q6lpN9OD.js} +1 -1
  54. package/dist/gateway/static/root/assets/{preview-open-alternatives-DlO_Ap5J.js → preview-open-alternatives-Ck63fJaQ.js} +1 -1
  55. package/dist/gateway/static/root/assets/{product-open-page-BYoaBpad.js → product-open-page-BIGvTs9n.js} +1 -1
  56. package/dist/gateway/static/root/assets/{project-detail-page-BaDF9vxI.js → project-detail-page-BFEtpkcf.js} +1 -1
  57. package/dist/gateway/static/root/assets/{projects-page-Bo_Tgir4.js → projects-page-CHr-P2z4.js} +1 -1
  58. package/dist/gateway/static/root/assets/{registry-api-DgKqRZl1.js → registry-api-CRnytFLF.js} +1 -1
  59. package/dist/gateway/static/root/assets/{remote-access-hub-Cln6drW5.js → remote-access-hub-PhEyHSOb.js} +1 -1
  60. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-8wtw5AOq.js → runtime-tools-settings-panel-Bu3XyrDI.js} +1 -1
  61. package/dist/gateway/static/root/assets/{scenes-page-8w1c7gx5.js → scenes-page-B_5HOUzl.js} +1 -1
  62. package/dist/gateway/static/root/assets/{schema-form-BkLzQsRu.js → schema-form-CbOOKW1B.js} +1 -1
  63. package/dist/gateway/static/root/assets/{sessions-page-DBb_Dif-.js → sessions-page-CmIhlHPS.js} +1 -1
  64. package/dist/gateway/static/root/assets/settings-advanced-gate-CCOTu1jb.js +1 -0
  65. package/dist/gateway/static/root/assets/{settings-form-section-BXm_AXYs.js → settings-form-section-D3UN4rb4.js} +1 -1
  66. package/dist/gateway/static/root/assets/{settings-page-IlYic2v9.js → settings-page-BHztZayW.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-page-layout-BD--CQW8.js → settings-page-layout-nskqtzZs.js} +1 -1
  68. package/dist/gateway/static/root/assets/{setup-status-panel-COOVP1gw.js → setup-status-panel-BtukdJ-z.js} +1 -1
  69. package/dist/gateway/static/root/assets/{share-preview-page-zZqVlKwP.js → share-preview-page-DbYXEqur.js} +1 -1
  70. package/dist/gateway/static/root/assets/{shares-settings-C3nvVwkU.js → shares-settings-CG1sll8V.js} +1 -1
  71. package/dist/gateway/static/root/assets/skill-reload-api-Byq23h9c.js +1 -0
  72. package/dist/gateway/static/root/assets/{skills-page-CSlXf8xJ.js → skills-page-CK9Jeaur.js} +1 -1
  73. package/dist/gateway/static/root/assets/{skills-page.utils-DBighWxW.js → skills-page.utils-B44aWbyL.js} +1 -1
  74. package/dist/gateway/static/root/assets/{system-settings-panel-CKhtP7t2.js → system-settings-panel-BwqpUGv_.js} +1 -1
  75. package/dist/gateway/static/root/assets/{task-detail-page-BiVJDXn1.js → task-detail-page-D5q-wk_d.js} +1 -1
  76. package/dist/gateway/static/root/assets/{tunnel-api-Dz2z5iRH.js → tunnel-api-BKTZzQIT.js} +1 -1
  77. package/dist/gateway/static/root/assets/{use-autosave-D7AnGlod.js → use-autosave-BB1JBOCz.js} +1 -1
  78. package/dist/gateway/static/root/assets/{user-model-page-vFnSEuh2.js → user-model-page-DE3ZeJL3.js} +1 -1
  79. package/dist/gateway/static/root/assets/{voice-api-key-field-zOuxp9Bl.js → voice-api-key-field-C-HsgED9.js} +1 -1
  80. package/dist/gateway/static/root/assets/{voice-config-api-F6NsgxNd.js → voice-config-api-DvJKYYA0.js} +1 -1
  81. package/dist/gateway/static/root/assets/{work-discovery-overlay-o7nsVHyj.js → work-discovery-overlay-K3e5ONUo.js} +1 -1
  82. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Dnw7G5sP.js → workflow-run-setup-panel-dXRTHBoE.js} +1 -1
  83. package/dist/gateway/static/root/assets/{workflows-page-nbri1Yf4.js → workflows-page-DGZaETmN.js} +1 -1
  84. package/dist/gateway/static/root/index.html +8 -8
  85. package/dist/package.js +1 -1
  86. package/dist/src/agent/agent-manager.js +4 -4
  87. package/dist/src/agent/background-review/run-background-review.js +1 -1
  88. package/dist/src/agent/embedded/model-runtime.js +1 -1
  89. package/dist/src/agent/embedded/run-for-session.js +1 -1
  90. package/dist/src/agent/embedded/run-turn.js +236 -42
  91. package/dist/src/agent/image/tool-model-config.js +1 -1
  92. package/dist/src/agent/mcp/oauth/mcp-oauth-store.js +1 -1
  93. package/dist/src/agent/memory/compaction-ledger.d.ts +3 -0
  94. package/dist/src/agent/memory/compaction-ledger.js +128 -25
  95. package/dist/src/agent/memory/compaction.d.ts +12 -0
  96. package/dist/src/agent/memory/compaction.js +50 -9
  97. package/dist/src/agent/memory/context-recovery.d.ts +5 -1
  98. package/dist/src/agent/memory/context-recovery.js +20 -7
  99. package/dist/src/agent/service.js +2 -2
  100. package/dist/src/agent/session/session-inspector.js +1 -1
  101. package/dist/src/agent/skills/hub-lock.js +1 -1
  102. package/dist/src/agent/tasks/task-judge-service.js +3 -3
  103. package/dist/src/ai-assist/text-assist.js +1 -1
  104. package/dist/src/auth/credentials.js +2 -2
  105. package/dist/src/auth/oauth/xopc-cloud.js +1 -1
  106. package/dist/src/auth/profiles/store.js +1 -1
  107. package/dist/src/auth/sync-provider-auth.js +2 -2
  108. package/dist/src/browser/providers/browser-ext-install.js +1 -1
  109. package/dist/src/chat-commands/agent-edit.js +1 -1
  110. package/dist/src/cli/commands/agent.js +1 -1
  111. package/dist/src/cli/commands/auth.js +1 -1
  112. package/dist/src/cli/commands/doctor/checks/provider-auth.js +3 -3
  113. package/dist/src/cli/commands/models.js +1 -1
  114. package/dist/src/cli/commands/onboard/model.js +1 -1
  115. package/dist/src/cli/commands/providers.js +1 -1
  116. package/dist/src/computer/runtime.js +1 -1
  117. package/dist/src/config/index.js +4 -4
  118. package/dist/src/config/loader.js +2 -2
  119. package/dist/src/config/models-json.js +2 -2
  120. package/dist/src/config/schema.js +1 -1
  121. package/dist/src/extensions/lockfile.js +1 -1
  122. package/dist/src/gateway/agents-admin.js +1 -1
  123. package/dist/src/gateway/file-path-classifier.js +1 -1
  124. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  125. package/dist/src/gateway/hono/routes/models.js +1 -1
  126. package/dist/src/gateway/service/config-coordinator.js +1 -1
  127. package/dist/src/gateway/service.js +11 -11
  128. package/dist/src/notes/service.js +1 -1
  129. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
  130. package/dist/src/providers/index.js +2 -2
  131. package/dist/src/providers/model-catalog-persistence.js +1 -1
  132. package/dist/src/providers/model-registry.js +1 -1
  133. package/dist/src/providers/register-runtime-providers.js +1 -1
  134. package/dist/src/tui/backends/embedded-backend.js +1 -1
  135. package/dist/src/tui/tui-commands.js +1 -1
  136. package/dist/src/tui/tui-startup-resources.js +1 -1
  137. package/dist/src/tui/tui.js +2 -2
  138. package/dist/src/voice/tts/audio.js +1 -1
  139. package/dist/src/work-discovery/service.js +2 -2
  140. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  141. package/package.json +4 -4
  142. package/dist/gateway/static/root/assets/index-DZXx1pet.js +0 -89
  143. package/dist/gateway/static/root/assets/messages-BoEi2k2p.js +0 -3
  144. package/dist/gateway/static/root/assets/settings-advanced-gate-Gb5Hhv_H.js +0 -1
  145. 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 { ContextRecoveryError, assessContext, recoverContext } from "../memory/context-recovery.js";
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
- async function maybeRecoverContextOverflow(params) {
92
- const errorMessage = getAssistantTurnErrorMessage(params.agent);
93
- if (!errorMessage || !isContextOverflowError(errorMessage)) return false;
94
- const messages = stripTrailingErrorAssistantMessages(params.agent.state.messages);
95
- log.warn({
96
- conversationId: params.conversationId,
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
- messageCount: messages.length
99
- }, "Provider rejected the context window; compacting and retrying the same turn");
100
- params.onEvent?.({
101
- type: "compaction",
102
- status: "started"
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
- const recovered = await recoverContext({
106
- conversationId: params.conversationId,
107
- transcript: params.transcriptRuntime,
108
- policy: params.policy,
109
- budget: {
110
- contextWindow: params.model.contextWindow ?? 128e3,
111
- systemPrompt: params.systemPrompt,
112
- tools: params.agent.state.tools,
113
- reserveTokens: params.policy.reserveTokens,
114
- triggerThreshold: params.policy.triggerThreshold,
115
- minToolResultKeepChars: params.policy.minToolResultKeepChars
116
- },
117
- summaryModel,
118
- fallbackModels: summaryModel === params.model ? [] : [params.model],
119
- signal: params.abortSignal,
120
- providerRejected: true,
121
- preserveLastUser: true
122
- }).catch((error) => {
123
- params.onEvent?.({
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) throw new ContextRecoveryError("unrecoverable", `Context budget exceeded before provider request (${assessed.evaluation.estimatedTokens}/${assessed.evaluation.hardLimitTokens} tokens)`);
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 maybeRecoverContextOverflow({
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,7 +1,7 @@
1
1
  import { getAgentDefaultImageModelConfig, getAgentDefaultModelRef, init_schema, parseModelRef } from "../../config/schema.js";
2
2
  import { init_agent_scope, resolveDefaultAgentId } from "../agent-scope.js";
3
- import { resolveEffectiveAgentConfigForAgent } from "../../config/agent-profile.js";
4
3
  import { getDefaultModelSync, getModelsByProvider, init_providers, isProviderConfiguredSync, resolveModel } from "../../providers/index.js";
4
+ import { resolveEffectiveAgentConfigForAgent } from "../../config/agent-profile.js";
5
5
  import { resolveAgentModelFallbackValues, resolveAgentModelPrimaryValue } from "../../config/model-input.js";
6
6
  import { buildCapabilityPlansForConfig } from "../../capabilities/readiness/from-config.js";
7
7
  import "../../capabilities/readiness/index.js";
@@ -1,5 +1,5 @@
1
- import { init_paths, resolveMcpOAuthPath } from "../../../config/paths.js";
2
1
  import { init_write_file_atomic, writeTextAtomic } from "../../../infra/write-file-atomic.js";
2
+ import { init_paths, resolveMcpOAuthPath } from "../../../config/paths.js";
3
3
  import { createHash } from "node:crypto";
4
4
  import { readFile, rm } from "node:fs/promises";
5
5
  //#region src/agent/mcp/oauth/mcp-oauth-store.ts
@@ -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(MAX_HANDOVER_ITEMS) }).strict();
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 itemId(kind, text, seqs) {
29
- return createHash("sha256").update(`${kind}\0${text.trim()}\0${[...seqs].sort((a, b) => a - b).join(",")}`).digest("hex").slice(0, 20);
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 seqs = [...new Set(item.sourceSeqs)].sort((a, b) => a - b);
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 id = item.id ?? itemId(item.kind, text, seqs);
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
- if (seenIds.has(id)) throw new Error(`Compaction handover repeats item ${id}`);
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: [...new Set(item.identifiers.map((value) => value.trim()).filter(Boolean))]
160
+ sources: mergeSources(previousItem?.sources ?? [], sources),
161
+ identifiers: normalizeIdentifiers([...previousItem?.identifiers ?? [], ...identifiers]).slice(0, 20)
60
162
  });
61
163
  }
62
- const retained = [...items.values()].filter((item) => item.status !== "superseded");
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: retained
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 { items: handover?.items.map((item) => ({
119
- id: item.id,
120
- kind: item.kind,
121
- text: item.text,
122
- status: item.status,
123
- sourceSeqs: item.sources.map((source) => source.seq),
124
- identifiers: item.identifiers
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;