@sentry/junior 0.95.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 (144) hide show
  1. package/dist/{agent-hooks-FTQ5EGPO.js → agent-hooks-XSUIVRQS.js} +10 -8
  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 +108 -370
  28. package/dist/build/virtual-config.d.ts +2 -2
  29. package/dist/{catalog-runtime-2D7IVKUT.js → catalog-runtime-PFHVMHDR.js} +5 -4
  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/logging.d.ts +2 -2
  41. package/dist/chat/model-profile.d.ts +14 -0
  42. package/dist/chat/pi/transcript.d.ts +5 -3
  43. package/dist/chat/plugins/auth/oauth-request.d.ts +2 -0
  44. package/dist/chat/resource-events/notification.d.ts +2 -0
  45. package/dist/chat/resource-events/store.d.ts +2 -2
  46. package/dist/chat/runtime/agent-run-outcome.d.ts +3 -0
  47. package/dist/chat/sentry.d.ts +1 -1
  48. package/dist/chat/services/context-compaction.d.ts +15 -0
  49. package/dist/chat/services/turn-result.d.ts +1 -0
  50. package/dist/chat/services/turn-session-record.d.ts +6 -6
  51. package/dist/chat/state/turn-session.d.ts +18 -5
  52. package/dist/chat/task-execution/store.d.ts +2 -2
  53. package/dist/chat/tools/handoff/tool.d.ts +45 -0
  54. package/dist/chat/tools/types.d.ts +7 -2
  55. package/dist/chat/usage.d.ts +27 -13
  56. package/dist/{chunk-6ONME5IG.js → chunk-35BNLDOJ.js} +339 -578
  57. package/dist/{chunk-HDTO3A74.js → chunk-3ECW2K2O.js} +8 -3
  58. package/dist/{chunk-CRRBJ3JP.js → chunk-3F4OPKGF.js} +90 -56
  59. package/dist/chunk-3V7OFJZC.js +264 -0
  60. package/dist/chunk-4ZNGQH7C.js +25 -0
  61. package/dist/{chunk-NSCHU54X.js → chunk-5HLDPVYS.js} +15 -448
  62. package/dist/{chunk-3DA7X2U3.js → chunk-6GWA276C.js} +15 -0
  63. package/dist/{chunk-HZNJLKKI.js → chunk-6U26PYI5.js} +24 -14
  64. package/dist/chunk-AUUCAFDE.js +546 -0
  65. package/dist/{chunk-6APU57E6.js → chunk-B3IT5END.js} +41 -260
  66. package/dist/chunk-CEA3A3UA.js +86 -0
  67. package/dist/{chunk-AYP4ERVZ.js → chunk-D5KZYS4T.js} +1 -1
  68. package/dist/{chunk-GUE7HTNR.js → chunk-EEZCY5WX.js} +79 -29
  69. package/dist/{chunk-E3E2XNCN.js → chunk-EOWXRY5E.js} +2 -2
  70. package/dist/{chunk-5XMNOD3S.js → chunk-FBHZJFHI.js} +3 -3
  71. package/dist/{chunk-CZEX6KIQ.js → chunk-G3E7SCME.js} +0 -23
  72. package/dist/chunk-GUAP3H7O.js +319 -0
  73. package/dist/{chunk-WCMQJE4F.js → chunk-H66UCBPW.js} +7 -5
  74. package/dist/{chunk-WPOJ443W.js → chunk-I35CTGPO.js} +58 -6
  75. package/dist/{chunk-GVOUCI2O.js → chunk-LMJPRYHP.js} +16 -6
  76. package/dist/chunk-LUAE4MGW.js +199 -0
  77. package/dist/{chunk-GY34VGP5.js → chunk-LVA4I4H7.js} +3 -1
  78. package/dist/chunk-LVUKF5CE.js +63 -0
  79. package/dist/{chunk-EKNJM5IG.js → chunk-NG2AV7IB.js} +2 -72
  80. package/dist/chunk-ON6YKIQ7.js +260 -0
  81. package/dist/chunk-PDO5BLNM.js +22 -0
  82. package/dist/chunk-QNVHH5PU.js +20 -0
  83. package/dist/{chunk-XX4N4E6C.js → chunk-T533JLM3.js} +9 -11
  84. package/dist/chunk-UAMKJPDF.js +11 -0
  85. package/dist/chunk-VFH4ZKZT.js +66 -0
  86. package/dist/{chunk-KDPCGVIT.js → chunk-WGXMTFNU.js} +17 -75
  87. package/dist/{chunk-WAGQ377B.js → chunk-WJOSZOON.js} +288 -263
  88. package/dist/chunk-WTYEKPXU.js +451 -0
  89. package/dist/{chunk-HCXXB35T.js → chunk-XIRPQQVE.js} +613 -369
  90. package/dist/cli/chat.js +26 -23
  91. package/dist/cli/check.js +6 -5
  92. package/dist/cli/init.js +12 -0
  93. package/dist/cli/main.js +1 -1
  94. package/dist/cli/plugins.js +14 -11
  95. package/dist/cli/snapshot-warmup.js +10 -8
  96. package/dist/cli/upgrade.js +267 -139
  97. package/dist/db/db.d.ts +4 -0
  98. package/dist/db/schema/conversations.d.ts +128 -0
  99. package/dist/db/schema.d.ts +129 -60
  100. package/dist/{db-W55B74OP.js → db-BWXYIKFQ.js} +5 -5
  101. package/dist/detail-GA6NXWQJ.js +43 -0
  102. package/dist/discovery-RMHYSRL7.js +43 -0
  103. package/dist/handlers/health.d.ts +3 -0
  104. package/dist/instrumentation.js +2 -2
  105. package/dist/{legacy-import-ZPLGLAFD.js → legacy-import-3NQJVGQL.js} +6 -6
  106. package/dist/{api/people/list.js → list-A3WFRIYX.js} +26 -16
  107. package/dist/list-UXJI5Q5X.js +27 -0
  108. package/dist/nitro.d.ts +1 -1
  109. package/dist/nitro.js +2 -1
  110. package/dist/plugin-conversations-DDNGC35A.js +36 -0
  111. package/dist/{api/people/profile.js → profile-4RYDXWTF.js} +33 -18
  112. package/dist/reporting/plugin-conversations.d.ts +3 -0
  113. package/dist/reporting-schema.d.ts +140 -0
  114. package/dist/reporting.d.ts +12 -81
  115. package/dist/{runner-OOSBDYRK.js → runner-JALSYH2P.js} +19 -14
  116. package/dist/{sentry-EGFOV3DH.js → sentry-JMWTS32N.js} +11 -1
  117. package/dist/skills-MAZXZ2KE.js +26 -0
  118. package/dist/stats-XKXH3PTY.js +217 -0
  119. package/dist/subagent-TZ55XY37.js +48 -0
  120. package/dist/usage-schema.d.ts +25 -0
  121. package/dist/{validation-QCGRFRFZ.js → validation-FMEAEKCY.js} +6 -5
  122. package/migrations/0000_initial.sql +115 -0
  123. package/migrations/0001_conversation_metrics.sql +4 -0
  124. package/migrations/README.md +12 -30
  125. package/migrations/meta/0000_snapshot.json +1 -33
  126. package/migrations/meta/0001_snapshot.json +972 -0
  127. package/migrations/meta/_journal.json +9 -2
  128. package/package.json +14 -18
  129. package/dist/api/people/types.d.ts +0 -85
  130. package/dist/chat/advisor-request.d.ts +0 -4
  131. package/dist/chat/conversations/sql/migrations.d.ts +0 -22
  132. package/dist/chat/tools/advisor/session-store.d.ts +0 -9
  133. package/dist/chat/tools/advisor/tool.d.ts +0 -72
  134. package/dist/chunk-N4RNJ32C.js +0 -48
  135. package/dist/chunk-RAIB6WSG.js +0 -142
  136. package/dist/db/schema/migrations.d.ts +0 -58
  137. package/dist/reporting/conversations/context.d.ts +0 -8
  138. package/dist/reporting/conversations/details.d.ts +0 -6
  139. package/dist/reporting/conversations/index.d.ts +0 -4
  140. package/dist/reporting/conversations/statistics.d.ts +0 -4
  141. package/dist/reporting/conversations/summaries.d.ts +0 -17
  142. package/dist/reporting/conversations/types.d.ts +0 -178
  143. package/dist/reporting/conversations.d.ts +0 -1
  144. package/dist/reporting.js +0 -1065
