@sentry/junior 0.96.0 → 0.97.0

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 (135) hide show
  1. package/dist/{agent-hooks-5URSMY3O.js → agent-hooks-XSUIVRQS.js} +8 -6
  2. package/dist/{reporting → api}/conversations/activity.d.ts +1 -1
  3. package/dist/api/conversations/detail-projection.d.ts +10 -0
  4. package/dist/api/conversations/detail.d.ts +3 -0
  5. package/dist/api/conversations/detail.query.d.ts +3 -0
  6. package/dist/api/conversations/list.d.ts +3 -0
  7. package/dist/api/conversations/list.query.d.ts +94 -0
  8. package/dist/api/conversations/projection.d.ts +14 -0
  9. package/dist/api/conversations/schema.d.ts +683 -0
  10. package/dist/{reporting → api}/conversations/shared.d.ts +1 -5
  11. package/dist/api/conversations/stats.d.ts +3 -0
  12. package/dist/api/conversations/stats.query.d.ts +3 -0
  13. package/dist/api/conversations/subagent.d.ts +3 -0
  14. package/dist/{reporting → api}/conversations/transcript.d.ts +4 -4
  15. package/dist/api/people/list.d.ts +1 -2
  16. package/dist/api/people/list.query.d.ts +3 -4
  17. package/dist/api/people/profile.d.ts +1 -2
  18. package/dist/api/people/profile.query.d.ts +3 -4
  19. package/dist/api/people/schema.d.ts +193 -0
  20. package/dist/api/people/shared.d.ts +31 -9
  21. package/dist/api/schema.d.ts +20 -0
  22. package/dist/api/schema.js +50 -0
  23. package/dist/api-reference.d.ts +4 -3
  24. package/dist/api.d.ts +3 -0
  25. package/dist/api.js +156 -0
  26. package/dist/app.d.ts +1 -4
  27. package/dist/app.js +100 -366
  28. package/dist/build/virtual-config.d.ts +2 -2
  29. package/dist/{catalog-runtime-MSMAYYIK.js → catalog-runtime-PFHVMHDR.js} +3 -2
  30. package/dist/chat/agent/resume.d.ts +3 -1
  31. package/dist/chat/agent/tools.d.ts +2 -1
  32. package/dist/chat/config.d.ts +1 -8
  33. package/dist/chat/conversations/history.d.ts +241 -27
  34. package/dist/chat/conversations/legacy-advisor-session.d.ts +7 -0
  35. package/dist/chat/conversations/legacy-import.d.ts +2 -2
  36. package/dist/chat/conversations/projection.d.ts +19 -3
  37. package/dist/chat/conversations/sql/legacy-history-import.d.ts +1 -1
  38. package/dist/chat/conversations/sql/store.d.ts +15 -9
  39. package/dist/chat/conversations/store.d.ts +9 -4
  40. package/dist/chat/model-profile.d.ts +14 -0
  41. package/dist/chat/pi/transcript.d.ts +5 -3
  42. package/dist/chat/resource-events/store.d.ts +2 -2
  43. package/dist/chat/services/context-compaction.d.ts +15 -0
  44. package/dist/chat/services/turn-result.d.ts +1 -0
  45. package/dist/chat/services/turn-session-record.d.ts +6 -6
  46. package/dist/chat/state/turn-session.d.ts +16 -5
  47. package/dist/chat/task-execution/store.d.ts +2 -2
  48. package/dist/chat/tools/handoff/tool.d.ts +45 -0
  49. package/dist/chat/tools/types.d.ts +7 -2
  50. package/dist/chat/usage.d.ts +25 -25
  51. package/dist/{chunk-RITD7E5L.js → chunk-35BNLDOJ.js} +338 -577
  52. package/dist/{chunk-PMNN6ST4.js → chunk-3ECW2K2O.js} +7 -2
  53. package/dist/{chunk-5C6XTTFI.js → chunk-3F4OPKGF.js} +89 -55
  54. package/dist/chunk-3V7OFJZC.js +264 -0
  55. package/dist/chunk-4ZNGQH7C.js +25 -0
  56. package/dist/{chunk-S5KZ5LPG.js → chunk-5HLDPVYS.js} +14 -447
  57. package/dist/{chunk-DZENG5ML.js → chunk-6U26PYI5.js} +24 -14
  58. package/dist/chunk-AUUCAFDE.js +546 -0
  59. package/dist/{chunk-6APU57E6.js → chunk-B3IT5END.js} +41 -260
  60. package/dist/chunk-CEA3A3UA.js +86 -0
  61. package/dist/{chunk-YDT7SNCN.js → chunk-D5KZYS4T.js} +1 -1
  62. package/dist/{chunk-YDVEH3SB.js → chunk-EEZCY5WX.js} +78 -28
  63. package/dist/{chunk-2NAZZPPS.js → chunk-EOWXRY5E.js} +1 -1
  64. package/dist/{chunk-GQ3GK22G.js → chunk-FBHZJFHI.js} +2 -2
  65. package/dist/{chunk-CZEX6KIQ.js → chunk-G3E7SCME.js} +0 -23
  66. package/dist/chunk-GUAP3H7O.js +319 -0
  67. package/dist/{chunk-BYZWPRJA.js → chunk-H66UCBPW.js} +6 -4
  68. package/dist/{chunk-L7CDEIVJ.js → chunk-LMJPRYHP.js} +4 -2
  69. package/dist/chunk-LUAE4MGW.js +199 -0
  70. package/dist/{chunk-GY34VGP5.js → chunk-LVA4I4H7.js} +3 -1
  71. package/dist/chunk-LVUKF5CE.js +63 -0
  72. package/dist/{chunk-AAZXTVJT.js → chunk-NG2AV7IB.js} +2 -72
  73. package/dist/chunk-ON6YKIQ7.js +260 -0
  74. package/dist/chunk-PDO5BLNM.js +22 -0
  75. package/dist/chunk-QNVHH5PU.js +20 -0
  76. package/dist/{chunk-P3YFQLEN.js → chunk-T533JLM3.js} +8 -10
  77. package/dist/chunk-UAMKJPDF.js +11 -0
  78. package/dist/chunk-VFH4ZKZT.js +66 -0
  79. package/dist/{chunk-UKHVMPX5.js → chunk-WGXMTFNU.js} +15 -17
  80. package/dist/{chunk-MNTINMWR.js → chunk-WJOSZOON.js} +184 -300
  81. package/dist/chunk-WTYEKPXU.js +451 -0
  82. package/dist/{chunk-IYM2SIVG.js → chunk-XIRPQQVE.js} +605 -363
  83. package/dist/cli/chat.js +24 -21
  84. package/dist/cli/check.js +4 -3
  85. package/dist/cli/init.js +12 -0
  86. package/dist/cli/plugins.js +12 -9
  87. package/dist/cli/snapshot-warmup.js +8 -6
  88. package/dist/cli/upgrade.js +265 -137
  89. package/dist/db/db.d.ts +4 -0
  90. package/dist/db/schema/conversations.d.ts +128 -0
  91. package/dist/db/schema.d.ts +129 -60
  92. package/dist/{db-2D6RMG67.js → db-BWXYIKFQ.js} +3 -3
  93. package/dist/detail-GA6NXWQJ.js +43 -0
  94. package/dist/discovery-RMHYSRL7.js +43 -0
  95. package/dist/handlers/health.d.ts +3 -0
  96. package/dist/instrumentation.js +1 -1
  97. package/dist/{legacy-import-VZ2MEGP7.js → legacy-import-3NQJVGQL.js} +4 -4
  98. package/dist/{api/people/list.js → list-A3WFRIYX.js} +26 -16
  99. package/dist/list-UXJI5Q5X.js +27 -0
  100. package/dist/nitro.d.ts +1 -1
  101. package/dist/nitro.js +2 -1
  102. package/dist/plugin-conversations-DDNGC35A.js +36 -0
  103. package/dist/{api/people/profile.js → profile-4RYDXWTF.js} +33 -18
  104. package/dist/reporting/plugin-conversations.d.ts +3 -0
  105. package/dist/reporting-schema.d.ts +140 -0
  106. package/dist/reporting.d.ts +12 -81
  107. package/dist/{runner-JZBRQP4F.js → runner-JALSYH2P.js} +17 -12
  108. package/dist/skills-MAZXZ2KE.js +26 -0
  109. package/dist/stats-XKXH3PTY.js +217 -0
  110. package/dist/subagent-TZ55XY37.js +48 -0
  111. package/dist/usage-schema.d.ts +25 -0
  112. package/dist/{validation-6XOOQGME.js → validation-FMEAEKCY.js} +4 -3
  113. package/migrations/0000_initial.sql +115 -0
  114. package/migrations/0001_conversation_metrics.sql +4 -0
  115. package/migrations/README.md +12 -30
  116. package/migrations/meta/0000_snapshot.json +1 -33
  117. package/migrations/meta/0001_snapshot.json +972 -0
  118. package/migrations/meta/_journal.json +9 -2
  119. package/package.json +14 -18
  120. package/dist/api/people/types.d.ts +0 -85
  121. package/dist/chat/advisor-request.d.ts +0 -4
  122. package/dist/chat/conversations/sql/migrations.d.ts +0 -22
  123. package/dist/chat/tools/advisor/session-store.d.ts +0 -9
  124. package/dist/chat/tools/advisor/tool.d.ts +0 -72
  125. package/dist/chunk-RAIB6WSG.js +0 -142
  126. package/dist/chunk-WM6KHPBQ.js +0 -48
  127. package/dist/db/schema/migrations.d.ts +0 -58
  128. package/dist/reporting/conversations/context.d.ts +0 -8
  129. package/dist/reporting/conversations/details.d.ts +0 -6
  130. package/dist/reporting/conversations/index.d.ts +0 -4
  131. package/dist/reporting/conversations/statistics.d.ts +0 -4
  132. package/dist/reporting/conversations/summaries.d.ts +0 -20
  133. package/dist/reporting/conversations/types.d.ts +0 -191
  134. package/dist/reporting/conversations.d.ts +0 -1
  135. package/dist/reporting.js +0 -1155
