@sentry/junior 0.132.0 → 0.134.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 (83) hide show
  1. package/dist/{agent-hooks-ONXOJDR6.js → agent-hooks-HGXNYOOD.js} +9 -9
  2. package/dist/api/schema/task.d.ts +60 -0
  3. package/dist/api/schema.d.ts +2 -2
  4. package/dist/api/schema.js +3 -1
  5. package/dist/api.js +90 -20
  6. package/dist/app.js +110 -39
  7. package/dist/{catalog-runtime-BATAY4R7.js → catalog-runtime-P6QHU6XE.js} +5 -5
  8. package/dist/chat/agent/prompt.d.ts +7 -5
  9. package/dist/chat/agent/repository-context.d.ts +20 -0
  10. package/dist/chat/agent/sandbox.d.ts +8 -0
  11. package/dist/chat/agent/tools.d.ts +3 -0
  12. package/dist/chat/config.d.ts +2 -0
  13. package/dist/chat/conversations/projection.d.ts +3 -1
  14. package/dist/chat/conversations/sql/bindings.d.ts +13 -0
  15. package/dist/chat/conversations/sql/store.d.ts +3 -0
  16. package/dist/chat/conversations/store.d.ts +15 -0
  17. package/dist/chat/plugins/agent-hooks.d.ts +2 -2
  18. package/dist/chat/repository-instructions.d.ts +44 -0
  19. package/dist/chat/runtime/reply-executor.d.ts +1 -0
  20. package/dist/chat/runtime/slack-runtime.d.ts +2 -0
  21. package/dist/chat/sandbox/noninteractive-command.d.ts +2 -0
  22. package/dist/chat/sandbox/sandbox.d.ts +3 -0
  23. package/dist/chat/sandbox/session.d.ts +1 -0
  24. package/dist/chat/services/context-compaction.d.ts +1 -0
  25. package/dist/chat/slack/dispatch-turn.d.ts +1 -0
  26. package/dist/chat/slack/reply.d.ts +1 -1
  27. package/dist/chat/slack/tools/user-lookup.d.ts +2 -6
  28. package/dist/chat/tasks/execution-stats.d.ts +29 -0
  29. package/dist/chat/tasks/registered.d.ts +3 -0
  30. package/dist/chat/tools/index.d.ts +2 -0
  31. package/dist/chat/tools/sandbox/bash.d.ts +1 -0
  32. package/dist/chat/tools/system-time.d.ts +11 -1
  33. package/dist/{chunk-O2UXPUVQ.js → chunk-52DIKFJV.js} +14 -6
  34. package/dist/{chunk-GOEZU33G.js → chunk-6T3AYZT7.js} +1 -1
  35. package/dist/{chunk-GPXVDJML.js → chunk-7GKQ7UIO.js} +449 -39
  36. package/dist/{chunk-QVEXYYFM.js → chunk-7UTBPDXK.js} +24 -1
  37. package/dist/{chunk-U76PS7AT.js → chunk-AS3I7OIL.js} +2 -2
  38. package/dist/{chunk-HX236W2U.js → chunk-CDQ4YIZM.js} +9 -0
  39. package/dist/{chunk-ODIP5CEM.js → chunk-FDNAUM7X.js} +32 -32
  40. package/dist/{chunk-GTKNPQNN.js → chunk-FL466XX5.js} +36 -24
  41. package/dist/{chunk-2425Q4OR.js → chunk-GSP5SMZJ.js} +1 -1
  42. package/dist/{chunk-XN656LB4.js → chunk-KWGJG4HO.js} +6 -12
  43. package/dist/{chunk-ISNRPCPG.js → chunk-LKK4XMUV.js} +1 -1
  44. package/dist/{chunk-G3WYA2RS.js → chunk-MFERLE3G.js} +1 -1
  45. package/dist/{chunk-ZMV2YBT6.js → chunk-NF2TNZPD.js} +788 -674
  46. package/dist/chunk-NOA34AJY.js +122 -0
  47. package/dist/{chunk-JYQAE2ZU.js → chunk-NYWSCJVW.js} +1 -1
  48. package/dist/{chunk-KB2M27DL.js → chunk-QLEKAO7O.js} +130 -40
  49. package/dist/{chunk-BKE47OOW.js → chunk-S6DMTEOM.js} +2 -2
  50. package/dist/{chunk-ALDPUN3Z.js → chunk-TR6UEEYD.js} +8 -6
  51. package/dist/{chunk-WEJIS7OC.js → chunk-U3ZI7CCW.js} +1 -1
  52. package/dist/{chunk-B7IY6SVJ.js → chunk-VEGF62FV.js} +1 -1
  53. package/dist/{chunk-SKWIIUS7.js → chunk-VGXJMECA.js} +7 -7
  54. package/dist/{chunk-V44FFBO3.js → chunk-VP6NCPRS.js} +1 -1
  55. package/dist/{chunk-WYQP7QSB.js → chunk-VPYKNCTN.js} +101 -61
  56. package/dist/{chunk-NNHUGGO4.js → chunk-XG5PHKPM.js} +19 -3
  57. package/dist/{chunk-AW5Z3T7V.js → chunk-YNLQQ622.js} +9 -8
  58. package/dist/cli/chat.js +25 -25
  59. package/dist/cli/check.js +6 -6
  60. package/dist/cli/init.js +1 -1
  61. package/dist/cli/plugins.js +12 -12
  62. package/dist/cli/snapshot-create.js +9 -9
  63. package/dist/cli/upgrade.js +9 -8
  64. package/dist/db/executor.d.ts +2 -1
  65. package/dist/db/neon.d.ts +1 -0
  66. package/dist/db/postgres.d.ts +1 -0
  67. package/dist/db/schema/conversation-bindings.d.ts +110 -0
  68. package/dist/db/schema/task-executions.d.ts +110 -0
  69. package/dist/db/schema.d.ts +221 -1
  70. package/dist/{db-QFFZACXF.js → db-GFK4NHWJ.js} +7 -7
  71. package/dist/instrumentation.js +4 -4
  72. package/dist/nitro.js +2 -2
  73. package/dist/{oauth-callback-server-5WXLD533.js → oauth-callback-server-HUJLAQ5N.js} +19 -18
  74. package/dist/{runner-Y3LTUNAA.js → runner-T77AFAH4.js} +13 -12
  75. package/dist/{skills-7M5C24GM.js → skills-3KUFJOOR.js} +6 -6
  76. package/dist/{validation-MRILMYDI.js → validation-FMWDHKYU.js} +6 -6
  77. package/dist/version.js +1 -1
  78. package/migrations/0018_curvy_excalibur.sql +12 -0
  79. package/migrations/0019_lowly_valkyrie.sql +14 -0
  80. package/migrations/meta/0018_snapshot.json +1756 -0
  81. package/migrations/meta/0019_snapshot.json +1904 -0
  82. package/migrations/meta/_journal.json +15 -1
  83. package/package.json +5 -5
@@ -30,30 +30,41 @@ import {
30
30
  toMcpProviderError,
31
31
  toolInvocationDestination,
32
32
  uploadFilesToConversation
33
- } from "./chunk-SKWIIUS7.js";
33
+ } from "./chunk-VGXJMECA.js";
34
34
  import {
35
+ AGENTS_REMOVAL_NOTICE,
36
+ AGENTS_REPLACEMENT_NOTICE,
35
37
  CooperativeTurnYieldError,
38
+ DEFAULT_SEARCH_COMMAND_TIMEOUT_MS,
36
39
  JUNIOR_THREAD_STATE_TTL_MS,
40
+ MAX_TEXT_CHARS,
37
41
  NO_REPLY_MARKER,
42
+ RIPGREP_EXCLUDED_GLOBS,
38
43
  TURN_CONTEXT_TAG,
39
44
  TurnInputCommitLostError,
40
45
  TurnSliceLimitExceededError,
41
46
  abandonReplacedPendingAuth,
47
+ buildAgentsInstructionsMessage,
42
48
  canReusePendingAuthLink,
49
+ collectFiles,
43
50
  containsNoReplyMarker,
44
51
  decideReply,
45
52
  estimateTextTokens,
46
53
  extractAssistantText,
54
+ findSingleRepositoryDirectory,
55
+ findVisibleAgentsInstructions,
47
56
  getAgentContextCompactionTriggerTokens,
48
57
  getAgentContextInputLimitTokens,
49
58
  getAssistantReplyText,
50
59
  getPiMessageRole,
60
+ getRipgrepSearchLocation,
51
61
  getTerminalAssistantMessages,
52
62
  getUserMessageInstructionText,
53
63
  hasRuntimeTurnContext,
54
64
  historyItemFromPiMessage,
55
65
  isAssistantMessage,
56
66
  isContinuablePiBoundary,
67
+ isMissingPathError,
57
68
  isNoReplyMarker,
58
69
  isToolResultError,
59
70
  isToolResultMessage,
@@ -63,6 +74,8 @@ import {
63
74
  loadTurnRoute,
64
75
  loadTurnSessionRecord,
65
76
  mergeArtifactsState,
77
+ missingPathSearchResult,
78
+ normalizeToLf,
66
79
  normalizeToolNameFromResult,
67
80
  openConversationProjection,
68
81
  persistAuthPauseSessionRecord,
@@ -70,17 +83,22 @@ import {
70
83
  persistRunningSessionRecord,
71
84
  persistYieldSessionRecord,
72
85
  piMessageFromHistoryItem,
86
+ positiveInteger,
73
87
  recordAuthorizationRequested,
74
88
  recordGuardianActionReviewed,
75
89
  recordMcpProviderConnected,
76
90
  recordToolExecutionStarted,
77
91
  recordTurnRoute,
92
+ renderAgentsInstructions,
93
+ resolveRepositoryInstructions,
94
+ resolveWorkspacePath,
78
95
  retainRuntimeTurnContext,
79
96
  sanitizeAssistantText,
80
97
  stripRuntimeTurnContext,
81
98
  trimTrailingAssistantMessages,
99
+ truncateText,
82
100
  updateConversationStats
83
- } from "./chunk-GPXVDJML.js";
101
+ } from "./chunk-7GKQ7UIO.js";
84
102
  import {
85
103
  parseSandboxEgressAuthRequiredSignal,
86
104
  parseSandboxEgressPermissionDeniedSignal,
@@ -89,6 +107,7 @@ import {
89
107
  } from "./chunk-EPBBYMNE.js";
90
108
  import {
91
109
  bindSandboxFileSystem,
110
+ buildCommandScript,
92
111
  buildNonInteractiveShellScript,
93
112
  createSandboxSession,
94
113
  getSandboxResources,
@@ -104,10 +123,9 @@ import {
104
123
  runNonInteractiveCommand,
105
124
  throwSandboxOperationError,
106
125
  wrapSandboxSetupError
107
- } from "./chunk-AW5Z3T7V.js";
126
+ } from "./chunk-YNLQQ622.js";
108
127
  import {
109
128
  SCHEDULED_TASK_SYSTEM_ACTOR,
110
- ToolInputError,
111
129
  compactEventTask,
112
130
  createEventTask,
113
131
  createSchedulerSqlStore,
@@ -136,14 +154,15 @@ import {
136
154
  resolveLocalScheduleAtMs,
137
155
  saveEventTask,
138
156
  writableEventTask
139
- } from "./chunk-XN656LB4.js";
157
+ } from "./chunk-KWGJG4HO.js";
140
158
  import {
141
159
  parseSlackThreadId
142
- } from "./chunk-G3WYA2RS.js";
160
+ } from "./chunk-MFERLE3G.js";
143
161
  import {
162
+ ToolInputError,
144
163
  addAgentTurnUsage,
145
164
  hasAgentTurnUsage
146
- } from "./chunk-HX236W2U.js";
165
+ } from "./chunk-CDQ4YIZM.js";
147
166
  import {
148
167
  privateTraceResultAttributes
149
168
  } from "./chunk-UIE3R5XU.js";
@@ -162,7 +181,7 @@ import {
162
181
  readActorIdentity,
163
182
  resolveChannelCapabilities,
164
183
  withSlackRetries
165
- } from "./chunk-ALDPUN3Z.js";
184
+ } from "./chunk-TR6UEEYD.js";
166
185
  import {
167
186
  KNOWN_CONVERSATION_EVENT_TYPES,
168
187
  contextProvenance,
@@ -173,7 +192,7 @@ import {
173
192
  instructionActors,
174
193
  instructionProvenanceFor,
175
194
  sameActorIdentity
176
- } from "./chunk-WYQP7QSB.js";
195
+ } from "./chunk-VPYKNCTN.js";
177
196
  import {
178
197
  SANDBOX_DATA_ROOT,
179
198
  SANDBOX_SKILLS_ROOT,
@@ -183,7 +202,7 @@ import {
183
202
  sandboxSkillFile,
184
203
  sandboxSkillPathResolution,
185
204
  sleep
186
- } from "./chunk-ODIP5CEM.js";
205
+ } from "./chunk-FDNAUM7X.js";
187
206
  import {
188
207
  ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
189
208
  COMPACTION_SUMMARY_PREFIX,
@@ -217,7 +236,7 @@ import {
217
236
  renderCurrentInstruction,
218
237
  resolveGatewayModel,
219
238
  unwrapCurrentInstruction
220
- } from "./chunk-NNHUGGO4.js";
239
+ } from "./chunk-XG5PHKPM.js";
221
240
  import {
222
241
  canExposeConversationPayload,
223
242
  resolveConversationPrivacy,
@@ -227,16 +246,16 @@ import {
227
246
  toGenAiMessagesTraceAttributes,
228
247
  toGenAiPayloadMetadata,
229
248
  toGenAiPayloadTraceAttributes
230
- } from "./chunk-ISNRPCPG.js";
249
+ } from "./chunk-LKK4XMUV.js";
231
250
  import {
232
251
  discoverSkills,
233
252
  findSkillByName,
234
253
  loadSkillsByName,
235
254
  parseSkillInvocation
236
- } from "./chunk-BKE47OOW.js";
255
+ } from "./chunk-S6DMTEOM.js";
237
256
  import {
238
257
  pluginCatalogRuntime
239
- } from "./chunk-B7IY6SVJ.js";
258
+ } from "./chunk-VEGF62FV.js";
240
259
  import {
241
260
  CredentialUnavailableError,
242
261
  OAuthProviderError,
@@ -244,7 +263,7 @@ import {
244
263
  getOAuthProviderErrorAttributes,
245
264
  resolveAuthTokenPlaceholder,
246
265
  resolvePluginCommandEnv
247
- } from "./chunk-V44FFBO3.js";
266
+ } from "./chunk-VP6NCPRS.js";
248
267
  import {
249
268
  credentialUserSubjectId
250
269
  } from "./chunk-77VDLF3Z.js";
