@sentry/junior 0.115.0 → 0.116.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 (111) hide show
  1. package/dist/{agent-hooks-4EUUXP2F.js → agent-hooks-GUAAS3XH.js} +9 -9
  2. package/dist/api/conversations/detail.d.ts +18 -0
  3. package/dist/api/conversations/event-page.d.ts +1 -1
  4. package/dist/api/conversations/events.d.ts +1 -1
  5. package/dist/api/people/profile.d.ts +2 -2
  6. package/dist/api/people/shared.d.ts +1 -1
  7. package/dist/api/personal-tokens/create.d.ts +11 -0
  8. package/dist/api/personal-tokens/list.d.ts +12 -0
  9. package/dist/api/personal-tokens/revoke.d.ts +5 -0
  10. package/dist/api/personal-tokens/routes.d.ts +4 -0
  11. package/dist/api/plugins.d.ts +0 -1
  12. package/dist/api/route.d.ts +1 -1
  13. package/dist/api/schema/conversation.d.ts +18 -0
  14. package/dist/api/schema/person.d.ts +3 -3
  15. package/dist/api/schema/personal-token.d.ts +38 -0
  16. package/dist/api/schema.d.ts +2 -0
  17. package/dist/api/schema.js +15 -3
  18. package/dist/api.d.ts +1 -0
  19. package/dist/api.js +179 -39
  20. package/dist/app.js +1035 -853
  21. package/dist/{catalog-runtime-Y32UEQLB.js → catalog-runtime-WMPC4J4U.js} +5 -5
  22. package/dist/chat/agent/request.d.ts +1 -0
  23. package/dist/chat/agent/resume.d.ts +1 -0
  24. package/dist/chat/agent/sandbox.d.ts +1 -0
  25. package/dist/chat/agent/tools.d.ts +1 -0
  26. package/dist/chat/agent-dispatch/context.d.ts +2 -0
  27. package/dist/chat/agent-dispatch/heartbeat.d.ts +11 -5
  28. package/dist/chat/agent-dispatch/signing.d.ts +14 -5
  29. package/dist/chat/agent-dispatch/store.d.ts +36 -15
  30. package/dist/chat/agent-dispatch/types.d.ts +23 -10
  31. package/dist/chat/agent-dispatch/work.d.ts +58 -0
  32. package/dist/chat/app/factory.d.ts +12 -3
  33. package/dist/chat/conversations/history.d.ts +285 -290
  34. package/dist/chat/conversations/projection.d.ts +2 -2
  35. package/dist/chat/mcp/tool-manager.d.ts +2 -1
  36. package/dist/chat/pi/conversation-events.d.ts +6 -2
  37. package/dist/chat/plugins/annotations.d.ts +8 -0
  38. package/dist/chat/plugins/registry.d.ts +0 -3
  39. package/dist/chat/plugins/types.d.ts +0 -2
  40. package/dist/chat/runtime/agent-continue-runner.d.ts +4 -0
  41. package/dist/chat/runtime/reply-executor.d.ts +6 -1
  42. package/dist/chat/runtime/slack-resume.d.ts +5 -1
  43. package/dist/chat/runtime/turn-input.d.ts +3 -0
  44. package/dist/chat/sandbox/sandbox.d.ts +2 -1
  45. package/dist/chat/sandbox/session.d.ts +2 -11
  46. package/dist/chat/sandbox/workspace.d.ts +10 -2
  47. package/dist/chat/services/turn-session-record.d.ts +14 -1
  48. package/dist/chat/slack/dispatch-turn.d.ts +25 -0
  49. package/dist/chat/state/turn-session.d.ts +11 -0
  50. package/dist/chat/tool-support/text-range-result.d.ts +3 -0
  51. package/dist/chat/tool-support/zod-tool.d.ts +1 -0
  52. package/dist/chat/tools/definition.d.ts +3 -10
  53. package/dist/chat/tools/query-conversation-events.d.ts +68 -0
  54. package/dist/chat/tools/sandbox/file-utils.d.ts +8 -1
  55. package/dist/chat/tools/sandbox/find-files.d.ts +2 -1
  56. package/dist/chat/tools/sandbox/grep.d.ts +2 -1
  57. package/dist/{chunk-6V762AB4.js → chunk-2YYPXQNQ.js} +1 -1
  58. package/dist/{chunk-MIAKQNYX.js → chunk-6ZK6FZIV.js} +1 -1
  59. package/dist/{chunk-67CVNDJ7.js → chunk-7CSVG54J.js} +1 -1
  60. package/dist/{chunk-HWCQRGLL.js → chunk-BFYVIOTS.js} +105 -37
  61. package/dist/{chunk-LYA2AB5G.js → chunk-CGXLCMWI.js} +8 -52
  62. package/dist/{chunk-SYJT7AKB.js → chunk-CY7NXM32.js} +1 -1
  63. package/dist/{chunk-2QG4MZQ7.js → chunk-DLLW5ITL.js} +2 -2
  64. package/dist/{chunk-NVUQSFWX.js → chunk-F3SWGSPG.js} +3 -15
  65. package/dist/{chunk-CWEZEPD4.js → chunk-FAEALXEH.js} +68 -2
  66. package/dist/{chunk-43RYMWJ2.js → chunk-GDAXWEIJ.js} +809 -213
  67. package/dist/{chunk-AGLQYELI.js → chunk-H35BDAUS.js} +170 -170
  68. package/dist/{chunk-YCLO7D7M.js → chunk-JPKGW6L6.js} +77 -26
  69. package/dist/{chunk-G5SX4UPR.js → chunk-KNCELO2M.js} +2 -2
  70. package/dist/{chunk-FHC4JUSF.js → chunk-N3C4XCRE.js} +1 -2
  71. package/dist/{chunk-6FKLPVF3.js → chunk-OY2PQQ4P.js} +1 -1
  72. package/dist/{chunk-6OPTF4BQ.js → chunk-PE44LDDQ.js} +0 -1
  73. package/dist/{chunk-JQFJO5WH.js → chunk-RXRQJF7Y.js} +74 -17
  74. package/dist/{chunk-CEYOL62C.js → chunk-RYFAVWP3.js} +120 -20
  75. package/dist/{chunk-G36V2YGB.js → chunk-TC4ZEM2D.js} +33 -1
  76. package/dist/{chunk-PVZHG2XZ.js → chunk-VBMMMVVQ.js} +112 -35
  77. package/dist/{chunk-SG5WAA7H.js → chunk-WKU7TY22.js} +0 -3
  78. package/dist/{chunk-TVE25LPW.js → chunk-WLT72MRP.js} +1 -1
  79. package/dist/{chunk-EW5MLKT5.js → chunk-XA3VLFPK.js} +1 -55
  80. package/dist/{chunk-RAVKCKXA.js → chunk-ZYHLNTC2.js} +20 -5
  81. package/dist/cli/chat.js +23 -23
  82. package/dist/cli/check.js +6 -6
  83. package/dist/cli/plugins.js +13 -13
  84. package/dist/cli/snapshot-warmup.js +8 -8
  85. package/dist/cli/upgrade.js +7 -7
  86. package/dist/db/schema/api-tokens.d.ts +160 -0
  87. package/dist/db/schema/conversation-annotations.d.ts +142 -0
  88. package/dist/db/schema.d.ts +305 -1
  89. package/dist/{db-KBSJHAZU.js → db-FH4R5ZQ4.js} +6 -6
  90. package/dist/handlers/agent-dispatch.d.ts +7 -3
  91. package/dist/instrumentation.js +2 -2
  92. package/dist/nitro.js +5 -5
  93. package/dist/{oauth-callback-server-EYRMJTCS.js → oauth-callback-server-C2SSJ3FQ.js} +18 -18
  94. package/dist/personal-tokens/store.d.ts +27 -0
  95. package/dist/{plugins-PZMDS7AT.js → plugins-IUJA4VBU.js} +1 -1
  96. package/dist/reporting-schema.d.ts +0 -2
  97. package/dist/{runner-RQGZ5Z2S.js → runner-NAALFIXM.js} +12 -12
  98. package/dist/{skills-JJQIE3BT.js → skills-UN2JXYYF.js} +6 -6
  99. package/dist/{validation-MZ47PZP5.js → validation-EEF4ROHR.js} +6 -6
  100. package/dist/version.js +1 -1
  101. package/migrations/0008_sticky_rage.sql +14 -0
  102. package/migrations/0009_slippery_harrier.sql +12 -0
  103. package/migrations/0010_native_conversation_history.sql +142 -0
  104. package/migrations/meta/0008_snapshot.json +1076 -0
  105. package/migrations/meta/0009_snapshot.json +1146 -0
  106. package/migrations/meta/0010_snapshot.json +1190 -0
  107. package/migrations/meta/_journal.json +22 -1
  108. package/package.json +5 -6
  109. package/dist/chat/agent-dispatch/runner.d.ts +0 -13
  110. package/dist/chat/capabilities/catalog.d.ts +0 -16
  111. package/dist/{chunk-KNFROR7R.js → chunk-5I6ORJUY.js} +1 -1
