@sentry/junior 0.117.0 → 0.118.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 (66) hide show
  1. package/dist/{agent-hooks-5IK4LPLP.js → agent-hooks-J2FB7XD6.js} +8 -10
  2. package/dist/api/schema/stats.d.ts +22 -0
  3. package/dist/api/schema.d.ts +2 -0
  4. package/dist/api/schema.js +7 -3
  5. package/dist/api/stats.d.ts +16 -0
  6. package/dist/api/user-pages/routes.d.ts +4 -0
  7. package/dist/api.js +179 -18
  8. package/dist/app.js +40 -35
  9. package/dist/{catalog-runtime-MTUYW3AA.js → catalog-runtime-UHHLU53I.js} +5 -5
  10. package/dist/chat/agent/prompt.d.ts +4 -2
  11. package/dist/chat/plugins/user-pages.d.ts +15 -0
  12. package/dist/chat/plugins/viewer-actors.d.ts +6 -0
  13. package/dist/chat/prompt.d.ts +1 -2
  14. package/dist/chat/sandbox/paths.d.ts +2 -0
  15. package/dist/chat/sandbox/sandbox.d.ts +3 -1
  16. package/dist/chat/sandbox/workspace.d.ts +1 -0
  17. package/dist/chat/skills.d.ts +1 -2
  18. package/dist/chat/tools/index.d.ts +4 -1
  19. package/dist/chat/tools/sandbox/file-utils.d.ts +11 -0
  20. package/dist/chat/tools/sandbox/find-files.d.ts +2 -1
  21. package/dist/chat/tools/sandbox/grep.d.ts +2 -1
  22. package/dist/{chunk-5O3JDQDG.js → chunk-3LGCVRBQ.js} +1 -1
  23. package/dist/{chunk-WEK3V5RI.js → chunk-3MUSSFMP.js} +17 -19
  24. package/dist/{chunk-KU6ALEJC.js → chunk-3N3FMCKN.js} +30 -2
  25. package/dist/{chunk-FAWBONPC.js → chunk-4LPPH7Y6.js} +5 -7
  26. package/dist/{chunk-W5JEQQEA.js → chunk-4SOQPUF4.js} +1 -1
  27. package/dist/{chunk-LN765Y4N.js → chunk-6BGUAOYP.js} +8 -8
  28. package/dist/{chunk-NHZEUHKH.js → chunk-6K2QE2UI.js} +231 -94
  29. package/dist/{chunk-AKG4TZNH.js → chunk-7GADTCNS.js} +30 -9
  30. package/dist/{chunk-BFIRIZPH.js → chunk-7SM3P4XR.js} +19 -1
  31. package/dist/{chunk-WKU7TY22.js → chunk-FIANEE7Z.js} +3 -1
  32. package/dist/{chunk-RXRQJF7Y.js → chunk-GCGJGSQL.js} +21 -0
  33. package/dist/{chunk-STUI6NIL.js → chunk-GT2ZUALJ.js} +511 -14
  34. package/dist/{chunk-LUMEBOWC.js → chunk-HN6LFYG3.js} +1 -1
  35. package/dist/{chunk-3GE3F4LT.js → chunk-HWUBBHLK.js} +1 -1
  36. package/dist/{chunk-ZRQUQJUV.js → chunk-KC5EE27Y.js} +3 -3
  37. package/dist/chunk-L72GDKSN.js +37 -0
  38. package/dist/{chunk-72Z3HNTQ.js → chunk-MLZFK4KP.js} +1 -1
  39. package/dist/{chunk-27KWTFP6.js → chunk-PJPKHGLG.js} +5 -7
  40. package/dist/{chunk-DS27ZBSP.js → chunk-PWRFTDTY.js} +1 -1
  41. package/dist/{chunk-WUNPRM76.js → chunk-QWCF4RAC.js} +1 -1
  42. package/dist/{chunk-6G45D3YG.js → chunk-S6R23MFB.js} +12 -16
  43. package/dist/{chunk-CU7HTGNI.js → chunk-UXLYAVYL.js} +1 -1
  44. package/dist/cli/chat.js +23 -24
  45. package/dist/cli/check.js +6 -6
  46. package/dist/cli/plugins.js +12 -14
  47. package/dist/cli/snapshot-create.js +8 -9
  48. package/dist/cli/upgrade.js +7 -7
  49. package/dist/db/schema/stats.d.ts +93 -0
  50. package/dist/db/schema.d.ts +94 -1
  51. package/dist/{db-J2ITKS63.js → db-PMRHFSYL.js} +6 -6
  52. package/dist/instrumentation.js +2 -2
  53. package/dist/nitro.js +3 -3
  54. package/dist/{oauth-callback-server-NQYKBT24.js → oauth-callback-server-QJUC7S4X.js} +15 -17
  55. package/dist/{plugins-IUJA4VBU.js → plugins-HW4VWW6G.js} +1 -1
  56. package/dist/{runner-53V5GIQM.js → runner-PZMV6JQW.js} +10 -12
  57. package/dist/{skills-BU3ZMLVB.js → skills-W4JETYSV.js} +6 -6
  58. package/dist/stats.d.ts +18 -0
  59. package/dist/{validation-74EIKKE6.js → validation-IJLNHZSU.js} +6 -6
  60. package/dist/version.js +1 -1
  61. package/migrations/0011_sour_golden_guardian.sql +8 -0
  62. package/migrations/meta/0011_snapshot.json +1195 -0
  63. package/migrations/meta/_journal.json +8 -1
  64. package/package.json +5 -5
  65. package/dist/chunk-4ZNGQH7C.js +0 -25
  66. package/dist/chunk-YRWJ3KEU.js +0 -517