@@ -273,7 +292,7 @@ import {
273
292
  summarizeMessageText,
274
293
  withLogContext,
275
294
  withSpan
276
- } from "./chunk-GOEZU33G.js";
295
+ } from "./chunk-6T3AYZT7.js";
277
296
  import {
278
297
  startInactiveSpan,
279
298
  withActiveSpan
@@ -568,6 +587,56 @@ var SkillSandbox = class {
568
587
  }
569
588
  };
570
589
 
590
+ // src/chat/agent/repository-context.ts
591
+ function findRestoredInstructions(messages, provenance) {
592
+ const restoredContext = messages?.map((message, index) => ({ message, provenance: provenance?.[index] })).reverse().find(
593
+ ({ message, provenance: messageProvenance }) => messageProvenance?.authority === "context" ? Boolean(findVisibleAgentsInstructions([message])) : false
594
+ );
595
+ return restoredContext ? findVisibleAgentsInstructions([restoredContext.message]) : void 0;
596
+ }
597
+ function createRepositoryInstructionsContext(options) {
598
+ const visibleInstructions = options.shouldPromptAgent ? findVisibleAgentsInstructions([
599
+ {
600
+ role: "user",
601
+ content: options.promptContextContentParts
602
+ }
603
+ ]) : findRestoredInstructions(
604
+ options.restoredMessages,
605
+ options.restoredProvenance
606
+ );
607
+ let instructionsVisible = visibleInstructions?.active === true;
608
+ let visibleFingerprint = options.initialInstructions && visibleInstructions?.active === true && visibleInstructions.directory === options.initialInstructions.directory && visibleInstructions.text === options.initialInstructions.text ? options.initialInstructions.fingerprint : void 0;
609
+ return {
610
+ async applyUpdate(currentUpdate, currentContext) {
611
+ if (!options.hasSandbox()) {
612
+ return currentUpdate;
613
+ }
614
+ const instructions = await options.capture();
615
+ if (instructions && instructions.fingerprint === visibleFingerprint && instructionsVisible) {
616
+ return currentUpdate;
617
+ }
618
+ if (!instructions && !instructionsVisible) {
619
+ return currentUpdate;
620
+ }
621
+ const message = instructions ? buildAgentsInstructionsMessage({
622
+ directory: instructions.directory,
623
+ text: instructionsVisible ? `${AGENTS_REPLACEMENT_NOTICE}
624
+
625
+ ${instructions.text}` : instructions.text
626
+ }) : buildAgentsInstructionsMessage({ text: AGENTS_REMOVAL_NOTICE });
627
+ const context = currentUpdate?.context ?? currentContext;
628
+ const messages = [...context.messages, message];
629
+ options.setMessages(messages);
630
+ visibleFingerprint = instructions?.fingerprint;
631
+ instructionsVisible = Boolean(instructions);
632
+ return {
633
+ ...currentUpdate,
634
+ context: { ...context, messages }
635
+ };
636
+ }
637
+ };
638
+ }
639
+
571
640
  // src/chat/pi/traced-stream.ts