@@ -25,7 +25,7 @@ import {
25
25
  surfaceFromRouting,
26
26
  toolInvocationDestination,
27
27
  uploadFilesToConversation
28
- } from "./chunk-LYA2AB5G.js";
28
+ } from "./chunk-CGXLCMWI.js";
29
29
  import {
30
30
  CooperativeTurnYieldError,
31
31
  JUNIOR_THREAD_STATE_TTL_MS,
@@ -41,6 +41,7 @@ import {
41
41
  getPiMessageRole,
42
42
  getTerminalAssistantMessages,
43
43
  hasRuntimeTurnContext,
44
+ historyItemFromPiMessage,
44
45
  isAssistantMessage,
45
46
  isContinuablePiBoundary,
46
47
  isToolResultError,
@@ -57,6 +58,7 @@ import {
57
58
  persistContinuationSessionRecord,
58
59
  persistRunningSessionRecord,
59
60
  persistYieldSessionRecord,
61
+ piMessageFromHistoryItem,
60
62
  recordAuthorizationRequested,
61
63
  recordMcpProviderConnected,
62
64
  recordToolExecutionStarted,
@@ -65,7 +67,7 @@ import {
65
67
  stripRuntimeTurnContext,
66
68
  trimTrailingAssistantMessages,
67
69
  updateConversationStats
68
- } from "./chunk-CEYOL62C.js";
70
+ } from "./chunk-RYFAVWP3.js";
69
71
  import {
70
72
  parseSandboxEgressAuthRequiredSignal,
71
73
  parseSandboxEgressPermissionDeniedSignal,
@@ -73,6 +75,7 @@ import {
73
75
  sandboxEgressCredentialLeaseSchema
74
76
  } from "./chunk-HDSDCEPK.js";
75
77
  import {
78
+ bindSandboxFileSystem,
76
79
  buildNonInteractiveShellScript,
77
80
  createSandboxSession,
78
81
  getRuntimeDependencyProfileHash,
@@ -88,7 +91,7 @@ import {
88
91
  runNonInteractiveCommand,
89
92
  throwSandboxOperationError,
90
93
  wrapSandboxSetupError
91
- } from "./chunk-PVZHG2XZ.js";
94
+ } from "./chunk-VBMMMVVQ.js";
92
95
  import {
93
96
  addAgentTurnUsage,
94
97
  hasAgentTurnUsage
@@ -103,7 +106,7 @@ import {
103
106
  getPluginUserPromptContributions,
104
107
  getSlackToolContext,
105
108
  resolveChannelCapabilities
106
- } from "./chunk-RAVKCKXA.js";
109
+ } from "./chunk-ZYHLNTC2.js";
107
110
  import {
108
111
  SANDBOX_DATA_ROOT,
109
112
  SANDBOX_SKILLS_ROOT,
@@ -111,7 +114,7 @@ import {
111
114
  getStateAdapter,
112
115
  sandboxSkillDir,
113
116
  sandboxSkillFile
114
- } from "./chunk-67CVNDJ7.js";
117
+ } from "./chunk-7CSVG54J.js";
115
118
  import {
116
119
  SlackActionError,
117
120
  downloadPrivateSlackFile,
@@ -120,15 +123,16 @@ import {
120
123
  isConversationScopedChannel,
121
124
  normalizeSlackConversationId,
122
125
  withSlackRetries
123
- } from "./chunk-CWEZEPD4.js";
126
+ } from "./chunk-FAEALXEH.js";
124
127
  import {
128
+ KNOWN_CONVERSATION_EVENT_TYPES,
125
129
  contextProvenance,
126
130
  getConversationEventStore,
127
131
  getConversationSearchStore,
128
132
  getConversationStore,
129
133
  instructionActors,
130
134
  instructionProvenanceFor
131
- } from "./chunk-HWCQRGLL.js";
135
+ } from "./chunk-BFYVIOTS.js";
132
136
  import {
133
137
  sleep
134
138
  } from "./chunk-4ZNGQH7C.js";
@@ -161,8 +165,9 @@ import {
161
165
  renderCurrentInstruction,
162
166
  resolveGatewayModel,
163
167
  unwrapCurrentInstruction
164
- } from "./chunk-SYJT7AKB.js";
168
+ } from "./chunk-CY7NXM32.js";
165
169
  import {
170
+ canExposeConversationPayload,
166
171
  parseSlackThreadId,
167
172
  resolveConversationPrivacy,
168
173
  runWithConversationPrivacy,
@@ -177,15 +182,15 @@ import {
177
182
  findSkillByName,
178
183
  loadSkillsByName,
179
184
  parseSkillInvocation
180
- } from "./chunk-2QG4MZQ7.js";
185
+ } from "./chunk-DLLW5ITL.js";
181
186
  import {
182
187
  pluginCatalogRuntime
183
- } from "./chunk-6FKLPVF3.js";
188
+ } from "./chunk-OY2PQQ4P.js";
184
189
  import {
185
190
  CredentialUnavailableError,
186
191
  resolveAuthTokenPlaceholder,
187
192
  resolvePluginCommandEnv
188
- } from "./chunk-EW5MLKT5.js";
193
+ } from "./chunk-XA3VLFPK.js";
189
194
  import {
190
195
  credentialUserSubjectId
191
196
  } from "./chunk-W3U3YQYI.js";
@@ -209,7 +214,7 @@ import {
209
214
  summarizeMessageText,
210
215
  withLogContext,
211
216
  withSpan
212
- } from "./chunk-NVUQSFWX.js";
217
+ } from "./chunk-F3SWGSPG.js";
213
218
  import {
214
219
  parseSlackChannelId,
215
220
  parseSlackMessageTs,
@@ -2623,7 +2628,6 @@ ${usage}
2623
2628
  name: plugin.manifest.name,
2624
2629
  displayName: plugin.manifest.displayName,
2625
2630
  description: plugin.manifest.description,
2626
- capabilities: [...plugin.manifest.capabilities],
2627
2631
  configKeys: [...plugin.manifest.configKeys]
2628
2632
  })).sort((left, right) => left.name.localeCompare(right.name));
