@xopcai/xopc 0.0.201 → 0.0.203

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 (164) 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/{activity-store-DOhnAQOQ.js → activity-store-SZAay-ze.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-D8Cw0Mve.js → agent-avatar-dicebear-DPLJtxoS.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-DyjRuuRa.js +68 -0
  6. package/dist/gateway/static/root/assets/{api-E9Bk-Idp.js → api-C1cSt2ru.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-BubrReXg.js → app-management-settings-panel-B3PCMqor.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-BY5Az2p4.js → appearance-settings-txSx3REK.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-BEhBaooZ.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-BY8843mB.js → archive-plugin-B2VVc17j.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-DG45KScf.js → attachment-load-Crk7x8qR.js} +1 -1
  12. package/dist/gateway/static/root/assets/automations-page-BUC6FL6O.js +6 -0
  13. package/dist/gateway/static/root/assets/{binary-plugins-DGIF2ZAv.js → binary-plugins-CLlJsq5D.js} +1 -1
  14. package/dist/gateway/static/root/assets/{block-editor-C1Qktr1g.js → block-editor-cJ5lVNAG.js} +46 -46
  15. package/dist/gateway/static/root/assets/browser-settings-page-Bq2m4VpX.js +8 -0
  16. package/dist/gateway/static/root/assets/{browser-workflow-inputs-D_jnndyz.js → browser-workflow-inputs-WRiFG88u.js} +1 -1
  17. package/dist/gateway/static/root/assets/{browser-workflows-page-DyIRvPaq.js → browser-workflows-page-BHLdVoOC.js} +2 -2
  18. package/dist/gateway/static/root/assets/capabilities-settings-panel-BiQbVvYU.js +7 -0
  19. package/dist/gateway/static/root/assets/{capability-presets-api--tz0mGLY.js → capability-presets-api-DmElfgC9.js} +1 -1
  20. package/dist/gateway/static/root/assets/capability-presets-settings-panel-CYlZQpLY.js +3 -0
  21. package/dist/gateway/static/root/assets/{channel-recipient-api-CkOKF9nc.js → channel-recipient-api-BUEOtPft.js} +1 -1
  22. package/dist/gateway/static/root/assets/channels-settings-DlDiCMhO.js +1 -0
  23. package/dist/gateway/static/root/assets/channels-status-swr-Dm_-934M.js +1 -0
  24. package/dist/gateway/static/root/assets/{connectors-api-FBOe9SWC.js → connectors-api-BCdn44_o.js} +1 -1
  25. package/dist/gateway/static/root/assets/connectors-page-Ce-eaPHw.js +2 -0
  26. package/dist/gateway/static/root/assets/cron-expression-D2hF3t68.js +2 -0
  27. package/dist/gateway/static/root/assets/{date-picker-Dtn7e6TW.js → date-picker-Dvjoq2mB.js} +1 -1
  28. package/dist/gateway/static/root/assets/dependency-picker-zv1OIhk3.js +1 -0
  29. package/dist/gateway/static/root/assets/desktop-pet-DJZhKLTb.js +1 -0
  30. package/dist/gateway/static/root/assets/{desktop-pet-settings-FDmaty3P.js → desktop-pet-settings-B3ubM-eY.js} +1 -1
  31. package/dist/gateway/static/root/assets/{directory-picker-path-field-X2e8xn8j.js → directory-picker-path-field-BZEJIWOT.js} +1 -1
  32. package/dist/gateway/static/root/assets/download-Dk-ZjtO6.js +1 -0
  33. package/dist/gateway/static/root/assets/{extension-debug-page-Dx4MBfga.js → extension-debug-page-Bvq3E1A1.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-Bf_FEDhM.js → extension-page-CuCJjWNm.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-settings-page-CrOgZxkf.js → extension-settings-page-CpTk_ajr.js} +1 -1
  36. package/dist/gateway/static/root/assets/{gateway-config-api-CZ4r-7qT.js → gateway-config-api-C2yM6qj7.js} +1 -1
  37. package/dist/gateway/static/root/assets/{gateway-config-swr-Cv2ywpdb.js → gateway-config-swr-CN-JHMAh.js} +1 -1
  38. package/dist/gateway/static/root/assets/{gateway-settings-DpWmVi0w.js → gateway-settings-COwi--2Q.js} +1 -1
  39. package/dist/gateway/static/root/assets/{gateway-startup-retry-undf-k5g.js → gateway-startup-retry-Dq1RyCjz.js} +1 -1
  40. package/dist/gateway/static/root/assets/{heartbeat-settings-DSQZhSvT.js → heartbeat-settings-Dc6H4VGF.js} +1 -1
  41. package/dist/gateway/static/root/assets/home-page-BB3RGxwm.js +3 -0
  42. package/dist/gateway/static/root/assets/index-CPTLS-Zn.js +85 -0
  43. package/dist/gateway/static/root/assets/index-Dm-cvIpl.css +2 -0
  44. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Bc3Quw42.js → keyboard-shortcuts-settings-cue30Z5p.js} +1 -1
  45. package/dist/gateway/static/root/assets/local-app-workbench-page-DfO6FOB9.js +2 -0
  46. package/dist/gateway/static/root/assets/local-apps-page-DKQSCsa0.js +1 -0
  47. package/dist/gateway/static/root/assets/{locale-store-CnamlR07.js → locale-store-81Gv0wwo.js} +1 -1
  48. package/dist/gateway/static/root/assets/logs-page-DODbODyl.js +2 -0
  49. package/dist/gateway/static/root/assets/{management-settings-CJmcaVje.js → management-settings--4H26QR-.js} +1 -1
  50. package/dist/gateway/static/root/assets/{markdown-split-AcuFp0TH.js → markdown-split-BTvpyjAi.js} +1 -1
  51. package/dist/gateway/static/root/assets/markdown-view-B12xeflb.css +1 -0
  52. package/dist/gateway/static/root/assets/markdown-view-B4iBrVWK.js +69 -0
  53. package/dist/gateway/static/root/assets/{media-plugins-BvFgsiJL.js → media-plugins-DrejWl8t.js} +1 -1
  54. package/dist/gateway/static/root/assets/messages-ELK_1h2D.js +3 -0
  55. package/dist/gateway/static/root/assets/{notes-page-IW7IQ9nJ.js → notes-page-DfSzeQKd.js} +1 -1
  56. package/dist/gateway/static/root/assets/notes-workbench-DxL4GhwV.js +4 -0
  57. package/dist/gateway/static/root/assets/plus-Q2yW6OE7.js +1 -0
  58. package/dist/gateway/static/root/assets/preview-open-alternatives-yjh1_7K3.js +1 -0
  59. package/dist/gateway/static/root/assets/{product-open-page-CWEzLykF.js → product-open-page-DopWBJLa.js} +1 -1
  60. package/dist/gateway/static/root/assets/project-detail-page-Bl3LJHZB.js +1 -0
  61. package/dist/gateway/static/root/assets/projects-page-DStOErTC.js +1 -0
  62. package/dist/gateway/static/root/assets/remote-access-hub-C_hh9ztK.js +1 -0
  63. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DAohEZrX.js → runtime-tools-settings-panel-_dHhbYV2.js} +2 -2
  64. package/dist/gateway/static/root/assets/{schema-form-C_OBW8A5.js → schema-form-DZhORvJZ.js} +1 -1
  65. package/dist/gateway/static/root/assets/sessions-page-DDY21KYw.js +1 -0
  66. package/dist/gateway/static/root/assets/settings-advanced-gate-BJrHIcw8.js +1 -0
  67. package/dist/gateway/static/root/assets/{settings-form-section-C7w2WS-A.js → settings-form-section-Du5eAgFS.js} +1 -1
  68. package/dist/gateway/static/root/assets/{settings-loading-skeleton-zwibve4m.js → settings-loading-skeleton-DWS27sv1.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-page-B9W03dSW.js → settings-page-DPD0MwoN.js} +1 -1
  70. package/dist/gateway/static/root/assets/setup-status-panel-CnXN4LH_.js +1 -0
  71. package/dist/gateway/static/root/assets/share-preview-page-D6HmH2ga.js +2 -0
  72. package/dist/gateway/static/root/assets/shares-settings-U3lvtO3Y.js +1 -0
  73. package/dist/gateway/static/root/assets/skill-reload-api-B6GiZgZU.js +1 -0
  74. package/dist/gateway/static/root/assets/skills-page-XVO7d3fh.js +2 -0
  75. package/dist/gateway/static/root/assets/{skills-page.utils-D9QT5cxj.js → skills-page.utils-DwxIpWrI.js} +1 -1
  76. package/dist/gateway/static/root/assets/{system-settings-panel-c1HVtv1Y.js → system-settings-panel-BmKbxR8_.js} +1 -1
  77. package/dist/gateway/static/root/assets/task-detail-page-DA6ItYrc.js +1 -0
  78. package/dist/gateway/static/root/assets/{theme-store-BDyUTObE.js → theme-store-hOF81tUQ.js} +1 -1
  79. package/dist/gateway/static/root/assets/{use-autosave-D6UkH_Ga.js → use-autosave-GJfDpf66.js} +1 -1
  80. package/dist/gateway/static/root/assets/user-context-page-CQ1QrRE6.js +1 -0
  81. package/dist/gateway/static/root/assets/{voice-api-key-field-BOxkKbGh.js → voice-api-key-field-B0zve9EE.js} +1 -1
  82. package/dist/gateway/static/root/assets/work-discovery-overlay-DrIbHnBz.js +1 -0
  83. package/dist/gateway/static/root/assets/work-discovery-page-CQW8pPec.js +1 -0
  84. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-ZA3bP65I.js → workflow-run-setup-panel-D-_zQxim.js} +2 -2
  85. package/dist/gateway/static/root/assets/workflows-page-O21l_f5c.js +5 -0
  86. package/dist/gateway/static/root/assets/{working-directory-picker-modal-BcyiDXU3.js → working-directory-picker-modal-5xYx48tV.js} +1 -1
  87. package/dist/gateway/static/root/assets/x-B_N4OPjy.js +1 -0
  88. package/dist/gateway/static/root/index.html +19 -14
  89. package/dist/package.js +1 -1
  90. package/dist/src/agent/agent-manager.js +2 -1
  91. package/dist/src/agent/embedded/run-for-session.js +3 -2
  92. package/dist/src/agent/embedded/run-turn.js +29 -16
  93. package/dist/src/agent/embedded/types.d.ts +2 -2
  94. package/dist/src/agent/embedded/xopc-stream-bridge.d.ts +2 -1
  95. package/dist/src/agent/embedded/xopc-stream-bridge.js +2 -2
  96. package/dist/src/agent/memory/context-budget.d.ts +6 -1
  97. package/dist/src/agent/memory/context-budget.js +94 -2
  98. package/dist/src/agent/prompt/cache-boundary.d.ts +2 -0
  99. package/dist/src/agent/prompt/cache-boundary.js +10 -1
  100. package/dist/src/agent/prompt/sections/project-context.d.ts +1 -4
  101. package/dist/src/agent/prompt/sections/project-context.js +6 -13
  102. package/dist/src/agent/prompt/service-prompt-builder.js +2 -1
  103. package/dist/src/agent/prompt/system-prompt.d.ts +1 -3
  104. package/dist/src/agent/prompt/system-prompt.js +6 -15
  105. package/dist/src/agent/tools/cache-stability.d.ts +3 -0
  106. package/dist/src/agent/tools/cache-stability.js +19 -0
  107. package/dist/src/agent/tools/factory.js +2 -1
  108. package/dist/src/agent-manifest/schema.d.ts +40 -20
  109. package/dist/src/agent-manifest/schema.js +4 -5
  110. package/dist/src/config/schema.d.ts +40 -20
  111. package/dist/src/extensions/types/providers.d.ts +0 -2
  112. package/dist/src/gateway/hono/lib/config-payload.d.ts +4 -1
  113. package/dist/src/gateway/hono/oauth-async.d.ts +1 -0
  114. package/dist/src/gateway/hono/oauth-async.js +14 -2
  115. package/dist/src/providers/extension-stream-bridge.js +0 -2
  116. package/dist/src/providers/google-managed-prompt-cache.d.ts +14 -0
  117. package/dist/src/providers/google-managed-prompt-cache.js +125 -0
  118. package/dist/src/providers/prompt-cache-fingerprint.d.ts +4 -0
  119. package/dist/src/providers/prompt-cache-fingerprint.js +44 -0
  120. package/dist/src/providers/prompt-cache-lifecycle.d.ts +9 -0
  121. package/dist/src/providers/prompt-cache-lifecycle.js +31 -0
  122. package/dist/src/providers/prompt-cache-observability.d.ts +20 -0
  123. package/dist/src/providers/prompt-cache-observability.js +44 -0
  124. package/dist/src/providers/prompt-cache-payload.d.ts +10 -3
  125. package/dist/src/providers/prompt-cache-payload.js +204 -31
  126. package/dist/src/providers/prompt-cache-plan.d.ts +15 -0
  127. package/dist/src/providers/prompt-cache-plan.js +29 -0
  128. package/package.json +3 -3
  129. package/dist/gateway/static/root/assets/agents-ANavi648.js +0 -68
  130. package/dist/gateway/static/root/assets/apps-page-CFh9MBxc.js +0 -1
  131. package/dist/gateway/static/root/assets/automations-page-751CeNI3.js +0 -6
  132. package/dist/gateway/static/root/assets/browser-settings-page-CGAWs-TT.js +0 -8
  133. package/dist/gateway/static/root/assets/capabilities-settings-panel-CghUK68J.js +0 -7
  134. package/dist/gateway/static/root/assets/capability-presets-settings-panel-XsTfkvUn.js +0 -3
  135. package/dist/gateway/static/root/assets/channels-settings-DkoAZZMe.js +0 -1
  136. package/dist/gateway/static/root/assets/channels-status-swr-DNDvMWpl.js +0 -1
  137. package/dist/gateway/static/root/assets/connectors-page-e4vekkAu.js +0 -2
  138. package/dist/gateway/static/root/assets/cron-expression-Q4QaYjpI.js +0 -2
  139. package/dist/gateway/static/root/assets/dependency-picker-Br6nPaM6.js +0 -1
  140. package/dist/gateway/static/root/assets/desktop-pet-DzTM6aH8.js +0 -1
  141. package/dist/gateway/static/root/assets/home-page-L0uHiu0T.js +0 -3
  142. package/dist/gateway/static/root/assets/index-Bp8I_eX4.css +0 -1
  143. package/dist/gateway/static/root/assets/index-DRrjYYzZ.js +0 -152
  144. package/dist/gateway/static/root/assets/local-app-workbench-page-Copi3sdP.js +0 -2
  145. package/dist/gateway/static/root/assets/local-apps-page-CP9X_vtz.js +0 -1
  146. package/dist/gateway/static/root/assets/logs-page-DMKq6e29.js +0 -2
  147. package/dist/gateway/static/root/assets/messages-D7kOD_o_.js +0 -3
  148. package/dist/gateway/static/root/assets/notes-workbench-Zb8U0anO.js +0 -4
  149. package/dist/gateway/static/root/assets/preview-open-alternatives-C-b34dgQ.js +0 -1
  150. package/dist/gateway/static/root/assets/project-detail-page-BuIbLnan.js +0 -1
  151. package/dist/gateway/static/root/assets/projects-page-BwUfRqNV.js +0 -1
  152. package/dist/gateway/static/root/assets/remote-access-hub-BTaXxcEi.js +0 -1
  153. package/dist/gateway/static/root/assets/sessions-page-BnHoTCD3.js +0 -1
  154. package/dist/gateway/static/root/assets/settings-advanced-gate-BTB_fxjt.js +0 -1
  155. package/dist/gateway/static/root/assets/setup-status-panel-C8Hec2Af.js +0 -1
  156. package/dist/gateway/static/root/assets/share-preview-page-6e3ks2be.js +0 -2
  157. package/dist/gateway/static/root/assets/shares-settings-Bwc5HfLI.js +0 -1
  158. package/dist/gateway/static/root/assets/skill-reload-api-D7kunhqy.js +0 -1
  159. package/dist/gateway/static/root/assets/skills-page-WgkaNzpc.js +0 -2
  160. package/dist/gateway/static/root/assets/task-detail-page-CjP-6WVU.js +0 -1
  161. package/dist/gateway/static/root/assets/user-context-page-D8ZhPDNF.js +0 -1
  162. package/dist/gateway/static/root/assets/work-discovery-overlay-Dd19yK9t.js +0 -1
  163. package/dist/gateway/static/root/assets/work-discovery-page-DUkdJl1e.js +0 -1
  164. package/dist/gateway/static/root/assets/workflows-page-C2b5W5bw.js +0 -5