572
641
  import {
573
642
  streamSimple
@@ -1302,11 +1371,11 @@ function projectToolActionRejection(pending, toolCallId, result) {
1302
1371
  function restoreToolActionRejections(messages) {
1303
1372
  const priorRejections = [];
1304
1373
  for (const message of messages) {
1305
- const record = message;
1306
- if (record.role !== "toolResult" || record.isError !== true) {
1374
+ const record2 = message;
1375
+ if (record2.role !== "toolResult" || record2.isError !== true) {
1307
1376
  continue;
1308
1377
  }
1309
- const rejection = getToolActionRejectionMarker(record);
1378
+ const rejection = getToolActionRejectionMarker(record2);
1310
1379
  if (!rejection) {
1311
1380
  continue;
1312
1381
  }
@@ -1806,6 +1875,7 @@ var EXECUTION_CONTRACT_RULES = [
1806
1875
  var CONVERSATION_RULES = [
1807
1876
  "- In thread follow-ups, answer from prior thread context; do not repeat resolved clarifying questions.",
1808
1877
  "- Preserve attribution roles from thread context: the actor is the person asking now, which may differ from the original reporter or subject.",
1878
+ "- Direct system/developer/user instructions (as part of a prompt) take precedence over AGENTS.md instructions.",
1809
1879
  "- Runtime owns continuation and authorization notices; on resumed turns, answer with the final requested content only."
1810
1880
  ];
1811
1881
  var SLACK_ACTION_RULES = [
@@ -2082,7 +2152,6 @@ function buildTurnContextPrompt(params) {
2082
2152
  const sections = [
2083
2153
  `<${TURN_CONTEXT_TAG}>`,
2084
2154
  TURN_CONTEXT_HEADER,
2085
- "The current user instruction appears after this block in `<current-instruction>` in the same message.",
2086
2155
  ...runtimeSections,
2087
2156
  `</${TURN_CONTEXT_TAG}>`
2088
2157
  ].filter((section) => Boolean(section));
@@ -2103,17 +2172,8 @@ function renderThreadContextForPrompt(context) {
2103
2172
  }
2104
2173
  return ["<thread-background>", context, "</thread-background>"].join("\n");
2105
2174
  }
2106
- function buildUserTurnText(userInput, conversationContext, actor) {
2107
- const trimmedContext = conversationContext?.trim();
2108
- const currentInstruction = renderCurrentInstruction(userInput, actor);
2109
- if (!trimmedContext) {
2110
- return currentInstruction;
2111
- }
2112
- return [
2113
- renderThreadContextForPrompt(trimmedContext),
2114
- "",
2115
- currentInstruction
2116
- ].join("\n");
2175
+ function buildUserTurnText(userInput, actor) {
2176
+ return renderCurrentInstruction(userInput, actor);
2117
2177
  }
2118
2178
  function renderExplicitSkillInstructions(skill) {
2119
2179
  return [
@@ -2246,24 +2306,24 @@ function buildUserTurnInput(args) {
2246
2306
  })
2247
2307
  });
2248
2308
  }
2249
- return { routerBlocks, userContentParts };
2309
+ return { contextContentParts: [], routerBlocks, userContentParts };
2250
2310
  }
2251
2311
  function buildPromptInput(input) {
2252
2312
  const promptConversationContext = input.piMessages && input.piMessages.length > 0 && !input.includeConversationContextWithPiMessages ? void 0 : input.conversationContext;
2253
- const userTurnText = buildUserTurnText(
2254
- input.messageText,
2255
- promptConversationContext,
2256
- input.actor
2257
- );
2258
- return buildUserTurnInput({
2313
+ const promptInput = buildUserTurnInput({
2259
2314
  omittedImageAttachmentCount: input.omittedImageAttachmentCount ?? 0,
2260
2315
  userAttachments: input.userAttachments,
2261
- userTurnText
2316
+ userTurnText: buildUserTurnText(input.messageText, input.actor)
2262
2317
  });
2318
+ const trimmedContext = promptConversationContext?.trim();
2319
+ return {
2320
+ ...promptInput,
2321
+ contextContentParts: trimmedContext ? [{ type: "text", text: renderThreadContextForPrompt(trimmedContext) }] : []
2322
+ };
2263
2323
  }
2264
2324
  function buildSteeringPiMessage(message) {
2265
2325
  const { userContentParts } = buildUserTurnInput({
2266
- userTurnText: buildUserTurnText(message.text, void 0, message.actor),
2326
+ userTurnText: buildUserTurnText(message.text, message.actor),
2267
2327
  userAttachments: message.userAttachments,
2268
2328
  omittedImageAttachmentCount: message.omittedImageAttachmentCount ?? 0
2269
2329
  });
@@ -2275,19 +2335,25 @@ function buildSteeringPiMessage(message) {
2275
2335
  }
2276
2336
  function withoutTrailingUncheckpointedUserPrompt(messages, userContentParts) {
2277
2337
  if (!messages || messages.length === 0) {
2278
- return [];
2338
+ return { messages: [] };
2279
2339
  }
2280
2340
  const lastMessage = messages.at(-1);
2281
2341
  if (lastMessage?.role !== "user") {
2282
- return messages;
2342
+ return { messages };
2283
2343
  }
2284
2344
  const comparableLastMessage = stripRuntimeTurnContext([
2285
2345
  lastMessage
2286
2346
  ])[0];
2287
2347
  if (!userPromptContentMatches(comparableLastMessage?.content, userContentParts)) {
2288
- return messages;
2348
+ return { messages };
2349
+ }
2350
+ const promptTimestamp = typeof lastMessage.timestamp === "number" ? lastMessage.timestamp : void 0;
2351
+ const withoutPrompt = messages.slice(0, -1);
2352
+ const previousMessage = withoutPrompt.at(-1);
2353
+ if (previousMessage && stripRuntimeTurnContext([previousMessage, lastMessage]).length === 1) {
2354
+ return { messages: withoutPrompt.slice(0, -1), promptTimestamp };
2289
2355
  }
2290
- return messages.slice(0, -1);
2356
+ return { messages: withoutPrompt, promptTimestamp };
2291
2357
  }
2292
2358
  function userPromptContentMatches(storedContent, currentContent) {
2293
2359
  return isDeepStrictEqual(storedContent, currentContent);
@@ -2301,53 +2367,58 @@ function isUserContentPart(value) {
2301
2367
  }
2302
2368
  return value.type === "image" && "data" in value && typeof value.data === "string" && "mimeType" in value && typeof value.mimeType === "string";
2303
2369
  }
2304
- function checkpointedPromptContent(args) {
2305
- if (!args.messages || args.turnStartMessageIndex === void 0 || args.turnStartMessageIndex !== args.messages.length - 1) {
2370
+ function checkpointedPrompt(args) {
2371
+ if (!args.messages || args.turnStartMessageIndex === void 0 || args.turnStartMessageIndex >= args.messages.length) {
2306
2372
  return void 0;
2307
2373
  }
2308
- const message = args.messages[args.turnStartMessageIndex];
2374
+ const turnMessages = args.messages.slice(args.turnStartMessageIndex);
2375
+ const durableTurnMessages = stripRuntimeTurnContext(turnMessages);
2376
+ if (durableTurnMessages.length !== 1) {
2377
+ return void 0;
2378
+ }
2379
+ const message = durableTurnMessages[0];
2309
2380
  if (message?.role !== "user" || !Array.isArray(message.content)) {
2310
2381
  return void 0;
2311
2382
  }
2312
- const durableMessage = stripRuntimeTurnContext([message])[0];
2313
- if (!userPromptContentMatches(durableMessage?.content, args.userContentParts)) {
2383
+ if (!userPromptContentMatches(message.content, args.userContentParts)) {
2314
2384
  return void 0;
2315
2385
  }
2316
- const content = message.content;
2317
- if (!content.every(isUserContentPart)) {
2386
+ if (!message.content.every(isUserContentPart)) {
2318
2387
  return void 0;
2319
2388
  }
2320
- return content;
2389
+ const contextContentParts = retainRuntimeTurnContext(turnMessages).flatMap(
2390
+ (runtimeMessage) => {
2391
+ const content = runtimeMessage.content;
2392
+ return Array.isArray(content) && content.every(isUserContentPart) ? content : [];
2393
+ }
2394
+ );
2395
+ return {
2396
+ contextContentParts,
2397
+ userContentParts: message.content
2398
+ };
2321
2399
  }
2322
2400
  async function assemblePrompt(args) {
2323
2401
  const source = args.routing.source;
2324
2402
  const hasPromptCheckpoint = args.resumedFromSessionRecord && args.existingTurnStartMessageIndex !== void 0;
2325
2403
  const shouldPromptAgent = !args.resumedFromSessionRecord || !hasPromptCheckpoint;
2326
- const requestContentParts = [
2327
- ...args.explicitSkill ? [
2328
- {
2329
- type: "text",
2330
- text: renderExplicitSkillInstructions(args.explicitSkill)
2331
- }
2332
- ] : [],
2333
- ...args.userContentParts
2334
- ];
2335
- const promptHistoryMessages = shouldPromptAgent ? withoutTrailingUncheckpointedUserPrompt(
2404
+ const requestContentParts = args.userContentParts;
2405
+ const trimmedPrompt = shouldPromptAgent ? withoutTrailingUncheckpointedUserPrompt(
2336
2406
  args.priorPiMessages,
2337
2407
  requestContentParts
2338
- ) : args.existingSessionPiMessages;
2339
- const replayedPromptContent = shouldPromptAgent && !args.resumedFromSessionRecord ? checkpointedPromptContent({
2408
+ ) : { messages: args.existingSessionPiMessages };
2409
+ const promptHistoryMessages = trimmedPrompt.messages;
2410
+ const replayedPrompt = shouldPromptAgent && !args.resumedFromSessionRecord ? checkpointedPrompt({
2340
2411
  messages: args.existingSessionPiMessages,
2341
2412
  turnStartMessageIndex: args.existingTurnStartMessageIndex,
2342
2413
  userContentParts: requestContentParts
2343
2414
  }) : void 0;
2344
- const needsBootstrapContextForPrompt = shouldPromptAgent && !replayedPromptContent && !hasRuntimeTurnContext(promptHistoryMessages);
2415
+ const needsBootstrapContextForPrompt = shouldPromptAgent && !replayedPrompt && !hasRuntimeTurnContext(promptHistoryMessages);
2345
2416
  const systemPromptContributions = await getPluginSystemPromptContributions(source);
2346
2417
  const pluginSystemPrompt = buildPluginSystemPromptContributions(
2347
2418
  systemPromptContributions
2348
2419
  );
2349
2420
  const baseInstructions = [buildSystemPrompt({ source }), pluginSystemPrompt].filter((section) => Boolean(section)).join("\n\n");
2350
- const pluginUserPromptContributions = !shouldPromptAgent || replayedPromptContent ? [] : await getPluginUserPromptContributions({
2421
+ const pluginUserPromptContributions = !shouldPromptAgent || replayedPrompt ? [] : await getPluginUserPromptContributions({
2351
2422
  context: args.toolRuntimeContext,
2352
2423
  turnId: args.turnId
2353
2424
  });
@@ -2371,18 +2442,33 @@ async function assemblePrompt(args) {
2371
2442
  configuration: args.configurationValues
2372
2443
  }) : null;
2373
2444
  const turnContextParts = turnContextPrompt ? [{ type: "text", text: turnContextPrompt }] : [];
2374
- const promptContentParts = replayedPromptContent ?? [
2445
+ const contextContentParts = replayedPrompt?.contextContentParts ?? [
2375
2446
  ...turnContextParts,
2376
- ...requestContentParts
2447
+ ...args.explicitSkill ? [
2448
+ {
2449
+ type: "text",
2450
+ text: renderExplicitSkillInstructions(args.explicitSkill)
2451
+ }
2452
+ ] : [],
2453
+ ...args.contextContentParts
2377
2454
  ];
2455
+ const userContentParts = replayedPrompt?.userContentParts ?? requestContentParts;
2378
2456
  const inputMessages = [
2379
2457
  {
2380
2458
  role: "system",
2381
2459
  content: [{ type: "text", text: baseInstructions }]
2382
2460
  },
2461
+ ...contextContentParts.length > 0 ? [
2462
+ {
2463
+ role: "user",
2464
+ content: contextContentParts.map(
2465
+ (part) => toObservablePromptPart(part)
2466
+ )
2467
+ }
2468
+ ] : [],
2383
2469
  {
2384
2470
  role: "user",
2385
- content: promptContentParts.map((part) => toObservablePromptPart(part))
2471
+ content: userContentParts.map((part) => toObservablePromptPart(part))
2386
2472
  }
2387
2473
  ];
2388
2474
  const inputMessagesAttribute = serializeGenAiAttribute(
@@ -2391,9 +2477,17 @@ async function assemblePrompt(args) {
2391
2477
  role: "system",
2392
2478
  parts: [{ type: "text", content: baseInstructions }]
2393
2479
  },
2480
+ ...contextContentParts.length > 0 ? [
2481
+ {
2482
+ role: "user",
2483
+ parts: contextContentParts.flatMap(
2484
+ (part) => part.type === "text" ? [{ type: "text", content: part.text }] : []
2485
+ )
2486
+ }
2487
+ ] : [],
2394
2488
  {
2395
2489
  role: "user",
2396
- parts: promptContentParts.flatMap(
2490
+ parts: userContentParts.flatMap(
2397
2491
  (part) => part.type === "text" ? [{ type: "text", content: part.text }] : []
2398
2492
  )
2399
2493
  }
@@ -2401,14 +2495,16 @@ async function assemblePrompt(args) {
2401
2495
  );
2402
2496
  return {
2403
2497
  baseInstructions,
2498
+ contextContentParts,
2404
2499
  inputMessages,
2405
2500
  inputMessagesAttribute,
2406
- promptContentParts,
2501
+ ...trimmedPrompt.promptTimestamp !== void 0 ? { promptTimestamp: trimmedPrompt.promptTimestamp } : {},
2407
2502
  promptHistoryMessages,
2408
2503
  shouldPromptAgent,
2409
2504
  turnContexts: pluginUserPromptContributions.flatMap(
2410
2505
  (contribution) => contribution.context ? [contribution.context] : []
2411
- )
2506
+ ),
2507
+ userContentParts
2412
2508
  };
2413
2509
  }
2414
2510
 
@@ -2967,8 +3063,8 @@ function sandboxProxyUrl(credentialToken) {
2967
3063
  "Cannot determine base URL for sandbox credential egress (set JUNIOR_BASE_URL or deploy to Vercel)"
2968
3064
  );
2969
3065
  }
2970
- const path9 = credentialToken ? `${SANDBOX_EGRESS_PROXY_PATH}/${credentialToken}` : SANDBOX_EGRESS_PROXY_PATH;
2971
- return new URL(path9, baseUrl).toString();
3066
+ const path8 = credentialToken ? `${SANDBOX_EGRESS_PROXY_PATH}/${credentialToken}` : SANDBOX_EGRESS_PROXY_PATH;
3067
+ return new URL(path8, baseUrl).toString();
2972
3068
  }
2973
3069
  function buildSandboxEgressNetworkPolicy(input) {
2974
3070
  const allow = {
@@ -3776,7 +3872,7 @@ function createSandboxRuntime(options) {
3776
3872
  const commandResult2 = await sandboxInstance.runCommand({
3777
3873
  cmd: "bash",
3778
3874
  args: ["-c", script],
3779
- cwd: SANDBOX_WORKSPACE_ROOT,
3875
+ cwd: input.cwd ?? SANDBOX_WORKSPACE_ROOT,
3780
3876
  signal: controller.signal
3781
3877
  });
3782
3878
  return await readCommandOutput(commandResult2);
@@ -3844,191 +3940,6 @@ function createSandboxRuntime(options) {
3844
3940
  };
3845
3941
  }
3846
3942
 
3847
- // src/chat/tools/sandbox/file-utils.ts
3848
- import path4 from "path";
3849
- var MAX_TEXT_CHARS = 6e4;
3850
- var RIPGREP_EXCLUDED_GLOBS = [
3851
- "!**/.git/**",
3852
- "!**/node_modules/**"
3853
- ];
3854
- var DEFAULT_SEARCH_COMMAND_TIMEOUT_MS = 3e4;
3855
- var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
3856
- function positiveInteger(value) {
3857
- if (typeof value !== "number" || !Number.isFinite(value)) {
3858
- return void 0;
3859
- }
3860
- const integer = Math.floor(value);
3861
- return integer > 0 ? integer : void 0;
3862
- }
3863
- function normalizeToLf(value) {
3864
- return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
3865
- }
3866
- function truncateText(value, maxChars = MAX_TEXT_CHARS) {
3867
- if (value.length <= maxChars) {
3868
- return { content: value, truncated: false };
3869
- }
3870
- const removed = value.length - maxChars;
3871
- return {
3872
- content: `${value.slice(0, maxChars)}
3873
-
3874
- [output truncated: ${removed} characters removed]`,
3875
- truncated: true
3876
- };
3877
- }
3878
- function isMissingPathError(error) {
3879
- if (!error || typeof error !== "object") {
3880
- return false;
3881
- }
3882
- const candidate = error;
3883
- if (candidate.code === "ENOENT") {
3884
- return true;
3885
- }
3886
- return typeof candidate.message === "string" && candidate.message.startsWith("File not found:");
3887
- }
3888
- function missingPathSearchResult(params) {
3889
- const textPath = params.displayPath ?? params.missingPath ?? params.path;
3890
- throw new ToolInputError(`Path not found: ${textPath}`);
3891
- }
3892
- function escapeRegExp(value) {
3893
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3894
- }
3895
- function globToRegExp(pattern) {
3896
- let source = "";
3897
- for (let index = 0; index < pattern.length; index += 1) {
3898
- const char = pattern[index];
3899
- const next = pattern[index + 1];
3900
- if (char === "*" && next === "*") {
3901
- if (pattern[index + 2] === "/") {
3902
- source += "(?:.*/)?";
3903
- index += 2;
3904
- continue;
3905
- }
3906
- source += ".*";
3907
- index += 1;
3908
- continue;
3909
- }
3910
- if (char === "*") {
3911
- source += "[^/]*";
3912
- continue;
3913
- }
3914
- if (char === "?") {
3915
- source += "[^/]";
3916
- continue;
3917
- }
3918
- source += escapeRegExp(char);
3919
- }
3920
- return new RegExp(`^${source}$`);
3921
- }
3922
- function matchesGlob(relativePath, pattern) {
3923
- const matcher = globToRegExp(pattern);
3924
- if (matcher.test(relativePath)) {
3925
- return true;
3926
- }
3927
- if (pattern.startsWith("**/") && matchesGlob(relativePath, pattern.slice(3))) {
3928
- return true;
3929
- }
3930
- return !pattern.includes("/") && matcher.test(path4.posix.basename(relativePath));
3931
- }
3932
- function resolveWorkspacePath(input, fallback = ".") {
3933
- const requested = (input ?? "").trim() || fallback;
3934
- const absolute = requested.startsWith("/") ? requested : path4.posix.join(SANDBOX_WORKSPACE_ROOT, requested);
3935
- const normalized = path4.posix.normalize(absolute);
3936
- if (normalized !== SANDBOX_WORKSPACE_ROOT && !normalized.startsWith(`${SANDBOX_WORKSPACE_ROOT}/`)) {
3937
- throw new ToolInputError(
3938
- `Path must stay within ${SANDBOX_WORKSPACE_ROOT}: ${requested}`
3939
- );
3940
- }
3941
- return normalized;
3942
- }
3943
- function getRipgrepSearchLocation(root, rootIsDirectory) {
3944
- if (rootIsDirectory) {
3945
- return { cwd: root, target: "." };
3946
- }
3947
- return {
3948
- cwd: path4.posix.dirname(root),
3949
- target: path4.posix.basename(root)
3950
- };
3951
- }
3952
- async function collectFiles(params) {
3953
- const files = [];
3954
- let limitReached = false;
3955
- let missingPath;
3956
- const visit = async (dirPath) => {
3957
- if (missingPath) {
3958
- return;
3959
- }
3960
- let entries;
3961
- try {
3962
- entries = (await params.fs.readdir(dirPath)).sort(
3963
- (a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
3964
- );
3965
- } catch (error) {
3966
- if (isMissingPathError(error)) {
3967
- missingPath = dirPath;
3968
- return;
3969
- }
3970
- throw error;
3971
- }
3972
- for (const entry of entries) {
3973
- const fullPath = path4.posix.join(dirPath, entry);
3974
- let stat2;
3975
- try {
3976
- stat2 = await params.fs.stat(fullPath);
3977
- } catch (error) {
3978
- if (isMissingPathError(error)) {
3979
- missingPath = fullPath;
3980
- return;
3981
- }
3982
- throw error;
3983
- }
3984
- if (stat2.isDirectory()) {
3985
- if (!SKIPPED_DIRECTORIES.has(entry)) {
3986
- await visit(fullPath);
3987
- }
3988
- if (limitReached) return;
3989
- continue;
3990
- }
3991
- const relativePath = path4.posix.relative(params.root, fullPath);
3992
- if (!params.pattern || matchesGlob(relativePath, params.pattern)) {
3993
- files.push(fullPath);
3994
- if (params.limit && files.length >= params.limit) {
3995
- limitReached = true;
3996
- return;
3997
- }
3998
- }
3999
- }
4000
- };
4001
- let stat;
4002
- try {
4003
- stat = await params.fs.stat(params.root);
4004
- } catch (error) {
4005
- if (isMissingPathError(error)) {
4006
- return {
4007
- files,
4008
- limitReached: false,
4009
- missingPath: params.root,
4010
- missingRoot: true
4011
- };
4012
- }
4013
- throw error;
4014
- }
4015
- if (!stat.isDirectory()) {
4016
- const relativePath = path4.posix.basename(params.root);
4017
- return {
4018
- files: !params.pattern || matchesGlob(relativePath, params.pattern) ? [params.root] : [],
4019
- limitReached: false,
4020
- missingRoot: false
4021
- };
4022
- }
4023
- await visit(params.root);
4024
- return {
4025
- files,
4026
- limitReached,
4027
- missingPath,
4028
- missingRoot: missingPath === params.root
4029
- };
4030
- }
4031
-
4032
3943
  // src/chat/tools/sandbox/text-edits.ts
4033
3944
  var MAX_DIFF_LINE_CHARS = 4e3;
4034
3945
  function truncateDiffLine(value) {
@@ -4094,12 +4005,12 @@ function prepareTextReplacementArguments(input) {
4094
4005
  if (!edit || typeof edit !== "object") {
4095
4006
  return edit;
4096
4007
  }
4097
- const record = edit;
4098
- const { old_text, new_text, ...rest } = record;
4008
+ const record2 = edit;
4009
+ const { old_text, new_text, ...rest } = record2;
4099
4010
  return {
4100
4011
  ...rest,
4101
- oldText: record.oldText ?? old_text,
4102
- newText: record.newText ?? new_text
4012
+ oldText: record2.oldText ?? old_text,
4013
+ newText: record2.newText ?? new_text
4103
4014
  };
4104
4015
  });
4105
4016
  }
@@ -4242,8 +4153,8 @@ function parseToolContent(content) {
4242
4153
  }
4243
4154
  return content;
4244
4155
  }
4245
- function formatZodPath(path9) {
4246
- return path9.length > 0 ? path9.map(String).join(".") : "root";
4156
+ function formatZodPath(path8) {
4157
+ return path8.length > 0 ? path8.map(String).join(".") : "root";
4247
4158
  }
4248
4159
  function formatToolInputError(error) {
4249
4160
  const details = error.issues.slice(0, 5).map((issue) => `${formatZodPath(issue.path)}: ${issue.message}`).join("; ");
@@ -4390,7 +4301,7 @@ function createEditFileTool() {
4390
4301
  }
4391
4302
 
4392
4303
  // src/chat/tools/sandbox/find-files.ts
4393
- import path5 from "path";
4304
+ import path4 from "path";
4394
4305
  import { z as z5 } from "zod";
4395
4306
  var DEFAULT_FIND_LIMIT = 100;
4396
4307
  async function findFiles(params) {
@@ -4423,7 +4334,7 @@ async function findFiles(params) {
4423
4334
  });
4424
4335
  }
4425
4336
  const relativePaths = files.map(
4426
- (filePath) => path5.posix.relative(root, filePath)
4337
+ (filePath) => path4.posix.relative(root, filePath)
4427
4338
  );
4428
4339
  const bounded = truncateText(
4429
4340
  relativePaths.length > 0 ? relativePaths.join("\n") : "No files found matching pattern"
@@ -4509,8 +4420,8 @@ async function findFilesWithRipgrep(params) {
4509
4420
  throw new Error(`ripgrep file search failed: ${detail}`);
4510
4421
  }
4511
4422
  const allPaths = result.stdout.split("\0").filter(Boolean).map((filePath) => {
4512
- const absolute = filePath.startsWith("/") ? path5.posix.normalize(filePath) : path5.posix.resolve(location.cwd, filePath);
4513
- return rootIsDirectory ? path5.posix.relative(params.root, absolute) : path5.posix.basename(absolute);
4423
+ const absolute = filePath.startsWith("/") ? path4.posix.normalize(filePath) : path4.posix.resolve(location.cwd, filePath);
4424
+ return rootIsDirectory ? path4.posix.relative(params.root, absolute) : path4.posix.basename(absolute);
4514
4425
  });
4515
4426
  const limitReached = allPaths.length > params.limit;
4516
4427
  const relativePaths = allPaths.slice(0, params.limit);
@@ -4583,7 +4494,7 @@ function createFindFilesTool() {
4583
4494
  }
4584
4495
 
4585
4496
  // src/chat/tools/sandbox/grep.ts
4586
- import path6 from "path";
4497
+ import path5 from "path";
4587
4498
  import { z as z6 } from "zod";
4588
4499
  var DEFAULT_GREP_LIMIT = 100;
4589
4500
  var MAX_GREP_LINE_CHARS = 500;
@@ -4674,7 +4585,7 @@ async function grepFiles(params) {
4674
4585
  continue;
4675
4586
  }
4676
4587
  const lines = normalizeToLf(content).split("\n");
4677
- const relativePath = files.length === 1 && filePath === root ? path6.posix.basename(filePath) : path6.posix.relative(root, filePath);
4588
+ const relativePath = files.length === 1 && filePath === root ? path5.posix.basename(filePath) : path5.posix.relative(root, filePath);
4678
4589
  const matchedLines = [];
4679
4590
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
4680
4591
  if (!lineMatches({
@@ -4813,37 +4724,37 @@ async function grepFilesWithRipgrep(params) {
4813
4724
  if (!rawLine) {
4814
4725
  continue;
4815
4726
  }
4816
- let record;
4727
+ let record2;
4817
4728
  try {
4818
- record = JSON.parse(rawLine);
4729
+ record2 = JSON.parse(rawLine);
4819
4730
  } catch (error) {
4820
4731
  throw new Error("ripgrep returned invalid JSON output", { cause: error });
4821
4732
  }
4822
4733
  parsedRecordCount += 1;
4823
- if (record.type !== "match" && record.type !== "context") {
4734
+ if (record2.type !== "match" && record2.type !== "context") {
4824
4735
  continue;
4825
4736
  }
4826
- const line = record.data?.line_number;
4827
- const rawPath = decodeRipgrepText(record.data?.path);
4737
+ const line = record2.data?.line_number;
4738
+ const rawPath = decodeRipgrepText(record2.data?.path);
4828
4739
  if (!line || !rawPath) {
4829
4740
  continue;
4830
4741
  }
4831
- const absolutePath = rawPath.startsWith("/") ? path6.posix.normalize(rawPath) : path6.posix.resolve(location.cwd, rawPath);
4832
- const displayPath = rootIsDirectory ? path6.posix.relative(params.root, absolutePath) : path6.posix.basename(absolutePath);
4742
+ const absolutePath = rawPath.startsWith("/") ? path5.posix.normalize(rawPath) : path5.posix.resolve(location.cwd, rawPath);
4743
+ const displayPath = rootIsDirectory ? path5.posix.relative(params.root, absolutePath) : path5.posix.basename(absolutePath);
4833
4744
  let records = recordsByPath.get(displayPath);
4834
4745
  if (!records) {
4835
4746
  records = [];
4836
4747
  recordsByPath.set(displayPath, records);
4837
4748
  pathOrder.push(displayPath);
4838
4749
  }
4839
- const matched = record.type === "match";
4750
+ const matched = record2.type === "match";
4840
4751
  if (matched) {
4841
4752
  totalMatches += 1;
4842
4753
  }
4843
4754
  records.push({
4844
4755
  line,
4845
4756
  matched,
4846
- text: decodeRipgrepText(record.data?.lines).replace(/\r?\n$/, "")
4757
+ text: decodeRipgrepText(record2.data?.lines).replace(/\r?\n$/, "")
4847
4758
  });
4848
4759
  }
4849
4760
  let remainingMatches = params.limit;
@@ -4852,9 +4763,9 @@ async function grepFilesWithRipgrep(params) {
4852
4763
  for (const displayPath of pathOrder) {
4853
4764
  const records = recordsByPath.get(displayPath) ?? [];
4854
4765
  const selectedMatchLines = [];
4855
- for (const record of records) {
4856
- if (record.matched && remainingMatches > 0) {
4857
- selectedMatchLines.push(record.line);
4766
+ for (const record2 of records) {
4767
+ if (record2.matched && remainingMatches > 0) {
4768
+ selectedMatchLines.push(record2.line);
4858
4769
  remainingMatches -= 1;
4859
4770
  }
4860
4771
  }
@@ -4863,22 +4774,22 @@ async function grepFilesWithRipgrep(params) {
4863
4774
  }
4864
4775
  const selectedMatchSet = new Set(selectedMatchLines);
4865
4776
  const emittedLines = /* @__PURE__ */ new Set();
4866
- for (const record of records) {
4867
- if (emittedLines.has(record.line)) {
4777
+ for (const record2 of records) {
4778
+ if (emittedLines.has(record2.line)) {
4868
4779
  continue;
4869
4780
  }
4870
4781
  const include = selectedMatchLines.some(
4871
- (matchLine) => Math.abs(record.line - matchLine) <= params.context
4782
+ (matchLine) => Math.abs(record2.line - matchLine) <= params.context
4872
4783
  );
4873
4784
  if (!include) {
4874
4785
  continue;
4875
4786
  }
4876
- emittedLines.add(record.line);
4877
- const truncated = truncateGrepLine(record.text);
4787
+ emittedLines.add(record2.line);
4788
+ const truncated = truncateGrepLine(record2.text);
4878
4789
  lineTruncated ||= truncated.truncated;
4879
- const separator = selectedMatchSet.has(record.line) ? ":" : "-";
4790
+ const separator = selectedMatchSet.has(record2.line) ? ":" : "-";
4880
4791
  output.push(
4881
- `${displayPath}${separator}${record.line}${separator} ${truncated.line}`
4792
+ `${displayPath}${separator}${record2.line}${separator} ${truncated.line}`
4882
4793
  );
4883
4794
  }
4884
4795
  }
@@ -4961,7 +4872,7 @@ function createGrepTool() {
4961
4872
  }
4962
4873
 
4963
4874
  // src/chat/tools/sandbox/list-dir.ts
4964
- import path7 from "path";
4875
+ import path6 from "path";
4965
4876
  import { z as z7 } from "zod";
4966
4877
  var DEFAULT_LIST_LIMIT = 500;
4967
4878
  async function listDir(params) {
@@ -4997,7 +4908,7 @@ async function listDir(params) {
4997
4908
  entryLimitReached = true;
4998
4909
  break;
4999
4910
  }
5000
- const entryPath = path7.posix.join(dirPath, entry);
4911
+ const entryPath = path6.posix.join(dirPath, entry);
5001
4912
  try {
5002
4913
  const entryStat = await params.fs.stat(entryPath);
5003
4914
  output.push(`${entry}${entryStat.isDirectory() ? "/" : ""}`);
@@ -5139,8 +5050,8 @@ function sliceFileContent(params) {
5139
5050
  } : {}
5140
5051
  });
5141
5052
  }
5142
- function missingFileResult(path9) {
5143
- throw new ToolInputError(`File not found: ${path9}`);
5053
+ function missingFileResult(path8) {
5054
+ throw new ToolInputError(`File not found: ${path8}`);
5144
5055
  }
5145
5056
 
5146
5057
  // src/chat/tools/sandbox/read-file.ts
@@ -5324,6 +5235,7 @@ function createSandbox(options) {
5324
5235
  };
5325
5236
  const executeBashTool = async (rawInput, command, context) => {
5326
5237
  const env = parseEnv(rawInput.env);
5238
+ const cwd = typeof rawInput.cwd === "string" ? resolveWorkspacePath(rawInput.cwd) : SANDBOX_WORKSPACE_ROOT;
5327
5239
  const timeoutMs = positiveInteger(rawInput.timeoutMs);
5328
5240
  logSandboxBootRequest("tool.bash", {
5329
5241
  "app.sandbox.command_length": command.length
@@ -5340,6 +5252,7 @@ function createSandbox(options) {
5340
5252
  try {
5341
5253
  const response = await executeBash({
5342
5254
  command,
5255
+ cwd,
5343
5256
  ...env ? { env } : {},
5344
5257
  ...timeoutMs ? { timeoutMs } : {},
5345
5258
  ...context.signal ? { signal: context.signal } : {}
@@ -5371,7 +5284,7 @@ function createSandbox(options) {
5371
5284
  return formatSandboxCommandResult({
5372
5285
  ok: result.exitCode === 0,
5373
5286
  command,
5374
- cwd: SANDBOX_WORKSPACE_ROOT,
5287
+ cwd,
5375
5288
  exit_code: result.exitCode,
5376
5289
  signal: null,
5377
5290
  timed_out: Boolean(result.timedOut),
@@ -5695,6 +5608,18 @@ function createSandbox(options) {
5695
5608
  })
5696
5609
  };
5697
5610
  return {
5611
+ async captureRepositoryInstructions() {
5612
+ if (!runtime.sandboxRef()) {
5613
+ return void 0;
5614
+ }
5615
+ const { fs: fs5 } = await runtime.tools();
5616
+ const selected = await findSingleRepositoryDirectory(fs5);
5617
+ if (!selected) return void 0;
5618
+ return await resolveRepositoryInstructions({
5619
+ cwd: selected,
5620
+ fs: fs5
5621
+ });
5622
+ },
5698
5623
  workspace,
5699
5624
  tools: {
5700
5625
  supports(toolName) {
@@ -5712,7 +5637,7 @@ function createSandbox(options) {
5712
5637
  }
5713
5638
 
5714
5639
  // src/chat/tools/sandbox/file-uploads.ts
5715
- import path8 from "path";
5640
+ import path7 from "path";
5716
5641
  import { z as z9 } from "zod";
5717
5642
  var MAX_SANDBOX_FILE_UPLOAD_BYTES = 10 * 1024 * 1024;
5718
5643
  var SANDBOX_ARTIFACTS_DIR = "/tmp/junior/artifacts";
@@ -5748,9 +5673,9 @@ var MIME_BY_EXTENSION = {
5748
5673
  ".log": "text/plain"
5749
5674
  };
5750
5675
  var SandboxFileNotFoundError = class extends ToolInputError {
5751
- constructor(path9) {
5752
- super(`failed to read file: ${path9}`);
5753
- this.path = path9;
5676
+ constructor(path8) {
5677
+ super(`failed to read file: ${path8}`);
5678
+ this.path = path8;
5754
5679
  }
5755
5680
  path;
5756
5681
  };
@@ -5759,27 +5684,27 @@ function normalizeSandboxPath(inputPath) {
5759
5684
  if (!trimmed) {
5760
5685
  throw new Error("path is required");
5761
5686
  }
5762
- if (path8.posix.isAbsolute(trimmed)) {
5687
+ if (path7.posix.isAbsolute(trimmed)) {
5763
5688
  return trimmed;
5764
5689
  }
5765
- return path8.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
5690
+ return path7.posix.join(SANDBOX_WORKSPACE_ROOT, trimmed);
5766
5691
  }
5767
5692
  function sanitizeFilename(value, fallbackPath) {
5768
5693
  const candidate = (value ?? "").trim();
5769
5694
  if (candidate) {
5770
- const base = path8.posix.basename(candidate);
5695
+ const base = path7.posix.basename(candidate);
5771
5696
  if (base && base !== "." && base !== "..") {
5772
5697
  return base;
5773
5698
  }
5774
5699
  }
5775
- const derived = path8.posix.basename(fallbackPath);
5700
+ const derived = path7.posix.basename(fallbackPath);
5776
5701
  if (derived && derived !== "." && derived !== "..") {
5777
5702
  return derived;
5778
5703
  }
5779
5704
  return "attachment.bin";
5780
5705
  }
5781
5706
  function sandboxArtifactPath(filename) {
5782
- return path8.posix.join(
5707
+ return path7.posix.join(
5783
5708
  SANDBOX_ARTIFACTS_DIR,
5784
5709
  sanitizeFilename(filename, "artifact.bin")
5785
5710
  );
@@ -5789,7 +5714,7 @@ function inferMimeType(filename, explicitMimeType) {
5789
5714
  if (explicit) {
5790
5715
  return explicit;
5791
5716
  }
5792
- const ext = path8.extname(filename).toLowerCase();
5717
+ const ext = path7.extname(filename).toLowerCase();
5793
5718
  return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
5794
5719
  }
5795
5720
  async function detectMimeType(workspace, targetPath) {
@@ -5880,94 +5805,321 @@ async function writeSandboxGeneratedArtifacts(workspace, files) {
5880
5805
  return artifacts.map((artifact) => artifact.ref);
5881
5806
  }
5882
5807
 
5883
- // src/chat/agent/sandbox.ts
5884
- function bashCommand(input) {
5885
- if (!input || typeof input !== "object" || Array.isArray(input)) {
5886
- return void 0;
5887
- }
5888
- const command = input.command;
5889
- return typeof command === "string" && command.trim() ? command.trim() : void 0;
5890
- }
5891
- function createAgentSandbox(options) {
5892
- const sandbox = createSandbox({
5893
- sandboxRef: options.sandboxRef,
5894
- skills: options.skills,
5895
- referenceFiles: listReferenceFiles(),
5896
- traceContext: options.traceContext,
5897
- tracePropagation: options.tracePropagation,
5898
- egressSignals: options.egressSignals,
5899
- credentialEgress: options.credentialEgress,
5900
- prepare: options.prepareSandbox,
5901
- onSandboxRefChanged: async (sandboxRef) => {
5902
- options.onSandboxRefChanged(sandboxRef);
5903
- await options.persistSandboxRef?.(sandboxRef);
5808
+ // src/chat/tool-support/normalize-result.ts
5809
+ function isToolContent(value) {
5810
+ return Array.isArray(value) && value.every((part) => {
5811
+ if (!part || typeof part !== "object") {
5812
+ return false;
5904
5813
  }
5905
- });
5906
- return {
5907
- workspace: sandbox.workspace,
5908
- sandboxRef: sandbox.sandboxRef,
5909
- close: sandbox.close,
5910
- tools: {
5911
- supports: sandbox.tools.supports,
5912
- async execute(params) {
5913
- const command = params.toolName === "bash" ? bashCommand(params.input) : void 0;
5914
- if (command) {
5915
- const result = await maybeExecuteJrRpcCustomCommand(command, {
5916
- activeSkill: options.getActiveSkill(),
5917
- channelConfiguration: options.channelConfiguration,
5918
- actorId: isUserActor(options.actor) ? options.actor.userId : void 0,
5919
- onConfigurationValueChanged: (key, value) => {
5920
- if (value === void 0) {
5921
- delete options.configurationValues[key];
5922
- return;
5923
- }
5924
- options.configurationValues[key] = value;
5925
- }
5926
- });
5927
- if (result.handled) {
5928
- return formatSandboxCommandResult(result.result);
5929
- }
5930
- }
5931
- return await sandbox.tools.execute(params);
5932
- }
5933
- },
5934
- async writeGeneratedArtifacts(files) {
5935
- return await writeSandboxGeneratedArtifacts(sandbox.workspace, files);
5814
+ const record2 = part;
5815
+ if (record2.type === "text") {
5816
+ return typeof record2.text === "string";
5936
5817
  }
5937
- };
5818
+ if (record2.type === "image") {
5819
+ return typeof record2.data === "string" && typeof record2.mimeType === "string";
5820
+ }
5821
+ return false;
5822
+ });
5938
5823
  }
5939
-
5940
- // src/chat/mcp/tool-manager.ts
5941
- import {
5942
- missingToolAnnotationKeys
5943
- } from "@sentry/junior-plugin-api";
5944
-
5945
- // src/chat/mcp/client.ts
5946
- import { AsyncLocalStorage } from "async_hooks";
5947
- import { Client } from "@modelcontextprotocol/sdk/client";
5948
- import {
5949
- UnauthorizedError
5950
- } from "@modelcontextprotocol/sdk/client/auth.js";
5951
- import {
5952
- StreamableHTTPClientTransport,
5953
- StreamableHTTPError
5954
- } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5955
- var MCP_CLIENT_INFO = {
5956
- name: "junior-mcp-client",
5957
- version: "1.0.0"
5958
- };
5959
- var MCP_TOOLS_CALL_METHOD = "tools/call";
5960
- function getDefaultPort(url) {
5961
- if (url.protocol === "https:") {
5962
- return 443;
5963
- }
5964
- if (url.protocol === "http:") {
5965
- return 80;
5966
- }
5967
- return void 0;
5824
+ function isStructuredToolExecutionResult(value) {
5825
+ const content = value?.content;
5826
+ return typeof value === "object" && value !== null && isToolContent(content) && "details" in value;
5968
5827
  }
5969
- function getMcpNetworkAttributes(url) {
5970
- const port = url.port ? Number(url.port) : getDefaultPort(url);
5828
+ function isContentOnlyToolExecutionResult(value) {
5829
+ const content = value?.content;
5830
+ return typeof value === "object" && value !== null && isToolContent(content) && !("details" in value);
5831
+ }
5832
+ function toToolContentText(value) {
5833
+ if (typeof value === "string") return value;
5834
+ try {
5835
+ return JSON.stringify(value);
5836
+ } catch {
5837
+ return String(value);
5838
+ }
5839
+ }
5840
+ function withoutFirstGeneratedTextContent(content) {
5841
+ const [first, ...rest] = content;
5842
+ if (first?.type === "text") {
5843
+ return rest;
5844
+ }
5845
+ return content;
5846
+ }
5847
+ function replaceGeneratedTextContent(content, text) {
5848
+ return [
5849
+ {
5850
+ type: "text",
5851
+ text
5852
+ },
5853
+ ...withoutFirstGeneratedTextContent(content)
5854
+ ];
5855
+ }
5856
+ function isRecord3(value) {
5857
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
5858
+ }
5859
+ function hasContinuation(value) {
5860
+ return isRecord3(value) && isRecord3(value.continuation);
5861
+ }
5862
+ function stringField(record2, key) {
5863
+ const value = record2[key];
5864
+ return typeof value === "string" ? value : "";
5865
+ }
5866
+ function numberField(record2, key) {
5867
+ const value = record2[key];
5868
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
5869
+ }
5870
+ function stringListField(record2, key) {
5871
+ const value = record2[key];
5872
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
5873
+ }
5874
+ function accountText(value) {
5875
+ if (!isRecord3(value)) {
5876
+ return void 0;
5877
+ }
5878
+ const label = stringField(value, "label") || stringField(value, "id");
5879
+ const id = stringField(value, "id");
5880
+ if (!label) {
5881
+ return void 0;
5882
+ }
5883
+ return id && id !== label ? `${label} (${id})` : label;
5884
+ }
5885
+ function upstreamPermissionDeniedText(value) {
5886
+ if (!isRecord3(value) || !isRecord3(value.permission_denied)) {
5887
+ return void 0;
5888
+ }
5889
+ const signal = value.permission_denied;
5890
+ if (signal.source !== "upstream") {
5891
+ return void 0;
5892
+ }
5893
+ const provider = stringField(signal, "provider");
5894
+ const message = stringField(signal, "message");
5895
+ const upstreamHost = stringField(signal, "upstreamHost");
5896
+ const upstreamPath = stringField(signal, "upstreamPath");
5897
+ const status = numberField(signal, "status");
5898
+ if (!provider || !message || !upstreamHost || !upstreamPath || !status) {
5899
+ return void 0;
5900
+ }
5901
+ const grant = isRecord3(signal.grant) ? signal.grant : {};
5902
+ const grantName = stringField(grant, "name");
5903
+ const grantAccess = stringField(grant, "access");
5904
+ const grantReason = stringField(grant, "reason");
5905
+ const grantRequirements = stringListField(grant, "requirements");
5906
+ const account = accountText(signal.account);
5907
+ const command = stringField(value, "command");
5908
+ const stderr = stringField(value, "stderr").trim();
5909
+ const stdout = stringField(value, "stdout").trim();
5910
+ const acceptedPermissions = stringField(signal, "acceptedPermissions");
5911
+ const sso = stringField(signal, "sso");
5912
+ return [
5913
+ "Upstream permission denied.",
5914
+ message,
5915
+ "",
5916
+ `Provider: ${provider}`,
5917
+ ...account ? [`Provider account: ${account}`] : [],
5918
+ `Grant: ${grantName || "unknown"}${grantAccess ? ` (${grantAccess}${grantReason ? `, ${grantReason}` : ""})` : ""}`,
5919
+ ...grantRequirements.length > 0 ? [
5920
+ "Provider requirements:",
5921
+ ...grantRequirements.map((item) => `- ${item}`)
5922
+ ] : [],
5923
+ `Upstream: ${upstreamHost}${upstreamPath}`,
5924
+ `Status: ${status}`,
5925
+ ...acceptedPermissions ? [`Accepted provider permissions: ${acceptedPermissions}`] : [],
5926
+ ...sso ? [`Provider SSO: ${sso}`] : [],
5927
+ ...command ? [`Command: ${command}`] : [],
5928
+ "",
5929
+ "Junior had a credential lease for this grant and forwarded the request. Do not diagnose this as a missing user token or a local Junior runtime block; diagnose provider-side permissions, installation scope, SSO, or actor-provider account access.",
5930
+ ...stderr ? ["", `stderr:
5931
+ ${stderr}`] : [],
5932
+ ...stdout ? ["", `stdout:
5933
+ ${stdout}`] : []
5934
+ ].join("\n");
5935
+ }
5936
+ function normalizeDetails(details, options) {
5937
+ const continuationToolName = options.toolName && hasContinuation(details) ? options.toolName : void 0;
5938
+ if (!continuationToolName) {
5939
+ return { details, replaceEnvelopeText: false };
5940
+ }
5941
+ return {
5942
+ details: injectContinuationToolName(details, continuationToolName),
5943
+ replaceEnvelopeText: true
5944
+ };
5945
+ }
5946
+ function normalizeToolResult(result, options = {}) {
5947
+ const unwrapped = result;
5948
+ if (isStructuredToolExecutionResult(unwrapped)) {
5949
+ const normalized2 = normalizeDetails(unwrapped.details, options);
5950
+ const permissionText = upstreamPermissionDeniedText(normalized2.details);
5951
+ if (!permissionText && normalized2.details === unwrapped.details) {
5952
+ return unwrapped;
5953
+ }
5954
+ const content = permissionText || normalized2.replaceEnvelopeText ? replaceGeneratedTextContent(
5955
+ unwrapped.content,
5956
+ permissionText ?? toToolContentText(normalized2.details)
5957
+ ) : unwrapped.content;
5958
+ return {
5959
+ content,
5960
+ details: normalized2.details
5961
+ };
5962
+ }
5963
+ if (isContentOnlyToolExecutionResult(unwrapped)) {
5964
+ if (options.requireStructuredResult) {
5965
+ throw new TypeError(
5966
+ "Structured tools must return details matching their outputSchema."
5967
+ );
5968
+ }
5969
+ return {
5970
+ content: unwrapped.content,
5971
+ details: {}
5972
+ };
5973
+ }
5974
+ const normalized = normalizeDetails(unwrapped, options);
5975
+ return {
5976
+ content: [
5977
+ {
5978
+ type: "text",
5979
+ text: upstreamPermissionDeniedText(normalized.details) ?? toToolContentText(normalized.details)
5980
+ }
5981
+ ],
5982
+ details: normalized.details
5983
+ };
5984
+ }
5985
+
5986
+ // src/chat/agent/sandbox.ts
5987
+ function record(value) {
5988
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
5989
+ }
5990
+ function textContent(value) {
5991
+ return Array.isArray(value) ? value.flatMap((part) => {
5992
+ const item = record(part);
5993
+ return item.type === "text" && typeof item.text === "string" ? [item.text] : [];
5994
+ }).join("\n") : "";
5995
+ }
5996
+ function createPluginToolSandbox(sandbox, options) {
5997
+ return {
5998
+ root: SANDBOX_WORKSPACE_ROOT,
5999
+ juniorRoot: `${SANDBOX_WORKSPACE_ROOT}/.junior`,
6000
+ async readFile(filePath) {
6001
+ return await sandbox.workspace.readFileToBuffer({ path: filePath }) ?? null;
6002
+ },
6003
+ async run(input) {
6004
+ input.signal?.throwIfAborted();
6005
+ const result = await sandbox.tools.execute({
6006
+ toolName: "bash",
6007
+ input: {
6008
+ command: `${input.sudo ? "sudo -- " : ""}${buildCommandScript(input)}`,
6009
+ ...input.cwd ? { cwd: input.cwd } : {},
6010
+ ...input.env ? { env: input.env } : {}
6011
+ },
6012
+ ...input.signal ? { signal: input.signal } : {}
6013
+ });
6014
+ const normalized = normalizeToolResult(result, { toolName: "bash" });
6015
+ await options.handleAuthSignal(normalized.details);
6016
+ input.signal?.throwIfAborted();
6017
+ const details = record(normalized.details);
6018
+ return {
6019
+ exitCode: typeof details.exit_code === "number" ? details.exit_code : 1,
6020
+ stdout: typeof details.stdout === "string" ? details.stdout : "",
6021
+ stderr: details.permission_denied !== void 0 ? textContent(normalized.content) : typeof details.stderr === "string" ? details.stderr : ""
6022
+ };
6023
+ },
6024
+ async writeFile(input) {
6025
+ await sandbox.workspace.writeFiles([
6026
+ {
6027
+ path: input.path,
6028
+ content: input.content,
6029
+ ...input.mode !== void 0 ? { mode: input.mode } : {}
6030
+ }
6031
+ ]);
6032
+ }
6033
+ };
6034
+ }
6035
+ function bashCommand(input) {
6036
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
6037
+ return void 0;
6038
+ }
6039
+ const command = input.command;
6040
+ return typeof command === "string" && command.trim() ? command.trim() : void 0;
6041
+ }
6042
+ function createAgentSandbox(options) {
6043
+ const sandbox = createSandbox({
6044
+ sandboxRef: options.sandboxRef,
6045
+ skills: options.skills,
6046
+ referenceFiles: listReferenceFiles(),
6047
+ traceContext: options.traceContext,
6048
+ tracePropagation: options.tracePropagation,
6049
+ egressSignals: options.egressSignals,
6050
+ credentialEgress: options.credentialEgress,
6051
+ prepare: options.prepareSandbox,
6052
+ onSandboxRefChanged: async (sandboxRef) => {
6053
+ options.onSandboxRefChanged(sandboxRef);
6054
+ await options.persistSandboxRef?.(sandboxRef);
6055
+ }
6056
+ });
6057
+ return {
6058
+ captureRepositoryInstructions: sandbox.captureRepositoryInstructions,
6059
+ workspace: sandbox.workspace,
6060
+ sandboxRef: sandbox.sandboxRef,
6061
+ close: sandbox.close,
6062
+ tools: {
6063
+ supports: sandbox.tools.supports,
6064
+ async execute(params) {
6065
+ const command = params.toolName === "bash" ? bashCommand(params.input) : void 0;
6066
+ if (command) {
6067
+ const result = await maybeExecuteJrRpcCustomCommand(command, {
6068
+ activeSkill: options.getActiveSkill(),
6069
+ channelConfiguration: options.channelConfiguration,
6070
+ actorId: isUserActor(options.actor) ? options.actor.userId : void 0,
6071
+ onConfigurationValueChanged: (key, value) => {
6072
+ if (value === void 0) {
6073
+ delete options.configurationValues[key];
6074
+ return;
6075
+ }
6076
+ options.configurationValues[key] = value;
6077
+ }
6078
+ });
6079
+ if (result.handled) {
6080
+ return formatSandboxCommandResult(result.result);
6081
+ }
6082
+ }
6083
+ return await sandbox.tools.execute(params);
6084
+ }
6085
+ },
6086
+ async writeGeneratedArtifacts(files) {
6087
+ return await writeSandboxGeneratedArtifacts(sandbox.workspace, files);
6088
+ }
6089
+ };
6090
+ }
6091
+
6092
+ // src/chat/mcp/tool-manager.ts
6093
+ import {
6094
+ missingToolAnnotationKeys
6095
+ } from "@sentry/junior-plugin-api";
6096
+
6097
+ // src/chat/mcp/client.ts
6098
+ import { AsyncLocalStorage } from "async_hooks";
6099
+ import { Client } from "@modelcontextprotocol/sdk/client";
6100
+ import {
6101
+ UnauthorizedError
6102
+ } from "@modelcontextprotocol/sdk/client/auth.js";
6103
+ import {
6104
+ StreamableHTTPClientTransport,
6105
+ StreamableHTTPError
6106
+ } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
6107
+ var MCP_CLIENT_INFO = {
6108
+ name: "junior-mcp-client",
6109
+ version: "1.0.0"
6110
+ };
6111
+ var MCP_TOOLS_CALL_METHOD = "tools/call";
6112
+ function getDefaultPort(url) {
6113
+ if (url.protocol === "https:") {
6114
+ return 443;
6115
+ }
6116
+ if (url.protocol === "http:") {
6117
+ return 80;
6118
+ }
6119
+ return void 0;
6120
+ }
6121
+ function getMcpNetworkAttributes(url) {
6122
+ const port = url.port ? Number(url.port) : getDefaultPort(url);
5971
6123
  return {
5972
6124
  "server.address": url.hostname,
5973
6125
  ...port !== void 0 ? { "server.port": port } : {},
@@ -6762,6 +6914,7 @@ function createBashTool() {
6762
6914
  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.",
6763
6915
  inputSchema: z10.object({
6764
6916
  command: z10.string().min(1).describe("Bash command to run inside the sandbox."),
6917
+ cwd: z10.string().min(1).describe("Optional working directory for this command only.").optional(),
6765
6918
  timeoutMs: z10.coerce.number().int().min(1e3).describe(
6766
6919
  "Optional command timeout in milliseconds. Use for commands that may hang."
6767
6920
  ).optional()
@@ -7355,13 +7508,13 @@ function createViewImageTool(sandbox, supportsImageInput, deps = {}) {
7355
7508
  description: "View an image file from the sandbox workspace when visual inspection is needed. Supports PNG, JPEG, GIF, and WebP images up to 5 MB.",
7356
7509
  exposure: "direct",
7357
7510
  inputSchema: viewImageInputSchema,
7358
- async execute({ path: path9 }) {
7511
+ async execute({ path: path8 }) {
7359
7512
  if (!supportsImageInput()) {
7360
7513
  throw new ToolInputError(
7361
7514
  "viewImage is unavailable because the current model does not support image inputs"
7362
7515
  );
7363
7516
  }
7364
- const targetPath = normalizeSandboxPath(path9);
7517
+ const targetPath = normalizeSandboxPath(path8);
7365
7518
  const data = deps.readFile ? await deps.readFile(targetPath) : await sandbox.readFileToBuffer({ path: targetPath });
7366
7519
  if (!data) {
7367
7520
  throw new SandboxFileNotFoundError(targetPath);
@@ -7618,8 +7771,8 @@ async function indexTtlMs(state, subscriptionIds, nowMs) {
7618
7771
  const latestExpiresAtMs = Math.max(
7619
7772
  nowMs,
7620
7773
  ...records.filter(
7621
- (record) => record !== void 0 && activeAt(record, nowMs)
7622
- ).map((record) => record.expiresAtMs)
7774
+ (record2) => record2 !== void 0 && activeAt(record2, nowMs)
7775
+ ).map((record2) => record2.expiresAtMs)
7623
7776
  );
7624
7777
  return ttlUntil(latestExpiresAtMs, nowMs);
7625
7778
  }
@@ -7655,7 +7808,7 @@ async function createResourceEventSubscription(input, options = {}) {
7655
7808
  identifier: input.identifier,
7656
7809
  teamId: subscriptionTeamId(input.destination)
7657
7810
  });
7658
- const record = {
7811
+ const record2 = {
7659
7812
  conversationId: input.conversationId,
7660
7813
  createdAtMs: nowMs,
7661
7814
  destination: input.destination,
@@ -7670,7 +7823,7 @@ async function createResourceEventSubscription(input, options = {}) {
7670
7823
  status: "active",
7671
7824
  updatedAtMs: nowMs
7672
7825
  };
7673
- const parsed = subscriptionSchema.parse(record);
7826
+ const parsed = subscriptionSchema.parse(record2);
7674
7827
  await state.set(
7675
7828
  subscriptionKey(id),
7676
7829
  parsed,
@@ -7708,7 +7861,7 @@ async function listResourceEventSubscriptions(input) {
7708
7861
  )
7709
7862
  );
7710
7863
  return records.filter(
7711
- (record) => record !== void 0 && record.conversationId === input.conversationId && activeAt(record, nowMs)
7864
+ (record2) => record2 !== void 0 && record2.conversationId === input.conversationId && activeAt(record2, nowMs)
7712
7865
  ).sort((left, right) => left.createdAtMs - right.createdAtMs);
7713
7866
  }
7714
7867
  async function cancelResourceEventSubscription(input) {
@@ -7776,7 +7929,7 @@ async function findMatchingResourceEventSubscriptions(input) {
7776
7929
  )
7777
7930
  );
7778
7931
  return records.filter(
7779
- (record) => record !== void 0 && matchesEvent(record, {
7932
+ (record2) => record2 !== void 0 && matchesEvent(record2, {
7780
7933
  eventType: input.eventType,
7781
7934
  nowMs,
7782
7935
  namespace: input.namespace,
@@ -11192,8 +11345,8 @@ function normalizeUser(raw) {
11192
11345
  display_name: raw.profile?.display_name || void 0,
11193
11346
  title: raw.profile?.title || void 0,
11194
11347
  email: raw.profile?.email || void 0,
11195
- status_text: raw.profile?.status_text || void 0,
11196
- status_emoji: raw.profile?.status_emoji || void 0,
11348
+ status_text: raw.profile?.status_text ?? void 0,
11349
+ status_emoji: raw.profile?.status_emoji ?? void 0,
11197
11350
  is_bot: raw.is_bot ?? false,
11198
11351
  is_deleted: raw.deleted ?? false,
11199
11352
  timezone: raw.tz || void 0,
@@ -11310,10 +11463,6 @@ async function searchSlackUsers(options) {
11310
11463
 
11311
11464
  // src/chat/slack/tools/user-lookup.ts
11312
11465
  import { z as z49 } from "zod";
11313
- var booleanInput4 = (description) => z49.preprocess(
11314
- (value) => value === "true" ? true : value === "false" ? false : value,
11315
- z49.boolean()
11316
- ).describe(description);
11317
11466
  function explicitUserLookupError(error) {
11318
11467
  if (error.apiError === "user_not_found") {
11319
11468
  return "No Slack user found for the supplied user ID.";
@@ -11334,7 +11483,7 @@ function explicitUserLookupError(error) {
11334
11483
  }
11335
11484
  function createSlackUserLookupTool() {
11336
11485
  return zodTool({
11337
- description: "Look up Slack user profiles by user ID, email, or name search. Use when you need to identify a user, resolve cross-platform identity, or look up profile details like title or status. Returns profile fields including custom fields. For user ID lookup, pass a Slack user ID (e.g. U039RR91S). For search, pass a name query.",
11486
+ description: "Look up Slack user profiles by user ID, email, or name search. Set mode to match the identifier in value. Returns profile fields including title, status, and custom fields.",
11338
11487
  annotations: {
11339
11488
  destructiveHint: false,
11340
11489
  idempotentHint: true,
@@ -11342,51 +11491,18 @@ function createSlackUserLookupTool() {
11342
11491
  readOnlyHint: true
11343
11492
  },
11344
11493
  inputSchema: z49.object({
11345
- user_id: slackUserIdParam(
11346
- "Slack user ID to look up (e.g. U039RR91S). Mutually exclusive with email and query."
11347
- ).optional(),
11348
- email: z49.string().min(3).describe(
11349
- "Email address to look up. Mutually exclusive with user_id and query."
11350
- ).optional(),
11351
- query: z49.string().min(2).describe(
11352
- "Name to search for (matches against username, display name, real name). Mutually exclusive with user_id and email."
11353
- ).optional(),
11354
- limit: z49.coerce.number().int().min(1).max(20).describe(
11355
- "Maximum number of results to return for name search. Defaults to 10."
11356
- ).optional(),
11357
- max_pages: z49.coerce.number().int().min(1).max(5).describe(
11358
- "Maximum number of Slack API pages to scan for name search. Defaults to 3."
11359
- ).optional(),
11360
- include_bots: booleanInput4(
11361
- "Include bot accounts in name search results. Defaults to false."
11362
- ).optional()
11363
- }),
11494
+ mode: z49.enum(["user_id", "email", "query"]).describe(
11495
+ "Lookup method: user_id for Slack IDs, email for email addresses, or query for name searches."
11496
+ ),
11497
+ value: z49.string().trim().min(2).describe(
11498
+ "The Slack user ID, email address, or name to look up, interpreted according to mode."
11499
+ )
11500
+ }).strict(),
11364
11501
  outputSchema: juniorToolOutputSchema,
11365
- execute: async ({
11366
- user_id,
11367
- email,
11368
- query,
11369
- limit,
11370
- max_pages,
11371
- include_bots
11372
- }) => {
11373
- const modes = [user_id, email, query].filter(Boolean);
11374
- if (modes.length === 0) {
11375
- throw new ToolInputError(
11376
- "Provide exactly one of user_id, email, or query to look up a Slack user."
11377
- );
11378
- }
11379
- if (modes.length > 1) {
11380
- throw new ToolInputError(
11381
- "Only one of user_id, email, or query can be provided."
11382
- );
11383
- }
11502
+ execute: async ({ mode, value }) => {
11384
11503
  try {
11385
- if (user_id) {
11386
- const parsedUserId = parseRequiredSlackUserIdParam(
11387
- "user_id",
11388
- user_id
11389
- );
11504
+ if (mode === "user_id") {
11505
+ const parsedUserId = parseRequiredSlackUserIdParam("value", value);
11390
11506
  if (!parsedUserId.ok) {
11391
11507
  throw new ToolInputError(parsedUserId.error);
11392
11508
  }
@@ -11395,11 +11511,11 @@ function createSlackUserLookupTool() {
11395
11511
  user: await lookupSlackUserProfile(parsedUserId.value)
11396
11512
  };
11397
11513
  }
11398
- if (email) {
11399
- const profile = await lookupSlackUserByEmail(email);
11514
+ if (mode === "email") {
11515
+ const profile = await lookupSlackUserByEmail(value);
11400
11516
  if (!profile) {
11401
11517
  throw new ToolInputError(
11402
- `No Slack user found with email address ${email}.`
11518
+ `No Slack user found with email address ${value}.`
11403
11519
  );
11404
11520
  }
11405
11521
  return {
@@ -11408,14 +11524,14 @@ function createSlackUserLookupTool() {
11408
11524
  };
11409
11525
  }
11410
11526
  const result = await searchSlackUsers({
11411
- query,
11412
- limit: limit ?? 10,
11413
- maxPages: max_pages ?? 3,
11414
- includeBots: include_bots ?? false
11527
+ query: value,
11528
+ limit: 10,
11529
+ maxPages: 3,
11530
+ includeBots: false
11415
11531
  });
11416
11532
  return {
11417
11533
  mode: "query",
11418
- query,
11534
+ query: value,
11419
11535
  count: result.users.length,
11420
11536
  searched_pages: result.searched_pages,
11421
11537
  searched_user_count: result.searched_user_count,
@@ -11437,43 +11553,94 @@ function createSlackUserLookupTool() {
11437
11553
 
11438
11554
  // src/chat/tools/system-time.ts
11439
11555
  import { z as z50 } from "zod";
11556
+ var systemTimeInputSchema = z50.object({
11557
+ timezone: z50.string().trim().min(1).optional().describe(
11558
+ "Optional IANA timezone, for example America/Los_Angeles. When set, iso_local and timezone_offset_minutes are for that zone."
11559
+ )
11560
+ });
11440
11561
  var systemTimeOutputSchema = juniorToolOutputSchema.extend({
11441
11562
  unix_ms: z50.number(),
11442
11563
  iso_utc: z50.string(),
11443
11564
  iso_local: z50.string(),
11565
+ timezone: z50.string().nullable(),
11444
11566
  timezone_offset_minutes: z50.number()
11445
11567
  });
11446
11568
  function createSystemTimeTool() {
11447
11569
  return zodTool({
11448
- description: "Return current system time in UTC and local ISO formats. Use when the user asks for current time/date context. Do not use as a substitute for historical or timezone-conversion research.",
11570
+ description: "Return current system time in UTC and optional local IANA timezone formats. Use when the user asks for current time/date context. Pass timezone when the answer should be in a known IANA zone such as America/Los_Angeles.",
11449
11571
  annotations: {
11450
11572
  destructiveHint: false,
11451
11573
  idempotentHint: true,
11452
11574
  openWorldHint: false,
11453
11575
  readOnlyHint: true
11454
11576
  },
11455
- inputSchema: z50.object({}),
11577
+ inputSchema: systemTimeInputSchema,
11456
11578
  outputSchema: systemTimeOutputSchema,
11457
11579
  privateTraceResult: (result) => ({
11458
11580
  unix_ms: result.unix_ms,
11459
11581
  iso_utc: result.iso_utc,
11460
11582
  iso_local: result.iso_local,
11583
+ timezone: result.timezone,
11461
11584
  timezone_offset_minutes: result.timezone_offset_minutes
11462
11585
  }),
11463
- execute: async () => {
11586
+ execute: async (input) => {
11464
11587
  const now = /* @__PURE__ */ new Date();
11465
- const details = {
11588
+ const timezone = input.timezone?.trim() || null;
11589
+ if (timezone) {
11590
+ if (!isValidTimeZone2(timezone)) {
11591
+ throw new Error("timezone must be a valid IANA time zone.");
11592
+ }
11593
+ const parts = getZonedDateTimeParts(now.getTime(), timezone);
11594
+ const isoLocal = [
11595
+ String(parts.year).padStart(4, "0"),
11596
+ String(parts.month).padStart(2, "0"),
11597
+ String(parts.day).padStart(2, "0")
11598
+ ].join("-") + "T" + [
11599
+ String(parts.hour).padStart(2, "0"),
11600
+ String(parts.minute).padStart(2, "0"),
11601
+ String(parts.second).padStart(2, "0")
11602
+ ].join(":");
11603
+ return {
11604
+ unix_ms: now.getTime(),
11605
+ iso_utc: now.toISOString(),
11606
+ iso_local: isoLocal,
11607
+ timezone,
11608
+ timezone_offset_minutes: getTimeZoneOffsetMinutes(
11609
+ now.getTime(),
11610
+ timezone
11611
+ )
11612
+ };
11613
+ }
11614
+ return {
11466
11615
  unix_ms: now.getTime(),
11467
11616
  iso_utc: now.toISOString(),
11468
11617
  iso_local: new Date(now.getTime() - now.getTimezoneOffset() * 6e4).toISOString().replace("Z", ""),
11618
+ timezone: null,
11469
11619
  timezone_offset_minutes: now.getTimezoneOffset()
11470
11620
  };
11471
- return {
11472
- ...details
11473
- };
11474
11621
  }
11475
11622
  });
11476
11623
  }
11624
+ function isValidTimeZone2(timezone) {
11625
+ try {
11626
+ new Intl.DateTimeFormat("en-US", { timeZone: timezone }).format();
11627
+ return true;
11628
+ } catch {
11629
+ return false;
11630
+ }
11631
+ }
11632
+ function getTimeZoneOffsetMinutes(timestampMs, timezone) {
11633
+ const parts = getZonedDateTimeParts(timestampMs, timezone);
11634
+ const asUtcMs = Date.UTC(
11635
+ parts.year,
11636
+ parts.month - 1,
11637
+ parts.day,
11638
+ parts.hour,
11639
+ parts.minute,
11640
+ parts.second
11641
+ );
11642
+ return Math.round((timestampMs - asUtcMs) / 6e4);
11643
+ }
11477
11644
 
11478
11645
  // src/chat/tools/query-conversation-events.ts
11479
11646
  import { z as z51 } from "zod";
@@ -11894,16 +12061,16 @@ async function resolvePublicHostname(hostname) {
11894
12061
  throw new Error("Could not resolve hostname");
11895
12062
  }
11896
12063
  const deduped = /* @__PURE__ */ new Map();
11897
- for (const record of records) {
11898
- const family = record.family === 6 ? 6 : 4;
11899
- if (family === 4 && isPrivateIpv4(record.address)) {
12064
+ for (const record2 of records) {
12065
+ const family = record2.family === 6 ? 6 : 4;
12066
+ if (family === 4 && isPrivateIpv4(record2.address)) {
11900
12067
  throw new Error("Resolved to a private IPv4 address");
11901
12068
  }
11902
- if (family === 6 && isPrivateIpv6(record.address)) {
12069
+ if (family === 6 && isPrivateIpv6(record2.address)) {
11903
12070
  throw new Error("Resolved to a private IPv6 address");
11904
12071
  }
11905
- deduped.set(`${family}:${record.address}`, {
11906
- address: record.address,
12072
+ deduped.set(`${family}:${record2.address}`, {
12073
+ address: record2.address,
11907
12074
  family
11908
12075
  });
11909
12076
  }
@@ -12564,7 +12731,9 @@ function createTools(availableSkills, hooks = {}, context, options = {}) {
12564
12731
  );
12565
12732
  }
12566
12733
  }
12567
- for (const [name, pluginTool] of Object.entries(getPluginTools(context))) {
12734
+ for (const [name, pluginTool] of Object.entries(
12735
+ getPluginTools(context, options.pluginSandbox)
12736
+ )) {
12568
12737
  if (tools[name]) {
12569
12738
  throw new Error(`Plugin tool "${name}" conflicts with a core tool`);
12570
12739
  }
@@ -12684,6 +12853,7 @@ function buildSandboxInput(toolName, params) {
12684
12853
  if (toolName === "bash") {
12685
12854
  return {
12686
12855
  command: String(params.command ?? ""),
12856
+ ...typeof params.cwd === "string" ? { cwd: params.cwd } : {},
12687
12857
  ...params.env && typeof params.env === "object" && !Array.isArray(params.env) ? { env: params.env } : {},
12688
12858
  ...optionalNumber(params.timeoutMs) ? { timeoutMs: optionalNumber(params.timeoutMs) } : {}
12689
12859
  };
@@ -12734,184 +12904,6 @@ function buildSandboxInput(toolName, params) {
12734
12904
  return params;
12735
12905
  }
12736
12906
 
12737
- // src/chat/tool-support/normalize-result.ts
12738
- function isToolContent(value) {
12739
- return Array.isArray(value) && value.every((part) => {
12740
- if (!part || typeof part !== "object") {
12741
- return false;
12742
- }
12743
- const record = part;
12744
- if (record.type === "text") {
12745
- return typeof record.text === "string";
12746
- }
12747
- if (record.type === "image") {
12748
- return typeof record.data === "string" && typeof record.mimeType === "string";
12749
- }
12750
- return false;
12751
- });
12752
- }
12753
- function isStructuredToolExecutionResult(value) {
12754
- const content = value?.content;
12755
- return typeof value === "object" && value !== null && isToolContent(content) && "details" in value;
12756
- }
12757
- function isContentOnlyToolExecutionResult(value) {
12758
- const content = value?.content;
12759
- return typeof value === "object" && value !== null && isToolContent(content) && !("details" in value);
12760
- }
12761
- function toToolContentText(value) {
12762
- if (typeof value === "string") return value;
12763
- try {
12764
- return JSON.stringify(value);
12765
- } catch {
12766
- return String(value);
12767
- }
12768
- }
12769
- function withoutFirstGeneratedTextContent(content) {
12770
- const [first, ...rest] = content;
12771
- if (first?.type === "text") {
12772
- return rest;
12773
- }
12774
- return content;
12775
- }
12776
- function replaceGeneratedTextContent(content, text) {
12777
- return [
12778
- {
12779
- type: "text",
12780
- text
12781
- },
12782
- ...withoutFirstGeneratedTextContent(content)
12783
- ];
12784
- }
12785
- function isRecord3(value) {
12786
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
12787
- }
12788
- function hasContinuation(value) {
12789
- return isRecord3(value) && isRecord3(value.continuation);
12790
- }
12791
- function stringField(record, key) {
12792
- const value = record[key];
12793
- return typeof value === "string" ? value : "";
12794
- }
12795
- function numberField(record, key) {
12796
- const value = record[key];
12797
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
12798
- }
12799
- function stringListField(record, key) {
12800
- const value = record[key];
12801
- return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
12802
- }
12803
- function accountText(value) {
12804
- if (!isRecord3(value)) {
12805
- return void 0;
12806
- }
12807
- const label = stringField(value, "label") || stringField(value, "id");
12808
- const id = stringField(value, "id");
12809
- if (!label) {
12810
- return void 0;
12811
- }
12812
- return id && id !== label ? `${label} (${id})` : label;
12813
- }
12814
- function upstreamPermissionDeniedText(value) {
12815
- if (!isRecord3(value) || !isRecord3(value.permission_denied)) {
12816
- return void 0;
12817
- }
12818
- const signal = value.permission_denied;
12819
- if (signal.source !== "upstream") {
12820
- return void 0;
12821
- }
12822
- const provider = stringField(signal, "provider");
12823
- const message = stringField(signal, "message");
12824
- const upstreamHost = stringField(signal, "upstreamHost");
12825
- const upstreamPath = stringField(signal, "upstreamPath");
12826
- const status = numberField(signal, "status");
12827
- if (!provider || !message || !upstreamHost || !upstreamPath || !status) {
12828
- return void 0;
12829
- }
12830
- const grant = isRecord3(signal.grant) ? signal.grant : {};
12831
- const grantName = stringField(grant, "name");
12832
- const grantAccess = stringField(grant, "access");
12833
- const grantReason = stringField(grant, "reason");
12834
- const grantRequirements = stringListField(grant, "requirements");
12835
- const account = accountText(signal.account);
12836
- const command = stringField(value, "command");
12837
- const stderr = stringField(value, "stderr").trim();
12838
- const stdout = stringField(value, "stdout").trim();
12839
- const acceptedPermissions = stringField(signal, "acceptedPermissions");
12840
- const sso = stringField(signal, "sso");
12841
- return [
12842
- "Upstream permission denied.",
12843
- message,
12844
- "",
12845
- `Provider: ${provider}`,
12846
- ...account ? [`Provider account: ${account}`] : [],
12847
- `Grant: ${grantName || "unknown"}${grantAccess ? ` (${grantAccess}${grantReason ? `, ${grantReason}` : ""})` : ""}`,
12848
- ...grantRequirements.length > 0 ? [
12849
- "Provider requirements:",
12850
- ...grantRequirements.map((item) => `- ${item}`)
12851
- ] : [],
12852
- `Upstream: ${upstreamHost}${upstreamPath}`,
12853
- `Status: ${status}`,
12854
- ...acceptedPermissions ? [`Accepted provider permissions: ${acceptedPermissions}`] : [],
12855
- ...sso ? [`Provider SSO: ${sso}`] : [],
12856
- ...command ? [`Command: ${command}`] : [],
12857
- "",
12858
- "Junior had a credential lease for this grant and forwarded the request. Do not diagnose this as a missing user token or a local Junior runtime block; diagnose provider-side permissions, installation scope, SSO, or actor-provider account access.",
12859
- ...stderr ? ["", `stderr:
12860
- ${stderr}`] : [],
12861
- ...stdout ? ["", `stdout:
12862
- ${stdout}`] : []
12863
- ].join("\n");
12864
- }
12865
- function normalizeDetails(details, options) {
12866
- const continuationToolName = options.toolName && hasContinuation(details) ? options.toolName : void 0;
12867
- if (!continuationToolName) {
12868
- return { details, replaceEnvelopeText: false };
12869
- }
12870
- return {
12871
- details: injectContinuationToolName(details, continuationToolName),
12872
- replaceEnvelopeText: true
12873
- };
12874
- }
12875
- function normalizeToolResult(result, options = {}) {
12876
- const unwrapped = result;
12877
- if (isStructuredToolExecutionResult(unwrapped)) {
12878
- const normalized2 = normalizeDetails(unwrapped.details, options);
12879
- const permissionText = upstreamPermissionDeniedText(normalized2.details);
12880
- if (!permissionText && normalized2.details === unwrapped.details) {
12881
- return unwrapped;
12882
- }
12883
- const content = permissionText || normalized2.replaceEnvelopeText ? replaceGeneratedTextContent(
12884
- unwrapped.content,
12885
- permissionText ?? toToolContentText(normalized2.details)
12886
- ) : unwrapped.content;
12887
- return {
12888
- content,
12889
- details: normalized2.details
12890
- };
12891
- }
12892
- if (isContentOnlyToolExecutionResult(unwrapped)) {
12893
- if (options.requireStructuredResult) {
12894
- throw new TypeError(
12895
- "Structured tools must return details matching their outputSchema."
12896
- );
12897
- }
12898
- return {
12899
- content: unwrapped.content,
12900
- details: {}
12901
- };
12902
- }
12903
- const normalized = normalizeDetails(unwrapped, options);
12904
- return {
12905
- content: [
12906
- {
12907
- type: "text",
12908
- text: upstreamPermissionDeniedText(normalized.details) ?? toToolContentText(normalized.details)
12909
- }
12910
- ],
12911
- details: normalized.details
12912
- };
12913
- }
12914
-
12915
12907
  // src/chat/tools/execution/tool-error-handler.ts
12916
12908
  import { PluginToolInputError } from "@sentry/junior-plugin-api";
12917
12909
 
@@ -15082,8 +15074,8 @@ function contentText(content) {
15082
15074
  if (!part || typeof part !== "object") {
15083
15075
  return [];
15084
15076
  }
15085
- const record = part;
15086
- return typeof record.text === "string" ? [record.text] : [];
15077
+ const record2 = part;
15078
+ return typeof record2.text === "string" ? [record2.text] : [];
15087
15079
  }).join("\n");
15088
15080
  }
15089
15081
  function assistantEntries(message) {
@@ -15103,13 +15095,13 @@ function assistantEntries(message) {
15103
15095
  if (!part || typeof part !== "object") {
15104
15096
  continue;
15105
15097
  }
15106
- const record = part;
15107
- if (record.type !== "toolCall" || typeof record.name !== "string" || !record.name) {
15098
+ const record2 = part;
15099
+ if (record2.type !== "toolCall" || typeof record2.name !== "string" || !record2.name) {
15108
15100
  continue;
15109
15101
  }
15110
- const toolInput = serializedText(record.arguments);
15102
+ const toolInput = serializedText(record2.arguments);
15111
15103
  if (toolInput.trim()) {
15112
- entries.push({ role: `tool ${record.name} call`, text: toolInput });
15104
+ entries.push({ role: `tool ${record2.name} call`, text: toolInput });
15113
15105
  }
15114
15106
  }
15115
15107
  return entries;
@@ -15434,7 +15426,12 @@ async function wireAgentTools(args) {
15434
15426
  }
15435
15427
  },
15436
15428
  toolRuntimeContext,
15437
- { includeLoadSkill: !explicitSkillLoaded }
15429
+ {
15430
+ includeLoadSkill: !explicitSkillLoaded,
15431
+ pluginSandbox: createPluginToolSandbox(agentSandbox, {
15432
+ handleAuthSignal: pluginAuth.maybeHandleAuthSignal
15433
+ })
15434
+ }
15438
15435
  );
15439
15436
  const plannedToolExposure = planToolExposure(
15440
15437
  tools
@@ -15509,6 +15506,7 @@ async function wireAgentTools(args) {
15509
15506
  return actionReview.projectToolResult(toolCallId, result);
15510
15507
  },
15511
15508
  getSandboxRef: agentSandbox.sandboxRef,
15509
+ captureRepositoryInstructions: agentSandbox.captureRepositoryInstructions,
15512
15510
  async close() {
15513
15511
  agentSandbox.close();
15514
15512
  try {
@@ -15799,6 +15797,59 @@ function userMessage(text) {
15799
15797
  timestamp: Date.now()
15800
15798
  };
15801
15799
  }
15800
+ function userMessageContent(message) {
15801
+ const content = message.content;
15802
+ return Array.isArray(content) ? content : [];
15803
+ }
15804
+ function hasTurnBootstrap(message) {
15805
+ return userMessageContent(message).some(
15806
+ (part) => textPart(part)?.startsWith(`<${TURN_CONTEXT_TAG}>`) === true
15807
+ );
15808
+ }
15809
+ function effectiveRuntimeContext(messages) {
15810
+ let latestBootstrapIndex = -1;
15811
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
15812
+ if (hasTurnBootstrap(messages[index])) {
15813
+ latestBootstrapIndex = index;
15814
+ break;
15815
+ }
15816
+ }
15817
+ const effectiveMessages = latestBootstrapIndex < 0 ? messages : messages.slice(latestBootstrapIndex);
15818
+ const allContent = effectiveMessages.flatMap(userMessageContent);
15819
+ const firstAgentsIndex = allContent.findIndex(
15820
+ (part) => textPart(part)?.startsWith("# AGENTS.md instructions") === true
15821
+ );
15822
+ const content = allContent.filter(
15823
+ (part) => textPart(part)?.startsWith("# AGENTS.md instructions") !== true
15824
+ );
15825
+ const visibleAgents = findVisibleAgentsInstructions(effectiveMessages);
15826
+ if (visibleAgents?.active === true) {
15827
+ const insertionIndex = firstAgentsIndex < 0 ? content.length : Math.min(firstAgentsIndex, content.length);
15828
+ content.splice(insertionIndex, 0, {
15829
+ type: "text",
15830
+ text: renderAgentsInstructions({
15831
+ directory: visibleAgents.directory,
15832
+ text: visibleAgents.text
15833
+ })
15834
+ });
15835
+ }
15836
+ const latestTimestamp = effectiveMessages.at(-1)?.timestamp;
15837
+ return {
15838
+ content,
15839
+ timestamp: typeof latestTimestamp === "number" ? latestTimestamp : Date.now()
15840
+ };
15841
+ }
15842
+ function runtimeContextMessage(messages, timestamp) {
15843
+ const runtimeContext = effectiveRuntimeContext(messages);
15844
+ if (runtimeContext.content.length === 0) {
15845
+ return void 0;
15846
+ }
15847
+ return {
15848
+ role: "user",
15849
+ content: runtimeContext.content,
15850
+ timestamp: timestamp ?? runtimeContext.timestamp
15851
+ };
15852
+ }
15802
15853
  function selectRetainedUserMessageEntries(messages, maxTokens = RETAINED_USER_MESSAGE_TOKENS) {
15803
15854
  const selected = [];
15804
15855
  let remaining = maxTokens;
@@ -16062,21 +16113,19 @@ function createContextCompactor(deps) {
16062
16113
  };
16063
16114
  }
16064
16115
  async function compactContextForHandoff(args, deps) {
16065
- const runtimeMessage = args.runtimeContext.at(-1);
16066
- if (!runtimeMessage) {
16116
+ const contextMessage = runtimeContextMessage(args.runtimeContext);
16117
+ if (!contextMessage) {
16067
16118
  throw new Error("Handoff requires the current runtime turn context");
16068
16119
  }
16069
16120
  const generatedSummary = await summarizeContext(args, deps);
16070
16121
  const summary = `${MODEL_HANDOFF_SUMMARY_PREFIX}
16071
16122
  ${generatedSummary}`;
16072
- const message = {
16073
- ...runtimeMessage,
16074
- content: [
16075
- ...runtimeMessage.content,
16076
- { type: "text", text: renderCurrentInstruction(summary) }
16077
- ]
16123
+ const instructionMessage = {
16124
+ role: "user",
16125
+ content: [{ type: "text", text: renderCurrentInstruction(summary) }],
16126
+ timestamp: contextMessage.timestamp
16078
16127
  };
16079
- const messages = [message];
16128
+ const messages = [contextMessage, instructionMessage];
16080
16129
  const replacementMessages = stripRuntimeTurnContext(messages);
16081
16130
  args.signal?.throwIfAborted();
16082
16131
  await getConversationEventStore().replaceHistory(args.conversationId, {
@@ -16138,10 +16187,18 @@ async function compactActiveContextIfNeeded(args, deps) {
16138
16187
  `${ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX}
16139
16188
  ${summary}`
16140
16189
  );
16141
- const runtimeMessages = retainRuntimeTurnContext(args.piMessages);
16142
16190
  const retainedInstruction = pendingMessages.length === 0 ? await loadLastInstruction(args.conversationId) : void 0;
16143
16191
  const instructionMessages = retainedInstruction ? [retainedInstruction.message] : pendingMessages.map((entry) => entry.message);
16144
16192
  const instructionProvenance = retainedInstruction ? [retainedInstruction.provenance] : pendingMessages.map((entry) => entry.provenance);
16193
+ const instructionTimestamp = instructionMessages[0]?.timestamp;
16194
+ const retainedRuntimeContext = retainRuntimeTurnContext(
16195
+ args.runtimeContextMessages ?? args.piMessages
16196
+ );
16197
+ const contextMessage = runtimeContextMessage(
16198
+ retainedRuntimeContext,
16199
+ typeof instructionTimestamp === "number" ? instructionTimestamp : void 0
16200
+ );
16201
+ const runtimeMessages = contextMessage ? [contextMessage] : [];
16145
16202
  const messages = [...runtimeMessages, ...instructionMessages, summaryMessage];
16146
16203
  const replacementInputTokens = estimateHistoryTokens(messages);
16147
16204
  if (replacementInputTokens >= inputLimitTokens) {
@@ -16447,7 +16504,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16447
16504
  syncLoadedSkillNamesForResume
16448
16505
  });
16449
16506
  const explicitSkill = invokedSkill ? activeSkills.find((skill) => skill.name === invokedSkill.name) ?? null : null;
16450
- const { routerBlocks, userContentParts } = buildPromptInput(input);
16507
+ const { contextContentParts, routerBlocks, userContentParts } = buildPromptInput(input);
16451
16508
  const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
16452
16509
  const storedTurnRoute = await loadTurnRoute({ conversationId, turnId });
16453
16510
  if (storedTurnRoute) {
@@ -16667,6 +16724,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16667
16724
  });
16668
16725
  mcpToolManager = wiring.mcpToolManager;
16669
16726
  closeTools = wiring.close;
16727
+ const initialRepositoryInstructions = wiring.getSandboxRef() ? await wiring.captureRepositoryInstructions() : void 0;
16670
16728
  const getPendingAuthPause = wiring.getPendingAuthPause;
16671
16729
  const toolsWithoutHandoff = wiring.agentTools.filter(
16672
16730
  (tool) => tool.name !== HANDOFF_TOOL_NAME
@@ -16694,18 +16752,29 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16694
16752
  };
16695
16753
  const {
16696
16754
  baseInstructions,
16755
+ contextContentParts: promptContextContentParts,
16697
16756
  inputMessages,
16698
16757
  inputMessagesAttribute,
16699
- promptContentParts,
16758
+ promptTimestamp: restoredPromptTimestamp,
16700
16759
  promptHistoryMessages,
16701
16760
  shouldPromptAgent,
16702
- turnContexts
16761
+ turnContexts,
16762
+ userContentParts: promptUserContentParts
16703
16763
  } = await assemblePrompt({
16704
16764
  activeMcpCatalogs: wiring.activeMcpCatalogs,
16705
16765
  currentActor: actor,
16706
16766
  artifactState: state.artifactState,
16707
16767
  availableSkills,
16708
16768
  configurationValues,
16769
+ contextContentParts: [
16770
+ ...initialRepositoryInstructions ? [
16771
+ {
16772
+ type: "text",
16773
+ text: renderAgentsInstructions(initialRepositoryInstructions)
16774
+ }
16775
+ ] : [],
16776
+ ...contextContentParts
16777
+ ],
16709
16778
  conversationPrivacy,
16710
16779
  existingSessionPiMessages: existingSessionRecord?.piMessages,
16711
16780
  existingTurnStartMessageIndex: existingSessionRecord?.turnStartMessageIndex,
@@ -16719,6 +16788,18 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16719
16788
  toolRuntimeContext: wiring.toolRuntimeContext,
16720
16789
  userContentParts
16721
16790
  });
16791
+ const repositoryInstructionsContext = createRepositoryInstructionsContext({
16792
+ capture: wiring.captureRepositoryInstructions,
16793
+ hasSandbox: () => Boolean(wiring.getSandboxRef()),
16794
+ initialInstructions: initialRepositoryInstructions,
16795
+ promptContextContentParts,
16796
+ restoredMessages: existingSessionRecord?.piMessages,
16797
+ restoredProvenance: existingSessionRecord?.piMessageProvenance,
16798
+ setMessages(messages) {
16799
+ agent.state.messages = messages;
16800
+ },
16801
+ shouldPromptAgent
16802
+ });
16722
16803
  runResume.setTurnContexts(turnContexts);
16723
16804
  const applyPendingHandoff = () => {
16724
16805
  if (!pendingHandoff) {
@@ -16750,7 +16831,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16750
16831
  thinkingLevel
16751
16832
  };
16752
16833
  };
16753
- const applyActiveContextCompaction = async (messages, hookSignal, pendingMessages, clearSteeringQueueOnCommit = false) => {
16834
+ const applyActiveContextCompaction = async (messages, hookSignal, pendingMessages, clearSteeringQueueOnCommit = false, pairPendingRuntimeContext = false) => {
16754
16835
  const compaction = await compactActiveContextIfNeeded(
16755
16836
  {
16756
16837
  conversationContext: input.conversationContext,
@@ -16766,6 +16847,12 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16766
16847
  onCompactionStart: () => observers.onStatus?.({ text: "Compacting context" }),
16767
16848
  pendingMessages,
16768
16849
  piMessages: messages,
16850
+ ...pairPendingRuntimeContext && pendingMessages ? {
16851
+ runtimeContextMessages: [
16852
+ ...messages,
16853
+ ...pendingMessages.map((entry) => entry.message)
16854
+ ]
16855
+ } : {},
16769
16856
  signal: hookSignal
16770
16857
  },
16771
16858
  {
@@ -16903,13 +16990,18 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16903
16990
  pendingMessages,
16904
16991
  true
16905
16992
  );
16993
+ const combinedUpdate = capacityUpdate && handoffUpdate ? { ...handoffUpdate, ...capacityUpdate } : capacityUpdate ?? handoffUpdate;
16994
+ const repositoryInstructionsUpdate = await repositoryInstructionsContext.applyUpdate(
16995
+ combinedUpdate,
16996
+ nextTurn.context
16997
+ );
16906
16998
  const yieldError = runResume.prepareYieldIfDue(
16907
16999
  currentAgentMessages()
16908
17000
  );
16909
17001
  if (yieldError) {
16910
17002
  throw yieldError;
16911
17003
  }
16912
- return capacityUpdate && handoffUpdate ? { ...handoffUpdate, ...capacityUpdate } : capacityUpdate ?? handoffUpdate;
17004
+ return repositoryInstructionsUpdate;
16913
17005
  } catch (error) {
16914
17006
  pendingPiHookError ??= error instanceof Error ? error : new Error(String(error));
16915
17007
  throw error;
@@ -16964,20 +17056,37 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
16964
17056
  "gen_ai.invoke_agent",
16965
17057
  spanContext,
16966
17058
  async () => {
17059
+ const promptTimestamp = restoredPromptTimestamp ?? Date.now();
17060
+ const contextMessage = shouldPromptAgent && promptContextContentParts.length > 0 ? {
17061
+ role: "user",
17062
+ content: promptContextContentParts,
17063
+ timestamp: promptTimestamp
17064
+ } : void 0;
16967
17065
  const freshPromptMessage = {
16968
17066
  role: "user",
16969
- content: promptContentParts,
16970
- timestamp: Date.now()
17067
+ content: promptUserContentParts,
17068
+ timestamp: promptTimestamp
16971
17069
  };
16972
17070
  if (shouldPromptAgent) {
16973
17071
  const promptPersisted = await runResume.requireDurableInputCheckpoint([
16974
17072
  ...agent.state.messages,
17073
+ ...contextMessage ? [contextMessage] : [],
16975
17074
  freshPromptMessage
16976
17075
  ]);
16977
17076
  if (promptPersisted) {
16978
17077
  await runResume.commitInput();
16979
17078
  }
16980
17079
  }
17080
+ if (contextMessage) {
17081
+ agent.state.messages = [...agent.state.messages, contextMessage];
17082
+ }
17083
+ if (!shouldPromptAgent) {
17084
+ await repositoryInstructionsContext.applyUpdate(void 0, {
17085
+ messages: agent.state.messages,
17086
+ systemPrompt: agent.state.systemPrompt,
17087
+ tools: agent.state.tools
17088
+ });
17089
+ }
16981
17090
  const runAgentStep = async (run2, abortRun = () => agent.abort()) => {
16982
17091
  let timeoutId;
16983
17092
  let removeAbortListener;
@@ -17074,7 +17183,10 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17074
17183
  await runAgentStep(
17075
17184
  scheduleHandoff({
17076
17185
  profile: requestedProfile,
17077
- runtimeContextSourceMessages: shouldPromptAgent ? [freshPromptMessage] : void 0,
17186
+ runtimeContextSourceMessages: shouldPromptAgent ? [
17187
+ ...contextMessage ? [contextMessage] : [],
17188
+ freshPromptMessage
17189
+ ] : void 0,
17078
17190
  signal: handoffAbortController.signal,
17079
17191
  sourceMessages: [...agent.state.messages]
17080
17192
  }),
@@ -17092,7 +17204,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
17092
17204
  message: freshPromptMessage,
17093
17205
  provenance: instructionProvenanceFor(actor)
17094
17206
  }
17095
- ] : void 0
17207
+ ] : void 0,
17208
+ false,
17209
+ Boolean(contextMessage)
17096
17210
  ),
17097
17211
  () => compactionAbortController.abort()
17098
17212
  );