2629
2633
  return commandResult({
@@ -3020,7 +3024,6 @@ async function resolveSandboxCommandEnvironment() {
3020
3024
  // src/chat/sandbox/session.ts
3021
3025
  import { randomUUID as randomUUID2 } from "crypto";
3022
3026
  import { Sandbox } from "@vercel/sandbox";
3023
- import { createBashTool } from "bash-tool";
3024
3027
 
3025
3028
  // src/chat/sandbox/skill-sync.ts
3026
3029
  import fs3 from "fs/promises";
@@ -3195,36 +3198,8 @@ var SANDBOX_RUNTIME_BIN_DIR = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin`;
3195
3198
  var SNAPSHOT_BOOT_RETRY_COUNT = 3;
3196
3199
  var SNAPSHOT_BOOT_RETRY_DELAY_MS = 1e3;
3197
3200
  var SANDBOX_NAME_PREFIX = "junior-";
3198
- function createBashToolSandboxAdapter(sandbox) {
3199
- return {
3200
- async executeCommand(command) {
3201
- const result = await sandbox.runCommand({
3202
- cmd: "bash",
3203
- args: ["-c", command]
3204
- });
3205
- return {
3206
- stdout: result.stdout,
3207
- stderr: result.stderr,
3208
- exitCode: result.exitCode
3209
- };
3210
- },
3211
- async readFile(filePath) {
3212
- const content = await sandbox.readFileToBuffer({ path: filePath });
3213
- if (content == null) {
3214
- throw new Error(`File not found: ${filePath}`);
3215
- }
3216
- return content.toString("utf8");
3217
- },
3218
- async writeFiles(files) {
3219
- await sandbox.writeFiles(
3220
- files.map((file) => ({
3221
- path: file.path,
3222
- content: file.content
3223
- }))
3224
- );
3225
- }
3226
- };
3227
- }
3201
+ var MAX_KEEPALIVE_INTERVAL_MS = 3e4;
3202
+ var MIN_KEEPALIVE_INTERVAL_MS = 1e3;
3228
3203
  function truncateOutput(output, maxLength) {
3229
3204
  if (output.length <= maxLength) {
3230
3205
  return { value: output, truncated: false };
@@ -3261,6 +3236,8 @@ function createSandboxRuntime(options) {
3261
3236
  const availableSkills = [...options.skills];
3262
3237
  const availableReferenceFiles = [...options.referenceFiles];
3263
3238
  let acquiringSandbox;
3239
+ let keepAliveTimer;
3240
+ let closed = false;
3264
3241
  const timeoutMs = options.timeoutMs ?? 1e3 * 60 * 30;
3265
3242
  const traceContext = options.traceContext ?? {};
3266
3243
  const dependencyProfileHash = getRuntimeDependencyProfileHash(SANDBOX_RUNTIME);
@@ -3271,6 +3248,10 @@ function createSandboxRuntime(options) {
3271
3248
  return;
3272
3249
  }
3273
3250
  activeSandbox = null;
3251
+ if (keepAliveTimer) {
3252
+ clearTimeout(keepAliveTimer);
3253
+ keepAliveTimer = void 0;
3254
+ }
3274
3255
  };
3275
3256
  const adaptSandbox = (vercelSandbox) => createSandboxSession(vercelSandbox, {
3276
3257
  onUnavailable: invalidateSession
@@ -3640,24 +3621,37 @@ function createSandboxRuntime(options) {
3640
3621
  }
3641
3622
  }
3642
3623
  };
3643
- const buildToolExecutors = async (sandboxInstance) => {
3644
- const toolkit = await withSandboxSpan(
3645
- "sandbox.bash_tool.init",
3646
- "sandbox.tool.init",
3647
- {
3648
- "app.sandbox.tool_name": "bash",
3649
- "app.sandbox.destination": SANDBOX_WORKSPACE_ROOT
3650
- },
3651
- async () => await createBashTool({
3652
- sandbox: createBashToolSandboxAdapter(sandboxInstance),
3653
- destination: SANDBOX_WORKSPACE_ROOT
3654
- })
3655
- );
3656
- const executeReadFile = toolkit.tools.readFile.execute;
3657
- const executeWriteFile = toolkit.tools.writeFile.execute;
3658
- if (!executeReadFile || !executeWriteFile) {
3659
- throw new Error("bash-tool did not return executable tool handlers");
3624
+ const startKeepAlive = (session) => {
3625
+ const keepAliveMs = parseKeepAliveMs();
3626
+ if (keepAliveMs === 0 || closed || keepAliveTimer) {
3627
+ return;
3660
3628
  }
3629
+ const intervalMs = Math.max(
3630
+ MIN_KEEPALIVE_INTERVAL_MS,
3631
+ Math.min(MAX_KEEPALIVE_INTERVAL_MS, Math.floor(keepAliveMs / 2))
3632
+ );
3633
+ const schedule = () => {
3634
+ keepAliveTimer = setTimeout(async () => {
3635
+ keepAliveTimer = void 0;
3636
+ if (closed || activeSandbox?.session !== session) {
3637
+ return;
3638
+ }
3639
+ try {
3640
+ await extendKeepAlive(session);
3641
+ } catch {
3642
+ invalidateSession(session.sessionId);
3643
+ return;
3644
+ }
3645
+ if (closed || activeSandbox?.session !== session) {
3646
+ return;
3647
+ }
3648
+ schedule();
3649
+ }, intervalMs);
3650
+ keepAliveTimer.unref?.();
3651
+ };
3652
+ schedule();
3653
+ };
3654
+ const createToolExecutors = (sandboxInstance) => {
3661
3655
  return {
3662
3656
  sessionId: sandboxInstance.sessionId,
3663
3657
  bash: async (input) => {
@@ -3725,50 +3719,17 @@ function createSandboxRuntime(options) {
3725
3719
  }
3726
3720
  }
3727
3721
  },
3728
- readFile: async (input) => await executeReadFile(input, {
3729
- toolCallId: "sandbox-read-file",
3730
- messages: []
3731
- }),
3732
- writeFile: async (input) => await executeWriteFile(input, {
3733
- toolCallId: "sandbox-write-file",
3734
- messages: []
3735
- }),
3736
- fs: sandboxInstance.fs
3722
+ fs: sandboxInstance.fs,
3723
+ runCommand: async (input) => await sandboxInstance.runCommand(input)
3737
3724
  };
3738
3725
  };
3739
3726
  const ensureReadySandbox = async () => {
3740
3727
  const activeSandbox2 = await getOrAcquireSandbox();
3741
3728
  await probeSession(activeSandbox2);
3742
3729
  await extendKeepAlive(activeSandbox2);
3730
+ startKeepAlive(activeSandbox2);
3743
3731
  return activeSandbox2;
3744
3732
  };
3745
- const loadToolExecutors = async (session) => {
3746
- const active = activeSandbox;
3747
- if (!active || active.session !== session) {
3748
- throw new Error("sandbox session changed before tool initialization");
3749
- }
3750
- if (active.toolExecutors) {
3751
- return active.toolExecutors;
3752
- }
3753
- if (active.loadingToolExecutors) {
3754
- return await active.loadingToolExecutors;
3755
- }
3756
- let nextToolExecutors;
3757
- nextToolExecutors = buildToolExecutors(session).then((executors) => {
3758
- if (activeSandbox === active && active.loadingToolExecutors === nextToolExecutors) {
3759
- active.toolExecutors = executors;
3760
- }
3761
- return executors;
3762
- });
3763
- active.loadingToolExecutors = nextToolExecutors;
3764
- try {
3765
- return await nextToolExecutors;
3766
- } finally {
3767
- if (active.loadingToolExecutors === nextToolExecutors) {
3768
- active.loadingToolExecutors = void 0;
3769
- }
3770
- }
3771
- };
3772
3733
  return {
3773
3734
  sandboxRef() {
3774
3735
  return sandboxRef ? { ...sandboxRef } : void 0;
@@ -3777,7 +3738,7 @@ function createSandboxRuntime(options) {
3777
3738
  return await getOrAcquireSandbox();
3778
3739
  },
3779
3740
  async tools() {
3780
- return await loadToolExecutors(await ensureReadySandbox());
3741
+ return createToolExecutors(await ensureReadySandbox());
3781
3742
  },
3782
3743
  async refreshNetworkPolicy(traceHeaders) {
3783
3744
  const active = activeSandbox;
@@ -3785,6 +3746,13 @@ function createSandboxRuntime(options) {
3785
3746
  return;
3786
3747
  }
3787
3748
  await applyNetworkPolicy(active.session, traceHeaders);
3749
+ },
3750
+ close() {
3751
+ closed = true;
3752
+ if (keepAliveTimer) {
3753
+ clearTimeout(keepAliveTimer);
3754
+ keepAliveTimer = void 0;
3755
+ }
3788
3756
  }
3789
3757
  };
3790
3758
  }
@@ -3802,6 +3770,10 @@ var ToolInputError = class extends Error {
3802
3770
 
3803
3771
  // src/chat/tools/sandbox/file-utils.ts
3804
3772
  var MAX_TEXT_CHARS = 6e4;
3773
+ var RIPGREP_EXCLUDED_GLOBS = [
3774
+ "!**/.git/**",
3775
+ "!**/node_modules/**"
3776
+ ];
3805
3777
  var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
3806
3778
  function positiveInteger(value) {
3807
3779
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -3905,6 +3877,15 @@ function resolveWorkspacePath(input, fallback = ".") {
3905
3877
  }
3906
3878
  return normalized;
3907
3879
  }
3880
+ function getRipgrepSearchLocation(root, rootIsDirectory) {
3881
+ if (rootIsDirectory) {
3882
+ return { cwd: root, target: "." };
3883
+ }
3884
+ return {
3885
+ cwd: path4.posix.dirname(root),
3886
+ target: path4.posix.basename(root)
3887
+ };
3888
+ }
3908
3889
  async function collectFiles(params) {
3909
3890
  const files = [];
3910
3891
  let limitReached = false;
@@ -4387,6 +4368,16 @@ async function findFiles(params) {
4387
4368
  }
4388
4369
  const root = resolveWorkspacePath(params.path);
4389
4370
  const limit = positiveInteger(params.limit) ?? DEFAULT_FIND_LIMIT;
4371
+ if (params.runCommand) {
4372
+ return await findFilesWithRipgrep({
4373
+ fs: params.fs,
4374
+ limit,
4375
+ path: params.path,
4376
+ pattern: params.pattern,
4377
+ root,
4378
+ runCommand: params.runCommand
4379
+ });
4380
+ }
4390
4381
  const { files, limitReached, missingPath, missingRoot } = await collectFiles({
4391
4382
  fs: params.fs,
4392
4383
  root,
@@ -4435,6 +4426,80 @@ async function findFiles(params) {
4435
4426
  { content: [{ type: "text", text }] }
4436
4427
  );
4437
4428
  }
4429
+ async function findFilesWithRipgrep(params) {
4430
+ let rootIsDirectory;
4431
+ try {
4432
+ rootIsDirectory = (await params.fs.stat(params.root)).isDirectory();
4433
+ } catch (error) {
4434
+ if (isMissingPathError(error)) {
4435
+ return missingPathSearchResult({
4436
+ path: params.path ?? ".",
4437
+ displayPath: params.path ?? "."
4438
+ });
4439
+ }
4440
+ throw error;
4441
+ }
4442
+ const location = getRipgrepSearchLocation(params.root, rootIsDirectory);
4443
+ const args = [
4444
+ "--files",
4445
+ "--null",
4446
+ "--hidden",
4447
+ "--sort=path",
4448
+ "--glob",
4449
+ params.pattern
4450
+ ];
4451
+ for (const excludedGlob of RIPGREP_EXCLUDED_GLOBS) {
4452
+ args.push("--glob", excludedGlob);
4453
+ }
4454
+ args.push("--", location.target);
4455
+ const result = await params.runCommand({
4456
+ cmd: "rg",
4457
+ args,
4458
+ cwd: location.cwd
4459
+ });
4460
+ if (result.exitCode !== 0 && result.exitCode !== 1) {
4461
+ const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
4462
+ throw new Error(`ripgrep file search failed: ${detail}`);
4463
+ }
4464
+ const allPaths = result.stdout.split("\0").filter(Boolean).map((filePath) => {
4465
+ const absolute = filePath.startsWith("/") ? path5.posix.normalize(filePath) : path5.posix.resolve(location.cwd, filePath);
4466
+ return rootIsDirectory ? path5.posix.relative(params.root, absolute) : path5.posix.basename(absolute);
4467
+ });
4468
+ const limitReached = allPaths.length > params.limit;
4469
+ const relativePaths = allPaths.slice(0, params.limit);
4470
+ const bounded = truncateText(
4471
+ relativePaths.length > 0 ? relativePaths.join("\n") : "No files found matching pattern"
4472
+ );
4473
+ const notices = [];
4474
+ if (limitReached) {
4475
+ notices.push(
4476
+ `${params.limit} results limit reached. Refine pattern or raise limit.`
4477
+ );
4478
+ }
4479
+ if (bounded.truncated) {
4480
+ notices.push(`${MAX_TEXT_CHARS} character output limit reached.`);
4481
+ }
4482
+ const text = notices.length > 0 ? `${bounded.content}
4483
+
4484
+ [${notices.join(" ")}]` : bounded.content;
4485
+ return makeStructuredToolResult(
4486
+ {
4487
+ ok: true,
4488
+ status: "success",
4489
+ target: params.path ?? ".",
4490
+ path: params.path ?? ".",
4491
+ truncated: limitReached || bounded.truncated,
4492
+ data: {
4493
+ files: relativePaths,
4494
+ file_count: relativePaths.length,
4495
+ path: params.path ?? ".",
4496
+ ...notices.length > 0 ? { truncation_reasons: notices } : {}
4497
+ },
4498
+ ...limitReached ? { result_limit_reached: params.limit } : {}
4499
+ },
4500
+ { content: [{ type: "text", text }] }
4501
+ );
4502
+ }
4438
4503
  function createFindFilesTool() {
4439
4504
  return zodTool({
4440
4505
  description: "Find sandbox workspace files by glob pattern. Returns bounded paths relative to the search root and skips dependency/cache directories.",
@@ -4491,6 +4556,20 @@ async function grepFiles(params) {
4491
4556
  const root = resolveWorkspacePath(params.path);
4492
4557
  const limit = positiveInteger(params.limit) ?? DEFAULT_GREP_LIMIT;
4493
4558
  const context = positiveInteger(params.context) ?? 0;
4559
+ if (params.runCommand) {
4560
+ return await grepFilesWithRipgrep({
4561
+ context,
4562
+ fs: params.fs,
4563
+ glob: params.glob,
4564
+ ignoreCase: params.ignoreCase,
4565
+ limit,
4566
+ literal: params.literal,
4567
+ path: params.path,
4568
+ pattern: params.pattern,
4569
+ root,
4570
+ runCommand: params.runCommand
4571
+ });
4572
+ }
4494
4573
  let regex;
4495
4574
  if (!params.literal) {
4496
4575
  try {
@@ -4609,6 +4688,174 @@ async function grepFiles(params) {
4609
4688
  ...lineTruncated ? { line_truncated: true } : {}
4610
4689
  });
4611
4690
  }
4691
+ function decodeRipgrepText(value) {
4692
+ if (typeof value?.text === "string") {
4693
+ return value.text;
4694
+ }
4695
+ if (typeof value?.bytes === "string") {
4696
+ return Buffer.from(value.bytes, "base64").toString("utf8");
4697
+ }
4698
+ return "";
4699
+ }
4700
+ async function grepFilesWithRipgrep(params) {
4701
+ let rootIsDirectory;
4702
+ try {
4703
+ rootIsDirectory = (await params.fs.stat(params.root)).isDirectory();
4704
+ } catch (error) {
4705
+ if (isMissingPathError(error)) {
4706
+ return missingPathSearchResult({
4707
+ path: params.path ?? ".",
4708
+ displayPath: params.path ?? "."
4709
+ });
4710
+ }
4711
+ throw error;
4712
+ }
4713
+ const location = getRipgrepSearchLocation(params.root, rootIsDirectory);
4714
+ const args = ["--json", "--line-number", "--hidden", "--sort=path"];
4715
+ if (params.ignoreCase) {
4716
+ args.push("--ignore-case");
4717
+ }
4718
+ if (params.literal) {
4719
+ args.push("--fixed-strings");
4720
+ }
4721
+ if (params.glob) {
4722
+ args.push("--glob", params.glob);
4723
+ }
4724
+ for (const excludedGlob of RIPGREP_EXCLUDED_GLOBS) {
4725
+ args.push("--glob", excludedGlob);
4726
+ }
4727
+ if (params.context > 0) {
4728
+ args.push("--context", String(params.context));
4729
+ }
4730
+ args.push("--", params.pattern, location.target);
4731
+ const result = await params.runCommand({
4732
+ cmd: "rg",
4733
+ args,
4734
+ cwd: location.cwd
4735
+ });
4736
+ if (result.exitCode !== 0 && result.exitCode !== 1) {
4737
+ const detail = result.stderr.trim() || result.stdout.trim() || "command failed";
4738
+ if (/regex parse error|error parsing regex/i.test(detail)) {
4739
+ throw new ToolInputError(`Invalid regex pattern: ${params.pattern}`, {
4740
+ cause: new Error(detail)
4741
+ });
4742
+ }
4743
+ throw new Error(`ripgrep search failed: ${detail}`);
4744
+ }
4745
+ const recordsByPath = /* @__PURE__ */ new Map();
4746
+ const pathOrder = [];
4747
+ let totalMatches = 0;
4748
+ for (const rawLine of normalizeToLf(result.stdout).split("\n")) {
4749
+ if (!rawLine) {
4750
+ continue;
4751
+ }
4752
+ let record;
4753
+ try {
4754
+ record = JSON.parse(rawLine);
4755
+ } catch (error) {
4756
+ throw new Error("ripgrep returned invalid JSON output", { cause: error });
4757
+ }
4758
+ if (record.type !== "match" && record.type !== "context") {
4759
+ continue;
4760
+ }
4761
+ const line = record.data?.line_number;
4762
+ const rawPath = decodeRipgrepText(record.data?.path);
4763
+ if (!line || !rawPath) {
4764
+ continue;
4765
+ }
4766
+ const absolutePath = rawPath.startsWith("/") ? path6.posix.normalize(rawPath) : path6.posix.resolve(location.cwd, rawPath);
4767
+ const displayPath = rootIsDirectory ? path6.posix.relative(params.root, absolutePath) : path6.posix.basename(absolutePath);
4768
+ let records = recordsByPath.get(displayPath);
4769
+ if (!records) {
4770
+ records = [];
4771
+ recordsByPath.set(displayPath, records);
4772
+ pathOrder.push(displayPath);
4773
+ }
4774
+ const matched = record.type === "match";
4775
+ if (matched) {
4776
+ totalMatches += 1;
4777
+ }
4778
+ records.push({
4779
+ line,
4780
+ matched,
4781
+ text: decodeRipgrepText(record.data?.lines).replace(/\r?\n$/, "")
4782
+ });
4783
+ }
4784
+ let remainingMatches = params.limit;
4785
+ const output = [];
4786
+ let lineTruncated = false;
4787
+ for (const displayPath of pathOrder) {
4788
+ const records = recordsByPath.get(displayPath) ?? [];
4789
+ const selectedMatchLines = [];
4790
+ for (const record of records) {
4791
+ if (record.matched && remainingMatches > 0) {
4792
+ selectedMatchLines.push(record.line);
4793
+ remainingMatches -= 1;
4794
+ }
4795
+ }
4796
+ if (selectedMatchLines.length === 0) {
4797
+ continue;
4798
+ }
4799
+ const selectedMatchSet = new Set(selectedMatchLines);
4800
+ const emittedLines = /* @__PURE__ */ new Set();
4801
+ for (const record of records) {
4802
+ if (emittedLines.has(record.line)) {
4803
+ continue;
4804
+ }
4805
+ const include = selectedMatchLines.some(
4806
+ (matchLine) => Math.abs(record.line - matchLine) <= params.context
4807
+ );
4808
+ if (!include) {
4809
+ continue;
4810
+ }
4811
+ emittedLines.add(record.line);
4812
+ const truncated = truncateGrepLine(record.text);
4813
+ lineTruncated ||= truncated.truncated;
4814
+ const separator = selectedMatchSet.has(record.line) ? ":" : "-";
4815
+ output.push(
4816
+ `${displayPath}${separator}${record.line}${separator} ${truncated.line}`
4817
+ );
4818
+ }
4819
+ }
4820
+ const matchCount = Math.min(totalMatches, params.limit);
4821
+ const matchLimitReached = totalMatches > params.limit;
4822
+ const bounded = truncateText(
4823
+ output.length > 0 ? output.join("\n") : "No matches found"
4824
+ );
4825
+ const notices = [];
4826
+ if (matchLimitReached) {
4827
+ notices.push(
4828
+ `${params.limit} matches limit reached. Refine pattern or raise limit.`
4829
+ );
4830
+ }
4831
+ if (lineTruncated) {
4832
+ notices.push(
4833
+ `Some lines were truncated to ${MAX_GREP_LINE_CHARS} characters.`
4834
+ );
4835
+ }
4836
+ if (bounded.truncated) {
4837
+ notices.push(`${MAX_TEXT_CHARS} character output limit reached.`);
4838
+ }
4839
+ return makeStructuredToolResult({
4840
+ ok: true,
4841
+ status: "success",
4842
+ target: params.path ?? ".",
4843
+ path: params.path ?? ".",
4844
+ truncated: matchLimitReached || lineTruncated || bounded.truncated,
4845
+ data: {
4846
+ context: params.context,
4847
+ ...params.glob ? { glob: params.glob } : {},
4848
+ line_count: output.length,
4849
+ lines: bounded.content === "No matches found" ? [] : bounded.content.split("\n"),
4850
+ match_count: matchCount,
4851
+ pattern: params.pattern,
4852
+ path: params.path ?? ".",
4853
+ ...notices.length > 0 ? { truncation_reasons: notices } : {}
4854
+ },
4855
+ ...matchLimitReached ? { match_limit_reached: params.limit } : {},
4856
+ ...lineTruncated ? { line_truncated: true } : {}
4857
+ });
4858
+ }
4612
4859
  function createGrepTool() {
4613
4860
  return zodTool({
4614
4861
  description: "Search sandbox workspace file contents. Returns bounded matching lines with file paths and line numbers. Respects path/glob filters and includes truncation notices.",
@@ -4738,6 +4985,7 @@ import { z as z8 } from "zod";
4738
4985
 
4739
4986
  // src/chat/tool-support/text-range-result.ts
4740
4987
  var DEFAULT_READ_LIMIT = 1e3;
4988
+ var MAX_READ_CHARS = 6e4;
4741
4989
  function positiveInteger2(value) {
4742
4990
  if (typeof value !== "number" || !Number.isFinite(value)) {
4743
4991
  return void 0;
@@ -4757,16 +5005,42 @@ function sliceFileContent(params) {
4757
5005
  const maxLines = requestedLimit ?? DEFAULT_READ_LIMIT;
4758
5006
  const startIndex = Math.min(lines.length, startLine - 1);
4759
5007
  const selected = lines.slice(startIndex, startIndex + maxLines);
4760
- const endLine = selected.length > 0 ? startLine + selected.length - 1 : startLine - 1;
4761
- const truncated = startIndex > 0 || endLine < lines.length;
5008
+ const returnedLines = [];
5009
+ let returnedCharacters = 0;
5010
+ let characterLimitReached = false;
5011
+ let lineTruncated = false;
5012
+ for (const line of selected) {
5013
+ const separatorLength = returnedLines.length > 0 ? 1 : 0;
5014
+ if (returnedCharacters + separatorLength + line.length <= MAX_READ_CHARS) {
5015
+ returnedLines.push(line);
5016
+ returnedCharacters += separatorLength + line.length;
5017
+ continue;
5018
+ }
5019
+ characterLimitReached = true;
5020
+ if (returnedLines.length === 0) {
5021
+ returnedLines.push(line.slice(0, MAX_READ_CHARS));
5022
+ lineTruncated = true;
5023
+ }
5024
+ break;
5025
+ }
5026
+ const endLine = returnedLines.length > 0 ? startLine + returnedLines.length - 1 : startLine - 1;
5027
+ const truncated = startIndex > 0 || endLine < lines.length || characterLimitReached;
4762
5028
  const rangeRequested = requestedOffset !== void 0 || requestedLimit !== void 0;
4763
- const returnedContent = !rangeRequested && !truncated ? params.content : selected.join("\n");
5029
+ const returnedContent = !rangeRequested && !truncated ? params.content : returnedLines.join("\n");
5030
+ const truncationReasons = [];
5031
+ if (characterLimitReached) {
5032
+ truncationReasons.push(`${MAX_READ_CHARS} character output limit reached.`);
5033
+ }
5034
+ if (lineTruncated) {
5035
+ truncationReasons.push(`Line ${startLine} was truncated.`);
5036
+ }
4764
5037
  const range = {
4765
5038
  content: returnedContent,
4766
5039
  end_line: selected.length > 0 ? endLine : void 0,
4767
5040
  path: params.path,
4768
5041
  start_line: startLine,
4769
5042
  total_lines: lines.length,
5043
+ ...truncationReasons.length > 0 ? { truncation_reasons: truncationReasons } : {},
4770
5044
  truncated
4771
5045
  };
4772
5046
  return makeStructuredToolResult({
@@ -4775,6 +5049,8 @@ function sliceFileContent(params) {
4775
5049
  target: params.path,
4776
5050
  data: range,
4777
5051
  truncated,
5052
+ ...characterLimitReached ? { character_limit_reached: MAX_READ_CHARS } : {},
5053
+ ...lineTruncated ? { line_truncated: true } : {},
4778
5054
  ...endLine < lines.length ? {
4779
5055
  continuation: {
4780
5056
  arguments: {
@@ -4782,7 +5058,7 @@ function sliceFileContent(params) {
4782
5058
  offset: endLine + 1,
4783
5059
  limit: maxLines
4784
5060
  },
4785
- reason: "file has more lines"
5061
+ reason: characterLimitReached ? "character output limit reached; file has more lines" : "file has more lines"
4786
5062
  }
4787
5063
  } : {}
4788
5064
  });
@@ -4918,6 +5194,30 @@ function createSandbox(options) {
4918
5194
  onSandboxPrepare: options.prepare,
4919
5195
  onSandboxRefChanged: options.onSandboxRefChanged
4920
5196
  });
5197
+ const createToolCallContext = (signal) => {
5198
+ let fileSystemPromise;
5199
+ let commandRunnerPromise;
5200
+ return {
5201
+ signal,
5202
+ fileSystem() {
5203
+ signal?.throwIfAborted();
5204
+ return fileSystemPromise ??= runtime.tools().then(({ fs: fs5 }) => {
5205
+ signal?.throwIfAborted();
5206
+ return bindSandboxFileSystem(fs5, signal);
5207
+ });
5208
+ },
5209
+ commandRunner() {
5210
+ signal?.throwIfAborted();
5211
+ return commandRunnerPromise ??= runtime.tools().then(({ runCommand }) => {
5212
+ signal?.throwIfAborted();
5213
+ return async (input) => await runCommand({
5214
+ ...input,
5215
+ ...signal ? { signal } : {}
5216
+ });
5217
+ });
5218
+ }
5219
+ };
5220
+ };
4921
5221
  const withSandboxSpan = (name, op, attributes, callback) => withSpan(name, op, traceContext, callback, attributes);
4922
5222
  const withSandboxToolSpan = (name, op, attributes, callback) => withSandboxSpan(name, op, attributes, async () => {
4923
5223
  await runtime.refreshNetworkPolicy(getTracePropagationHeaders());
@@ -4937,7 +5237,7 @@ function createSandbox(options) {
4937
5237
  "Sandbox boot requested"
4938
5238
  );
4939
5239
  };
4940
- const executeBashTool = async (rawInput, command, signal) => {
5240
+ const executeBashTool = async (rawInput, command, context) => {
4941
5241
  const env = parseEnv(rawInput.env);
4942
5242
  const timeoutMs = positiveInteger(rawInput.timeoutMs);
4943
5243
  logSandboxBootRequest("tool.bash", {
@@ -4957,7 +5257,7 @@ function createSandbox(options) {
4957
5257
  command,
4958
5258
  ...env ? { env } : {},
4959
5259
  ...timeoutMs ? { timeoutMs } : {},
4960
- ...signal ? { signal } : {}
5260
+ ...context.signal ? { signal: context.signal } : {}
4961
5261
  });
4962
5262
  setSpanAttributes({
4963
5263
  "process.exit.code": response.exitCode,
@@ -4999,7 +5299,7 @@ function createSandbox(options) {
4999
5299
  ...permissionDenied ? { permission_denied: permissionDenied } : {}
5000
5300
  });
5001
5301
  };
5002
- const executeReadFileTool = async (rawInput) => {
5302
+ const executeReadFileTool = async (rawInput, context) => {
5003
5303
  const filePath = String(rawInput.path ?? "").trim();
5004
5304
  if (!filePath) {
5005
5305
  throw new ToolInputError("path is required");
@@ -5010,7 +5310,10 @@ function createSandbox(options) {
5010
5310
  const hostPath = resolveHostSkillPath(options.skills, filePath) ?? resolveHostDataPath(options.referenceFiles, filePath);
5011
5311
  if (hostPath) {
5012
5312
  try {
5013
- const content = await fs4.readFile(hostPath, "utf8");
5313
+ const content = await fs4.readFile(hostPath, {
5314
+ encoding: "utf8",
5315
+ ...context.signal ? { signal: context.signal } : {}
5316
+ });
5014
5317
  setSpanAttributes({
5015
5318
  "app.sandbox.path.length": filePath.length,
5016
5319
  "app.sandbox.read.bytes": Buffer.byteLength(content, "utf8"),
@@ -5034,7 +5337,7 @@ function createSandbox(options) {
5034
5337
  logSandboxBootRequest("tool.readFile", {
5035
5338
  "file.path": filePath
5036
5339
  });
5037
- const executeReadFile = (await runtime.tools()).readFile;
5340
+ const fileSystem = await context.fileSystem();
5038
5341
  const result = await withSandboxToolSpan(
5039
5342
  "sandbox.readFile",
5040
5343
  "sandbox.fs.read",
@@ -5044,7 +5347,11 @@ function createSandbox(options) {
5044
5347
  async () => {
5045
5348
  let response;
5046
5349
  try {
5047
- response = await executeReadFile({ path: filePath });
5350
+ response = {
5351
+ content: await fileSystem.readFile(resolveWorkspacePath(filePath), {
5352
+ encoding: "utf8"
5353
+ })
5354
+ };
5048
5355
  } catch (error) {
5049
5356
  if (isMissingPathError(error)) {
5050
5357
  setSpanStatus("ok");
@@ -5070,16 +5377,17 @@ function createSandbox(options) {
5070
5377
  );
5071
5378
  return result;
5072
5379
  };
5073
- const executeWriteFileTool = async (rawInput) => {
5380
+ const executeWriteFileTool = async (rawInput, context) => {
5074
5381
  const filePath = String(rawInput.path ?? "").trim();
5075
5382
  if (!filePath) {
5076
5383
  throw new ToolInputError("path is required");
5077
5384
  }
5385
+ const workspacePath = resolveWorkspacePath(filePath);
5078
5386
  const content = String(rawInput.content ?? "");
5079
5387
  logSandboxBootRequest("tool.writeFile", {
5080
5388
  "file.path": filePath
5081
5389
  });
5082
- const executeWriteFile = (await runtime.tools()).writeFile;
5390
+ const fileSystem = await context.fileSystem();
5083
5391
  await withSandboxToolSpan(
5084
5392
  "sandbox.writeFile",
5085
5393
  "sandbox.fs.write",
@@ -5089,8 +5397,13 @@ function createSandbox(options) {
5089
5397
  },
5090
5398
  async () => {
5091
5399
  try {
5092
- await executeWriteFile({ path: filePath, content });
5400
+ await fileSystem.writeFile(workspacePath, content, {
5401
+ encoding: "utf8"
5402
+ });
5093
5403
  } catch (error) {
5404
+ if (context.signal?.aborted) {
5405
+ throw error;
5406
+ }
5094
5407
  throwSandboxOperationError("sandbox writeFile", error);
5095
5408
  }
5096
5409
  setSpanStatus("ok");
@@ -5108,7 +5421,7 @@ function createSandbox(options) {
5108
5421
  bytes_written: Buffer.byteLength(content, "utf8")
5109
5422
  });
5110
5423
  };
5111
- const executeEditFileTool = async (rawInput) => {
5424
+ const executeEditFileTool = async (rawInput, context) => {
5112
5425
  const filePath = String(rawInput.path ?? "").trim();
5113
5426
  if (!filePath) {
5114
5427
  throw new ToolInputError("path is required");
@@ -5119,7 +5432,7 @@ function createSandbox(options) {
5119
5432
  logSandboxBootRequest("tool.editFile", {
5120
5433
  "file.path": filePath
5121
5434
  });
5122
- const executors = await runtime.tools();
5435
+ const fileSystem = await context.fileSystem();
5123
5436
  const result = await withSandboxToolSpan(
5124
5437
  "sandbox.editFile",
5125
5438
  "sandbox.fs.edit",
@@ -5129,7 +5442,7 @@ function createSandbox(options) {
5129
5442
  },
5130
5443
  async () => {
5131
5444
  const response = await editFile({
5132
- fs: executors.fs,
5445
+ fs: fileSystem,
5133
5446
  path: filePath,
5134
5447
  edits: rawInput.edits
5135
5448
  });
@@ -5139,7 +5452,7 @@ function createSandbox(options) {
5139
5452
  );
5140
5453
  return result;
5141
5454
  };
5142
- const executeGrepTool = async (rawInput) => {
5455
+ const executeGrepTool = async (rawInput, context) => {
5143
5456
  const pattern = String(rawInput.pattern ?? "");
5144
5457
  if (!pattern) {
5145
5458
  throw new ToolInputError("pattern is required");
@@ -5147,7 +5460,7 @@ function createSandbox(options) {
5147
5460
  logSandboxBootRequest("tool.grep");
5148
5461
  const contextLines = positiveInteger(rawInput.context);
5149
5462
  const limit = positiveInteger(rawInput.limit);
5150
- const executors = await runtime.tools();
5463
+ const fileSystem = await context.fileSystem();
5151
5464
  const result = await withSandboxToolSpan(
5152
5465
  "sandbox.grep",
5153
5466
  "sandbox.fs.search",
@@ -5156,7 +5469,8 @@ function createSandbox(options) {
5156
5469
  },
5157
5470
  async () => {
5158
5471
  const response = await grepFiles({
5159
- fs: executors.fs,
5472
+ fs: fileSystem,
5473
+ runCommand: await context.commandRunner(),
5160
5474
  pattern,
5161
5475
  ...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
5162
5476
  ...typeof rawInput.glob === "string" ? { glob: rawInput.glob } : {},
@@ -5171,14 +5485,14 @@ function createSandbox(options) {
5171
5485
  );
5172
5486
  return result;
5173
5487
  };
5174
- const executeFindFilesTool = async (rawInput) => {
5488
+ const executeFindFilesTool = async (rawInput, context) => {
5175
5489
  const pattern = String(rawInput.pattern ?? "");
5176
5490
  if (!pattern) {
5177
5491
  throw new ToolInputError("pattern is required");
5178
5492
  }
5179
5493
  logSandboxBootRequest("tool.findFiles");
5180
5494
  const limit = positiveInteger(rawInput.limit);
5181
- const executors = await runtime.tools();
5495
+ const fileSystem = await context.fileSystem();
5182
5496
  const result = await withSandboxToolSpan(
5183
5497
  "sandbox.findFiles",
5184
5498
  "sandbox.fs.find",
@@ -5187,7 +5501,8 @@ function createSandbox(options) {
5187
5501
  },
5188
5502
  async () => {
5189
5503
  const response = await findFiles({
5190
- fs: executors.fs,
5504
+ fs: fileSystem,
5505
+ runCommand: await context.commandRunner(),
5191
5506
  pattern,
5192
5507
  ...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
5193
5508
  ...limit ? { limit } : {}
@@ -5198,17 +5513,17 @@ function createSandbox(options) {
5198
5513
  );
5199
5514
  return result;
5200
5515
  };
5201
- const executeListDirTool = async (rawInput) => {
5516
+ const executeListDirTool = async (rawInput, context) => {
5202
5517
  logSandboxBootRequest("tool.listDir");
5203
5518
  const limit = positiveInteger(rawInput.limit);
5204
- const executors = await runtime.tools();
5519
+ const fileSystem = await context.fileSystem();
5205
5520
  const result = await withSandboxToolSpan(
5206
5521
  "sandbox.listDir",
5207
5522
  "sandbox.fs.list",
5208
5523
  {},
5209
5524
  async () => {
5210
5525
  const response = await listDir({
5211
- fs: executors.fs,
5526
+ fs: fileSystem,
5212
5527
  ...typeof rawInput.path === "string" ? { path: rawInput.path } : {},
5213
5528
  ...limit ? { limit } : {}
5214
5529
  });
@@ -5220,6 +5535,7 @@ function createSandbox(options) {
5220
5535
  };
5221
5536
  const execute = async (params) => {
5222
5537
  const rawInput = params.input ?? {};
5538
+ const context = createToolCallContext(params.signal);
5223
5539
  const bashCommand2 = params.toolName === "bash" ? String(rawInput.command ?? "").trim() : void 0;
5224
5540
  if (params.toolName === "bash") {
5225
5541
  if (!bashCommand2) {
@@ -5228,27 +5544,28 @@ function createSandbox(options) {
5228
5544
  }
5229
5545
  try {
5230
5546
  if (bashCommand2 !== void 0) {
5231
- return await executeBashTool(rawInput, bashCommand2, params.signal);
5547
+ return await executeBashTool(rawInput, bashCommand2, context);
5232
5548
  }
5233
5549
  if (params.toolName === "readFile") {
5234
- return await executeReadFileTool(rawInput);
5550
+ return await executeReadFileTool(rawInput, context);
5235
5551
  }
5236
5552
  if (params.toolName === "editFile") {
5237
- return await executeEditFileTool(rawInput);
5553
+ return await executeEditFileTool(rawInput, context);
5238
5554
  }
5239
5555
  if (params.toolName === "grep") {
5240
- return await executeGrepTool(rawInput);
5556
+ return await executeGrepTool(rawInput, context);
5241
5557
  }
5242
5558
  if (params.toolName === "findFiles") {
5243
- return await executeFindFilesTool(rawInput);
5559
+ return await executeFindFilesTool(rawInput, context);
5244
5560
  }
5245
5561
  if (params.toolName === "listDir") {
5246
- return await executeListDirTool(rawInput);
5562
+ return await executeListDirTool(rawInput, context);
5247
5563
  }
5248
5564
  if (params.toolName === "writeFile") {
5249
- return await executeWriteFileTool(rawInput);
5565
+ return await executeWriteFileTool(rawInput, context);
5250
5566
  }
5251
5567
  } catch (error) {
5568
+ params.signal?.throwIfAborted();
5252
5569
  if (isSandboxUnavailableError(error)) {
5253
5570
  throw createSandboxUnavailableToolError(params.toolName, error);
5254
5571
  }
@@ -5295,6 +5612,9 @@ function createSandbox(options) {
5295
5612
  },
5296
5613
  sandboxRef() {
5297
5614
  return runtime.sandboxRef();
5615
+ },
5616
+ close() {
5617
+ runtime.close();
5298
5618
  }
5299
5619
  };
5300
5620
  }
@@ -5494,6 +5814,7 @@ function createAgentSandbox(options) {
5494
5814
  return {
5495
5815
  workspace: sandbox.workspace,
5496
5816
  sandboxRef: sandbox.sandboxRef,
5817
+ close: sandbox.close,
5497
5818
  tools: {
5498
5819
  supports: sandbox.tools.supports,
5499
5820
  async execute(params) {
@@ -6077,7 +6398,7 @@ var McpToolManager = class {
6077
6398
  }
6078
6399
  toManagedTool(plugin, client, tool) {
6079
6400
  const outputSchema = toOptionalRecord(tool.outputSchema);
6080
- const annotations = toOptionalRecord(tool.annotations);
6401
+ const annotations = tool.annotations;
6081
6402
  return {
6082
6403
  name: normalizeMcpToolName(plugin.manifest.name, tool.name),
6083
6404
  description: describeMcpTool(plugin.manifest.name, tool),
@@ -6224,7 +6545,7 @@ function serializeMcpPayload(payload, privacy, options = {}) {
6224
6545
 
6225
6546
  // src/chat/tools/sandbox/bash.ts
6226
6547
  import { z as z10 } from "zod";
6227
- function createBashTool2() {
6548
+ function createBashTool() {
6228
6549
  return zodTool({
6229
6550
  description: "Run a bash command inside the isolated sandbox workspace. Use this for repository inspection/execution tasks that need shell access. Do not use for network-sensitive or destructive actions unless explicitly required.",
6230
6551
  inputSchema: z10.object({
@@ -9646,11 +9967,249 @@ function createSystemTimeTool() {
9646
9967
  });
9647
9968
  }
9648
9969
 
9649
- // src/chat/tools/handoff/tool.ts
9970
+ // src/chat/tools/query-conversation-events.ts
9650
9971
  import { z as z36 } from "zod";
9972
+ var DEFAULT_LIMIT3 = 25;
9973
+ var MAX_LIMIT = 100;
9974
+ var CONVERSATION_EVENTS_TOOL_SOURCE = {
9975
+ id: "conversation-events",
9976
+ description: "Inspect Junior's durable conversation event log for debugging runtime behavior."
9977
+ };
9978
+ var knownEventTypeSchema = z36.enum(KNOWN_CONVERSATION_EVENT_TYPES);
9979
+ var projectedEventSchema = z36.object({
9980
+ seq: z36.number().int().nonnegative().describe("Stable event position; use it for pagination bounds."),
9981
+ history_version: z36.number().int().nonnegative().describe(
9982
+ "Model-history generation. It increments when handoff or compaction replaces active model context."
9983
+ ),
9984
+ created_at: z36.iso.datetime().describe("When the event was recorded."),
9985
+ idempotency_key: z36.string().min(1).optional().describe("Retry-stable event identity, when one was assigned."),
9986
+ data: z36.record(z36.string(), z36.unknown()).describe(
9987
+ "Event payload. The type field identifies the event; replacement-history events report replacement_history_count instead of replaying the full model context."
9988
+ )
9989
+ }).strict();
9990
+ var queryConversationEventsOutputSchema = juniorToolResultSchema.extend({
9991
+ conversation_id: z36.string().min(1),
9992
+ events: z36.array(projectedEventSchema),
9993
+ has_older: z36.boolean().describe(
9994
+ "Whether matching events exist before this page; request them with before_seq set to the first returned seq."
9995
+ ),
9996
+ has_newer: z36.boolean().describe(
9997
+ "Whether matching events exist after this page; request them with after_seq set to the last returned seq."
9998
+ )
9999
+ });
10000
+ function createQueryConversationEventsTool(context) {
10001
+ return zodTool({
10002
+ description: "Inspect Junior's stored turns, tool calls, handoffs, and compaction events when debugging its behavior. Returns events from the current conversation tree or another retained public conversation in the same Slack workspace, newest first by default.",
10003
+ exposure: "deferred",
10004
+ source: CONVERSATION_EVENTS_TOOL_SOURCE,
10005
+ annotations: { readOnlyHint: true, destructiveHint: false },
10006
+ inputSchema: z36.object({
10007
+ conversation_id: z36.string().trim().min(1).describe(
10008
+ "Conversation ID to inspect, such as slack:{channelId}:{threadTs} or a child conversation id."
10009
+ ),
10010
+ after_seq: z36.number().int().nonnegative().nullable().describe(
10011
+ "Exclusive lower bound on event seq. When set, returns the next newer page in ascending order."
10012
+ ).optional(),
10013
+ before_seq: z36.number().int().nonnegative().nullable().describe(
10014
+ "Exclusive upper bound on event seq. Combine with defaults or after_seq to page older or bounded ranges."
10015
+ ).optional(),
10016
+ limit: z36.number().int().min(1).max(MAX_LIMIT).nullable().describe(
10017
+ `Maximum events to return. Defaults to ${DEFAULT_LIMIT3}; max ${MAX_LIMIT}.`
10018
+ ).optional(),
10019
+ types: z36.array(knownEventTypeSchema).min(1).nullable().describe(
10020
+ "Optional event-type filter. Omit to return every durable event type."
10021
+ ).optional()
10022
+ }).strict(),
10023
+ outputSchema: queryConversationEventsOutputSchema,
10024
+ execute: async (input) => {
10025
+ const currentConversationId = context.conversationId?.trim();
10026
+ if (!currentConversationId) {
10027
+ throw new ToolInputError(
10028
+ "queryConversationEvents requires an active conversation"
10029
+ );
10030
+ }
10031
+ const conversationId = input.conversation_id;
10032
+ const afterSeq = input.after_seq ?? void 0;
10033
+ const beforeSeq = input.before_seq ?? void 0;
10034
+ const limit = input.limit ?? DEFAULT_LIMIT3;
10035
+ const types = input.types ?? void 0;
10036
+ if (afterSeq !== void 0 && beforeSeq !== void 0 && afterSeq >= beforeSeq) {
10037
+ throw new ToolInputError(
10038
+ "after_seq must be less than before_seq when both are provided"
10039
+ );
10040
+ }
10041
+ const conversationStore = getConversationStore();
10042
+ const target = await conversationStore.get({ conversationId });
10043
+ if (!target) {
10044
+ throw new ToolInputError(`Conversation not found: ${conversationId}`);
10045
+ }
10046
+ if (target.transcriptPurgedAtMs !== void 0) {
10047
+ throw new ToolInputError(
10048
+ `Conversation transcript was purged: ${conversationId}`
10049
+ );
10050
+ }
10051
+ const accessScope = await resolveAccessScope(
10052
+ conversationStore,
10053
+ context,
10054
+ currentConversationId
10055
+ );
10056
+ const targetRootConversationId = await resolveRootConversationId(
10057
+ conversationStore,
10058
+ conversationId,
10059
+ target.lineage?.parentConversationId
10060
+ );
10061
+ const targetRoot = targetRootConversationId === conversationId ? target : await conversationStore.get({
10062
+ conversationId: targetRootConversationId
10063
+ });
10064
+ assertCanQueryConversationEvents({
10065
+ accessScope,
10066
+ targetConversationId: conversationId,
10067
+ targetRootConversationId,
10068
+ targetVisibility: targetRoot?.visibility ?? target.visibility,
10069
+ targetDestination: targetRoot?.destination ?? target.destination
10070
+ });
10071
+ const page = await getConversationEventStore().query(conversationId, {
10072
+ limit,
10073
+ ...afterSeq === void 0 ? {} : { afterSeq },
10074
+ ...beforeSeq === void 0 ? {} : { beforeSeq },
10075
+ ...types === void 0 ? {} : { types }
10076
+ });
10077
+ const events = projectEventsForTool(page);
10078
+ return {
10079
+ ok: true,
10080
+ status: "success",
10081
+ conversation_id: conversationId,
10082
+ events,
10083
+ has_older: page.hasOlder,
10084
+ has_newer: page.hasNewer
10085
+ };
10086
+ }
10087
+ });
10088
+ }
10089
+ async function resolveAccessScope(conversationStore, context, currentConversationId) {
10090
+ const current = await conversationStore.get({
10091
+ conversationId: currentConversationId
10092
+ });
10093
+ const currentRootConversationId = current ? await resolveRootConversationId(
10094
+ conversationStore,
10095
+ currentConversationId,
10096
+ current.lineage?.parentConversationId
10097
+ ) : currentConversationId;
10098
+ if (context.source.platform === "slack") {
10099
+ return {
10100
+ currentConversationId,
10101
+ currentRootConversationId,
10102
+ provider: "slack",
10103
+ providerTenantId: context.source.teamId
10104
+ };
10105
+ }
10106
+ if (context.destination.platform === "slack" && "teamId" in context.destination) {
10107
+ return {
10108
+ currentConversationId,
10109
+ currentRootConversationId,
10110
+ provider: "slack",
10111
+ providerTenantId: context.destination.teamId
10112
+ };
10113
+ }
10114
+ return {
10115
+ currentConversationId,
10116
+ currentRootConversationId
10117
+ };
10118
+ }
10119
+ async function resolveRootConversationId(conversationStore, conversationId, parentConversationId) {
10120
+ if (!parentConversationId) {
10121
+ return conversationId;
10122
+ }
10123
+ let cursor = parentConversationId;
10124
+ const seen = /* @__PURE__ */ new Set([conversationId]);
10125
+ while (cursor) {
10126
+ if (seen.has(cursor)) {
10127
+ return conversationId;
10128
+ }
10129
+ seen.add(cursor);
10130
+ const parent = await conversationStore.get({ conversationId: cursor });
10131
+ if (!parent) {
10132
+ return conversationId;
10133
+ }
10134
+ if (!parent.lineage?.parentConversationId) {
10135
+ return parent.conversationId;
10136
+ }
10137
+ cursor = parent.lineage.parentConversationId;
10138
+ }
10139
+ return conversationId;
10140
+ }
10141
+ function assertCanQueryConversationEvents(args) {
10142
+ const {
10143
+ accessScope,
10144
+ targetConversationId,
10145
+ targetRootConversationId,
10146
+ targetVisibility,
10147
+ targetDestination
10148
+ } = args;
10149
+ if (targetConversationId === accessScope.currentConversationId || targetRootConversationId !== void 0 && targetRootConversationId === accessScope.currentRootConversationId) {
10150
+ return;
10151
+ }
10152
+ const publicPayloadAllowed = canExposeConversationPayload({
10153
+ conversationId: targetRootConversationId ?? targetConversationId,
10154
+ ...targetVisibility ? { visibility: targetVisibility } : {},
10155
+ ...targetDestination?.platform === "slack" ? { channelId: targetDestination.channelId } : {}
10156
+ });
10157
+ if (!publicPayloadAllowed) {
10158
+ throw new ToolInputError(
10159
+ `Conversation events are not accessible: ${targetConversationId}`
10160
+ );
10161
+ }
10162
+ if (accessScope.provider === "slack" && targetDestination?.platform === "slack" && accessScope.providerTenantId && targetDestination.teamId === accessScope.providerTenantId) {
10163
+ return;
10164
+ }
10165
+ throw new ToolInputError(
10166
+ `Conversation events are not accessible: ${targetConversationId}`
10167
+ );
10168
+ }
10169
+ function projectEventsForTool(page) {
10170
+ return page.events.map(projectEvent);
10171
+ }
10172
+ function projectEvent(event) {
10173
+ const data = stripReplacementHistory(event.data);
10174
+ return {
10175
+ seq: event.seq,
10176
+ history_version: event.historyVersion,
10177
+ created_at: new Date(event.createdAtMs).toISOString(),
10178
+ ...event.idempotencyKey ? { idempotency_key: event.idempotencyKey } : {},
10179
+ data
10180
+ };
10181
+ }
10182
+ function stripReplacementHistory(data) {
10183
+ if (data.type === "unknown") {
10184
+ return {
10185
+ ...data,
10186
+ payload: stripReplacementHistoryField(data.payload)
10187
+ };
10188
+ }
10189
+ if (data.type === "compaction" || data.type === "handoff") {
10190
+ return stripReplacementHistoryField(data);
10191
+ }
10192
+ return data;
10193
+ }
10194
+ function stripReplacementHistoryField(value) {
10195
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
10196
+ return value;
10197
+ }
10198
+ const { replacementHistory, ...rest } = value;
10199
+ if (replacementHistory === void 0) {
10200
+ return value;
10201
+ }
10202
+ return {
10203
+ ...rest,
10204
+ replacement_history_count: Array.isArray(replacementHistory) ? replacementHistory.length : 0
10205
+ };
10206
+ }
10207
+
10208
+ // src/chat/tools/handoff/tool.ts
10209
+ import { z as z37 } from "zod";
9651
10210
  var HANDOFF_TOOL_NAME = "handoff";
9652
10211
  function createHandoffTool(handoff) {
9653
- const profileSchema = z36.enum(handoff.profiles);
10212
+ const profileSchema = z37.enum(handoff.profiles);
9654
10213
  const handoffResultSchema = juniorToolResultSchema.extend({
9655
10214
  model_profile: profileSchema
9656
10215
  });
@@ -9658,7 +10217,7 @@ function createHandoffTool(handoff) {
9658
10217
  return zodTool({
9659
10218
  description: `Switch to another execution profile and continue the same task. Profiles: ${profileNames}. Call this as the only tool.`,
9660
10219
  executionMode: "sequential",
9661
- inputSchema: z36.object({
10220
+ inputSchema: z37.object({
9662
10221
  profile: profileSchema.describe("Target execution profile")
9663
10222
  }).strict(),
9664
10223
  outputSchema: handoffResultSchema,
@@ -9681,7 +10240,7 @@ function createHandoffTool(handoff) {
9681
10240
  }
9682
10241
 
9683
10242
  // src/chat/tools/web/fetch-tool.ts
9684
- import { z as z37 } from "zod";
10243
+ import { z as z38 } from "zod";
9685
10244
 
9686
10245
  // src/chat/tools/web/constants.ts
9687
10246
  var USER_AGENT = "junior-bot/0.1";
@@ -10131,9 +10690,9 @@ function createWebFetchTool(hooks, options = {}) {
10131
10690
  destructiveHint: false,
10132
10691
  openWorldHint: true
10133
10692
  },
10134
- inputSchema: z37.object({
10135
- url: z37.string().min(1).describe("HTTP(S) URL to fetch."),
10136
- max_chars: z37.coerce.number().int().min(500).max(MAX_FETCH_CHARS).describe("Optional maximum number of extracted characters to return.").optional()
10693
+ inputSchema: z38.object({
10694
+ url: z38.string().min(1).describe("HTTP(S) URL to fetch."),
10695
+ max_chars: z38.coerce.number().int().min(500).max(MAX_FETCH_CHARS).describe("Optional maximum number of extracted characters to return.").optional()
10137
10696
  }),
10138
10697
  outputSchema: juniorToolResultSchema,
10139
10698
  execute: async ({ url, max_chars }) => {
@@ -10194,7 +10753,7 @@ function createWebFetchTool(hooks, options = {}) {
10194
10753
  }
10195
10754
 
10196
10755
  // src/chat/tools/web/search.ts
10197
- import { z as z38 } from "zod";
10756
+ import { z as z39 } from "zod";
10198
10757
  import { generateText } from "ai";
10199
10758
  import { createGatewayProvider } from "@ai-sdk/gateway";
10200
10759
  var SEARCH_TIMEOUT_MS = 6e4;
@@ -10243,9 +10802,9 @@ function createWebSearchTool(modelId, override) {
10243
10802
  destructiveHint: false,
10244
10803
  openWorldHint: true
10245
10804
  },
10246
- inputSchema: z38.object({
10247
- query: z38.string().min(1).max(500).describe("Search query."),
10248
- max_results: z38.coerce.number().int().min(1).max(MAX_RESULTS2).describe("Max results to return.").optional()
10805
+ inputSchema: z39.object({
10806
+ query: z39.string().min(1).max(500).describe("Search query."),
10807
+ max_results: z39.coerce.number().int().min(1).max(MAX_RESULTS2).describe("Max results to return.").optional()
10249
10808
  }),
10250
10809
  outputSchema: juniorToolResultSchema,
10251
10810
  execute: async ({ query, max_results }) => {
@@ -10314,14 +10873,14 @@ function createWebSearchTool(modelId, override) {
10314
10873
  }
10315
10874
 
10316
10875
  // src/chat/tools/sandbox/write-file.ts
10317
- import { z as z39 } from "zod";
10876
+ import { z as z40 } from "zod";
10318
10877
  function createWriteFileTool() {
10319
10878
  return zodTool({
10320
10879
  description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or deliberate full-file replacement after validation; use editFile instead for targeted changes to existing files. Do not use for exploratory analysis-only turns.",
10321
10880
  executionMode: "sequential",
10322
- inputSchema: z39.object({
10323
- path: z39.string().min(1).describe("Path to write in the sandbox workspace."),
10324
- content: z39.string().describe("UTF-8 file content to write.")
10881
+ inputSchema: z40.object({
10882
+ path: z40.string().min(1).describe("Path to write in the sandbox workspace."),
10883
+ content: z40.string().describe("UTF-8 file content to write.")
10325
10884
  }),
10326
10885
  outputSchema: juniorToolResultSchema,
10327
10886
  execute: async () => {
@@ -10374,7 +10933,7 @@ function createTools(availableSkills, hooks = {}, context) {
10374
10933
  }),
10375
10934
  reportProgress: createReportProgressTool(),
10376
10935
  systemTime: createSystemTimeTool(),
10377
- bash: createBashTool2(),
10936
+ bash: createBashTool(),
10378
10937
  readFile: createReadFileTool(),
10379
10938
  editFile: createEditFileTool(),
10380
10939
  grep: createGrepTool(),
@@ -10390,6 +10949,9 @@ function createTools(availableSkills, hooks = {}, context) {
10390
10949
  }),
10391
10950
  ...createResourceEventTools(context)
10392
10951
  };
10952
+ if (context.conversationId) {
10953
+ tools.queryConversationEvents = createQueryConversationEventsTool(context);
10954
+ }
10393
10955
  if (hooks.writeGeneratedArtifacts) {
10394
10956
  tools.imageGenerate = createImageGenerateTool(
10395
10957
  {
@@ -11063,17 +11625,17 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
11063
11625
  }
11064
11626
 
11065
11627
  // src/chat/tools/execute-tool.ts
11066
- import { z as z40 } from "zod";
11628
+ import { z as z41 } from "zod";
11067
11629
  var EXECUTE_TOOL_NAME = "executeTool";
11068
11630
  function createExecuteToolTool() {
11069
11631
  return zodTool({
11070
11632
  description: "Execute any catalog tool by exact tool_name from searchTools or a tool-result execution hint. Put tool-specific parameters inside arguments.",
11071
11633
  executionMode: "sequential",
11072
- inputSchema: z40.object({
11073
- tool_name: z40.string().min(1).describe(
11634
+ inputSchema: z41.object({
11635
+ tool_name: z41.string().min(1).describe(
11074
11636
  "Exact catalog tool_name returned by searchTools or a tool-result execution hint."
11075
11637
  ),
11076
- arguments: z40.record(z40.string(), z40.unknown()).describe(
11638
+ arguments: z41.record(z41.string(), z41.unknown()).describe(
11077
11639
  'Arguments matching the selected catalog tool schema, for example { "query": "..." }.'
11078
11640
  ).optional()
11079
11641
  }).strict(),
@@ -11113,42 +11675,42 @@ function planToolExposure(tools) {
11113
11675
  }
11114
11676
 
11115
11677
  // src/chat/tools/search-tools.ts
11116
- import { z as z41 } from "zod";
11678
+ import { z as z42 } from "zod";
11117
11679
  var SEARCH_TOOLS_NAME = "searchTools";
11118
11680
  var DEFAULT_MAX_RESULTS2 = 5;
11119
11681
  var MAX_RESULTS3 = 20;
11120
11682
  var MODEL_VISIBLE_DESCRIPTION_CAP = 180;
11121
- var searchToolsSourceSchema = z41.object({
11122
- id: z41.string(),
11123
- description: z41.string()
11683
+ var searchToolsSourceSchema = z42.object({
11684
+ id: z42.string(),
11685
+ description: z42.string()
11124
11686
  }).strict();
11125
- var toolCallExampleSchema = z41.object({
11126
- tool_name: z41.string(),
11127
- arguments: z41.record(z41.string(), z41.string())
11687
+ var toolCallExampleSchema = z42.object({
11688
+ tool_name: z42.string(),
11689
+ arguments: z42.record(z42.string(), z42.string())
11128
11690
  }).strict();
11129
- var searchToolsToolSchema = z41.object({
11130
- tool_name: z41.string(),
11131
- description: z41.string(),
11132
- exposure: z41.enum(["direct", "deferred", "modelOnly", "hidden"]),
11133
- source: z41.string().optional(),
11134
- signature: z41.string(),
11691
+ var searchToolsToolSchema = z42.object({
11692
+ tool_name: z42.string(),
11693
+ description: z42.string(),
11694
+ exposure: z42.enum(["direct", "deferred", "modelOnly", "hidden"]),
11695
+ source: z42.string().optional(),
11696
+ signature: z42.string(),
11135
11697
  call: toolCallExampleSchema,
11136
- input_schema: z41.unknown(),
11137
- input_schema_summary: z41.string(),
11138
- call_notes: z41.array(z41.string()),
11139
- annotations: z41.record(z41.string(), z41.unknown())
11698
+ input_schema: z42.unknown(),
11699
+ input_schema_summary: z42.string(),
11700
+ call_notes: z42.array(z42.string()),
11701
+ annotations: z42.record(z42.string(), z42.unknown())
11140
11702
  }).strict();
11141
11703
  var searchToolsOutputSchema = juniorToolResultSchema.extend({
11142
- query: z41.string().nullable(),
11143
- source: z41.string().nullable(),
11144
- sources: z41.array(searchToolsSourceSchema),
11145
- total_catalog_tools: z41.number().int().nonnegative(),
11146
- total_eligible_tools: z41.number().int().nonnegative(),
11147
- total_matches: z41.number().int().nonnegative(),
11148
- returned_tools: z41.number().int().nonnegative(),
11149
- execution_tool: z41.literal("executeTool"),
11704
+ query: z42.string().nullable(),
11705
+ source: z42.string().nullable(),
11706
+ sources: z42.array(searchToolsSourceSchema),
11707
+ total_catalog_tools: z42.number().int().nonnegative(),
11708
+ total_eligible_tools: z42.number().int().nonnegative(),
11709
+ total_matches: z42.number().int().nonnegative(),
11710
+ returned_tools: z42.number().int().nonnegative(),
11711
+ execution_tool: z42.literal("executeTool"),
11150
11712
  execution_example: toolCallExampleSchema,
11151
- tools: z41.array(searchToolsToolSchema)
11713
+ tools: z42.array(searchToolsToolSchema)
11152
11714
  }).strict();
11153
11715
  function normalize2(value) {
11154
11716
  return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
@@ -11340,14 +11902,14 @@ function createSearchToolsTool(catalogTools) {
11340
11902
  return zodTool({
11341
11903
  description: renderSearchToolsDescription(knownSources),
11342
11904
  annotations: { readOnlyHint: true, destructiveHint: false },
11343
- inputSchema: z41.object({
11344
- query: z41.string().nullable().describe(
11905
+ inputSchema: z42.object({
11906
+ query: z42.string().nullable().describe(
11345
11907
  "Optional search terms describing the tool, owner, action, or arguments needed. Empty string lists catalog tools."
11346
11908
  ).optional(),
11347
- source: z41.string().nullable().describe(
11909
+ source: z42.string().nullable().describe(
11348
11910
  "Optional source id to search within, such as a plugin source returned in sources."
11349
11911
  ).optional(),
11350
- max_results: z41.number().int().min(1).max(MAX_RESULTS3).nullable().describe("Maximum matching catalog tool descriptors to return.").optional()
11912
+ max_results: z42.number().int().min(1).max(MAX_RESULTS3).nullable().describe("Maximum matching catalog tool descriptors to return.").optional()
11351
11913
  }).strict(),
11352
11914
  outputSchema: searchToolsOutputSchema,
11353
11915
  privateTraceResult: (result) => ({
@@ -12859,6 +13421,21 @@ async function wireAgentTools(args) {
12859
13421
  mcpToolManager,
12860
13422
  pluginHooks,
12861
13423
  getSandboxRef: agentSandbox.sandboxRef,
13424
+ async close() {
13425
+ agentSandbox.close();
13426
+ try {
13427
+ await mcpToolManager.close();
13428
+ } catch (closeError) {
13429
+ logWarn(
13430
+ "mcp_tool_manager_close_failed",
13431
+ {},
13432
+ {
13433
+ "exception.message": closeError instanceof Error ? closeError.message : String(closeError)
13434
+ },
13435
+ "Failed to close MCP tool manager"
13436
+ );
13437
+ }
13438
+ },
12862
13439
  toolGuidance,
12863
13440
  toolRuntimeContext
12864
13441
  };
@@ -12884,6 +13461,7 @@ function createResumeState(args) {
12884
13461
  channelName: args.channelName,
12885
13462
  conversationId: args.conversationId,
12886
13463
  destination: args.destination,
13464
+ ...args.dispatchId ? { dispatchId: args.dispatchId } : {},
12887
13465
  source: args.runSource,
12888
13466
  sessionId: args.turnId,
12889
13467
  loadedSkillNames: args.getLoadedSkillNames(),
@@ -13249,12 +13827,29 @@ function estimateHistoryTokens(messages) {
13249
13827
  }
13250
13828
  function buildReplacementProvenance(args) {
13251
13829
  return [
13252
- ...args.retained.map(
13253
- (entry) => args.sourceProvenance[entry.sourceIndex] ?? contextProvenance
13254
- ),
13830
+ ...args.retained.map((entry) => {
13831
+ const provenance = args.sourceProvenance[entry.sourceIndex];
13832
+ if (!provenance) {
13833
+ throw new Error("retained message provenance is missing");
13834
+ }
13835
+ return provenance;
13836
+ }),
13255
13837
  contextProvenance
13256
13838
  ];
13257
13839
  }
13840
+ async function loadLastInstruction(conversationId) {
13841
+ const event = await getConversationEventStore().loadLatestInstruction(conversationId);
13842
+ if (!event) return void 0;
13843
+ if (event.data.type === "user_message") {
13844
+ if (event.data.provenance.authority !== "instruction") return void 0;
13845
+ return {
13846
+ message: piMessageFromHistoryItem(event.data),
13847
+ provenance: event.data.provenance,
13848
+ sourceEventSeq: event.seq
13849
+ };
13850
+ }
13851
+ return void 0;
13852
+ }
13258
13853
  function loadCompactionSource(messages) {
13259
13854
  if (messages.length > 0) {
13260
13855
  return {
@@ -13346,8 +13941,10 @@ ${summary}`)
13346
13941
  replacementHistory: replacement.map((message, index) => {
13347
13942
  const sourceEventSeq = index < retained.length ? sourceProjection.seqs[retained[index].sourceIndex] : void 0;
13348
13943
  return {
13349
- message,
13350
- provenance: replacementProvenance[index],
13944
+ item: historyItemFromPiMessage(
13945
+ message,
13946
+ replacementProvenance[index]
13947
+ ),
13351
13948
  ...sourceEventSeq === void 0 ? {} : { sourceEventSeq }
13352
13949
  };
13353
13950
  })
@@ -13399,8 +13996,7 @@ ${generatedSummary}`;
13399
13996
  ...args.triggeringToolCallId ? { triggeringToolCallId: args.triggeringToolCallId } : {},
13400
13997
  summary: generatedSummary,
13401
13998
  replacementHistory: replacementMessages.map((replacementMessage) => ({
13402
- message: replacementMessage,
13403
- provenance: contextProvenance
13999
+ item: historyItemFromPiMessage(replacementMessage, contextProvenance)
13404
14000
  }))
13405
14001
  }
13406
14002
  });
@@ -13464,14 +14060,14 @@ ${summary}`;
13464
14060
  ...runtimeMessage,
13465
14061
  content: [
13466
14062
  ...runtimeMessage.content,
13467
- { type: "text", text: renderCurrentInstruction(summaryText) }
14063
+ { type: "text", text: summaryText }
13468
14064
  ]
13469
14065
  }
13470
- ] : [userMessage(renderCurrentInstruction(summaryText))];
13471
- const messages = [
13472
- ...summaryMessages,
13473
- ...pendingMessages.map((entry) => entry.message)
13474
- ];
14066
+ ] : [userMessage(summaryText)];
14067
+ const retainedInstruction = pendingMessages.length === 0 ? await loadLastInstruction(args.conversationId) : void 0;
14068
+ const instructionMessages = retainedInstruction ? [retainedInstruction.message] : pendingMessages.map((entry) => entry.message);
14069
+ const instructionProvenance = retainedInstruction ? [retainedInstruction.provenance] : pendingMessages.map((entry) => entry.provenance);
14070
+ const messages = [...summaryMessages, ...instructionMessages];
13475
14071
  const replacementInputTokens = estimateHistoryTokens(messages);