@@ -1,16 +1,19 @@
1
1
  import { createLogger } from "../../utils/logger/index.js";
2
2
  import { init_logger } from "../../utils/logger.js";
3
3
  import { resolveAgentTurnTimeoutMs, runAgentTurnWithTimeout } from "../orchestration/run-agent-turn-with-timeout.js";
4
+ import { resolvePromptCachePolicy } from "../../providers/prompt-cache-plan.js";
4
5
  import { wrapStreamFnForXopcExtensions } from "./xopc-stream-bridge.js";
5
6
  import { acquireEmbeddedSessionRunner } from "./session-runner.js";
6
7
  import { getAssistantTurnErrorMessage, isAssistantTurnAborted, isAssistantTurnFailed, maybeRetryTurnAfterTransientLlmFailure, stripTrailingErrorAssistantMessages } from "../orchestration/llm-turn-retry.js";
7
- import { pruneToolResultsToFit } from "../memory/context-budget.js";
8
+ import { projectContextForModel } from "../memory/context-budget.js";
8
9
  import { abortEmbeddedRun, queueEmbeddedSteer, registerEmbeddedRun, unregisterEmbeddedRun } from "./runs.js";
9
10
  import { lastAssistantPlainText, subscribeEmbeddedSessionEvents } from "./subscribe-session.js";
