@sentry/junior 0.121.0 → 0.122.1

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 (66) hide show
  1. package/dist/{agent-hooks-CQDGDBCP.js → agent-hooks-65WXVKKX.js} +8 -8
  2. package/dist/api.js +14 -14
  3. package/dist/app.js +604 -997
  4. package/dist/{catalog-runtime-AD3YNTTM.js → catalog-runtime-VNNMSKCB.js} +5 -5
  5. package/dist/chat/agent/index.d.ts +19 -2
  6. package/dist/chat/agent/request.d.ts +8 -8
  7. package/dist/chat/agent/resume.d.ts +1 -3
  8. package/dist/chat/agent/retry-usage.d.ts +8 -0
  9. package/dist/chat/agent/skills.d.ts +0 -9
  10. package/dist/chat/conversations/messages.d.ts +7 -0
  11. package/dist/chat/conversations/projection.d.ts +16 -0
  12. package/dist/chat/logging.d.ts +46 -13
  13. package/dist/chat/mcp/tool-manager.d.ts +11 -0
  14. package/dist/chat/plugins/agent-hooks.d.ts +8 -0
  15. package/dist/chat/runtime/processing-reaction.d.ts +2 -4
  16. package/dist/chat/runtime/slack-runtime.d.ts +2 -2
  17. package/dist/chat/services/assistant-reply.d.ts +16 -0
  18. package/dist/chat/services/empty-output-continuation.d.ts +20 -0
  19. package/dist/chat/services/turn-failure-response.d.ts +1 -3
  20. package/dist/chat/services/turn-result.d.ts +0 -5
  21. package/dist/chat/services/turn-session-record.d.ts +0 -11
  22. package/dist/chat/tool-support/zod-tool.d.ts +11 -6
  23. package/dist/chat/tools/execute-tool.d.ts +1 -33
  24. package/dist/chat/tools/execution/tool-error-handler.d.ts +2 -2
  25. package/dist/chat/tools/query-conversation-events.d.ts +3 -1
  26. package/dist/chat/tools/sandbox/bash.d.ts +1 -33
  27. package/dist/chat/tools/sandbox/edit-file.d.ts +1 -33
  28. package/dist/chat/tools/sandbox/find-files.d.ts +1 -33
  29. package/dist/chat/tools/sandbox/grep.d.ts +1 -33
  30. package/dist/chat/tools/sandbox/list-dir.d.ts +1 -33
  31. package/dist/chat/tools/sandbox/read-file.d.ts +1 -33
  32. package/dist/chat/tools/sandbox/write-file.d.ts +1 -33
  33. package/dist/{chunk-VWBKDPMQ.js → chunk-3KYLHUVC.js} +487 -686
  34. package/dist/{chunk-JZ47AFSH.js → chunk-4JPJTFUH.js} +37 -45
  35. package/dist/{chunk-ET2SDCNV.js → chunk-4ZV2GNHX.js} +16 -31
  36. package/dist/{chunk-WUP7NWMP.js → chunk-73TQBD5S.js} +1 -1
  37. package/dist/{chunk-Z2XXPUZG.js → chunk-BXT63TNS.js} +2061 -2015
  38. package/dist/{chunk-JYDIBXFY.js → chunk-GPQLJDT5.js} +13 -19
  39. package/dist/{chunk-LE77HHF3.js → chunk-HGREP4UX.js} +1 -1
  40. package/dist/{chunk-XDRGYDED.js → chunk-IEE5CSAP.js} +1 -1
  41. package/dist/{chunk-D2EMOH3N.js → chunk-LZ4XXS76.js} +3 -3
  42. package/dist/{chunk-254MFDOF.js → chunk-M7KAZXTE.js} +2 -2
  43. package/dist/{chunk-ITMEXX5H.js → chunk-MSDKKTRG.js} +4 -4
  44. package/dist/{chunk-UXQ5XSA2.js → chunk-PXPBBOIU.js} +2 -2
  45. package/dist/{chunk-IK4QSS5H.js → chunk-PYS4QZAF.js} +1 -1
  46. package/dist/{chunk-7HYV7KFM.js → chunk-QE33ATQW.js} +83 -92
  47. package/dist/{chunk-HDFGNU5E.js → chunk-S7G34U75.js} +3 -1
  48. package/dist/{chunk-IDXLBKPS.js → chunk-TI7SYDD4.js} +11 -22
  49. package/dist/{chunk-3DBMANUI.js → chunk-U5PRRZMZ.js} +22 -45
  50. package/dist/{chunk-KLUMVJRQ.js → chunk-VLRECFIC.js} +133 -206
  51. package/dist/{chunk-UURFXX66.js → chunk-WFJ2EG4E.js} +1 -1
  52. package/dist/{chunk-TRECK7RV.js → chunk-XBYNPBPQ.js} +14 -29
  53. package/dist/cli/chat.js +22 -22
  54. package/dist/cli/check.js +6 -6
  55. package/dist/cli/plugins.js +11 -11
  56. package/dist/cli/snapshot-create.js +9 -9
  57. package/dist/cli/upgrade.js +6 -6
  58. package/dist/{db-7HQT2SXQ.js → db-36HXKAKH.js} +6 -6
  59. package/dist/instrumentation.js +3 -3
  60. package/dist/nitro.js +2 -2
  61. package/dist/{oauth-callback-server-VHTUCNHK.js → oauth-callback-server-UKOO7KKP.js} +15 -15
  62. package/dist/{runner-3ONPZRED.js → runner-M66KMCQ7.js} +62 -51
  63. package/dist/{skills-WGAYGGPA.js → skills-TTXTVFOG.js} +6 -6
  64. package/dist/{validation-ZIWNBG4D.js → validation-TGASRGNN.js} +6 -6
  65. package/dist/version.js +1 -1
  66. package/package.json +8 -8
@@ -30,15 +30,18 @@ import {
30
30
  toMcpProviderError,
31
31
  toolInvocationDestination,
32
32
  uploadFilesToConversation
33
- } from "./chunk-3DBMANUI.js";
33
+ } from "./chunk-U5PRRZMZ.js";
34
34
  import {
35
35
  CooperativeTurnYieldError,
36
36
  JUNIOR_THREAD_STATE_TTL_MS,
37
+ NO_REPLY_MARKER,
37
38
  TURN_CONTEXT_TAG,
38
39
  TurnInputCommitLostError,
39
40
  TurnSliceLimitExceededError,
40
41
  abandonReplacedPendingAuth,
41
42
  canReusePendingAuthLink,
43
+ containsNoReplyMarker,
44
+ decideReply,
42
45
  estimateTextTokens,
43
46
  extractAssistantText,
44
47
  getAgentContextCompactionTriggerTokens,
@@ -49,6 +52,7 @@ import {
49
52
  historyItemFromPiMessage,
50
53
  isAssistantMessage,
51
54
  isContinuablePiBoundary,
55
+ isNoReplyMarker,
52
56
  isToolResultError,
53
57
  isToolResultMessage,
54
58
  isTurnInputCommitLostError,
@@ -69,10 +73,11 @@ import {
69
73
  recordToolExecutionStarted,
70
74
  recordTurnRoute,
71
75
  retainRuntimeTurnContext,
76
+ sanitizeAssistantText,
72
77
  stripRuntimeTurnContext,
73
78
  trimTrailingAssistantMessages,
74
79
  updateConversationStats
75
- } from "./chunk-Z2XXPUZG.js";
80
+ } from "./chunk-BXT63TNS.js";
76
81
  import {
77
82
  parseSandboxEgressAuthRequiredSignal,
78
83
  parseSandboxEgressPermissionDeniedSignal,
@@ -96,10 +101,10 @@ import {
96
101
  runNonInteractiveCommand,
97
102
  throwSandboxOperationError,
98
103
  wrapSandboxSetupError
99
- } from "./chunk-ITMEXX5H.js";
104
+ } from "./chunk-MSDKKTRG.js";
100
105
  import {
101
106
  incrementStat
102
- } from "./chunk-XDRGYDED.js";
107
+ } from "./chunk-IEE5CSAP.js";
103
108
  import {
104
109
  addAgentTurnUsage,
105
110
  hasAgentTurnUsage
@@ -121,7 +126,7 @@ import {
121
126
  normalizeSlackConversationId,
122
127
  resolveChannelCapabilities,
123
128
  withSlackRetries
124
- } from "./chunk-7HYV7KFM.js";
129
+ } from "./chunk-QE33ATQW.js";
125
130
  import {
126
131
  KNOWN_CONVERSATION_EVENT_TYPES,
127
132
  contextProvenance,
@@ -130,7 +135,7 @@ import {
130
135
  getConversationStore,
131
136
  instructionActors,
132
137
  instructionProvenanceFor
133
- } from "./chunk-254MFDOF.js";
138
+ } from "./chunk-M7KAZXTE.js";
134
139
  import {
135
140
  SANDBOX_DATA_ROOT,
136
141
  SANDBOX_SKILLS_ROOT,
@@ -140,7 +145,7 @@ import {
140
145
  sandboxSkillFile,
141
146
  sandboxSkillPathResolution,
142
147
  sleep
143
- } from "./chunk-UURFXX66.js";
148
+ } from "./chunk-WFJ2EG4E.js";
144
149
  import {
145
150
  ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
146
151
  COMPACTION_SUMMARY_PREFIX,
@@ -171,7 +176,7 @@ import {
171
176
  renderCurrentInstruction,
172
177
  resolveGatewayModel,
173
178
  unwrapCurrentInstruction
174
- } from "./chunk-IDXLBKPS.js";
179
+ } from "./chunk-TI7SYDD4.js";
175
180
  import {
176
181
  canExposeConversationPayload,
177
182
  parseSlackThreadId,
@@ -182,16 +187,16 @@ import {
182
187
  toGenAiMessagesTraceAttributes,
183
188
  toGenAiPayloadMetadata,
184
189
  toGenAiPayloadTraceAttributes
185
- } from "./chunk-HDFGNU5E.js";
190
+ } from "./chunk-S7G34U75.js";
186
191
  import {
187
192
  discoverSkills,
188
193
  findSkillByName,
189
194
  loadSkillsByName,
190
195
  parseSkillInvocation
191
- } from "./chunk-TRECK7RV.js";
196
+ } from "./chunk-XBYNPBPQ.js";
192
197
  import {
193
198
  pluginCatalogRuntime
194
- } from "./chunk-WUP7NWMP.js";
199
+ } from "./chunk-73TQBD5S.js";
195
200
  import {
196
201
  CredentialUnavailableError,
197
202
  OAuthProviderError,
@@ -199,7 +204,7 @@ import {
199
204
  getOAuthProviderErrorAttributes,
200
205
  resolveAuthTokenPlaceholder,
201
206
  resolvePluginCommandEnv
202
- } from "./chunk-ET2SDCNV.js";
207
+ } from "./chunk-4ZV2GNHX.js";
203
208
  import {
204
209
  credentialUserSubjectId
205
210
  } from "./chunk-W3U3YQYI.js";