13476
14072
  if (replacementInputTokens >= inputLimitTokens) {
13477
14073
  throw new ContextInputLimitExceededError(
@@ -13480,6 +14076,11 @@ ${summary}`;
13480
14076
  );
13481
14077
  }
13482
14078
  const replacementMessages = stripRuntimeTurnContext(messages);
14079
+ if (replacementMessages.length !== 1 + instructionProvenance.length) {
14080
+ throw new Error(
14081
+ "persisted instruction provenance must align one-to-one with messages"
14082
+ );
14083
+ }
13483
14084
  args.signal?.throwIfAborted();
13484
14085
  await getConversationEventStore().replaceHistory(args.conversationId, {
13485
14086
  createdAtMs: Date.now(),
@@ -13494,20 +14095,23 @@ ${summary}`;
13494
14095
  triggerTokens,
13495
14096
  inputLimitTokens,
13496
14097
  inputMessageCount: source.messages.length,
13497
- retainedMessageCount: pendingMessages.length,
14098
+ retainedMessageCount: instructionMessages.length,
13498
14099
  summaryChars: summary.length
13499
14100
  },
13500
14101
  summary,
13501
- replacementHistory: replacementMessages.map((message, index) => ({
13502
- message,
13503
- provenance: index === 0 ? contextProvenance : pendingMessages[index - 1]?.provenance ?? contextProvenance
13504
- }))
14102
+ replacementHistory: replacementMessages.map((message, index) => {
14103
+ const provenance = index === 0 ? contextProvenance : instructionProvenance[index - 1];
14104
+ return {
14105
+ item: historyItemFromPiMessage(message, provenance),
14106
+ ...index === 1 && retainedInstruction ? { sourceEventSeq: retainedInstruction.sourceEventSeq } : {}
14107
+ };
14108
+ })
13505
14109
  }