@@ -1,10 +1,8 @@
1
1
  import {
2
2
  CooperativeTurnYieldError,
3
3
  TurnInputCommitLostError,
4
- addAgentTurnUsage,
5
4
  canReusePendingAuthLink,
6
5
  getInterruptionMarker,
7
- hasAgentTurnUsage,
8
6
  isTurnInputCommitLostError,
9
7
  loadTurnSessionRecord,
10
8
  mergeArtifactsState,
@@ -12,7 +10,25 @@ import {
12
10
  persistRunningSessionRecord,
13
11
  persistTimeoutSessionRecord,
14
12
  persistYieldSessionRecord
15
- } from "./chunk-KDPCGVIT.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";
16
32
  import {
17
33
  buildNonInteractiveShellScript,
18
34
  createSandboxInstance,
@@ -22,64 +38,58 @@ import {
22
38
  isSnapshotMissingError,
23
39
  resolveRuntimeDependencySnapshot,
24
40
  runNonInteractiveCommand
25
- } from "./chunk-WCMQJE4F.js";
41
+ } from "./chunk-H66UCBPW.js";
26
42
  import {
27
43
  privateTraceResultAttributes
28
44
  } from "./chunk-CM5EZD5F.js";
29
45
  import {
30
- renderAdvisorRequest
31
- } from "./chunk-N4RNJ32C.js";
32
- import {
33
- commitMessages,
34
- loadConnectedMcpProviders,
35
- loadProjection,
36
- recordAuthorizationRequested,
37
- recordMcpProviderConnected,
38
- recordToolExecutionStarted
39
- } from "./chunk-WAGQ377B.js";
40
- import {
41
- JUNIOR_THREAD_STATE_TTL_MS
42
- } from "./chunk-CRRBJ3JP.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-NSCHU54X.js";
63
+ } from "./chunk-WTYEKPXU.js";
60
64
  import {
61
65
  createPluginLogger
62
- } from "./chunk-E3E2XNCN.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-6ONME5IG.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-GUE7HTNR.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-HDTO3A74.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-GVOUCI2O.js";
147
+ } from "./chunk-LMJPRYHP.js";
134
148
  import {
135
149
  createActor,
136
150
  extractGenAiUsageAttributes,
@@ -149,24 +163,24 @@ import {
149
163
  setTags,
150
164
  summarizeMessageText,
151
165
  withSpan
152
- } from "./chunk-WPOJ443W.js";
166
+ } from "./chunk-I35CTGPO.js";
153
167
  import {
154
168
  isRecord
155
169
  } from "./chunk-OB42YVAE.js";