@@ -223,7 +228,7 @@ import {
223
228
  summarizeMessageText,
224
229
  withLogContext,
225
230
  withSpan
226
- } from "./chunk-JZ47AFSH.js";
231
+ } from "./chunk-4JPJTFUH.js";
227
232
  import {
228
233
  parseSlackChannelId,
229
234
  parseSlackMessageTs,
@@ -320,7 +325,9 @@ function shouldPropagateSandboxEgressTrace(host, config = {}) {
320
325
  }
321
326
 
322
327
  // src/chat/agent/index.ts
323
- import { Agent } from "@earendil-works/pi-agent-core";
328
+ import {
329
+ Agent
330
+ } from "@earendil-works/pi-agent-core";
324
331
 
325
332
  // src/chat/sandbox/skill-sandbox.ts
326
333
  import fs from "fs/promises";
@@ -661,103 +668,6 @@ function shouldEmitDevAgentTrace() {
661
668
 
662
669
  // src/chat/services/turn-result.ts
663
670
  import { isRetryableAssistantError } from "@earendil-works/pi-ai";
664
-
665
- // src/chat/no-reply.ts
666
- var NO_REPLY_MARKER = "[[NO_REPLY]]";
667
- function isNoReplyMarker(text) {
668
- return text.trim() === NO_REPLY_MARKER;
669
- }
670
- function containsNoReplyMarker(text) {
671
- return text.includes(NO_REPLY_MARKER);
672
- }
673
-
674
- // src/chat/services/turn-result.ts
675
- function isExecutionDeferralResponse(text) {
676
- return /\b(want me to proceed|do you want me to proceed|shall i proceed|can i proceed|should i proceed|let me do that now|give me a moment|tag me again|fresh invocation)\b/i.test(
677
- text
678
- );
679
- }
680
- function isToolAccessDisclaimerResponse(text) {
681
- return /\b(i (don't|do not) have access to (active )?tool|tool results came back empty|prior results .* empty|cannot access .*tool|need to (run|load) .*tool .* first)\b/i.test(
682
- text
683
- );
684
- }
685
- function isExecutionEscapeResponse(text) {
686
- const trimmed = text.trim();
687
- if (!trimmed) return false;
688
- return isExecutionDeferralResponse(trimmed) || isToolAccessDisclaimerResponse(trimmed);
689
- }
690
- function parseJsonCandidate(text) {
691
- const trimmed = text.trim();
692
- if (!trimmed) return void 0;
693
- try {
694
- return JSON.parse(trimmed);
695
- } catch {
696
- const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
697
- if (!fenced) return void 0;
698
- try {
699
- return JSON.parse(fenced[1]);
700
- } catch {
701
- return void 0;
702
- }
703
- }
704
- }
705
- function isToolPayloadShape(payload) {
706
- if (!payload || typeof payload !== "object") return false;
707
- const record = payload;
708
- const type = typeof record.type === "string" ? record.type.toLowerCase() : "";
709
- if (type.startsWith("tool-")) return true;
710
- if (type === "tool_use" || type === "tool_call" || type === "tool_result" || type === "tool_error")
711
- return true;
712
- const hasToolName = typeof record.toolName === "string" || typeof record.name === "string";
713
- const hasToolInput = Object.prototype.hasOwnProperty.call(record, "input") || Object.prototype.hasOwnProperty.call(record, "args");
714
- if (hasToolName && hasToolInput) return true;
715
- return false;
716
- }
717
- function isRawToolPayloadResponse(text) {
718
- const parsed = parseJsonCandidate(text);
719
- if (Array.isArray(parsed)) {
720
- return parsed.some((entry) => isToolPayloadShape(entry));
721
- }
722
- if (isToolPayloadShape(parsed)) {
723
- return true;
724
- }
725
- return false;
726
- }
727
- var THINKING_XML_BLOCK_PATTERN = /[ \t]*<thinking\b[^>]*>[\s\S]*?<\/thinking>[ \t]*(?:\r?\n)?/gi;
728
- var FENCED_CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
729
- function stripThinkingXmlBlocks(text) {
730
- let result = "";
731
- let cursor = 0;
732
- for (const match of text.matchAll(FENCED_CODE_BLOCK_PATTERN)) {
733
- const start = match.index;
734
- if (start === void 0) {
735
- continue;
736
- }
737
- result += text.slice(cursor, start).replace(THINKING_XML_BLOCK_PATTERN, "");
738
- result += match[0];
739
- cursor = start + match[0].length;
740
- }
741
- result += text.slice(cursor).replace(THINKING_XML_BLOCK_PATTERN, "");
742
- return result;
743
- }
744
- function getVisibleAssistantText(rawText) {
745
- const text = stripThinkingXmlBlocks(rawText).trim();
746
- if (!text || isNoReplyMarker(text) || containsNoReplyMarker(text) || isRawToolPayloadResponse(text)) {
747
- return void 0;
748
- }
749
- return text;
750
- }
751
- function getAssistantMessageText(message) {
752
- if (message.content.some((part) => part.type === "toolCall")) {
753
- return void 0;
754
- }
755
- const text = getVisibleAssistantText(extractAssistantText(message));
756
- if (!text) {
757
- return void 0;
758
- }
759
- return isExecutionEscapeResponse(text) ? void 0 : text;
760
- }
761
671
  function buildTurnResult(input) {
762
672
  const {
763
673
  newMessages,
@@ -766,74 +676,53 @@ function buildTurnResult(input) {
766
676
  sandboxRef,
767
677
  durationMs,
768
678
  shouldTrace,
769
- spanContext,
770
679
  usage,
771
680
  executionProfile,
772
- assistantUserName,
773
681
  modelId
774
682
  } = input;
775
683
  const toolResults = newMessages.filter(isToolResultMessage);
776
684
  const assistantMessages = newMessages.filter(isAssistantMessage);
777
685
  const terminalAssistantMessages = getTerminalAssistantMessages(newMessages);
778
- const rawPrimaryText = stripThinkingXmlBlocks(
686
+ const rawPrimaryText = sanitizeAssistantText(
779
687
  terminalAssistantMessages.map((message) => extractAssistantText(message)).join("\n\n")
780
688
  ).trim();
781
689
  const exactNoReplyMarker = isNoReplyMarker(rawPrimaryText);
782
690
  const mixedNoReplyMarker = !exactNoReplyMarker && containsNoReplyMarker(rawPrimaryText);
783
691
  const noReplyRequested = exactNoReplyMarker || mixedNoReplyMarker;
784
- const primaryText = noReplyRequested ? "" : terminalAssistantMessages.map((message) => getAssistantMessageText(message)).filter((text) => Boolean(text)).join("\n\n");
692
+ const primaryText = noReplyRequested ? "" : terminalAssistantMessages.map((message) => decideReply(message)).filter(
693
+ (output) => output.kind === "deliver"
694
+ ).map((output) => output.text).join("\n\n");
785
695
  const toolErrorCount = toolResults.filter((result) => result.isError).length;
786
696
  const reactionPerformed = toolResults.some(
787
697
  (result) => !isToolResultError(result) && normalizeToolNameFromResult(result) === "addReaction"
788
698
  );
789
699
  const completedWithoutTerminalText = noReplyRequested;
790
- const resultLogContext = {
791
- ...spanContext,
792
- assistantUserName,
793
- modelId
794
- };
795
700
  const lastAssistant = terminalAssistantMessages.at(-1);
796
701
  const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
797
702
  const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
798
703
  const isProviderError = stopReason === "error";
799
704
  if (exactNoReplyMarker) {
800
705
  const markerCategory = reactionPerformed ? "reaction" : "none";
801
- const markerContext = resultLogContext;
802
706
  const markerAttributes = {
803
707
  "app.ai.no_reply_marker": true,
804
708
  "app.ai.no_reply_marker_category": markerCategory,
805
709
  "app.ai.no_reply_marker_accepted": !isProviderError
806
710
  };
807
711
  if (!isProviderError) {
808
- logInfo(
809
- "ai_no_reply_marker_accepted",
810
- markerContext,
811
- markerAttributes,
812
- "No-reply marker suppressed visible thread text"
813
- );
712
+ logInfo("ai.no_reply_marker.accepted", markerAttributes);
814
713
  }
815
714
  } else if (mixedNoReplyMarker) {
816
- logWarn(
817
- "ai_no_reply_marker_mixed_text",
818
- resultLogContext,
819
- {
820
- "app.ai.no_reply_marker": true,
821
- "app.ai.no_reply_marker_mode": "mixed"
822
- },
823
- "No-reply marker appeared with visible assistant text"
824
- );
715
+ logWarn("ai.no_reply_marker.mixed_text", {
716
+ "app.ai.no_reply_marker": true,
717
+ "app.ai.no_reply_marker_mode": "mixed"
718
+ });
825
719
  }
826
720
  if (!primaryText && !completedWithoutTerminalText && !isProviderError) {
827
- logWarn(
828
- "ai_model_response_empty",
829
- resultLogContext,
830
- {
831
- "app.ai.tool_results": toolResults.length,
832
- "app.ai.tool_error_results": toolErrorCount,
833
- "app.ai.generated_files": input.generatedFileCount
834
- },
835
- "Model returned empty text response"
836
- );
721
+ logWarn("ai.model_response.empty", {
722
+ "app.ai.tool_results": toolResults.length,
723
+ "app.ai.tool_error_results": toolErrorCount,
724
+ "app.ai.generated_files": input.generatedFileCount
725
+ });
837
726
  }
838
727
  const usedPrimaryText = Boolean(rawPrimaryText);
839
728
  let outcome;
@@ -844,24 +733,19 @@ function buildTurnResult(input) {
844
733
  } else {
845
734
  outcome = "execution_failure";
846
735
  }
847
- const rejectedPrimaryText = Boolean(
736
+ const suppressedPrimaryText = Boolean(
848
737
  rawPrimaryText && !noReplyRequested && !primaryText
849
738
  );
850
- const resolvedOutcome = rejectedPrimaryText ? "execution_failure" : outcome;
739
+ const resolvedOutcome = suppressedPrimaryText ? "execution_failure" : outcome;
851
740
  if (shouldTrace) {
852
- logInfo(
853
- "agent_message_out",
854
- spanContext,
855
- {
856
- "app.message.kind": "assistant_outbound",
857
- "app.message.length": primaryText.length,
858
- "app.message.output": summarizeMessageText(primaryText),
859
- "app.ai.outcome": resolvedOutcome,
860
- "app.ai.assistant_messages": assistantMessages.length,
861
- ...stopReason ? { "gen_ai.response.finish_reasons": [stopReason] } : {}
862
- },
863
- "Agent message sent"
864
- );
741
+ logInfo("agent.message.generated", {
742
+ "app.message.kind": "assistant_outbound",
743
+ "app.message.length": primaryText.length,
744
+ "app.message.output": summarizeMessageText(primaryText),
745
+ "app.ai.outcome": resolvedOutcome,
746
+ "app.ai.assistant_messages": assistantMessages.length,
747
+ ...stopReason ? { "gen_ai.response.finish_reasons": [stopReason] } : {}
748
+ });
865
749
  }
866
750
  const resolvedDiagnostics = {
867
751
  outcome: resolvedOutcome,
@@ -924,6 +808,45 @@ function nextProviderRetry(args) {
924
808
  return { delayMs, messages };
925
809
  }
926
810
 
811
+ // src/chat/services/empty-output-continuation.ts
812
+ function nextEmptyOutputContinuation(args) {
813
+ if (!hasCompactedConversationContext(args.messages)) {
814
+ return { kind: "none" };
815
+ }
816
+ if (args.lastAssistant?.stopReason === "error" || args.lastAssistant?.stopReason === "aborted") {
817
+ return { kind: "none" };
818
+ }
819
+ if (args.lastAssistant && decideReply(args.lastAssistant).kind !== "empty") {
820
+ return { kind: "none" };
821
+ }
822
+ if (args.attempt > 0) {
823
+ return { kind: "exhausted" };
824
+ }
825
+ if (!args.lastAssistant) {
826
+ return isContinuablePiBoundary(args.messages) ? {
827
+ kind: "retry",
828
+ messages: [...args.messages]
829
+ } : { kind: "exhausted" };
830
+ }
831
+ if (args.messages.at(-1) !== args.lastAssistant) {
832
+ return { kind: "exhausted" };
833
+ }
834
+ const messages = args.messages.slice(0, -1);
835
+ return isContinuablePiBoundary(messages) ? { kind: "retry", messages } : { kind: "exhausted" };
836
+ }
837
+
838
+ // src/chat/agent/retry-usage.ts
839
+ function getDiscardedRetryUsage(messages, retryMessages) {
840
+ const keepsExactPrefix = retryMessages.length <= messages.length && retryMessages.every((message, index) => message === messages[index]);
841
+ if (!keepsExactPrefix) {
842
+ throw new Error("Agent retry must retain an exact message prefix");
843
+ }
844
+ const usage = extractGenAiUsageSummary(
845
+ ...messages.slice(retryMessages.length).filter(isAssistantMessage)
846
+ );
847
+ return hasAgentTurnUsage(usage) ? usage : void 0;
848
+ }
849
+
927
850
  // src/chat/tool-support/structured-result.ts
928
851
  import { z } from "zod";
929
852
  var juniorToolContinuationSchema = z.object({
@@ -1262,20 +1185,9 @@ async function classifyTurn(args) {
1262
1185
  source: "router"
1263
1186
  };
1264
1187
  } catch (error) {
1265
- logWarn(
1266
- "turn_router_classifier_failed",
1267
- {
1268
- messageConversationId: args.metadata.threadId,
1269
- destinationName: args.metadata.channelId,
1270
- userId: args.metadata.actorId,
1271
- runId: args.metadata.runId,
1272
- modelId: args.fastModelId
1273
- },
1274
- {
1275
- "exception.message": error instanceof Error ? error.message : String(error)
1276
- },
1277
- "Turn router classifier failed; using the default route"
1278
- );
1188
+ logWarn("turn.router.classifier.failed", {
1189
+ "exception.message": error instanceof Error ? error.message : String(error)
1190
+ });
1279
1191
  return {
1280
1192
  profile: STANDARD_MODEL_PROFILE,
1281
1193
  reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
@@ -1509,18 +1421,13 @@ async function discoverRunSkills(args) {
1509
1421
  const roots = [
1510
1422
  ...new Set(availableSkills.map((skill) => skill.skillPath))
1511
1423
  ].sort();
1512
- logInfo(
1513
- "startup_discovery_summary",
1514
- args.spanContext,
1515
- {
1516
- "app.skill.count": availableSkills.length,
1517
- "app.skill.names": availableSkills.map((skill) => skill.name).sort(),
1518
- "app.file.directories": roots,
1519
- "app.plugin.count": plugins.length,
1520
- "app.plugin.names": plugins.map((plugin) => plugin.manifest.name).sort()
1521
- },
1522
- "Discovered startup SOUL/skills/plugins"
1523
- );
1424
+ logInfo("startup.discovery.completed", {
1425
+ "app.skill.count": availableSkills.length,
1426
+ "app.skill.names": availableSkills.map((skill) => skill.name).sort(),
1427
+ "app.file.directories": roots,
1428
+ "app.plugin.count": plugins.length,
1429
+ "app.plugin.names": plugins.map((plugin) => plugin.manifest.name).sort()
1430
+ });
1524
1431
  }
1525
1432
  return availableSkills;
1526
1433
  }
@@ -1557,7 +1464,7 @@ function getLoggedMarkdownFiles() {
1557
1464
  globalState.__juniorLoggedMarkdownFiles ??= /* @__PURE__ */ new Set();
1558
1465
  return globalState.__juniorLoggedMarkdownFiles;
1559
1466
  }
1560
- function loadOptionalMarkdownFile(candidates, fileName) {
1467
+ function loadOptionalMarkdownFile(candidates, fileName, loadedEventName) {
1561
1468
  for (const resolved of candidates) {
1562
1469
  try {
1563
1470
  const raw = fs2.readFileSync(resolved, "utf8").trim();
@@ -1566,14 +1473,9 @@ function loadOptionalMarkdownFile(candidates, fileName) {
1566
1473
  const logKey = `${fileName}:${resolved}`;
1567
1474
  if (!loggedMarkdownFiles.has(logKey)) {
1568
1475
  loggedMarkdownFiles.add(logKey);
1569
- logInfo(
1570
- `${fileName.toLowerCase()}_loaded`,
1571
- {},
1572
- {
1573
- "file.path": resolved
1574
- },
1575
- `Loaded ${fileName}`
1576
- );
1476
+ logInfo(loadedEventName, {
1477
+ "file.path": resolved
1478
+ });
1577
1479
  }
1578
1480
  return raw;
1579
1481
  }
@@ -1584,35 +1486,33 @@ function loadOptionalMarkdownFile(candidates, fileName) {
1584
1486
  return null;
1585
1487
  }
1586
1488
  function loadSoul() {
1587
- const soul = loadOptionalMarkdownFile(soulPathCandidates(), "SOUL.md");
1489
+ const soul = loadOptionalMarkdownFile(
1490
+ soulPathCandidates(),
1491
+ "SOUL.md",
1492
+ "soul.loaded"
1493
+ );
1588
1494
  if (soul) {
1589
1495
  return soul;
1590
1496
  }
1591
- logWarn(
1592
- "soul_load_fallback",
1593
- {},
1594
- {
1595
- "app.file.candidates": soulPathCandidates()
1596
- },
1597
- "SOUL.md not found; using built-in default personality"
1598
- );
1497
+ logWarn("soul.load.defaulted", {
1498
+ "app.file.candidates": soulPathCandidates()
1499
+ });
1599
1500
  return DEFAULT_SOUL;
1600
1501
  }
1601
1502
  function loadWorld() {
1602
- return loadOptionalMarkdownFile(worldPathCandidates(), "WORLD.md");
1503
+ return loadOptionalMarkdownFile(
1504
+ worldPathCandidates(),
1505
+ "WORLD.md",
1506
+ "world.loaded"
1507
+ );
1603
1508
  }
1604
1509
  var JUNIOR_PERSONALITY = (() => {
1605
1510
  try {
1606
1511
  return loadSoul();
1607
1512
  } catch (error) {
1608
- logWarn(
1609
- "soul_load_failed",
1610
- {},
1611
- {
1612
- "exception.message": error instanceof Error ? error.message : String(error)
1613
- },
1614
- "Failed to load SOUL.md; using built-in default personality"
1615
- );
1513
+ logWarn("soul.load.failed", {
1514
+ "exception.message": error instanceof Error ? error.message : String(error)
1515
+ });
1616
1516
  return DEFAULT_SOUL;
1617
1517
  }
1618
1518
  })();
@@ -1620,14 +1520,9 @@ var JUNIOR_WORLD = (() => {
1620
1520
  try {
1621
1521
  return loadWorld();
1622
1522
  } catch (error) {
1623
- logWarn(
1624
- "world_load_failed",
1625
- {},
1626
- {
1627
- "exception.message": error instanceof Error ? error.message : String(error)
1628
- },
1629
- "Failed to load WORLD.md; omitting world prompt context"
1630
- );
1523
+ logWarn("world.load.failed", {
1524
+ "exception.message": error instanceof Error ? error.message : String(error)
1525
+ });
1631
1526
  return null;
1632
1527
  }
1633
1528
  })();
@@ -1828,7 +1723,7 @@ var SAFETY_RULES = [
1828
1723
  var FAILURE_RULES = [
1829
1724
  "- For tool/runtime failures, run the named check before diagnosing and report the exact failed command plus stderr/exit code.",
1830
1725
  "- If a fact cannot be verified after focused checks, say what you checked and what blocked a stronger answer.",
1831
- "- Do not surface raw tool payloads, execution-escape text, or internal routing metadata as the final answer."
1726
+ "- Keep raw tool payloads and internal routing metadata out of the final answer."
1832
1727
  ];
1833
1728
  function renderRuleSection(tag, lines) {
1834
1729
  return [`<${tag}>`, ...lines, `</${tag}>`].join("\n");
@@ -2559,17 +2454,12 @@ ${usage}
2559
2454
  updatedBy: deps.actorId,
2560
2455
  source: "jr-rpc"
2561
2456
  });
2562
- logInfo(
2563
- "jr_rpc_config_set",
2564
- {},
2565
- {
2566
- "app.config.key": entry.key,
2567
- "app.config.scope": entry.scope,
2568
- "app.config.source": entry.source ?? "jr-rpc",
2569
- ...deps.activeSkill?.name ? { "app.skill.name": deps.activeSkill.name } : {}
2570
- },
2571
- "Set channel configuration via jr-rpc"
2572
- );
2457
+ logInfo("jr_rpc.config.updated", {
2458
+ "app.config.key": entry.key,
2459
+ "app.config.scope": entry.scope,
2460
+ "app.config.source": entry.source ?? "jr-rpc",
2461
+ ...deps.activeSkill?.name ? { "app.skill.name": deps.activeSkill.name } : {}
2462
+ });
2573
2463
  deps.onConfigurationValueChanged?.(entry.key, entry.value);
2574
2464
  return commandResult({
2575
2465
  stdout: {
@@ -2604,15 +2494,10 @@ ${usage}
2604
2494
  }
2605
2495
  const deleted = await configuration.unset(key);
2606
2496
  if (deleted) {
2607
- logInfo(
2608
- "jr_rpc_config_unset",
2609
- {},
2610
- {
2611
- "app.config.key": key,
2612
- ...deps.activeSkill?.name ? { "app.skill.name": deps.activeSkill.name } : {}
2613
- },
2614
- "Unset channel configuration via jr-rpc"
2615
- );
2497
+ logInfo("jr_rpc.config.removed", {
2498
+ "app.config.key": key,
2499
+ ...deps.activeSkill?.name ? { "app.skill.name": deps.activeSkill.name } : {}
2500
+ });
2616
2501
  deps.onConfigurationValueChanged?.(key, void 0);
2617
2502
  }
2618
2503
  return commandResult({
@@ -3563,17 +3448,13 @@ function createSandboxRuntime(options) {
3563
3448
  } : {},
3564
3449
  ...dependencyProfileHash ? { "app.sandbox.current_profile_hash": dependencyProfileHash } : {}
3565
3450
  });
3566
- logInfo(
3567
- "sandbox_hint_discarded_profile_mismatch",
3568
- traceContext,
3569
- {
3570
- ...sandboxRef.profileHash ? {
3571
- "app.sandbox.previous_profile_hash": sandboxRef.profileHash
3572
- } : {},
3573
- ...dependencyProfileHash ? { "app.sandbox.current_profile_hash": dependencyProfileHash } : {}
3574
- },
3575
- "Dependency profile changed; discarding sandbox hint and creating fresh session"
3576
- );
3451
+ logInfo("sandbox.hint.discarded", {
3452
+ "app.decision.reason": "dependency_profile_mismatch",
3453
+ ...sandboxRef.profileHash ? {
3454
+ "app.sandbox.previous_profile_hash": sandboxRef.profileHash
3455
+ } : {},
3456
+ ...dependencyProfileHash ? { "app.sandbox.current_profile_hash": dependencyProfileHash } : {}
3457
+ });
3577
3458
  sandboxRef = void 0;
3578
3459
  };
3579
3460
  const tryReuseCachedSandbox = async () => {
@@ -4294,6 +4175,19 @@ import { z as z3 } from "zod";
4294
4175
  function isContentOnlyToolResult(value) {
4295
4176
  return value !== null && typeof value === "object" && Array.isArray(value.content) && !("details" in value);
4296
4177
  }
4178
+ function isStructuredToolResultEnvelope(value) {
4179
+ return value !== null && typeof value === "object" && Array.isArray(value.content) && "details" in value;
4180
+ }
4181
+ function parseToolContent(content) {
4182
+ for (const item of content) {
4183
+ if (!item || typeof item !== "object" || (item.type === "text" ? typeof item.text !== "string" : item.type === "image" ? typeof item.data !== "string" || typeof item.mimeType !== "string" : true)) {
4184
+ throw new TypeError(
4185
+ "zodTool() content must contain valid text or image items."
4186
+ );
4187
+ }
4188
+ }
4189
+ return content;
4190
+ }
4297
4191
  function formatZodPath(path9) {
4298
4192
  return path9.length > 0 ? path9.map(String).join(".") : "root";
4299
4193
  }
@@ -4356,6 +4250,14 @@ function zodTool(definition) {
4356
4250
  "zodTool() content-only tools must return { content } without details."
4357
4251
  );
4358
4252
  }
4253
+ if (isStructuredToolResultEnvelope(result)) {
4254
+ return {
4255
+ content: parseToolContent(result.content),
4256
+ details: outputSchema.parse(
4257
+ juniorToolResultSchema.parse(result.details)
4258
+ )
4259
+ };
4260
+ }
4359
4261
  return outputSchema.parse(juniorToolResultSchema.parse(result));
4360
4262
  }
4361
4263
  } : {}
@@ -5434,15 +5336,10 @@ function createSandbox(options) {
5434
5336
  if (runtime.sandboxRef()) {
5435
5337
  return;
5436
5338
  }
5437
- logInfo(
5438
- "sandbox_boot_requested",
5439
- traceContext,
5440
- {
5441
- "app.sandbox.boot.trigger": trigger,
5442
- ...details
5443
- },
5444
- "Sandbox boot requested"
5445
- );
5339
+ logInfo("sandbox.boot.requested", {
5340
+ "app.sandbox.boot.trigger": trigger,
5341
+ ...details
5342
+ });
5446
5343
  };
5447
5344
  const executeBashTool = async (rawInput, command, context) => {
5448
5345
  const env = parseEnv(rawInput.env);
@@ -6717,11 +6614,18 @@ var McpToolManager = class {
6717
6614
  const providerContent = boundMcpContent(
6718
6615
  toAgentToolContent(result)
6719
6616
  );
6720
- return {
6617
+ const successResult = {
6721
6618
  content: toModelVisibleMcpContent(result),
6722
6619
  providerContent,
6723
6620
  ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {}
6724
6621
  };
6622
+ await this.options.onToolSuccess?.({
6623
+ arguments: resolvedArgs,
6624
+ provider: plugin.manifest.name,
6625
+ ...result.structuredContent !== void 0 ? { structuredContent: result.structuredContent } : {},
6626
+ toolName: tool.name
6627
+ });
6628
+ return successResult;
6725
6629
  } catch (error) {
6726
6630
  if (error instanceof McpAuthorizationRequiredError && await this.handleAuthorizationRequired(
6727
6631
  plugin.manifest.name,
@@ -6747,12 +6651,7 @@ var McpToolManager = class {
6747
6651
  };
6748
6652
  setSpanAttributes(errorAttributes);
6749
6653
  if (error instanceof McpToolError) {
6750
- logWarn(
6751
- "mcp_tool_call_failed",
6752
- {},
6753
- errorAttributes,
6754
- "MCP tool call failed"
6755
- );
6654
+ logWarn("mcp.tool_call.failed", errorAttributes);
6756
6655
  }
6757
6656
  throw error;
6758
6657
  }
@@ -6908,22 +6807,16 @@ async function enrichImagePrompt(rawPrompt) {
6908
6807
  maxTokens: 1024
6909
6808
  });
6910
6809
  if (text && text.trim().length > 0) {
6911
- logInfo(
6912
- "image_prompt_enriched",
6913
- {},
6914
- { "app.image.enriched_prompt_length": text.trim().length },
6915
- "Image prompt enriched with persona"
6916
- );
6810
+ logInfo("image.prompt.enriched", {
6811
+ "app.image.enriched_prompt_length": text.trim().length
6812
+ });
6917
6813
  return text.trim();
6918
6814
  }
6919
6815
  return rawPrompt;
6920
6816
  } catch (error) {
6921
- logWarn(
6922
- "image_prompt_enrichment_failed",
6923
- {},
6924
- { "exception.message": String(error) },
6925
- "Image prompt enrichment failed, using raw prompt"
6926
- );
6817
+ logWarn("image.prompt.enrichment.failed", {
6818
+ "exception.message": String(error)
6819
+ });
6927
6820
  return rawPrompt;
6928
6821
  }
6929
6822
  }
@@ -9002,18 +8895,13 @@ async function grantChannelCanvasAccess(canvasId, channelId) {
9002
8895
  }
9003
8896
  }
9004
8897
  );
9005
- } catch (error) {
9006
- logWarn(
9007
- "slack_canvas_access_set_failed",
9008
- {},
9009
- {
9010
- "app.slack.action": "canvases.access.set",
9011
- "app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1),
9012
- "app.slack.canvas.channel_id_prefix": channelId.slice(0, 1),
9013
- "app.slack.canvas.access_level": "write"
9014
- },
9015
- error instanceof Error ? error.message : "Failed to grant channel access to canvas"
9016
- );
8898
+ } catch {
8899
+ logWarn("slack.canvas.access_set.failed", {
8900
+ "app.slack.action": "canvases.access.set",
8901
+ "app.slack.canvas.canvas_id_prefix": canvasId.slice(0, 1),
8902
+ "app.slack.canvas.channel_id_prefix": channelId.slice(0, 1),
8903
+ "app.slack.canvas.access_level": "write"
8904
+ });
9017
8905
  }
9018
8906
  }
9019
8907
  async function writeCanvasMarkdown(input) {
@@ -9150,16 +9038,11 @@ function createSlackCanvasCreateTool(context, state) {
9150
9038
  execute: async ({ title, markdown }) => {
9151
9039
  const targetChannelId = context.destinationChannelId;
9152
9040
  if (!isConversationScopedChannel(targetChannelId)) {
9153
- logError(
9154
- "slack_canvas_create_invalid_context",
9155
- {},
9156
- {
9157
- "gen_ai.tool.name": "slackCanvasCreate",
9158
- "messaging.destination.name": targetChannelId ?? "none",
9159
- "app.slack.canvas.has_channel_context": Boolean(targetChannelId)
9160
- },
9161
- "Canvas create failed due to missing or invalid assistant channel context"
9162
- );
9041
+ logError("slack.canvas.create.context_invalid", {
9042
+ "gen_ai.tool.name": "slackCanvasCreate",
9043
+ "messaging.destination.name": targetChannelId ?? "none",
9044
+ "app.slack.canvas.has_channel_context": Boolean(targetChannelId)
9045
+ });
9163
9046
  throw new Error(
9164
9047
  "Cannot create a canvas without an active assistant channel context (C/G/D)."
9165
9048
  );
@@ -9292,15 +9175,10 @@ function createSlackCanvasEditTool(state) {
9292
9175
  return response;
9293
9176
  } catch (error) {
9294
9177
  const message = error instanceof Error ? error.message : "canvas edit failed";
9295
- logWarn(
9296
- "slack_canvas_edit_failed",
9297
- {},
9298
- {
9299
- "gen_ai.tool.name": "slackCanvasEdit",
9300
- "app.slack.canvas.canvas_id_prefix": target.canvasId.slice(0, 1)
9301
- },
9302
- message
9303
- );
9178
+ logWarn("slack.canvas.edit.failed", {
9179
+ "gen_ai.tool.name": "slackCanvasEdit",
9180
+ "app.slack.canvas.canvas_id_prefix": target.canvasId.slice(0, 1)
9181
+ });
9304
9182
  return {
9305
9183
  ok: false,
9306
9184
  status: "error",
@@ -9371,15 +9249,10 @@ function createSlackCanvasReadTool() {
9371
9249
  };
9372
9250
  } catch (error) {
9373
9251
  const message = error instanceof Error ? error.message : "canvas read failed";
9374
- logWarn(
9375
- "slack_canvas_read_failed",
9376
- {},
9377
- {
9378
- "gen_ai.tool.name": "slackCanvasRead",
9379
- "app.slack.canvas.canvas_id_prefix": target.canvasId.slice(0, 1)
9380
- },
9381
- message
9382
- );
9252
+ logWarn("slack.canvas.read.failed", {
9253
+ "gen_ai.tool.name": "slackCanvasRead",
9254
+ "app.slack.canvas.canvas_id_prefix": target.canvasId.slice(0, 1)
9255
+ });
9383
9256
  return {
9384
9257
  ok: false,
9385
9258
  status: "error",
@@ -9438,15 +9311,10 @@ function createSlackCanvasWriteTool(state) {
9438
9311
  return response;
9439
9312
  } catch (error) {
9440
9313
  const message = error instanceof Error ? error.message : "canvas write failed";
9441
- logWarn(
9442
- "slack_canvas_write_failed",
9443
- {},
9444
- {
9445
- "gen_ai.tool.name": "slackCanvasWrite",
9446
- "app.slack.canvas.canvas_id_prefix": target.canvasId.slice(0, 1)
9447
- },
9448
- message
9449
- );
9314
+ logWarn("slack.canvas.write.failed", {
9315
+ "gen_ai.tool.name": "slackCanvasWrite",
9316
+ "app.slack.canvas.canvas_id_prefix": target.canvasId.slice(0, 1)
9317
+ });
9450
9318
  return {
9451
9319
  ok: false,
9452
9320
  status: "error",
@@ -10494,6 +10362,10 @@ function createSystemTimeTool() {
10494
10362
  import { z as z37 } from "zod";
10495
10363
  var DEFAULT_LIMIT3 = 25;
10496
10364
  var MAX_LIMIT = 100;
10365
+ var MAX_EVENT_DATA_BYTES = 4e3;
10366
+ var MAX_EVENTS_JSON_BYTES = 2e4;
10367
+ var MAX_IDENTITY_CHARS = 256;
10368
+ var textEncoder = new TextEncoder();
10497
10369
  var CONVERSATION_EVENTS_TOOL_SOURCE = {
10498
10370
  id: "conversation-events",
10499
10371
  description: "Inspect Junior's durable conversation event log for debugging runtime behavior."
@@ -10507,7 +10379,7 @@ var projectedEventSchema = z37.object({
10507
10379
  created_at: z37.iso.datetime().describe("When the event was recorded."),
10508
10380
  idempotency_key: z37.string().min(1).optional().describe("Retry-stable event identity, when one was assigned."),
10509
10381
  data: z37.record(z37.string(), z37.unknown()).describe(
10510
- "Event payload. The type field identifies the event; replacement-history events report replacement_history_count instead of replaying the full model context."
10382
+ "Event data. Oversized payloads are replaced by identifying fields, payload_omitted, and payload_json_bytes."
10511
10383
  )
10512
10384
  }).strict();
10513
10385
  var queryConversationEventsOutputSchema = juniorToolResultSchema.extend({
@@ -10518,6 +10390,9 @@ var queryConversationEventsOutputSchema = juniorToolResultSchema.extend({
10518
10390
  ),
10519
10391
  has_newer: z37.boolean().describe(
10520
10392
  "Whether matching events exist after this page; request them with after_seq set to the last returned seq."
10393
+ ),
10394
+ omitted_event_count: z37.number().int().nonnegative().optional().describe(
10395
+ "Fetched events omitted to keep this response bounded. Continue with the pagination cursor for the omitted direction."
10521
10396
  )
10522
10397
  });
10523
10398
  function createQueryConversationEventsTool(context) {
@@ -10597,14 +10472,17 @@ function createQueryConversationEventsTool(context) {
10597
10472
  ...beforeSeq === void 0 ? {} : { beforeSeq },
10598
10473
  ...types === void 0 ? {} : { types }
10599
10474
  });
10600
- const events = projectEventsForTool(page);
10475
+ const newestFirst = afterSeq === void 0;
10476
+ const projected = projectEventsForTool(page, { newestFirst });
10601
10477
  return {
10602
10478
  ok: true,
10603
10479
  status: "success",
10604
10480
  conversation_id: conversationId,
10605
- events,
10606
- has_older: page.hasOlder,
10607
- has_newer: page.hasNewer
10481
+ events: projected.events,
10482
+ has_older: page.hasOlder || newestFirst && projected.omittedEventCount > 0,
10483
+ has_newer: page.hasNewer || !newestFirst && projected.omittedEventCount > 0,
10484
+ truncated: projected.omittedEventCount > 0,
10485
+ ...projected.omittedEventCount > 0 ? { omitted_event_count: projected.omittedEventCount } : {}
10608
10486
  };
10609
10487
  }
10610
10488
  });
@@ -10689,11 +10567,30 @@ function assertCanQueryConversationEvents(args) {
10689
10567
  `Conversation events are not accessible: ${targetConversationId}`
10690
10568
  );
10691
10569
  }
10692
- function projectEventsForTool(page) {
10693
- return page.events.map(projectEvent);
10570
+ function projectEventsForTool(page, options) {
10571
+ const projected = page.events.map(projectEvent);
10572
+ const events = [];
10573
+ let jsonBytes = 2;
10574
+ const candidates = options.newestFirst ? [...projected].reverse() : projected;
10575
+ for (const event of candidates) {
10576
+ const eventBytes = jsonByteLength(event);
10577
+ const separatorBytes = events.length === 0 ? 0 : 1;
10578
+ if (jsonBytes + separatorBytes + eventBytes > MAX_EVENTS_JSON_BYTES) {
10579
+ break;
10580
+ }
10581
+ events.push(event);
10582
+ jsonBytes += separatorBytes + eventBytes;
10583
+ }
10584
+ if (options.newestFirst) {
10585
+ events.reverse();
10586
+ }
10587
+ return {
10588
+ events,
10589
+ omittedEventCount: projected.length - events.length
10590
+ };
10694
10591
  }
10695
10592
  function projectEvent(event) {
10696
- const data = stripReplacementHistory(event.data);
10593
+ const data = boundEventData(stripReplacementHistory(event.data));
10697
10594
  return {
10698
10595
  seq: event.seq,
10699
10596
  history_version: event.historyVersion,
@@ -10702,6 +10599,46 @@ function projectEvent(event) {
10702
10599
  data
10703
10600
  };
10704
10601
  }
10602
+ function boundEventData(data) {
10603
+ const payloadJsonBytes = jsonByteLength(data);
10604
+ if (payloadJsonBytes <= MAX_EVENT_DATA_BYTES) {
10605
+ return data;
10606
+ }
10607
+ return {
10608
+ ...eventIdentity(data),
10609
+ payload_omitted: true,
10610
+ payload_json_bytes: payloadJsonBytes
10611
+ };
10612
+ }
10613
+ function jsonByteLength(value) {
10614
+ return textEncoder.encode(JSON.stringify(value)).byteLength;
10615
+ }
10616
+ function eventIdentity(data) {
10617
+ const identityKeys = [
10618
+ "type",
10619
+ "originalType",
10620
+ "messageId",
10621
+ "role",
10622
+ "turnId",
10623
+ "toolCallId",
10624
+ "toolName",
10625
+ "subagentInvocationId",
10626
+ "subagentKind",
10627
+ "childConversationId",
10628
+ "outcome",
10629
+ "failureCode",
10630
+ "provider",
10631
+ "kind",
10632
+ "modelProfile",
10633
+ "modelId"
10634
+ ];
10635
+ return Object.fromEntries(
10636
+ identityKeys.flatMap((key) => {
10637
+ const value = data[key];
10638
+ return typeof value === "string" ? [[key, value.slice(0, MAX_IDENTITY_CHARS)]] : [];
10639
+ })
10640
+ );
10641
+ }
10705
10642
  function stripReplacementHistory(data) {
10706
10643
  if (data.type === "unknown") {
10707
10644
  return {
@@ -11368,18 +11305,12 @@ function createWebSearchTool(modelId, override) {
11368
11305
  const message = formatSearchFailure(error);
11369
11306
  const timeout = /timed out/i.test(message);
11370
11307
  const retryable = !isAuthFailure(message);
11371
- logException(
11372
- error,
11373
- "web_search_failed",
11374
- {},
11375
- {
11376
- "gen_ai.tool.name": "webSearch",
11377
- "app.web_search.timeout": timeout,
11378
- "app.web_search.retryable": retryable,
11379
- "app.web_search.query": query
11380
- },
11381
- message
11382
- );
11308
+ logException(error, "web.search.failed", {
11309
+ "gen_ai.tool.name": "webSearch",
11310
+ "app.web_search.timeout": timeout,
11311
+ "app.web_search.retryable": retryable,
11312
+ "app.web_search.query": query
11313
+ });
11383
11314
  return {
11384
11315
  ok: false,
11385
11316
  status: "error",
@@ -12097,7 +12028,7 @@ function getToolErrorAttributes(error) {
12097
12028
  ...error.detailRule ? { "app.slack.detail_rule": error.detailRule } : {}
12098
12029
  };
12099
12030
  }
12100
- function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, traceContext, conversationPrivacy, setExecutionSpanAttributes) {
12031
+ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, conversationPrivacy, setExecutionSpanAttributes) {
12101
12032
  const errorType = getToolErrorType(error);
12102
12033
  const errorMessage = getMcpAwareTelemetryMessage(error, conversationPrivacy);
12103
12034
  const errorAttributes = {
@@ -12113,50 +12044,34 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
12113
12044
  }
12114
12045
  if (error instanceof PluginCredentialFailureError) {
12115
12046
  if (shouldTrace) {
12116
- logInfo(
12117
- "plugin_credential_rejected",
12118
- traceContext,
12119
- {
12120
- "app.credential.provider": error.provider,
12121
- "gen_ai.operation.name": "execute_tool",
12122
- "gen_ai.tool.name": toolName,
12123
- ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
12124
- "error.type": errorType
12125
- },
12126
- "Plugin credentials were rejected during tool execution"
12127
- );
12047
+ logInfo("plugin.credential.rejected", {
12048
+ "app.credential.provider": error.provider,
12049
+ "gen_ai.operation.name": "execute_tool",
12050
+ "gen_ai.tool.name": toolName,
12051
+ ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
12052
+ "error.type": errorType
12053
+ });
12128
12054
  }
12129
12055
  throw error;
12130
12056
  }
12131
12057
  if (shouldTrace) {
12132
- logWarn(
12133
- "agent_tool_call_failed",
12134
- traceContext,
12135
- {
12136
- "gen_ai.operation.name": "execute_tool",
12137
- "gen_ai.tool.name": toolName,
12138
- ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
12139
- ...errorAttributes,
12140
- "exception.message": errorMessage
12141
- },
12142
- "Agent tool call failed"
12143
- );
12058
+ logWarn("agent.tool_call.failed", {
12059
+ "gen_ai.operation.name": "execute_tool",
12060
+ "gen_ai.tool.name": toolName,
12061
+ ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
12062
+ ...errorAttributes,
12063
+ "exception.message": errorMessage
12064
+ });
12144
12065
  }
12145
12066
  const isExpectedToolFailure = error instanceof McpToolError || error instanceof ToolInputError || isPluginToolInputError(error);
12146
12067
  if (!isExpectedToolFailure) {
12147
- logException(
12148
- error,
12149
- "agent_tool_call_failed",
12150
- {},
12151
- {
12152
- "gen_ai.operation.name": "execute_tool",
12153
- "gen_ai.tool.name": toolName,
12154
- ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
12155
- ...errorAttributes,
12156
- ...getToolErrorAttributes(error)
12157
- },
12158
- "Agent tool call failed"
12159
- );
12068
+ logException(error, "agent.tool_call.failed", {
12069
+ "gen_ai.operation.name": "execute_tool",
12070
+ "gen_ai.tool.name": toolName,
12071
+ ...toolCallId ? { "gen_ai.tool.call.id": toolCallId } : {},
12072
+ ...errorAttributes,
12073
+ ...getToolErrorAttributes(error)
12074
+ });
12160
12075
  }
12161
12076
  throw error;
12162
12077
  }
@@ -12524,15 +12439,10 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxTools,
12524
12439
  try {
12525
12440
  await onToolResult?.(report);
12526
12441
  } catch (error) {
12527
- logWarn(
12528
- "tool_result_observer_failed",
12529
- spanContext,
12530
- {
12531
- "gen_ai.tool.name": report.toolName,
12532
- "exception.message": error instanceof Error ? error.message : String(error)
12533
- },
12534
- "Tool result observer failed"
12535
- );
12442
+ logWarn("tool.result.observer.failed", {
12443
+ "gen_ai.tool.name": report.toolName,
12444
+ "exception.message": error instanceof Error ? error.message : String(error)
12445
+ });
12536
12446
  }
12537
12447
  };
12538
12448
  const toolResultOk = (details) => {
@@ -12591,15 +12501,10 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxTools,
12591
12501
  projectedPrivateResult = toolDef.privateTraceResult(resultAttributeValue);
12592
12502
  hasProjectedPrivateResult = projectedPrivateResult !== void 0;
12593
12503
  } catch (error) {
12594
- logWarn(
12595
- "tool_private_trace_projection_failed",
12596
- spanContext,
12597
- {
12598
- "error.type": error instanceof Error ? error.name : typeof error,
12599
- "gen_ai.tool.name": toolName
12600
- },
12601
- "Tool private trace projection failed"
12602
- );
12504
+ logWarn("tool.private.trace_projection.failed", {
12505
+ "error.type": error instanceof Error ? error.name : typeof error,
12506
+ "gen_ai.tool.name": toolName
12507
+ });
12603
12508
  }
12604
12509
  }
12605
12510
  const toolResultAttribute = effectiveConversationPrivacy === "private" && toolDef.privateTraceResult && !hasProjectedPrivateResult ? void 0 : serializeToolPayload(
@@ -12713,7 +12618,6 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxTools,
12713
12618
  executionToolName,
12714
12619
  toolCallId,
12715
12620
  shouldTrace,
12716
- spanContext,
12717
12621
  effectiveConversationPrivacy,
12718
12622
  setSpanAttributes2
12719
12623
  );
@@ -13186,26 +13090,21 @@ function logSandboxEgressUpstreamRequest(input) {
13186
13090
  if (!shouldLogSandboxEgressInfo()) {
13187
13091
  return;
13188
13092
  }
13189
- logInfo(
13190
- "sandbox_egress_upstream_request",
13191
- {},
13192
- {
13193
- ...egressAttributes({
13194
- egressId: input.egressId,
13195
- grantAccess: input.grantAccess,
13196
- grantName: input.grantName,
13197
- grantReason: input.grantReason,
13198
- host: input.upstreamUrl.hostname,
13199
- method: input.request.method,
13200
- path: input.upstreamUrl.pathname,
13201
- provider: input.provider,
13202
- status: input.upstream.status
13203
- }),
13204
- ...routingAttributes(input.request, input.upstreamUrl),
13205
- "app.sandbox.egress.upstream_ok": input.upstream.ok
13206
- },
13207
- `Sandbox egress ${input.request.method} ${input.upstreamUrl.hostname}${displayedUpstreamPath(input.upstreamUrl)} -> ${input.upstream.status}`
13208
- );
13093
+ logInfo("sandbox.egress.upstream.requested", {
13094
+ ...egressAttributes({
13095
+ egressId: input.egressId,
13096
+ grantAccess: input.grantAccess,
13097
+ grantName: input.grantName,
13098
+ grantReason: input.grantReason,
13099
+ host: input.upstreamUrl.hostname,
13100
+ method: input.request.method,
13101
+ path: input.upstreamUrl.pathname,
13102
+ provider: input.provider,
13103
+ status: input.upstream.status
13104
+ }),
13105
+ ...routingAttributes(input.request, input.upstreamUrl),
13106
+ "app.sandbox.egress.upstream_ok": input.upstream.ok
13107
+ });
13209
13108
  }
13210
13109
  async function requestBodyBytes(request) {
13211
13110
  if (request.method === "GET" || request.method === "HEAD" || request.body === null) {
@@ -13402,22 +13301,18 @@ async function executeCredentialedEgressRequest(input) {
13402
13301
  });
13403
13302
  } catch (error) {
13404
13303
  if (isEgressPolicyDenied(error)) {
13405
- logWarn(
13406
- "sandbox_egress_policy_denied",
13407
- {},
13408
- {
13409
- ...egressAttributes({
13410
- egressId: activeEgressId,
13411
- host: upstreamUrl.hostname,
13412
- method: request.method,
13413
- path: upstreamUrl.pathname,
13414
- provider,
13415
- status: 403
13416
- }),
13417
- ...routingAttributes(request, upstreamUrl)
13418
- },
13419
- error.message
13420
- );
13304
+ logWarn("sandbox.egress.policy.denied", {
13305
+ ...egressAttributes({
13306
+ egressId: activeEgressId,
13307
+ host: upstreamUrl.hostname,
13308
+ method: request.method,
13309
+ path: upstreamUrl.pathname,
13310
+ provider,
13311
+ status: 403
13312
+ }),
13313
+ ...routingAttributes(request, upstreamUrl),
13314
+ "app.sandbox.egress.policy.reason": error.message
13315
+ });
13421
13316
  return policyDeniedResponse(error);
13422
13317
  }
13423
13318
  throw error;
@@ -13445,8 +13340,7 @@ async function executeCredentialedEgressRequest(input) {
13445
13340
  });
13446
13341
  const isAuthRequired = error.kind === "auth_required";
13447
13342
  logWarn(
13448
- isAuthRequired ? "sandbox_egress_credential_needed" : "sandbox_egress_credential_unavailable",
13449
- {},
13343
+ isAuthRequired ? "sandbox.egress.credential.needed" : "sandbox.egress.credential.unavailable",
13450
13344
  {
13451
13345
  ...egressAttributes({
13452
13346
  egressId: activeEgressId,
@@ -13460,8 +13354,7 @@ async function executeCredentialedEgressRequest(input) {
13460
13354
  status: 401
13461
13355
  }),
13462
13356
  ...routingAttributes(request, upstreamUrl)
13463
- },
13464
- isAuthRequired ? "Sandbox egress grant needs user authorization before issuing a credential lease" : "Sandbox egress credential lease is unavailable for selected grant"
13357
+ }
13465
13358
  );
13466
13359
  return authRequiredResponse({
13467
13360
  provider: error.provider,
@@ -13481,17 +13374,12 @@ async function executeCredentialedEgressRequest(input) {
13481
13374
  upstreamUrl
13482
13375
  });
13483
13376
  if (!hasSandboxEgressLeaseTransformForHost(lease, upstreamUrl.hostname)) {
13484
- logWarn(
13485
- "sandbox_egress_transform_missing",
13486
- {},
13487
- {
13488
- ...attributes(403),
13489
- "app.sandbox.egress.transform_domains": lease.headerTransforms.map(
13490
- (transform) => transform.domain
13491
- )
13492
- },
13493
- "Sandbox egress credential lease does not cover forwarded host"
13494
- );
13377
+ logWarn("sandbox.egress.transform.missing", {
13378
+ ...attributes(403),
13379
+ "app.sandbox.egress.transform_domains": lease.headerTransforms.map(
13380
+ (transform) => transform.domain
13381
+ )
13382
+ });
13495
13383
  return Response.json(
13496
13384
  { error: "Credential lease does not cover forwarded host" },
13497
13385
  { status: 403 }
@@ -13545,14 +13433,9 @@ async function executeCredentialedEgressRequest(input) {
13545
13433
  upstream,
13546
13434
  upstreamUrl
13547
13435
  });
13548
- logWarn(
13549
- "sandbox_egress_upstream_permission_classified",
13550
- {},
13551
- {
13552
- ...attributes(upstream.status, upstream)
13553
- },
13554
- "Sandbox egress plugin classified upstream response as permission denied"
13555
- );
13436
+ logWarn("sandbox.egress.upstream_permission.classified", {
13437
+ ...attributes(upstream.status, upstream)
13438
+ });
13556
13439
  }
13557
13440
  } catch (error) {
13558
13441
  if (!isEgressAuthRequired(error)) {
@@ -13566,14 +13449,9 @@ async function executeCredentialedEgressRequest(input) {
13566
13449
  authorization: error.authorization ?? lease.authorization,
13567
13450
  message: error.message
13568
13451
  });
13569
- logWarn(
13570
- "sandbox_egress_upstream_auth_required_classified",
13571
- {},
13572
- {
13573
- ...attributes(upstream.status, upstream)
13574
- },
13575
- "Sandbox egress plugin classified upstream response as auth required"
13576
- );
13452
+ logWarn("sandbox.egress.upstream_auth_requirement.classified", {
13453
+ ...attributes(upstream.status, upstream)
13454
+ });
13577
13455
  await upstream.body?.cancel().catch(() => void 0);
13578
13456
  return authRequiredResponse({
13579
13457
  provider,
@@ -13592,28 +13470,18 @@ async function executeCredentialedEgressRequest(input) {
13592
13470
  upstreamUrl
13593
13471
  });
13594
13472
  if (upstream.status >= 400) {
13595
- logWarn(
13596
- "sandbox_egress_upstream_error_response",
13597
- {},
13598
- {
13599
- ...attributes(upstream.status, upstream),
13600
- "error.type": `http_${upstream.status}`
13601
- },
13602
- `Sandbox egress upstream returned HTTP ${upstream.status}`
13603
- );
13473
+ logWarn("sandbox.egress.upstream_response.failed", {
13474
+ ...attributes(upstream.status, upstream),
13475
+ "error.type": `http_${upstream.status}`
13476
+ });
13604
13477
  }
13605
13478
  if (upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS || upstream.status === UPSTREAM_PERMISSION_REJECTION_STATUS) {
13606
- logWarn(
13607
- "sandbox_egress_upstream_auth_rejected",
13608
- {},
13609
- {
13610
- ...attributes(upstream.status, upstream),
13611
- ...upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS ? {
13612
- "app.sandbox.egress.www_authenticate": upstream.headers.get("www-authenticate") ?? void 0
13613
- } : {}
13614
- },
13615
- upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS ? "Sandbox egress upstream auth rejected injected credential" : "Sandbox egress upstream permission denied"
13616
- );
13479
+ logWarn("sandbox.egress.upstream_auth.rejected", {
13480
+ ...attributes(upstream.status, upstream),
13481
+ ...upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS ? {
13482
+ "app.sandbox.egress.www_authenticate": upstream.headers.get("www-authenticate") ?? void 0
13483
+ } : {}
13484
+ });
13617
13485
  if (upstream.status === UPSTREAM_TOKEN_REJECTION_STATUS) {
13618
13486
  await clearCredentialLease(provider, lease.grant, credentialContext);
13619
13487
  await recordAuthRequired({
@@ -13712,7 +13580,7 @@ function createPluginEgress(deps) {
13712
13580
  }
13713
13581
 
13714
13582
  // src/chat/agent/tools.ts
13715
- async function tryRecordSkillLoadStat(skill, spanContext) {
13583
+ async function tryRecordSkillLoadStat(skill) {
13716
13584
  if (!process.env.DATABASE_URL) return;
13717
13585
  try {
13718
13586
  await incrementStat({
@@ -13721,16 +13589,11 @@ async function tryRecordSkillLoadStat(skill, spanContext) {
13721
13589
  name: skill.name
13722
13590
  });
13723
13591
  } catch (error) {
13724
- logWarn(
13725
- "skill_load_stat_failed",
13726
- spanContext,
13727
- {
13728
- "app.skill.name": skill.name,
13729
- "app.plugin.name": skill.pluginProvider,
13730
- "exception.message": error instanceof Error ? error.message : String(error)
13731
- },
13732
- "Failed to record skill load stat"
13733
- );
13592
+ logWarn("skill.load.stat.failed", {
13593
+ "app.skill.name": skill.name,
13594
+ "app.plugin.name": skill.pluginProvider,
13595
+ "exception.message": error instanceof Error ? error.message : String(error)
13596
+ });
13734
13597
  }
13735
13598
  }
13736
13599
  async function wireAgentTools(args) {
@@ -13800,7 +13663,13 @@ async function wireAgentTools(args) {
13800
13663
  pluginCatalogRuntime.getMcpProviders(),
13801
13664
  {
13802
13665
  authProviderFactory: mcpAuth.authProviderFactory,
13803
- onAuthorizationRequired: mcpAuth.onAuthorizationRequired
13666
+ onAuthorizationRequired: mcpAuth.onAuthorizationRequired,
13667
+ onToolSuccess: async (input) => {
13668
+ await pluginHooks.afterMcpTool({
13669
+ ...input,
13670
+ conversationId: args.conversationId
13671
+ });
13672
+ }
13804
13673
  }
13805
13674
  );
13806
13675
  const getPendingAuthPause = () => pluginAuth.getPendingPause() ?? mcpAuth.getPendingPause();
@@ -13890,7 +13759,7 @@ async function wireAgentTools(args) {
13890
13759
  if (await mcpToolManager.activateForSkill(effective)) {
13891
13760
  await args.recordConnectedMcpProvider(effective.pluginProvider);
13892
13761
  }
13893
- await tryRecordSkillLoadStat(effective, args.spanContext);
13762
+ await tryRecordSkillLoadStat(effective);
13894
13763
  if (mcpAuth.getPendingPause()) {
13895
13764
  return void 0;
13896
13765
  }
@@ -13956,15 +13825,10 @@ async function wireAgentTools(args) {
13956
13825
  toolName
13957
13826
  });
13958
13827
  } catch (error) {
13959
- logWarn(
13960
- "tool_invocation_observer_failed",
13961
- args.spanContext,
13962
- {
13963
- "gen_ai.tool.name": toolName,
13964
- "exception.message": error instanceof Error ? error.message : String(error)
13965
- },
13966
- "Tool invocation observer failed"
13967
- );
13828
+ logWarn("tool.invocation.observer.failed", {
13829
+ "gen_ai.tool.name": toolName,
13830
+ "exception.message": error instanceof Error ? error.message : String(error)
13831
+ });
13968
13832
  }
13969
13833
  };
13970
13834
  const agentTools = createPiAgentTools(
@@ -13991,18 +13855,13 @@ async function wireAgentTools(args) {
13991
13855
  try {
13992
13856
  await mcpToolManager.close();
13993
13857
  } catch (closeError) {
13994
- logWarn(
13995
- "mcp_tool_manager_close_failed",
13996
- {},
13997
- {
13998
- ...getMcpProviderErrorAttributes(closeError),
13999
- "exception.message": getMcpAwareTelemetryMessage(
14000
- closeError,
14001
- args.conversationPrivacy
14002
- )
14003
- },
14004
- "Failed to close MCP tool manager"
14005
- );
13858
+ logWarn("mcp.tool_manager.close.failed", {
13859
+ ...getMcpProviderErrorAttributes(closeError),
13860
+ "exception.message": getMcpAwareTelemetryMessage(
13861
+ closeError,
13862
+ args.conversationPrivacy
13863
+ )
13864
+ });
14006
13865
  }
14007
13866
  },
14008
13867
  toolGuidance,
@@ -14035,7 +13894,6 @@ function createResumeState(args) {
14035
13894
  source: args.runSource,
14036
13895
  sessionId: args.turnId,
14037
13896
  loadedSkillNames: args.getLoadedSkillNames(),
14038
- logContext: args.logContext,
14039
13897
  modelId: args.getModelId(),
14040
13898
  ...args.getReasoningLevel() ? { reasoningLevel: args.getReasoningLevel() } : {},
14041
13899
  actor: args.actor,
@@ -14216,6 +14074,7 @@ function createResumeState(args) {
14216
14074
  }
14217
14075
 
14218
14076
  // src/chat/services/context-compaction.ts
14077
+ import { estimateContextTokens } from "@earendil-works/pi-agent-core";
14219
14078
  var RETAINED_USER_MESSAGE_TOKENS = 2e4;
14220
14079
  var MAX_SUMMARY_INPUT_CHARS = 8e4;
14221
14080
  var MAX_VISIBLE_CONTEXT_CHARS = 2e4;
@@ -14409,10 +14268,7 @@ async function summarizeContext(args, deps) {
14409
14268
  return summary.slice(0, MAX_SUMMARY_CHARS);
14410
14269
  }
14411
14270
  function estimateHistoryTokens(messages) {
14412
- return messages.reduce(
14413
- (total, message) => total + estimateTextTokens(JSON.stringify(message) ?? ""),
14414
- 0
14415
- );
14271
+ return estimateContextTokens(messages).tokens;
14416
14272
  }
14417
14273
  function buildReplacementProvenance(args) {
14418
14274
  return [
@@ -14469,21 +14325,9 @@ async function maybeCompactWithDeps(args, deps) {
14469
14325
  deps
14470
14326
  );
14471
14327
  } catch (error) {
14472
- logWarn(
14473
- "context_compaction_summary_failed",
14474
- {
14475
- messageConversationId: args.metadata?.threadId,
14476
- userId: args.metadata?.actorId,
14477
- destinationName: args.metadata?.channelId,
14478
- runId: args.metadata?.runId,
14479
- assistantUserName: botConfig.userName,
14480
- modelId: botConfig.fastModelId
14481
- },
14482
- {
14483
- "exception.message": error instanceof Error ? error.message : String(error)
14484
- },
14485
- "Context compaction failed; continuing with prior history"
14486
- );
14328
+ logWarn("context_compaction.summary.failed", {
14329
+ "exception.message": error instanceof Error ? error.message : String(error)
14330
+ });
14487
14331
  return { compacted: false, reason: "summary_failed" };
14488
14332
  }
14489
14333
  return await writeCompactedThreadContext(args, source.messages, summary, {
@@ -14623,23 +14467,11 @@ async function compactActiveContextIfNeeded(args, deps) {
14623
14467
  inputLimitTokens
14624
14468
  );
14625
14469
  }
14626
- logWarn(
14627
- "active_context_compaction_summary_failed",
14628
- {
14629
- messageConversationId: args.metadata?.threadId,
14630
- userId: args.metadata?.actorId,
14631
- destinationName: args.metadata?.channelId,
14632
- runId: args.metadata?.runId,
14633
- assistantUserName: botConfig.userName,
14634
- modelId: args.modelId
14635
- },
14636
- {
14637
- "app.context_input_limit_tokens": inputLimitTokens,
14638
- "app.context_tokens_estimated": source.estimatedTokens,
14639
- "exception.message": error instanceof Error ? error.message : String(error)
14640
- },
14641
- "Active context compaction failed below the hard input limit"
14642
- );
14470
+ logWarn("context_compaction.active.summary.failed", {
14471
+ "app.context_input_limit_tokens": inputLimitTokens,
14472
+ "app.context_tokens_estimated": source.estimatedTokens,
14473
+ "exception.message": error instanceof Error ? error.message : String(error)
14474
+ });
14643
14475
  return { compacted: false, reason: "summary_failed" };
14644
14476
  }
14645
14477
  const summaryMessage = userMessage(
@@ -14741,7 +14573,7 @@ function waitForAbortSettlement(promise, timeoutMs) {
14741
14573
  );
14742
14574
  });
14743
14575
  }
14744
- async function executeAgentRun(request) {
14576
+ async function executeAgentRun(request, streamFn) {
14745
14577
  if (!request.routing.destination) {
14746
14578
  throw new TypeError("Assistant reply generation requires a destination");
14747
14579
  }
@@ -14776,12 +14608,13 @@ async function executeAgentRun(request) {
14776
14608
  () => executeAgentRunInPrivacyContext(
14777
14609
  request,
14778
14610
  conversationPrivacy,
14779
- runLogContext
14611
+ runLogContext,
14612
+ streamFn
14780
14613
  )
14781
14614
  )
14782
14615
  );
14783
14616
  }
14784
- async function executeAgentRunInPrivacyContext(request, conversationPrivacy, runLogContext) {
14617
+ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, runLogContext, streamFn) {
14785
14618
  const { conversationId, input, routing, runId, turnId } = request;
14786
14619
  const policy = request.policy ?? {};
14787
14620
  const signal = policy.signal;
@@ -14819,19 +14652,6 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
14819
14652
  const slackDestination = routing.destination.platform === "slack" ? routing.destination : void 0;
14820
14653
  const slackActor = actor?.platform === "slack" ? actor : void 0;
14821
14654
  const userInput = input.messageText;
14822
- const credentialActor = routing.credentialContext?.actor;
14823
- const credentialActorLogContext = credentialActor ? {
14824
- actorType: "type" in credentialActor ? credentialActor.type : "system",
14825
- actorId: "type" in credentialActor ? credentialActor.userId : credentialActor.name
14826
- } : {};
14827
- const sessionRecordLogContext = {
14828
- threadId: slackSource ? conversationId : void 0,
14829
- actorId: slackActor?.userId,
14830
- channelId: slackDestination?.channelId,
14831
- runId,
14832
- ...credentialActorLogContext,
14833
- assistantUserName: botConfig.userName
14834
- };
14835
14655
  const recordConnectedMcpProvider = async (provider) => {
14836
14656
  if (connectedMcpProviders.has(provider)) {
14837
14657
  return;
@@ -14858,27 +14678,21 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
14858
14678
  const shouldTrace = shouldEmitDevAgentTrace();
14859
14679
  const spanContext = { modelId: activeModelId };
14860
14680
  const availableSkills = await discoverRunSkills({
14861
- skillDirs: policy.skillDirs,
14862
- spanContext
14681
+ skillDirs: policy.skillDirs
14863
14682
  });
14864
14683
  if (shouldTrace) {
14865
14684
  const inboundAttachmentCount = input.inboundAttachmentCount ?? 0;
14866
14685
  const promptAttachmentCount = input.userAttachments?.length ?? 0;
14867
- logInfo(
14868
- "agent_message_in",
14869
- spanContext,
14870
- {
14871
- "app.message.kind": "user_inbound",
14872
- "app.message.length": userInput.length,
14873
- "app.message.input": summarizeMessageText(userInput),
14874
- // Log both counts so image uploads filtered by vision/config do not
14875
- // look indistinguishable from Slack ingress dropping attachments.
14876
- "app.message.attachment_count": inboundAttachmentCount,
14877
- "app.message.prompt_attachment_count": promptAttachmentCount,
14878
- "messaging.message.id": slackSource?.messageTs ?? ""
14879
- },
14880
- "Agent message received"
14881
- );
14686
+ logInfo("agent.message.received", {
14687
+ "app.message.kind": "user_inbound",
14688
+ "app.message.length": userInput.length,
14689
+ "app.message.input": summarizeMessageText(userInput),
14690
+ // Log both counts so image uploads filtered by vision/config do not
14691
+ // look indistinguishable from Slack ingress dropping attachments.
14692
+ "app.message.attachment_count": inboundAttachmentCount,
14693
+ "app.message.prompt_attachment_count": promptAttachmentCount,
14694
+ "messaging.message.id": slackSource?.messageTs ?? ""
14695
+ });
14882
14696
  }
14883
14697
  const skillInvocation = parseSkillInvocation(userInput, availableSkills);
14884
14698
  const invokedSkill = skillInvocation ? findSkillByName(skillInvocation.skillName, availableSkills) : null;
@@ -14910,7 +14724,6 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
14910
14724
  durability,
14911
14725
  getLoadedSkillNames: () => loadedSkillNamesForResume,
14912
14726
  getReasoningLevel: () => turnRoute?.reasoningLevel,
14913
- logContext: sessionRecordLogContext,
14914
14727
  getModelId: () => activeModelId,
14915
14728
  recordActiveMcpProviders,
14916
14729
  actor,
@@ -14930,15 +14743,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
14930
14743
  toolName: event.toolName
14931
14744
  });
14932
14745
  } catch (error) {
14933
- logException(
14934
- error,
14935
- "agent_turn_session_log_append_failed",
14936
- spanContext,
14937
- {
14938
- "gen_ai.tool.name": event.toolName
14939
- },
14940
- "Failed to record host-only tool execution start"
14941
- );
14746
+ logException(error, "agent.turn.session_log_append.failed", {
14747
+ "gen_ai.tool.name": event.toolName
14748
+ });
14942
14749
  }
14943
14750
  };
14944
14751
  const persistedConfigurationValues = policy.channelConfiguration ? await policy.channelConfiguration.resolveValues() : {};
@@ -15073,14 +14880,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15073
14880
  void (async () => {
15074
14881
  await observers.onStatus?.({ text: "Switching models" });
15075
14882
  })().catch((error) => {
15076
- logWarn(
15077
- "assistant_status_observer_failed",
15078
- {},
15079
- {
15080
- "exception.message": error instanceof Error ? error.message : String(error)
15081
- },
15082
- "Failed to report assistant status"
15083
- );
14883
+ logWarn("assistant.status.observer.failed", {
14884
+ "exception.message": error instanceof Error ? error.message : String(error)
14885
+ });
15084
14886
  });
15085
14887
  const handoffMessages = await compactContextForHandoff(
15086
14888
  {
@@ -15306,12 +15108,12 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15306
15108
  };
15307
15109
  let assistantMessageDeliveryError;
15308
15110
  const deliverAssistantMessage = async (message) => {
15309
- const text = getAssistantMessageText(message);
15310
- if (!text || !delivery) {
15111
+ const decision = decideReply(message);
15112
+ if (decision.kind !== "deliver" || !delivery) {
15311
15113
  return;
15312
15114
  }
15313
15115
  try {
15314
- await delivery.onAssistantMessage({ text });
15116
+ await delivery(message);
15315
15117
  } catch (error) {
15316
15118
  assistantMessageDeliveryError = new AssistantMessageDeliveryError(
15317
15119
  error
@@ -15350,27 +15152,17 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15350
15152
  steeredMessageCount += piMessages.length;
15351
15153
  });
15352
15154
  if (steeredMessageCount > 0) {
15353
- logInfo(
15354
- "agent_turn_steering_messages_accepted",
15355
- spanContext,
15356
- {
15357
- "app.ai.steering_message_count": steeredMessageCount
15358
- },
15359
- "Agent turn steering messages accepted"
15360
- );
15155
+ logInfo("agent.turn.steering_messages.accepted", {
15156
+ "app.ai.steering_message_count": steeredMessageCount
15157
+ });
15361
15158
  }
15362
15159
  } catch (error) {
15363
15160
  if (isTurnInputCommitLostError(error)) {
15364
15161
  throw error;
15365
15162
  }
15366
- logWarn(
15367
- "agent_turn_steering_messages_drain_failed",
15368
- spanContext,
15369
- {
15370
- "exception.message": error instanceof Error ? error.message : String(error)
15371
- },
15372
- "Agent turn steering message drain failed"
15373
- );
15163
+ logWarn("agent.turn.steering_messages_drain.failed", {
15164
+ "exception.message": error instanceof Error ? error.message : String(error)
15165
+ });
15374
15166
  }
15375
15167
  return acceptedMessages;
15376
15168
  };
@@ -15378,7 +15170,10 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15378
15170
  let pendingPiHookError;
15379
15171
  agent = new Agent({
15380
15172
  ...apiKeyOverride ? { getApiKey: () => apiKeyOverride } : {},
15381
- streamFn: createTracedStreamFn({ conversationPrivacy }),
15173
+ streamFn: createTracedStreamFn({
15174
+ conversationPrivacy,
15175
+ ...streamFn ? { base: streamFn } : {}
15176
+ }),
15382
15177
  steeringMode: "all",
15383
15178
  beforeToolCall: async ({ assistantMessage }) => {
15384
15179
  const toolCalls2 = assistantMessage.content.filter(
@@ -15522,37 +15317,27 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15522
15317
  );
15523
15318
  } catch (error) {
15524
15319
  if (runResume.timedOut) {
15525
- logWarn(
15526
- "agent_turn_timeout",
15527
- {},
15528
- {
15529
- "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
15530
- "gen_ai.operation.name": "invoke_agent",
15531
- "gen_ai.request.model": activeModelId,
15532
- ...turnRoute ? {
15533
- "gen_ai.request.reasoning.level": turnRoute.reasoningLevel
15534
- } : {},
15535
- "app.ai.turn_timeout_ms": turnTimeoutBudgetMs,
15536
- "app.ai.turn_deadline_remaining_ms": Math.max(
15537
- 0,
15538
- turnDeadlineAtMs - Date.now()
15539
- )
15540
- },
15541
- "Agent turn timed out and was aborted"
15542
- );
15320
+ logWarn("agent.turn.timed_out", {
15321
+ "gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
15322
+ "gen_ai.operation.name": "invoke_agent",
15323
+ "gen_ai.request.model": activeModelId,
15324
+ ...turnRoute ? {
15325
+ "gen_ai.request.reasoning.level": turnRoute.reasoningLevel
15326
+ } : {},
15327
+ "app.ai.turn_timeout_ms": turnTimeoutBudgetMs,
15328
+ "app.ai.turn_deadline_remaining_ms": Math.max(
15329
+ 0,
15330
+ turnDeadlineAtMs - Date.now()
15331
+ )
15332
+ });
15543
15333
  const settled = await waitForAbortSettlement(
15544
15334
  run2,
15545
15335
  AGENT_ABORT_SETTLE_GRACE_MS
15546
15336
  );
15547
15337
  if (!settled) {
15548
- logWarn(
15549
- "agent_turn_abort_settle_timeout",
15550
- {},
15551
- {
15552
- "app.ai.abort_settle_grace_ms": AGENT_ABORT_SETTLE_GRACE_MS
15553
- },
15554
- "Timed-out agent run did not settle after abort before resume snapshot"
15555
- );
15338
+ logWarn("agent.turn.abort_settle.timed_out", {
15339
+ "app.ai.abort_settle_grace_ms": AGENT_ABORT_SETTLE_GRACE_MS
15340
+ });
15556
15341
  }
15557
15342
  runResume.captureResumeSnapshot(
15558
15343
  runResume.getResumeSnapshot(currentAgentMessages())
@@ -15616,9 +15401,20 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15616
15401
  ]);
15617
15402
  }
15618
15403
  run = shouldPromptAgent && !capacityUpdate ? agent.prompt(freshPromptMessage) : agent.continue();
15619
- let retryUsage;
15404
+ let discardedRetryUsage;
15405
+ let providerRetryAttempt = 0;
15406
+ let emptyOutputAttempt = 0;
15407
+ const prepareRetry = async (messages) => {
15408
+ discardedRetryUsage = addAgentTurnUsage(
15409
+ discardedRetryUsage,
15410
+ getDiscardedRetryUsage(agent.state.messages, messages)
15411
+ );
15412
+ agent.state.messages = messages;
15413
+ await runResume.persistSafeBoundary(messages);
15414
+ signal?.throwIfAborted();
15415
+ };
15620
15416
  try {
15621
- for (let attempt = 0; ; attempt += 1) {
15417
+ for (; ; ) {
15622
15418
  await runAgentStep(run);
15623
15419
  if (assistantMessageDeliveryError) {
15624
15420
  throw assistantMessageDeliveryError;
@@ -15635,7 +15431,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15635
15431
  const usageSummary = extractGenAiUsageSummary(...outputMessages);
15636
15432
  const currentUsage = hasAgentTurnUsage(usageSummary) ? usageSummary : void 0;
15637
15433
  const currentPhaseUsage = addAgentTurnUsage(
15638
- retryUsage,
15434
+ discardedRetryUsage,
15639
15435
  currentUsage
15640
15436
  );
15641
15437
  turnUsage = addAgentTurnUsage(priorPhaseUsage, currentPhaseUsage);
@@ -15650,7 +15446,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15650
15446
  normalizeGenAiFinishReason(lastAssistant.stopReason)
15651
15447
  ]
15652
15448
  } : {},
15653
- ...extractGenAiUsageAttributes(usageSummary)
15449
+ ...extractGenAiUsageAttributes(currentPhaseUsage)
15654
15450
  });
15655
15451
  const pendingAuthPause = getPendingAuthPause();
15656
15452
  if (pendingAuthPause) {
@@ -15659,25 +15455,37 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15659
15455
  );
15660
15456
  throw pendingAuthPause;
15661
15457
  }
15458
+ const emptyOutputContinuation = nextEmptyOutputContinuation({
15459
+ attempt: emptyOutputAttempt,
15460
+ lastAssistant,
15461
+ messages: agent.state.messages
15462
+ });
15463
+ if (emptyOutputContinuation.kind === "retry") {
15464
+ emptyOutputAttempt += 1;
15465
+ await prepareRetry(emptyOutputContinuation.messages);
15466
+ logWarn("agent.turn.empty_output.retrying", {
15467
+ "app.ai.empty_output.attempt": emptyOutputAttempt
15468
+ });
15469
+ run = agent.continue();
15470
+ continue;
15471
+ }
15472
+ if (emptyOutputContinuation.kind === "exhausted") {
15473
+ logWarn("agent.turn.empty_output.exhausted", {
15474
+ "app.ai.empty_output.attempt": emptyOutputAttempt
15475
+ });
15476
+ }
15662
15477
  const providerRetry = nextProviderRetry({
15663
- attempt,
15478
+ attempt: providerRetryAttempt,
15664
15479
  failure: lastAssistant,
15665
15480
  messages: agent.state.messages
15666
15481
  });
15667
15482
  if (!providerRetry) {
15668
15483
  break;
15669
15484
  }
15670
- retryUsage = currentPhaseUsage;
15671
- agent.state.messages = providerRetry.messages;
15672
- await runResume.persistSafeBoundary(providerRetry.messages);
15673
- logWarn(
15674
- "agent_turn_provider_retry",
15675
- spanContext,
15676
- {},
15677
- "Retrying transient provider failure"
15678
- );
15485
+ providerRetryAttempt += 1;
15486
+ await prepareRetry(providerRetry.messages);
15487
+ logWarn("agent.turn.provider.retrying");
15679
15488
  await sleep(providerRetry.delayMs, signal);
15680
- signal?.throwIfAborted();
15681
15489
  run = agent.continue();
15682
15490
  }
15683
15491
  } catch (error) {
@@ -15722,7 +15530,6 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15722
15530
  durationMs: Date.now() - replyStartedAtMs,
15723
15531
  generatedFileCount: generatedFiles.length,
15724
15532
  shouldTrace,
15725
- spanContext,
15726
15533
  usage: turnUsage,
15727
15534
  executionProfile: turnRoute,
15728
15535
  assistantUserName: botConfig.userName,
@@ -15770,13 +15577,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
15770
15577
  if (durability.onInputCommitted && !resume?.inputCommitted) {
15771
15578
  throw error;
15772
15579
  }
15773
- logException(
15774
- error,
15775
- "assistant_reply_generation_failed",
15776
- { modelId: activeModelId },
15777
- {},
15778
- "executeAgentRun failed"
15779
- );
15580
+ logException(error, "assistant.reply.generation.failed");
15780
15581
  const message = error instanceof Error ? error.message : String(error);
15781
15582
  return {
15782
15583
  status: "completed",