13506
14110
  });
13507
14111
  setSpanAttributes({
13508
14112
  "app.compaction.input_messages": source.messages.length,
13509
14113
  "app.compaction.replacement_tokens": replacementInputTokens,
13510
- "app.compaction.retained_messages": pendingMessages.length,
14114
+ "app.compaction.retained_messages": instructionMessages.length,
13511
14115
  "app.compaction.summary_chars": summary.length,
13512
14116
  "app.compaction.trigger_tokens": triggerTokens,
13513
14117
  "app.context_input_limit_tokens": inputLimitTokens,
@@ -13612,6 +14216,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
13612
14216
  let resume;
13613
14217
  let lastKnownSandboxRef = state.sandboxRef;
13614
14218
  let mcpToolManager;
14219
+ let closeTools;
13615
14220
  let connectedMcpProviders = /* @__PURE__ */ new Set();
13616
14221
  let turnUsage;
13617
14222
  let priorPhaseUsage;
@@ -13717,6 +14322,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
13717
14322
  resume = createResumeState({
13718
14323
  channelName: routing.slackConversation?.name,
13719
14324
  destination: routing.destination,
14325
+ ...routing.dispatch?.id ? { dispatchId: routing.dispatch.id } : {},
13720
14326
  durability,
13721
14327
  getLoadedSkillNames: () => loadedSkillNamesForResume,
13722
14328
  getReasoningLevel: () => turnRoute?.reasoningLevel,
@@ -13977,6 +14583,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
13977
14583
  userInput
13978
14584
  });
13979
14585
  mcpToolManager = wiring.mcpToolManager;
14586
+ closeTools = wiring.close;
13980
14587
  const getPendingAuthPause = wiring.getPendingAuthPause;
13981
14588
  const toolsAfterHandoff = wiring.agentTools;
13982
14589
  const {
@@ -14576,18 +15183,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
14576
15183
  }
14577
15184
  };
14578
15185
  } finally {
14579
- try {
14580
- await mcpToolManager?.close();
14581
- } catch (closeError) {
14582
- logWarn(
14583
- "mcp_tool_manager_close_failed",
14584
- {},
14585
- {
14586
- "exception.message": closeError instanceof Error ? closeError.message : String(closeError)
14587
- },
14588
- "Failed to close MCP tool manager"
14589
- );
14590
- }
15186
+ await closeTools?.();
14591
15187
  }
14592
15188
  }
14593
15189