156
170
  import {
157
171
  startInactiveSpan,
158
172
  withActiveSpan
159
- } from "./chunk-3DA7X2U3.js";
160
- import {
161
- listReferenceFiles,
162
- soulPathCandidates,
163
- worldPathCandidates
164
- } from "./chunk-6APU57E6.js";
173
+ } from "./chunk-6GWA276C.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.",
@@ -2062,7 +2078,6 @@ var EXECUTION_CONTRACT_RULES = [
2062
2078
  var CONVERSATION_RULES = [
2063
2079
  "- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
2064
2080
  "- Preserve attribution roles from thread context: the actor is the person asking now, which may differ from the original reporter or subject.",
2065
- "- Treat event notifications as subscribed conversation updates, not user-authored commands. Use their subscription intent to decide whether to reply, inspect, suggest, or stay brief.",
2066
2081
  "- Runtime owns continuation and authorization notices; on resumed turns, answer with the final requested content only."
2067
2082
  ];
2068
2083
  var SLACK_ACTION_RULES = [
@@ -8368,252 +8383,33 @@ function createSystemTimeTool() {
8368
8383
  });
8369
8384
  }
8370
8385
 
8371
- // src/chat/tools/advisor/tool.ts
8372
- import {
8373
- Agent
8374
- } from "@earendil-works/pi-agent-core";
8386
+ // src/chat/tools/handoff/tool.ts
8375
8387
  import { z as z33 } from "zod";
