@sentry/junior 0.117.0 → 0.119.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 (73) hide show
  1. package/dist/{agent-hooks-5IK4LPLP.js → agent-hooks-NZSV4QQR.js} +9 -11
  2. package/dist/api/conversations/detail.d.ts +17 -0
  3. package/dist/api/conversations/event-list.d.ts +17 -0
  4. package/dist/api/schema/conversation.d.ts +68 -0
  5. package/dist/api/schema/stats.d.ts +22 -0
  6. package/dist/api/schema.d.ts +2 -0
  7. package/dist/api/schema.js +7 -3
  8. package/dist/api/stats.d.ts +16 -0
  9. package/dist/api/user-pages/routes.d.ts +4 -0
  10. package/dist/api.js +223 -20
  11. package/dist/app.js +43 -38
  12. package/dist/{catalog-runtime-MTUYW3AA.js → catalog-runtime-TAOYIZIZ.js} +5 -5
  13. package/dist/chat/agent/prompt.d.ts +4 -2
  14. package/dist/chat/plugins/user-pages.d.ts +15 -0
  15. package/dist/chat/plugins/viewer-actors.d.ts +6 -0
  16. package/dist/chat/prompt.d.ts +1 -2
  17. package/dist/chat/sandbox/paths.d.ts +2 -0
  18. package/dist/chat/sandbox/sandbox.d.ts +3 -1
  19. package/dist/chat/sandbox/workspace.d.ts +1 -0
  20. package/dist/chat/skills.d.ts +1 -2
  21. package/dist/chat/slack/tools/canvas/api.d.ts +2 -9
  22. package/dist/chat/slack/tools/canvas/markdown.d.ts +9 -0
  23. package/dist/chat/tools/index.d.ts +4 -1
  24. package/dist/chat/tools/sandbox/file-utils.d.ts +11 -0
  25. package/dist/chat/tools/sandbox/find-files.d.ts +2 -1
  26. package/dist/chat/tools/sandbox/grep.d.ts +2 -1
  27. package/dist/{chunk-6G45D3YG.js → chunk-4UIUDOOV.js} +16 -233
  28. package/dist/{chunk-LUMEBOWC.js → chunk-6ZR455DC.js} +2 -2
  29. package/dist/{chunk-STUI6NIL.js → chunk-BLQZACHV.js} +511 -14
  30. package/dist/{chunk-27KWTFP6.js → chunk-BNJEPYTC.js} +5 -14
  31. package/dist/{chunk-ZRQUQJUV.js → chunk-D3N4BPSL.js} +3 -3
  32. package/dist/{chunk-WKU7TY22.js → chunk-FIANEE7Z.js} +3 -1
  33. package/dist/{chunk-RXRQJF7Y.js → chunk-GCGJGSQL.js} +21 -0
  34. package/dist/{chunk-FAWBONPC.js → chunk-GKFDU3ZI.js} +5 -7
  35. package/dist/{chunk-FXOYURO4.js → chunk-HDFGNU5E.js} +1 -0
  36. package/dist/{chunk-3GE3F4LT.js → chunk-JLJLZS2N.js} +1 -1
  37. package/dist/{chunk-CU7HTGNI.js → chunk-JSTGI7TX.js} +1 -1
  38. package/dist/chunk-JXRAOOXQ.js +37 -0
  39. package/dist/{chunk-WUNPRM76.js → chunk-NUENIPQL.js} +1 -1
  40. package/dist/{chunk-BFIRIZPH.js → chunk-O2ULQJWR.js} +101 -2
  41. package/dist/{chunk-AKG4TZNH.js → chunk-QZ7XRJDB.js} +30 -9
  42. package/dist/{chunk-5O3JDQDG.js → chunk-R53UT2OI.js} +1 -1
  43. package/dist/{chunk-W5JEQQEA.js → chunk-TAGV4Z3J.js} +1 -1
  44. package/dist/{chunk-NHZEUHKH.js → chunk-TGIQN7QW.js} +388 -113
  45. package/dist/{chunk-KU6ALEJC.js → chunk-VJTWKJWQ.js} +30 -2
  46. package/dist/{chunk-DS27ZBSP.js → chunk-VLTWQQKH.js} +2 -2
  47. package/dist/{chunk-WEK3V5RI.js → chunk-YLYJIQRX.js} +226 -21
  48. package/dist/{chunk-LN765Y4N.js → chunk-ZFX4M743.js} +15 -9
  49. package/dist/{chunk-72Z3HNTQ.js → chunk-ZJ6IVZVO.js} +1 -1
  50. package/dist/cli/chat.js +24 -25
  51. package/dist/cli/check.js +6 -6
  52. package/dist/cli/init.js +2 -2
  53. package/dist/cli/plugins.js +13 -15
  54. package/dist/cli/snapshot-create.js +9 -10
  55. package/dist/cli/upgrade.js +8 -8
  56. package/dist/db/schema/stats.d.ts +93 -0
  57. package/dist/db/schema.d.ts +94 -1
  58. package/dist/{db-J2ITKS63.js → db-FWGP5CKY.js} +7 -7
  59. package/dist/instrumentation.js +3 -3
  60. package/dist/nitro.js +3 -3
  61. package/dist/{oauth-callback-server-NQYKBT24.js → oauth-callback-server-PM3QNXJQ.js} +16 -18
  62. package/dist/{plugins-IUJA4VBU.js → plugins-HW4VWW6G.js} +1 -1
  63. package/dist/{runner-53V5GIQM.js → runner-DV55N6ZB.js} +11 -13
  64. package/dist/{skills-BU3ZMLVB.js → skills-MANV5ZK6.js} +6 -6
  65. package/dist/stats.d.ts +18 -0
  66. package/dist/{validation-74EIKKE6.js → validation-QGB4INTI.js} +6 -6
  67. package/dist/version.js +1 -1
  68. package/migrations/0011_sour_golden_guardian.sql +8 -0
  69. package/migrations/meta/0011_snapshot.json +1195 -0
  70. package/migrations/meta/_journal.json +8 -1
  71. package/package.json +5 -5
  72. package/dist/chunk-4ZNGQH7C.js +0 -25
  73. package/dist/chunk-YRWJ3KEU.js +0 -517