@@ -31,7 +31,7 @@ import {
31
31
  toMcpProviderError,
32
32
  toolInvocationDestination,
33
33
  uploadFilesToConversation
34
- } from "./chunk-6G45D3YG.js";
34
+ } from "./chunk-S6R23MFB.js";
35
35
  import {
36
36
  CooperativeTurnYieldError,
37
37
  JUNIOR_THREAD_STATE_TTL_MS,
@@ -73,7 +73,7 @@ import {
73
73
  stripRuntimeTurnContext,
74
74
  trimTrailingAssistantMessages,
75
75
  updateConversationStats
76
- } from "./chunk-27KWTFP6.js";
76
+ } from "./chunk-PJPKHGLG.js";
77
77
  import {
78
78
  parseSandboxEgressAuthRequiredSignal,
79
79
  parseSandboxEgressPermissionDeniedSignal,
@@ -97,7 +97,10 @@ import {
97
97
  runNonInteractiveCommand,
98
98
  throwSandboxOperationError,
99
99
  wrapSandboxSetupError
100
- } from "./chunk-FAWBONPC.js";
100
+ } from "./chunk-4LPPH7Y6.js";
101
+ import {
102
+ incrementStat
103
+ } from "./chunk-L72GDKSN.js";
101
104
  import {
102
105
  addAgentTurnUsage,
103
106
  hasAgentTurnUsage
@@ -106,30 +109,20 @@ import {
106
109
  privateTraceResultAttributes
107
110
  } from "./chunk-UIE3R5XU.js";
108
111
  import {
112
+ SlackActionError,
109
113
  createPluginHookRunner,
114
+ downloadPrivateSlackFile,
115
+ getFilePermalink,
110
116
  getPluginSystemPromptContributions,
111
117
  getPluginTools,
112
118
  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
119
  getSlackClient,
120
+ getSlackToolContext,
129
121
  isConversationScopedChannel,
130
122
  normalizeSlackConversationId,
123
+ resolveChannelCapabilities,
131
124
  withSlackRetries
132
- } from "./chunk-YRWJ3KEU.js";
125
+ } from "./chunk-GT2ZUALJ.js";
133
126
  import {
134
127
  KNOWN_CONVERSATION_EVENT_TYPES,
135
128
  contextProvenance,
@@ -138,10 +131,17 @@ import {
138
131
  getConversationStore,
139
132
  instructionActors,
140
133
  instructionProvenanceFor
141
- } from "./chunk-ZRQUQJUV.js";
134
+ } from "./chunk-KC5EE27Y.js";
142
135
  import {
136
+ SANDBOX_DATA_ROOT,
137
+ SANDBOX_SKILLS_ROOT,
138
+ SANDBOX_WORKSPACE_ROOT,
139
+ getStateAdapter,
140
+ sandboxSkillDir,
141
+ sandboxSkillFile,
142
+ sandboxSkillPathResolution,
143
143
  sleep
144
- } from "./chunk-4ZNGQH7C.js";
144
+ } from "./chunk-3N3FMCKN.js";
145
145
  import {
146
146
  ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
147
147
  COMPACTION_SUMMARY_PREFIX,
@@ -172,7 +172,7 @@ import {
172
172
  renderCurrentInstruction,
173
173
  resolveGatewayModel,
174
174
  unwrapCurrentInstruction
175
- } from "./chunk-DS27ZBSP.js";
175
+ } from "./chunk-PWRFTDTY.js";
176
176
  import {
177
177
  canExposeConversationPayload,
178
178
  parseSlackThreadId,
@@ -189,10 +189,10 @@ import {
189
189
  findSkillByName,
190
190
  loadSkillsByName,
191
191
  parseSkillInvocation
192
- } from "./chunk-AKG4TZNH.js";
192
+ } from "./chunk-7GADTCNS.js";
193
193
  import {
194
194
  pluginCatalogRuntime
195
- } from "./chunk-CU7HTGNI.js";
195
+ } from "./chunk-UXLYAVYL.js";
196
196
  import {
197
197
  CredentialUnavailableError,
198
198
  OAuthProviderError,
@@ -200,7 +200,7 @@ import {
200
200
  getOAuthProviderErrorAttributes,
201
201
  resolveAuthTokenPlaceholder,
202
202
  resolvePluginCommandEnv
203
- } from "./chunk-5O3JDQDG.js";
203
+ } from "./chunk-3LGCVRBQ.js";
204
204
  import {
205
205
  credentialUserSubjectId
206
206
  } from "./chunk-W3U3YQYI.js";