8376
- function advisorChildConversationId(parentConversationId) {
8377
- return `advisor:${parentConversationId}`;
8378
- }
8379
- 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.";
8380
- var ADVISOR_SYSTEM_PROMPT = [
8381
- "You are a senior technical advisor for the executor.",
8382
- "Analyze the executor-supplied context deeply. Use read-only tools when direct inspection or verification would materially improve the advice.",
8383
- "Distinguish evidence from inference. Treat the advisor task as the focus for this call and the executor context as the starting evidence packet.",
8384
- "Do not assume access to parent transcript or tool output that was not included or gathered in this advisor call.",
8385
- "Use only the read-only tools provided to you.",
8386
- "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.",
8387
- "Identify the hard part, recommend a concrete plan or correction, call out blocking risks, and propose focused verification.",
8388
- "If the supplied context is insufficient, say exactly what additional evidence the executor needs to gather before acting.",
8389
- "Do not write user-facing prose.",
8390
- "Use concise technical memo sections when helpful: Assessment, Recommended Plan, Risks, Verification, Stop Conditions."
8391
- ].join("\n");
8392
- function lastAssistantMessage(messages) {
8393
- for (let index = messages.length - 1; index >= 0; index -= 1) {
8394
- const message = messages[index];
8395
- if (isAssistantMessage(message)) {
8396
- return message;
8397
- }
8398
- }
8399
- return void 0;
8400
- }
8401
- function failure(errorCode, text = `Advisor guidance is unavailable (${errorCode}). Continue only if the next step is clear from verified evidence.`) {
8402
- return {
8403
- ok: false,
8404
- status: "error",
8405
- error_code: errorCode,
8406
- memo: text
8407
- };
8408
- }
8409
- function success(memo) {
8410
- return {
8411
- ok: true,
8412
- status: "success",
8413
- memo
8414
- };
8415
- }
8416
- function hasReadOnlyToolAnnotations(annotations) {
8417
- return annotations?.readOnlyHint === true && annotations.destructiveHint !== true;
8418
- }
8419
- function createAdvisorToolDefinitions(definitions) {
8420
- return Object.fromEntries(
8421
- Object.entries(definitions).filter(
8422
- ([name, definition]) => name !== "callMcpTool" && name !== "searchMcpTools" && hasReadOnlyToolAnnotations(definition.annotations)
8423
- )
8424
- );
8425
- }
8426
- function createAdvisorTool(context) {
8427
- const conversationStore = () => context.conversationStore ?? getConversationStore();
8428
- 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(", ");
8429
8396
  return zodTool({
8430
- 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",
8431
8399
  inputSchema: z33.object({
8432
- question: z33.string().min(1).describe("Focused advisor question or decision point."),
8433
- context: z33.string().min(1).describe(
8434
- "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"
8435
8402
  )
8436
- }),
8437
- outputSchema: juniorToolResultSchema,
8438
- execute: async ({ question, context: suppliedContext }, { toolCallId } = {}) => {
8439
- if (typeof question !== "string" || !question.trim()) {
8440
- return failure(
8441
- "invalid_question",
8442
- "Advisor guidance is unavailable because the question was empty or invalid. Ask a focused advisor question before retrying."
8443
- );
8444
- }
8445
- const advisorQuestion = question.trim();
8446
- if (typeof suppliedContext !== "string" || !suppliedContext.trim()) {
8447
- return failure(
8448
- "invalid_context",
8449
- "Advisor guidance is unavailable because the curated context was empty or invalid. Include the relevant evidence and constraints before retrying."
8450
- );
8451
- }
8452
- const advisorContext = suppliedContext.trim();
8453
- if (!context.conversationId) {
8454
- return failure(
8455
- "missing_conversation_id",
8456
- "Advisor guidance is unavailable because this turn has no parent conversation id. Continue without assuming advisor history."
8457
- );
8458
- }
8459
- const conversationId = context.conversationId;
8460
- const childConversationId = advisorChildConversationId(conversationId);
8461
- const subagentInvocationId = typeof toolCallId === "string" && toolCallId.length > 0 ? toolCallId : `advisor:${Date.now()}`;
8462
- const endSubagent = async (outcome, errorCode) => {
8463
- try {
8464
- await getAgentStepStore().append(conversationId, [
8465
- {
8466
- entry: {
8467
- type: "subagent_ended",
8468
- subagentInvocationId,
8469
- outcome,
8470
- ...errorCode ? { errorCode } : {}
8471
- },
8472
- createdAtMs: Date.now()
8473
- }
8474
- ]);
8475
- } catch (error) {
8476
- logWarn("advisor_subagent_ended_append_failed", spanContext, {
8477
- "exception.message": error instanceof Error ? error.message : String(error)
8478
- });
8479
- }
8480
- };
8481
- try {
8482
- await conversationStore().ensureChildConversation({
8483
- conversationId: childConversationId,
8484
- parentConversationId: conversationId
8485
- });
8486
- await getAgentStepStore().append(conversationId, [
8487
- {
8488
- entry: {
8489
- type: "subagent_started",
8490
- subagentInvocationId,
8491
- subagentKind: "advisor",
8492
- modelId: context.config.modelId,
8493
- ...typeof toolCallId === "string" && toolCallId.length > 0 ? { parentToolCallId: toolCallId } : {},
8494
- childConversationId,
8495
- historyMode: "shared",
8496
- reasoningLevel: context.config.thinkingLevel
8497
- },
8498
- createdAtMs: Date.now()
8499
- }
8500
- ]);
8501
- } catch (error) {
8502
- logWarn("advisor_subagent_started_append_failed", spanContext, {
8503
- "exception.message": error instanceof Error ? error.message : String(error)
8504
- });
8505
- }
8506
- const conversationPrivacy = context.conversationPrivacy ?? "private";
8507
- const requestText = renderAdvisorRequest(advisorQuestion, advisorContext);
8508
- const advisorInputMessage = {
8509
- role: "user",
8510
- content: [{ type: "text", text: requestText }],
8511
- 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
8512
8412
  };
8513
- const advisorInputMessagesAttribute = serializeGenAiAttribute(
8514
- conversationPrivacy !== "public" ? [toGenAiMessageMetadata(advisorInputMessage)] : [toCanonicalInputMessage(advisorInputMessage)]
8515
- );
8516
- return await withSpan(
8517
- `invoke_agent ${context.config.modelId}`,
8518
- "gen_ai.invoke_agent",
8519
- spanContext,
8520
- async () => {
8521
- const requestMessage = {
8522
- role: "user",
8523
- content: [{ type: "text", text: requestText }],
8524
- timestamp: Date.now()
8525
- };
8526
- let advisorMessages;
8527
- try {
8528
- advisorMessages = await loadProjection({
8529
- conversationId: childConversationId
8530
- });
8531
- } catch {
8532
- setSpanStatus("error");
8533
- await endSubagent("error", "session_unavailable");
8534
- return failure(
8535
- "session_unavailable",
8536
- "Advisor guidance is unavailable because advisor history could not be loaded. Continue without assuming advisor history."
8537
- );
8538
- }
8539
- const apiKeyOverride = getPiGatewayApiKey();
8540
- const advisorAgent = new Agent({
8541
- ...apiKeyOverride ? { getApiKey: () => apiKeyOverride } : {},
8542
- initialState: {
8543
- systemPrompt: ADVISOR_SYSTEM_PROMPT,
8544
- model: resolveGatewayModel(context.config.modelId),
8545
- thinkingLevel: context.config.thinkingLevel,
8546
- tools: context.getTools()
8547
- },
8548
- sessionId: childConversationId,
8549
- streamFn: context.streamFn
8550
- });
8551
- advisorAgent.state.messages = advisorMessages;
8552
- const beforeMessageCount = advisorAgent.state.messages.length;
8553
- try {
8554
- await advisorAgent.prompt(requestMessage);
8555
- } catch {
8556
- setSpanStatus("error");
8557
- await endSubagent("error", "unavailable");
8558
- return failure(
8559
- "unavailable",
8560
- "Advisor guidance is unavailable. Continue without advisor guidance if the next step is clear from verified evidence."
8561
- );
8562
- }
8563
- const assistant = lastAssistantMessage(advisorAgent.state.messages);
8564
- const newAdvisorMessages = advisorAgent.state.messages.slice(beforeMessageCount);
8565
- const outputMessages = newAdvisorMessages.filter(isAssistantMessage);
8566
- const outputMessagesAttribute = serializeGenAiAttribute(
8567
- conversationPrivacy !== "public" ? outputMessages.map(toGenAiMessageMetadata) : outputMessages.map(toCanonicalOutputMessage)
8568
- );
8569
- setSpanAttributes({
8570
- ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
8571
- ...toGenAiMessagesTraceAttributes("app.ai.output", outputMessages),
8572
- ...extractGenAiUsageAttributes(...newAdvisorMessages)
8573
- });
8574
- if (!assistant || assistant.stopReason === "error" || assistant.stopReason === "aborted") {
8575
- setSpanStatus("error");
8576
- await endSubagent(
8577
- assistant?.stopReason === "aborted" ? "aborted" : "error",
8578
- "unavailable"
8579
- );
8580
- return failure(
8581
- "unavailable",
8582
- "Advisor guidance is unavailable. Continue without advisor guidance if the next step is clear from verified evidence."
8583
- );
8584
- }
8585
- const memo = extractAssistantText(assistant);
8586
- try {
8587
- await commitMessages({
8588
- conversationId: childConversationId,
8589
- messages: advisorAgent.state.messages
8590
- });
8591
- } catch {
8592
- setSpanStatus("error");
8593
- await endSubagent("error", "session_unavailable");
8594
- return failure(
8595
- "session_unavailable",
8596
- "Advisor guidance is unavailable because advisor history could not be saved. Retry the advisor call or continue without assuming advisor history."
8597
- );
8598
- }
8599
- await endSubagent("success");
8600
- setSpanStatus("ok");
8601
- return success(memo);
8602
- },
8603
- {
8604
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
8605
- "gen_ai.operation.name": "invoke_agent",
8606
- "gen_ai.request.model": context.config.modelId,
8607
- "gen_ai.output.type": "text",
8608
- "server.address": GEN_AI_SERVER_ADDRESS,
8609
- "server.port": GEN_AI_SERVER_PORT,
8610
- "app.conversation.privacy": conversationPrivacy,
8611
- ...toGenAiMessagesTraceAttributes("app.ai.input", [
8612
- advisorInputMessage
8613
- ]),
8614
- ...advisorInputMessagesAttribute ? { "gen_ai.input.messages": advisorInputMessagesAttribute } : {}
8615
- }
8616
- );
8617
8413
  }
8618
8414
  });
8619
8415
  }