@@ -10,7 +10,25 @@ import {
10
10
  persistRunningSessionRecord,
11
11
  persistTimeoutSessionRecord,
12
12
  persistYieldSessionRecord
13
- } from "./chunk-UKHVMPX5.js";
13
+ } from "./chunk-WGXMTFNU.js";
14
+ import {
15
+ JUNIOR_THREAD_STATE_TTL_MS,
16
+ addAgentTurnUsage,
17
+ hasAgentTurnUsage
18
+ } from "./chunk-WJOSZOON.js";
19
+ import {
20
+ loadConnectedMcpProviders,
21
+ loadConversationProjection,
22
+ openConversationProjection,
23
+ recordAuthorizationRequested,
24
+ recordMcpProviderConnected,
25
+ recordToolExecutionStarted
26
+ } from "./chunk-GUAP3H7O.js";
27
+ import {
28
+ estimateTextTokens,
29
+ getAgentContextCompactionTriggerTokens,
30
+ updateConversationStats
31
+ } from "./chunk-3F4OPKGF.js";
14
32
  import {
15
33
  buildNonInteractiveShellScript,
16
34
  createSandboxInstance,
@@ -20,66 +38,58 @@ import {
20
38
  isSnapshotMissingError,
21
39
  resolveRuntimeDependencySnapshot,
22
40
  runNonInteractiveCommand
23
- } from "./chunk-BYZWPRJA.js";
41
+ } from "./chunk-H66UCBPW.js";
24
42
  import {
25
43
  privateTraceResultAttributes
26
44
  } from "./chunk-CM5EZD5F.js";
27
45
  import {
28
- renderAdvisorRequest
29
- } from "./chunk-WM6KHPBQ.js";
30
- import {
31
- addAgentTurnUsage,
32
- commitMessages,
33
- hasAgentTurnUsage,
34
- loadConnectedMcpProviders,
35
- loadProjection,
36
- recordAuthorizationRequested,
37
- recordMcpProviderConnected,
38
- recordToolExecutionStarted
39
- } from "./chunk-MNTINMWR.js";
40
- import {
41
- JUNIOR_THREAD_STATE_TTL_MS
42
- } from "./chunk-5C6XTTFI.js";
43
- import {
44
- SlackActionError,
45
46
  createPluginHookRunner,
46
- downloadPrivateSlackFile,
47
- getFilePermalink,
48
47
  getPluginSystemPromptContributions,
49
48
  getPluginTools,
50
49
  getPluginUserPromptContributions,
51
50
  getPlugins,
52
- getSlackClient,
53
51
  getSlackToolContext,
52
+ resolveChannelCapabilities
53
+ } from "./chunk-5HLDPVYS.js";
54
+ import {
55
+ SlackActionError,
56
+ downloadPrivateSlackFile,
57
+ getFilePermalink,
58
+ getSlackClient,
54
59
  isConversationScopedChannel,
55
60
  isDmChannel,
56
61
  normalizeSlackConversationId,
57
- resolveChannelCapabilities,
58
62
  withSlackRetries
59
- } from "./chunk-S5KZ5LPG.js";
63
+ } from "./chunk-WTYEKPXU.js";
60
64
  import {
61
65
  createPluginLogger
62
- } from "./chunk-2NAZZPPS.js";
63
- import {
64
- SANDBOX_DATA_ROOT,
65
- SANDBOX_SKILLS_ROOT,
66
- SANDBOX_WORKSPACE_ROOT,
67
- sandboxSkillDir,
68
- sandboxSkillFile,
69
- sleep
70
- } from "./chunk-CZEX6KIQ.js";
66
+ } from "./chunk-EOWXRY5E.js";
71
67
  import {
68
+ contextProvenance,
72
69
  getAgentStepStore,
73
70
  getConversationStore,
74
71
  getDb,
75
72
  instructionActors,
76
73
  instructionProvenanceFor
77
- } from "./chunk-RITD7E5L.js";
74
+ } from "./chunk-35BNLDOJ.js";
78
75
  import {
76
+ SANDBOX_DATA_ROOT,
77
+ SANDBOX_SKILLS_ROOT,
78
+ SANDBOX_WORKSPACE_ROOT,
79
+ sandboxSkillDir,
80
+ sandboxSkillFile
81
+ } from "./chunk-G3E7SCME.js";
82
+ import {
83
+ sleep
84
+ } from "./chunk-4ZNGQH7C.js";
85
+ import {
86
+ DEFAULT_HANDOFF_MODEL_PROFILE,
79
87
  GEN_AI_PROVIDER_NAME,
80
88
  GEN_AI_SERVER_ADDRESS,
81
89
  GEN_AI_SERVER_PORT,
82
90
  MISSING_GATEWAY_CREDENTIALS_ERROR,
91
+ ModelProfileNotConfiguredError,
92
+ STANDARD_MODEL_PROFILE,
83
93
  TURN_CONTEXT_TAG,
84
94
  acquireActiveLock,
85
95
  botConfig,
@@ -96,13 +106,17 @@ import {
96
106
  isProviderRetryError,
97
107
  isToolResultError,
98
108
  isToolResultMessage,
109
+ modelIdForProfile,
99
110
  nextProviderRetry,
100
111
  normalizeSlackEmojiName,
101
112
  normalizeToolNameFromResult,
102
113
  renderCurrentInstruction,
103
114
  resolveGatewayModel,
104
- stripRuntimeTurnContext
105
- } from "./chunk-YDVEH3SB.js";
115
+ retainRuntimeTurnContext,
116
+ stripRuntimeTurnContext,
117
+ trimTrailingAssistantMessages,
118
+ unwrapCurrentInstruction
119
+ } from "./chunk-EEZCY5WX.js";
106
120
  import {
107
121
  parseSlackThreadId,
108
122
  resolveConversationPrivacy,
@@ -114,7 +128,7 @@ import {
114
128
  toGenAiPayloadMetadata,
115
129
  toGenAiPayloadTraceAttributes,
116
130
  toGenAiTextMetadata
117
- } from "./chunk-GY34VGP5.js";
131
+ } from "./chunk-LVA4I4H7.js";
118
132
  import {
119
133
  parseDestination
120
134
  } from "./chunk-B7PUBQQM.js";