@@ -25,13 +25,12 @@ import {
25
25
  patchMcpAuthSession,
26
26
  resolveBaseUrl,
27
27
  selectPluginGrant,
28
- slackOutputPolicy,
29
28
  startOAuthFlow,
30
29
  surfaceFromRouting,
31
30
  toMcpProviderError,
32
31
  toolInvocationDestination,
33
32
  uploadFilesToConversation
34
- } from "./chunk-6G45D3YG.js";
33
+ } from "./chunk-4UIUDOOV.js";
35
34
  import {
36
35
  CooperativeTurnYieldError,
37
36
  JUNIOR_THREAD_STATE_TTL_MS,
@@ -73,7 +72,7 @@ import {
73
72
  stripRuntimeTurnContext,
74
73
  trimTrailingAssistantMessages,
75
74
  updateConversationStats
76
- } from "./chunk-27KWTFP6.js";
75
+ } from "./chunk-BNJEPYTC.js";
77
76
  import {
78
77
  parseSandboxEgressAuthRequiredSignal,
79
78
  parseSandboxEgressPermissionDeniedSignal,
@@ -97,7 +96,10 @@ import {
97
96
  runNonInteractiveCommand,
98
97
  throwSandboxOperationError,
99
98
  wrapSandboxSetupError
100
- } from "./chunk-FAWBONPC.js";
99
+ } from "./chunk-GKFDU3ZI.js";
100
+ import {
101
+ incrementStat
102
+ } from "./chunk-JXRAOOXQ.js";
101
103
  import {
102
104
  addAgentTurnUsage,
103
105
  hasAgentTurnUsage
@@ -106,30 +108,20 @@ import {
106
108
  privateTraceResultAttributes
107
109
  } from "./chunk-UIE3R5XU.js";
108
110
  import {
111
+ SlackActionError,
109
112
  createPluginHookRunner,
113
+ downloadPrivateSlackFile,
114
+ getFilePermalink,
110
115
  getPluginSystemPromptContributions,
111
116
  getPluginTools,
112
117
  getPluginUserPromptContributions,
113
- getSlackToolContext,
114
- resolveChannelCapabilities
115
- } from "./chunk-STUI6NIL.js";
116
- import {
117
- SANDBOX_DATA_ROOT,
118
- SANDBOX_SKILLS_ROOT,
119
- SANDBOX_WORKSPACE_ROOT,
120
- getStateAdapter,
121
- sandboxSkillDir,
122
- sandboxSkillFile
123
- } from "./chunk-KU6ALEJC.js";
124
- import {
125
- SlackActionError,
126
- downloadPrivateSlackFile,
127
- getFilePermalink,
128
118
  getSlackClient,
119
+ getSlackToolContext,
129
120
  isConversationScopedChannel,
130
121
  normalizeSlackConversationId,
122
+ resolveChannelCapabilities,
131
123
  withSlackRetries
132
- } from "./chunk-YRWJ3KEU.js";
124
+ } from "./chunk-BLQZACHV.js";
133
125
  import {
134
126
  KNOWN_CONVERSATION_EVENT_TYPES,
135
127
  contextProvenance,
@@ -138,10 +130,17 @@ import {
138
130
  getConversationStore,
139
131
  instructionActors,
140
132
  instructionProvenanceFor
141
- } from "./chunk-ZRQUQJUV.js";
133
+ } from "./chunk-D3N4BPSL.js";
142
134
  import {
135
+ SANDBOX_DATA_ROOT,
136
+ SANDBOX_SKILLS_ROOT,
137
+ SANDBOX_WORKSPACE_ROOT,
138
+ getStateAdapter,
139
+ sandboxSkillDir,
140
+ sandboxSkillFile,
141
+ sandboxSkillPathResolution,
143
142
  sleep
144
- } from "./chunk-4ZNGQH7C.js";
143
+ } from "./chunk-VJTWKJWQ.js";
145
144
  import {
146
145
  ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
147
146
  COMPACTION_SUMMARY_PREFIX,
@@ -172,7 +171,7 @@ import {
172
171
  renderCurrentInstruction,
173
172
  resolveGatewayModel,
174
173
  unwrapCurrentInstruction
175
- } from "./chunk-DS27ZBSP.js";
174
+ } from "./chunk-VLTWQQKH.js";
176
175
  import {
177
176
  canExposeConversationPayload,
178
177
  parseSlackThreadId,
@@ -183,16 +182,16 @@ import {
183
182
  toGenAiMessagesTraceAttributes,
184
183
  toGenAiPayloadMetadata,
185
184
  toGenAiPayloadTraceAttributes
186
- } from "./chunk-FXOYURO4.js";
185
+ } from "./chunk-HDFGNU5E.js";
187
186
  import {
188
187
  discoverSkills,
189
188
  findSkillByName,
190
189
  loadSkillsByName,
191
190
  parseSkillInvocation
192
- } from "./chunk-AKG4TZNH.js";
191
+ } from "./chunk-QZ7XRJDB.js";
193
192
  import {
194
193
  pluginCatalogRuntime
195
- } from "./chunk-CU7HTGNI.js";
194
+ } from "./chunk-JSTGI7TX.js";
196
195
  import {
197
196
  CredentialUnavailableError,
198
197
  OAuthProviderError,
@@ -200,7 +199,7 @@ import {
200
199
  getOAuthProviderErrorAttributes,
201
200
  resolveAuthTokenPlaceholder,
202
201
  resolvePluginCommandEnv
203
- } from "./chunk-5O3JDQDG.js";
202
+ } from "./chunk-R53UT2OI.js";
204
203
  import {
205
204
  credentialUserSubjectId
206
205
  } from "./chunk-W3U3YQYI.js";