@@ -224,7 +224,7 @@ import {
224
224
  summarizeMessageText,
225
225
  withLogContext,
226
226
  withSpan
227
- } from "./chunk-72Z3HNTQ.js";
227
+ } from "./chunk-MLZFK4KP.js";
228
228
  import {
229
229
  parseSlackChannelId,
230
230
  parseSlackMessageTs,
@@ -1668,13 +1668,10 @@ function formatSkillEntry(skill) {
1668
1668
  lines.push(" </skill>");
1669
1669
  return lines;
1670
1670
  }
1671
- function formatAvailableSkillsForPrompt(skills, invocation) {
1671
+ function formatAvailableSkillsForPrompt(skills) {
1672
1672
  const autoSelectable = skills.filter(
1673
1673
  (s) => s.disableModelInvocation !== true
1674
1674
  );
1675
- const invokedExplicitOnly = invocation ? skills.filter(
1676
- (s) => s.disableModelInvocation === true && s.name === invocation.skillName
1677
- ) : [];
1678
1675
  const sections = [];
1679
1676
  if (autoSelectable.length > 0) {
1680
1677
  const available = [
@@ -1687,17 +1684,6 @@ function formatAvailableSkillsForPrompt(skills, invocation) {
1687
1684
  available.push("</available-skills>");
1688
1685
  sections.push(available.join("\n"));
1689
1686
  }
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
1687
  return sections.length > 0 ? sections.join("\n") : null;
1702
1688
  }
1703
1689
  function formatActiveMcpCatalogsForPrompt(catalogs) {
@@ -1810,7 +1796,8 @@ var TOOL_CALL_STYLE_RULES = [
1810
1796
  "- Keep tool-call explanations separate from final answers; final answers should report results, evidence, or blockers."
1811
1797
  ];
1812
1798
  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.",
1799
+ "- A `<skill>` block in the current user turn is already loaded. Follow its instructions directly and do not call `loadSkill` for that skill.",
1800
+ "- Otherwise, only load skills listed in `<available-skills>`. Never guess or invent a skill name.",
1814
1801
  "- Load one skill at a time. After `loadSkill`, follow the instructions returned by that tool result."
1815
1802
  ];
1816
1803
  var EXECUTION_CONTRACT_RULES = [
@@ -1995,14 +1982,6 @@ function buildContextSection(params) {
1995
1982
  ])
1996
1983
  );
1997
1984
  }
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
1985
  const body = blocks.map((block) => block.join("\n")).join("\n\n");
2007
1986
  if (!body) {
2008
1987
  return null;
@@ -2012,8 +1991,7 @@ function buildContextSection(params) {
2012
1991
  function buildCapabilitiesSection(params) {
2013
1992
  const blocks = [];
2014
1993
  const availableSkills = formatAvailableSkillsForPrompt(
2015
- params.availableSkills,
2016
- params.invocation
1994
+ params.availableSkills
2017
1995
  );
2018
1996
  if (availableSkills) {
2019
1997
  blocks.push(availableSkills);
@@ -2094,7 +2072,6 @@ function buildTurnContextPrompt(params) {
2094
2072
  includeSessionContext ? buildCapabilitiesSection({
2095
2073
  availableSkills: params.availableSkills,
2096
2074
  activeMcpCatalogs: params.activeMcpCatalogs ?? [],
2097
- invocation: params.invocation,
2098
2075
  toolGuidance: params.toolGuidance ?? []
2099
2076
  }) : null,
2100
2077
  pluginPromptContributions,
@@ -2102,8 +2079,7 @@ function buildTurnContextPrompt(params) {
2102
2079
  actor: params.actor,
2103
2080
  artifactState: params.artifactState,
2104
2081
  configuration: params.configuration,
2105
- dispatch: params.dispatch,
2106
- invocation: params.invocation
2082
+ dispatch: params.dispatch
2107
2083
  }) : null,
2108
2084
  includeSessionContext ? buildRuntimeSection(params.runtime ?? {}) : null
2109
2085
  ].filter((section) => Boolean(section));
@@ -2146,6 +2122,17 @@ function buildUserTurnText(userInput, conversationContext, actor) {
2146
2122
  currentInstruction
2147
2123
  ].join("\n");
2148
2124
  }
2125
+ function renderExplicitSkillInstructions(skill) {
2126
+ return [
2127
+ "<skill>",
2128
+ `<name>${escapeXml(skill.name)}</name>`,
2129
+ `<path>${escapeXml(sandboxSkillFile(skill.name))}</path>`,
2130
+ `<working_directory>${escapeXml(sandboxSkillDir(skill.name))}</working_directory>`,
2131
+ `<path_resolution>${escapeXml(sandboxSkillPathResolution(skill.name))}</path_resolution>`,
2132
+ skill.body,
2133
+ "</skill>"
2134
+ ].join("\n");
2135
+ }
2149
2136
  function encodeNonImageAttachmentForPrompt(attachment) {
2150
2137
  const base64 = attachment.data.toString("base64");
2151
2138
  const wasTruncated = base64.length > MAX_INLINE_ATTACHMENT_BASE64_CHARS;
@@ -2343,14 +2330,23 @@ async function assemblePrompt(args) {
2343
2330
  const source = args.routing.source;
2344
2331
  const hasPromptCheckpoint = args.resumedFromSessionRecord && args.existingTurnStartMessageIndex !== void 0;
2345
2332
  const shouldPromptAgent = !args.resumedFromSessionRecord || !hasPromptCheckpoint;
2333
+ const requestContentParts = [
2334
+ ...args.explicitSkill ? [
2335
+ {
2336
+ type: "text",
2337
+ text: renderExplicitSkillInstructions(args.explicitSkill)
2338
+ }
2339
+ ] : [],
2340
+ ...args.userContentParts
2341
+ ];
2346
2342
  const promptHistoryMessages = shouldPromptAgent ? withoutTrailingUncheckpointedUserPrompt(
2347
2343
  args.priorPiMessages,
2348
- args.userContentParts
2344
+ requestContentParts
2349
2345
  ) : args.existingSessionPiMessages;
2350
2346
  const replayedPromptContent = shouldPromptAgent && !args.resumedFromSessionRecord ? checkpointedPromptContent({
2351
2347
  messages: args.existingSessionPiMessages,
2352
2348
  turnStartMessageIndex: args.existingTurnStartMessageIndex,
2353
- userContentParts: args.userContentParts
2349
+ userContentParts: requestContentParts
2354
2350
  }) : void 0;
2355
2351
  const needsBootstrapContextForPrompt = shouldPromptAgent && !replayedPromptContent && !hasRuntimeTurnContext(promptHistoryMessages);
2356
2352
  const systemPromptContributions = await getPluginSystemPromptContributions(source);
@@ -2376,7 +2372,6 @@ async function assemblePrompt(args) {
2376
2372
  destination: args.routing.destination,
2377
2373
  source
2378
2374
  } : void 0,
2379
- invocation: args.invocation,
2380
2375
  actor: isUserActor(args.currentActor) ? args.currentActor : void 0,
2381
2376
  artifactState: args.artifactState,
2382
2377
  configuration: args.configurationValues
@@ -2384,7 +2379,7 @@ async function assemblePrompt(args) {
2384
2379
  const turnContextParts = turnContextPrompt ? [{ type: "text", text: turnContextPrompt }] : [];
2385
2380
  const promptContentParts = replayedPromptContent ?? [
2386
2381
  ...turnContextParts,
2387
- ...args.userContentParts
2382
+ ...requestContentParts
2388
2383
  ];
2389
2384
  const inputMessages = [
2390
2385
  {
@@ -3076,6 +3071,7 @@ import { Sandbox } from "@vercel/sandbox";
3076
3071
  // src/chat/sandbox/skill-sync.ts
3077
3072
  import fs3 from "fs/promises";
3078
3073
  import path3 from "path";
3074
+ var DIRECTORY_CREATE_CONCURRENCY = 8;
3079
3075
  function toPosixRelative(base, absolute) {
3080
3076
  return path3.relative(base, absolute).split(path3.sep).join("/");
3081
3077
  }
@@ -3150,6 +3146,16 @@ function collectDirectories(filesToWrite, workspaceRoot) {
3150
3146
  (directory) => directory === workspaceRoot || directory.startsWith(`${workspaceRoot}/`)
3151
3147
  ).sort((a, b) => a.length - b.length);
3152
3148
  }
3149
+ function groupDirectoriesByDepth(directories) {
3150
+ const groups = /* @__PURE__ */ new Map();
3151
+ for (const directory of directories) {
3152
+ const depth = directory.split("/").filter(Boolean).length;
3153
+ const group = groups.get(depth) ?? [];
3154
+ group.push(directory);
3155
+ groups.set(depth, group);
3156
+ }
3157
+ return [...groups.entries()].sort(([left], [right]) => left - right).map(([, group]) => group);
3158
+ }
3153
3159
  function resolveHostSkillPath(availableSkills, sandboxPath) {
3154
3160
  const normalizedPath = path3.posix.normalize(sandboxPath.trim());
3155
3161
  for (const skill of availableSkills) {
@@ -3219,13 +3225,21 @@ async function syncSkillsToSandbox(params) {
3219
3225
  },
3220
3226
  async () => {
3221
3227
  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
- }
3228
+ for (const directoriesAtDepth of groupDirectoriesByDepth(
3229
+ directories
3230
+ )) {
3231
+ for (let offset = 0; offset < directoriesAtDepth.length; offset += DIRECTORY_CREATE_CONCURRENCY) {
3232
+ await Promise.all(
3233
+ directoriesAtDepth.slice(offset, offset + DIRECTORY_CREATE_CONCURRENCY).map(async (directory) => {
3234
+ try {
3235
+ await params.sandbox.mkDir(directory);
3236
+ } catch (error) {
3237
+ if (!isAlreadyExistsError(error)) {
3238
+ throw error;
3239
+ }
3240
+ }
3241
+ })
3242
+ );
3229
3243
  }
3230
3244
  }
3231
3245
  await params.sandbox.writeFiles(filesToWrite);
@@ -3822,6 +3836,7 @@ var RIPGREP_EXCLUDED_GLOBS = [
3822
3836
  "!**/.git/**",
3823
3837
  "!**/node_modules/**"
3824
3838
  ];
3839
+ var DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 3e4;
3825
3840
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
3826
3841
  function positiveInteger(value) {
3827
3842
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -4409,7 +4424,7 @@ function createEditFileTool() {
4409
4424
  // src/chat/tools/sandbox/find-files.ts
4410
4425
  import path5 from "path";
4411
4426
  import { z as z5 } from "zod";
4412
- var DEFAULT_FIND_LIMIT = 1e3;
4427
+ var DEFAULT_FIND_LIMIT = 100;
4413
4428
  async function findFiles(params) {
4414
4429
  if (!params.pattern.trim()) {
4415
4430
  throw new Error("pattern is required");
@@ -4420,6 +4435,7 @@ async function findFiles(params) {
4420
4435
  return await findFilesWithRipgrep({
4421
4436
  fs: params.fs,
4422
4437
  limit,
4438
+ onTelemetry: params.onTelemetry,
4423
4439
  path: params.path,
4424
4440
  pattern: params.pattern,
4425
4441
  root,
@@ -4488,25 +4504,45 @@ async function findFilesWithRipgrep(params) {
4488
4504
  throw error;
4489
4505
  }
4490
4506
  const location = getRipgrepSearchLocation(params.root, rootIsDirectory);
4491
- const args = [
4507
+ const ripgrepArgs = [
4492
4508
  "--files",
4493
4509
  "--null",
4494
4510
  "--hidden",
4495
- "--sort=path",
4496
4511
  "--glob",
4497
4512
  params.pattern
4498
4513
  ];
4499
4514
  for (const excludedGlob of RIPGREP_EXCLUDED_GLOBS) {
4500
- args.push("--glob", excludedGlob);
4515
+ ripgrepArgs.push("--glob", excludedGlob);
4501
4516
  }
4502
- args.push("--", location.target);
4517
+ ripgrepArgs.push("--", location.target);
4503
4518
  const result = await params.runCommand({
4504
- cmd: "rg",
4505
- args,
4506
- cwd: location.cwd
4519
+ cmd: "bash",
4520
+ args: [
4521
+ "-c",
4522
+ [
4523
+ `rg "$@" | head -z -n ${params.limit + 1}`,
4524
+ 'statuses=("${PIPESTATUS[@]}")',
4525
+ "if (( statuses[1] != 0 )); then",
4526
+ ' exit "${statuses[1]}"',
4527
+ "fi",
4528
+ "if (( statuses[0] == 0 || statuses[0] == 1 || statuses[0] == 141 )); then",
4529
+ " exit 0",
4530
+ "fi",
4531
+ 'exit "${statuses[0]}"'
4532
+ ].join("\n"),
4533
+ "find-files",
4534
+ ...ripgrepArgs
4535
+ ],
4536
+ cwd: location.cwd,
4537
+ timeoutMs: DEFAULT_SEARCH_COMMAND_TIMEOUT_MS
4507
4538
  });
4508
- if (result.exitCode !== 0 && result.exitCode !== 1) {
4539
+ if (result.exitCode !== 0) {
4509
4540
  const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
4541
+ if (/error parsing glob|invalid glob/i.test(detail)) {
4542
+ throw new ToolInputError(`Invalid glob pattern: ${params.pattern}`, {
4543
+ cause: new Error(detail)
4544
+ });
4545
+ }
4510
4546
  throw new Error(`ripgrep file search failed: ${detail}`);
4511
4547
  }
4512
4548
  const allPaths = result.stdout.split("\0").filter(Boolean).map((filePath) => {
@@ -4530,7 +4566,7 @@ async function findFilesWithRipgrep(params) {
4530
4566
  const text = notices.length > 0 ? `${bounded.content}
4531
4567
 
4532
4568
  [${notices.join(" ")}]` : bounded.content;
4533
- return makeStructuredToolResult(
4569
+ const response = makeStructuredToolResult(
4534
4570
  {
4535
4571
  ok: true,
4536
4572
  status: "success",
@@ -4547,6 +4583,19 @@ async function findFilesWithRipgrep(params) {
4547
4583
  },
4548
4584
  { content: [{ type: "text", text }] }
4549
4585
  );
4586
+ params.onTelemetry?.({
4587
+ emittedLineCount: relativePaths.length,
4588
+ limit: params.limit,
4589
+ limitReached,
4590
+ parsedRecordCount: allPaths.length,
4591
+ rawOutputBytes: Buffer.byteLength(result.stdout, "utf8"),
4592
+ resultBytes: response.content.reduce(
4593
+ (total, item) => total + (item.type === "text" ? Buffer.byteLength(item.text, "utf8") : 0),
4594
+ 0
4595
+ ),
4596
+ resultCount: relativePaths.length
4597
+ });
4598
+ return response;
4550
4599
  }
4551
4600
  function createFindFilesTool() {
4552
4601
  return zodTool({
@@ -4559,7 +4608,7 @@ function createFindFilesTool() {
4559
4608
  path: z5.string().min(1).describe(
4560
4609
  "Directory or file path in the sandbox workspace. Defaults to the workspace root."
4561
4610
  ).optional(),
4562
- limit: z5.coerce.number().int().min(1).describe("Maximum number of file paths to return. Defaults to 1000.").optional()
4611
+ limit: z5.coerce.number().int().min(1).describe("Maximum number of file paths to return. Defaults to 100.").optional()
4563
4612
  }),
4564
4613
  outputSchema: juniorToolResultSchema,
4565
4614
  execute: async () => {
@@ -4612,6 +4661,7 @@ async function grepFiles(params) {
4612
4661
  ignoreCase: params.ignoreCase,
4613
4662
  limit,
4614
4663
  literal: params.literal,
4664
+ onTelemetry: params.onTelemetry,
4615
4665
  path: params.path,
4616
4666
  pattern: params.pattern,
4617
4667
  root,
@@ -4759,7 +4809,13 @@ async function grepFilesWithRipgrep(params) {
4759
4809
  throw error;
4760
4810
  }
4761
4811
  const location = getRipgrepSearchLocation(params.root, rootIsDirectory);
4762
- const args = ["--json", "--line-number", "--hidden", "--sort=path"];
4812
+ const args = [
4813
+ "--json",
4814
+ "--line-number",
4815
+ "--hidden",
4816
+ "--max-count",
4817
+ String(params.limit + 1)
4818
+ ];
4763
4819
  if (params.ignoreCase) {
4764
4820
  args.push("--ignore-case");
4765
4821
  }
@@ -4779,7 +4835,8 @@ async function grepFilesWithRipgrep(params) {
4779
4835
  const result = await params.runCommand({
4780
4836
  cmd: "rg",
4781
4837
  args,
4782
- cwd: location.cwd
4838
+ cwd: location.cwd,
4839
+ timeoutMs: DEFAULT_SEARCH_COMMAND_TIMEOUT_MS
4783
4840
  });
4784
4841
  if (result.exitCode !== 0 && result.exitCode !== 1) {
4785
4842
  const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
@@ -4793,6 +4850,7 @@ async function grepFilesWithRipgrep(params) {
4793
4850
  const recordsByPath = /* @__PURE__ */ new Map();
4794
4851
  const pathOrder = [];
4795
4852
  let totalMatches = 0;
4853
+ let parsedRecordCount = 0;
4796
4854
  for (const rawLine of normalizeToLf(result.stdout).split("\n")) {
4797
4855
  if (!rawLine) {
4798
4856
  continue;
@@ -4803,6 +4861,7 @@ async function grepFilesWithRipgrep(params) {
4803
4861
  } catch (error) {
4804
4862
  throw new Error("ripgrep returned invalid JSON output", { cause: error });
4805
4863
  }
4864
+ parsedRecordCount += 1;
4806
4865
  if (record.type !== "match" && record.type !== "context") {
4807
4866
  continue;
4808
4867
  }
@@ -4884,7 +4943,7 @@ async function grepFilesWithRipgrep(params) {
4884
4943
  if (bounded.truncated) {
4885
4944
  notices.push(`${MAX_TEXT_CHARS} character output limit reached.`);
4886
4945
  }
4887
- return makeStructuredToolResult({
4946
+ const response = makeStructuredToolResult({
4888
4947
  ok: true,
4889
4948
  status: "success",
4890
4949
  target: params.path ?? ".",
@@ -4903,6 +4962,19 @@ async function grepFilesWithRipgrep(params) {
4903
4962
  ...matchLimitReached ? { match_limit_reached: params.limit } : {},
4904
4963
  ...lineTruncated ? { line_truncated: true } : {}
4905
4964
  });
4965
+ params.onTelemetry?.({
4966
+ emittedLineCount: output.length,
4967
+ limit: params.limit,
4968
+ limitReached: matchLimitReached,
4969
+ parsedRecordCount,
4970
+ rawOutputBytes: Buffer.byteLength(result.stdout, "utf8"),
4971
+ resultBytes: response.content.reduce(
4972
+ (total, item) => total + (item.type === "text" ? Buffer.byteLength(item.text, "utf8") : 0),
4973
+ 0
4974
+ ),
4975
+ resultCount: matchCount
4976
+ });
4977
+ return response;
4906
4978
  }
4907
4979
  function createGrepTool() {
4908
4980
  return zodTool({
@@ -5242,11 +5314,12 @@ function createSandbox(options) {
5242
5314
  onSandboxPrepare: options.prepare,
5243
5315
  onSandboxRefChanged: options.onSandboxRefChanged
5244
5316
  });
5245
- const createToolCallContext = (signal) => {
5317
+ const createToolCallContext = (signal, setToolCallSpanAttributes) => {
5246
5318
  let fileSystemPromise;
5247
5319
  let commandRunnerPromise;
5248
5320
  return {
5249
5321
  signal,
5322
+ setToolCallSpanAttributes,
5250
5323
  fileSystem() {
5251
5324
  signal?.throwIfAborted();
5252
5325
  return fileSystemPromise ??= runtime.tools().then(({ fs: fs5 }) => {
@@ -5267,10 +5340,31 @@ function createSandbox(options) {
5267
5340
  };
5268
5341
  };
5269
5342
  const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
5270
- const withSandboxToolSpan = (name, op, attributes, callback) => withSandboxSpan(name, op, attributes, async () => {
5343
+ const withSandboxToolSpan = (name, op, attributes, callback) => withSandboxSpan(name, op, attributes, async (setSpanAttributes2) => {
5271
5344
  await runtime.refreshNetworkPolicy(getTracePropagationHeaders());
5272
- return await callback();
5345
+ return await callback(setSpanAttributes2);
5346
+ });
5347
+ const searchTelemetryAttributes = (telemetry) => ({
5348
+ "app.sandbox.search.raw_output_bytes": telemetry.rawOutputBytes,
5349
+ "app.sandbox.search.parsed_records": telemetry.parsedRecordCount,
5350
+ "app.sandbox.search.result_count": telemetry.resultCount,
5351
+ "app.sandbox.search.emitted_lines": telemetry.emittedLineCount,
5352
+ "app.sandbox.search.result_bytes": telemetry.resultBytes,
5353
+ "app.sandbox.search.limit": telemetry.limit,
5354
+ "app.sandbox.search.limit_reached": telemetry.limitReached
5273
5355
  });
5356
+ const recordSearchTelemetry = (telemetry, setSearchSpanAttributes, setToolCallSpanAttributes) => {
5357
+ const attributes = searchTelemetryAttributes(telemetry);
5358
+ for (const setAttributes of [
5359
+ setSearchSpanAttributes,
5360
+ setToolCallSpanAttributes
5361
+ ]) {
5362
+ try {
5363
+ setAttributes?.(attributes);
5364
+ } catch {
5365
+ }
5366
+ }
5367
+ };
5274
5368
  const logSandboxBootRequest = (trigger, details = {}) => {
5275
5369
  if (runtime.sandboxRef()) {
5276
5370
  return;
@@ -5515,10 +5609,15 @@ function createSandbox(options) {
5515
5609
  {
5516
5610
  "app.sandbox.pattern.length": pattern.length
5517
5611
  },
5518
- async () => {
5612
+ async (setSearchSpanAttributes) => {
5519
5613
  const response = await grepFiles({
5520
5614
  fs: fileSystem,
5521
5615
  runCommand: await context.commandRunner(),
5616
+ onTelemetry: (telemetry) => recordSearchTelemetry(
5617
+ telemetry,
5618
+ setSearchSpanAttributes,
5619
+ context.setToolCallSpanAttributes
5620
+ ),
5522
5621
  pattern,
5523
5622
  ...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
5524
5623
  ...typeof rawInput.glob === "string" ? { glob: rawInput.glob } : {},
@@ -5547,10 +5646,15 @@ function createSandbox(options) {
5547
5646
  {
5548
5647
  "app.sandbox.pattern.length": pattern.length
5549
5648
  },
5550
- async () => {
5649
+ async (setSearchSpanAttributes) => {
5551
5650
  const response = await findFiles({
5552
5651
  fs: fileSystem,
5553
5652
  runCommand: await context.commandRunner(),
5653
+ onTelemetry: (telemetry) => recordSearchTelemetry(
5654
+ telemetry,
5655
+ setSearchSpanAttributes,
5656
+ context.setToolCallSpanAttributes
5657
+ ),
5554
5658
  pattern,
5555
5659
  ...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
5556
5660
  ...limit ? { limit } : {}
@@ -5583,7 +5687,10 @@ function createSandbox(options) {
5583
5687
  };
5584
5688
  const execute = async (params) => {
5585
5689
  const rawInput = params.input ?? {};
5586
- const context = createToolCallContext(params.signal);
5690
+ const context = createToolCallContext(
5691
+ params.signal,
5692
+ params.setToolCallSpanAttributes
5693
+ );
5587
5694
  const bashCommand2 = params.toolName === "bash" ? String(rawInput.command ?? "").trim() : void 0;
5588
5695
  if (params.toolName === "bash") {
5589
5696
  if (!bashCommand2) {
@@ -6912,7 +7019,7 @@ async function loadSkillFromHost(availableSkills, skillName) {
6912
7019
  skill_dir: skillDir,
6913
7020
  working_directory: skillDir,
6914
7021
  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.`,
7022
+ path_resolution: sandboxSkillPathResolution(skill.name),
6916
7023
  instructions: loaded.body
6917
7024
  };
6918
7025
  }
@@ -11056,7 +11163,7 @@ function createToolState(hooks, context) {
11056
11163
  }
11057
11164
  };
11058
11165
  }
11059
- function createTools(availableSkills, hooks = {}, context) {
11166
+ function createTools(availableSkills, hooks = {}, context, options = {}) {
11060
11167
  const state = createToolState(hooks, context);
11061
11168
  const slackContext = getSlackToolContext(context);
11062
11169
  const slackSourceCapabilities = slackContext ? resolveChannelCapabilities(slackContext.sourceChannelId) : void 0;
@@ -11064,9 +11171,11 @@ function createTools(availableSkills, hooks = {}, context) {
11064
11171
  slackContext && slackSourceCapabilities?.canSendFiles
11065
11172
  );
11066
11173
  const tools = {
11067
- loadSkill: createLoadSkillTool(availableSkills, {
11068
- onSkillLoaded: hooks.onSkillLoaded
11069
- }),
11174
+ ...options.includeLoadSkill === false ? {} : {
11175
+ loadSkill: createLoadSkillTool(availableSkills, {
11176
+ onSkillLoaded: hooks.onSkillLoaded
11177
+ })
11178
+ },
11070
11179
  reportProgress: createReportProgressTool(),
11071
11180
  systemTime: createSystemTimeTool(),
11072
11181
  bash: createBashTool(),
@@ -12175,7 +12284,8 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxTools,
12175
12284
  const result = isSandbox ? await sandboxTools.execute({
12176
12285
  toolName,
12177
12286
  input: sandboxInput,
12178
- ...signal ? { signal } : {}
12287
+ ...signal ? { signal } : {},
12288
+ ...toolName === "grep" || toolName === "findFiles" ? { setToolCallSpanAttributes: setSpanAttributes2 } : {}
12179
12289
  }) : await toolDef.execute(toolInput, {
12180
12290
  experimental_context: sandbox,
12181
12291
  ...signal ? { signal } : {},
@@ -13322,6 +13432,27 @@ function createPluginEgress(deps) {
13322
13432
  }
13323
13433
 
13324
13434
  // src/chat/agent/tools.ts
13435
+ async function tryRecordSkillLoadStat(skill, spanContext) {
13436
+ if (!process.env.DATABASE_URL) return;
13437
+ try {
13438
+ await incrementStat({
13439
+ namespace: skill.pluginProvider ?? "junior",
13440
+ metric: "skill_load",
13441
+ name: skill.name
13442
+ });
13443
+ } catch (error) {
13444
+ logWarn(
13445
+ "skill_load_stat_failed",
13446
+ spanContext,
13447
+ {
13448
+ "app.skill.name": skill.name,
13449
+ "app.plugin.name": skill.pluginProvider,
13450
+ "exception.message": error instanceof Error ? error.message : String(error)
13451
+ },
13452
+ "Failed to record skill load stat"
13453
+ );
13454
+ }
13455
+ }
13325
13456
  async function wireAgentTools(args) {
13326
13457
  const runSource = args.routing.source;
13327
13458
  const credentialUserId = args.routing.credentialContext ? credentialUserSubjectId(args.routing.credentialContext) : void 0;
@@ -13396,6 +13527,9 @@ async function wireAgentTools(args) {
13396
13527
  const loadableSkills = args.availableSkills.filter(
13397
13528
  (skill) => skill.disableModelInvocation !== true || skill.name === args.invokedSkill?.name
13398
13529
  );
13530
+ const explicitSkillLoaded = Boolean(
13531
+ args.invokedSkill && args.activeSkills.some((skill) => skill.name === args.invokedSkill?.name)
13532
+ );
13399
13533
  const commonToolRuntimeContext = {
13400
13534
  conversationId: args.conversationId,
13401
13535
  userText: args.userInput,
@@ -13476,6 +13610,7 @@ async function wireAgentTools(args) {
13476
13610
  if (await mcpToolManager.activateForSkill(effective)) {
13477
13611
  await args.recordConnectedMcpProvider(effective.pluginProvider);
13478
13612
  }
13613
+ await tryRecordSkillLoadStat(effective, args.spanContext);
13479
13614
  if (mcpAuth.getPendingPause()) {
13480
13615
  return void 0;
13481
13616
  }
@@ -13494,7 +13629,8 @@ async function wireAgentTools(args) {
13494
13629
  };
13495
13630
  }
13496
13631
  },
13497
- toolRuntimeContext
13632
+ toolRuntimeContext,
13633
+ { includeLoadSkill: !explicitSkillLoaded }
13498
13634
  );
13499
13635
  const plannedToolExposure = planToolExposure(
13500
13636
  tools
@@ -14541,6 +14677,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
14541
14677
  skillSandbox,
14542
14678
  syncLoadedSkillNamesForResume
14543
14679
  });
14680
+ const explicitSkill = invokedSkill ? activeSkills.find((skill) => skill.name === invokedSkill.name) ?? null : null;
14544
14681
  const { routerBlocks, userContentParts } = buildPromptInput(input);
14545
14682
  const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
14546
14683
  const storedTurnRoute = await loadTurnRoute({ conversationId, turnId });
@@ -14771,7 +14908,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
14771
14908
  conversationPrivacy,
14772
14909
  existingSessionPiMessages: existingSessionRecord?.piMessages,
14773
14910
  existingTurnStartMessageIndex: existingSessionRecord?.turnStartMessageIndex,
14774
- invocation: skillInvocation,
14911
+ explicitSkill,
14775
14912
  priorPiMessages,
14776
14913
  resumedFromSessionRecord,
14777
14914
  routing,