@@ -123,14 +137,14 @@ import {
123
137
  findSkillByName,
124
138
  loadSkillsByName,
125
139
  parseSkillInvocation
126
- } from "./chunk-PMNN6ST4.js";
140
+ } from "./chunk-3ECW2K2O.js";
127
141
  import {
128
142
  CredentialUnavailableError,
129
143
  credentialContextSchema,
130
144
  pluginCatalogRuntime,
131
145
  resolveAuthTokenPlaceholder,
132
146
  resolvePluginCommandEnv
133
- } from "./chunk-L7CDEIVJ.js";
147
+ } from "./chunk-LMJPRYHP.js";
134
148
  import {
135
149
  createActor,
136
150
  extractGenAiUsageAttributes,
@@ -157,16 +171,16 @@ import {
157
171
  startInactiveSpan,
158
172
  withActiveSpan
159
173
  } from "./chunk-6GWA276C.js";
160
- import {
161
- listReferenceFiles,
162
- soulPathCandidates,
163
- worldPathCandidates
164
- } from "./chunk-6APU57E6.js";
165
174
  import {
166
175
  parseSlackChannelId,
167
176
  parseSlackMessageTs,
168
177
  parseSlackUserId
169
178
  } from "./chunk-7FBGKXPE.js";
179
+ import {
180
+ listReferenceFiles,
181
+ soulPathCandidates,
182
+ worldPathCandidates
183
+ } from "./chunk-3V7OFJZC.js";
170
184
 
171
185
  // src/chat/configuration/defaults.ts
172
186
  var installDefaults = {};
@@ -249,7 +263,7 @@ function shouldPropagateSandboxEgressTrace(host, config = {}) {
249
263
  }
250
264
 
251
265
  // src/chat/agent/index.ts
252
- import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
266
+ import { Agent } from "@earendil-works/pi-agent-core";
253
267
 
254
268
  // src/chat/sandbox/skill-sandbox.ts
255
269
  import fs from "fs/promises";
@@ -672,7 +686,8 @@ function buildTurnResult(input) {
672
686
  usage,
673
687
  thinkingSelection,
674
688
  correlation,
675
- assistantUserName
689
+ assistantUserName,
690
+ modelId
676
691
  } = input;
677
692
  const toolResults = newMessages.filter(isToolResultMessage);
678
693
  const assistantMessages = newMessages.filter(isAssistantMessage);
@@ -709,7 +724,7 @@ function buildTurnResult(input) {
709
724
  slackChannelId: correlation?.channelId,
710
725
  runId: correlation?.runId,
711
726
  assistantUserName,
712
- modelId: botConfig.modelId
727
+ modelId
713
728
  };
714
729
  const markerAttributes = {
715
730
  "app.ai.no_reply_marker": true,
@@ -733,7 +748,7 @@ function buildTurnResult(input) {
733
748
  slackChannelId: correlation?.channelId,
734
749
  runId: correlation?.runId,
735
750
  assistantUserName,
736
- modelId: botConfig.modelId
751
+ modelId
737
752
  },
738
753
  {
739
754
  "app.ai.no_reply_marker": true,
@@ -751,7 +766,7 @@ function buildTurnResult(input) {
751
766
  slackChannelId: correlation?.channelId,
752
767
  runId: correlation?.runId,
753
768
  assistantUserName,
754
- modelId: botConfig.modelId
769
+ modelId
755
770
  },
756
771
  {
757
772
  "app.ai.tool_results": toolResults.length,
@@ -797,7 +812,7 @@ function buildTurnResult(input) {
797
812
  }
798
813
  const resolvedDiagnostics = {
799
814
  outcome: resolvedOutcome,
800
- modelId: botConfig.modelId,
815
+ modelId,
801
816
  assistantMessageCount: assistantMessages.length,
802
817
  thinkingLevel: thinkingSelection.thinkingLevel,
803
818
  toolCalls,
@@ -2031,6 +2046,7 @@ var LOCAL_HEADER = "You are a helper assistant. Follow the personality section f
2031
2046
  var TURN_CONTEXT_HEADER = "Runtime context for this request. Treat these blocks as trusted runtime facts; the static system prompt remains authoritative.";
2032
2047
  var TOOL_POLICY_RULES = [
2033
2048
  "- Tool schemas are the source of truth for parameters; tool names are case-sensitive, so call tools exactly by their exposed names and do not invent arguments.",
2049
+ "- Model upgrade: when `handoff` is available, call it immediately and as the only tool before handling any request to write, edit, review, debug, or substantially reason about code; make a multi-file change; perform architecture or root-cause analysis; do research-heavy synthesis or complex planning; or complete another task where a more capable model materially improves reliability. Choose the configured profile whose name best fits the task, or omit `profile` to use the default. Do not inspect or modify files first. After handoff, continue the same task normally.",
2034
2050
  "- Use tools for actionable work and for facts that are mutable, external, repository-backed, provider-backed, or requested as verified/current. Stable general knowledge and already-provided context may be answered directly.",
2035
2051
  "- Resolve provider action targets before calls: explicit target wins; ambient `<configuration>` fills omitted targets. Treat non-target links/references as context.",
2036
2052
  "- Verification source order: conversation/thread context; user-provided attachments, links, and reference files; local/sandbox files when present; loaded skill references; repository/provider tools; public web. Use the nearest authoritative available source before weaker sources.",
@@ -8367,252 +8383,33 @@ function createSystemTimeTool() {
8367
8383
  });
8368
8384
  }
8369
8385
 
8370
- // src/chat/tools/advisor/tool.ts
8371
- import {
8372
- Agent
8373
- } from "@earendil-works/pi-agent-core";
8386
+ // src/chat/tools/handoff/tool.ts
8374
8387
  import { z as z33 } from "zod";
8375
- function advisorChildConversationId(parentConversationId) {
8376
- return `advisor:${parentConversationId}`;
8377
- }
8378
- var ADVISOR_TOOL_DESCRIPTION = "High-reasoning technical advisor for planning and reviewing non-trivial work. Use it at two high-leverage checkpoints: (1) after enough investigation to understand the problem, but before implementation, to challenge the proposed approach, architecture, risks, alternatives, and verification plan; and (2) after implementation and initial validation, but before finalizing, committing, opening a PR, or reporting completion, to review the diff and results for correctness, regressions, omissions, and insufficient testing. Also consult it when requirements remain ambiguous, risk is unusually high, or repeated attempts have failed. Ask a focused question and provide self-contained, curated context such as requirements, exact evidence, constraints, relevant code, command output, proposed plan, alternatives considered, diff, and validation results. The advisor does not automatically receive the parent transcript and may use read-only tools to verify supplied context. For follow-ups, include new evidence, changes made, or the specific unresolved concern. Do not use it for greetings, routine formatting, simple deterministic edits, or as a substitute for direct investigation and validation.";
8379
- var ADVISOR_SYSTEM_PROMPT = [
8380
- "You are a senior technical advisor for the executor.",
8381
- "Analyze the executor-supplied context deeply. Use read-only tools when direct inspection or verification would materially improve the advice.",
8382
- "Distinguish evidence from inference. Treat the advisor task as the focus for this call and the executor context as the starting evidence packet.",
8383
- "Do not assume access to parent transcript or tool output that was not included or gathered in this advisor call.",
8384
- "Use only the read-only tools provided to you.",
8385
- "Do not make user-visible side effects, post Slack messages, or mutate files. If a mutating action is needed, recommend it to the executor instead.",
8386
- "Identify the hard part, recommend a concrete plan or correction, call out blocking risks, and propose focused verification.",
8387
- "If the supplied context is insufficient, say exactly what additional evidence the executor needs to gather before acting.",
8388
- "Do not write user-facing prose.",
8389
- "Use concise technical memo sections when helpful: Assessment, Recommended Plan, Risks, Verification, Stop Conditions."
8390
- ].join("\n");
8391
- function lastAssistantMessage(messages) {
8392
- for (let index = messages.length - 1; index >= 0; index -= 1) {
8393
- const message = messages[index];
8394
- if (isAssistantMessage(message)) {
8395
- return message;
8396
- }
8397
- }
8398
- return void 0;
8399
- }
8400
- function failure(errorCode, text = `Advisor guidance is unavailable (${errorCode}). Continue only if the next step is clear from verified evidence.`) {
8401
- return {
8402
- ok: false,
8403
- status: "error",
8404
- error_code: errorCode,
8405
- memo: text
8406
- };
8407
- }
8408
- function success(memo) {
8409
- return {
8410
- ok: true,
8411
- status: "success",
8412
- memo
8413
- };
8414
- }
8415
- function hasReadOnlyToolAnnotations(annotations) {
8416
- return annotations?.readOnlyHint === true && annotations.destructiveHint !== true;
8417
- }
8418
- function createAdvisorToolDefinitions(definitions) {
8419
- return Object.fromEntries(
8420
- Object.entries(definitions).filter(
8421
- ([name, definition]) => name !== "callMcpTool" && name !== "searchMcpTools" && hasReadOnlyToolAnnotations(definition.annotations)
8422
- )
8423
- );
8424
- }
8425
- function createAdvisorTool(context) {
8426
- const conversationStore = () => context.conversationStore ?? getConversationStore();
8427
- const spanContext = context.logContext ?? {};
8388
+ var HANDOFF_TOOL_NAME = "handoff";
8389
+ function createHandoffTool(handoff) {
8390
+ const profileSchema = z33.enum(handoff.profiles);
8391
+ const defaultProfile = handoff.profiles[0];
8392
+ const handoffResultSchema = juniorToolResultSchema.extend({
8393
+ model_profile: profileSchema
8394
+ });
8395
+ const profileNames = handoff.profiles.map((profile) => `\`${profile}\``).join(", ");
8428
8396
  return zodTool({
8429
- description: ADVISOR_TOOL_DESCRIPTION,
8397
+ description: `Permanently switch this conversation to a more capable model profile, replace prior context with a continuation summary, and continue the same task with the same workspace and all other normal tools. Available profiles: ${profileNames}. Omit profile to use \`${defaultProfile}\`. Call it as the only tool in the assistant message when the system tool policy requires a model upgrade.`,
8398
+ executionMode: "sequential",
8430
8399
  inputSchema: z33.object({
8431
- question: z33.string().min(1).describe("Focused advisor question or decision point."),
8432
- context: z33.string().min(1).describe(
8433
- "Curated evidence packet: relevant requirements, constraints, current plan, alternatives, code snippets, diffs, command output, and open questions."
8400
+ profile: profileSchema.nullish().describe(
8401
+ "Named model profile to use for the rest of the conversation; omit or pass null for the default"
8434
8402
  )
8435
- }),
8436
- outputSchema: juniorToolResultSchema,
8437
- execute: async ({ question, context: suppliedContext }, { toolCallId } = {}) => {
8438
- if (typeof question !== "string" || !question.trim()) {
8439
- return failure(
8440
- "invalid_question",
8441
- "Advisor guidance is unavailable because the question was empty or invalid. Ask a focused advisor question before retrying."
8442
- );
8443
- }
8444
- const advisorQuestion = question.trim();
8445
- if (typeof suppliedContext !== "string" || !suppliedContext.trim()) {
8446
- return failure(
8447
- "invalid_context",
8448
- "Advisor guidance is unavailable because the curated context was empty or invalid. Include the relevant evidence and constraints before retrying."
8449
- );
8450
- }
8451
- const advisorContext = suppliedContext.trim();
8452
- if (!context.conversationId) {
8453
- return failure(
8454
- "missing_conversation_id",
8455
- "Advisor guidance is unavailable because this turn has no parent conversation id. Continue without assuming advisor history."
8456
- );
8457
- }
8458
- const conversationId = context.conversationId;
8459
- const childConversationId = advisorChildConversationId(conversationId);
8460
- const subagentInvocationId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : `advisor:${Date.now()}`;
8461
- const endSubagent = async (outcome, errorCode) => {
8462
- try {
8463
- await getAgentStepStore().append(conversationId, [
8464
- {
8465
- entry: {
8466
- type: "subagent_ended",
8467
- subagentInvocationId,
8468
- outcome,
8469
- ...errorCode ? { errorCode } : {}
8470
- },
8471
- createdAtMs: Date.now()
8472
- }
8473
- ]);
8474
- } catch (error) {
8475
- logWarn("advisor_subagent_ended_append_failed", spanContext, {
8476
- "exception.message": error instanceof Error ? error.message : String(error)
8477
- });
8478
- }
8479
- };
8480
- try {
8481
- await conversationStore().ensureChildConversation({
8482
- conversationId: childConversationId,
8483
- parentConversationId: conversationId
8484
- });
8485
- await getAgentStepStore().append(conversationId, [
8486
- {
8487
- entry: {
8488
- type: "subagent_started",
8489
- subagentInvocationId,
8490
- subagentKind: "advisor",
8491
- modelId: context.config.modelId,
8492
- ...typeof toolCallId === "string" && toolCallId.length > 0 ? { parentToolCallId: toolCallId } : {},
8493
- childConversationId,
8494
- historyMode: "shared",
8495
- reasoningLevel: context.config.thinkingLevel
8496
- },
8497
- createdAtMs: Date.now()
8498
- }
8499
- ]);
8500
- } catch (error) {
8501
- logWarn("advisor_subagent_started_append_failed", spanContext, {
8502
- "exception.message": error instanceof Error ? error.message : String(error)
8503
- });
8504
- }
8505
- const conversationPrivacy = context.conversationPrivacy ?? "private";
8506
- const requestText = renderAdvisorRequest(advisorQuestion, advisorContext);
8507
- const advisorInputMessage = {
8508
- role: "user",
8509
- content: [{ type: "text", text: requestText }],
8510
- timestamp: Date.now()
8403
+ }).strict(),
8404
+ outputSchema: handoffResultSchema,
8405
+ execute: async (input, options) => {
8406
+ const profile = input.profile ?? defaultProfile;
8407
+ await handoff.execute(profile, options.signal);
8408
+ return {
8409
+ ok: true,
8410
+ status: "success",
8411
+ model_profile: profile
8511
8412
  };
8512
- const advisorInputMessagesAttribute = serializeGenAiAttribute(
8513
- conversationPrivacy !== "public" ? [toGenAiMessageMetadata(advisorInputMessage)] : [toCanonicalInputMessage(advisorInputMessage)]
8514
- );
8515
- return await withSpan(
8516
- `invoke_agent ${context.config.modelId}`,
8517
- "gen_ai.invoke_agent",
8518
- spanContext,
8519
- async () => {
8520
- const requestMessage = {
8521
- role: "user",
8522
- content: [{ type: "text", text: requestText }],
8523
- timestamp: Date.now()
8524
- };
8525
- let advisorMessages;
8526
- try {
8527
- advisorMessages = await loadProjection({
8528
- conversationId: childConversationId
8529
- });
8530
- } catch {
8531
- setSpanStatus("error");
8532
- await endSubagent("error", "session_unavailable");
8533
- return failure(
8534
- "session_unavailable",
8535
- "Advisor guidance is unavailable because advisor history could not be loaded. Continue without assuming advisor history."
8536
- );
8537
- }
8538
- const apiKeyOverride = getPiGatewayApiKey();
8539
- const advisorAgent = new Agent({
8540
- ...apiKeyOverride ? { getApiKey: () => apiKeyOverride } : {},
8541
- initialState: {
8542
- systemPrompt: ADVISOR_SYSTEM_PROMPT,
8543
- model: resolveGatewayModel(context.config.modelId),
8544
- thinkingLevel: context.config.thinkingLevel,
8545
- tools: context.getTools()
8546
- },
8547
- sessionId: childConversationId,
8548
- streamFn: context.streamFn
8549
- });
8550
- advisorAgent.state.messages = advisorMessages;
8551
- const beforeMessageCount = advisorAgent.state.messages.length;
8552
- try {
8553
- await advisorAgent.prompt(requestMessage);
8554
- } catch {
8555
- setSpanStatus("error");
8556
- await endSubagent("error", "unavailable");
8557
- return failure(
8558
- "unavailable",
8559
- "Advisor guidance is unavailable. Continue without advisor guidance if the next step is clear from verified evidence."
8560
- );
8561
- }
8562
- const assistant = lastAssistantMessage(advisorAgent.state.messages);
8563
- const newAdvisorMessages = advisorAgent.state.messages.slice(beforeMessageCount);
8564
- const outputMessages = newAdvisorMessages.filter(isAssistantMessage);
8565
- const outputMessagesAttribute = serializeGenAiAttribute(
8566
- conversationPrivacy !== "public" ? outputMessages.map(toGenAiMessageMetadata) : outputMessages.map(toCanonicalOutputMessage)
8567
- );
8568
- setSpanAttributes({
8569
- ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
8570
- ...toGenAiMessagesTraceAttributes("app.ai.output", outputMessages),
8571
- ...extractGenAiUsageAttributes(...newAdvisorMessages)
8572
- });
8573
- if (!assistant || assistant.stopReason === "error" || assistant.stopReason === "aborted") {
8574
- setSpanStatus("error");
8575
- await endSubagent(
8576
- assistant?.stopReason === "aborted" ? "aborted" : "error",
8577
- "unavailable"
8578
- );
8579
- return failure(
8580
- "unavailable",
8581
- "Advisor guidance is unavailable. Continue without advisor guidance if the next step is clear from verified evidence."
8582
- );
8583
- }
8584
- const memo = extractAssistantText(assistant);
8585
- try {
8586
- await commitMessages({
8587
- conversationId: childConversationId,
8588
- messages: advisorAgent.state.messages
8589
- });
8590
- } catch {
8591
- setSpanStatus("error");
8592
- await endSubagent("error", "session_unavailable");
8593
- return failure(
8594
- "session_unavailable",
8595
- "Advisor guidance is unavailable because advisor history could not be saved. Retry the advisor call or continue without assuming advisor history."
8596
- );
8597
- }
8598
- await endSubagent("success");
8599
- setSpanStatus("ok");
8600
- return success(memo);
8601
- },
8602
- {
8603
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8604
- "gen_ai.operation.name": "invoke_agent",
8605
- "gen_ai.request.model": context.config.modelId,
8606
- "gen_ai.output.type": "text",
8607
- "server.address": GEN_AI_SERVER_ADDRESS,
8608
- "server.port": GEN_AI_SERVER_PORT,
8609
- "app.conversation.privacy": conversationPrivacy,
8610
- ...toGenAiMessagesTraceAttributes("app.ai.input", [
8611
- advisorInputMessage
8612
- ]),
8613
- ...advisorInputMessagesAttribute ? { "gen_ai.input.messages": advisorInputMessagesAttribute } : {}
8614
- }
8615
- );
8616
8413
  }
8617
8414
  });
8618
8415
  }
@@ -9340,8 +9137,8 @@ function createTools(availableSkills, hooks = {}, context) {
9340
9137
  hooks.toolOverrides?.imageGenerate
9341
9138
  );
9342
9139
  }
9343
- if (context.advisor) {
9344
- tools.advisor = createAdvisorTool(context.advisor);
9140
+ if (context.handoff) {
9141
+ tools.handoff = createHandoffTool(context.handoff);
9345
9142
  }
9346
9143
  if (canUseResourceEventSubscriptionTools(context)) {
9347
9144
  tools.subscribeToResourceEvents = createSubscribeToResourceEventsTool(context);
@@ -14539,7 +14336,6 @@ async function wireAgentTools(args) {
14539
14336
  const loadableSkills = args.availableSkills.filter(
14540
14337
  (skill) => skill.disableModelInvocation !== true || skill.name === args.invokedSkill?.name
14541
14338
  );
14542
- let advisorTools = [];
14543
14339
  const commonToolRuntimeContext = {
14544
14340
  conversationId: args.sessionConversationId,
14545
14341
  userText: args.userInput,
@@ -14565,16 +14361,7 @@ async function wireAgentTools(args) {
14565
14361
  mcpToolManager,
14566
14362
  sandbox,
14567
14363
  surface: args.surface,
14568
- advisor: {
14569
- config: botConfig.advisor,
14570
- conversationId: args.sessionConversationId,
14571
- conversationPrivacy: args.conversationPrivacy,
14572
- logContext: args.spanContext,
14573
- getTools: () => advisorTools,
14574
- streamFn: createTracedStreamFn({
14575
- conversationPrivacy: args.conversationPrivacy
14576
- })
14577
- }
14364
+ ...args.requestHandoff ? { handoff: args.requestHandoff } : {}
14578
14365
  };
14579
14366
  const toolDestination = toolInvocationDestination(args.routing);
14580
14367
  let toolRuntimeContext;
@@ -14720,18 +14507,6 @@ async function wireAgentTools(args) {
14720
14507
  args.conversationPrivacy,
14721
14508
  args.observers.onToolResult
14722
14509
  );
14723
- advisorTools = createPiAgentTools(
14724
- createAdvisorToolDefinitions(tools),
14725
- args.skillSandbox,
14726
- args.spanContext,
14727
- args.observers.onStatus,
14728
- sandboxExecutor,
14729
- pluginAuth,
14730
- onToolCall,
14731
- pluginHooks,
14732
- args.conversationPrivacy,
14733
- args.observers.onToolResult
14734
- );
14735
14510
  return {
14736
14511
  activeMcpCatalogs,
14737
14512
  agentTools,
@@ -14770,7 +14545,7 @@ function createResumeState(args) {
14770
14545
  sessionId: args.sessionId,
14771
14546
  loadedSkillNames: args.getLoadedSkillNames(),
14772
14547
  logContext: args.logContext,
14773
- modelId: args.modelId,
14548
+ modelId: args.getModelId(),
14774
14549
  ...args.getReasoningLevel() ? { reasoningLevel: args.getReasoningLevel() } : {},
14775
14550
  actor: args.actor,
14776
14551
  ...args.surface ? { surface: args.surface } : {}
@@ -14794,6 +14569,11 @@ function createResumeState(args) {
14794
14569
  setBeforeMessageCount(count) {
14795
14570
  beforeMessageCount = count;
14796
14571
  },
14572
+ /** Adopt an already committed epoch replacement as every resume baseline. */
14573
+ adoptCommittedBoundary(messages) {
14574
+ latestSafeBoundaryMessages = [...messages];
14575
+ resumeMessages = [...messages];
14576
+ },
14797
14577
  captureResumeSnapshot(messages) {
14798
14578
  resumeMessages = [...messages];
14799
14579
  },
@@ -14938,6 +14718,319 @@ function createResumeState(args) {
14938
14718
  };
14939
14719
  }
14940
14720
 
14721
+ // src/chat/services/context-compaction.ts
14722
+ import {
14723
+ estimateContextTokens,
14724
+ estimateTokens
14725
+ } from "@earendil-works/pi-agent-core";
14726
+ var RETAINED_USER_MESSAGE_TOKENS = 2e4;
14727
+ var MAX_SUMMARY_INPUT_CHARS = 8e4;
14728
+ var MAX_VISIBLE_CONTEXT_CHARS = 2e4;
14729
+ var MAX_SUMMARY_CHARS = 6e3;
14730
+ var MAX_RENDERED_MESSAGE_CHARS = 4e3;
14731
+ var COMPACTION_SUMMARY_PREFIX = "Context compaction summary for future Junior turns:";
14732
+ var LEGACY_COMPACTION_SUMMARY_PREFIX = "Context handoff summary for future Junior turns:";
14733
+ var MODEL_HANDOFF_SUMMARY_PREFIX = "Model handoff checkpoint. Continue the outstanding request now using this summary as the complete prior context:";
14734
+ var OMITTED_OLDER_CONTEXT_NOTICE = "[older context omitted]";
14735
+ function textPart(value) {
14736
+ if (value && typeof value === "object" && value.type === "text" && typeof value.text === "string") {
14737
+ return value.text;
14738
+ }
14739
+ return void 0;
14740
+ }
14741
+ function messageText(message) {
14742
+ const content = message.content;
14743
+ const unwrapTask = message.role === "user";
14744
+ const displayText = (text) => unwrapTask ? unwrapCurrentInstruction(text) ?? text : text;
14745
+ if (!Array.isArray(content)) {
14746
+ return typeof content === "string" ? displayText(content) : "";
14747
+ }
14748
+ return content.map(textPart).filter((text) => Boolean(text)).map(displayText).join("\n").trim();
14749
+ }
14750
+ function sanitizeText(text) {
14751
+ return text.replace(
14752
+ /<data_base64>[\s\S]*?<\/data_base64>/g,
14753
+ "<data_base64>[omitted]</data_base64>"
14754
+ ).replace(
14755
+ /data:image\/[a-z0-9.+-]+;base64,[a-z0-9+/=]+/gi,
14756
+ "[image data omitted]"
14757
+ ).replaceAll("\0", " ").trim();
14758
+ }
14759
+ function truncateToTokenBudget(text, maxTokens) {
14760
+ const maxChars = Math.max(0, maxTokens * 4);
14761
+ if (text.length <= maxChars) {
14762
+ return text;
14763
+ }
14764
+ return `${text.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
14765
+ }
14766
+ function isCompactionSummary(text) {
14767
+ const normalized = text.trimStart();
14768
+ return normalized.startsWith(COMPACTION_SUMMARY_PREFIX) || normalized.startsWith(LEGACY_COMPACTION_SUMMARY_PREFIX) || normalized.startsWith(MODEL_HANDOFF_SUMMARY_PREFIX);
14769
+ }
14770
+ function isPayloadHeavy(text) {
14771
+ return /<data_base64>[\s\S]*?<\/data_base64>|data:image\/[a-z0-9.+-]+;base64,/i.test(
14772
+ text
14773
+ );
14774
+ }
14775
+ function userMessage(text) {
14776
+ return {
14777
+ role: "user",
14778
+ content: [{ type: "text", text }],
14779
+ timestamp: Date.now()
14780
+ };
14781
+ }
14782
+ function piMessageTimestamp(message) {
14783
+ const timestamp = message.timestamp;
14784
+ return typeof timestamp === "number" ? timestamp : Date.now();
14785
+ }
14786
+ function selectRetainedUserMessageEntries(messages, maxTokens = RETAINED_USER_MESSAGE_TOKENS) {
14787
+ const selected = [];
14788
+ let remaining = maxTokens;
14789
+ for (let sourceIndex = messages.length - 1; sourceIndex >= 0; sourceIndex -= 1) {
14790
+ const stripped = stripRuntimeTurnContext([messages[sourceIndex]]);
14791
+ const message = stripped[0];
14792
+ if (!message || message.role !== "user" || remaining <= 0) {
14793
+ continue;
14794
+ }
14795
+ const text = sanitizeText(messageText(message));
14796
+ if (!text || isCompactionSummary(text) || isPayloadHeavy(text)) {
14797
+ continue;
14798
+ }
14799
+ const tokens = estimateTextTokens(text);
14800
+ if (tokens <= remaining) {
14801
+ selected.push({ message: userMessage(text), sourceIndex });
14802
+ remaining -= tokens;
14803
+ continue;
14804
+ }
14805
+ const truncated = truncateToTokenBudget(text, remaining);
14806
+ if (truncated) {
14807
+ selected.push({ message: userMessage(truncated), sourceIndex });
14808
+ }
14809
+ break;
14810
+ }
14811
+ return selected.reverse();
14812
+ }
14813
+ function renderMessageForSummary(message) {
14814
+ const role = message.role;
14815
+ if (typeof role !== "string") {
14816
+ return void 0;
14817
+ }
14818
+ const text = sanitizeText(messageText(message));
14819
+ if (!text) {
14820
+ return void 0;
14821
+ }
14822
+ const trimmed = text.length > MAX_RENDERED_MESSAGE_CHARS ? `${text.slice(0, MAX_RENDERED_MESSAGE_CHARS).trimEnd()}...` : text;
14823
+ return `[${role}] ${trimmed}`;
14824
+ }
14825
+ function keepTail(text, maxChars) {
14826
+ if (text.length <= maxChars) {
14827
+ return text;
14828
+ }
14829
+ const prefix = `${OMITTED_OLDER_CONTEXT_NOTICE}
14830
+ `;
14831
+ return `${prefix}${text.slice(Math.max(0, text.length - maxChars + prefix.length))}`;
14832
+ }
14833
+ function renderSummaryInput(piMessages, conversationContext) {
14834
+ const lines = [];
14835
+ const visibleContext = conversationContext?.trim();
14836
+ if (visibleContext) {
14837
+ lines.push(
14838
+ "<visible-thread-context>",
14839
+ keepTail(visibleContext, MAX_VISIBLE_CONTEXT_CHARS),
14840
+ "</visible-thread-context>",
14841
+ ""
14842
+ );
14843
+ }
14844
+ const renderedPiMessages = stripRuntimeTurnContext(piMessages).map(renderMessageForSummary).filter((line) => Boolean(line));
14845
+ if (renderedPiMessages.length > 0) {
14846
+ const piEnvelopeChars = "<pi-history>\n</pi-history>".length + 2;
14847
+ const piHistory = keepTail(
14848
+ renderedPiMessages.join("\n"),
14849
+ Math.max(
14850
+ 1,
14851
+ MAX_SUMMARY_INPUT_CHARS - lines.join("\n").length - piEnvelopeChars
14852
+ )
14853
+ );
14854
+ lines.push("<pi-history>", piHistory, "</pi-history>");
14855
+ }
14856
+ return keepTail(lines.join("\n"), MAX_SUMMARY_INPUT_CHARS);
14857
+ }
14858
+ async function summarizeContext(args, deps) {
14859
+ const source = renderSummaryInput(args.piMessages, args.conversationContext);
14860
+ const result = await deps.completeText({
14861
+ modelId: botConfig.fastModelId,
14862
+ messageAttributeMode: "metadata",
14863
+ temperature: 0,
14864
+ signal: args.signal,
14865
+ messages: [
14866
+ {
14867
+ role: "user",
14868
+ content: [
14869
+ "You are performing a CONTEXT CHECKPOINT COMPACTION for Junior.",
14870
+ "Create a concise continuation summary for the agent that will continue this Slack thread.",
14871
+ "",
14872
+ "Include:",
14873
+ "- Current outstanding asks",
14874
+ "- Key decisions, completed work, and outcomes",
14875
+ "- Durable constraints, user preferences, IDs, URLs, artifacts, canvas links, sandbox references, and auth state",
14876
+ "- Clear next steps and unresolved blockers",
14877
+ "",
14878
+ "Do not invent details. Do not include raw secrets or credentials.",
14879
+ "",
14880
+ source
14881
+ ].join("\n"),
14882
+ timestamp: Date.now()
14883
+ }
14884
+ ],
14885
+ metadata: {
14886
+ modelId: botConfig.fastModelId,
14887
+ threadId: args.metadata?.threadId ?? "",
14888
+ channelId: args.metadata?.channelId ?? "",
14889
+ actorId: args.metadata?.actorId ?? "",
14890
+ runId: args.metadata?.runId ?? ""
14891
+ }
14892
+ });
14893
+ const summary = result.text.trim();
14894
+ if (!summary) {
14895
+ throw new Error("Compaction summary was empty");
14896
+ }
14897
+ return summary.slice(0, MAX_SUMMARY_CHARS);
14898
+ }
14899
+ function estimateHistoryTokens(messages) {
14900
+ const stripped = stripRuntimeTurnContext(messages);
14901
+ const usageEstimate = estimateContextTokens(stripped).tokens;
14902
+ const structuralEstimate = stripped.reduce(
14903
+ (total, message) => total + estimateTokens(message),
14904
+ 0
14905
+ );
14906
+ return Math.max(usageEstimate, structuralEstimate);
14907
+ }
14908
+ function buildReplacementProvenance(args) {
14909
+ return [
14910
+ ...args.retained.map(
14911
+ (entry) => args.sourceProvenance[entry.sourceIndex] ?? contextProvenance
14912
+ ),
14913
+ contextProvenance
14914
+ ];
14915
+ }
14916
+ function loadCompactionSource(messages) {
14917
+ if (messages.length > 0) {
14918
+ return {
14919
+ estimatedTokens: estimateHistoryTokens(messages),
14920
+ messages
14921
+ };
14922
+ }
14923
+ return { reason: "missing_context" };
14924
+ }
14925
+ async function maybeCompactWithDeps(args, deps) {
14926
+ const source = loadCompactionSource(args.piMessages);
14927
+ if ("reason" in source) {
14928
+ return { compacted: false, reason: source.reason };
14929
+ }
14930
+ const triggerTokens = deps.autoCompactionTriggerTokens ?? getAgentContextCompactionTriggerTokens();
14931
+ if (source.estimatedTokens <= triggerTokens) {
14932
+ return { compacted: false, reason: "below_threshold" };
14933
+ }
14934
+ args.onCompactionStart?.();
14935
+ let summary;
14936
+ try {
14937
+ summary = await summarizeContext(
14938
+ {
14939
+ conversationContext: args.conversationContext,
14940
+ piMessages: source.messages,
14941
+ metadata: args.metadata
14942
+ },
14943
+ deps
14944
+ );
14945
+ } catch (error) {
14946
+ logWarn(
14947
+ "context_compaction_summary_failed",
14948
+ {
14949
+ slackThreadId: args.metadata?.threadId,
14950
+ slackUserId: args.metadata?.actorId,
14951
+ slackChannelId: args.metadata?.channelId,
14952
+ runId: args.metadata?.runId,
14953
+ assistantUserName: botConfig.userName,
14954
+ modelId: botConfig.fastModelId
14955
+ },
14956
+ {
14957
+ "exception.message": error instanceof Error ? error.message : String(error)
14958
+ },
14959
+ "Context compaction failed; continuing with prior history"
14960
+ );
14961
+ return { compacted: false, reason: "summary_failed" };
14962
+ }
14963
+ return await writeCompactedThreadContext(args, source.messages, summary, {
14964
+ estimatedTokens: source.estimatedTokens,
14965
+ triggerTokens
14966
+ });
14967
+ }
14968
+ async function writeCompactedThreadContext(args, sourceMessages, summary, context) {
14969
+ const stepStore = getAgentStepStore();
14970
+ const sourceProjection = await loadConversationProjection({
14971
+ conversationId: args.conversationId
14972
+ });
14973
+ const retained = selectRetainedUserMessageEntries(
14974
+ trimTrailingAssistantMessages(sourceProjection.messages)
14975
+ );
14976
+ const replacement = [
14977
+ ...retained.map((entry) => entry.message),
14978
+ userMessage(`${COMPACTION_SUMMARY_PREFIX}
14979
+ ${summary}`)
14980
+ ];
14981
+ const replacementProvenance = buildReplacementProvenance({
14982
+ retained,
14983
+ sourceProvenance: sourceProjection.provenance
14984
+ });
14985
+ await stepStore.startEpoch(args.conversationId, {
14986
+ reason: "compaction",
14987
+ modelProfile: sourceProjection.modelProfile,
14988
+ modelId: modelIdForProfile(botConfig, sourceProjection.modelProfile),
14989
+ messages: replacement.map((message, index) => ({
14990
+ message,
14991
+ createdAtMs: piMessageTimestamp(message),
14992
+ provenance: replacementProvenance[index]
14993
+ }))
14994
+ });
14995
+ updateConversationStats(args.conversation);
14996
+ setSpanAttributes({
14997
+ "app.compaction.input_messages": sourceMessages.length,
14998
+ "app.compaction.retained_messages": replacement.length - 1,
14999
+ "app.compaction.summary_chars": summary.length,
15000
+ ...context.triggerTokens !== void 0 ? { "app.compaction.trigger_tokens": context.triggerTokens } : {},
15001
+ "app.context_tokens_estimated": context.estimatedTokens
15002
+ });
15003
+ return {
15004
+ compacted: true,
15005
+ piMessages: replacement
15006
+ };
15007
+ }
15008
+ function createContextCompactor(deps) {
15009
+ return {
15010
+ maybeCompact: async (args) => await maybeCompactWithDeps(args, deps)
15011
+ };
15012
+ }
15013
+ async function compactContextForHandoff(args, deps) {
15014
+ const summary = await summarizeContext(args, deps);
15015
+ const message = userMessage(`${MODEL_HANDOFF_SUMMARY_PREFIX}
15016
+ ${summary}`);
15017
+ const messages = [message];
15018
+ args.signal?.throwIfAborted();
15019
+ await getAgentStepStore().startEpoch(args.conversationId, {
15020
+ reason: "handoff",
15021
+ modelProfile: args.target.modelProfile,
15022
+ modelId: args.target.modelId,
15023
+ messages: [
15024
+ {
15025
+ message,
15026
+ createdAtMs: piMessageTimestamp(message),
15027
+ provenance: contextProvenance
15028
+ }
15029
+ ]
15030
+ });
15031
+ return messages;
15032
+ }
15033
+
14941
15034
  // src/chat/agent/index.ts
14942
15035
  var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
14943
15036
  function waitForAbortSettlement(promise, timeoutMs) {
@@ -15006,7 +15099,10 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15006
15099
  let connectedMcpProviders = /* @__PURE__ */ new Set();
15007
15100
  let canRecordMcpProviders = false;
15008
15101
  let turnUsage;
15102
+ let handoffPhaseUsage;
15009
15103
  let thinkingSelection;
15104
+ let activeModelProfile = STANDARD_MODEL_PROFILE;
15105
+ let activeModelId = modelIdForProfile(botConfig, activeModelProfile);
15010
15106
  const actor = actorFromRouting(routing);
15011
15107
  const surface = surfaceFromRouting(routing);
15012
15108
  const runSource = routing.source;
@@ -15022,8 +15118,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15022
15118
  channelId: routing.correlation?.channelId,
15023
15119
  runId: routing.correlation?.runId,
15024
15120
  ...credentialActorLogContext,
15025
- assistantUserName: botConfig.userName,
15026
- modelId: botConfig.modelId
15121
+ assistantUserName: botConfig.userName
15027
15122
  };
15028
15123
  const { conversationId: sessionConversationId, sessionId } = getSessionIdentifiers(routing);
15029
15124
  const recordConnectedMcpProvider = async (provider) => {
@@ -15049,6 +15144,14 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15049
15144
  sandboxDependencyProfileHash: lastKnownSandboxDependencyProfileHash
15050
15145
  });
15051
15146
  try {
15147
+ if (sessionConversationId) {
15148
+ const projection = await openConversationProjection({
15149
+ conversationId: sessionConversationId,
15150
+ modelId: activeModelId
15151
+ });
15152
+ activeModelProfile = projection.modelProfile;
15153
+ activeModelId = modelIdForProfile(botConfig, activeModelProfile);
15154
+ }
15052
15155
  const shouldTrace = shouldEmitDevAgentTrace();
15053
15156
  const spanContext = {
15054
15157
  conversationId: sessionConversationId,
@@ -15058,7 +15161,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15058
15161
  runId: routing.correlation?.runId,
15059
15162
  ...credentialActorLogContext,
15060
15163
  assistantUserName: botConfig.userName,
15061
- modelId: botConfig.modelId
15164
+ modelId: activeModelId
15062
15165
  };
15063
15166
  const availableSkills = await discoverRunSkills({
15064
15167
  skillDirs: policy.skillDirs,
@@ -15097,10 +15200,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15097
15200
  currentSliceId,
15098
15201
  existingSessionRecord
15099
15202
  } = await restoreSessionRecord(routing);
15100
- const committedInstructionProvenance = existingSessionRecord?.piMessageProvenance ? [
15101
- ...existingSessionRecord.piMessageProvenance,
15203
+ const committedInstructionProvenance = [
15204
+ ...existingSessionRecord?.piMessageProvenance ?? [],
15205
+ ...(existingSessionRecord?.actors ?? []).map(instructionProvenanceFor),
15102
15206
  ...resumedFromSessionRecord ? [] : [instructionProvenanceFor(actor)]
15103
- ] : [instructionProvenanceFor(actor)];
15207
+ ];
15104
15208
  const runActors = () => instructionActors(committedInstructionProvenance);
15105
15209
  canRecordMcpProviders = Boolean(
15106
15210
  sessionRecordState.canUseTurnSession && sessionConversationId && sessionId
@@ -15112,7 +15216,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15112
15216
  getLoadedSkillNames: () => loadedSkillNamesForResume,
15113
15217
  getReasoningLevel: () => thinkingSelection?.thinkingLevel,
15114
15218
  logContext: sessionRecordLogContext,
15115
- modelId: botConfig.modelId,
15219
+ getModelId: () => activeModelId,
15116
15220
  recordActiveMcpProviders,
15117
15221
  actor,
15118
15222
  runSource,
@@ -15181,7 +15285,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15181
15285
  messageText: userInput
15182
15286
  });
15183
15287
  setSpanAttributes({
15184
- "gen_ai.request.model": botConfig.modelId,
15288
+ "gen_ai.request.model": activeModelId,
15185
15289
  "gen_ai.request.reasoning.level": thinkingSelection.thinkingLevel,
15186
15290
  "app.ai.thinking_level_reason": thinkingSelection.reason,
15187
15291
  ...thinkingSelection.confidence !== void 0 ? {
@@ -15192,7 +15296,55 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15192
15296
  const artifactStatePatch = {};
15193
15297
  const toolCalls = [];
15194
15298
  let agent;
15299
+ let pendingHandoff;
15195
15300
  const currentAgentMessages = () => agent ? [...agent.state.messages] : [];
15301
+ const handoffProfiles = [
15302
+ DEFAULT_HANDOFF_MODEL_PROFILE,
15303
+ ...Object.keys(botConfig.modelProfiles).filter((profile) => profile !== DEFAULT_HANDOFF_MODEL_PROFILE).sort()
15304
+ ];
15305
+ const requestHandoff = activeModelProfile === STANDARD_MODEL_PROFILE && sessionConversationId ? {
15306
+ profiles: handoffProfiles,
15307
+ execute: async (profile, signal2) => {
15308
+ const sourceMessages = [...agent.state.messages];
15309
+ const runtimeContext = retainRuntimeTurnContext(sourceMessages);
15310
+ const standardPhaseUsage = extractGenAiUsageSummary(
15311
+ ...sourceMessages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
15312
+ );
15313
+ const phaseUsage = hasAgentTurnUsage(standardPhaseUsage) ? standardPhaseUsage : void 0;
15314
+ const target = {
15315
+ modelId: modelIdForProfile(botConfig, profile),
15316
+ modelProfile: profile
15317
+ };
15318
+ const handoffModel = resolveGatewayModel(target.modelId);
15319
+ const handoffThinkingLevel = toAgentThinkingLevel(
15320
+ thinkingSelection.thinkingLevel
15321
+ );
15322
+ const handoffMessages = await compactContextForHandoff(
15323
+ {
15324
+ conversationContext: input.conversationContext,
15325
+ conversationId: sessionConversationId,
15326
+ piMessages: sourceMessages,
15327
+ signal: signal2,
15328
+ target,
15329
+ metadata: {
15330
+ threadId: routing.correlation?.threadId,
15331
+ channelId: routing.correlation?.channelId,
15332
+ actorId: routing.correlation?.actorId,
15333
+ runId: routing.correlation?.runId
15334
+ }
15335
+ },
15336
+ { completeText }
15337
+ );
15338
+ handoffPhaseUsage = phaseUsage;
15339
+ pendingHandoff = {
15340
+ messages: [...handoffMessages, ...runtimeContext],
15341
+ model: handoffModel,
15342
+ thinkingLevel: handoffThinkingLevel
15343
+ };
15344
+ activeModelProfile = profile;
15345
+ activeModelId = target.modelId;
15346
+ }
15347
+ } : void 0;
15196
15348
  setTags({
15197
15349
  conversationId: spanContext.conversationId,
15198
15350
  slackThreadId: routing.correlation?.threadId,
@@ -15201,7 +15353,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15201
15353
  runId: routing.correlation?.runId,
15202
15354
  ...credentialActorLogContext,
15203
15355
  assistantUserName: botConfig.userName,
15204
- modelId: botConfig.modelId
15356
+ modelId: activeModelId
15205
15357
  });
15206
15358
  const wiring = await wireAgentTools({
15207
15359
  abortAgent: () => agent?.abort(),
@@ -15225,6 +15377,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15225
15377
  preAgentPromptMessages,
15226
15378
  priorPiMessages,
15227
15379
  recordConnectedMcpProvider,
15380
+ requestHandoff,
15228
15381
  resume: runResume,
15229
15382
  routing,
15230
15383
  sessionConversationId,
@@ -15240,6 +15393,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15240
15393
  mcpToolManager = wiring.mcpToolManager;
15241
15394
  const sandboxExecutor = wiring.sandboxExecutor;
15242
15395
  const getPendingAuthPause = wiring.getPendingAuthPause;
15396
+ const toolsAfterHandoff = wiring.agentTools.filter(
15397
+ (tool) => tool.name !== HANDOFF_TOOL_NAME
15398
+ );
15243
15399
  const {
15244
15400
  baseInstructions,
15245
15401
  inputMessages,
@@ -15267,6 +15423,38 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15267
15423
  });
15268
15424
  let hasEmittedText = false;
15269
15425
  let needsSeparator = false;
15426
+ let bufferedStandardText = "";
15427
+ let bufferedStandardMessageStart = false;
15428
+ const startAssistantMessage = () => {
15429
+ Promise.resolve(observers.onAssistantMessageStart?.()).catch((error) => {
15430
+ logWarn(
15431
+ "streaming_message_start_error",
15432
+ {},
15433
+ {
15434
+ "exception.message": error instanceof Error ? error.message : String(error)
15435
+ },
15436
+ "Failed to deliver assistant message start to stream coordinator"
15437
+ );
15438
+ });
15439
+ if (hasEmittedText) {
15440
+ needsSeparator = true;
15441
+ }
15442
+ };
15443
+ const deliverText = (deltaText) => {
15444
+ const text = needsSeparator ? "\n\n" + deltaText : deltaText;
15445
+ needsSeparator = false;
15446
+ hasEmittedText = true;
15447
+ Promise.resolve(observers.onTextDelta?.(text)).catch((error) => {
15448
+ logWarn(
15449
+ "streaming_text_delta_error",
15450
+ {},
15451
+ {
15452
+ "exception.message": error instanceof Error ? error.message : String(error)
15453
+ },
15454
+ "Failed to deliver text delta to stream"
15455
+ );
15456
+ });
15457
+ };
15270
15458
  const drainSteeringMessages = async () => {
15271
15459
  if (!durability.drainSteeringMessages || !sessionRecordState.canUseTurnSession || !sessionConversationId || !sessionId) {
15272
15460
  return;
@@ -15315,18 +15503,60 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15315
15503
  }
15316
15504
  };
15317
15505
  const apiKeyOverride = getPiGatewayApiKey();
15318
- agent = new Agent2({
15506
+ agent = new Agent({
15319
15507
  ...apiKeyOverride ? { getApiKey: () => apiKeyOverride } : {},
15320
15508
  streamFn: createTracedStreamFn({ conversationPrivacy }),
15321
15509
  steeringMode: "all",
15510
+ beforeToolCall: async ({ assistantMessage }) => {
15511
+ const toolCalls2 = assistantMessage.content.filter(
15512
+ (part) => part.type === "toolCall"
15513
+ );
15514
+ const containsHandoff = toolCalls2.some(
15515
+ (call) => call.name === HANDOFF_TOOL_NAME
15516
+ );
15517
+ if (containsHandoff && toolCalls2.length !== 1) {
15518
+ return {
15519
+ block: true,
15520
+ reason: "handoff must be the only tool call in its assistant message; reissue it alone"
15521
+ };
15522
+ }
15523
+ return void 0;
15524
+ },
15322
15525
  prepareNextTurn: async () => {
15526
+ let update;
15527
+ if (pendingHandoff) {
15528
+ const { messages, model, thinkingLevel } = pendingHandoff;
15529
+ const replacement = [...messages];
15530
+ pendingHandoff = void 0;
15531
+ agent.state.messages = replacement;
15532
+ agent.state.model = model;
15533
+ agent.state.thinkingLevel = thinkingLevel;
15534
+ agent.state.tools = toolsAfterHandoff;
15535
+ runResume.setBeforeMessageCount(replacement.length);
15536
+ runResume.setTurnStartMessageIndex(0);
15537
+ runResume.adoptCommittedBoundary(replacement);
15538
+ setSpanAttributes({
15539
+ "gen_ai.request.model": activeModelId,
15540
+ "gen_ai.request.reasoning.level": thinkingSelection.thinkingLevel,
15541
+ "app.ai.model_profile": activeModelProfile
15542
+ });
15543
+ update = {
15544
+ context: {
15545
+ systemPrompt: baseInstructions,
15546
+ messages: replacement,
15547
+ tools: toolsAfterHandoff
15548
+ },
15549
+ model,
15550
+ thinkingLevel
15551
+ };
15552
+ }
15323
15553
  await drainSteeringMessages();
15324
15554
  runResume.yieldAtSafeBoundaryIfDue(currentAgentMessages());
15325
- return void 0;
15555
+ return update;
15326
15556
  },
15327
15557
  initialState: {
15328
15558
  systemPrompt: baseInstructions,
15329
- model: resolveGatewayModel(botConfig.modelId),
15559
+ model: resolveGatewayModel(activeModelId),
15330
15560
  thinkingLevel: toAgentThinkingLevel(thinkingSelection.thinkingLevel),
15331
15561
  tools: wiring.agentTools
15332
15562
  }
@@ -15336,43 +15566,46 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15336
15566
  return recordParentToolExecutionStart(event);
15337
15567
  }
15338
15568
  if (event.type === "turn_end" && event.toolResults.length > 0) {
15569
+ if (pendingHandoff) {
15570
+ return;
15571
+ }
15339
15572
  return runResume.persistSafeBoundary([...agent.state.messages]).then(() => void 0);
15340
15573
  }
15341
15574
  if (event.type === "message_start") {
15342
- Promise.resolve(observers.onAssistantMessageStart?.()).catch(
15343
- (error) => {
15344
- logWarn(
15345
- "streaming_message_start_error",
15346
- {},
15347
- {
15348
- "exception.message": error instanceof Error ? error.message : String(error)
15349
- },
15350
- "Failed to deliver assistant message start to stream coordinator"
15351
- );
15352
- }
15575
+ if (activeModelProfile === STANDARD_MODEL_PROFILE && requestHandoff) {
15576
+ bufferedStandardMessageStart = true;
15577
+ bufferedStandardText = "";
15578
+ } else {
15579
+ startAssistantMessage();
15580
+ }
15581
+ return;
15582
+ }
15583
+ if (event.type === "message_end" && isAssistantMessage(event.message)) {
15584
+ if (!bufferedStandardMessageStart) {
15585
+ return;
15586
+ }
15587
+ const containsHandoff = event.message.content.some(
15588
+ (part) => part.type === "toolCall" && part.name === HANDOFF_TOOL_NAME
15353
15589
  );
15354
- if (hasEmittedText) {
15355
- needsSeparator = true;
15590
+ if (!containsHandoff) {
15591
+ startAssistantMessage();
15592
+ if (bufferedStandardText) {
15593
+ deliverText(bufferedStandardText);
15594
+ }
15356
15595
  }
15596
+ bufferedStandardMessageStart = false;
15597
+ bufferedStandardText = "";
15357
15598
  return;
15358
15599
  }
15359
15600
  if (event.type !== "message_update") return;
15360
15601
  if (event.assistantMessageEvent.type !== "text_delta") return;
15361
15602
  const deltaText = event.assistantMessageEvent.delta;
15362
15603
  if (!deltaText) return;
15363
- const text = needsSeparator ? "\n\n" + deltaText : deltaText;
15364
- needsSeparator = false;
15365
- hasEmittedText = true;
15366
- Promise.resolve(observers.onTextDelta?.(text)).catch((error) => {
15367
- logWarn(
15368
- "streaming_text_delta_error",
15369
- {},
15370
- {
15371
- "exception.message": error instanceof Error ? error.message : String(error)
15372
- },
15373
- "Failed to deliver text delta to stream"
15374
- );
15375
- });
15604
+ if (bufferedStandardMessageStart) {
15605
+ bufferedStandardText += deltaText;
15606
+ } else {
15607
+ deliverText(deltaText);
15608
+ }
15376
15609
  });
15377
15610
  let newMessages = [];
15378
15611
  try {
@@ -15389,7 +15622,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15389
15622
  runResume.setTurnStartMessageIndex(agent.state.messages.length);
15390
15623
  }
15391
15624
  await withSpan(
15392
- `invoke_agent ${botConfig.modelId}`,
15625
+ `invoke_agent ${activeModelId}`,
15393
15626
  "gen_ai.invoke_agent",
15394
15627
  spanContext,
15395
15628
  async () => {
@@ -15454,7 +15687,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15454
15687
  {
15455
15688
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
15456
15689
  "gen_ai.operation.name": "invoke_agent",
15457
- "gen_ai.request.model": botConfig.modelId,
15690
+ "gen_ai.request.model": activeModelId,
15458
15691
  ...thinkingSelection ? {
15459
15692
  "gen_ai.request.reasoning.level": thinkingSelection.thinkingLevel
15460
15693
  } : {},
@@ -15519,7 +15752,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15519
15752
  ...outputMessages
15520
15753
  );
15521
15754
  const currentUsage = hasAgentTurnUsage(usageSummary) ? usageSummary : void 0;
15522
- turnUsage = addAgentTurnUsage(retryUsage, currentUsage);
15755
+ const currentPhaseUsage = addAgentTurnUsage(
15756
+ retryUsage,
15757
+ currentUsage
15758
+ );
15759
+ turnUsage = addAgentTurnUsage(handoffPhaseUsage, currentPhaseUsage);
15523
15760
  setSpanAttributes({
15524
15761
  ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
15525
15762
  ...toGenAiMessagesTraceAttributes(
@@ -15543,7 +15780,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15543
15780
  if (!providerRetry) {
15544
15781
  break;
15545
15782
  }
15546
- retryUsage = turnUsage;
15783
+ retryUsage = currentPhaseUsage;
15547
15784
  agent.state.messages = providerRetry.messages;
15548
15785
  await runResume.persistSafeBoundary(providerRetry.messages);
15549
15786
  logWarn(
@@ -15560,7 +15797,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15560
15797
  {
15561
15798
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
15562
15799
  "gen_ai.operation.name": "invoke_agent",
15563
- "gen_ai.request.model": botConfig.modelId,
15800
+ "gen_ai.request.model": activeModelId,
15564
15801
  "gen_ai.output.type": "text",
15565
15802
  "server.address": GEN_AI_SERVER_ADDRESS,
15566
15803
  "server.port": GEN_AI_SERVER_PORT,
@@ -15596,7 +15833,8 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15596
15833
  usage: turnUsage,
15597
15834
  thinkingSelection,
15598
15835
  correlation: routing.correlation,
15599
- assistantUserName: botConfig.userName
15836
+ assistantUserName: botConfig.userName,
15837
+ modelId: activeModelId
15600
15838
  })
15601
15839
  };
15602
15840
  } catch (error) {
@@ -15609,6 +15847,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15609
15847
  return outcome;
15610
15848
  }
15611
15849
  }
15850
+ if (error instanceof ModelProfileNotConfiguredError) {
15851
+ throw error;
15852
+ }
15612
15853
  if (isProviderRetryError(error)) {
15613
15854
  throw error;
15614
15855
  }
@@ -15631,7 +15872,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15631
15872
  runId: routing.correlation?.runId,
15632
15873
  ...credentialActorLogContext,
15633
15874
  assistantUserName: botConfig.userName,
15634
- modelId: botConfig.modelId
15875
+ modelId: activeModelId
15635
15876
  },
15636
15877
  {},
15637
15878
  "executeAgentRun failed"
@@ -15644,7 +15885,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15644
15885
  ...getSandboxMetadata(),
15645
15886
  diagnostics: {
15646
15887
  outcome: "provider_error",
15647
- modelId: botConfig.modelId,
15888
+ modelId: activeModelId,
15648
15889
  assistantMessageCount: 0,
15649
15890
  ...thinkingSelection ? {
15650
15891
  thinkingLevel: thinkingSelection.thinkingLevel
@@ -15731,6 +15972,7 @@ export {
15731
15972
  finalizeMcpAuthorization,
15732
15973
  resolvePluginOAuthAccount,
15733
15974
  executeCredentialedEgressRequest,
15975
+ createContextCompactor,
15734
15976
  executeAgentRun,
15735
15977
  createAgentRunner
15736
15978
  };