@@ -224,7 +223,7 @@ import {
224
223
  summarizeMessageText,
225
224
  withLogContext,
226
225
  withSpan
227
- } from "./chunk-72Z3HNTQ.js";
226
+ } from "./chunk-ZJ6IVZVO.js";
228
227
  import {
229
228
  parseSlackChannelId,
230
229
  parseSlackMessageTs,
@@ -1668,13 +1667,10 @@ function formatSkillEntry(skill) {
1668
1667
  lines.push(" </skill>");
1669
1668
  return lines;
1670
1669
  }
1671
- function formatAvailableSkillsForPrompt(skills, invocation) {
1670
+ function formatAvailableSkillsForPrompt(skills) {
1672
1671
  const autoSelectable = skills.filter(
1673
1672
  (s) => s.disableModelInvocation !== true
1674
1673
  );
1675
- const invokedExplicitOnly = invocation ? skills.filter(
1676
- (s) => s.disableModelInvocation === true && s.name === invocation.skillName
1677
- ) : [];
1678
1674
  const sections = [];
1679
1675
  if (autoSelectable.length > 0) {
1680
1676
  const available = [
@@ -1687,17 +1683,6 @@ function formatAvailableSkillsForPrompt(skills, invocation) {
1687
1683
  available.push("</available-skills>");
1688
1684
  sections.push(available.join("\n"));
1689
1685
  }
1690
- if (invokedExplicitOnly.length > 0) {
1691
- const userCallable = [
1692
- "<user-callable-skills>",
1693
- "The user's current message explicitly references this skill by name. Load it when relevant to the request."
1694
- ];
1695
- for (const skill of invokedExplicitOnly) {
1696
- userCallable.push(...formatSkillEntry(skill));
1697
- }
1698
- userCallable.push("</user-callable-skills>");
1699
- sections.push(userCallable.join("\n"));
1700
- }
1701
1686
  return sections.length > 0 ? sections.join("\n") : null;
1702
1687
  }
1703
1688
  function formatActiveMcpCatalogsForPrompt(catalogs) {
@@ -1810,7 +1795,8 @@ var TOOL_CALL_STYLE_RULES = [
1810
1795
  "- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
1811
1796
  ];
1812
1797
  var SKILL_POLICY_RULES = [
1813
- "- Only load skills listed in `<available-skills>`, `<user-callable-skills>`, or named by `<explicit-skill-trigger>`. Never guess or invent a skill name.",
1798
+ "- A `<skill>` block in the current user turn is already loaded. Follow its instructions directly and do not call `loadSkill` for that skill.",
1799
+ "- Otherwise, only load skills listed in `<available-skills>`. Never guess or invent a skill name.",
1814
1800
  "- Load one skill at a time. After `loadSkill`, follow the instructions returned by that tool result."
1815
1801
  ];
1816
1802
  var EXECUTION_CONTRACT_RULES = [
@@ -1874,13 +1860,11 @@ function buildOutputSection(platform) {
1874
1860
  "</output>"
1875
1861
  ].join("\n");
1876
1862
  }
1877
- const openTag = `<output format="slack-markdown" max_inline_chars="${slackOutputPolicy.maxInlineChars}" max_inline_lines="${slackOutputPolicy.maxInlineLines}">`;
1878
1863
  return [
1879
- openTag,
1864
+ `<output format="slack-markdown">`,
1865
+ "- Default to the shortest complete reply\u2014usually 1\u20135 sentences and under 800 characters. Include only the outcome, decisive evidence, and any blocker or required next action. If useful detail would exceed that, put it in a Slack canvas and reply with the link. An explicit user request for detail overrides this target.",
1880
1866
  "- Start with the answer or result, not internal process narration.",
1881
1867
  "- Use Slack-flavored Markdown: **bold** section labels, `code`, [text](url) links, bullet lists, and fenced code blocks. No hash-prefixed headings and no tables. When the answer primarily lists several URLs, show each URL bare instead of as a labeled link.",
1882
- "- Default to one compact thread reply containing the conclusion and only decisive evidence or action. Do not split an explainer across multiple Slack messages.",
1883
- "- Put plans, research, and other long or multi-section explainers in a Slack canvas; reply with one or two short sentences plus the link, without recapping the canvas.",
1884
1868
  "- End every turn with a final user-facing markdown response unless the Slack action rules allow a no-reply completion.",
1885
1869
  "</output>"
1886
1870
  ].join("\n");
@@ -1995,14 +1979,6 @@ function buildContextSection(params) {
1995
1979
  ])
1996
1980
  );
1997
1981
  }
1998
- if (params.invocation) {
1999
- blocks.push(
2000
- renderTag("explicit-skill-trigger", [
2001
- "Treat this skill as selected. Load it unless the tool says it is unavailable.",
2002
- `/${escapeXml(params.invocation.skillName)}`
2003
- ])
2004
- );
2005
- }
2006
1982
  const body = blocks.map((block) => block.join("\n")).join("\n\n");
2007
1983
  if (!body) {
2008
1984
  return null;
@@ -2012,8 +1988,7 @@ function buildContextSection(params) {
2012
1988
  function buildCapabilitiesSection(params) {
2013
1989
  const blocks = [];
2014
1990
  const availableSkills = formatAvailableSkillsForPrompt(
2015
- params.availableSkills,
2016
- params.invocation
1991
+ params.availableSkills
2017
1992
  );
2018
1993
  if (availableSkills) {
2019
1994
  blocks.push(availableSkills);
@@ -2094,7 +2069,6 @@ function buildTurnContextPrompt(params) {
2094
2069
  includeSessionContext ? buildCapabilitiesSection({
2095
2070
  availableSkills: params.availableSkills,
2096
2071
  activeMcpCatalogs: params.activeMcpCatalogs ?? [],
2097
- invocation: params.invocation,
2098
2072
  toolGuidance: params.toolGuidance ?? []
2099
2073
  }) : null,
2100
2074
  pluginPromptContributions,
@@ -2102,8 +2076,7 @@ function buildTurnContextPrompt(params) {
2102
2076
  actor: params.actor,
2103
2077
  artifactState: params.artifactState,
2104
2078
  configuration: params.configuration,
2105
- dispatch: params.dispatch,
2106
- invocation: params.invocation
2079
+ dispatch: params.dispatch
2107
2080
  }) : null,
2108
2081
  includeSessionContext ? buildRuntimeSection(params.runtime ?? {}) : null
2109
2082
  ].filter((section) => Boolean(section));
@@ -2146,6 +2119,17 @@ function buildUserTurnText(userInput, conversationContext, actor) {
2146
2119
  currentInstruction
2147
2120
  ].join("\n");
2148
2121
  }
2122
+ function renderExplicitSkillInstructions(skill) {
2123
+ return [
2124
+ "<skill>",
2125
+ `<name>${escapeXml(skill.name)}</name>`,
2126
+ `<path>${escapeXml(sandboxSkillFile(skill.name))}</path>`,
2127
+ `<working_directory>${escapeXml(sandboxSkillDir(skill.name))}</working_directory>`,
2128
+ `<path_resolution>${escapeXml(sandboxSkillPathResolution(skill.name))}</path_resolution>`,
2129
+ skill.body,
2130
+ "</skill>"
2131
+ ].join("\n");
2132
+ }
2149
2133
  function encodeNonImageAttachmentForPrompt(attachment) {
2150
2134
  const base64 = attachment.data.toString("base64");
2151
2135
  const wasTruncated = base64.length > MAX_INLINE_ATTACHMENT_BASE64_CHARS;
@@ -2343,14 +2327,23 @@ async function assemblePrompt(args) {
2343
2327
  const source = args.routing.source;
2344
2328
  const hasPromptCheckpoint = args.resumedFromSessionRecord && args.existingTurnStartMessageIndex !== void 0;
2345
2329
  const shouldPromptAgent = !args.resumedFromSessionRecord || !hasPromptCheckpoint;
2330
+ const requestContentParts = [
2331
+ ...args.explicitSkill ? [
2332
+ {
2333
+ type: "text",
2334
+ text: renderExplicitSkillInstructions(args.explicitSkill)
2335
+ }
2336
+ ] : [],
2337
+ ...args.userContentParts
2338
+ ];
2346
2339
  const promptHistoryMessages = shouldPromptAgent ? withoutTrailingUncheckpointedUserPrompt(
2347
2340
  args.priorPiMessages,
2348
- args.userContentParts
2341
+ requestContentParts
2349
2342
  ) : args.existingSessionPiMessages;
2350
2343
  const replayedPromptContent = shouldPromptAgent && !args.resumedFromSessionRecord ? checkpointedPromptContent({
2351
2344
  messages: args.existingSessionPiMessages,
2352
2345
  turnStartMessageIndex: args.existingTurnStartMessageIndex,
2353
- userContentParts: args.userContentParts
2346
+ userContentParts: requestContentParts
2354
2347
  }) : void 0;
2355
2348
  const needsBootstrapContextForPrompt = shouldPromptAgent && !replayedPromptContent && !hasRuntimeTurnContext(promptHistoryMessages);
2356
2349
  const systemPromptContributions = await getPluginSystemPromptContributions(source);
@@ -2376,7 +2369,6 @@ async function assemblePrompt(args) {
2376
2369
  destination: args.routing.destination,
2377
2370
  source
2378
2371
  } : void 0,
2379
- invocation: args.invocation,
2380
2372
  actor: isUserActor(args.currentActor) ? args.currentActor : void 0,
2381
2373
  artifactState: args.artifactState,
2382
2374
  configuration: args.configurationValues
@@ -2384,7 +2376,7 @@ async function assemblePrompt(args) {
2384
2376
  const turnContextParts = turnContextPrompt ? [{ type: "text", text: turnContextPrompt }] : [];
2385
2377
  const promptContentParts = replayedPromptContent ?? [
2386
2378
  ...turnContextParts,
2387
- ...args.userContentParts
2379
+ ...requestContentParts
2388
2380
  ];
2389
2381
  const inputMessages = [
2390
2382
  {
@@ -3076,6 +3068,7 @@ import { Sandbox } from "@vercel/sandbox";
3076
3068
  // src/chat/sandbox/skill-sync.ts
3077
3069
  import fs3 from "fs/promises";
3078
3070
  import path3 from "path";
3071
+ var DIRECTORY_CREATE_CONCURRENCY = 8;
3079
3072
  function toPosixRelative(base, absolute) {
3080
3073
  return path3.relative(base, absolute).split(path3.sep).join("/");
3081
3074
  }
@@ -3150,6 +3143,16 @@ function collectDirectories(filesToWrite, workspaceRoot) {
3150
3143
  (directory) => directory === workspaceRoot || directory.startsWith(`${workspaceRoot}/`)
3151
3144
  ).sort((a, b) => a.length - b.length);
3152
3145
  }
3146
+ function groupDirectoriesByDepth(directories) {
3147
+ const groups = /* @__PURE__ */ new Map();
3148
+ for (const directory of directories) {
3149
+ const depth = directory.split("/").filter(Boolean).length;
3150
+ const group = groups.get(depth) ?? [];
3151
+ group.push(directory);
3152
+ groups.set(depth, group);
3153
+ }
3154
+ return [...groups.entries()].sort(([left], [right]) => left - right).map(([, group]) => group);
3155
+ }
3153
3156
  function resolveHostSkillPath(availableSkills, sandboxPath) {
3154
3157
  const normalizedPath = path3.posix.normalize(sandboxPath.trim());
3155
3158
  for (const skill of availableSkills) {
@@ -3219,13 +3222,21 @@ async function syncSkillsToSandbox(params) {
3219
3222
  },
3220
3223
  async () => {
3221
3224
  try {
3222
- for (const directory of directories) {
3223
- try {
3224
- await params.sandbox.mkDir(directory);
3225
- } catch (error) {
3226
- if (!isAlreadyExistsError(error)) {
3227
- throw error;
3228
- }
3225
+ for (const directoriesAtDepth of groupDirectoriesByDepth(
3226
+ directories
3227
+ )) {
3228
+ for (let offset = 0; offset < directoriesAtDepth.length; offset += DIRECTORY_CREATE_CONCURRENCY) {
3229
+ await Promise.all(
3230
+ directoriesAtDepth.slice(offset, offset + DIRECTORY_CREATE_CONCURRENCY).map(async (directory) => {
3231
+ try {
3232
+ await params.sandbox.mkDir(directory);
3233
+ } catch (error) {
3234
+ if (!isAlreadyExistsError(error)) {
3235
+ throw error;
3236
+ }
3237
+ }
3238
+ })
3239
+ );
3229
3240
  }
3230
3241
  }
3231
3242
  await params.sandbox.writeFiles(filesToWrite);
@@ -3822,6 +3833,7 @@ var RIPGREP_EXCLUDED_GLOBS = [
3822
3833
  "!**/.git/**",
3823
3834
  "!**/node_modules/**"
3824
3835
  ];
3836
+ var DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 3e4;
3825
3837
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
3826
3838
  function positiveInteger(value) {
3827
3839
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -4409,7 +4421,7 @@ function createEditFileTool() {
4409
4421
  // src/chat/tools/sandbox/find-files.ts
4410
4422
  import path5 from "path";
4411
4423
  import { z as z5 } from "zod";
4412
- var DEFAULT_FIND_LIMIT = 1e3;
4424
+ var DEFAULT_FIND_LIMIT = 100;
4413
4425
  async function findFiles(params) {
4414
4426
  if (!params.pattern.trim()) {
4415
4427
  throw new Error("pattern is required");
@@ -4420,6 +4432,7 @@ async function findFiles(params) {
4420
4432
  return await findFilesWithRipgrep({
4421
4433
  fs: params.fs,
4422
4434
  limit,
4435
+ onTelemetry: params.onTelemetry,
4423
4436
  path: params.path,
4424
4437
  pattern: params.pattern,
4425
4438
  root,
@@ -4488,25 +4501,45 @@ async function findFilesWithRipgrep(params) {
4488
4501
  throw error;
4489
4502
  }
4490
4503
  const location = getRipgrepSearchLocation(params.root, rootIsDirectory);
4491
- const args = [
4504
+ const ripgrepArgs = [
4492
4505
  "--files",
4493
4506
  "--null",
4494
4507
  "--hidden",
4495
- "--sort=path",
4496
4508
  "--glob",
4497
4509
  params.pattern
4498
4510
  ];
4499
4511
  for (const excludedGlob of RIPGREP_EXCLUDED_GLOBS) {
4500
- args.push("--glob", excludedGlob);
4512
+ ripgrepArgs.push("--glob", excludedGlob);
4501
4513
  }
4502
- args.push("--", location.target);
4514
+ ripgrepArgs.push("--", location.target);
4503
4515
  const result = await params.runCommand({
4504
- cmd: "rg",
4505
- args,
4506
- cwd: location.cwd
4516
+ cmd: "bash",
4517
+ args: [
4518
+ "-c",
4519
+ [
4520
+ `rg "$@" | head -z -n ${params.limit + 1}`,
4521
+ 'statuses=("${PIPESTATUS[@]}")',
4522
+ "if (( statuses[1] != 0 )); then",
4523
+ ' exit "${statuses[1]}"',
4524
+ "fi",
4525
+ "if (( statuses[0] == 0 || statuses[0] == 1 || statuses[0] == 141 )); then",
4526
+ " exit 0",
4527
+ "fi",
4528
+ 'exit "${statuses[0]}"'
4529
+ ].join("\n"),
4530
+ "find-files",
4531
+ ...ripgrepArgs
4532
+ ],
4533
+ cwd: location.cwd,
4534
+ timeoutMs: DEFAULT_SEARCH_COMMAND_TIMEOUT_MS
4507
4535
  });
4508
- if (result.exitCode !== 0 && result.exitCode !== 1) {
4536
+ if (result.exitCode !== 0) {
4509
4537
  const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
4538
+ if (/error parsing glob|invalid glob/i.test(detail)) {
4539
+ throw new ToolInputError(`Invalid glob pattern: ${params.pattern}`, {
4540
+ cause: new Error(detail)
4541
+ });
4542
+ }
4510
4543
  throw new Error(`ripgrep file search failed: ${detail}`);
4511
4544
  }
4512
4545
  const allPaths = result.stdout.split("\0").filter(Boolean).map((filePath) => {
@@ -4530,7 +4563,7 @@ async function findFilesWithRipgrep(params) {
4530
4563
  const text = notices.length > 0 ? `${bounded.content}
4531
4564
 
4532
4565
  [${notices.join(" ")}]` : bounded.content;
4533
- return makeStructuredToolResult(
4566
+ const response = makeStructuredToolResult(
4534
4567
  {
4535
4568
  ok: true,
4536
4569
  status: "success",
@@ -4547,6 +4580,19 @@ async function findFilesWithRipgrep(params) {
4547
4580
  },
4548
4581
  { content: [{ type: "text", text }] }
4549
4582
  );
4583
+ params.onTelemetry?.({
4584
+ emittedLineCount: relativePaths.length,
4585
+ limit: params.limit,
4586
+ limitReached,
4587
+ parsedRecordCount: allPaths.length,
4588
+ rawOutputBytes: Buffer.byteLength(result.stdout, "utf8"),
4589
+ resultBytes: response.content.reduce(
4590
+ (total, item) => total + (item.type === "text" ? Buffer.byteLength(item.text, "utf8") : 0),
4591
+ 0
4592
+ ),
4593
+ resultCount: relativePaths.length
4594
+ });
4595
+ return response;
4550
4596
  }
4551
4597
  function createFindFilesTool() {
4552
4598
  return zodTool({
@@ -4559,7 +4605,7 @@ function createFindFilesTool() {
4559
4605
  path: z5.string().min(1).describe(
4560
4606
  "Directory or file path in the sandbox workspace. Defaults to the workspace root."
4561
4607
  ).optional(),
4562
- limit: z5.coerce.number().int().min(1).describe("Maximum number of file paths to return. Defaults to 1000.").optional()
4608
+ limit: z5.coerce.number().int().min(1).describe("Maximum number of file paths to return. Defaults to 100.").optional()
4563
4609
  }),
4564
4610
  outputSchema: juniorToolResultSchema,
4565
4611
  execute: async () => {
@@ -4612,6 +4658,7 @@ async function grepFiles(params) {
4612
4658
  ignoreCase: params.ignoreCase,
4613
4659
  limit,
4614
4660
  literal: params.literal,
4661
+ onTelemetry: params.onTelemetry,
4615
4662
  path: params.path,
4616
4663
  pattern: params.pattern,
4617
4664
  root,
@@ -4759,7 +4806,13 @@ async function grepFilesWithRipgrep(params) {
4759
4806
  throw error;
4760
4807
  }
4761
4808
  const location = getRipgrepSearchLocation(params.root, rootIsDirectory);
4762
- const args = ["--json", "--line-number", "--hidden", "--sort=path"];
4809
+ const args = [
4810
+ "--json",
4811
+ "--line-number",
4812
+ "--hidden",
4813
+ "--max-count",
4814
+ String(params.limit + 1)
4815
+ ];
4763
4816
  if (params.ignoreCase) {
4764
4817
  args.push("--ignore-case");
4765
4818
  }
@@ -4779,7 +4832,8 @@ async function grepFilesWithRipgrep(params) {
4779
4832
  const result = await params.runCommand({
4780
4833
  cmd: "rg",
4781
4834
  args,
4782
- cwd: location.cwd
4835
+ cwd: location.cwd,
4836
+ timeoutMs: DEFAULT_SEARCH_COMMAND_TIMEOUT_MS
4783
4837
  });
4784
4838
  if (result.exitCode !== 0 && result.exitCode !== 1) {
4785
4839
  const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
@@ -4793,6 +4847,7 @@ async function grepFilesWithRipgrep(params) {
4793
4847
  const recordsByPath = /* @__PURE__ */ new Map();
4794
4848
  const pathOrder = [];
4795
4849
  let totalMatches = 0;
4850
+ let parsedRecordCount = 0;
4796
4851
  for (const rawLine of normalizeToLf(result.stdout).split("\n")) {
4797
4852
  if (!rawLine) {
4798
4853
  continue;
@@ -4803,6 +4858,7 @@ async function grepFilesWithRipgrep(params) {
4803
4858
  } catch (error) {
4804
4859
  throw new Error("ripgrep returned invalid JSON output", { cause: error });
4805
4860
  }
4861
+ parsedRecordCount += 1;
4806
4862
  if (record.type !== "match" && record.type !== "context") {
4807
4863
  continue;
4808
4864
  }
@@ -4884,7 +4940,7 @@ async function grepFilesWithRipgrep(params) {
4884
4940
  if (bounded.truncated) {
4885
4941
  notices.push(`${MAX_TEXT_CHARS} character output limit reached.`);
4886
4942
  }
4887
- return makeStructuredToolResult({
4943
+ const response = makeStructuredToolResult({
4888
4944
  ok: true,
4889
4945
  status: "success",
4890
4946
  target: params.path ?? ".",
@@ -4903,6 +4959,19 @@ async function grepFilesWithRipgrep(params) {
4903
4959
  ...matchLimitReached ? { match_limit_reached: params.limit } : {},
4904
4960
  ...lineTruncated ? { line_truncated: true } : {}
4905
4961
  });
4962
+ params.onTelemetry?.({
4963
+ emittedLineCount: output.length,
4964
+ limit: params.limit,
4965
+ limitReached: matchLimitReached,
4966
+ parsedRecordCount,
4967
+ rawOutputBytes: Buffer.byteLength(result.stdout, "utf8"),
4968
+ resultBytes: response.content.reduce(
4969
+ (total, item) => total + (item.type === "text" ? Buffer.byteLength(item.text, "utf8") : 0),
4970
+ 0
4971
+ ),
4972
+ resultCount: matchCount
4973
+ });
4974
+ return response;
4906
4975
  }
4907
4976
  function createGrepTool() {
4908
4977
  return zodTool({
@@ -5242,11 +5311,12 @@ function createSandbox(options) {
5242
5311
  onSandboxPrepare: options.prepare,
5243
5312
  onSandboxRefChanged: options.onSandboxRefChanged
5244
5313
  });
5245
- const createToolCallContext = (signal) => {
5314
+ const createToolCallContext = (signal, setToolCallSpanAttributes) => {
5246
5315
  let fileSystemPromise;
5247
5316
  let commandRunnerPromise;
5248
5317
  return {
5249
5318
  signal,
5319
+ setToolCallSpanAttributes,
5250
5320
  fileSystem() {
5251
5321
  signal?.throwIfAborted();
5252
5322
  return fileSystemPromise ??= runtime.tools().then(({ fs: fs5 }) => {
@@ -5267,10 +5337,31 @@ function createSandbox(options) {
5267
5337
  };
5268
5338
  };
5269
5339
  const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
5270
- const withSandboxToolSpan = (name, op, attributes, callback) => withSandboxSpan(name, op, attributes, async () => {
5340
+ const withSandboxToolSpan = (name, op, attributes, callback) => withSandboxSpan(name, op, attributes, async (setSpanAttributes2) => {
5271
5341
  await runtime.refreshNetworkPolicy(getTracePropagationHeaders());
5272
- return await callback();
5342
+ return await callback(setSpanAttributes2);
5273
5343
  });
5344
+ const searchTelemetryAttributes = (telemetry) => ({
5345
+ "app.sandbox.search.raw_output_bytes": telemetry.rawOutputBytes,
5346
+ "app.sandbox.search.parsed_records": telemetry.parsedRecordCount,
5347
+ "app.sandbox.search.result_count": telemetry.resultCount,
5348
+ "app.sandbox.search.emitted_lines": telemetry.emittedLineCount,
5349
+ "app.sandbox.search.result_bytes": telemetry.resultBytes,
5350
+ "app.sandbox.search.limit": telemetry.limit,
5351
+ "app.sandbox.search.limit_reached": telemetry.limitReached
5352
+ });
5353
+ const recordSearchTelemetry = (telemetry, setSearchSpanAttributes, setToolCallSpanAttributes) => {
5354
+ const attributes = searchTelemetryAttributes(telemetry);
5355
+ for (const setAttributes of [
5356
+ setSearchSpanAttributes,
5357
+ setToolCallSpanAttributes
5358
+ ]) {
5359
+ try {
5360
+ setAttributes?.(attributes);
5361
+ } catch {
5362
+ }
5363
+ }
5364
+ };
5274
5365
  const logSandboxBootRequest = (trigger, details = {}) => {
5275
5366
  if (runtime.sandboxRef()) {
5276
5367
  return;
@@ -5515,10 +5606,15 @@ function createSandbox(options) {
5515
5606
  {
5516
5607
  "app.sandbox.pattern.length": pattern.length
5517
5608
  },
5518
- async () => {
5609
+ async (setSearchSpanAttributes) => {
5519
5610
  const response = await grepFiles({
5520
5611
  fs: fileSystem,
5521
5612
  runCommand: await context.commandRunner(),
5613
+ onTelemetry: (telemetry) => recordSearchTelemetry(
5614
+ telemetry,
5615
+ setSearchSpanAttributes,
5616
+ context.setToolCallSpanAttributes
5617
+ ),
5522
5618
  pattern,
5523
5619
  ...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
5524
5620
  ...typeof rawInput.glob === "string" ? { glob: rawInput.glob } : {},
@@ -5547,10 +5643,15 @@ function createSandbox(options) {
5547
5643
  {
5548
5644
  "app.sandbox.pattern.length": pattern.length
5549
5645
  },
5550
- async () => {
5646
+ async (setSearchSpanAttributes) => {
5551
5647
  const response = await findFiles({
5552
5648
  fs: fileSystem,
5553
5649
  runCommand: await context.commandRunner(),
5650
+ onTelemetry: (telemetry) => recordSearchTelemetry(
5651
+ telemetry,
5652
+ setSearchSpanAttributes,
5653
+ context.setToolCallSpanAttributes
5654
+ ),
5554
5655
  pattern,
5555
5656
  ...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
5556
5657
  ...limit ? { limit } : {}
@@ -5583,7 +5684,10 @@ function createSandbox(options) {
5583
5684
  };
5584
5685
  const execute = async (params) => {
5585
5686
  const rawInput = params.input ?? {};
5586
- const context = createToolCallContext(params.signal);
5687
+ const context = createToolCallContext(
5688
+ params.signal,
5689
+ params.setToolCallSpanAttributes
5690
+ );
5587
5691
  const bashCommand2 = params.toolName === "bash" ? String(rawInput.command ?? "").trim() : void 0;
5588
5692
  if (params.toolName === "bash") {
5589
5693
  if (!bashCommand2) {
@@ -6912,7 +7016,7 @@ async function loadSkillFromHost(availableSkills, skillName) {
6912
7016
  skill_dir: skillDir,
6913
7017
  working_directory: skillDir,
6914
7018
  location: skillFilePath,
6915
- path_resolution: `Resolve relative paths in this skill against ${skillDir}. For bash commands from this skill, cd to ${skillDir} first or use absolute paths.`,
7019
+ path_resolution: sandboxSkillPathResolution(skill.name),
6916
7020
  instructions: loaded.body
6917
7021
  };
6918
7022
  }
@@ -8540,20 +8644,156 @@ function extractCanvasId(input) {
8540
8644
  }
8541
8645
  }
8542
8646
 
8543
- // src/chat/slack/tools/canvas/api.ts
8647
+ // src/chat/slack/tools/canvas/markdown.ts
8648
+ function getListType(marker) {
8649
+ return /^\d/.test(marker) ? "numbered" : "bullet";
8650
+ }
8651
+ function getPlainListMarker(marker, type) {
8652
+ return type === "numbered" ? `${marker.match(/^\d+/)?.[0] ?? "1"}:` : "\u2022";
8653
+ }
8654
+ function getIndentWidth(whitespace, startColumn = 0) {
8655
+ let column = startColumn;
8656
+ for (const character of whitespace) {
8657
+ column += character === " " ? 4 - column % 4 : 1;
8658
+ }
8659
+ return column - startColumn;
8660
+ }
8661
+ function getLineIndent(line) {
8662
+ return getIndentWidth(line.match(/^[ \t]*/)?.[0] ?? "");
8663
+ }
8664
+ function getFence(line) {
8665
+ const match = line.match(/^([ \t]*)(`{3,}|~{3,})(.*)$/);
8666
+ const run = match?.[2];
8667
+ if (!run) return void 0;
8668
+ return {
8669
+ fence: {
8670
+ marker: run[0],
8671
+ length: run.length
8672
+ },
8673
+ indent: getIndentWidth(match[1] ?? ""),
8674
+ suffix: match[3] ?? ""
8675
+ };
8676
+ }
8677
+ function isListBlockIndent(indent, listItems2) {
8678
+ return listItems2.some(
8679
+ (item) => indent >= item.contentIndent && indent <= item.contentIndent + 3
8680
+ );
8681
+ }
8682
+ function canOpenFence(fenceLine, listItems2) {
8683
+ return fenceLine.indent <= 3 || isListBlockIndent(fenceLine.indent, listItems2);
8684
+ }
8685
+ function unwrapNestedBlockquote(line, listItems2, maxQuoteDepth = Number.POSITIVE_INFINITY) {
8686
+ const match = line.match(/^([ \t]+)((?:>[ \t]*)+)(.*)$/);
8687
+ if (!match || !isListBlockIndent(getIndentWidth(match[1] ?? ""), listItems2)) {
8688
+ return { line, quoteDepth: 0 };
8689
+ }
8690
+ let quotePrefix = match[2] ?? "";
8691
+ const quoteDepth = Math.min(
8692
+ quotePrefix.match(/>/g)?.length ?? 0,
8693
+ maxQuoteDepth
8694
+ );
8695
+ for (let index = 0; index < quoteDepth; index += 1) {
8696
+ quotePrefix = quotePrefix.replace(/^>[ \t]*/, "");
8697
+ }
8698
+ return {
8699
+ line: `${match[1]}${quotePrefix}${match[3]}`,
8700
+ quoteDepth
8701
+ };
8702
+ }
8544
8703
  function normalizeCanvasMarkdown(markdown) {
8545
8704
  let normalizedHeadingCount = 0;
8546
- const normalized = markdown.split("\n").map(
8547
- (line) => line.replace(/^(#{4,})(?=\s)/, () => {
8705
+ let normalizedMixedListCount = 0;
8706
+ let unwrappedBlockquoteCount = 0;
8707
+ let openFence;
8708
+ const listItems2 = [];
8709
+ const lines = markdown.split("\n").map((originalLine) => {
8710
+ if (openFence) {
8711
+ const unwrapped2 = openFence.quoteDepth > 0 ? unwrapNestedBlockquote(
8712
+ originalLine,
8713
+ listItems2,
8714
+ openFence.quoteDepth
8715
+ ) : { line: originalLine, quoteDepth: 0 };
8716
+ if (unwrapped2.quoteDepth > 0) {
8717
+ unwrappedBlockquoteCount += 1;
8718
+ }
8719
+ const fenceLine2 = getFence(unwrapped2.line);
8720
+ if (fenceLine2?.fence.marker === openFence.marker && fenceLine2.fence.length >= openFence.length && !fenceLine2.suffix.trim()) {
8721
+ openFence = void 0;
8722
+ }
8723
+ return unwrapped2.line;
8724
+ }
8725
+ const unwrapped = unwrapNestedBlockquote(originalLine, listItems2);
8726
+ if (unwrapped.quoteDepth > 0) {
8727
+ unwrappedBlockquoteCount += 1;
8728
+ }
8729
+ const line = unwrapped.line;
8730
+ const fenceLine = getFence(line);
8731
+ if (fenceLine && canOpenFence(fenceLine, listItems2)) {
8732
+ const rootListContentIndent2 = listItems2[0]?.contentIndent;
8733
+ if (rootListContentIndent2 !== void 0 && fenceLine.indent < rootListContentIndent2) {
8734
+ listItems2.length = 0;
8735
+ }
8736
+ openFence = {
8737
+ ...fenceLine.fence,
8738
+ quoteDepth: unwrapped.quoteDepth
8739
+ };
8740
+ return line;
8741
+ }
8742
+ const listMatch = line.match(/^([ \t]*)([-+*]|\d+[.)])([ \t]+)(.*)$/);
8743
+ if (listMatch) {
8744
+ const [, whitespace = "", marker = "", gap = "", rawContent = ""] = listMatch;
8745
+ const indent = getIndentWidth(whitespace);
8746
+ const type = getListType(marker);
8747
+ const markerEnd = indent + getIndentWidth(marker);
8748
+ const contentIndent = markerEnd + getIndentWidth(gap, markerEnd);
8749
+ const contentMatch = rawContent.match(/^(?:>[ \t]*)+(.*)$/);
8750
+ const content = contentMatch?.[1] ?? rawContent;
8751
+ if (contentMatch) {
8752
+ unwrappedBlockquoteCount += 1;
8753
+ }
8754
+ while (listItems2.length > 0 && listItems2[listItems2.length - 1].indent >= indent) {
8755
+ listItems2.pop();
8756
+ }
8757
+ const parent = listItems2[listItems2.length - 1];
8758
+ if (parent && (parent.unwrapped || parent.type !== type)) {
8759
+ listItems2.push({
8760
+ contentIndent,
8761
+ indent,
8762
+ type: parent.type,
8763
+ unwrapped: true
8764
+ });
8765
+ normalizedMixedListCount += 1;
8766
+ return `${whitespace}${getPlainListMarker(marker, type)}${gap}${content}`;
8767
+ }
8768
+ listItems2.push({ contentIndent, indent, type, unwrapped: false });
8769
+ return contentMatch ? `${whitespace}${marker}${gap}${content}` : line;
8770
+ }
8771
+ const rootListContentIndent = listItems2[0]?.contentIndent;
8772
+ if (rootListContentIndent !== void 0 && line.trim() && getLineIndent(line) < rootListContentIndent) {
8773
+ listItems2.length = 0;
8774
+ }
8775
+ const headingMatch = line.match(/^([ \t]*)#{4,}(?=[ \t])/);
8776
+ const headingIndent = headingMatch ? getIndentWidth(headingMatch[1] ?? "") : void 0;
8777
+ if (headingMatch && headingIndent !== void 0 && (headingIndent <= 3 || isListBlockIndent(headingIndent, listItems2))) {
8548
8778
  normalizedHeadingCount += 1;
8549
- return "###";
8550
- })
8551
- ).join("\n");
8779
+ return line.replace(
8780
+ /^([ \t]*)#{4,}(?=[ \t])/,
8781
+ (_, indent) => `${indent}###`
8782
+ );
8783
+ }
8784
+ return line;
8785
+ });
8786
+ const normalizedCount = normalizedHeadingCount + normalizedMixedListCount + unwrappedBlockquoteCount;
8552
8787
  return {
8553
- markdown: normalized,
8554
- normalizedHeadingCount
8788
+ markdown: lines.join("\n"),
8789
+ normalizedCount,
8790
+ normalizedHeadingCount,
8791
+ normalizedMixedListCount,
8792
+ unwrappedBlockquoteCount
8555
8793
  };
8556
8794
  }
8795
+
8796
+ // src/chat/slack/tools/canvas/api.ts
8557
8797
  async function createCanvas(input) {
8558
8798
  const client = getSlackClient();
8559
8799
  const normalizedChannelId = normalizeSlackConversationId(input.channelId);
@@ -8577,8 +8817,10 @@ async function createCanvas(input) {
8577
8817
  "app.slack.canvas.has_channel_id": Boolean(input.channelId),
8578
8818
  "app.slack.canvas.title_length": input.title.length,
8579
8819
  "app.slack.canvas.markdown_length": normalizedContent.markdown.length,
8580
- "app.slack.canvas.markdown_normalized": normalizedContent.normalizedHeadingCount > 0,
8581
- "app.slack.canvas.normalized_heading_count": normalizedContent.normalizedHeadingCount
8820
+ "app.slack.canvas.markdown_normalized": normalizedContent.normalizedCount > 0,
8821
+ "app.slack.canvas.normalized_heading_count": normalizedContent.normalizedHeadingCount,
8822
+ "app.slack.canvas.normalized_mixed_list_count": normalizedContent.normalizedMixedListCount,
8823
+ "app.slack.canvas.unwrapped_blockquote_count": normalizedContent.unwrappedBlockquoteCount
8582
8824
  }
8583
8825
  }
8584
8826
  );
@@ -8654,8 +8896,10 @@ async function writeCanvasMarkdown(input) {
8654
8896
  "app.slack.canvas.canvas_id_prefix": input.canvasId.slice(0, 1),
8655
8897
  "app.slack.canvas.operation": "replace",
8656
8898
  "app.slack.canvas.markdown_length": normalizedContent.markdown.length,
8657
- "app.slack.canvas.markdown_normalized": normalizedContent.normalizedHeadingCount > 0,
8658
- "app.slack.canvas.normalized_heading_count": normalizedContent.normalizedHeadingCount
8899
+ "app.slack.canvas.markdown_normalized": normalizedContent.normalizedCount > 0,
8900
+ "app.slack.canvas.normalized_heading_count": normalizedContent.normalizedHeadingCount,
8901
+ "app.slack.canvas.normalized_mixed_list_count": normalizedContent.normalizedMixedListCount,
8902
+ "app.slack.canvas.unwrapped_blockquote_count": normalizedContent.unwrappedBlockquoteCount
8659
8903
  }
8660
8904
  }
8661
8905
  );
@@ -11056,7 +11300,7 @@ function createToolState(hooks, context) {
11056
11300
  }
11057
11301
  };
11058
11302
  }
11059
- function createTools(availableSkills, hooks = {}, context) {
11303
+ function createTools(availableSkills, hooks = {}, context, options = {}) {
11060
11304
  const state = createToolState(hooks, context);
11061
11305
  const slackContext = getSlackToolContext(context);
11062
11306
  const slackSourceCapabilities = slackContext ? resolveChannelCapabilities(slackContext.sourceChannelId) : void 0;
@@ -11064,9 +11308,11 @@ function createTools(availableSkills, hooks = {}, context) {
11064
11308
  slackContext && slackSourceCapabilities?.canSendFiles
11065
11309
  );
11066
11310
  const tools = {
11067
- loadSkill: createLoadSkillTool(availableSkills, {
11068
- onSkillLoaded: hooks.onSkillLoaded
11069
- }),
11311
+ ...options.includeLoadSkill === false ? {} : {
11312
+ loadSkill: createLoadSkillTool(availableSkills, {
11313
+ onSkillLoaded: hooks.onSkillLoaded
11314
+ })
11315
+ },
11070
11316
  reportProgress: createReportProgressTool(),
11071
11317
  systemTime: createSystemTimeTool(),
11072
11318
  bash: createBashTool(),
@@ -12175,7 +12421,8 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxTools,
12175
12421
  const result = isSandbox ? await sandboxTools.execute({
12176
12422
  toolName,
12177
12423
  input: sandboxInput,
12178
- ...signal ? { signal } : {}
12424
+ ...signal ? { signal } : {},
12425
+ ...toolName === "grep" || toolName === "findFiles" ? { setToolCallSpanAttributes: setSpanAttributes2 } : {}
12179
12426
  }) : await toolDef.execute(toolInput, {
12180
12427
  experimental_context: sandbox,
12181
12428
  ...signal ? { signal } : {},
@@ -13322,6 +13569,27 @@ function createPluginEgress(deps) {
13322
13569
  }
13323
13570
 
13324
13571
  // src/chat/agent/tools.ts
13572
+ async function tryRecordSkillLoadStat(skill, spanContext) {
13573
+ if (!process.env.DATABASE_URL) return;
13574
+ try {
13575
+ await incrementStat({
13576
+ namespace: skill.pluginProvider ?? "junior",
13577
+ metric: "skill_load",
13578
+ name: skill.name
13579
+ });
13580
+ } catch (error) {
13581
+ logWarn(
13582
+ "skill_load_stat_failed",
13583
+ spanContext,
13584
+ {
13585
+ "app.skill.name": skill.name,
13586
+ "app.plugin.name": skill.pluginProvider,
13587
+ "exception.message": error instanceof Error ? error.message : String(error)
13588
+ },
13589
+ "Failed to record skill load stat"
13590
+ );
13591
+ }
13592
+ }
13325
13593
  async function wireAgentTools(args) {
13326
13594
  const runSource = args.routing.source;
13327
13595
  const credentialUserId = args.routing.credentialContext ? credentialUserSubjectId(args.routing.credentialContext) : void 0;
@@ -13396,6 +13664,9 @@ async function wireAgentTools(args) {
13396
13664
  const loadableSkills = args.availableSkills.filter(
13397
13665
  (skill) => skill.disableModelInvocation !== true || skill.name === args.invokedSkill?.name
13398
13666
  );
13667
+ const explicitSkillLoaded = Boolean(
13668
+ args.invokedSkill && args.activeSkills.some((skill) => skill.name === args.invokedSkill?.name)
13669
+ );
13399
13670
  const commonToolRuntimeContext = {
13400
13671
  conversationId: args.conversationId,
13401
13672
  userText: args.userInput,
@@ -13476,6 +13747,7 @@ async function wireAgentTools(args) {
13476
13747
  if (await mcpToolManager.activateForSkill(effective)) {
13477
13748
  await args.recordConnectedMcpProvider(effective.pluginProvider);
13478
13749
  }
13750
+ await tryRecordSkillLoadStat(effective, args.spanContext);
13479
13751
  if (mcpAuth.getPendingPause()) {
13480
13752
  return void 0;
13481
13753
  }
@@ -13494,7 +13766,8 @@ async function wireAgentTools(args) {
13494
13766
  };
13495
13767
  }
13496
13768
  },
13497
- toolRuntimeContext
13769
+ toolRuntimeContext,
13770
+ { includeLoadSkill: !explicitSkillLoaded }
13498
13771
  );
13499
13772
  const plannedToolExposure = planToolExposure(
13500
13773
  tools
@@ -13948,7 +14221,8 @@ async function summarizeContext(args, deps) {
13948
14221
  "- The immediate next action",
13949
14222
  "- Durable constraints, user preferences, IDs, URLs, artifacts, sandbox references, auth state, and unresolved blockers",
13950
14223
  "",
13951
- "Do not write a user-facing reply, announce a plan, or imply that the task is complete unless the source history proves it is complete."
14224
+ "Do not write a user-facing reply or announce a plan.",
14225
+ "Treat the task as complete only when the source history contains concrete evidence that every requested outcome occurred. Do not infer completion from intent, plans, progress, intermediate artifacts, or adjacent tool activity. If evidence is missing or ambiguous, preserve the task as unfinished and state the next required action."
13952
14226
  ] : [
13953
14227
  "You are performing a CONTEXT CHECKPOINT COMPACTION for Junior.",
13954
14228
  "Create a concise continuation summary for the agent that will continue this Slack thread.",
@@ -14541,6 +14815,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
14541
14815
  skillSandbox,
14542
14816
  syncLoadedSkillNamesForResume
14543
14817
  });
14818
+ const explicitSkill = invokedSkill ? activeSkills.find((skill) => skill.name === invokedSkill.name) ?? null : null;
14544
14819
  const { routerBlocks, userContentParts } = buildPromptInput(input);
14545
14820
  const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
14546
14821
  const storedTurnRoute = await loadTurnRoute({ conversationId, turnId });
@@ -14771,7 +15046,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
14771
15046
  conversationPrivacy,
14772
15047
  existingSessionPiMessages: existingSessionRecord?.piMessages,
14773
15048
  existingTurnStartMessageIndex: existingSessionRecord?.turnStartMessageIndex,
14774
- invocation: skillInvocation,
15049
+ explicitSkill,
14775
15050
  priorPiMessages,
14776
15051
  resumedFromSessionRecord,
14777
15052
  routing,