@@ -9341,8 +9137,8 @@ function createTools(availableSkills, hooks = {}, context) {
9341
9137
  hooks.toolOverrides?.imageGenerate
9342
9138
  );
9343
9139
  }
9344
- if (context.advisor) {
9345
- tools.advisor = createAdvisorTool(context.advisor);
9140
+ if (context.handoff) {
9141
+ tools.handoff = createHandoffTool(context.handoff);
9346
9142
  }
9347
9143
  if (canUseResourceEventSubscriptionTools(context)) {
9348
9144
  tools.subscribeToResourceEvents = createSubscribeToResourceEventsTool(context);
@@ -14540,7 +14336,6 @@ async function wireAgentTools(args) {
14540
14336
  const loadableSkills = args.availableSkills.filter(
14541
14337
  (skill) => skill.disableModelInvocation !== true || skill.name === args.invokedSkill?.name
14542
14338
  );
14543
- let advisorTools = [];
14544
14339
  const commonToolRuntimeContext = {
14545
14340
  conversationId: args.sessionConversationId,
14546
14341
  userText: args.userInput,
@@ -14566,16 +14361,7 @@ async function wireAgentTools(args) {
14566
14361
  mcpToolManager,
14567
14362
  sandbox,
14568
14363
  surface: args.surface,
14569
- advisor: {
14570
- config: botConfig.advisor,
14571
- conversationId: args.sessionConversationId,
14572
- conversationPrivacy: args.conversationPrivacy,
14573
- logContext: args.spanContext,
14574
- getTools: () => advisorTools,
14575
- streamFn: createTracedStreamFn({
14576
- conversationPrivacy: args.conversationPrivacy
14577
- })
14578
- }
14364
+ ...args.requestHandoff ? { handoff: args.requestHandoff } : {}
14579
14365
  };
14580
14366
  const toolDestination = toolInvocationDestination(args.routing);
14581
14367
  let toolRuntimeContext;
@@ -14721,18 +14507,6 @@ async function wireAgentTools(args) {
14721
14507
  args.conversationPrivacy,
14722
14508
  args.observers.onToolResult
14723
14509
  );
14724
- advisorTools = createPiAgentTools(
14725
- createAdvisorToolDefinitions(tools),
14726
- args.skillSandbox,
14727
- args.spanContext,
14728
- args.observers.onStatus,
14729
- sandboxExecutor,
14730
- pluginAuth,
14731
- onToolCall,
14732
- pluginHooks,
14733
- args.conversationPrivacy,
14734
- args.observers.onToolResult
14735
- );
14736
14510
  return {
14737
14511
  activeMcpCatalogs,
14738
14512
  agentTools,
@@ -14771,7 +14545,7 @@ function createResumeState(args) {
14771
14545
  sessionId: args.sessionId,
14772
14546
  loadedSkillNames: args.getLoadedSkillNames(),
14773
14547
  logContext: args.logContext,
14774
- modelId: args.modelId,
14548
+ modelId: args.getModelId(),
14775
14549
  ...args.getReasoningLevel() ? { reasoningLevel: args.getReasoningLevel() } : {},
14776
14550
  actor: args.actor,
14777
14551
  ...args.surface ? { surface: args.surface } : {}
@@ -14795,6 +14569,11 @@ function createResumeState(args) {
14795
14569
  setBeforeMessageCount(count) {
14796
14570
  beforeMessageCount = count;
14797
14571
  },
14572
+ /** Adopt an already committed epoch replacement as every resume baseline. */
14573
+ adoptCommittedBoundary(messages) {
14574
+ latestSafeBoundaryMessages = [...messages];
14575
+ resumeMessages = [...messages];
14576
+ },
14798
14577
  captureResumeSnapshot(messages) {
14799
14578
  resumeMessages = [...messages];
14800
14579
  },
@@ -14879,7 +14658,8 @@ function createResumeState(args) {
14879
14658
  return {
14880
14659
  outcome: {
14881
14660
  status: "suspended",
14882
- resumeVersion: sessionRecord.version
14661
+ resumeVersion: sessionRecord.version,
14662
+ ...usage ? { usage } : {}
14883
14663
  }
14884
14664
  };
14885
14665
  }
@@ -14903,7 +14683,8 @@ function createResumeState(args) {
14903
14683
  return {
14904
14684
  outcome: {
14905
14685
  status: "suspended",
14906
- resumeVersion: sessionRecord.version
14686
+ resumeVersion: sessionRecord.version,
14687
+ ...usage ? { usage } : {}
14907
14688
  }
14908
14689
  };
14909
14690
  }
@@ -14926,7 +14707,8 @@ function createResumeState(args) {
14926
14707
  return {
14927
14708
  outcome: {
14928
14709
  status: "awaiting_auth",
14929
- providerDisplayName: error.providerDisplayName
14710
+ providerDisplayName: error.providerDisplayName,
14711
+ ...usage ? { usage } : {}
14930
14712
  }
14931
14713
  };
14932
14714
  }
@@ -14936,6 +14718,319 @@ function createResumeState(args) {
14936
14718
  };
14937
14719
  }
14938
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
+
14939
15034
  // src/chat/agent/index.ts
14940
15035
  var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
14941
15036
  function waitForAbortSettlement(promise, timeoutMs) {
@@ -15004,7 +15099,10 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15004
15099
  let connectedMcpProviders = /* @__PURE__ */ new Set();
15005
15100
  let canRecordMcpProviders = false;
15006
15101
  let turnUsage;
15102
+ let handoffPhaseUsage;
15007
15103
  let thinkingSelection;
15104
+ let activeModelProfile = STANDARD_MODEL_PROFILE;
15105
+ let activeModelId = modelIdForProfile(botConfig, activeModelProfile);
15008
15106
  const actor = actorFromRouting(routing);
15009
15107
  const surface = surfaceFromRouting(routing);
15010
15108
  const runSource = routing.source;
@@ -15020,8 +15118,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15020
15118
  channelId: routing.correlation?.channelId,
15021
15119
  runId: routing.correlation?.runId,
15022
15120
  ...credentialActorLogContext,
15023
- assistantUserName: botConfig.userName,
15024
- modelId: botConfig.modelId
15121
+ assistantUserName: botConfig.userName
15025
15122
  };
15026
15123
  const { conversationId: sessionConversationId, sessionId } = getSessionIdentifiers(routing);
15027
15124
  const recordConnectedMcpProvider = async (provider) => {
@@ -15047,6 +15144,14 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15047
15144
  sandboxDependencyProfileHash: lastKnownSandboxDependencyProfileHash
15048
15145
  });
15049
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
+ }
15050
15155
  const shouldTrace = shouldEmitDevAgentTrace();
15051
15156
  const spanContext = {
15052
15157
  conversationId: sessionConversationId,
@@ -15056,7 +15161,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15056
15161
  runId: routing.correlation?.runId,
15057
15162
  ...credentialActorLogContext,
15058
15163
  assistantUserName: botConfig.userName,
15059
- modelId: botConfig.modelId
15164
+ modelId: activeModelId
15060
15165
  };
15061
15166
  const availableSkills = await discoverRunSkills({
15062
15167
  skillDirs: policy.skillDirs,
@@ -15095,10 +15200,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15095
15200
  currentSliceId,
15096
15201
  existingSessionRecord
15097
15202
  } = await restoreSessionRecord(routing);
15098
- const committedInstructionProvenance = existingSessionRecord?.piMessageProvenance ? [
15099
- ...existingSessionRecord.piMessageProvenance,
15203
+ const committedInstructionProvenance = [
15204
+ ...existingSessionRecord?.piMessageProvenance ?? [],
15205
+ ...(existingSessionRecord?.actors ?? []).map(instructionProvenanceFor),
15100
15206
  ...resumedFromSessionRecord ? [] : [instructionProvenanceFor(actor)]
15101
- ] : [instructionProvenanceFor(actor)];
15207
+ ];
15102
15208
  const runActors = () => instructionActors(committedInstructionProvenance);
15103
15209
  canRecordMcpProviders = Boolean(
15104
15210
  sessionRecordState.canUseTurnSession && sessionConversationId && sessionId
@@ -15110,7 +15216,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15110
15216
  getLoadedSkillNames: () => loadedSkillNamesForResume,
15111
15217
  getReasoningLevel: () => thinkingSelection?.thinkingLevel,
15112
15218
  logContext: sessionRecordLogContext,
15113
- modelId: botConfig.modelId,
15219
+ getModelId: () => activeModelId,
15114
15220
  recordActiveMcpProviders,
15115
15221
  actor,
15116
15222
  runSource,
@@ -15179,7 +15285,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15179
15285
  messageText: userInput
15180
15286
  });
15181
15287
  setSpanAttributes({
15182
- "gen_ai.request.model": botConfig.modelId,
15288
+ "gen_ai.request.model": activeModelId,
15183
15289
  "gen_ai.request.reasoning.level": thinkingSelection.thinkingLevel,
15184
15290
  "app.ai.thinking_level_reason": thinkingSelection.reason,
15185
15291
  ...thinkingSelection.confidence !== void 0 ? {
@@ -15190,7 +15296,55 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15190
15296
  const artifactStatePatch = {};
15191
15297
  const toolCalls = [];
15192
15298
  let agent;
15299
+ let pendingHandoff;
15193
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;
15194
15348
  setTags({
15195
15349
  conversationId: spanContext.conversationId,
15196
15350
  slackThreadId: routing.correlation?.threadId,
@@ -15199,7 +15353,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15199
15353
  runId: routing.correlation?.runId,
15200
15354
  ...credentialActorLogContext,
15201
15355
  assistantUserName: botConfig.userName,
15202
- modelId: botConfig.modelId
15356
+ modelId: activeModelId
15203
15357
  });
15204
15358
  const wiring = await wireAgentTools({
15205
15359
  abortAgent: () => agent?.abort(),
@@ -15223,6 +15377,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15223
15377
  preAgentPromptMessages,
15224
15378
  priorPiMessages,
15225
15379
  recordConnectedMcpProvider,
15380
+ requestHandoff,
15226
15381
  resume: runResume,
15227
15382
  routing,
15228
15383
  sessionConversationId,
@@ -15238,6 +15393,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15238
15393
  mcpToolManager = wiring.mcpToolManager;
15239
15394
  const sandboxExecutor = wiring.sandboxExecutor;
15240
15395
  const getPendingAuthPause = wiring.getPendingAuthPause;
15396
+ const toolsAfterHandoff = wiring.agentTools.filter(
15397
+ (tool) => tool.name !== HANDOFF_TOOL_NAME
15398
+ );
15241
15399
  const {
15242
15400
  baseInstructions,
15243
15401
  inputMessages,
@@ -15265,6 +15423,38 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15265
15423
  });
15266
15424
  let hasEmittedText = false;
15267
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
+ };
15268
15458
  const drainSteeringMessages = async () => {
15269
15459
  if (!durability.drainSteeringMessages || !sessionRecordState.canUseTurnSession || !sessionConversationId || !sessionId) {
15270
15460
  return;
@@ -15313,18 +15503,60 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15313
15503
  }
15314
15504
  };
15315
15505
  const apiKeyOverride = getPiGatewayApiKey();
15316
- agent = new Agent2({
15506
+ agent = new Agent({
15317
15507
  ...apiKeyOverride ? { getApiKey: () => apiKeyOverride } : {},
15318
15508
  streamFn: createTracedStreamFn({ conversationPrivacy }),
15319
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
+ },
15320
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
+ }
15321
15553
  await drainSteeringMessages();
15322
15554
  runResume.yieldAtSafeBoundaryIfDue(currentAgentMessages());
15323
- return void 0;
15555
+ return update;
15324
15556
  },
15325
15557
  initialState: {
15326
15558
  systemPrompt: baseInstructions,
15327
- model: resolveGatewayModel(botConfig.modelId),
15559
+ model: resolveGatewayModel(activeModelId),
15328
15560
  thinkingLevel: toAgentThinkingLevel(thinkingSelection.thinkingLevel),
15329
15561
  tools: wiring.agentTools
15330
15562
  }
@@ -15334,43 +15566,46 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15334
15566
  return recordParentToolExecutionStart(event);
15335
15567
  }
15336
15568
  if (event.type === "turn_end" && event.toolResults.length > 0) {
15569
+ if (pendingHandoff) {
15570
+ return;
15571
+ }
15337
15572
  return runResume.persistSafeBoundary([...agent.state.messages]).then(() => void 0);
15338
15573
  }
15339
15574
  if (event.type === "message_start") {
15340
- Promise.resolve(observers.onAssistantMessageStart?.()).catch(
15341
- (error) => {
15342
- logWarn(
15343
- "streaming_message_start_error",
15344
- {},
15345
- {
15346
- "exception.message": error instanceof Error ? error.message : String(error)
15347
- },
15348
- "Failed to deliver assistant message start to stream coordinator"
15349
- );
15350
- }
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
15351
15589
  );
15352
- if (hasEmittedText) {
15353
- needsSeparator = true;
15590
+ if (!containsHandoff) {
15591
+ startAssistantMessage();
15592
+ if (bufferedStandardText) {
15593
+ deliverText(bufferedStandardText);
15594
+ }
15354
15595
  }
15596
+ bufferedStandardMessageStart = false;
15597
+ bufferedStandardText = "";
15355
15598
  return;
15356
15599
  }
15357
15600
  if (event.type !== "message_update") return;
15358
15601
  if (event.assistantMessageEvent.type !== "text_delta") return;
15359
15602
  const deltaText = event.assistantMessageEvent.delta;
15360
15603
  if (!deltaText) return;
15361
- const text = needsSeparator ? "\n\n" + deltaText : deltaText;
15362
- needsSeparator = false;
15363
- hasEmittedText = true;
15364
- Promise.resolve(observers.onTextDelta?.(text)).catch((error) => {
15365
- logWarn(
15366
- "streaming_text_delta_error",
15367
- {},
15368
- {
15369
- "exception.message": error instanceof Error ? error.message : String(error)
15370
- },
15371
- "Failed to deliver text delta to stream"
15372
- );
15373
- });
15604
+ if (bufferedStandardMessageStart) {
15605
+ bufferedStandardText += deltaText;
15606
+ } else {
15607
+ deliverText(deltaText);
15608
+ }
15374
15609
  });
15375
15610
  let newMessages = [];
15376
15611
  try {
@@ -15387,7 +15622,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15387
15622
  runResume.setTurnStartMessageIndex(agent.state.messages.length);
15388
15623
  }
15389
15624
  await withSpan(
15390
- `invoke_agent ${botConfig.modelId}`,
15625
+ `invoke_agent ${activeModelId}`,
15391
15626
  "gen_ai.invoke_agent",
15392
15627
  spanContext,
15393
15628
  async () => {
@@ -15452,7 +15687,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15452
15687
  {
15453
15688
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
15454
15689
  "gen_ai.operation.name": "invoke_agent",
15455
- "gen_ai.request.model": botConfig.modelId,
15690
+ "gen_ai.request.model": activeModelId,
15456
15691
  ...thinkingSelection ? {
15457
15692
  "gen_ai.request.reasoning.level": thinkingSelection.thinkingLevel
15458
15693
  } : {},
@@ -15517,7 +15752,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15517
15752
  ...outputMessages
15518
15753
  );
15519
15754
  const currentUsage = hasAgentTurnUsage(usageSummary) ? usageSummary : void 0;
15520
- turnUsage = addAgentTurnUsage(retryUsage, currentUsage);
15755
+ const currentPhaseUsage = addAgentTurnUsage(
15756
+ retryUsage,
15757
+ currentUsage
15758
+ );
15759
+ turnUsage = addAgentTurnUsage(handoffPhaseUsage, currentPhaseUsage);
15521
15760
  setSpanAttributes({
15522
15761
  ...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
15523
15762
  ...toGenAiMessagesTraceAttributes(
@@ -15541,7 +15780,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15541
15780
  if (!providerRetry) {
15542
15781
  break;
15543
15782
  }
15544
- retryUsage = turnUsage;
15783
+ retryUsage = currentPhaseUsage;
15545
15784
  agent.state.messages = providerRetry.messages;
15546
15785
  await runResume.persistSafeBoundary(providerRetry.messages);
15547
15786
  logWarn(
@@ -15558,7 +15797,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15558
15797
  {
15559
15798
  "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
15560
15799
  "gen_ai.operation.name": "invoke_agent",
15561
- "gen_ai.request.model": botConfig.modelId,
15800
+ "gen_ai.request.model": activeModelId,
15562
15801
  "gen_ai.output.type": "text",
15563
15802
  "server.address": GEN_AI_SERVER_ADDRESS,
15564
15803
  "server.port": GEN_AI_SERVER_PORT,
@@ -15594,7 +15833,8 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15594
15833
  usage: turnUsage,
15595
15834
  thinkingSelection,
15596
15835
  correlation: routing.correlation,
15597
- assistantUserName: botConfig.userName
15836
+ assistantUserName: botConfig.userName,
15837
+ modelId: activeModelId
15598
15838
  })
15599
15839
  };
15600
15840
  } catch (error) {
@@ -15607,6 +15847,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15607
15847
  return outcome;
15608
15848
  }
15609
15849
  }
15850
+ if (error instanceof ModelProfileNotConfiguredError) {
15851
+ throw error;
15852
+ }
15610
15853
  if (isProviderRetryError(error)) {
15611
15854
  throw error;
15612
15855
  }
@@ -15629,7 +15872,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15629
15872
  runId: routing.correlation?.runId,
15630
15873
  ...credentialActorLogContext,
15631
15874
  assistantUserName: botConfig.userName,
15632
- modelId: botConfig.modelId
15875
+ modelId: activeModelId
15633
15876
  },
15634
15877
  {},
15635
15878
  "executeAgentRun failed"
@@ -15642,7 +15885,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy) {
15642
15885
  ...getSandboxMetadata(),
15643
15886
  diagnostics: {
15644
15887
  outcome: "provider_error",
15645
- modelId: botConfig.modelId,
15888
+ modelId: activeModelId,
15646
15889
  assistantMessageCount: 0,
15647
15890
  ...thinkingSelection ? {
15648
15891
  thinkingLevel: thinkingSelection.thinkingLevel
@@ -15729,6 +15972,7 @@ export {
15729
15972
  finalizeMcpAuthorization,
15730
15973
  resolvePluginOAuthAccount,
15731
15974
  executeCredentialedEgressRequest,
15975
+ createContextCompactor,
15732
15976
  executeAgentRun,
15733
15977
  createAgentRunner
15734
15978
  };