10
11
  import { detectToolLoops } from "../orchestration/loop-guard.js";
11
12
  import { tryApplySessionTranscriptHygiene } from "../transcript/transcript-hygiene.js";
12
13
  import { createSqliteTranscriptRuntime } from "./transcript-runtime.js";
13
14
  import { isContextOverflowError } from "../orchestration/context-overflow.js";
15
+ import { buildPromptCacheSnapshot, observePromptCacheSnapshot } from "../../providers/prompt-cache-observability.js";
16
+ import { isPromptCacheExpired, recordPromptCacheTouch } from "../../providers/prompt-cache-lifecycle.js";
14
17
  //#region src/agent/embedded/run-turn.ts
15
18
  init_logger();
16
19
  const log = createLogger("EmbeddedRun");
@@ -98,6 +101,7 @@ async function runXopcEmbeddedTurn(params) {
98
101
  const { sessionKey, runId, userMessage, model, tools, systemPrompt, thinkingLevel, workspaceDir, sessionStore, onEvent } = params;
99
102
  const timeoutMs = params.timeoutMs || resolveAgentTurnTimeoutMs();
100
103
  const resolvedModel = requireEmbeddedModel(model, params.modelRef);
104
+ const promptCachePolicy = resolvePromptCachePolicy(params.promptCachePolicy);
101
105
  const transcriptRuntime = params.transcriptRuntime ?? (sessionStore ? await createSqliteTranscriptRuntime({
102
106
  sessionKey,
103
107
  sessionStore
@@ -119,7 +123,7 @@ async function runXopcEmbeddedTurn(params) {
119
123
  });
120
124
  const { session, reused } = runner;
121
125
  runner.piSm.setActiveTurnId?.(runId);
122
- const streamFnWithXopcExtensions = wrapStreamFnForXopcExtensions(session.agent.streamFunction);
126
+ const streamFnWithXopcExtensions = wrapStreamFnForXopcExtensions(session.agent.streamFunction, params.promptCachePolicy);
123
127
  const loggingStreamFn = (streamModel, context, options) => {
124
128
  const sourceMessages = [...context.messages];
125
129
  const hygienicMessages = tryApplySessionTranscriptHygiene(sourceMessages, streamModel);
@@ -141,26 +145,35 @@ async function runXopcEmbeddedTurn(params) {
141
145
  tools: contextTools
142
146
  };
143
147
  }
144
- const pruned = pruneToolResultsToFit({
148
+ const projection = projectContextForModel({
145
149
  messages: effectiveContext.messages,
146
150
  contextWindow: streamModel.contextWindow ?? 128e3,
147
151
  systemPrompt: effectiveContext.systemPrompt,
148
152
  tools: effectiveContext.tools,
149
- canCompact: false
153
+ canCompact: false,
154
+ reason: isPromptCacheExpired(sessionKey, streamModel, promptCachePolicy) ? "cache_expired" : "normal"
150
155
  });
151
- if (pruned.prunedToolResults > 0) {
156
+ if (projection.prunedToolResults > 0 || projection.prunedImages > 0) {
152
157
  effectiveContext = {
153
158
  ...effectiveContext,
154
- messages: pruned.messages
159
+ messages: projection.messages
155
160
  };
156
161
  log.warn({
157
162
  sessionKey,
158
163
  runId,
159
- prunedToolResults: pruned.prunedToolResults,
160
- estimatedTokens: pruned.evaluation.estimatedTokens,
161
- hardLimitTokens: pruned.evaluation.hardLimitTokens
162
- }, "Pruned old tool results to fit the model context window");
164
+ prunedToolResults: projection.prunedToolResults,
165
+ prunedImages: projection.prunedImages,
166
+ estimatedTokens: projection.evaluation.estimatedTokens,
167
+ hardLimitTokens: projection.evaluation.hardLimitTokens
168
+ }, "Projected a smaller provider context");
163
169
  }
170
+ const promptCacheSnapshot = buildPromptCacheSnapshot({
171
+ model: streamModel,
172
+ systemPrompt: effectiveContext.systemPrompt,
173
+ tools: effectiveContext.tools,
174
+ reasoning: options?.reasoning
175
+ });
176
+ const promptCacheChanges = observePromptCacheSnapshot(sessionKey, promptCacheSnapshot);
164
177
  log.debug({
165
178
  sessionKey,
166
179
  runId,
@@ -173,20 +186,20 @@ async function runXopcEmbeddedTurn(params) {
173
186
  lastUserMessagePreview: getLastUserMessagePreview(effectiveContext.messages),
174
187
  loopWarningInjected: !!loopGuard.injection,
175
188
  hiddenToolCount: loopGuard.hiddenTools.size,
189
+ promptCache: promptCacheSnapshot,
190
+ promptCacheChanges,
176
191
  ...process.env.XOPC_LOG_LLM_PAYLOAD === "true" ? { effectiveContext: {
177
192
  systemPrompt: effectiveContext.systemPrompt,
178
193
  messages: effectiveContext.messages,
179
194
  tools: effectiveContext.tools
180
195
  } } : {}
181
196
  }, "Sending messages to AI");
182
- return streamFnWithXopcExtensions(streamModel, effectiveContext, {
183
- ...options,
184
- ...params.cacheRetention ? { cacheRetention: params.cacheRetention } : {}
185
- });
197
+ return streamFnWithXopcExtensions(streamModel, effectiveContext, { ...options });
186
198
  };
187
199
  session.agent.streamFunction = loggingStreamFn;
188
- if (onEvent) unsubscribe = subscribeEmbeddedSessionEvents(session, (event) => {
189
- onEvent({
200
+ unsubscribe = subscribeEmbeddedSessionEvents(session, (event) => {
201
+ if (event.type === "message_end" && event.message.role === "assistant") recordPromptCacheTouch(sessionKey, resolvedModel, event.message.usage);
202
+ onEvent?.({
190
203
  ...event,
191
204
  runId
192
205
  });
@@ -1,5 +1,5 @@
1
1
  import type { AgentMessage, AgentToolResult, ThinkingLevel } from '@earendil-works/pi-agent-core';
2
- import type { AssistantMessageEvent, Model, Api, CacheRetention, ImageContent } from '@earendil-works/pi-ai';
2
+ import type { AssistantMessageEvent, Model, Api, ImageContent } from '@earendil-works/pi-ai';
3
3
  import type { SessionStore } from '../../session/store.js';
4
4
  import type { EmbeddedTranscriptRuntime } from './transcript-runtime.js';
5
5
  export type EmbeddedStreamEvent = {
@@ -92,7 +92,7 @@ export type RunXopcEmbeddedTurnParams = {
92
92
  tools: import('@earendil-works/pi-agent-core').AgentTool[];
93
93
  systemPrompt: string;
94
94
  thinkingLevel?: ThinkingLevel;
95
- cacheRetention?: CacheRetention;
95
+ promptCachePolicy?: import('../../providers/prompt-cache-plan.js').PromptCachePolicy;
96
96
  workspaceDir: string;
97
97
  sessionStore?: SessionStore;
98
98
  transcriptRuntime?: EmbeddedTranscriptRuntime;
@@ -1,4 +1,5 @@
1
1
  import type { StreamFn } from '@earendil-works/pi-agent-core';
2
+ import type { PromptCachePolicy } from '../../providers/prompt-cache-plan.js';
2
3
  /**
3
4
  * pi-coding-agent's default {@link createAgentSession} streamFn always routes through
4
5
  * pi-ai's HTTP `streamSimple`, so xopc plugin providers (whose models carry the sentinel
@@ -8,4 +9,4 @@ import type { StreamFn } from '@earendil-works/pi-agent-core';
8
9
  *
9
10
  * Use this by reassigning {@link Agent.streamFunction} after `createAgentSession` returns.
10
11
  */
11
- export declare function wrapStreamFnForXopcExtensions(originalStreamFn: StreamFn): StreamFn;
12
+ export declare function wrapStreamFnForXopcExtensions(originalStreamFn: StreamFn, promptCachePolicy?: PromptCachePolicy): StreamFn;
@@ -13,11 +13,11 @@ init_providers();
13
13
  *
14
14
  * Use this by reassigning {@link Agent.streamFunction} after `createAgentSession` returns.
15
15
  */
16
- function wrapStreamFnForXopcExtensions(originalStreamFn) {
16
+ function wrapStreamFnForXopcExtensions(originalStreamFn, promptCachePolicy) {
17
17
  const extensionStreamFn = createExtensionAwareStreamFn();
18
18
  return ((model, context, options) => {
19
19
  if (model.baseUrl === "extension://provider-plugin") return extensionStreamFn(model, preparePromptCacheContext(model, context), options);
20
- return originalStreamFn(model, preparePromptCacheContext(model, context), withPromptCachePayloadTransform(model, context, options));
20
+ return originalStreamFn(model, preparePromptCacheContext(model, context), withPromptCachePayloadTransform(model, context, options, promptCachePolicy));
21
21
  });
22
22
  }
23
23
  //#endregion
@@ -16,6 +16,7 @@ export interface ContextBudgetInput {
16
16
  minToolResultKeepChars?: number;
17
17
  canCompact?: boolean;
18
18
  }
19
+ export type ContextProjectionReason = 'normal' | 'cache_expired' | 'hard_limit';
19
20
  export interface ContextBudgetEvaluation {
20
21
  route: ContextBudgetRoute;
21
22
  estimatedTokens: number;
@@ -33,8 +34,12 @@ export declare function estimateTextTokens(text: string): number;
33
34
  export declare function estimateMessageTokens(message: AgentMessage): number;
34
35
  export declare function estimateMessagesTokens(messages: readonly AgentMessage[]): number;
35
36
  export declare function evaluateContextBudget(input: ContextBudgetInput): ContextBudgetEvaluation;
36
- export declare function pruneToolResultsToFit(input: ContextBudgetInput): {
37
+ export declare function projectContextForModel(input: ContextBudgetInput & {
38
+ reason: ContextProjectionReason;
39
+ recentTurns?: number;
40
+ }): {
37
41
  messages: AgentMessage[];
38
42
  evaluation: ContextBudgetEvaluation;
39
43
  prunedToolResults: number;
44
+ prunedImages: number;
40
45
  };
@@ -73,7 +73,7 @@ function evaluateContextBudget(input) {
73
73
  usagePercent: estimatedTokens / contextWindow
74
74
  };
75
75
  }
76
- function pruneToolResultsToFit(input) {
76
+ function truncateToolResultsToFit(input) {
77
77
  const initial = evaluateContextBudget(input);
78
78
  if (initial.estimatedTokens <= initial.hardLimitTokens || initial.reducibleToolResultTokens === 0) return {
79
79
  messages: input.messages,
@@ -101,5 +101,97 @@ function pruneToolResultsToFit(input) {
101
101
  prunedToolResults
102
102
  };
103
103
  }
104
+ function protectedRecentTurnStart(messages, recentTurns) {
105
+ let remaining = recentTurns;
106
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
107
+ if (messages[index].role !== "assistant") continue;
108
+ remaining -= 1;
109
+ if (remaining === 0) return index;
110
+ }
111
+ return 0;
112
+ }
113
+ function replaceOldImages(message) {
114
+ const content = message.content;
115
+ if (!Array.isArray(content)) return {
116
+ message,
117
+ removed: 0
118
+ };
119
+ let removed = 0;
120
+ const nextContent = content.map((block) => {
121
+ if (!block || typeof block !== "object" || block.type !== "image") return block;
122
+ removed += 1;
123
+ return {
124
+ type: "text",
125
+ text: "[Earlier image omitted from the model context.]"
126
+ };
127
+ });
128
+ return removed > 0 ? {
129
+ message: {
130
+ ...message,
131
+ content: nextContent
132
+ },
133
+ removed
134
+ } : {
135
+ message,
136
+ removed: 0
137
+ };
138
+ }
139
+ function projectContextForModel(input) {
140
+ const initial = evaluateContextBudget(input);
141
+ const mustFit = input.reason === "hard_limit" || initial.estimatedTokens > initial.hardLimitTokens;
142
+ if (input.reason !== "cache_expired" && !mustFit) return {
143
+ messages: input.messages,
144
+ evaluation: initial,
145
+ prunedToolResults: 0,
146
+ prunedImages: 0
147
+ };
148
+ if (input.reason === "cache_expired" && initial.usagePercent < .3 && !mustFit) return {
149
+ messages: input.messages,
150
+ evaluation: initial,
151
+ prunedToolResults: 0,
152
+ prunedImages: 0
153
+ };
154
+ const protectedStart = protectedRecentTurnStart(input.messages, input.recentTurns ?? 3);
155
+ let prunedToolResults = 0;
156
+ let prunedImages = 0;
157
+ let messages = input.messages.map((message, index) => {
158
+ if (index >= protectedStart) return message;
159
+ const imageProjection = replaceOldImages(message);
160
+ prunedImages += imageProjection.removed;
161
+ if (getToolResultTextLength(imageProjection.message) <= 3e3) return imageProjection.message;
162
+ prunedToolResults += 1;
163
+ return truncateToolResultMessage(imageProjection.message, 3e3, { minKeepChars: 1500 });
164
+ });
165
+ let evaluation = evaluateContextBudget({
166
+ ...input,
167
+ messages
168
+ });
169
+ if (input.reason === "cache_expired" && evaluation.usagePercent > .5) {
170
+ messages = messages.map((message, index) => {
171
+ if (index >= protectedStart || getToolResultTextLength(message) <= 400) return message;
172
+ prunedToolResults += 1;
173
+ return truncateToolResultMessage(message, 400, { minKeepChars: 200 });
174
+ });
175
+ evaluation = evaluateContextBudget({
176
+ ...input,
177
+ messages
178
+ });
179
+ }
180
+ if (evaluation.estimatedTokens > evaluation.hardLimitTokens) {
181
+ const fitted = truncateToolResultsToFit({
182
+ ...input,
183
+ messages
184
+ });
185
+ messages = fitted.messages;
186
+ prunedToolResults += fitted.prunedToolResults;
187
+ evaluation = fitted.evaluation;
188
+ }
189
+ return {
190
+ messages,
191
+ evaluation,
192
+ prunedToolResults,
193
+ prunedImages
194
+ };
195
+ }
104
196
  //#endregion
105
- export { estimateMessageTokens, estimateMessagesTokens, estimateTextTokens, evaluateContextBudget, pruneToolResultsToFit };
197
+ export { estimateMessageTokens, estimateMessagesTokens, estimateTextTokens, evaluateContextBudget, projectContextForModel };
@@ -22,3 +22,5 @@ export declare function splitPromptCacheBoundary(text: string): {
22
22
  * Trims trailing whitespace per line, normalizes line endings, trims overall.
23
23
  */
24
24
  export declare function normalizePromptSection(text: string): string;
25
+ /** Add stable content immediately before the cache boundary. */
26
+ export declare function appendStablePromptSection(systemPrompt: string, section: string): string;
@@ -31,5 +31,14 @@ function splitPromptCacheBoundary(text) {
31
31
  function normalizePromptSection(text) {
32
32
  return text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "").trim();
33
33
  }
34
+ /** Add stable content immediately before the cache boundary. */
35
+ function appendStablePromptSection(systemPrompt, section) {
36
+ const normalizedSection = normalizePromptSection(section);
37
+ if (!normalizedSection) return systemPrompt;
38
+ const split = splitPromptCacheBoundary(systemPrompt);
39
+ if (!split) return [normalizePromptSection(systemPrompt), normalizedSection].filter(Boolean).join("\n\n");
40
+ const stablePrefix = [split.stablePrefix, normalizedSection].filter(Boolean).join("\n\n");
41
+ return split.dynamicSuffix ? `${stablePrefix}${PROMPT_CACHE_BOUNDARY}${split.dynamicSuffix}` : `${stablePrefix}${PROMPT_CACHE_BOUNDARY}`;
42
+ }
34
43
  //#endregion
35
- export { PROMPT_CACHE_BOUNDARY, normalizePromptSection, splitPromptCacheBoundary, stripPromptCacheBoundary };
44
+ export { PROMPT_CACHE_BOUNDARY, appendStablePromptSection, normalizePromptSection, splitPromptCacheBoundary, stripPromptCacheBoundary };
@@ -1,16 +1,13 @@
1
1
  import type { EmbeddedContextFile } from '../../bootstrap/types.js';
2
2
  export declare const CONTEXT_FILE_ORDER: Map<string, number>;
3
- export declare const DYNAMIC_CONTEXT_FILE_BASENAMES: Set<string>;
4
3
  export declare const DEFAULT_HEARTBEAT_PROMPT_CONTEXT_BLOCK = "Default heartbeat prompt:\n`Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`";
5
4
  export declare function normalizeContextFilePath(pathValue: string): string;
6
5
  export declare function getContextFileBasename(pathValue: string): string;
7
- export declare function isDynamicContextFile(pathValue: string): boolean;
8
6
  export declare function sanitizeContextFileContentForPrompt(content: string): string;
9
7
  export declare function sortContextFilesForPrompt(contextFiles: EmbeddedContextFile[]): EmbeddedContextFile[];
10
8
  export declare function buildProjectContextSection(params: {
11
9
  files: EmbeddedContextFile[];
12
10
  heading: string;
13
- dynamic: boolean;
14
11
  }): string[];
15
- /** Whether HEARTBEAT.md is injected as dynamic context (vs behavior-only section). */
12
+ /** Heartbeat content belongs to the heartbeat user turn, never the system prompt. */
16
13
  export declare function isHeartbeatContextFile(pathValue: string): boolean;
@@ -9,7 +9,6 @@ const CONTEXT_FILE_ORDER = new Map([
9
9
  ["bootstrap.md", 60],
10
10
  ["memory.md", 70]
11
11
  ]);
12
- const DYNAMIC_CONTEXT_FILE_BASENAMES = new Set(["heartbeat.md"]);
13
12
  const DEFAULT_HEARTBEAT_PROMPT_CONTEXT_BLOCK = "Default heartbeat prompt:\n`Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`";
14
13
  function normalizeContextFilePath(pathValue) {
15
14
  return pathValue.trim().replace(/\\/g, "/");
@@ -18,9 +17,6 @@ function getContextFileBasename(pathValue) {
18
17
  const normalizedPath = normalizeContextFilePath(pathValue);
19
18
  return (normalizedPath.split("/").pop() ?? normalizedPath).toLowerCase();
20
19
  }
21
- function isDynamicContextFile(pathValue) {
22
- return DYNAMIC_CONTEXT_FILE_BASENAMES.has(getContextFileBasename(pathValue));
23
- }
24
20
  function sanitizeContextFileContentForPrompt(content) {
25
21
  return content.replaceAll(DEFAULT_HEARTBEAT_PROMPT_CONTEXT_BLOCK, "").replace(/\n{3,}/g, "\n\n");
26
22
  }
@@ -38,19 +34,16 @@ function sortContextFilesForPrompt(contextFiles) {
38
34
  function buildProjectContextSection(params) {
39
35
  if (params.files.length === 0) return [];
40
36
  const lines = [params.heading, ""];
41
- if (params.dynamic) lines.push("The following frequently-changing project context files are kept below the cache boundary when possible:", "");
42
- else {
43
- const hasSoulFile = params.files.some((file) => getContextFileBasename(file.path) === "soul.md");
44
- lines.push("The following project context files have been loaded:");
45
- if (hasSoulFile) lines.push("If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.");
46
- lines.push("");
47
- }
37
+ const hasSoulFile = params.files.some((file) => getContextFileBasename(file.path) === "soul.md");
38
+ lines.push("The following project context files have been loaded:");
39
+ if (hasSoulFile) lines.push("If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.");
40
+ lines.push("");
48
41
  for (const file of params.files) lines.push(`## ${file.path}`, "", sanitizeContextFileContentForPrompt(file.content), "");
49
42
  return lines;
50
43
  }
51
- /** Whether HEARTBEAT.md is injected as dynamic context (vs behavior-only section). */
44
+ /** Heartbeat content belongs to the heartbeat user turn, never the system prompt. */
52
45
  function isHeartbeatContextFile(pathValue) {
53
46
  return getContextFileBasename(pathValue) === DEFAULT_HEARTBEAT_FILENAME.toLowerCase();
54
47
  }
55
48
  //#endregion
56
- export { CONTEXT_FILE_ORDER, DEFAULT_HEARTBEAT_PROMPT_CONTEXT_BLOCK, DYNAMIC_CONTEXT_FILE_BASENAMES, buildProjectContextSection, getContextFileBasename, isDynamicContextFile, isHeartbeatContextFile, normalizeContextFilePath, sanitizeContextFileContentForPrompt, sortContextFilesForPrompt };
49
+ export { CONTEXT_FILE_ORDER, DEFAULT_HEARTBEAT_PROMPT_CONTEXT_BLOCK, buildProjectContextSection, getContextFileBasename, isHeartbeatContextFile, normalizeContextFilePath, sanitizeContextFileContentForPrompt, sortContextFilesForPrompt };
@@ -7,6 +7,7 @@ import { getInteractionState } from "../../storage/sqlite/interaction-state-repo
7
7
  import { DEFAULT_USER_TRUST_LEVEL } from "../../user-context/trust-policy.js";
8
8
  import { getUserTrustPolicy } from "../../storage/sqlite/user-trust-repository.js";
9
9
  import "../../storage/sqlite/index.js";
10
+ import { appendStablePromptSection } from "./cache-boundary.js";
10
11
  import { resolveSystemPromptBuildParams } from "./system-prompt-params.js";
11
12
  import { buildSystemPrompt } from "./system-prompt.js";
12
13
  import { resolveResponseLanguageForSession } from "./response-language.js";
@@ -85,7 +86,7 @@ var SystemPromptBuilder = class {
85
86
  customInstructions: options.customInstructions
86
87
  });
87
88
  const skillPrompt = options.skillPromptText !== void 0 ? options.skillPromptText : this.skillManager.getPromptForSkillAllowlist(options.skillAllowlist, options.registeredToolNames);
88
- const fullPrompt = skillPrompt ? `${basePrompt}\n\n${skillPrompt}` : basePrompt;
89
+ const fullPrompt = skillPrompt ? appendStablePromptSection(basePrompt, skillPrompt) : basePrompt;
89
90
  log.debug({
90
91
  baseLength: basePrompt.length,
91
92
  skillLength: skillPrompt?.length || 0,
@@ -12,7 +12,7 @@ import type { ProviderSystemPromptContribution } from './contribution.js';
12
12
  import { type RuntimeInfoInput } from './sections/workspace-runtime.js';
13
13
  import type { MemoryCitationsMode, PromptMode, SilentReplyPromptMode } from './types.js';
14
14
  export type { MemoryCitationsMode } from './types.js';
15
- export { getContextFileBasename, isDynamicContextFile, sortContextFilesForPrompt, } from './sections/project-context.js';
15
+ export { getContextFileBasename, sortContextFilesForPrompt, } from './sections/project-context.js';
16
16
  export interface SystemPromptOptions {
17
17
  contextFiles?: EmbeddedContextFile[];
18
18
  promptMode?: PromptMode;
@@ -21,8 +21,6 @@ export interface SystemPromptOptions {
21
21
  /** Registered tool names for Tooling section and memory/skills gating. */
22
22
  toolNames?: string[];
23
23
  toolSummaries?: Record<string, string>;
24
- /** @deprecated Prefer toolNames — kept for skill-section gating compatibility. */
25
- availableTools?: string[];
26
24
  memoryCitationsMode?: MemoryCitationsMode;
27
25
  includeMemorySection?: boolean;
28
26
  userTimezone?: string;
@@ -3,7 +3,7 @@ import { PROMPT_CACHE_BOUNDARY, splitPromptCacheBoundary } from "./cache-boundar
3
3
  import { buildAestheticSection, buildExecutionBiasSection, buildHumanCollaborationSection, buildProblemSolvingSection, buildSafetySection, buildToolCallStyleSection, buildWorkContinuitySection } from "./sections/behavior.js";
4
4
  import { buildExternalMemorySection, buildMemorySection, buildSkillsSection } from "./sections/memory-skills.js";
5
5
  import { buildHeartbeatBehaviorSection, buildMessagingSection, buildOutputDirectivesSection, buildSilentRepliesSection, buildTimeSection } from "./sections/messaging-runtime.js";
6
- import { buildProjectContextSection, getContextFileBasename, isDynamicContextFile, sortContextFilesForPrompt } from "./sections/project-context.js";
6
+ import { buildProjectContextSection, getContextFileBasename, isHeartbeatContextFile, sortContextFilesForPrompt } from "./sections/project-context.js";
7
7
  import { buildToolingSection, hasSkillsTools } from "./sections/tooling.js";
8
8
  import { buildRuntimeSection, buildWorkspaceFilesIntroSection, buildWorkspaceSection } from "./sections/workspace-runtime.js";
9
9
  import { buildResponseLanguageSection } from "./sections/response-language.js";
@@ -16,7 +16,7 @@ function joinSections(sections) {
16
16
  * Build system prompt with bootstrap Project Context integration.
17
17
  */
18
18
  function buildSystemPrompt(workspaceDir, options = {}) {
19
- const { contextFiles = [], promptMode = "full", heartbeatEnabled = false, heartbeatPrompt, toolNames: toolNamesOption, toolSummaries, availableTools = [], memoryCitationsMode = "on", includeMemorySection, userTimezone, runtime, agentId, channels = [], externalMemoryInstructions, ttsSystemHint, extraSystemPrompt, activeProjectContext, silentReplyPromptMode = "generic", promptContribution, includeProblemSolving = true, includeToneSection = true, actionTrustLevel, responseLanguage = "auto", customInstructions } = options;
19
+ const { contextFiles = [], promptMode = "full", heartbeatEnabled = false, heartbeatPrompt, toolNames = [], toolSummaries, memoryCitationsMode = "on", includeMemorySection, userTimezone, runtime, agentId, channels = [], externalMemoryInstructions, ttsSystemHint, extraSystemPrompt, activeProjectContext, silentReplyPromptMode = "generic", promptContribution, includeProblemSolving = true, includeToneSection = true, actionTrustLevel, responseLanguage = "auto", customInstructions } = options;
20
20
  if (promptMode === "none") return joinSections([
21
21
  "You are a personal AI assistant running inside xopc.",
22
22
  customInstructions?.trim() ? `<custom_instructions>\n${customInstructions.trim()}\n</custom_instructions>` : void 0,
@@ -24,13 +24,10 @@ function buildSystemPrompt(workspaceDir, options = {}) {
24
24
  activeProjectContext
25
25
  ]);
26
26
  const isMinimal = promptMode === "minimal";
27
- const toolNames = toolNamesOption ?? availableTools;
28
27
  const normalizedTools = new Set(toolNames.map((tool) => tool.toLowerCase()));
29
28
  const effectiveAgentId = agentId ?? runtime?.agentId;
30
29
  const sectionOverrides = promptContribution?.sectionOverrides ?? {};
31
- const orderedContextFiles = sortContextFilesForPrompt(contextFiles.filter((file) => file.path.trim().length > 0));
32
- const stableContextFiles = orderedContextFiles.filter((file) => !isDynamicContextFile(file.path));
33
- const dynamicContextFiles = orderedContextFiles.filter((file) => isDynamicContextFile(file.path));
30
+ const orderedContextFiles = sortContextFilesForPrompt(contextFiles.filter((file) => file.path.trim().length > 0 && !isHeartbeatContextFile(file.path)));
34
31
  const hasProfileMemory = orderedContextFiles.some((file) => getContextFileBasename(file.path) === "memory.md");
35
32
  const stableSections = [
36
33
  "You are a personal AI assistant running inside xopc.",
@@ -79,20 +76,14 @@ function buildSystemPrompt(workspaceDir, options = {}) {
79
76
  }));
80
77
  if (!isMinimal && ttsSystemHint?.trim()) stableSections.push(`## Voice (TTS)\n\n${ttsSystemHint.trim()}`);
81
78
  stableSections.push(joinSections(buildProjectContextSection({
82
- files: stableContextFiles,
83
- heading: "# Project Context",
84
- dynamic: false
79
+ files: orderedContextFiles,
80
+ heading: "# Project Context"
85
81
  })));
86
82
  if (!isMinimal) stableSections.push(buildSilentRepliesSection({
87
83
  isMinimal,
88
84
  silentReplyPromptMode
89
85
  }));
90
86
  const dynamicSections = [];
91
- dynamicSections.push(joinSections(buildProjectContextSection({
92
- files: dynamicContextFiles,
93
- heading: stableContextFiles.length > 0 ? "# Dynamic Project Context" : "# Project Context",
94
- dynamic: true
95
- })));
96
87
  if (extraSystemPrompt?.trim()) {
97
88
  const contextHeader = isMinimal ? "## Subagent Context" : "## Group Chat Context";
98
89
  dynamicSections.push(`${contextHeader}\n\n${extraSystemPrompt.trim()}`);
@@ -129,4 +120,4 @@ function splitBuiltSystemPrompt(text) {
129
120
  return splitPromptCacheBoundary(text);
130
121
  }
131
122
  //#endregion
132
- export { buildSystemPrompt, getContextFileBasename, isDynamicContextFile, sortContextFilesForPrompt, splitBuiltSystemPrompt };
123
+ export { buildSystemPrompt, getContextFileBasename, sortContextFilesForPrompt, splitBuiltSystemPrompt };
@@ -0,0 +1,3 @@
1
+ import type { AgentTool } from '@earendil-works/pi-agent-core';
2
+ /** Provider-visible tool order is part of the cached prompt prefix. */
3
+ export declare function sortToolsForPromptCache<T extends Pick<AgentTool, 'name' | 'description'>>(tools: readonly T[]): T[];
@@ -0,0 +1,19 @@
1
+ //#region src/agent/tools/cache-stability.ts
2
+ const TOOL_ORDER = new Map([
3
+ ["xopc_tool_search", 10],
4
+ ["xopc_tool_describe", 20],
5
+ ["xopc_tool_execute", 30]
6
+ ]);
7
+ /** Provider-visible tool order is part of the cached prompt prefix. */
8
+ function sortToolsForPromptCache(tools) {
9
+ return [...tools].sort((left, right) => {
10
+ const leftOrder = TOOL_ORDER.get(left.name) ?? Number.MAX_SAFE_INTEGER;
11
+ const rightOrder = TOOL_ORDER.get(right.name) ?? Number.MAX_SAFE_INTEGER;
12
+ if (leftOrder !== rightOrder) return leftOrder - rightOrder;
13
+ const byName = left.name.localeCompare(right.name);
14
+ if (byName !== 0) return byName;
15
+ return left.description.localeCompare(right.description);
16
+ });
17
+ }
18
+ //#endregion
19
+ export { sortToolsForPromptCache };
@@ -55,6 +55,7 @@ import { mergeTtsConfigFromAppConfig } from "../../voice/tts/merge-config.js";
55
55
  import { createTextToSpeechTool } from "./tts-tool.js";
56
56
  import { getAgentCapabilityToolNames } from "../capabilities/registry.js";
57
57
  import "../capabilities/index.js";
58
+ import { sortToolsForPromptCache } from "./cache-stability.js";
58
59
  //#region src/agent/tools/factory.ts
59
60
  init_agent_scope();
60
61
  init_logger();
@@ -352,7 +353,7 @@ var AgentToolsFactory = class {
352
353
  return ["create_desktop_pet"];
353
354
  }
354
355
  createAllTools(coreOptions) {
355
- return wrapToolsWithProtection(this.createCoreTools(coreOptions), this.deps.toolExecutorConfig);
356
+ return sortToolsForPromptCache(wrapToolsWithProtection(this.createCoreTools(coreOptions), this.deps.toolExecutorConfig));
356
357
  }
357
358
  };
358
359
  function filterToolsByDisabledSet(tools, disabled) {
@@ -250,11 +250,16 @@ export declare const RuntimePolicySchema: z.ZodOptional<z.ZodObject<{
250
250
  maxTurns: z.ZodOptional<z.ZodNumber>;
251
251
  timeoutMs: z.ZodOptional<z.ZodNumber>;
252
252
  maxToolFailuresPerTurn: z.ZodOptional<z.ZodNumber>;
253
- promptCacheRetention: z.ZodOptional<z.ZodEnum<{
254
- none: "none";
255
- short: "short";
256
- long: "long";
257
- }>>;
253
+ promptCache: z.ZodOptional<z.ZodObject<{
254
+ mode: z.ZodDefault<z.ZodEnum<{
255
+ off: "off";
256
+ auto: "auto";
257
+ }>>;
258
+ lifetime: z.ZodDefault<z.ZodEnum<{
259
+ short: "short";
260
+ long: "long";
261
+ }>>;
262
+ }, z.core.$strict>>;
258
263
  }, z.core.$strict>>;
259
264
  export declare const PromptPolicySchema: z.ZodOptional<z.ZodObject<{
260
265
  customInstructions: z.ZodOptional<z.ZodString>;
@@ -399,11 +404,16 @@ export declare const AgentManifestSchema: z.ZodObject<{
399
404
  maxTurns: z.ZodOptional<z.ZodNumber>;
400
405
  timeoutMs: z.ZodOptional<z.ZodNumber>;
401
406
  maxToolFailuresPerTurn: z.ZodOptional<z.ZodNumber>;
402
- promptCacheRetention: z.ZodOptional<z.ZodEnum<{
403
- none: "none";
404
- short: "short";
405
- long: "long";
406
- }>>;
407
+ promptCache: z.ZodOptional<z.ZodObject<{
408
+ mode: z.ZodDefault<z.ZodEnum<{
409
+ off: "off";
410
+ auto: "auto";
411
+ }>>;
412
+ lifetime: z.ZodDefault<z.ZodEnum<{
413
+ short: "short";
414
+ long: "long";
415
+ }>>;
416
+ }, z.core.$strict>>;
407
417
  }, z.core.$strict>>;
408
418
  prompt: z.ZodOptional<z.ZodObject<{
409
419
  customInstructions: z.ZodOptional<z.ZodString>;
@@ -536,11 +546,16 @@ export declare const AgentConfigEntrySchema: z.ZodObject<{
536
546
  maxTurns: z.ZodOptional<z.ZodNumber>;
537
547
  timeoutMs: z.ZodOptional<z.ZodNumber>;
538
548
  maxToolFailuresPerTurn: z.ZodOptional<z.ZodNumber>;
539
- promptCacheRetention: z.ZodOptional<z.ZodEnum<{
540
- none: "none";
541
- short: "short";
542
- long: "long";
543
- }>>;
549
+ promptCache: z.ZodOptional<z.ZodObject<{
550
+ mode: z.ZodDefault<z.ZodEnum<{
551
+ off: "off";
552
+ auto: "auto";
553
+ }>>;
554
+ lifetime: z.ZodDefault<z.ZodEnum<{
555
+ short: "short";
556
+ long: "long";
557
+ }>>;
558
+ }, z.core.$strict>>;
544
559
  }, z.core.$strict>>;
545
560
  prompt: z.ZodOptional<z.ZodObject<{
546
561
  customInstructions: z.ZodOptional<z.ZodString>;
@@ -659,11 +674,16 @@ export declare const CapabilityPresetSchema: z.ZodObject<{
659
674
  maxTurns: z.ZodOptional<z.ZodNumber>;
660
675
  timeoutMs: z.ZodOptional<z.ZodNumber>;
661
676
  maxToolFailuresPerTurn: z.ZodOptional<z.ZodNumber>;
662
- promptCacheRetention: z.ZodOptional<z.ZodEnum<{
663
- none: "none";
664
- short: "short";
665
- long: "long";
666
- }>>;
677
+ promptCache: z.ZodOptional<z.ZodObject<{
678
+ mode: z.ZodDefault<z.ZodEnum<{
679
+ off: "off";
680
+ auto: "auto";
681
+ }>>;
682
+ lifetime: z.ZodDefault<z.ZodEnum<{
683
+ short: "short";
684
+ long: "long";
685
+ }>>;
686
+ }, z.core.$strict>>;
667
687
  }, z.core.$strict>>;
668
688
  locks: z.ZodOptional<z.ZodArray<z.ZodString>>;
669
689
  }, z.core.$strict>;
@@ -157,11 +157,10 @@ var init_schema = __esmMin((() => {
157
157
  maxTurns: z.number().int().positive().optional(),
158
158
  timeoutMs: z.number().int().positive().optional(),
159
159
  maxToolFailuresPerTurn: z.number().int().positive().optional(),
160
- promptCacheRetention: z.enum([
161
- "none",
162
- "short",
163
- "long"
164
- ]).optional()
160
+ promptCache: z.object({
161
+ mode: z.enum(["off", "auto"]).default("auto"),
162
+ lifetime: z.enum(["short", "long"]).default("short")
163
+ }).strict().optional()
165
164
  }).strict().optional();
166
165
  PromptPolicySchema = z.object({ customInstructions: z.string().optional() }).strict().optional();
167
166
  AgentIdentitySchema = z.object({