@sentry/junior 0.113.0 → 0.115.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.
- package/dist/{agent-hooks-BKEMWR3Z.js → agent-hooks-4EUUXP2F.js} +12 -11
- package/dist/api/conversations/detail.d.ts +26 -0
- package/dist/api/conversations/event-list.d.ts +26 -0
- package/dist/api/conversations/events.d.ts +1 -1
- package/dist/api/health.d.ts +7 -0
- package/dist/api/plugin-reports.d.ts +62 -0
- package/dist/api/plugins.d.ts +9 -0
- package/dist/api/runtime.d.ts +24 -0
- package/dist/api/schema/conversation.d.ts +104 -0
- package/dist/api/schema.d.ts +2 -2
- package/dist/api/schema.js +6 -6
- package/dist/api/skills.d.ts +6 -0
- package/dist/api.js +140 -100
- package/dist/app.d.ts +2 -0
- package/dist/app.js +1117 -5010
- package/dist/catalog-runtime-Y32UEQLB.js +18 -0
- package/dist/chat/agent/request.d.ts +6 -0
- package/dist/chat/agent/sandbox.d.ts +2 -0
- package/dist/chat/agent/tools.d.ts +2 -0
- package/dist/chat/config.d.ts +3 -1
- package/dist/chat/conversations/history.d.ts +92 -0
- package/dist/chat/conversations/projection.d.ts +20 -1
- package/dist/chat/local/credential-sync.d.ts +5 -0
- package/dist/chat/local/oauth-relay.d.ts +4 -0
- package/dist/chat/mcp/oauth.d.ts +1 -0
- package/dist/chat/oauth-authorization.d.ts +17 -0
- package/dist/chat/oauth-flow.d.ts +9 -0
- package/dist/chat/sandbox/egress/schemas.d.ts +46 -0
- package/dist/chat/sandbox/egress/signals.d.ts +9 -0
- package/dist/chat/sandbox/sandbox.d.ts +2 -0
- package/dist/chat/sentry-payload-filter.d.ts +4 -4
- package/dist/chat/services/context-budget.d.ts +7 -2
- package/dist/chat/services/context-compaction.d.ts +28 -1
- package/dist/chat/services/conversation-memory.d.ts +1 -0
- package/dist/chat/services/mcp-auth-orchestration.d.ts +5 -3
- package/dist/chat/services/plugin-auth-orchestration.d.ts +2 -0
- package/dist/chat/services/provider-error.d.ts +1 -0
- package/dist/chat/services/turn-router.d.ts +1 -0
- package/dist/chat/slack/message-control.d.ts +2 -0
- package/dist/chat/slack/oauth-authorization-message.d.ts +3 -0
- package/dist/chat/task-execution/slack-work.d.ts +2 -0
- package/dist/chat/tool-support/zod-tool.d.ts +1 -1
- package/dist/chat/tools/definition.d.ts +5 -0
- package/dist/chat/tools/sandbox/edit-file.d.ts +1 -1
- package/dist/chat/tools/sandbox/text-edits.d.ts +1 -0
- package/dist/{chunk-4WZ7YKDF.js → chunk-2QG4MZQ7.js} +2 -2
- package/dist/{chunk-H5ON7XTE.js → chunk-43RYMWJ2.js} +954 -2514
- package/dist/chunk-4CODA7P5.js +43 -0
- package/dist/{chunk-HMRVILWZ.js → chunk-67CVNDJ7.js} +1 -1
- package/dist/{chunk-LR2SKL5Y.js → chunk-6FKLPVF3.js} +1 -1
- package/dist/{chunk-2ZTTXTSZ.js → chunk-6OPTF4BQ.js} +10 -4
- package/dist/{chunk-3CTIAKSS.js → chunk-6V762AB4.js} +1 -1
- package/dist/{chunk-WBB22ANL.js → chunk-AGLQYELI.js} +77 -8
- package/dist/{chunk-DXRPHBOU.js → chunk-CEYOL62C.js} +77 -36
- package/dist/{chunk-QTS66GMR.js → chunk-CWEZEPD4.js} +3 -3
- package/dist/chunk-DIB4GX3G.js +8 -0
- package/dist/{chunk-OXE5ZUTS.js → chunk-EW5MLKT5.js} +10 -75
- package/dist/{chunk-42WRQLJ6.js → chunk-F3B2YJ76.js} +0 -14
- package/dist/chunk-FA6POGLH.js +70 -0
- package/dist/{chunk-ZRSUCJGA.js → chunk-FHC4JUSF.js} +1 -1
- package/dist/{chunk-6Y5L4BOM.js → chunk-FXOYURO4.js} +5 -3
- package/dist/{chunk-UKGNSJQQ.js → chunk-G36V2YGB.js} +108 -82
- package/dist/{chunk-K5BXPM74.js → chunk-G5SX4UPR.js} +3 -43
- package/dist/chunk-HDSDCEPK.js +80 -0
- package/dist/{chunk-LLKDNPYV.js → chunk-HWCQRGLL.js} +37 -3
- package/dist/{chunk-C4MZ3GP5.js → chunk-HZF5P5UN.js} +1 -1
- package/dist/chunk-LYA2AB5G.js +1914 -0
- package/dist/{chunk-36UJA3OD.js → chunk-NVUQSFWX.js} +2 -2
- package/dist/chunk-OB42YVAE.js +16 -0
- package/dist/{chunk-4VKFQPCW.js → chunk-PVZHG2XZ.js} +4 -4
- package/dist/{chunk-YJ44UT7D.js → chunk-RAVKCKXA.js} +7 -7
- package/dist/{chunk-RCBUZGIT.js → chunk-SYJT7AKB.js} +23 -20
- package/dist/{chunk-NP7TYFYR.js → chunk-TVE25LPW.js} +1 -1
- package/dist/chunk-W3U3YQYI.js +71 -0
- package/dist/chunk-YCLO7D7M.js +4267 -0
- package/dist/cli/chat.js +72 -40
- package/dist/cli/check.js +10 -8
- package/dist/cli/init.js +1 -0
- package/dist/cli/plugins.js +16 -14
- package/dist/cli/snapshot-warmup.js +13 -11
- package/dist/cli/upgrade.js +10 -8
- package/dist/{db-2ASK6MV6.js → db-KBSJHAZU.js} +8 -7
- package/dist/handlers/sandbox-egress-signals.d.ts +4 -0
- package/dist/instrumentation.js +72 -4
- package/dist/nitro.js +2 -2
- package/dist/oauth-callback-server-EYRMJTCS.js +231 -0
- package/dist/oauth-relay-3ZU72UGL.js +9 -0
- package/dist/reporting-schema.d.ts +12 -4
- package/dist/reporting.d.ts +5 -5
- package/dist/{runner-L363K62Z.js → runner-RQGZ5Z2S.js} +139 -72
- package/dist/sandbox-egress-signals-CDFAP67O.js +75 -0
- package/dist/{skills-VCPG3NOO.js → skills-JJQIE3BT.js} +10 -8
- package/dist/validation-MZ47PZP5.js +21 -0
- package/dist/version.js +1 -1
- package/package.json +5 -5
- package/dist/catalog-runtime-6XYOUVRZ.js +0 -16
- package/dist/chat/oauth-authorization-message.d.ts +0 -6
- package/dist/validation-GY7AJBLC.js +0 -19
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
authorizationId
|
|
3
|
+
} from "./chunk-DIB4GX3G.js";
|
|
4
|
+
import {
|
|
5
|
+
RetryableDeliveryError,
|
|
6
|
+
actorFromRouting,
|
|
7
|
+
addReactionToMessage,
|
|
8
|
+
assertRunRoutingConsistency,
|
|
9
|
+
createMcpOAuthClientProvider,
|
|
10
|
+
createUserTokenStore,
|
|
11
|
+
deleteMcpAuthSession,
|
|
12
|
+
deliverOAuthAuthorization,
|
|
13
|
+
formatProviderLabel,
|
|
14
|
+
getMcpAuthSession,
|
|
15
|
+
getSlackMessagePermalink,
|
|
16
|
+
hasEgressCredentialHooks,
|
|
17
|
+
issuePluginCredential,
|
|
18
|
+
issueProviderCredentialLease,
|
|
19
|
+
onPluginEgressResponse,
|
|
20
|
+
patchMcpAuthSession,
|
|
21
|
+
resolveBaseUrl,
|
|
22
|
+
selectPluginGrant,
|
|
23
|
+
slackOutputPolicy,
|
|
24
|
+
startOAuthFlow,
|
|
25
|
+
surfaceFromRouting,
|
|
26
|
+
toolInvocationDestination,
|
|
27
|
+
uploadFilesToConversation
|
|
28
|
+
} from "./chunk-LYA2AB5G.js";
|
|
1
29
|
import {
|
|
2
30
|
CooperativeTurnYieldError,
|
|
3
31
|
JUNIOR_THREAD_STATE_TTL_MS,
|
|
@@ -9,7 +37,7 @@ import {
|
|
|
9
37
|
estimateTextTokens,
|
|
10
38
|
extractAssistantText,
|
|
11
39
|
getAgentContextCompactionTriggerTokens,
|
|
12
|
-
|
|
40
|
+
getAgentContextInputLimitTokens,
|
|
13
41
|
getPiMessageRole,
|
|
14
42
|
getTerminalAssistantMessages,
|
|
15
43
|
hasRuntimeTurnContext,
|
|
@@ -20,6 +48,7 @@ import {
|
|
|
20
48
|
isTurnInputCommitLostError,
|
|
21
49
|
loadConnectedMcpProviders,
|
|
22
50
|
loadConversationProjection,
|
|
51
|
+
loadTurnRoute,
|
|
23
52
|
loadTurnSessionRecord,
|
|
24
53
|
mergeArtifactsState,
|
|
25
54
|
normalizeToolNameFromResult,
|
|
@@ -31,11 +60,18 @@ import {
|
|
|
31
60
|
recordAuthorizationRequested,
|
|
32
61
|
recordMcpProviderConnected,
|
|
33
62
|
recordToolExecutionStarted,
|
|
63
|
+
recordTurnRoute,
|
|
34
64
|
retainRuntimeTurnContext,
|
|
35
65
|
stripRuntimeTurnContext,
|
|
36
66
|
trimTrailingAssistantMessages,
|
|
37
67
|
updateConversationStats
|
|
38
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-CEYOL62C.js";
|
|
69
|
+
import {
|
|
70
|
+
parseSandboxEgressAuthRequiredSignal,
|
|
71
|
+
parseSandboxEgressPermissionDeniedSignal,
|
|
72
|
+
sandboxEgressCredentialContextSchema,
|
|
73
|
+
sandboxEgressCredentialLeaseSchema
|
|
74
|
+
} from "./chunk-HDSDCEPK.js";
|
|
39
75
|
import {
|
|
40
76
|
buildNonInteractiveShellScript,
|
|
41
77
|
createSandboxSession,
|
|
@@ -52,7 +88,7 @@ import {
|
|
|
52
88
|
runNonInteractiveCommand,
|
|
53
89
|
throwSandboxOperationError,
|
|
54
90
|
wrapSandboxSetupError
|
|
55
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-PVZHG2XZ.js";
|
|
56
92
|
import {
|
|
57
93
|
addAgentTurnUsage,
|
|
58
94
|
hasAgentTurnUsage
|
|
@@ -62,43 +98,37 @@ import {
|
|
|
62
98
|
} from "./chunk-UIE3R5XU.js";
|
|
63
99
|
import {
|
|
64
100
|
createPluginHookRunner,
|
|
65
|
-
createPluginLogger,
|
|
66
101
|
getPluginSystemPromptContributions,
|
|
67
102
|
getPluginTools,
|
|
68
103
|
getPluginUserPromptContributions,
|
|
69
|
-
getPlugins,
|
|
70
104
|
getSlackToolContext,
|
|
71
105
|
resolveChannelCapabilities
|
|
72
|
-
} from "./chunk-
|
|
106
|
+
} from "./chunk-RAVKCKXA.js";
|
|
73
107
|
import {
|
|
74
108
|
SANDBOX_DATA_ROOT,
|
|
75
109
|
SANDBOX_SKILLS_ROOT,
|
|
76
110
|
SANDBOX_WORKSPACE_ROOT,
|
|
77
|
-
acquireActiveLock,
|
|
78
111
|
getStateAdapter,
|
|
79
112
|
sandboxSkillDir,
|
|
80
113
|
sandboxSkillFile
|
|
81
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-67CVNDJ7.js";
|
|
82
115
|
import {
|
|
83
116
|
SlackActionError,
|
|
84
117
|
downloadPrivateSlackFile,
|
|
85
118
|
getFilePermalink,
|
|
86
119
|
getSlackClient,
|
|
87
120
|
isConversationScopedChannel,
|
|
88
|
-
isDmChannel,
|
|
89
121
|
normalizeSlackConversationId,
|
|
90
122
|
withSlackRetries
|
|
91
|
-
} from "./chunk-
|
|
123
|
+
} from "./chunk-CWEZEPD4.js";
|
|
92
124
|
import {
|
|
93
125
|
contextProvenance,
|
|
94
126
|
getConversationEventStore,
|
|
95
127
|
getConversationSearchStore,
|
|
96
128
|
getConversationStore,
|
|
97
|
-
getDb,
|
|
98
129
|
instructionActors,
|
|
99
|
-
instructionProvenanceFor
|
|
100
|
-
|
|
101
|
-
} from "./chunk-LLKDNPYV.js";
|
|
130
|
+
instructionProvenanceFor
|
|
131
|
+
} from "./chunk-HWCQRGLL.js";
|
|
102
132
|
import {
|
|
103
133
|
sleep
|
|
104
134
|
} from "./chunk-4ZNGQH7C.js";
|
|
@@ -126,11 +156,12 @@ import {
|
|
|
126
156
|
modelIdForProfile,
|
|
127
157
|
modelProfileSchema,
|
|
128
158
|
normalizeSlackEmojiName,
|
|
159
|
+
parseTurnReasoningLevel,
|
|
129
160
|
profileConfig,
|
|
130
161
|
renderCurrentInstruction,
|
|
131
162
|
resolveGatewayModel,
|
|
132
163
|
unwrapCurrentInstruction
|
|
133
|
-
} from "./chunk-
|
|
164
|
+
} from "./chunk-SYJT7AKB.js";
|
|
134
165
|
import {
|
|
135
166
|
parseSlackThreadId,
|
|
136
167
|
resolveConversationPrivacy,
|
|
@@ -140,26 +171,27 @@ import {
|
|
|
140
171
|
toGenAiMessagesTraceAttributes,
|
|
141
172
|
toGenAiPayloadMetadata,
|
|
142
173
|
toGenAiPayloadTraceAttributes
|
|
143
|
-
} from "./chunk-
|
|
174
|
+
} from "./chunk-FXOYURO4.js";
|
|
144
175
|
import {
|
|
145
176
|
discoverSkills,
|
|
146
177
|
findSkillByName,
|
|
147
178
|
loadSkillsByName,
|
|
148
179
|
parseSkillInvocation
|
|
149
|
-
} from "./chunk-
|
|
180
|
+
} from "./chunk-2QG4MZQ7.js";
|
|
150
181
|
import {
|
|
151
182
|
pluginCatalogRuntime
|
|
152
|
-
} from "./chunk-
|
|
183
|
+
} from "./chunk-6FKLPVF3.js";
|
|
153
184
|
import {
|
|
154
185
|
CredentialUnavailableError,
|
|
155
|
-
credentialContextSchema,
|
|
156
|
-
credentialUserSubjectId,
|
|
157
186
|
resolveAuthTokenPlaceholder,
|
|
158
187
|
resolvePluginCommandEnv
|
|
159
|
-
} from "./chunk-
|
|
188
|
+
} from "./chunk-EW5MLKT5.js";
|
|
189
|
+
import {
|
|
190
|
+
credentialUserSubjectId
|
|
191
|
+
} from "./chunk-W3U3YQYI.js";
|
|
160
192
|
import {
|
|
161
193
|
isUserActor
|
|
162
|
-
} from "./chunk-
|
|
194
|
+
} from "./chunk-HZF5P5UN.js";
|
|
163
195
|
import {
|
|
164
196
|
extractGenAiUsageAttributes,
|
|
165
197
|
extractGenAiUsageSummary,
|
|
@@ -177,13 +209,12 @@ import {
|
|
|
177
209
|
summarizeMessageText,
|
|
178
210
|
withLogContext,
|
|
179
211
|
withSpan
|
|
180
|
-
} from "./chunk-
|
|
212
|
+
} from "./chunk-NVUQSFWX.js";
|
|
181
213
|
import {
|
|
182
|
-
isRecord,
|
|
183
214
|
parseSlackChannelId,
|
|
184
215
|
parseSlackMessageTs,
|
|
185
216
|
parseSlackUserId
|
|
186
|
-
} from "./chunk-
|
|
217
|
+
} from "./chunk-F3B2YJ76.js";
|
|
187
218
|
import {
|
|
188
219
|
startInactiveSpan,
|
|
189
220
|
withActiveSpan
|
|
@@ -916,12 +947,12 @@ function sortJsonValue(value) {
|
|
|
916
947
|
Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, sortJsonValue(item)])
|
|
917
948
|
);
|
|
918
949
|
}
|
|
919
|
-
function
|
|
950
|
+
function isRecord(value) {
|
|
920
951
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
921
952
|
}
|
|
922
953
|
function injectContinuationToolName(details, toolName) {
|
|
923
954
|
const parsed = juniorToolResultSchema.parse(details);
|
|
924
|
-
if (!
|
|
955
|
+
if (!isRecord(parsed.continuation)) {
|
|
925
956
|
return parsed;
|
|
926
957
|
}
|
|
927
958
|
return juniorToolResultWithBoundContinuationSchema.parse({
|
|
@@ -1098,7 +1129,8 @@ function configuredTurnRoute(profile, reasoningLevel, source) {
|
|
|
1098
1129
|
return {
|
|
1099
1130
|
profile,
|
|
1100
1131
|
reasoningLevel,
|
|
1101
|
-
reason: `configured:${source}
|
|
1132
|
+
reason: `configured:${source}`,
|
|
1133
|
+
source: source === "profile" ? "inherited" : "configured"
|
|
1102
1134
|
};
|
|
1103
1135
|
}
|
|
1104
1136
|
async function selectTurnRoute(args) {
|
|
@@ -1158,7 +1190,7 @@ async function selectTurnRoute(args) {
|
|
|
1158
1190
|
"gen_ai.request.reasoning.level_confidence": normalizedSelection.confidence
|
|
1159
1191
|
} : {}
|
|
1160
1192
|
});
|
|
1161
|
-
return normalizedSelection;
|
|
1193
|
+
return { ...normalizedSelection, source: "router" };
|
|
1162
1194
|
}
|
|
1163
1195
|
);
|
|
1164
1196
|
}
|
|
@@ -1204,20 +1236,37 @@ async function classifyTurn(args) {
|
|
|
1204
1236
|
confidence: parsed.confidence,
|
|
1205
1237
|
profile: STANDARD_MODEL_PROFILE,
|
|
1206
1238
|
reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
|
|
1207
|
-
reason: `low_confidence_medium_default:${reason}
|
|
1239
|
+
reason: `low_confidence_medium_default:${reason}`,
|
|
1240
|
+
source: "router"
|
|
1208
1241
|
};
|
|
1209
1242
|
}
|
|
1210
1243
|
return {
|
|
1211
1244
|
confidence: parsed.confidence,
|
|
1212
1245
|
profile: parsed.profile,
|
|
1213
1246
|
reasoningLevel: parsed.reasoning_level,
|
|
1214
|
-
reason
|
|
1247
|
+
reason,
|
|
1248
|
+
source: "router"
|
|
1215
1249
|
};
|
|
1216
|
-
} catch {
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
logWarn(
|
|
1252
|
+
"turn_router_classifier_failed",
|
|
1253
|
+
{
|
|
1254
|
+
messageConversationId: args.metadata.threadId,
|
|
1255
|
+
destinationName: args.metadata.channelId,
|
|
1256
|
+
userId: args.metadata.actorId,
|
|
1257
|
+
runId: args.metadata.runId,
|
|
1258
|
+
modelId: args.fastModelId
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
1262
|
+
},
|
|
1263
|
+
"Turn router classifier failed; using the default route"
|
|
1264
|
+
);
|
|
1217
1265
|
return {
|
|
1218
1266
|
profile: STANDARD_MODEL_PROFILE,
|
|
1219
1267
|
reasoningLevel: CLASSIFIER_FALLBACK_REASONING_LEVEL,
|
|
1220
|
-
reason: "classifier_error_default"
|
|
1268
|
+
reason: "classifier_error_default",
|
|
1269
|
+
source: "router"
|
|
1221
1270
|
};
|
|
1222
1271
|
}
|
|
1223
1272
|
}
|
|
@@ -1277,69 +1326,6 @@ var AuthorizationFlowDisabledError = class extends Error {
|
|
|
1277
1326
|
}
|
|
1278
1327
|
};
|
|
1279
1328
|
|
|
1280
|
-
// src/chat/agent/request.ts
|
|
1281
|
-
var RetryableDeliveryError = class extends Error {
|
|
1282
|
-
constructor(cause) {
|
|
1283
|
-
super("Assistant delivery was transient or ambiguous", { cause });
|
|
1284
|
-
this.name = "RetryableDeliveryError";
|
|
1285
|
-
}
|
|
1286
|
-
};
|
|
1287
|
-
function actorFromRouting(routing) {
|
|
1288
|
-
if (routing.dispatch?.actor) {
|
|
1289
|
-
return routing.dispatch.actor;
|
|
1290
|
-
}
|
|
1291
|
-
if (routing.actor) {
|
|
1292
|
-
return routing.actor;
|
|
1293
|
-
}
|
|
1294
|
-
if (routing.credentialContext && !("type" in routing.credentialContext.actor)) {
|
|
1295
|
-
return routing.credentialContext.actor;
|
|
1296
|
-
}
|
|
1297
|
-
return void 0;
|
|
1298
|
-
}
|
|
1299
|
-
function assertRunRoutingConsistency(request) {
|
|
1300
|
-
const { destination, source } = request.routing;
|
|
1301
|
-
if (source.platform !== destination.platform) {
|
|
1302
|
-
throw new TypeError("Run source and destination platforms do not match");
|
|
1303
|
-
}
|
|
1304
|
-
if (source.platform === "slack" && destination.platform === "slack") {
|
|
1305
|
-
if (source.teamId !== destination.teamId) {
|
|
1306
|
-
throw new TypeError("Slack source and destination teams do not match");
|
|
1307
|
-
}
|
|
1308
|
-
} else if (source.platform === "local" && destination.platform === "local" && (source.conversationId !== request.conversationId || destination.conversationId !== request.conversationId)) {
|
|
1309
|
-
throw new TypeError(
|
|
1310
|
-
"Local source, destination, and run conversation IDs do not match"
|
|
1311
|
-
);
|
|
1312
|
-
}
|
|
1313
|
-
const actor = request.routing.dispatch?.actor ?? request.routing.actor;
|
|
1314
|
-
if (!actor || actor.platform === "system") {
|
|
1315
|
-
return;
|
|
1316
|
-
}
|
|
1317
|
-
if (actor.platform !== destination.platform) {
|
|
1318
|
-
throw new TypeError(
|
|
1319
|
-
`Actor platform "${actor.platform}" does not match destination platform "${destination.platform}"`
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
if (actor.platform === "slack" && destination.platform === "slack" && actor.teamId !== destination.teamId) {
|
|
1323
|
-
throw new TypeError("Slack actor team does not match destination team");
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
function toolInvocationDestination(routing) {
|
|
1327
|
-
if (routing.destination.platform !== "slack" || !routing.toolChannelId) {
|
|
1328
|
-
return routing.destination;
|
|
1329
|
-
}
|
|
1330
|
-
return {
|
|
1331
|
-
platform: "slack",
|
|
1332
|
-
teamId: routing.destination.teamId,
|
|
1333
|
-
channelId: routing.toolChannelId
|
|
1334
|
-
};
|
|
1335
|
-
}
|
|
1336
|
-
function surfaceFromRouting(routing) {
|
|
1337
|
-
if (routing.surface) {
|
|
1338
|
-
return routing.surface;
|
|
1339
|
-
}
|
|
1340
|
-
return routing.source.platform === "slack" ? "slack" : "internal";
|
|
1341
|
-
}
|
|
1342
|
-
|
|
1343
1329
|
// src/chat/agent/session.ts
|
|
1344
1330
|
async function restoreSessionRecord(args) {
|
|
1345
1331
|
const sessionRecordState = await loadTurnSessionRecord({
|
|
@@ -1365,7 +1351,7 @@ function parseMcpProviderFromToolName(toolName) {
|
|
|
1365
1351
|
|
|
1366
1352
|
// src/chat/pi/derived-state.ts
|
|
1367
1353
|
var MCP_BRIDGE_TOOLS = /* @__PURE__ */ new Set(["callMcpTool", "searchMcpTools"]);
|
|
1368
|
-
function
|
|
1354
|
+
function isRecord2(value) {
|
|
1369
1355
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1370
1356
|
}
|
|
1371
1357
|
function providerFromToolName(value) {
|
|
@@ -1387,7 +1373,7 @@ function addBridgeToolProvider(toolName, value, providers) {
|
|
|
1387
1373
|
if (bridgeTool === "searchMcpTools") {
|
|
1388
1374
|
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
1389
1375
|
const args = value[argsKey];
|
|
1390
|
-
if (
|
|
1376
|
+
if (isRecord2(args)) {
|
|
1391
1377
|
addString(providers, args.provider);
|
|
1392
1378
|
}
|
|
1393
1379
|
}
|
|
@@ -1396,7 +1382,7 @@ function addBridgeToolProvider(toolName, value, providers) {
|
|
|
1396
1382
|
if (bridgeTool === "callMcpTool") {
|
|
1397
1383
|
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
1398
1384
|
const args = value[argsKey];
|
|
1399
|
-
if (
|
|
1385
|
+
if (isRecord2(args)) {
|
|
1400
1386
|
addString(providers, providerFromToolName(args.tool_name));
|
|
1401
1387
|
}
|
|
1402
1388
|
}
|
|
@@ -1409,18 +1395,18 @@ function addMcpResultProvider(message, providers) {
|
|
|
1409
1395
|
return;
|
|
1410
1396
|
}
|
|
1411
1397
|
if (toolName === "loadSkill") {
|
|
1412
|
-
if (
|
|
1398
|
+
if (isRecord2(message.details)) {
|
|
1413
1399
|
addString(providers, message.details.mcp_provider);
|
|
1414
1400
|
}
|
|
1415
1401
|
addString(providers, message.mcp_provider);
|
|
1416
1402
|
return;
|
|
1417
1403
|
}
|
|
1418
1404
|
if (toolName === "searchMcpTools") {
|
|
1419
|
-
if (
|
|
1405
|
+
if (isRecord2(message.details)) {
|
|
1420
1406
|
addString(providers, message.details.provider);
|
|
1421
1407
|
if (Array.isArray(message.details.tools)) {
|
|
1422
1408
|
for (const tool of message.details.tools) {
|
|
1423
|
-
if (
|
|
1409
|
+
if (isRecord2(tool)) {
|
|
1424
1410
|
addString(providers, providerFromToolName(tool.tool_name));
|
|
1425
1411
|
}
|
|
1426
1412
|
}
|
|
@@ -1432,11 +1418,11 @@ function addMcpResultProvider(message, providers) {
|
|
|
1432
1418
|
if (toolName === "callMcpTool") {
|
|
1433
1419
|
for (const argsKey of ["input", "args", "arguments", "params"]) {
|
|
1434
1420
|
const args = message[argsKey];
|
|
1435
|
-
if (
|
|
1421
|
+
if (isRecord2(args)) {
|
|
1436
1422
|
addString(providers, providerFromToolName(args.tool_name));
|
|
1437
1423
|
}
|
|
1438
1424
|
}
|
|
1439
|
-
if (
|
|
1425
|
+
if (isRecord2(message.details)) {
|
|
1440
1426
|
addString(providers, message.details.provider);
|
|
1441
1427
|
addString(providers, providerFromToolName(message.details.tool_name));
|
|
1442
1428
|
}
|
|
@@ -1444,7 +1430,7 @@ function addMcpResultProvider(message, providers) {
|
|
|
1444
1430
|
}
|
|
1445
1431
|
}
|
|
1446
1432
|
function scanMcpProviders(message, providers) {
|
|
1447
|
-
if (!
|
|
1433
|
+
if (!isRecord2(message)) {
|
|
1448
1434
|
return;
|
|
1449
1435
|
}
|
|
1450
1436
|
if (message.role === "toolResult") {
|
|
@@ -1456,15 +1442,15 @@ function scanMcpProviders(message, providers) {
|
|
|
1456
1442
|
return;
|
|
1457
1443
|
}
|
|
1458
1444
|
for (const part of content) {
|
|
1459
|
-
if (!
|
|
1445
|
+
if (!isRecord2(part)) {
|
|
1460
1446
|
continue;
|
|
1461
1447
|
}
|
|
1462
1448
|
addBridgeToolProvider(getToolName(part), part, providers);
|
|
1463
1449
|
}
|
|
1464
1450
|
}
|
|
1465
1451
|
function scanLoadedSkills(message, skills) {
|
|
1466
|
-
if (
|
|
1467
|
-
if (
|
|
1452
|
+
if (isRecord2(message) && message.role === "toolResult" && message.toolName === "loadSkill" && message.isError !== true) {
|
|
1453
|
+
if (isRecord2(message.details)) {
|
|
1468
1454
|
addString(skills, message.details.skill_name);
|
|
1469
1455
|
}
|
|
1470
1456
|
addString(skills, message.skill_name);
|
|
@@ -1551,424 +1537,6 @@ import { isDeepStrictEqual } from "util";
|
|
|
1551
1537
|
// src/chat/prompt.ts
|
|
1552
1538
|
import fs2 from "fs";
|
|
1553
1539
|
import path2 from "path";
|
|
1554
|
-
|
|
1555
|
-
// src/chat/slack/status-format.ts
|
|
1556
|
-
var SLACK_STATUS_MAX_LENGTH = 50;
|
|
1557
|
-
function truncateStatusText(text) {
|
|
1558
|
-
const trimmed = text.trim();
|
|
1559
|
-
if (!trimmed) {
|
|
1560
|
-
return "";
|
|
1561
|
-
}
|
|
1562
|
-
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
|
|
1563
|
-
return trimmed;
|
|
1564
|
-
}
|
|
1565
|
-
return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
// src/chat/slack/mrkdwn.ts
|
|
1569
|
-
function escapeSlackMrkdwnText(text) {
|
|
1570
|
-
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
1571
|
-
}
|
|
1572
|
-
function escapeSlackLinkUrl(url) {
|
|
1573
|
-
return url.replaceAll("&", "&").replaceAll("<", "%3C").replaceAll(">", "%3E");
|
|
1574
|
-
}
|
|
1575
|
-
function formatSlackLink(url, label) {
|
|
1576
|
-
return `<${escapeSlackLinkUrl(url)}|${escapeSlackMrkdwnText(label)}>`;
|
|
1577
|
-
}
|
|
1578
|
-
function readInlineCodeSpan(line, start) {
|
|
1579
|
-
if (line[start] !== "`") {
|
|
1580
|
-
return void 0;
|
|
1581
|
-
}
|
|
1582
|
-
let n = 1;
|
|
1583
|
-
while (line[start + n] === "`") {
|
|
1584
|
-
n++;
|
|
1585
|
-
}
|
|
1586
|
-
const marker = "`".repeat(n);
|
|
1587
|
-
let search = start + n;
|
|
1588
|
-
while (search < line.length) {
|
|
1589
|
-
const close = line.indexOf(marker, search);
|
|
1590
|
-
if (close === -1) {
|
|
1591
|
-
return void 0;
|
|
1592
|
-
}
|
|
1593
|
-
const after = close + n;
|
|
1594
|
-
if (line[after] !== "`") {
|
|
1595
|
-
return { text: line.slice(start, after), end: after };
|
|
1596
|
-
}
|
|
1597
|
-
search = after + 1;
|
|
1598
|
-
}
|
|
1599
|
-
return void 0;
|
|
1600
|
-
}
|
|
1601
|
-
function readExistingSlackAngleToken(line, start) {
|
|
1602
|
-
if (line[start] !== "<") {
|
|
1603
|
-
return void 0;
|
|
1604
|
-
}
|
|
1605
|
-
const close = line.indexOf(">", start + 1);
|
|
1606
|
-
if (close === -1) {
|
|
1607
|
-
return void 0;
|
|
1608
|
-
}
|
|
1609
|
-
const body = line.slice(start + 1, close);
|
|
1610
|
-
if (/^(?:https?:\/\/|@|#|!)/.test(body)) {
|
|
1611
|
-
return { text: line.slice(start, close + 1), end: close + 1 };
|
|
1612
|
-
}
|
|
1613
|
-
return void 0;
|
|
1614
|
-
}
|
|
1615
|
-
function readMarkdownLink(line, start) {
|
|
1616
|
-
if (line[start] !== "[") {
|
|
1617
|
-
return void 0;
|
|
1618
|
-
}
|
|
1619
|
-
const labelEnd = line.indexOf("](", start + 1);
|
|
1620
|
-
if (labelEnd === -1) {
|
|
1621
|
-
return void 0;
|
|
1622
|
-
}
|
|
1623
|
-
const destStart = labelEnd + 2;
|
|
1624
|
-
if (!line.startsWith("http://", destStart) && !line.startsWith("https://", destStart)) {
|
|
1625
|
-
return void 0;
|
|
1626
|
-
}
|
|
1627
|
-
const closeParens = line.indexOf(")", destStart);
|
|
1628
|
-
if (closeParens === -1) {
|
|
1629
|
-
return void 0;
|
|
1630
|
-
}
|
|
1631
|
-
return { text: line.slice(start, closeParens + 1), end: closeParens + 1 };
|
|
1632
|
-
}
|
|
1633
|
-
function hasUnmatchedClosingParen(text) {
|
|
1634
|
-
let balance = 0;
|
|
1635
|
-
for (const ch of text) {
|
|
1636
|
-
if (ch === "(") balance++;
|
|
1637
|
-
else if (ch === ")") balance--;
|
|
1638
|
-
}
|
|
1639
|
-
return balance < 0;
|
|
1640
|
-
}
|
|
1641
|
-
function readBareUrl(line, start) {
|
|
1642
|
-
let end = start;
|
|
1643
|
-
while (end < line.length) {
|
|
1644
|
-
const ch = line[end];
|
|
1645
|
-
if (/\s/.test(ch) || ch === "<" || ch === ">" || ch === '"' || ch === "`" || ch === "|" || ch === "*") {
|
|
1646
|
-
break;
|
|
1647
|
-
}
|
|
1648
|
-
end++;
|
|
1649
|
-
}
|
|
1650
|
-
if (end === start) {
|
|
1651
|
-
return void 0;
|
|
1652
|
-
}
|
|
1653
|
-
let raw = line.slice(start, end);
|
|
1654
|
-
let suffix = "";
|
|
1655
|
-
const peel = () => {
|
|
1656
|
-
suffix = raw.slice(-1) + suffix;
|
|
1657
|
-
raw = raw.slice(0, -1);
|
|
1658
|
-
};
|
|
1659
|
-
const shouldPeel = () => raw.endsWith("_") || /[.,!?;:]$/.test(raw) || raw.endsWith(")") && hasUnmatchedClosingParen(raw);
|
|
1660
|
-
while (raw.length > 0 && shouldPeel()) {
|
|
1661
|
-
peel();
|
|
1662
|
-
}
|
|
1663
|
-
if (!/^https?:\/\/.+/.test(raw)) {
|
|
1664
|
-
return void 0;
|
|
1665
|
-
}
|
|
1666
|
-
return { url: raw, suffix, end };
|
|
1667
|
-
}
|
|
1668
|
-
function wrapBareUrlsOnLine(line) {
|
|
1669
|
-
let result = "";
|
|
1670
|
-
let i = 0;
|
|
1671
|
-
while (i < line.length) {
|
|
1672
|
-
const codeSpan = readInlineCodeSpan(line, i);
|
|
1673
|
-
if (codeSpan) {
|
|
1674
|
-
result += codeSpan.text;
|
|
1675
|
-
i = codeSpan.end;
|
|
1676
|
-
continue;
|
|
1677
|
-
}
|
|
1678
|
-
const angleToken = readExistingSlackAngleToken(line, i);
|
|
1679
|
-
if (angleToken) {
|
|
1680
|
-
result += angleToken.text;
|
|
1681
|
-
i = angleToken.end;
|
|
1682
|
-
continue;
|
|
1683
|
-
}
|
|
1684
|
-
const mdLink = readMarkdownLink(line, i);
|
|
1685
|
-
if (mdLink) {
|
|
1686
|
-
result += mdLink.text;
|
|
1687
|
-
i = mdLink.end;
|
|
1688
|
-
continue;
|
|
1689
|
-
}
|
|
1690
|
-
if (line.startsWith("https://", i) || line.startsWith("http://", i)) {
|
|
1691
|
-
const parsed = readBareUrl(line, i);
|
|
1692
|
-
if (parsed) {
|
|
1693
|
-
result += `<${parsed.url}>${parsed.suffix}`;
|
|
1694
|
-
i = parsed.end;
|
|
1695
|
-
continue;
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
result += line[i];
|
|
1699
|
-
i++;
|
|
1700
|
-
}
|
|
1701
|
-
return result;
|
|
1702
|
-
}
|
|
1703
|
-
function wrapBareUrls(text) {
|
|
1704
|
-
const lines = text.split("\n");
|
|
1705
|
-
const out = [];
|
|
1706
|
-
let inCodeBlock = false;
|
|
1707
|
-
for (const line of lines) {
|
|
1708
|
-
if (line.trimStart().startsWith("```")) {
|
|
1709
|
-
inCodeBlock = !inCodeBlock;
|
|
1710
|
-
out.push(line);
|
|
1711
|
-
continue;
|
|
1712
|
-
}
|
|
1713
|
-
out.push(inCodeBlock ? line : wrapBareUrlsOnLine(line));
|
|
1714
|
-
}
|
|
1715
|
-
return out.join("\n");
|
|
1716
|
-
}
|
|
1717
|
-
function ensureBlockSpacing(text) {
|
|
1718
|
-
const codeBlockPattern = /^```/;
|
|
1719
|
-
const listItemPattern = /^[-*•]\s|^\d+\.\s/;
|
|
1720
|
-
const lines = text.split("\n");
|
|
1721
|
-
const result = [];
|
|
1722
|
-
let inCodeBlock = false;
|
|
1723
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1724
|
-
const line = lines[i];
|
|
1725
|
-
const isCodeFence = codeBlockPattern.test(line.trimStart());
|
|
1726
|
-
if (isCodeFence) {
|
|
1727
|
-
if (!inCodeBlock) {
|
|
1728
|
-
const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
|
|
1729
|
-
if (prev2 !== void 0 && prev2.trim() !== "") {
|
|
1730
|
-
result.push("");
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
inCodeBlock = !inCodeBlock;
|
|
1734
|
-
result.push(line);
|
|
1735
|
-
continue;
|
|
1736
|
-
}
|
|
1737
|
-
if (inCodeBlock) {
|
|
1738
|
-
result.push(line);
|
|
1739
|
-
continue;
|
|
1740
|
-
}
|
|
1741
|
-
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
1742
|
-
if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
|
|
1743
|
-
result.push("");
|
|
1744
|
-
}
|
|
1745
|
-
result.push(line);
|
|
1746
|
-
}
|
|
1747
|
-
return result.join("\n");
|
|
1748
|
-
}
|
|
1749
|
-
function normalizeSlackReplyMarkdown(text) {
|
|
1750
|
-
let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
|
|
1751
|
-
normalized = wrapBareUrls(normalized);
|
|
1752
|
-
normalized = ensureBlockSpacing(normalized);
|
|
1753
|
-
return normalized.replace(/\n{3,}/g, "\n\n").trim();
|
|
1754
|
-
}
|
|
1755
|
-
function normalizeSlackStatusText(text) {
|
|
1756
|
-
const trimmed = text.trim();
|
|
1757
|
-
if (!trimmed) {
|
|
1758
|
-
return "";
|
|
1759
|
-
}
|
|
1760
|
-
return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
// src/chat/slack/output.ts
|
|
1764
|
-
var MAX_INLINE_CHARS = 2200;
|
|
1765
|
-
var MAX_INLINE_LINES = 45;
|
|
1766
|
-
var CONTINUED_MARKER = "\n\n[Continued below]";
|
|
1767
|
-
function countSlackLines(text) {
|
|
1768
|
-
if (!text) {
|
|
1769
|
-
return 0;
|
|
1770
|
-
}
|
|
1771
|
-
return text.split("\n").length;
|
|
1772
|
-
}
|
|
1773
|
-
function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
1774
|
-
return text.length <= maxChars && countSlackLines(text) <= maxLines;
|
|
1775
|
-
}
|
|
1776
|
-
function findSplitIndex(text, maxChars) {
|
|
1777
|
-
if (text.length <= maxChars) {
|
|
1778
|
-
return text.length;
|
|
1779
|
-
}
|
|
1780
|
-
const bounded = text.slice(0, maxChars);
|
|
1781
|
-
const newlineIndex = bounded.lastIndexOf("\n");
|
|
1782
|
-
if (newlineIndex > 0) {
|
|
1783
|
-
return newlineIndex;
|
|
1784
|
-
}
|
|
1785
|
-
const spaceIndex = bounded.lastIndexOf(" ");
|
|
1786
|
-
if (spaceIndex > 0) {
|
|
1787
|
-
return spaceIndex;
|
|
1788
|
-
}
|
|
1789
|
-
return maxChars;
|
|
1790
|
-
}
|
|
1791
|
-
function splitByLineBudget(text, maxLines) {
|
|
1792
|
-
if (maxLines <= 0) {
|
|
1793
|
-
return "";
|
|
1794
|
-
}
|
|
1795
|
-
const lines = text.split("\n");
|
|
1796
|
-
if (lines.length <= maxLines) {
|
|
1797
|
-
return text;
|
|
1798
|
-
}
|
|
1799
|
-
return lines.slice(0, maxLines).join("\n");
|
|
1800
|
-
}
|
|
1801
|
-
function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
1802
|
-
return {
|
|
1803
|
-
maxChars: Math.max(1, maxChars - suffix.length),
|
|
1804
|
-
maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
|
|
1805
|
-
};
|
|
1806
|
-
}
|
|
1807
|
-
function forceSplitBudget(text, budget) {
|
|
1808
|
-
const lineCount = countSlackLines(text);
|
|
1809
|
-
return {
|
|
1810
|
-
maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
|
|
1811
|
-
maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
|
|
1812
|
-
};
|
|
1813
|
-
}
|
|
1814
|
-
function getFenceContinuation(text) {
|
|
1815
|
-
let open;
|
|
1816
|
-
for (const line of text.split("\n")) {
|
|
1817
|
-
const trimmed = line.trimStart();
|
|
1818
|
-
const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
|
|
1819
|
-
if (!openerMatch) {
|
|
1820
|
-
continue;
|
|
1821
|
-
}
|
|
1822
|
-
if (!open) {
|
|
1823
|
-
open = {
|
|
1824
|
-
fence: openerMatch[1],
|
|
1825
|
-
openerLine: trimmed
|
|
1826
|
-
};
|
|
1827
|
-
continue;
|
|
1828
|
-
}
|
|
1829
|
-
if (new RegExp(`^${open.fence}\\s*$`).test(trimmed)) {
|
|
1830
|
-
open = void 0;
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
if (!open) {
|
|
1834
|
-
return null;
|
|
1835
|
-
}
|
|
1836
|
-
return {
|
|
1837
|
-
closeSuffix: text.endsWith("\n") ? open.fence : `
|
|
1838
|
-
${open.fence}`,
|
|
1839
|
-
reopenPrefix: `${open.openerLine}
|
|
1840
|
-
`
|
|
1841
|
-
};
|
|
1842
|
-
}
|
|
1843
|
-
function appendSlackSuffix(text, marker) {
|
|
1844
|
-
const carryover = getFenceContinuation(text);
|
|
1845
|
-
return `${text}${carryover?.closeSuffix ?? ""}${marker}`;
|
|
1846
|
-
}
|
|
1847
|
-
function stripTrailingContinuationMarker(text) {
|
|
1848
|
-
return text.endsWith(CONTINUED_MARKER) ? text.slice(0, -CONTINUED_MARKER.length) : text;
|
|
1849
|
-
}
|
|
1850
|
-
function takeSlackContinuationChunk(text, budget) {
|
|
1851
|
-
let { prefix, rest } = takeSlackInlinePrefix(text, budget);
|
|
1852
|
-
if (!rest) {
|
|
1853
|
-
({ prefix, rest } = takeSlackInlinePrefix(
|
|
1854
|
-
text,
|
|
1855
|
-
forceSplitBudget(text, budget)
|
|
1856
|
-
));
|
|
1857
|
-
}
|
|
1858
|
-
let carryover = rest ? getFenceContinuation(prefix) : null;
|
|
1859
|
-
if (!carryover) {
|
|
1860
|
-
return { prefix, rest };
|
|
1861
|
-
}
|
|
1862
|
-
const carryoverBudget = reserveInlineBudgetForSuffix(
|
|
1863
|
-
`${carryover.closeSuffix}${CONTINUED_MARKER}`
|
|
1864
|
-
);
|
|
1865
|
-
({ prefix, rest } = takeSlackInlinePrefix(text, carryoverBudget));
|
|
1866
|
-
if (!rest) {
|
|
1867
|
-
({ prefix, rest } = takeSlackInlinePrefix(
|
|
1868
|
-
text,
|
|
1869
|
-
forceSplitBudget(text, carryoverBudget)
|
|
1870
|
-
));
|
|
1871
|
-
}
|
|
1872
|
-
carryover = rest ? getFenceContinuation(prefix) : null;
|
|
1873
|
-
if (!carryover) {
|
|
1874
|
-
return { prefix, rest };
|
|
1875
|
-
}
|
|
1876
|
-
return {
|
|
1877
|
-
prefix,
|
|
1878
|
-
rest: `${carryover.reopenPrefix}${rest}`
|
|
1879
|
-
};
|
|
1880
|
-
}
|
|
1881
|
-
function takeSlackContinuationPrefix(text, options) {
|
|
1882
|
-
const budget = {
|
|
1883
|
-
maxChars: options?.maxChars ?? getSlackContinuationBudget().maxChars,
|
|
1884
|
-
maxLines: options?.maxLines ?? getSlackContinuationBudget().maxLines
|
|
1885
|
-
};
|
|
1886
|
-
const { prefix, rest } = (() => {
|
|
1887
|
-
if (options?.forceSplit) {
|
|
1888
|
-
return takeSlackContinuationChunk(text, budget);
|
|
1889
|
-
}
|
|
1890
|
-
const initial = takeSlackInlinePrefix(text, budget);
|
|
1891
|
-
return initial.rest ? takeSlackContinuationChunk(text, budget) : initial;
|
|
1892
|
-
})();
|
|
1893
|
-
return {
|
|
1894
|
-
prefix,
|
|
1895
|
-
renderedPrefix: rest ? appendSlackSuffix(prefix, CONTINUED_MARKER) : prefix,
|
|
1896
|
-
rest
|
|
1897
|
-
};
|
|
1898
|
-
}
|
|
1899
|
-
function takeSlackInlinePrefix(text, options) {
|
|
1900
|
-
const maxChars = options?.maxChars ?? MAX_INLINE_CHARS;
|
|
1901
|
-
const maxLines = options?.maxLines ?? MAX_INLINE_LINES;
|
|
1902
|
-
const normalized = text.replace(/\r\n?/g, "\n");
|
|
1903
|
-
if (!normalized) {
|
|
1904
|
-
return { prefix: "", rest: "" };
|
|
1905
|
-
}
|
|
1906
|
-
if (fitsInlineBudget(normalized, maxChars, maxLines)) {
|
|
1907
|
-
return { prefix: normalized, rest: "" };
|
|
1908
|
-
}
|
|
1909
|
-
const lineBounded = splitByLineBudget(normalized, maxLines);
|
|
1910
|
-
const cutIndex = findSplitIndex(lineBounded, maxChars);
|
|
1911
|
-
const prefix = lineBounded.slice(0, cutIndex).trimEnd();
|
|
1912
|
-
if (prefix) {
|
|
1913
|
-
return {
|
|
1914
|
-
prefix,
|
|
1915
|
-
rest: normalized.slice(prefix.length).trimStart()
|
|
1916
|
-
};
|
|
1917
|
-
}
|
|
1918
|
-
const hardPrefix = normalized.slice(0, Math.max(1, maxChars)).trimEnd();
|
|
1919
|
-
return {
|
|
1920
|
-
prefix: hardPrefix || normalized.slice(0, Math.max(1, maxChars)),
|
|
1921
|
-
rest: normalized.slice(hardPrefix.length || Math.max(1, maxChars)).trimStart()
|
|
1922
|
-
};
|
|
1923
|
-
}
|
|
1924
|
-
function splitSlackReplyText(text, options) {
|
|
1925
|
-
const normalized = normalizeSlackReplyMarkdown(text);
|
|
1926
|
-
if (!normalized) {
|
|
1927
|
-
return [];
|
|
1928
|
-
}
|
|
1929
|
-
const chunks = [];
|
|
1930
|
-
const continuationBudget = reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
1931
|
-
let remaining = normalized;
|
|
1932
|
-
while (remaining) {
|
|
1933
|
-
const fitsFinalChunk = options?.interrupted ? fitsInlineBudget(appendSlackSuffix(remaining, getInterruptionMarker())) : fitsInlineBudget(remaining);
|
|
1934
|
-
if (fitsFinalChunk) {
|
|
1935
|
-
chunks.push(
|
|
1936
|
-
options?.interrupted ? appendSlackSuffix(remaining, getInterruptionMarker()) : remaining
|
|
1937
|
-
);
|
|
1938
|
-
break;
|
|
1939
|
-
}
|
|
1940
|
-
const { renderedPrefix, rest } = takeSlackContinuationPrefix(remaining, {
|
|
1941
|
-
...continuationBudget,
|
|
1942
|
-
forceSplit: true
|
|
1943
|
-
});
|
|
1944
|
-
chunks.push(renderedPrefix);
|
|
1945
|
-
remaining = rest;
|
|
1946
|
-
}
|
|
1947
|
-
if (chunks.length === 2) {
|
|
1948
|
-
chunks[0] = stripTrailingContinuationMarker(chunks[0] ?? "");
|
|
1949
|
-
}
|
|
1950
|
-
return chunks;
|
|
1951
|
-
}
|
|
1952
|
-
function getSlackContinuationBudget() {
|
|
1953
|
-
return reserveInlineBudgetForSuffix(CONTINUED_MARKER);
|
|
1954
|
-
}
|
|
1955
|
-
function buildSlackOutputMessage(text) {
|
|
1956
|
-
const normalized = normalizeSlackReplyMarkdown(text);
|
|
1957
|
-
if (!normalized) {
|
|
1958
|
-
throw new Error(
|
|
1959
|
-
`Slack output normalized to empty content: original_length=${text.length} parsed_length=${normalized.length}`
|
|
1960
|
-
);
|
|
1961
|
-
}
|
|
1962
|
-
return {
|
|
1963
|
-
markdown: normalized
|
|
1964
|
-
};
|
|
1965
|
-
}
|
|
1966
|
-
var slackOutputPolicy = {
|
|
1967
|
-
maxInlineChars: MAX_INLINE_CHARS,
|
|
1968
|
-
maxInlineLines: MAX_INLINE_LINES
|
|
1969
|
-
};
|
|
1970
|
-
|
|
1971
|
-
// src/chat/prompt.ts
|
|
1972
1540
|
var DEFAULT_SOUL = "You are Junior, a practical and concise assistant.";
|
|
1973
1541
|
function getLoggedMarkdownFiles() {
|
|
1974
1542
|
const globalState = globalThis;
|
|
@@ -2802,160 +2370,6 @@ async function assemblePrompt(args) {
|
|
|
2802
2370
|
};
|
|
2803
2371
|
}
|
|
2804
2372
|
|
|
2805
|
-
// src/chat/capabilities/catalog.ts
|
|
2806
|
-
var cachedCatalog;
|
|
2807
|
-
function getCapabilityCatalog() {
|
|
2808
|
-
const signature = pluginCatalogRuntime.getSignature();
|
|
2809
|
-
if (cachedCatalog?.signature === signature) return cachedCatalog;
|
|
2810
|
-
const providers = pluginCatalogRuntime.getCapabilityProviders();
|
|
2811
|
-
const capabilityToProvider = /* @__PURE__ */ new Map();
|
|
2812
|
-
for (const provider of providers) {
|
|
2813
|
-
for (const capability of provider.capabilities) {
|
|
2814
|
-
if (capabilityToProvider.has(capability)) {
|
|
2815
|
-
throw new Error(
|
|
2816
|
-
`Duplicate capability registration for "${capability}"`
|
|
2817
|
-
);
|
|
2818
|
-
}
|
|
2819
|
-
capabilityToProvider.set(capability, provider);
|
|
2820
|
-
}
|
|
2821
|
-
}
|
|
2822
|
-
cachedCatalog = { signature, providers, capabilityToProvider };
|
|
2823
|
-
return cachedCatalog;
|
|
2824
|
-
}
|
|
2825
|
-
var catalogLogged = false;
|
|
2826
|
-
function logCapabilityCatalogLoadedOnce() {
|
|
2827
|
-
if (catalogLogged) return;
|
|
2828
|
-
catalogLogged = true;
|
|
2829
|
-
const { providers } = getCapabilityCatalog();
|
|
2830
|
-
const capabilityNames = providers.flatMap((p) => p.capabilities).sort();
|
|
2831
|
-
const configKeys = [
|
|
2832
|
-
...new Set(providers.flatMap((p) => p.configKeys))
|
|
2833
|
-
].sort();
|
|
2834
|
-
logInfo(
|
|
2835
|
-
"capability_catalog_loaded",
|
|
2836
|
-
{},
|
|
2837
|
-
{
|
|
2838
|
-
"app.capability.providers": providers.map((p) => p.provider),
|
|
2839
|
-
"app.capability.count": capabilityNames.length,
|
|
2840
|
-
"app.capability.names": capabilityNames,
|
|
2841
|
-
"app.config.key_count": configKeys.length,
|
|
2842
|
-
"app.config.keys": configKeys
|
|
2843
|
-
},
|
|
2844
|
-
"Loaded capability provider catalog"
|
|
2845
|
-
);
|
|
2846
|
-
}
|
|
2847
|
-
|
|
2848
|
-
// src/chat/capabilities/router.ts
|
|
2849
|
-
var ProviderCredentialRouter = class {
|
|
2850
|
-
brokersByProvider;
|
|
2851
|
-
constructor(input) {
|
|
2852
|
-
this.brokersByProvider = input.brokersByProvider;
|
|
2853
|
-
}
|
|
2854
|
-
async issue(input) {
|
|
2855
|
-
const broker = this.brokersByProvider[input.provider];
|
|
2856
|
-
if (!broker) {
|
|
2857
|
-
throw new Error(
|
|
2858
|
-
`No credential broker registered for provider: ${input.provider}`
|
|
2859
|
-
);
|
|
2860
|
-
}
|
|
2861
|
-
return await broker.issue({
|
|
2862
|
-
context: input.context,
|
|
2863
|
-
reason: input.reason
|
|
2864
|
-
});
|
|
2865
|
-
}
|
|
2866
|
-
};
|
|
2867
|
-
|
|
2868
|
-
// src/chat/credentials/user-token-store.ts
|
|
2869
|
-
import {
|
|
2870
|
-
pluginStoredTokensSchema
|
|
2871
|
-
} from "@sentry/junior-plugin-api";
|
|
2872
|
-
var storedTokensSchema = pluginStoredTokensSchema;
|
|
2873
|
-
|
|
2874
|
-
// src/chat/credentials/state-adapter-token-store.ts
|
|
2875
|
-
var KEY_PREFIX = "oauth-token";
|
|
2876
|
-
var BUFFER_MS = 24 * 60 * 60 * 1e3;
|
|
2877
|
-
var LONG_LIVED_TTL_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
2878
|
-
var REFRESH_LOCK_WAIT_MS = 3e4;
|
|
2879
|
-
var REFRESH_LOCK_RETRY_MS = 100;
|
|
2880
|
-
function tokenKey(userId, provider) {
|
|
2881
|
-
return `${KEY_PREFIX}:${userId}:${provider}`;
|
|
2882
|
-
}
|
|
2883
|
-
function refreshLockKey(userId, provider) {
|
|
2884
|
-
return `${tokenKey(userId, provider)}:refresh`;
|
|
2885
|
-
}
|
|
2886
|
-
var StateAdapterTokenStore = class {
|
|
2887
|
-
state;
|
|
2888
|
-
constructor(stateAdapter) {
|
|
2889
|
-
this.state = stateAdapter;
|
|
2890
|
-
}
|
|
2891
|
-
async get(userId, provider) {
|
|
2892
|
-
const stored = await this.state.get(tokenKey(userId, provider));
|
|
2893
|
-
return stored === null || stored === void 0 ? void 0 : storedTokensSchema.parse(stored);
|
|
2894
|
-
}
|
|
2895
|
-
async set(userId, provider, tokens) {
|
|
2896
|
-
const parsed = storedTokensSchema.parse(tokens);
|
|
2897
|
-
const expiresAt = parsed.refreshTokenExpiresAt ?? parsed.expiresAt;
|
|
2898
|
-
const ttlMs2 = expiresAt ? Math.max(expiresAt - Date.now() + BUFFER_MS, BUFFER_MS) : LONG_LIVED_TTL_MS;
|
|
2899
|
-
await this.state.set(tokenKey(userId, provider), parsed, ttlMs2);
|
|
2900
|
-
}
|
|
2901
|
-
async delete(userId, provider) {
|
|
2902
|
-
await this.state.delete(tokenKey(userId, provider));
|
|
2903
|
-
}
|
|
2904
|
-
/** Wait for the per-slot refresh gate so rotated refresh tokens are used once. */
|
|
2905
|
-
async withRefresh(userId, provider, callback) {
|
|
2906
|
-
const lockKey = refreshLockKey(userId, provider);
|
|
2907
|
-
const deadline = Date.now() + REFRESH_LOCK_WAIT_MS;
|
|
2908
|
-
while (true) {
|
|
2909
|
-
const lock = await acquireActiveLock(this.state, lockKey);
|
|
2910
|
-
if (lock) {
|
|
2911
|
-
try {
|
|
2912
|
-
return await callback();
|
|
2913
|
-
} finally {
|
|
2914
|
-
await this.state.releaseLock(lock);
|
|
2915
|
-
}
|
|
2916
|
-
}
|
|
2917
|
-
if (Date.now() >= deadline) {
|
|
2918
|
-
throw new Error(`Could not acquire OAuth token refresh lock`);
|
|
2919
|
-
}
|
|
2920
|
-
await sleep(REFRESH_LOCK_RETRY_MS);
|
|
2921
|
-
}
|
|
2922
|
-
}
|
|
2923
|
-
};
|
|
2924
|
-
|
|
2925
|
-
// src/chat/capabilities/factory.ts
|
|
2926
|
-
var sandboxEgressRouters = /* @__PURE__ */ new WeakMap();
|
|
2927
|
-
function createUserTokenStore() {
|
|
2928
|
-
return new StateAdapterTokenStore(getStateAdapter());
|
|
2929
|
-
}
|
|
2930
|
-
function createProviderCredentialRouter(userTokenStore) {
|
|
2931
|
-
logCapabilityCatalogLoadedOnce();
|
|
2932
|
-
const brokersByProvider = {};
|
|
2933
|
-
for (const plugin of pluginCatalogRuntime.getProviders()) {
|
|
2934
|
-
const { name } = plugin.manifest;
|
|
2935
|
-
if (!plugin.manifest.credentials && !plugin.manifest.apiHeaders) {
|
|
2936
|
-
continue;
|
|
2937
|
-
}
|
|
2938
|
-
brokersByProvider[name] = pluginCatalogRuntime.createBroker(name, {
|
|
2939
|
-
userTokenStore
|
|
2940
|
-
});
|
|
2941
|
-
}
|
|
2942
|
-
return new ProviderCredentialRouter({ brokersByProvider });
|
|
2943
|
-
}
|
|
2944
|
-
function getSandboxEgressRouter() {
|
|
2945
|
-
const stateAdapter = getStateAdapter();
|
|
2946
|
-
let router = sandboxEgressRouters.get(stateAdapter);
|
|
2947
|
-
if (!router) {
|
|
2948
|
-
router = createProviderCredentialRouter(
|
|
2949
|
-
new StateAdapterTokenStore(stateAdapter)
|
|
2950
|
-
);
|
|
2951
|
-
sandboxEgressRouters.set(stateAdapter, router);
|
|
2952
|
-
}
|
|
2953
|
-
return router;
|
|
2954
|
-
}
|
|
2955
|
-
async function issueProviderCredentialLease(input) {
|
|
2956
|
-
return await getSandboxEgressRouter().issue(input);
|
|
2957
|
-
}
|
|
2958
|
-
|
|
2959
2373
|
// src/chat/capabilities/jr-rpc-command.ts
|
|
2960
2374
|
import { Bash, defineCommand } from "just-bash";
|
|
2961
2375
|
function commandResult(input) {
|
|
@@ -3185,672 +2599,137 @@ ${usage}
|
|
|
3185
2599
|
}))
|
|
3186
2600
|
},
|
|
3187
2601
|
exitCode: 0
|
|
3188
|
-
});
|
|
3189
|
-
}
|
|
3190
|
-
return commandResult({
|
|
3191
|
-
stderr: `Usage:
|
|
3192
|
-
${usage}
|
|
3193
|
-
`,
|
|
3194
|
-
exitCode: 2
|
|
3195
|
-
});
|
|
3196
|
-
}
|
|
3197
|
-
async function handlePluginsCommand(args) {
|
|
3198
|
-
const usage = "jr-rpc plugins list";
|
|
3199
|
-
const subverb = (args[0] ?? "").trim();
|
|
3200
|
-
if (subverb !== "list" || args.length !== 1) {
|
|
3201
|
-
return commandResult({
|
|
3202
|
-
stderr: `Usage:
|
|
3203
|
-
${usage}
|
|
3204
|
-
`,
|
|
3205
|
-
exitCode: 2
|
|
3206
|
-
});
|
|
3207
|
-
}
|
|
3208
|
-
const plugins = pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
3209
|
-
name: plugin.manifest.name,
|
|
3210
|
-
displayName: plugin.manifest.displayName,
|
|
3211
|
-
description: plugin.manifest.description,
|
|
3212
|
-
capabilities: [...plugin.manifest.capabilities],
|
|
3213
|
-
configKeys: [...plugin.manifest.configKeys]
|
|
3214
|
-
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
3215
|
-
return commandResult({
|
|
3216
|
-
stdout: {
|
|
3217
|
-
ok: true,
|
|
3218
|
-
plugins
|
|
3219
|
-
},
|
|
3220
|
-
exitCode: 0
|
|
3221
|
-
});
|
|
3222
|
-
}
|
|
3223
|
-
function createJrRpcCommand(deps) {
|
|
3224
|
-
return defineCommand("jr-rpc", async (args) => {
|
|
3225
|
-
const usage = [
|
|
3226
|
-
"jr-rpc config get <key>",
|
|
3227
|
-
"jr-rpc config set <key> <value> [--json]",
|
|
3228
|
-
"jr-rpc config unset <key>",
|
|
3229
|
-
"jr-rpc config list [--prefix <value>]",
|
|
3230
|
-
"jr-rpc plugins list"
|
|
3231
|
-
].join("\n");
|
|
3232
|
-
const verb = (args[0] ?? "").trim();
|
|
3233
|
-
if (verb === "config") {
|
|
3234
|
-
return handleConfigCommand(args.slice(1), deps);
|
|
3235
|
-
}
|
|
3236
|
-
if (verb === "plugins") {
|
|
3237
|
-
return handlePluginsCommand(args.slice(1));
|
|
3238
|
-
}
|
|
3239
|
-
return commandResult({
|
|
3240
|
-
stderr: `Unsupported jr-rpc command. Use:
|
|
3241
|
-
${usage}
|
|
3242
|
-
`,
|
|
3243
|
-
exitCode: 2
|
|
3244
|
-
});
|
|
3245
|
-
});
|
|
3246
|
-
}
|
|
3247
|
-
async function maybeExecuteJrRpcCustomCommand(command, deps) {
|
|
3248
|
-
const normalized = command.trim();
|
|
3249
|
-
if (!/^jr-rpc(?:\s|$)/.test(normalized)) {
|
|
3250
|
-
return { handled: false };
|
|
3251
|
-
}
|
|
3252
|
-
const shell = new Bash({
|
|
3253
|
-
customCommands: [createJrRpcCommand(deps)]
|
|
3254
|
-
});
|
|
3255
|
-
const execResult = await shell.exec(normalized);
|
|
3256
|
-
return {
|
|
3257
|
-
handled: true,
|
|
3258
|
-
result: {
|
|
3259
|
-
ok: execResult.exitCode === 0,
|
|
3260
|
-
command: normalized,
|
|
3261
|
-
cwd: "/",
|
|
3262
|
-
exit_code: execResult.exitCode,
|
|
3263
|
-
signal: null,
|
|
3264
|
-
timed_out: false,
|
|
3265
|
-
stdout: execResult.stdout,
|
|
3266
|
-
stderr: execResult.stderr,
|
|
3267
|
-
stdout_truncated: false,
|
|
3268
|
-
stderr_truncated: false
|
|
3269
|
-
}
|
|
3270
|
-
};
|
|
3271
|
-
}
|
|
3272
|
-
|
|
3273
|
-
// src/chat/sandbox/command-result.ts
|
|
3274
|
-
function formatSandboxCommandResult(params) {
|
|
3275
|
-
return makeStructuredToolResult({
|
|
3276
|
-
ok: params.ok,
|
|
3277
|
-
status: params.ok ? "success" : "error",
|
|
3278
|
-
target: params.command,
|
|
3279
|
-
data: {
|
|
3280
|
-
command: params.command,
|
|
3281
|
-
cwd: params.cwd,
|
|
3282
|
-
exit_code: params.exit_code,
|
|
3283
|
-
signal: params.signal,
|
|
3284
|
-
timed_out: params.timed_out,
|
|
3285
|
-
aborted: Boolean(params.aborted),
|
|
3286
|
-
stdout: params.stdout,
|
|
3287
|
-
stderr: params.stderr,
|
|
3288
|
-
stdout_truncated: params.stdout_truncated,
|
|
3289
|
-
stderr_truncated: params.stderr_truncated
|
|
3290
|
-
},
|
|
3291
|
-
truncated: params.stdout_truncated || params.stderr_truncated,
|
|
3292
|
-
...!params.ok ? {
|
|
3293
|
-
error: {
|
|
3294
|
-
kind: params.aborted ? "outcome_unknown" : params.timed_out ? "timeout" : "nonzero_exit",
|
|
3295
|
-
message: params.aborted ? "Command was interrupted before its outcome was confirmed. It may have produced side effects; reconcile external state before retrying or reporting failure." : params.stderr.trim() || `Command exited with code ${params.exit_code}`,
|
|
3296
|
-
retryable: params.timed_out
|
|
3297
|
-
}
|
|
3298
|
-
} : {},
|
|
3299
|
-
command: params.command,
|
|
3300
|
-
cwd: params.cwd,
|
|
3301
|
-
exit_code: params.exit_code,
|
|
3302
|
-
signal: params.signal,
|
|
3303
|
-
timed_out: params.timed_out,
|
|
3304
|
-
aborted: Boolean(params.aborted),
|
|
3305
|
-
stdout: params.stdout,
|
|
3306
|
-
stderr: params.stderr,
|
|
3307
|
-
stdout_truncated: params.stdout_truncated,
|
|
3308
|
-
stderr_truncated: params.stderr_truncated,
|
|
3309
|
-
...params.auth_required ? { auth_required: params.auth_required } : {},
|
|
3310
|
-
...params.permission_denied ? { permission_denied: params.permission_denied } : {}
|
|
3311
|
-
});
|
|
3312
|
-
}
|
|
3313
|
-
|
|
3314
|
-
// src/chat/sandbox/sandbox.ts
|
|
3315
|
-
import fs4 from "fs/promises";
|
|
3316
|
-
|
|
3317
|
-
// src/chat/oauth-flow.ts
|
|
3318
|
-
import { randomBytes } from "crypto";
|
|
3319
|
-
import {
|
|
3320
|
-
sourceSchema
|
|
3321
|
-
} from "@sentry/junior-plugin-api";
|
|
3322
|
-
|
|
3323
|
-
// src/chat/slack/outbound.ts
|
|
3324
|
-
var MAX_SLACK_MESSAGE_TEXT_CHARS = 4e4;
|
|
3325
|
-
function requireSlackConversationId(channelId, action) {
|
|
3326
|
-
const normalized = normalizeSlackConversationId(channelId);
|
|
3327
|
-
if (!normalized) {
|
|
3328
|
-
throw new Error(`${action} requires a valid channel ID`);
|
|
3329
|
-
}
|
|
3330
|
-
return normalized;
|
|
3331
|
-
}
|
|
3332
|
-
function requireSlackThreadTimestamp(threadTs, action) {
|
|
3333
|
-
const normalized = threadTs.trim();
|
|
3334
|
-
if (!normalized) {
|
|
3335
|
-
throw new Error(`${action} requires a thread timestamp`);
|
|
3336
|
-
}
|
|
3337
|
-
return normalized;
|
|
3338
|
-
}
|
|
3339
|
-
function requireSlackMessageTimestamp(timestamp, action) {
|
|
3340
|
-
const normalized = parseSlackMessageTs(timestamp);
|
|
3341
|
-
if (!normalized) {
|
|
3342
|
-
throw new Error(`${action} requires a target message timestamp`);
|
|
3343
|
-
}
|
|
3344
|
-
return normalized;
|
|
3345
|
-
}
|
|
3346
|
-
function requireSlackMessageText(text, action) {
|
|
3347
|
-
if (text.trim().length === 0) {
|
|
3348
|
-
throw new Error(`${action} requires non-empty text`);
|
|
3349
|
-
}
|
|
3350
|
-
if (text.length > MAX_SLACK_MESSAGE_TEXT_CHARS) {
|
|
3351
|
-
throw new Error(
|
|
3352
|
-
`${action} text exceeds Slack's 40000 character truncation limit`
|
|
3353
|
-
);
|
|
3354
|
-
}
|
|
3355
|
-
return text;
|
|
3356
|
-
}
|
|
3357
|
-
async function getSlackMessagePermalink(args) {
|
|
3358
|
-
try {
|
|
3359
|
-
const response = await withSlackRetries(
|
|
3360
|
-
() => getSlackClient().chat.getPermalink({
|
|
3361
|
-
channel: args.channelId,
|
|
3362
|
-
message_ts: args.messageTs
|
|
3363
|
-
}),
|
|
3364
|
-
3,
|
|
3365
|
-
{
|
|
3366
|
-
action: "chat.getPermalink",
|
|
3367
|
-
idempotent: true,
|
|
3368
|
-
spanAttributes: {
|
|
3369
|
-
"app.slack.channel_id": args.channelId,
|
|
3370
|
-
"app.slack.message_ts": args.messageTs
|
|
3371
|
-
}
|
|
3372
|
-
}
|
|
3373
|
-
);
|
|
3374
|
-
return response.permalink;
|
|
3375
|
-
} catch {
|
|
3376
|
-
return void 0;
|
|
3377
|
-
}
|
|
3378
|
-
}
|
|
3379
|
-
async function postSlackMessage(input) {
|
|
3380
|
-
const channelId = requireSlackConversationId(
|
|
3381
|
-
input.channelId,
|
|
3382
|
-
"Slack message posting"
|
|
3383
|
-
);
|
|
3384
|
-
const text = requireSlackMessageText(input.text, "Slack message posting");
|
|
3385
|
-
const threadTs = input.threadTs ? requireSlackThreadTimestamp(
|
|
3386
|
-
input.threadTs,
|
|
3387
|
-
"Slack thread message posting"
|
|
3388
|
-
) : void 0;
|
|
3389
|
-
const response = await withSlackRetries(
|
|
3390
|
-
() => getSlackClient().chat.postMessage({
|
|
3391
|
-
channel: channelId,
|
|
3392
|
-
text,
|
|
3393
|
-
...input.blocks?.length ? {
|
|
3394
|
-
blocks: input.blocks
|
|
3395
|
-
} : {},
|
|
3396
|
-
...threadTs ? { thread_ts: threadTs } : {}
|
|
3397
|
-
}),
|
|
3398
|
-
3,
|
|
3399
|
-
{
|
|
3400
|
-
action: "chat.postMessage",
|
|
3401
|
-
spanAttributes: {
|
|
3402
|
-
"app.slack.channel_id": channelId,
|
|
3403
|
-
...threadTs ? { "app.slack.thread_ts": threadTs } : {}
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
|
-
);
|
|
3407
|
-
const messageTs = parseSlackMessageTs(response.ts);
|
|
3408
|
-
if (!messageTs) {
|
|
3409
|
-
throw new Error("Slack message posted without ts");
|
|
3410
|
-
}
|
|
3411
|
-
return {
|
|
3412
|
-
ts: messageTs,
|
|
3413
|
-
...input.includePermalink ? {
|
|
3414
|
-
permalink: await getSlackMessagePermalink({
|
|
3415
|
-
channelId,
|
|
3416
|
-
messageTs
|
|
3417
|
-
})
|
|
3418
|
-
} : {}
|
|
3419
|
-
};
|
|
3420
|
-
}
|
|
3421
|
-
async function postSlackEphemeralMessage(input) {
|
|
3422
|
-
const channelId = requireSlackConversationId(
|
|
3423
|
-
input.channelId,
|
|
3424
|
-
"Slack ephemeral message posting"
|
|
3425
|
-
);
|
|
3426
|
-
const userId = parseSlackUserId(input.userId);
|
|
3427
|
-
if (!userId) {
|
|
3428
|
-
throw new Error("Slack ephemeral message posting requires a user ID");
|
|
3429
|
-
}
|
|
3430
|
-
const text = requireSlackMessageText(
|
|
3431
|
-
input.text,
|
|
3432
|
-
"Slack ephemeral message posting"
|
|
3433
|
-
);
|
|
3434
|
-
const threadTs = input.threadTs ? requireSlackThreadTimestamp(
|
|
3435
|
-
input.threadTs,
|
|
3436
|
-
"Slack ephemeral thread message posting"
|
|
3437
|
-
) : void 0;
|
|
3438
|
-
const response = await withSlackRetries(
|
|
3439
|
-
() => getSlackClient().chat.postEphemeral({
|
|
3440
|
-
channel: channelId,
|
|
3441
|
-
user: userId,
|
|
3442
|
-
text,
|
|
3443
|
-
...threadTs ? { thread_ts: threadTs } : {}
|
|
3444
|
-
}),
|
|
3445
|
-
3,
|
|
3446
|
-
{
|
|
3447
|
-
action: "chat.postEphemeral",
|
|
3448
|
-
spanAttributes: {
|
|
3449
|
-
"app.slack.channel_id": channelId,
|
|
3450
|
-
"app.slack.user_id": userId,
|
|
3451
|
-
...threadTs ? { "app.slack.thread_ts": threadTs } : {}
|
|
3452
|
-
}
|
|
3453
|
-
}
|
|
3454
|
-
);
|
|
3455
|
-
return {
|
|
3456
|
-
messageTs: response.message_ts
|
|
3457
|
-
};
|
|
3458
|
-
}
|
|
3459
|
-
async function uploadFilesToConversation(input) {
|
|
3460
|
-
const channelId = requireSlackConversationId(
|
|
3461
|
-
input.channelId,
|
|
3462
|
-
"Slack file upload"
|
|
3463
|
-
);
|
|
3464
|
-
const threadTs = input.threadTs ? requireSlackThreadTimestamp(input.threadTs, "Slack file upload") : void 0;
|
|
3465
|
-
if (input.files.length === 0) {
|
|
3466
|
-
throw new Error("Slack file upload requires at least one file");
|
|
3467
|
-
}
|
|
3468
|
-
const fileUploads = input.files.map((file) => {
|
|
3469
|
-
const filename = file.filename.trim();
|
|
3470
|
-
if (!filename) {
|
|
3471
|
-
throw new Error(
|
|
3472
|
-
"Slack file upload requires every file to have a filename"
|
|
3473
|
-
);
|
|
3474
|
-
}
|
|
3475
|
-
return {
|
|
3476
|
-
file: file.data,
|
|
3477
|
-
filename
|
|
3478
|
-
};
|
|
3479
|
-
});
|
|
3480
|
-
const response = await withSlackRetries(
|
|
3481
|
-
() => getSlackClient().filesUploadV2({
|
|
3482
|
-
channel_id: channelId,
|
|
3483
|
-
...threadTs ? { thread_ts: threadTs } : {},
|
|
3484
|
-
file_uploads: fileUploads
|
|
3485
|
-
}),
|
|
3486
|
-
3,
|
|
3487
|
-
{
|
|
3488
|
-
action: "filesUploadV2",
|
|
3489
|
-
spanAttributes: {
|
|
3490
|
-
"app.slack.channel_id": channelId,
|
|
3491
|
-
...threadTs ? { "app.slack.thread_ts": threadTs } : {}
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
);
|
|
3495
|
-
return {
|
|
3496
|
-
files: (response.files ?? []).flatMap(
|
|
3497
|
-
(completion) => (completion.files ?? []).map((file) => file.id ? { id: file.id } : {})
|
|
3498
|
-
)
|
|
3499
|
-
};
|
|
3500
|
-
}
|
|
3501
|
-
async function addReactionToMessage(input) {
|
|
3502
|
-
const channelId = requireSlackConversationId(
|
|
3503
|
-
input.channelId,
|
|
3504
|
-
"Slack reaction"
|
|
3505
|
-
);
|
|
3506
|
-
const timestamp = requireSlackMessageTimestamp(
|
|
3507
|
-
input.timestamp,
|
|
3508
|
-
"Slack reaction"
|
|
3509
|
-
);
|
|
3510
|
-
const emoji = normalizeSlackEmojiName(input.emoji);
|
|
3511
|
-
if (!emoji) {
|
|
3512
|
-
throw new Error("Slack reaction requires a valid emoji alias name");
|
|
3513
|
-
}
|
|
3514
|
-
try {
|
|
3515
|
-
await withSlackRetries(
|
|
3516
|
-
() => getSlackClient().reactions.add({
|
|
3517
|
-
channel: channelId,
|
|
3518
|
-
timestamp,
|
|
3519
|
-
name: emoji
|
|
3520
|
-
}),
|
|
3521
|
-
3,
|
|
3522
|
-
{
|
|
3523
|
-
action: "reactions.add",
|
|
3524
|
-
idempotent: true,
|
|
3525
|
-
spanAttributes: {
|
|
3526
|
-
"app.slack.channel_id": channelId,
|
|
3527
|
-
"app.slack.message_ts": timestamp,
|
|
3528
|
-
"app.slack.reaction": emoji
|
|
3529
|
-
}
|
|
3530
|
-
}
|
|
3531
|
-
);
|
|
3532
|
-
} catch (error) {
|
|
3533
|
-
if (error instanceof SlackActionError && error.code === "already_reacted") {
|
|
3534
|
-
return { ok: true };
|
|
3535
|
-
}
|
|
3536
|
-
throw error;
|
|
3537
|
-
}
|
|
3538
|
-
return { ok: true };
|
|
3539
|
-
}
|
|
3540
|
-
async function removeReactionFromMessage(input) {
|
|
3541
|
-
const channelId = requireSlackConversationId(
|
|
3542
|
-
input.channelId,
|
|
3543
|
-
"Slack reaction removal"
|
|
3544
|
-
);
|
|
3545
|
-
const timestamp = requireSlackMessageTimestamp(
|
|
3546
|
-
input.timestamp,
|
|
3547
|
-
"Slack reaction removal"
|
|
3548
|
-
);
|
|
3549
|
-
const emoji = normalizeSlackEmojiName(input.emoji);
|
|
3550
|
-
if (!emoji) {
|
|
3551
|
-
throw new Error("Slack reaction removal requires a valid emoji alias name");
|
|
3552
|
-
}
|
|
3553
|
-
try {
|
|
3554
|
-
await withSlackRetries(
|
|
3555
|
-
() => getSlackClient().reactions.remove({
|
|
3556
|
-
channel: channelId,
|
|
3557
|
-
timestamp,
|
|
3558
|
-
name: emoji
|
|
3559
|
-
}),
|
|
3560
|
-
3,
|
|
3561
|
-
{
|
|
3562
|
-
action: "reactions.remove",
|
|
3563
|
-
idempotent: true,
|
|
3564
|
-
spanAttributes: {
|
|
3565
|
-
"app.slack.channel_id": channelId,
|
|
3566
|
-
"app.slack.message_ts": timestamp,
|
|
3567
|
-
"app.slack.reaction": emoji
|
|
3568
|
-
}
|
|
3569
|
-
}
|
|
3570
|
-
);
|
|
3571
|
-
} catch (error) {
|
|
3572
|
-
if (error instanceof SlackActionError && error.code === "no_reaction") {
|
|
3573
|
-
return { ok: true };
|
|
3574
|
-
}
|
|
3575
|
-
throw error;
|
|
3576
|
-
}
|
|
3577
|
-
return { ok: true };
|
|
3578
|
-
}
|
|
3579
|
-
|
|
3580
|
-
// src/chat/oauth-authorization-message.ts
|
|
3581
|
-
function formatOAuthAuthorizationMessage(args) {
|
|
3582
|
-
return `${formatSlackLink(args.authorizationUrl, args.label)}. ${args.completionText}`;
|
|
3583
|
-
}
|
|
3584
|
-
|
|
3585
|
-
// src/chat/oauth-flow.ts
|
|
3586
|
-
var OAUTH_STATE_TTL_MS = 10 * 60 * 1e3;
|
|
3587
|
-
function optionalString(value) {
|
|
3588
|
-
return typeof value === "string" ? value : void 0;
|
|
3589
|
-
}
|
|
3590
|
-
function parseOAuthStatePayload(value) {
|
|
3591
|
-
if (!isRecord(value)) {
|
|
3592
|
-
return void 0;
|
|
3593
|
-
}
|
|
3594
|
-
if (typeof value.userId !== "string" || typeof value.provider !== "string") {
|
|
3595
|
-
return void 0;
|
|
3596
|
-
}
|
|
3597
|
-
const destination = parseDestination(value.destination);
|
|
3598
|
-
if (value.destination !== void 0 && !destination) {
|
|
3599
|
-
return void 0;
|
|
3600
|
-
}
|
|
3601
|
-
const source = value.source === void 0 ? void 0 : sourceSchema.safeParse(value.source);
|
|
3602
|
-
if (value.source !== void 0 && (!source || !source.success)) {
|
|
3603
|
-
return void 0;
|
|
3604
|
-
}
|
|
3605
|
-
return {
|
|
3606
|
-
userId: value.userId,
|
|
3607
|
-
provider: value.provider,
|
|
3608
|
-
...optionalString(value.channelId) ? { channelId: optionalString(value.channelId) } : {},
|
|
3609
|
-
...destination ? { destination } : {},
|
|
3610
|
-
...source?.success ? { source: source.data } : {},
|
|
3611
|
-
...optionalString(value.threadTs) ? { threadTs: optionalString(value.threadTs) } : {},
|
|
3612
|
-
...optionalString(value.resumeConversationId) ? { resumeConversationId: optionalString(value.resumeConversationId) } : {},
|
|
3613
|
-
...optionalString(value.resumeSessionId) ? { resumeSessionId: optionalString(value.resumeSessionId) } : {},
|
|
3614
|
-
...optionalString(value.scope) ? { scope: optionalString(value.scope) } : {}
|
|
3615
|
-
};
|
|
3616
|
-
}
|
|
3617
|
-
function formatProviderLabel(provider) {
|
|
3618
|
-
const displayName = pluginCatalogRuntime.getDisplayName(provider);
|
|
3619
|
-
if (!displayName) {
|
|
3620
|
-
throw new Error(`Unknown plugin provider display name: "${provider}"`);
|
|
3621
|
-
}
|
|
3622
|
-
return displayName;
|
|
3623
|
-
}
|
|
3624
|
-
function resolveBaseUrl() {
|
|
3625
|
-
const explicit = process.env.JUNIOR_BASE_URL?.trim();
|
|
3626
|
-
if (explicit) return explicit;
|
|
3627
|
-
const vercelProd = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
|
|
3628
|
-
if (vercelProd) return `https://${vercelProd}`;
|
|
3629
|
-
const vercelUrl = process.env.VERCEL_URL?.trim();
|
|
3630
|
-
if (vercelUrl) return `https://${vercelUrl}`;
|
|
3631
|
-
return void 0;
|
|
3632
|
-
}
|
|
3633
|
-
async function deliverPrivateMessage(input) {
|
|
3634
|
-
let client;
|
|
3635
|
-
try {
|
|
3636
|
-
client = getSlackClient();
|
|
3637
|
-
} catch {
|
|
3638
|
-
logWarn(
|
|
3639
|
-
"oauth_private_delivery_skip",
|
|
3640
|
-
{},
|
|
3641
|
-
{ "app.reason": "missing_bot_token" },
|
|
3642
|
-
"Skipped private message delivery \u2014 no SLACK_BOT_TOKEN"
|
|
3643
|
-
);
|
|
3644
|
-
return false;
|
|
3645
|
-
}
|
|
3646
|
-
if (input.channelId) {
|
|
3647
|
-
try {
|
|
3648
|
-
if (isDmChannel(input.channelId)) {
|
|
3649
|
-
await postSlackMessage({
|
|
3650
|
-
channelId: input.channelId,
|
|
3651
|
-
text: input.text,
|
|
3652
|
-
threadTs: input.threadTs
|
|
3653
|
-
});
|
|
3654
|
-
} else {
|
|
3655
|
-
await postSlackEphemeralMessage({
|
|
3656
|
-
channelId: input.channelId,
|
|
3657
|
-
userId: input.userId,
|
|
3658
|
-
text: input.text,
|
|
3659
|
-
threadTs: input.threadTs
|
|
3660
|
-
});
|
|
3661
|
-
}
|
|
3662
|
-
return "in_context";
|
|
3663
|
-
} catch (error) {
|
|
3664
|
-
logWarn(
|
|
3665
|
-
"oauth_private_delivery_failed",
|
|
3666
|
-
{},
|
|
3667
|
-
{
|
|
3668
|
-
"app.slack.error": error instanceof Error ? error.message : String(error),
|
|
3669
|
-
"app.slack.channel": input.channelId
|
|
3670
|
-
},
|
|
3671
|
-
"Private message delivery failed, falling back to DM"
|
|
3672
|
-
);
|
|
3673
|
-
}
|
|
3674
|
-
}
|
|
3675
|
-
try {
|
|
3676
|
-
const dmChannelId = (await withSlackRetries(
|
|
3677
|
-
() => client.conversations.open({ users: input.userId }),
|
|
3678
|
-
3,
|
|
3679
|
-
{
|
|
3680
|
-
action: "conversations.open",
|
|
3681
|
-
spanAttributes: { "app.slack.user_id": input.userId }
|
|
3682
|
-
}
|
|
3683
|
-
)).channel?.id;
|
|
3684
|
-
if (!dmChannelId) {
|
|
3685
|
-
logWarn(
|
|
3686
|
-
"oauth_dm_fallback_failed",
|
|
3687
|
-
{},
|
|
3688
|
-
{ "app.reason": "no_dm_channel_id" },
|
|
3689
|
-
"conversations.open returned no channel ID"
|
|
3690
|
-
);
|
|
3691
|
-
return false;
|
|
3692
|
-
}
|
|
3693
|
-
await postSlackMessage({ channelId: dmChannelId, text: input.text });
|
|
3694
|
-
return "fallback_dm";
|
|
3695
|
-
} catch (error) {
|
|
3696
|
-
logWarn(
|
|
3697
|
-
"oauth_dm_fallback_failed",
|
|
3698
|
-
{},
|
|
3699
|
-
{
|
|
3700
|
-
"app.slack.error": error instanceof Error ? error.message : String(error)
|
|
3701
|
-
},
|
|
3702
|
-
"DM fallback delivery failed"
|
|
3703
|
-
);
|
|
3704
|
-
return false;
|
|
2602
|
+
});
|
|
3705
2603
|
}
|
|
2604
|
+
return commandResult({
|
|
2605
|
+
stderr: `Usage:
|
|
2606
|
+
${usage}
|
|
2607
|
+
`,
|
|
2608
|
+
exitCode: 2
|
|
2609
|
+
});
|
|
3706
2610
|
}
|
|
3707
|
-
async function
|
|
3708
|
-
const
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
return {
|
|
3718
|
-
ok: false,
|
|
3719
|
-
error: `Missing ${providerConfig.clientIdEnv} environment variable`
|
|
3720
|
-
};
|
|
3721
|
-
}
|
|
3722
|
-
const baseUrl = resolveBaseUrl();
|
|
3723
|
-
if (!baseUrl) {
|
|
3724
|
-
return {
|
|
3725
|
-
ok: false,
|
|
3726
|
-
error: "Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
3727
|
-
};
|
|
2611
|
+
async function handlePluginsCommand(args) {
|
|
2612
|
+
const usage = "jr-rpc plugins list";
|
|
2613
|
+
const subverb = (args[0] ?? "").trim();
|
|
2614
|
+
if (subverb !== "list" || args.length !== 1) {
|
|
2615
|
+
return commandResult({
|
|
2616
|
+
stderr: `Usage:
|
|
2617
|
+
${usage}
|
|
2618
|
+
`,
|
|
2619
|
+
exitCode: 2
|
|
2620
|
+
});
|
|
3728
2621
|
}
|
|
3729
|
-
const
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
...input.resumeConversationId ? { resumeConversationId: input.resumeConversationId } : {},
|
|
3741
|
-
...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {},
|
|
3742
|
-
...requestedScope ? { scope: requestedScope } : {}
|
|
2622
|
+
const plugins = pluginCatalogRuntime.getProviders().map((plugin) => ({
|
|
2623
|
+
name: plugin.manifest.name,
|
|
2624
|
+
displayName: plugin.manifest.displayName,
|
|
2625
|
+
description: plugin.manifest.description,
|
|
2626
|
+
capabilities: [...plugin.manifest.capabilities],
|
|
2627
|
+
configKeys: [...plugin.manifest.configKeys]
|
|
2628
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
2629
|
+
return commandResult({
|
|
2630
|
+
stdout: {
|
|
2631
|
+
ok: true,
|
|
2632
|
+
plugins
|
|
3743
2633
|
},
|
|
3744
|
-
|
|
3745
|
-
);
|
|
3746
|
-
const authorizeParams = new URLSearchParams({
|
|
3747
|
-
client_id: clientId,
|
|
3748
|
-
state,
|
|
3749
|
-
redirect_uri: `${baseUrl}${providerConfig.callbackPath}`,
|
|
3750
|
-
response_type: "code"
|
|
2634
|
+
exitCode: 0
|
|
3751
2635
|
});
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
2636
|
+
}
|
|
2637
|
+
function createJrRpcCommand(deps) {
|
|
2638
|
+
return defineCommand("jr-rpc", async (args) => {
|
|
2639
|
+
const usage = [
|
|
2640
|
+
"jr-rpc config get <key>",
|
|
2641
|
+
"jr-rpc config set <key> <value> [--json]",
|
|
2642
|
+
"jr-rpc config unset <key>",
|
|
2643
|
+
"jr-rpc config list [--prefix <value>]",
|
|
2644
|
+
"jr-rpc plugins list"
|
|
2645
|
+
].join("\n");
|
|
2646
|
+
const verb = (args[0] ?? "").trim();
|
|
2647
|
+
if (verb === "config") {
|
|
2648
|
+
return handleConfigCommand(args.slice(1), deps);
|
|
2649
|
+
}
|
|
2650
|
+
if (verb === "plugins") {
|
|
2651
|
+
return handlePluginsCommand(args.slice(1));
|
|
2652
|
+
}
|
|
2653
|
+
return commandResult({
|
|
2654
|
+
stderr: `Unsupported jr-rpc command. Use:
|
|
2655
|
+
${usage}
|
|
2656
|
+
`,
|
|
2657
|
+
exitCode: 2
|
|
2658
|
+
});
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
async function maybeExecuteJrRpcCustomCommand(command, deps) {
|
|
2662
|
+
const normalized = command.trim();
|
|
2663
|
+
if (!/^jr-rpc(?:\s|$)/.test(normalized)) {
|
|
2664
|
+
return { handled: false };
|
|
3759
2665
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
"app.credential.provider": provider,
|
|
3765
|
-
...input.activeSkillName ? { "app.skill.name": input.activeSkillName } : {}
|
|
3766
|
-
},
|
|
3767
|
-
"Initiated OAuth authorization code flow"
|
|
3768
|
-
);
|
|
3769
|
-
const authorizationUrl = `${providerConfig.authorizeEndpoint}?${authorizeParams.toString()}`;
|
|
2666
|
+
const shell = new Bash({
|
|
2667
|
+
customCommands: [createJrRpcCommand(deps)]
|
|
2668
|
+
});
|
|
2669
|
+
const execResult = await shell.exec(normalized);
|
|
3770
2670
|
return {
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
2671
|
+
handled: true,
|
|
2672
|
+
result: {
|
|
2673
|
+
ok: execResult.exitCode === 0,
|
|
2674
|
+
command: normalized,
|
|
2675
|
+
cwd: "/",
|
|
2676
|
+
exit_code: execResult.exitCode,
|
|
2677
|
+
signal: null,
|
|
2678
|
+
timed_out: false,
|
|
2679
|
+
stdout: execResult.stdout,
|
|
2680
|
+
stderr: execResult.stderr,
|
|
2681
|
+
stdout_truncated: false,
|
|
2682
|
+
stderr_truncated: false
|
|
2683
|
+
}
|
|
3782
2684
|
};
|
|
3783
2685
|
}
|
|
3784
2686
|
|
|
3785
|
-
// src/chat/sandbox/
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
ctx.addIssue({
|
|
3825
|
-
code: z3.ZodIssueCode.custom,
|
|
3826
|
-
message: "Auth signal authorization provider must match provider",
|
|
3827
|
-
path: ["authorization", "provider"]
|
|
3828
|
-
});
|
|
3829
|
-
}
|
|
3830
|
-
});
|
|
3831
|
-
var sandboxEgressPermissionDeniedSignalSchema = z3.object({
|
|
3832
|
-
account: pluginProviderAccountSchema.optional(),
|
|
3833
|
-
acceptedPermissions: z3.string().optional(),
|
|
3834
|
-
grant: sandboxEgressGrantSchema,
|
|
3835
|
-
message: z3.string().min(1),
|
|
3836
|
-
provider: providerNameSchema,
|
|
3837
|
-
source: z3.literal("upstream"),
|
|
3838
|
-
sso: z3.string().optional(),
|
|
3839
|
-
status: httpStatusSchema,
|
|
3840
|
-
upstreamHost: z3.string().min(1),
|
|
3841
|
-
upstreamPath: z3.string().min(1),
|
|
3842
|
-
createdAtMs: finiteNumberSchema
|
|
3843
|
-
}).strict();
|
|
3844
|
-
function parseSandboxEgressAuthRequiredSignal(value) {
|
|
3845
|
-
const result = sandboxEgressAuthRequiredSignalSchema.safeParse(value);
|
|
3846
|
-
return result.success ? result.data : void 0;
|
|
3847
|
-
}
|
|
3848
|
-
function parseSandboxEgressPermissionDeniedSignal(value) {
|
|
3849
|
-
const result = sandboxEgressPermissionDeniedSignalSchema.safeParse(value);
|
|
3850
|
-
return result.success ? result.data : void 0;
|
|
2687
|
+
// src/chat/sandbox/command-result.ts
|
|
2688
|
+
function formatSandboxCommandResult(params) {
|
|
2689
|
+
return makeStructuredToolResult({
|
|
2690
|
+
ok: params.ok,
|
|
2691
|
+
status: params.ok ? "success" : "error",
|
|
2692
|
+
target: params.command,
|
|
2693
|
+
data: {
|
|
2694
|
+
command: params.command,
|
|
2695
|
+
cwd: params.cwd,
|
|
2696
|
+
exit_code: params.exit_code,
|
|
2697
|
+
signal: params.signal,
|
|
2698
|
+
timed_out: params.timed_out,
|
|
2699
|
+
aborted: Boolean(params.aborted),
|
|
2700
|
+
stdout: params.stdout,
|
|
2701
|
+
stderr: params.stderr,
|
|
2702
|
+
stdout_truncated: params.stdout_truncated,
|
|
2703
|
+
stderr_truncated: params.stderr_truncated
|
|
2704
|
+
},
|
|
2705
|
+
truncated: params.stdout_truncated || params.stderr_truncated,
|
|
2706
|
+
...!params.ok ? {
|
|
2707
|
+
error: {
|
|
2708
|
+
kind: params.aborted ? "outcome_unknown" : params.timed_out ? "timeout" : "nonzero_exit",
|
|
2709
|
+
message: params.aborted ? "Command was interrupted before its outcome was confirmed. It may have produced side effects; reconcile external state before retrying or reporting failure." : params.stderr.trim() || `Command exited with code ${params.exit_code}`,
|
|
2710
|
+
retryable: params.timed_out
|
|
2711
|
+
}
|
|
2712
|
+
} : {},
|
|
2713
|
+
command: params.command,
|
|
2714
|
+
cwd: params.cwd,
|
|
2715
|
+
exit_code: params.exit_code,
|
|
2716
|
+
signal: params.signal,
|
|
2717
|
+
timed_out: params.timed_out,
|
|
2718
|
+
aborted: Boolean(params.aborted),
|
|
2719
|
+
stdout: params.stdout,
|
|
2720
|
+
stderr: params.stderr,
|
|
2721
|
+
stdout_truncated: params.stdout_truncated,
|
|
2722
|
+
stderr_truncated: params.stderr_truncated,
|
|
2723
|
+
...params.auth_required ? { auth_required: params.auth_required } : {},
|
|
2724
|
+
...params.permission_denied ? { permission_denied: params.permission_denied } : {}
|
|
2725
|
+
});
|
|
3851
2726
|
}
|
|
3852
2727
|
|
|
2728
|
+
// src/chat/sandbox/sandbox.ts
|
|
2729
|
+
import fs4 from "fs/promises";
|
|
2730
|
+
|
|
3853
2731
|
// src/chat/sandbox/egress/session.ts
|
|
2732
|
+
import { createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
3854
2733
|
var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
|
|
3855
2734
|
var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
|
|
3856
2735
|
var SANDBOX_EGRESS_HMAC_CONTEXT = "junior.sandbox_egress.v1";
|
|
@@ -5107,6 +3986,16 @@ async function collectFiles(params) {
|
|
|
5107
3986
|
}
|
|
5108
3987
|
|
|
5109
3988
|
// src/chat/tools/sandbox/text-edits.ts
|
|
3989
|
+
var MAX_DIFF_LINE_CHARS = 4e3;
|
|
3990
|
+
function truncateDiffLine(value) {
|
|
3991
|
+
if (value.length <= MAX_DIFF_LINE_CHARS) {
|
|
3992
|
+
return { line: value, truncated: false };
|
|
3993
|
+
}
|
|
3994
|
+
return {
|
|
3995
|
+
line: `${value.slice(0, MAX_DIFF_LINE_CHARS)}... [line truncated]`,
|
|
3996
|
+
truncated: true
|
|
3997
|
+
};
|
|
3998
|
+
}
|
|
5110
3999
|
function detectLineEnding(value) {
|
|
5111
4000
|
return value.includes("\r\n") ? "\r\n" : "\n";
|
|
5112
4001
|
}
|
|
@@ -5194,27 +4083,35 @@ function buildCompactDiff(oldContent, newContent) {
|
|
|
5194
4083
|
const oldContextEnd = Math.min(oldLines.length - 1, oldSuffix + 3);
|
|
5195
4084
|
const width = String(Math.max(oldLines.length, newLines.length)).length;
|
|
5196
4085
|
const output = [];
|
|
4086
|
+
let lineTruncated = false;
|
|
4087
|
+
const pushLine = (value) => {
|
|
4088
|
+
const bounded2 = truncateDiffLine(value);
|
|
4089
|
+
output.push(bounded2.line);
|
|
4090
|
+
lineTruncated ||= bounded2.truncated;
|
|
4091
|
+
};
|
|
5197
4092
|
if (contextStart > 0) {
|
|
5198
|
-
|
|
4093
|
+
pushLine(` ${"".padStart(width)} ...`);
|
|
5199
4094
|
}
|
|
5200
4095
|
for (let index = contextStart; index < prefix; index += 1) {
|
|
5201
|
-
|
|
4096
|
+
pushLine(` ${String(index + 1).padStart(width)} ${oldLines[index]}`);
|
|
5202
4097
|
}
|
|
5203
4098
|
for (let index = prefix; index <= oldSuffix; index += 1) {
|
|
5204
|
-
|
|
4099
|
+
pushLine(`-${String(index + 1).padStart(width)} ${oldLines[index]}`);
|
|
5205
4100
|
}
|
|
5206
4101
|
for (let index = prefix; index <= newSuffix; index += 1) {
|
|
5207
|
-
|
|
4102
|
+
pushLine(`+${String(index + 1).padStart(width)} ${newLines[index]}`);
|
|
5208
4103
|
}
|
|
5209
4104
|
for (let index = newSuffix + 1; index <= newContextEnd; index += 1) {
|
|
5210
|
-
|
|
4105
|
+
pushLine(` ${String(index + 1).padStart(width)} ${newLines[index]}`);
|
|
5211
4106
|
}
|
|
5212
4107
|
if (newContextEnd < newLines.length - 1 || oldContextEnd < oldLines.length - 1) {
|
|
5213
|
-
|
|
4108
|
+
pushLine(` ${"".padStart(width)} ...`);
|
|
5214
4109
|
}
|
|
4110
|
+
const bounded = truncateText(output.join("\n"), MAX_TEXT_CHARS);
|
|
5215
4111
|
return {
|
|
5216
|
-
diff:
|
|
5217
|
-
firstChangedLine: firstChangedLine(oldContent, newContent)
|
|
4112
|
+
diff: bounded.content,
|
|
4113
|
+
firstChangedLine: firstChangedLine(oldContent, newContent),
|
|
4114
|
+
truncated: lineTruncated || bounded.truncated
|
|
5218
4115
|
};
|
|
5219
4116
|
}
|
|
5220
4117
|
function validateAndApplyTextEdits(content, edits, targetName) {
|
|
@@ -5281,10 +4178,10 @@ function validateAndApplyTextEdits(content, edits, targetName) {
|
|
|
5281
4178
|
}
|
|
5282
4179
|
|
|
5283
4180
|
// src/chat/tools/sandbox/edit-file.ts
|
|
5284
|
-
import { z as
|
|
4181
|
+
import { z as z4 } from "zod";
|
|
5285
4182
|
|
|
5286
4183
|
// src/chat/tool-support/zod-tool.ts
|
|
5287
|
-
import { z as
|
|
4184
|
+
import { z as z3 } from "zod";
|
|
5288
4185
|
function isContentOnlyToolResult(value) {
|
|
5289
4186
|
return value !== null && typeof value === "object" && Array.isArray(value.content) && !("details" in value);
|
|
5290
4187
|
}
|
|
@@ -5308,7 +4205,7 @@ function zodTool(definition) {
|
|
|
5308
4205
|
const { inputSchema, outputSchema, prepareArguments, execute, ...toolDef } = definition;
|
|
5309
4206
|
let modelInputSchema;
|
|
5310
4207
|
try {
|
|
5311
|
-
modelInputSchema =
|
|
4208
|
+
modelInputSchema = z3.toJSONSchema(inputSchema);
|
|
5312
4209
|
} catch (error) {
|
|
5313
4210
|
throw new TypeError(
|
|
5314
4211
|
"zodTool() inputSchema must be representable as JSON Schema.",
|
|
@@ -5318,7 +4215,7 @@ function zodTool(definition) {
|
|
|
5318
4215
|
let modelOutputSchema;
|
|
5319
4216
|
if (outputSchema) {
|
|
5320
4217
|
try {
|
|
5321
|
-
modelOutputSchema =
|
|
4218
|
+
modelOutputSchema = z3.toJSONSchema(outputSchema);
|
|
5322
4219
|
} catch (error) {
|
|
5323
4220
|
throw new TypeError(
|
|
5324
4221
|
"zodTool() outputSchema must be representable as JSON Schema.",
|
|
@@ -5443,7 +4340,6 @@ async function editFile(params) {
|
|
|
5443
4340
|
status: "success",
|
|
5444
4341
|
target: params.path,
|
|
5445
4342
|
data: {
|
|
5446
|
-
diff: diff.diff,
|
|
5447
4343
|
first_changed_line: diff.firstChangedLine,
|
|
5448
4344
|
path: params.path,
|
|
5449
4345
|
replacements: params.edits.length
|
|
@@ -5451,23 +4347,24 @@ async function editFile(params) {
|
|
|
5451
4347
|
diff: diff.diff,
|
|
5452
4348
|
first_changed_line: diff.firstChangedLine,
|
|
5453
4349
|
path: params.path,
|
|
5454
|
-
replacements: params.edits.length
|
|
4350
|
+
replacements: params.edits.length,
|
|
4351
|
+
truncated: diff.truncated
|
|
5455
4352
|
});
|
|
5456
4353
|
}
|
|
5457
|
-
var editReplacementSchema =
|
|
5458
|
-
oldText:
|
|
4354
|
+
var editReplacementSchema = z4.object({
|
|
4355
|
+
oldText: z4.string().min(1).describe(
|
|
5459
4356
|
"Exact text to replace. It must be unique in the original file and must not overlap another edit."
|
|
5460
4357
|
),
|
|
5461
|
-
newText:
|
|
4358
|
+
newText: z4.string().describe("Replacement text for this edit.")
|
|
5462
4359
|
});
|
|
5463
4360
|
function createEditFileTool() {
|
|
5464
4361
|
return zodTool({
|
|
5465
4362
|
description: "Edit one sandbox workspace file with exact text replacements. Use for precise changes to existing files; prefer this over writeFile for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same file: use one edits[] call.",
|
|
5466
4363
|
prepareArguments: prepareEditFileArguments,
|
|
5467
4364
|
executionMode: "sequential",
|
|
5468
|
-
inputSchema:
|
|
5469
|
-
path:
|
|
5470
|
-
edits:
|
|
4365
|
+
inputSchema: z4.object({
|
|
4366
|
+
path: z4.string().min(1).describe("Path to edit in the sandbox workspace."),
|
|
4367
|
+
edits: z4.array(editReplacementSchema).min(1).describe(
|
|
5471
4368
|
"Exact replacements matched against the original file, not incrementally."
|
|
5472
4369
|
)
|
|
5473
4370
|
}),
|
|
@@ -5482,7 +4379,7 @@ function createEditFileTool() {
|
|
|
5482
4379
|
|
|
5483
4380
|
// src/chat/tools/sandbox/find-files.ts
|
|
5484
4381
|
import path5 from "path";
|
|
5485
|
-
import { z as
|
|
4382
|
+
import { z as z5 } from "zod";
|
|
5486
4383
|
var DEFAULT_FIND_LIMIT = 1e3;
|
|
5487
4384
|
async function findFiles(params) {
|
|
5488
4385
|
if (!params.pattern.trim()) {
|
|
@@ -5542,14 +4439,14 @@ function createFindFilesTool() {
|
|
|
5542
4439
|
return zodTool({
|
|
5543
4440
|
description: "Find sandbox workspace files by glob pattern. Returns bounded paths relative to the search root and skips dependency/cache directories.",
|
|
5544
4441
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5545
|
-
inputSchema:
|
|
5546
|
-
pattern:
|
|
4442
|
+
inputSchema: z5.object({
|
|
4443
|
+
pattern: z5.string().min(1).describe(
|
|
5547
4444
|
"Glob pattern to match, for example '*.ts', '**/*.json', or 'src/**/*.test.ts'."
|
|
5548
4445
|
),
|
|
5549
|
-
path:
|
|
4446
|
+
path: z5.string().min(1).describe(
|
|
5550
4447
|
"Directory or file path in the sandbox workspace. Defaults to the workspace root."
|
|
5551
4448
|
).optional(),
|
|
5552
|
-
limit:
|
|
4449
|
+
limit: z5.coerce.number().int().min(1).describe("Maximum number of file paths to return. Defaults to 1000.").optional()
|
|
5553
4450
|
}),
|
|
5554
4451
|
outputSchema: juniorToolResultSchema,
|
|
5555
4452
|
execute: async () => {
|
|
@@ -5562,12 +4459,12 @@ function createFindFilesTool() {
|
|
|
5562
4459
|
|
|
5563
4460
|
// src/chat/tools/sandbox/grep.ts
|
|
5564
4461
|
import path6 from "path";
|
|
5565
|
-
import { z as
|
|
4462
|
+
import { z as z6 } from "zod";
|
|
5566
4463
|
var DEFAULT_GREP_LIMIT = 100;
|
|
5567
4464
|
var MAX_GREP_LINE_CHARS = 500;
|
|
5568
|
-
var booleanInput = (description) =>
|
|
4465
|
+
var booleanInput = (description) => z6.preprocess(
|
|
5569
4466
|
(value) => value === "true" ? true : value === "false" ? false : value,
|
|
5570
|
-
|
|
4467
|
+
z6.boolean()
|
|
5571
4468
|
).describe(description);
|
|
5572
4469
|
function truncateGrepLine(value) {
|
|
5573
4470
|
if (value.length <= MAX_GREP_LINE_CHARS) {
|
|
@@ -5716,20 +4613,20 @@ function createGrepTool() {
|
|
|
5716
4613
|
return zodTool({
|
|
5717
4614
|
description: "Search sandbox workspace file contents. Returns bounded matching lines with file paths and line numbers. Respects path/glob filters and includes truncation notices.",
|
|
5718
4615
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5719
|
-
inputSchema:
|
|
5720
|
-
pattern:
|
|
5721
|
-
path:
|
|
4616
|
+
inputSchema: z6.object({
|
|
4617
|
+
pattern: z6.string().min(1).describe("Regex pattern or literal text to search for."),
|
|
4618
|
+
path: z6.string().min(1).describe(
|
|
5722
4619
|
"Directory or file path in the sandbox workspace. Defaults to the workspace root."
|
|
5723
4620
|
).optional(),
|
|
5724
|
-
glob:
|
|
4621
|
+
glob: z6.string().min(1).describe("Optional glob filter such as '*.ts' or '**/*.test.ts'.").optional(),
|
|
5725
4622
|
ignoreCase: booleanInput(
|
|
5726
4623
|
"Whether matching should ignore case."
|
|
5727
4624
|
).optional(),
|
|
5728
4625
|
literal: booleanInput(
|
|
5729
4626
|
"Treat pattern as literal text instead of regex."
|
|
5730
4627
|
).optional(),
|
|
5731
|
-
context:
|
|
5732
|
-
limit:
|
|
4628
|
+
context: z6.coerce.number().int().min(0).describe("Number of surrounding context lines to include.").optional(),
|
|
4629
|
+
limit: z6.coerce.number().int().min(1).describe("Maximum matches to return. Defaults to 100.").optional()
|
|
5733
4630
|
}),
|
|
5734
4631
|
outputSchema: juniorToolResultSchema,
|
|
5735
4632
|
execute: async () => {
|
|
@@ -5740,7 +4637,7 @@ function createGrepTool() {
|
|
|
5740
4637
|
|
|
5741
4638
|
// src/chat/tools/sandbox/list-dir.ts
|
|
5742
4639
|
import path7 from "path";
|
|
5743
|
-
import { z as
|
|
4640
|
+
import { z as z7 } from "zod";
|
|
5744
4641
|
var DEFAULT_LIST_LIMIT = 500;
|
|
5745
4642
|
async function listDir(params) {
|
|
5746
4643
|
const dirPath = resolveWorkspacePath(params.path);
|
|
@@ -5821,11 +4718,11 @@ function createListDirTool() {
|
|
|
5821
4718
|
return zodTool({
|
|
5822
4719
|
description: "List a sandbox workspace directory. Returns sorted entries with '/' suffixes for directories and bounded truncation notices.",
|
|
5823
4720
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5824
|
-
inputSchema:
|
|
5825
|
-
path:
|
|
4721
|
+
inputSchema: z7.object({
|
|
4722
|
+
path: z7.string().min(1).describe(
|
|
5826
4723
|
"Directory path in the sandbox workspace. Defaults to the workspace root."
|
|
5827
4724
|
).optional(),
|
|
5828
|
-
limit:
|
|
4725
|
+
limit: z7.coerce.number().int().min(1).describe("Maximum entries to return. Defaults to 500.").optional()
|
|
5829
4726
|
}),
|
|
5830
4727
|
outputSchema: juniorToolResultSchema,
|
|
5831
4728
|
execute: async () => {
|
|
@@ -5837,7 +4734,7 @@ function createListDirTool() {
|
|
|
5837
4734
|
}
|
|
5838
4735
|
|
|
5839
4736
|
// src/chat/tools/sandbox/read-file.ts
|
|
5840
|
-
import { z as
|
|
4737
|
+
import { z as z8 } from "zod";
|
|
5841
4738
|
|
|
5842
4739
|
// src/chat/tool-support/text-range-result.ts
|
|
5843
4740
|
var DEFAULT_READ_LIMIT = 1e3;
|
|
@@ -5912,10 +4809,10 @@ function createReadFileTool() {
|
|
|
5912
4809
|
return zodTool({
|
|
5913
4810
|
description: "Read a bounded line range from a file in the sandbox workspace. Use when you need exact file contents to verify facts or make edits safely. Prefer grep/findFiles/listDir for broad discovery.",
|
|
5914
4811
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
5915
|
-
inputSchema:
|
|
5916
|
-
path:
|
|
5917
|
-
offset:
|
|
5918
|
-
limit:
|
|
4812
|
+
inputSchema: z8.object({
|
|
4813
|
+
path: z8.string().min(1).describe("Path to the file in the sandbox workspace."),
|
|
4814
|
+
offset: z8.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
|
|
4815
|
+
limit: z8.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
|
|
5919
4816
|
}),
|
|
5920
4817
|
outputSchema: juniorToolResultSchema,
|
|
5921
4818
|
execute: async () => {
|
|
@@ -5980,6 +4877,30 @@ function createSandbox(options) {
|
|
|
5980
4877
|
});
|
|
5981
4878
|
return token;
|
|
5982
4879
|
};
|
|
4880
|
+
const clearEgressSignals = async (egressId) => {
|
|
4881
|
+
if (!options.egressSignals) {
|
|
4882
|
+
await clearSandboxEgressSignals(egressId);
|
|
4883
|
+
return;
|
|
4884
|
+
}
|
|
4885
|
+
await options.egressSignals.clear(
|
|
4886
|
+
sandboxEgressCredentialTokenFor(egressId)
|
|
4887
|
+
);
|
|
4888
|
+
};
|
|
4889
|
+
const consumeEgressSignals = async (egressId) => {
|
|
4890
|
+
if (options.egressSignals) {
|
|
4891
|
+
return await options.egressSignals.consume(
|
|
4892
|
+
sandboxEgressCredentialTokenFor(egressId)
|
|
4893
|
+
);
|
|
4894
|
+
}
|
|
4895
|
+
const [authRequired, permissionDenied] = await Promise.all([
|
|
4896
|
+
consumeSandboxEgressAuthRequiredSignal(egressId),
|
|
4897
|
+
consumeSandboxEgressPermissionDeniedSignal(egressId)
|
|
4898
|
+
]);
|
|
4899
|
+
return {
|
|
4900
|
+
...authRequired ? { authRequired } : {},
|
|
4901
|
+
...permissionDenied ? { permissionDenied } : {}
|
|
4902
|
+
};
|
|
4903
|
+
};
|
|
5983
4904
|
const runtime = createSandboxRuntime({
|
|
5984
4905
|
sandboxRef: options.sandboxRef,
|
|
5985
4906
|
skills: options.skills,
|
|
@@ -6023,7 +4944,7 @@ function createSandbox(options) {
|
|
|
6023
4944
|
"app.sandbox.command_length": command.length
|
|
6024
4945
|
});
|
|
6025
4946
|
const { bash: executeBash, sessionId: activeSessionId } = await runtime.tools();
|
|
6026
|
-
await
|
|
4947
|
+
await clearEgressSignals(activeSessionId);
|
|
6027
4948
|
const result = await withSandboxToolSpan(
|
|
6028
4949
|
"bash",
|
|
6029
4950
|
"process.exec",
|
|
@@ -6061,8 +4982,7 @@ function createSandbox(options) {
|
|
|
6061
4982
|
}
|
|
6062
4983
|
}
|
|
6063
4984
|
);
|
|
6064
|
-
const authRequired = await
|
|
6065
|
-
const permissionDenied = await consumeSandboxEgressPermissionDeniedSignal(activeSessionId);
|
|
4985
|
+
const { authRequired, permissionDenied } = await consumeEgressSignals(activeSessionId);
|
|
6066
4986
|
return formatSandboxCommandResult({
|
|
6067
4987
|
ok: result.exitCode === 0,
|
|
6068
4988
|
command,
|
|
@@ -6381,25 +5301,25 @@ function createSandbox(options) {
|
|
|
6381
5301
|
|
|
6382
5302
|
// src/chat/tools/sandbox/file-uploads.ts
|
|
6383
5303
|
import path8 from "path";
|
|
6384
|
-
import { z as
|
|
5304
|
+
import { z as z9 } from "zod";
|
|
6385
5305
|
var MAX_SANDBOX_FILE_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
6386
5306
|
var SANDBOX_ARTIFACTS_DIR = "/tmp/junior/artifacts";
|
|
6387
|
-
var sandboxFileReferenceSchema =
|
|
6388
|
-
path:
|
|
5307
|
+
var sandboxFileReferenceSchema = z9.object({
|
|
5308
|
+
path: z9.string().min(1).describe(
|
|
6389
5309
|
"Exact source path of an existing sandbox file. Preserve paths returned by other tools unchanged; do not move or rewrite them before sending."
|
|
6390
5310
|
),
|
|
6391
|
-
filename:
|
|
5311
|
+
filename: z9.string().min(1).nullable().optional().describe(
|
|
6392
5312
|
"Optional filename override shown to the recipient. Null is treated as omitted."
|
|
6393
5313
|
),
|
|
6394
|
-
mimeType:
|
|
6395
|
-
bytes:
|
|
5314
|
+
mimeType: z9.string().min(1).nullable().optional().describe("Optional MIME type override. Null is treated as omitted."),
|
|
5315
|
+
bytes: z9.number().int().nonnegative().nullable().optional().describe(
|
|
6396
5316
|
"Optional known file size returned by a producing tool. Null is treated as omitted; sending tools validate the file contents directly."
|
|
6397
5317
|
)
|
|
6398
5318
|
});
|
|
6399
5319
|
var generatedArtifactFileRefSchema = sandboxFileReferenceSchema.extend({
|
|
6400
|
-
filename:
|
|
6401
|
-
mimeType:
|
|
6402
|
-
bytes:
|
|
5320
|
+
filename: z9.string().min(1),
|
|
5321
|
+
mimeType: z9.string().min(1).optional(),
|
|
5322
|
+
bytes: z9.number().int().nonnegative()
|
|
6403
5323
|
}).strict();
|
|
6404
5324
|
var MIME_BY_EXTENSION = {
|
|
6405
5325
|
".png": "image/png",
|
|
@@ -6563,6 +5483,7 @@ function createAgentSandbox(options) {
|
|
|
6563
5483
|
referenceFiles: listReferenceFiles(),
|
|
6564
5484
|
traceContext: options.traceContext,
|
|
6565
5485
|
tracePropagation: options.tracePropagation,
|
|
5486
|
+
egressSignals: options.egressSignals,
|
|
6566
5487
|
credentialEgress: options.credentialEgress,
|
|
6567
5488
|
prepare: options.prepareSandbox,
|
|
6568
5489
|
onSandboxRefChanged: async (sandboxRef) => {
|
|
@@ -7302,13 +6223,13 @@ function serializeMcpPayload(payload, privacy, options = {}) {
|
|
|
7302
6223
|
}
|
|
7303
6224
|
|
|
7304
6225
|
// src/chat/tools/sandbox/bash.ts
|
|
7305
|
-
import { z as
|
|
6226
|
+
import { z as z10 } from "zod";
|
|
7306
6227
|
function createBashTool2() {
|
|
7307
6228
|
return zodTool({
|
|
7308
6229
|
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.",
|
|
7309
|
-
inputSchema:
|
|
7310
|
-
command:
|
|
7311
|
-
timeoutMs:
|
|
6230
|
+
inputSchema: z10.object({
|
|
6231
|
+
command: z10.string().min(1).describe("Bash command to run inside the sandbox."),
|
|
6232
|
+
timeoutMs: z10.coerce.number().int().min(1e3).describe(
|
|
7312
6233
|
"Optional command timeout in milliseconds. Use for commands that may hang."
|
|
7313
6234
|
).optional()
|
|
7314
6235
|
}),
|
|
@@ -7322,14 +6243,14 @@ function createBashTool2() {
|
|
|
7322
6243
|
}
|
|
7323
6244
|
|
|
7324
6245
|
// src/chat/tools/web/image-generate.ts
|
|
7325
|
-
import { z as
|
|
6246
|
+
import { z as z11 } from "zod";
|
|
7326
6247
|
var imageGenerateOutputSchema = juniorToolResultSchema.extend({
|
|
7327
|
-
model:
|
|
7328
|
-
prompt:
|
|
7329
|
-
enrichedPrompt:
|
|
7330
|
-
image_count:
|
|
7331
|
-
images:
|
|
7332
|
-
delivery:
|
|
6248
|
+
model: z11.string(),
|
|
6249
|
+
prompt: z11.string(),
|
|
6250
|
+
enrichedPrompt: z11.string(),
|
|
6251
|
+
image_count: z11.number().int().nonnegative(),
|
|
6252
|
+
images: z11.array(generatedArtifactFileRefSchema),
|
|
6253
|
+
delivery: z11.string()
|
|
7333
6254
|
}).strict();
|
|
7334
6255
|
var ENRICHMENT_SYSTEM_PROMPT = `You are an image prompt enrichment agent. Your job is to rewrite image generation requests to reflect a specific visual identity and mood.
|
|
7335
6256
|
|
|
@@ -7390,8 +6311,8 @@ function parseImageGenerationError(status, body, model) {
|
|
|
7390
6311
|
function createImageGenerateTool(hooks, options, deps = {}) {
|
|
7391
6312
|
return zodTool({
|
|
7392
6313
|
description: "Generate images from a prompt. Use when the user wants to visually show or represent something \u2014 feelings, concepts, art, humor, or any visual idea. Also use for explicit image creation requests.",
|
|
7393
|
-
inputSchema:
|
|
7394
|
-
prompt:
|
|
6314
|
+
inputSchema: z11.object({
|
|
6315
|
+
prompt: z11.string().min(1).max(4e3).describe("Image generation prompt.")
|
|
7395
6316
|
}),
|
|
7396
6317
|
outputSchema: imageGenerateOutputSchema,
|
|
7397
6318
|
execute: async ({ prompt }) => {
|
|
@@ -7477,7 +6398,7 @@ function createImageGenerateTool(hooks, options, deps = {}) {
|
|
|
7477
6398
|
}
|
|
7478
6399
|
|
|
7479
6400
|
// src/chat/tools/skill/call-mcp-tool.ts
|
|
7480
|
-
import { z as
|
|
6401
|
+
import { z as z12 } from "zod";
|
|
7481
6402
|
function resolveMcpArguments(input) {
|
|
7482
6403
|
const extraKeys = Object.keys(input).filter(
|
|
7483
6404
|
(key) => key !== "tool_name" && key !== "arguments"
|
|
@@ -7513,9 +6434,9 @@ function missingToolMessage(toolName, provider) {
|
|
|
7513
6434
|
function createCallMcpToolTool(mcpToolManager) {
|
|
7514
6435
|
return zodTool({
|
|
7515
6436
|
description: "Call an active MCP tool by exact tool_name. Use searchMcpTools to discover tool names and schemas; copy required provider fields into arguments. Do not call with only tool_name unless the discovered tool has no arguments. Authorization is handled by the runtime when required.",
|
|
7516
|
-
inputSchema:
|
|
7517
|
-
tool_name:
|
|
7518
|
-
arguments:
|
|
6437
|
+
inputSchema: z12.object({
|
|
6438
|
+
tool_name: z12.string().min(1).describe("Exact MCP tool_name from searchMcpTools."),
|
|
6439
|
+
arguments: z12.record(z12.string(), z12.unknown()).describe(
|
|
7519
6440
|
'Arguments matching the disclosed MCP tool schema, for example { "query": "..." } when searchMcpTools shows query is required.'
|
|
7520
6441
|
).optional()
|
|
7521
6442
|
}).passthrough(),
|
|
@@ -7556,7 +6477,7 @@ function createCallMcpToolTool(mcpToolManager) {
|
|
|
7556
6477
|
}
|
|
7557
6478
|
|
|
7558
6479
|
// src/chat/tools/skill/load-skill.ts
|
|
7559
|
-
import { z as
|
|
6480
|
+
import { z as z13 } from "zod";
|
|
7560
6481
|
function toLoadedSkill(result, availableSkills) {
|
|
7561
6482
|
if (result.ok !== true || typeof result.skill_name !== "string" || typeof result.description !== "string" || typeof result.skill_dir !== "string" || typeof result.instructions !== "string") {
|
|
7562
6483
|
return null;
|
|
@@ -7605,8 +6526,8 @@ async function loadSkillFromHost(availableSkills, skillName) {
|
|
|
7605
6526
|
function createLoadSkillTool(availableSkills, options) {
|
|
7606
6527
|
return zodTool({
|
|
7607
6528
|
description: "Load a skill by name for this turn. The result includes working_directory; resolve skill paths there and run skill-owned bash commands from there or with absolute paths. When the result includes mcp_provider, use searchMcpTools before callMcpTool. Use when a request clearly matches a known skill.",
|
|
7608
|
-
inputSchema:
|
|
7609
|
-
skill_name:
|
|
6529
|
+
inputSchema: z13.object({
|
|
6530
|
+
skill_name: z13.string().min(1).describe("Skill name to load, without the leading slash.")
|
|
7610
6531
|
}),
|
|
7611
6532
|
outputSchema: juniorToolResultSchema,
|
|
7612
6533
|
execute: async ({ skill_name }) => {
|
|
@@ -7624,7 +6545,7 @@ function createLoadSkillTool(availableSkills, options) {
|
|
|
7624
6545
|
}
|
|
7625
6546
|
|
|
7626
6547
|
// src/chat/tools/skill/search-mcp-tools.ts
|
|
7627
|
-
import { z as
|
|
6548
|
+
import { z as z14 } from "zod";
|
|
7628
6549
|
|
|
7629
6550
|
// src/chat/tool-support/schema-summary.ts
|
|
7630
6551
|
function getSchemaProperties(schema) {
|
|
@@ -7754,37 +6675,37 @@ function toActiveMcpCatalogSummaries(toolDefs) {
|
|
|
7754
6675
|
// src/chat/tools/skill/search-mcp-tools.ts
|
|
7755
6676
|
var DEFAULT_MAX_RESULTS = 5;
|
|
7756
6677
|
var MAX_RESULTS = 20;
|
|
7757
|
-
var providerSummarySchema =
|
|
7758
|
-
provider:
|
|
7759
|
-
description:
|
|
7760
|
-
active:
|
|
6678
|
+
var providerSummarySchema = z14.object({
|
|
6679
|
+
provider: z14.string(),
|
|
6680
|
+
description: z14.string(),
|
|
6681
|
+
active: z14.boolean()
|
|
7761
6682
|
}).strict();
|
|
7762
|
-
var mcpCallExampleSchema =
|
|
7763
|
-
tool_name:
|
|
7764
|
-
arguments:
|
|
6683
|
+
var mcpCallExampleSchema = z14.object({
|
|
6684
|
+
tool_name: z14.string(),
|
|
6685
|
+
arguments: z14.record(z14.string(), z14.string())
|
|
7765
6686
|
}).strict();
|
|
7766
|
-
var exposedToolSummarySchema =
|
|
7767
|
-
tool_name:
|
|
7768
|
-
mcp_tool_name:
|
|
7769
|
-
provider:
|
|
7770
|
-
title:
|
|
7771
|
-
description:
|
|
7772
|
-
signature:
|
|
6687
|
+
var exposedToolSummarySchema = z14.object({
|
|
6688
|
+
tool_name: z14.string(),
|
|
6689
|
+
mcp_tool_name: z14.string(),
|
|
6690
|
+
provider: z14.string(),
|
|
6691
|
+
title: z14.string().optional(),
|
|
6692
|
+
description: z14.string(),
|
|
6693
|
+
signature: z14.string(),
|
|
7773
6694
|
call: mcpCallExampleSchema,
|
|
7774
|
-
input_schema:
|
|
7775
|
-
input_schema_summary:
|
|
7776
|
-
output_schema:
|
|
7777
|
-
annotations:
|
|
6695
|
+
input_schema: z14.record(z14.string(), z14.unknown()),
|
|
6696
|
+
input_schema_summary: z14.string(),
|
|
6697
|
+
output_schema: z14.record(z14.string(), z14.unknown()).optional(),
|
|
6698
|
+
annotations: z14.record(z14.string(), z14.unknown()).optional()
|
|
7778
6699
|
}).strict();
|
|
7779
6700
|
var searchMcpToolsOutputSchema = juniorToolResultSchema.extend({
|
|
7780
|
-
query:
|
|
7781
|
-
provider:
|
|
7782
|
-
total_active_tools:
|
|
7783
|
-
returned_tools:
|
|
7784
|
-
execution_tool:
|
|
6701
|
+
query: z14.string().nullable(),
|
|
6702
|
+
provider: z14.string().nullable(),
|
|
6703
|
+
total_active_tools: z14.number().int().nonnegative(),
|
|
6704
|
+
returned_tools: z14.number().int().nonnegative(),
|
|
6705
|
+
execution_tool: z14.literal("callMcpTool"),
|
|
7785
6706
|
execution_example: mcpCallExampleSchema,
|
|
7786
|
-
available_providers:
|
|
7787
|
-
tools:
|
|
6707
|
+
available_providers: z14.array(providerSummarySchema),
|
|
6708
|
+
tools: z14.array(exposedToolSummarySchema)
|
|
7788
6709
|
}).strict();
|
|
7789
6710
|
function normalize(value) {
|
|
7790
6711
|
return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
|
|
@@ -7904,14 +6825,14 @@ function searchProviderCatalog(providers, query) {
|
|
|
7904
6825
|
function createSearchMcpToolsTool(mcpToolManager) {
|
|
7905
6826
|
return zodTool({
|
|
7906
6827
|
description: "List or search MCP providers and active MCP tools. When provider is supplied and not yet active, Junior connects to it on demand and returns tool descriptors including schemas. Without provider, returns active tools plus matching configured providers without connecting. Use when choosing a provider tool or when callMcpTool arguments are unclear.",
|
|
7907
|
-
inputSchema:
|
|
7908
|
-
query:
|
|
6828
|
+
inputSchema: z14.object({
|
|
6829
|
+
query: z14.string().min(1).describe(
|
|
7909
6830
|
"Optional search terms describing the MCP tool or arguments needed."
|
|
7910
6831
|
).optional(),
|
|
7911
|
-
provider:
|
|
6832
|
+
provider: z14.string().min(1).describe(
|
|
7912
6833
|
"Optional provider name to list or search within. If configured but not yet connected, Junior activates it on demand."
|
|
7913
6834
|
).optional(),
|
|
7914
|
-
max_results:
|
|
6835
|
+
max_results: z14.number().int().min(1).max(MAX_RESULTS).describe("Maximum matching tool descriptors to return.").optional()
|
|
7915
6836
|
}).strict(),
|
|
7916
6837
|
outputSchema: searchMcpToolsOutputSchema,
|
|
7917
6838
|
privateTraceResult: (result) => ({
|
|
@@ -7966,12 +6887,12 @@ function createSearchMcpToolsTool(mcpToolManager) {
|
|
|
7966
6887
|
}
|
|
7967
6888
|
|
|
7968
6889
|
// src/chat/tools/runtime/report-progress.ts
|
|
7969
|
-
import { z as
|
|
6890
|
+
import { z as z15 } from "zod";
|
|
7970
6891
|
function createReportProgressTool() {
|
|
7971
6892
|
return zodTool({
|
|
7972
6893
|
description: "Update the user-visible assistant loading message with a short progress phase. For every non-trivial turn, call this early with the initial major work phase, then call it again only when the major phase meaningfully changes. Messages must be written in sentence case with a present-participle verb (e.g. 'Searching docs', 'Reviewing results', 'Running checks'). Skip trivial direct answers, generic filler, and minor substeps.",
|
|
7973
|
-
inputSchema:
|
|
7974
|
-
message:
|
|
6894
|
+
inputSchema: z15.object({
|
|
6895
|
+
message: z15.string().min(1).describe("Short user-facing progress message.")
|
|
7975
6896
|
}),
|
|
7976
6897
|
outputSchema: juniorToolResultSchema,
|
|
7977
6898
|
execute: async () => ({
|
|
@@ -7982,34 +6903,34 @@ function createReportProgressTool() {
|
|
|
7982
6903
|
}
|
|
7983
6904
|
|
|
7984
6905
|
// src/chat/tools/resource-events.ts
|
|
7985
|
-
import { z as
|
|
6906
|
+
import { z as z17 } from "zod";
|
|
7986
6907
|
|
|
7987
6908
|
// src/chat/resource-events/store.ts
|
|
7988
6909
|
import { createHash } from "crypto";
|
|
7989
6910
|
import { destinationSchema } from "@sentry/junior-plugin-api";
|
|
7990
|
-
import { z as
|
|
6911
|
+
import { z as z16 } from "zod";
|
|
7991
6912
|
var RESOURCE_EVENT_PREFIX = "junior:resource_event_subscription";
|
|
7992
6913
|
var INDEX_LOCK_TTL_MS = 1e4;
|
|
7993
6914
|
var SUBSCRIPTION_LOCK_TTL_MS = 1e4;
|
|
7994
6915
|
var SUBSCRIPTION_LOCK_WAIT_MS = 1e4;
|
|
7995
6916
|
var SUBSCRIPTION_LOCK_RETRY_MS = 25;
|
|
7996
6917
|
var SUBSCRIPTION_LOCK_HEARTBEAT_MS = 3e3;
|
|
7997
|
-
var subscriptionStatusSchema =
|
|
7998
|
-
var subscriptionIdIndexSchema =
|
|
7999
|
-
var subscriptionSchema =
|
|
8000
|
-
conversationId:
|
|
8001
|
-
createdAtMs:
|
|
6918
|
+
var subscriptionStatusSchema = z16.enum(["active", "cancelled", "completed"]);
|
|
6919
|
+
var subscriptionIdIndexSchema = z16.array(z16.string().min(1));
|
|
6920
|
+
var subscriptionSchema = z16.object({
|
|
6921
|
+
conversationId: z16.string().min(1),
|
|
6922
|
+
createdAtMs: z16.number().finite(),
|
|
8002
6923
|
destination: destinationSchema,
|
|
8003
|
-
events:
|
|
8004
|
-
expiresAtMs:
|
|
8005
|
-
id:
|
|
8006
|
-
intent:
|
|
8007
|
-
label:
|
|
8008
|
-
provider:
|
|
8009
|
-
resourceRef:
|
|
8010
|
-
resourceType:
|
|
6924
|
+
events: z16.array(z16.string().min(1)).min(1),
|
|
6925
|
+
expiresAtMs: z16.number().finite(),
|
|
6926
|
+
id: z16.string().min(1),
|
|
6927
|
+
intent: z16.string().min(1),
|
|
6928
|
+
label: z16.string().min(1),
|
|
6929
|
+
provider: z16.string().min(1),
|
|
6930
|
+
resourceRef: z16.string().min(1),
|
|
6931
|
+
resourceType: z16.string().min(1),
|
|
8011
6932
|
status: subscriptionStatusSchema,
|
|
8012
|
-
updatedAtMs:
|
|
6933
|
+
updatedAtMs: z16.number().finite()
|
|
8013
6934
|
}).strict();
|
|
8014
6935
|
function digest(value) {
|
|
8015
6936
|
return createHash("sha256").update(value).digest("hex").slice(0, 32);
|
|
@@ -8365,18 +7286,18 @@ var RESOURCE_WATCH_TOOL_SOURCE = {
|
|
|
8365
7286
|
id: "resource-watches",
|
|
8366
7287
|
description: "Inspect or stop resource watches for the current conversation."
|
|
8367
7288
|
};
|
|
8368
|
-
var subscribeInputSchema =
|
|
8369
|
-
resourceRef:
|
|
8370
|
-
provider:
|
|
8371
|
-
resourceType:
|
|
8372
|
-
label:
|
|
8373
|
-
events:
|
|
7289
|
+
var subscribeInputSchema = z17.object({
|
|
7290
|
+
resourceRef: z17.string().describe("Opaque resource ref copied from a subscribable tool result."),
|
|
7291
|
+
provider: z17.string().describe("Provider that owns the resource ref."),
|
|
7292
|
+
resourceType: z17.string().describe("Provider-defined resource type from the subscribable hint."),
|
|
7293
|
+
label: z17.string().describe("Human-readable resource label from the subscribable hint."),
|
|
7294
|
+
events: z17.array(z17.string()).min(1).describe(
|
|
8374
7295
|
"High-signal event names to deliver to this conversation when they occur."
|
|
8375
7296
|
),
|
|
8376
|
-
intent:
|
|
7297
|
+
intent: z17.string().describe(
|
|
8377
7298
|
"Concise reason this conversation wants these events, used when an event arrives."
|
|
8378
7299
|
),
|
|
8379
|
-
ttlMs:
|
|
7300
|
+
ttlMs: z17.coerce.number().describe(
|
|
8380
7301
|
"How long to keep the subscription active. Defaults to 14 days and is capped at 30 days."
|
|
8381
7302
|
).optional()
|
|
8382
7303
|
});
|
|
@@ -8469,7 +7390,7 @@ function createListResourceEventSubscriptionsTool(context) {
|
|
|
8469
7390
|
description: "List active resource event subscriptions for the current conversation.",
|
|
8470
7391
|
exposure: "deferred",
|
|
8471
7392
|
source: RESOURCE_WATCH_TOOL_SOURCE,
|
|
8472
|
-
inputSchema:
|
|
7393
|
+
inputSchema: z17.object({}),
|
|
8473
7394
|
outputSchema: juniorToolResultSchema,
|
|
8474
7395
|
async execute() {
|
|
8475
7396
|
const conversationId = requireConversationContext(context);
|
|
@@ -8502,7 +7423,7 @@ function createStopWatchingResourcesTool(context) {
|
|
|
8502
7423
|
description: "Stop every resource watch for the current conversation. Infer the user's intent from context instead of requiring a special command, and call this tool before confirming that watching stopped.",
|
|
8503
7424
|
exposure: "deferred",
|
|
8504
7425
|
source: RESOURCE_WATCH_TOOL_SOURCE,
|
|
8505
|
-
inputSchema:
|
|
7426
|
+
inputSchema: z17.object({}),
|
|
8506
7427
|
outputSchema: juniorToolResultSchema,
|
|
8507
7428
|
async execute() {
|
|
8508
7429
|
const conversationId = requireConversationContext(context);
|
|
@@ -8609,11 +7530,11 @@ async function listThreadReplies(input) {
|
|
|
8609
7530
|
}
|
|
8610
7531
|
|
|
8611
7532
|
// src/chat/slack/timestamp-param.ts
|
|
8612
|
-
import { z as
|
|
7533
|
+
import { z as z18 } from "zod";
|
|
8613
7534
|
function slackTimestampParam(description) {
|
|
8614
|
-
return
|
|
7535
|
+
return z18.preprocess(
|
|
8615
7536
|
(value) => typeof value === "number" && Number.isFinite(value) ? String(value) : value,
|
|
8616
|
-
|
|
7537
|
+
z18.string().min(1)
|
|
8617
7538
|
).describe(description);
|
|
8618
7539
|
}
|
|
8619
7540
|
function optionalSlackTimestampParam(description) {
|
|
@@ -8644,10 +7565,10 @@ function parseRequiredSlackTimestampParam(field, value) {
|
|
|
8644
7565
|
}
|
|
8645
7566
|
|
|
8646
7567
|
// src/chat/slack/tools/channel-list-messages.ts
|
|
8647
|
-
import { z as
|
|
8648
|
-
var booleanInput2 = (description) =>
|
|
7568
|
+
import { z as z19 } from "zod";
|
|
7569
|
+
var booleanInput2 = (description) => z19.preprocess(
|
|
8649
7570
|
(value) => value === "true" ? true : value === "false" ? false : value,
|
|
8650
|
-
|
|
7571
|
+
z19.boolean()
|
|
8651
7572
|
).describe(description);
|
|
8652
7573
|
function normalizeRangeTimestamp(field, value, targetChannelId) {
|
|
8653
7574
|
if (value === void 0) {
|
|
@@ -8674,9 +7595,9 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
8674
7595
|
return zodTool({
|
|
8675
7596
|
description: "List channel messages from Slack history in the active channel context. Use when the user asks for recent or historical channel context outside this thread. Do not use for live monitoring or when current thread context already answers the question.",
|
|
8676
7597
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
8677
|
-
inputSchema:
|
|
8678
|
-
limit:
|
|
8679
|
-
cursor:
|
|
7598
|
+
inputSchema: z19.object({
|
|
7599
|
+
limit: z19.coerce.number().int().min(1).max(1e3).describe("Maximum number of messages to return across pages.").optional(),
|
|
7600
|
+
cursor: z19.string().min(1).describe("Optional cursor to continue from a prior call.").optional(),
|
|
8680
7601
|
oldest: optionalSlackTimestampParam(
|
|
8681
7602
|
"Optional oldest message timestamp (Slack ts) for range filtering."
|
|
8682
7603
|
),
|
|
@@ -8686,7 +7607,7 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
8686
7607
|
inclusive: booleanInput2(
|
|
8687
7608
|
"Whether oldest/latest bounds should be inclusive."
|
|
8688
7609
|
).optional(),
|
|
8689
|
-
max_pages:
|
|
7610
|
+
max_pages: z19.coerce.number().int().min(1).max(10).describe("Maximum number of API pages to traverse in a single call.").optional()
|
|
8690
7611
|
}),
|
|
8691
7612
|
outputSchema: juniorToolResultSchema,
|
|
8692
7613
|
execute: async ({
|
|
@@ -8760,20 +7681,20 @@ function createSlackChannelListMessagesTool(context) {
|
|
|
8760
7681
|
}
|
|
8761
7682
|
|
|
8762
7683
|
// src/chat/slack/tools/conversation-search.ts
|
|
8763
|
-
import { z as
|
|
7684
|
+
import { z as z20 } from "zod";
|
|
8764
7685
|
var DEFAULT_LIMIT = 5;
|
|
8765
7686
|
var conversationSearchOutputSchema = juniorToolResultSchema.extend({
|
|
8766
|
-
query:
|
|
8767
|
-
count:
|
|
8768
|
-
threads:
|
|
8769
|
-
|
|
8770
|
-
conversation_id:
|
|
8771
|
-
thread_ts:
|
|
8772
|
-
message_id:
|
|
8773
|
-
message_role:
|
|
8774
|
-
message_timestamp:
|
|
8775
|
-
excerpt:
|
|
8776
|
-
permalink:
|
|
7687
|
+
query: z20.string(),
|
|
7688
|
+
count: z20.number().int().nonnegative(),
|
|
7689
|
+
threads: z20.array(
|
|
7690
|
+
z20.object({
|
|
7691
|
+
conversation_id: z20.string().min(1),
|
|
7692
|
+
thread_ts: z20.string().min(1),
|
|
7693
|
+
message_id: z20.string().min(1),
|
|
7694
|
+
message_role: z20.enum(["assistant", "user"]),
|
|
7695
|
+
message_timestamp: z20.string().datetime(),
|
|
7696
|
+
excerpt: z20.string(),
|
|
7697
|
+
permalink: z20.string().url().optional()
|
|
8777
7698
|
}).strict()
|
|
8778
7699
|
)
|
|
8779
7700
|
});
|
|
@@ -8786,9 +7707,9 @@ function createSlackConversationSearchTool(scope, currentConversationId, deps =
|
|
|
8786
7707
|
description: "Search retained public Junior conversation threads in the current Slack workspace."
|
|
8787
7708
|
},
|
|
8788
7709
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
8789
|
-
inputSchema:
|
|
8790
|
-
query:
|
|
8791
|
-
limit:
|
|
7710
|
+
inputSchema: z20.object({
|
|
7711
|
+
query: z20.string().trim().min(1).max(200).describe("Words or a short phrase to find in prior conversations."),
|
|
7712
|
+
limit: z20.number().int().min(1).max(10).describe("Maximum number of prior conversation threads to return.").nullable().optional()
|
|
8792
7713
|
}),
|
|
8793
7714
|
outputSchema: conversationSearchOutputSchema,
|
|
8794
7715
|
execute: async ({ query, limit }) => {
|
|
@@ -8835,27 +7756,27 @@ function createSlackConversationSearchTool(scope, currentConversationId, deps =
|
|
|
8835
7756
|
}
|
|
8836
7757
|
|
|
8837
7758
|
// src/chat/slack/tools/public-search.ts
|
|
8838
|
-
import { z as
|
|
7759
|
+
import { z as z21 } from "zod";
|
|
8839
7760
|
var DEFAULT_LIMIT2 = 10;
|
|
8840
|
-
var optionalTimestampSchema =
|
|
7761
|
+
var optionalTimestampSchema = z21.preprocess(
|
|
8841
7762
|
(value) => typeof value === "string" && value.trim() === "" ? void 0 : value,
|
|
8842
|
-
|
|
7763
|
+
z21.coerce.number().int().nonnegative().describe("Optional Unix timestamp bound.").optional()
|
|
8843
7764
|
);
|
|
8844
|
-
var searchMessageSchema =
|
|
8845
|
-
author_name:
|
|
8846
|
-
author_user_id:
|
|
8847
|
-
channel_id:
|
|
8848
|
-
channel_name:
|
|
8849
|
-
message_ts:
|
|
8850
|
-
content:
|
|
8851
|
-
is_author_bot:
|
|
8852
|
-
permalink:
|
|
7765
|
+
var searchMessageSchema = z21.object({
|
|
7766
|
+
author_name: z21.string().optional(),
|
|
7767
|
+
author_user_id: z21.string().optional(),
|
|
7768
|
+
channel_id: z21.string().min(1),
|
|
7769
|
+
channel_name: z21.string().optional(),
|
|
7770
|
+
message_ts: z21.string().min(1),
|
|
7771
|
+
content: z21.string(),
|
|
7772
|
+
is_author_bot: z21.boolean().optional(),
|
|
7773
|
+
permalink: z21.string().url()
|
|
8853
7774
|
});
|
|
8854
7775
|
var publicSearchOutputSchema = juniorToolResultSchema.extend({
|
|
8855
|
-
query:
|
|
8856
|
-
count:
|
|
8857
|
-
messages:
|
|
8858
|
-
next_cursor:
|
|
7776
|
+
query: z21.string(),
|
|
7777
|
+
count: z21.number().int().nonnegative(),
|
|
7778
|
+
messages: z21.array(searchMessageSchema),
|
|
7779
|
+
next_cursor: z21.string().optional()
|
|
8859
7780
|
});
|
|
8860
7781
|
function normalizeMessage(value) {
|
|
8861
7782
|
const parsed = searchMessageSchema.safeParse(value);
|
|
@@ -8877,8 +7798,8 @@ function createSlackPublicSearchTool(actionToken) {
|
|
|
8877
7798
|
return zodTool({
|
|
8878
7799
|
description: "Search public Slack channel messages across the current workspace. Use when the user asks about company activity, announcements, public mentions, or context outside the active channel. Search only when requested or clearly needed, prefer focused keywords and time bounds, and cite returned permalinks. This never searches private channels or DMs.",
|
|
8879
7800
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
8880
|
-
inputSchema:
|
|
8881
|
-
query:
|
|
7801
|
+
inputSchema: z21.object({
|
|
7802
|
+
query: z21.string().trim().min(1).max(500).describe(
|
|
8882
7803
|
"A focused Slack search query, including Slack search filters when useful."
|
|
8883
7804
|
),
|
|
8884
7805
|
after: optionalTimestampSchema.describe(
|
|
@@ -8887,10 +7808,10 @@ function createSlackPublicSearchTool(actionToken) {
|
|
|
8887
7808
|
before: optionalTimestampSchema.describe(
|
|
8888
7809
|
"Optional Unix timestamp upper bound."
|
|
8889
7810
|
),
|
|
8890
|
-
cursor:
|
|
8891
|
-
limit:
|
|
8892
|
-
sort:
|
|
8893
|
-
sort_dir:
|
|
7811
|
+
cursor: z21.string().min(1).describe("Cursor for the next result page.").optional(),
|
|
7812
|
+
limit: z21.coerce.number().int().min(1).max(20).describe("Maximum results to return; Slack allows at most 20.").optional(),
|
|
7813
|
+
sort: z21.enum(["score", "timestamp"]).describe("Rank by relevance or timestamp.").optional(),
|
|
7814
|
+
sort_dir: z21.enum(["asc", "desc"]).describe("Sort direction.").optional()
|
|
8894
7815
|
}),
|
|
8895
7816
|
outputSchema: publicSearchOutputSchema,
|
|
8896
7817
|
execute: async ({
|
|
@@ -8956,7 +7877,7 @@ function createSlackPublicSearchTool(actionToken) {
|
|
|
8956
7877
|
}
|
|
8957
7878
|
|
|
8958
7879
|
// src/chat/slack/tools/message-add-reaction.ts
|
|
8959
|
-
import { z as
|
|
7880
|
+
import { z as z22 } from "zod";
|
|
8960
7881
|
|
|
8961
7882
|
// src/chat/tools/idempotency.ts
|
|
8962
7883
|
function stableSerialize(value) {
|
|
@@ -8980,8 +7901,8 @@ function createOperationKey(toolName, input) {
|
|
|
8980
7901
|
function createSlackMessageAddReactionTool(context, state) {
|
|
8981
7902
|
return zodTool({
|
|
8982
7903
|
description: "Add an emoji reaction to the current inbound Slack message. Use when the user asks for a reaction on the current message without another target. Provide a Slack emoji alias name (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`), not a unicode emoji glyph. The target message is injected by runtime context; do not use this for arbitrary historical messages.",
|
|
8983
|
-
inputSchema:
|
|
8984
|
-
emoji:
|
|
7904
|
+
inputSchema: z22.object({
|
|
7905
|
+
emoji: z22.string().min(1).max(64).describe(
|
|
8985
7906
|
"Slack emoji alias name to react with (for example `thumbsup`, `white_check_mark`, or `thumbsup::skin-tone-6`). Optional surrounding colons are allowed."
|
|
8986
7907
|
)
|
|
8987
7908
|
}),
|
|
@@ -9032,18 +7953,18 @@ function createSlackMessageAddReactionTool(context, state) {
|
|
|
9032
7953
|
|
|
9033
7954
|
// src/chat/slack/tools/send-files.ts
|
|
9034
7955
|
import { createHash as createHash2 } from "crypto";
|
|
9035
|
-
import { z as
|
|
9036
|
-
var sendFilesDataSchema =
|
|
9037
|
-
channel_id:
|
|
9038
|
-
deduplicated:
|
|
9039
|
-
file_count:
|
|
9040
|
-
file_ids:
|
|
9041
|
-
thread_ts:
|
|
7956
|
+
import { z as z23 } from "zod";
|
|
7957
|
+
var sendFilesDataSchema = z23.object({
|
|
7958
|
+
channel_id: z23.string().min(1),
|
|
7959
|
+
deduplicated: z23.boolean().optional(),
|
|
7960
|
+
file_count: z23.number().int().nonnegative(),
|
|
7961
|
+
file_ids: z23.array(z23.string().min(1)).optional(),
|
|
7962
|
+
thread_ts: z23.string().min(1)
|
|
9042
7963
|
});
|
|
9043
7964
|
var sendFilesResultSchema = juniorToolResultSchema.extend({
|
|
9044
|
-
ok:
|
|
9045
|
-
status:
|
|
9046
|
-
target:
|
|
7965
|
+
ok: z23.literal(true),
|
|
7966
|
+
status: z23.literal("success"),
|
|
7967
|
+
target: z23.string().min(1),
|
|
9047
7968
|
data: sendFilesDataSchema
|
|
9048
7969
|
});
|
|
9049
7970
|
function normalizeFiles(files) {
|
|
@@ -9068,8 +7989,8 @@ function fileOperationInput(files) {
|
|
|
9068
7989
|
function createSendFilesTool(context, state, materializeFile) {
|
|
9069
7990
|
return zodTool({
|
|
9070
7991
|
description: "Send one or more sandbox files into the active Slack conversation. Use when the user asks to attach, send, or share files here, in this conversation, or in this thread. Do not use for ordinary assistant text, top-level channel posts, other named channels, inline @mentions, or pinging mentioned users.",
|
|
9071
|
-
inputSchema:
|
|
9072
|
-
files:
|
|
7992
|
+
inputSchema: z23.object({
|
|
7993
|
+
files: z23.array(sandboxFileReferenceSchema).min(1).describe(
|
|
9073
7994
|
"One or more existing sandbox files. Objects returned by imageGenerate or webFetch can be passed unchanged."
|
|
9074
7995
|
)
|
|
9075
7996
|
}),
|
|
@@ -9373,13 +8294,13 @@ function storedCanvasUrl(state, canvasId) {
|
|
|
9373
8294
|
}
|
|
9374
8295
|
|
|
9375
8296
|
// src/chat/slack/tools/canvas/create.ts
|
|
9376
|
-
import { z as
|
|
8297
|
+
import { z as z24 } from "zod";
|
|
9377
8298
|
function createSlackCanvasCreateTool(context, state) {
|
|
9378
8299
|
return zodTool({
|
|
9379
8300
|
description: "Create a Slack canvas for long-form output in the active assistant context channel. Use when the answer is better as a reusable document than a thread reply: long-form research, timelines, bios/profiles, structured notes, plans, comparisons, or anything likely to exceed one compact Slack reply. After creating it, reply with one or two short sentences plus the canvas link; do not recap the canvas contents. Do not use for short answers that fit cleanly in one normal thread reply.",
|
|
9380
|
-
inputSchema:
|
|
9381
|
-
title:
|
|
9382
|
-
markdown:
|
|
8301
|
+
inputSchema: z24.object({
|
|
8302
|
+
title: z24.string().min(1).max(160).describe("Canvas title."),
|
|
8303
|
+
markdown: z24.string().min(1).describe("Canvas markdown body content.")
|
|
9383
8304
|
}),
|
|
9384
8305
|
outputSchema: juniorToolResultSchema,
|
|
9385
8306
|
execute: async ({ title, markdown }) => {
|
|
@@ -9442,35 +8363,35 @@ function createSlackCanvasCreateTool(context, state) {
|
|
|
9442
8363
|
}
|
|
9443
8364
|
|
|
9444
8365
|
// src/chat/slack/tools/canvas/edit.ts
|
|
9445
|
-
import { z as
|
|
8366
|
+
import { z as z25 } from "zod";
|
|
9446
8367
|
function prepareCanvasEditArguments(input) {
|
|
9447
8368
|
return prepareTextReplacementArguments(input);
|
|
9448
8369
|
}
|
|
9449
|
-
var editReplacementSchema2 =
|
|
9450
|
-
oldText:
|
|
8370
|
+
var editReplacementSchema2 = z25.object({
|
|
8371
|
+
oldText: z25.string().min(1).describe(
|
|
9451
8372
|
"Exact Canvas markdown to replace. It must be unique in the current Canvas body and must not overlap another edit."
|
|
9452
8373
|
),
|
|
9453
|
-
newText:
|
|
8374
|
+
newText: z25.string().describe("Replacement Canvas markdown for this edit.")
|
|
9454
8375
|
});
|
|
9455
8376
|
var slackCanvasEditOutputSchema = juniorToolResultSchema.extend({
|
|
9456
|
-
canvas_id:
|
|
9457
|
-
title:
|
|
9458
|
-
permalink:
|
|
9459
|
-
diff:
|
|
9460
|
-
first_changed_line:
|
|
9461
|
-
replacements:
|
|
9462
|
-
normalized_heading_count:
|
|
9463
|
-
summary:
|
|
9464
|
-
deduplicated:
|
|
8377
|
+
canvas_id: z25.string().optional(),
|
|
8378
|
+
title: z25.string().optional(),
|
|
8379
|
+
permalink: z25.string().optional(),
|
|
8380
|
+
diff: z25.string().optional(),
|
|
8381
|
+
first_changed_line: z25.number().int().positive().optional(),
|
|
8382
|
+
replacements: z25.number().int().nonnegative().optional(),
|
|
8383
|
+
normalized_heading_count: z25.number().int().nonnegative().optional(),
|
|
8384
|
+
summary: z25.string().optional(),
|
|
8385
|
+
deduplicated: z25.boolean().optional()
|
|
9465
8386
|
}).strict();
|
|
9466
8387
|
function createSlackCanvasEditTool(state) {
|
|
9467
8388
|
return zodTool({
|
|
9468
8389
|
description: "Edit one Slack canvas with exact markdown replacements. Use for precise changes to existing Canvas content; prefer this over slackCanvasWrite for targeted changes. Each oldText must match exactly, be unique, and not overlap another edit. Returns a diff. Multiple changes to the same canvas: use one edits[] call.",
|
|
9469
8390
|
prepareArguments: prepareCanvasEditArguments,
|
|
9470
8391
|
executionMode: "sequential",
|
|
9471
|
-
inputSchema:
|
|
9472
|
-
canvas:
|
|
9473
|
-
edits:
|
|
8392
|
+
inputSchema: z25.object({
|
|
8393
|
+
canvas: z25.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
|
|
8394
|
+
edits: z25.array(editReplacementSchema2).min(1).describe(
|
|
9474
8395
|
"Exact replacements matched against the current Canvas body, not incrementally."
|
|
9475
8396
|
)
|
|
9476
8397
|
}),
|
|
@@ -9548,29 +8469,29 @@ function createSlackCanvasEditTool(state) {
|
|
|
9548
8469
|
}
|
|
9549
8470
|
|
|
9550
8471
|
// src/chat/slack/tools/canvas/read.ts
|
|
9551
|
-
import { z as
|
|
8472
|
+
import { z as z26 } from "zod";
|
|
9552
8473
|
var slackCanvasReadOutputSchema = juniorToolResultSchema.extend({
|
|
9553
|
-
canvas_id:
|
|
9554
|
-
title:
|
|
9555
|
-
permalink:
|
|
9556
|
-
mimetype:
|
|
9557
|
-
filetype:
|
|
9558
|
-
original_byte_length:
|
|
9559
|
-
content:
|
|
9560
|
-
start_line:
|
|
9561
|
-
end_line:
|
|
9562
|
-
total_lines:
|
|
8474
|
+
canvas_id: z26.string().optional(),
|
|
8475
|
+
title: z26.string().optional(),
|
|
8476
|
+
permalink: z26.string().optional(),
|
|
8477
|
+
mimetype: z26.string().optional(),
|
|
8478
|
+
filetype: z26.string().optional(),
|
|
8479
|
+
original_byte_length: z26.number().int().nonnegative().optional(),
|
|
8480
|
+
content: z26.string().optional(),
|
|
8481
|
+
start_line: z26.number().int().positive().optional(),
|
|
8482
|
+
end_line: z26.number().int().nonnegative().optional(),
|
|
8483
|
+
total_lines: z26.number().int().nonnegative().optional()
|
|
9563
8484
|
}).strict();
|
|
9564
8485
|
function createSlackCanvasReadTool() {
|
|
9565
8486
|
return zodTool({
|
|
9566
8487
|
description: "Read a bounded line range from a Slack canvas as markdown. Use when you need exact Canvas contents to verify facts or make edits safely. Do not use for generic web pages \u2014 use webFetch for those.",
|
|
9567
8488
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
9568
|
-
inputSchema:
|
|
9569
|
-
canvas:
|
|
8489
|
+
inputSchema: z26.object({
|
|
8490
|
+
canvas: z26.string().min(1).describe(
|
|
9570
8491
|
"Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL (e.g. `https://team.slack.com/docs/T.../F...`)."
|
|
9571
8492
|
),
|
|
9572
|
-
offset:
|
|
9573
|
-
limit:
|
|
8493
|
+
offset: z26.coerce.number().int().min(1).describe("1-indexed line number to start reading from.").optional(),
|
|
8494
|
+
limit: z26.coerce.number().int().min(1).describe("Maximum number of lines to read. Defaults to 1000.").optional()
|
|
9574
8495
|
}),
|
|
9575
8496
|
outputSchema: slackCanvasReadOutputSchema,
|
|
9576
8497
|
execute: async ({ canvas, offset, limit }) => {
|
|
@@ -9627,14 +8548,14 @@ function createSlackCanvasReadTool() {
|
|
|
9627
8548
|
}
|
|
9628
8549
|
|
|
9629
8550
|
// src/chat/slack/tools/canvas/write.ts
|
|
9630
|
-
import { z as
|
|
8551
|
+
import { z as z27 } from "zod";
|
|
9631
8552
|
function createSlackCanvasWriteTool(state) {
|
|
9632
8553
|
return zodTool({
|
|
9633
8554
|
description: "Write UTF-8 markdown content to a Slack canvas. Use for deliberate full-Canvas replacement after validation; use slackCanvasEdit for targeted changes to existing canvas content.",
|
|
9634
8555
|
executionMode: "sequential",
|
|
9635
|
-
inputSchema:
|
|
9636
|
-
canvas:
|
|
9637
|
-
content:
|
|
8556
|
+
inputSchema: z27.object({
|
|
8557
|
+
canvas: z27.string().min(1).describe("Canvas/file ID (e.g. `F0ABCDEF`) or Slack canvas/docs URL."),
|
|
8558
|
+
content: z27.string().describe("UTF-8 markdown content to write.")
|
|
9638
8559
|
}),
|
|
9639
8560
|
outputSchema: juniorToolResultSchema,
|
|
9640
8561
|
execute: async ({ canvas, content }) => {
|
|
@@ -9870,12 +8791,12 @@ async function updateListItem(input) {
|
|
|
9870
8791
|
}
|
|
9871
8792
|
|
|
9872
8793
|
// src/chat/slack/id-param.ts
|
|
9873
|
-
import { z as
|
|
8794
|
+
import { z as z28 } from "zod";
|
|
9874
8795
|
function slackChannelIdParam(description) {
|
|
9875
|
-
return
|
|
8796
|
+
return z28.string().min(1).describe(description);
|
|
9876
8797
|
}
|
|
9877
8798
|
function slackUserIdParam(description) {
|
|
9878
|
-
return
|
|
8799
|
+
return z28.string().min(1).describe(description);
|
|
9879
8800
|
}
|
|
9880
8801
|
function parseRequiredSlackChannelIdParam(field, value) {
|
|
9881
8802
|
const channelId = parseSlackChannelId(value);
|
|
@@ -9899,16 +8820,16 @@ function parseRequiredSlackUserIdParam(field, value) {
|
|
|
9899
8820
|
}
|
|
9900
8821
|
|
|
9901
8822
|
// src/chat/slack/tools/list/add-items.ts
|
|
9902
|
-
import { z as
|
|
8823
|
+
import { z as z29 } from "zod";
|
|
9903
8824
|
function createSlackListAddItemsTool(state) {
|
|
9904
8825
|
return zodTool({
|
|
9905
8826
|
description: "Add tasks to the active Slack list tracked in artifact context. Use when the user wants actionable items recorded in the current thread list. Do not use when no list exists and list creation was not requested.",
|
|
9906
|
-
inputSchema:
|
|
9907
|
-
items:
|
|
8827
|
+
inputSchema: z29.object({
|
|
8828
|
+
items: z29.array(z29.string().min(1)).min(1).max(25).describe("List item titles to create."),
|
|
9908
8829
|
assignee_user_id: slackUserIdParam(
|
|
9909
8830
|
"Optional Slack user ID assigned to all created items."
|
|
9910
8831
|
).optional(),
|
|
9911
|
-
due_date:
|
|
8832
|
+
due_date: z29.string().regex(/^\d{4}-\d{2}-\d{2}$/).describe("Optional due date in YYYY-MM-DD format.").optional()
|
|
9912
8833
|
}),
|
|
9913
8834
|
outputSchema: juniorToolResultSchema,
|
|
9914
8835
|
execute: async ({ items, assignee_user_id, due_date }) => {
|
|
@@ -9962,12 +8883,12 @@ function createSlackListAddItemsTool(state) {
|
|
|
9962
8883
|
}
|
|
9963
8884
|
|
|
9964
8885
|
// src/chat/slack/tools/list/create.ts
|
|
9965
|
-
import { z as
|
|
8886
|
+
import { z as z30 } from "zod";
|
|
9966
8887
|
function createSlackListCreateTool(state) {
|
|
9967
8888
|
return zodTool({
|
|
9968
8889
|
description: "Create a Slack todo list for action tracking. Use when the user needs structured tasks with ownership/completion tracking. Do not use for one-off notes without task management needs.",
|
|
9969
|
-
inputSchema:
|
|
9970
|
-
name:
|
|
8890
|
+
inputSchema: z30.object({
|
|
8891
|
+
name: z30.string().min(1).max(160).describe("Name for the new Slack list.")
|
|
9971
8892
|
}),
|
|
9972
8893
|
outputSchema: juniorToolResultSchema,
|
|
9973
8894
|
execute: async ({ name }) => {
|
|
@@ -9999,13 +8920,13 @@ function createSlackListCreateTool(state) {
|
|
|
9999
8920
|
}
|
|
10000
8921
|
|
|
10001
8922
|
// src/chat/slack/tools/list/get-items.ts
|
|
10002
|
-
import { z as
|
|
8923
|
+
import { z as z31 } from "zod";
|
|
10003
8924
|
function createSlackListGetItemsTool(state) {
|
|
10004
8925
|
return zodTool({
|
|
10005
8926
|
description: "Read items from the active Slack list tracked in artifact context. Use when the user asks for task status, open items, or list contents. Do not use when list state is already known from the immediately prior result.",
|
|
10006
8927
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
10007
|
-
inputSchema:
|
|
10008
|
-
limit:
|
|
8928
|
+
inputSchema: z31.object({
|
|
8929
|
+
limit: z31.coerce.number().int().min(1).max(200).describe("Maximum number of list items to return.").optional()
|
|
10009
8930
|
}),
|
|
10010
8931
|
outputSchema: juniorToolResultSchema,
|
|
10011
8932
|
execute: async ({ limit }) => {
|
|
@@ -10030,21 +8951,21 @@ function createSlackListGetItemsTool(state) {
|
|
|
10030
8951
|
}
|
|
10031
8952
|
|
|
10032
8953
|
// src/chat/slack/tools/list/update-item.ts
|
|
10033
|
-
import { z as
|
|
10034
|
-
var booleanInput3 = (description) =>
|
|
8954
|
+
import { z as z32 } from "zod";
|
|
8955
|
+
var booleanInput3 = (description) => z32.preprocess(
|
|
10035
8956
|
(value) => value === "true" ? true : value === "false" ? false : value,
|
|
10036
|
-
|
|
8957
|
+
z32.boolean()
|
|
10037
8958
|
).describe(description);
|
|
10038
|
-
var updateListItemInputSchema =
|
|
10039
|
-
|
|
10040
|
-
item_id:
|
|
8959
|
+
var updateListItemInputSchema = z32.union([
|
|
8960
|
+
z32.object({
|
|
8961
|
+
item_id: z32.string().min(1).describe("ID of the Slack list item to update."),
|
|
10041
8962
|
completed: booleanInput3("Optional completion status update."),
|
|
10042
|
-
title:
|
|
8963
|
+
title: z32.string().min(1).describe("Optional new item title.").optional()
|
|
10043
8964
|
}),
|
|
10044
|
-
|
|
10045
|
-
item_id:
|
|
8965
|
+
z32.object({
|
|
8966
|
+
item_id: z32.string().min(1).describe("ID of the Slack list item to update."),
|
|
10046
8967
|
completed: booleanInput3("Optional completion status update.").optional(),
|
|
10047
|
-
title:
|
|
8968
|
+
title: z32.string().min(1).describe("Optional new item title.")
|
|
10048
8969
|
})
|
|
10049
8970
|
]);
|
|
10050
8971
|
function createSlackListUpdateItemTool(state) {
|
|
@@ -10120,7 +9041,7 @@ async function checkSlackChannelReadAccess(args) {
|
|
|
10120
9041
|
}
|
|
10121
9042
|
|
|
10122
9043
|
// src/chat/slack/tools/thread-read.ts
|
|
10123
|
-
import { z as
|
|
9044
|
+
import { z as z33 } from "zod";
|
|
10124
9045
|
|
|
10125
9046
|
// src/chat/slack/tools/slack-message-url.ts
|
|
10126
9047
|
var SLACK_HOST_PATTERN = /^[a-z0-9-]+\.slack(?:-gov)?\.com$/;
|
|
@@ -10293,8 +9214,8 @@ function createSlackThreadReadTool(context, deps = {}) {
|
|
|
10293
9214
|
return zodTool({
|
|
10294
9215
|
description: "Read a Slack thread from a shared Slack message archive URL or explicit channel + timestamp. Use when the user shares a Slack message link (https://*.slack.com/archives/...) and you need the referenced message and its thread context. Only the current conversation and public channels Junior has seen in this workspace are readable.",
|
|
10295
9216
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
10296
|
-
inputSchema:
|
|
10297
|
-
url:
|
|
9217
|
+
inputSchema: z33.object({
|
|
9218
|
+
url: z33.string().min(1).describe(
|
|
10298
9219
|
"Slack message archive URL, e.g. https://workspace.slack.com/archives/C123/p1700000000123456"
|
|
10299
9220
|
).optional(),
|
|
10300
9221
|
channel_id: slackChannelIdParam(
|
|
@@ -10303,8 +9224,8 @@ function createSlackThreadReadTool(context, deps = {}) {
|
|
|
10303
9224
|
ts: slackTimestampParam(
|
|
10304
9225
|
"Slack message timestamp (e.g. 1700000000.123456). May be the thread root or any message in the thread."
|
|
10305
9226
|
).optional(),
|
|
10306
|
-
limit:
|
|
10307
|
-
max_pages:
|
|
9227
|
+
limit: z33.coerce.number().int().min(1).max(1e3).describe("Maximum number of thread messages to fetch.").optional(),
|
|
9228
|
+
max_pages: z33.coerce.number().int().min(1).max(10).describe("Maximum number of Slack API pages to traverse.").optional()
|
|
10308
9229
|
}),
|
|
10309
9230
|
outputSchema: juniorToolResultSchema,
|
|
10310
9231
|
execute: async ({ url, channel_id, ts, limit, max_pages }) => {
|
|
@@ -10560,29 +9481,29 @@ async function searchSlackUsers(options) {
|
|
|
10560
9481
|
}
|
|
10561
9482
|
|
|
10562
9483
|
// src/chat/slack/tools/user-lookup.ts
|
|
10563
|
-
import { z as
|
|
10564
|
-
var booleanInput4 = (description) =>
|
|
9484
|
+
import { z as z34 } from "zod";
|
|
9485
|
+
var booleanInput4 = (description) => z34.preprocess(
|
|
10565
9486
|
(value) => value === "true" ? true : value === "false" ? false : value,
|
|
10566
|
-
|
|
9487
|
+
z34.boolean()
|
|
10567
9488
|
).describe(description);
|
|
10568
9489
|
function createSlackUserLookupTool() {
|
|
10569
9490
|
return zodTool({
|
|
10570
9491
|
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.",
|
|
10571
9492
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
10572
|
-
inputSchema:
|
|
9493
|
+
inputSchema: z34.object({
|
|
10573
9494
|
user_id: slackUserIdParam(
|
|
10574
9495
|
"Slack user ID to look up (e.g. U039RR91S). Mutually exclusive with email and query."
|
|
10575
9496
|
).optional(),
|
|
10576
|
-
email:
|
|
9497
|
+
email: z34.string().min(3).describe(
|
|
10577
9498
|
"Email address to look up. Mutually exclusive with user_id and query."
|
|
10578
9499
|
).optional(),
|
|
10579
|
-
query:
|
|
9500
|
+
query: z34.string().min(2).describe(
|
|
10580
9501
|
"Name to search for (matches against username, display name, real name). Mutually exclusive with user_id and email."
|
|
10581
9502
|
).optional(),
|
|
10582
|
-
limit:
|
|
9503
|
+
limit: z34.coerce.number().int().min(1).max(20).describe(
|
|
10583
9504
|
"Maximum number of results to return for name search. Defaults to 10."
|
|
10584
9505
|
).optional(),
|
|
10585
|
-
max_pages:
|
|
9506
|
+
max_pages: z34.coerce.number().int().min(1).max(5).describe(
|
|
10586
9507
|
"Maximum number of Slack API pages to scan for name search. Defaults to 3."
|
|
10587
9508
|
).optional(),
|
|
10588
9509
|
include_bots: booleanInput4(
|
|
@@ -10686,18 +9607,18 @@ function createSlackUserLookupTool() {
|
|
|
10686
9607
|
}
|
|
10687
9608
|
|
|
10688
9609
|
// src/chat/tools/system-time.ts
|
|
10689
|
-
import { z as
|
|
9610
|
+
import { z as z35 } from "zod";
|
|
10690
9611
|
var systemTimeOutputSchema = juniorToolResultSchema.extend({
|
|
10691
|
-
unix_ms:
|
|
10692
|
-
iso_utc:
|
|
10693
|
-
iso_local:
|
|
10694
|
-
timezone_offset_minutes:
|
|
9612
|
+
unix_ms: z35.number(),
|
|
9613
|
+
iso_utc: z35.string(),
|
|
9614
|
+
iso_local: z35.string(),
|
|
9615
|
+
timezone_offset_minutes: z35.number()
|
|
10695
9616
|
});
|
|
10696
9617
|
function createSystemTimeTool() {
|
|
10697
9618
|
return zodTool({
|
|
10698
9619
|
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.",
|
|
10699
9620
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
10700
|
-
inputSchema:
|
|
9621
|
+
inputSchema: z35.object({}),
|
|
10701
9622
|
outputSchema: systemTimeOutputSchema,
|
|
10702
9623
|
privateTraceResult: (result) => ({
|
|
10703
9624
|
ok: result.ok,
|
|
@@ -10726,10 +9647,10 @@ function createSystemTimeTool() {
|
|
|
10726
9647
|
}
|
|
10727
9648
|
|
|
10728
9649
|
// src/chat/tools/handoff/tool.ts
|
|
10729
|
-
import { z as
|
|
9650
|
+
import { z as z36 } from "zod";
|
|
10730
9651
|
var HANDOFF_TOOL_NAME = "handoff";
|
|
10731
9652
|
function createHandoffTool(handoff) {
|
|
10732
|
-
const profileSchema =
|
|
9653
|
+
const profileSchema = z36.enum(handoff.profiles);
|
|
10733
9654
|
const handoffResultSchema = juniorToolResultSchema.extend({
|
|
10734
9655
|
model_profile: profileSchema
|
|
10735
9656
|
});
|
|
@@ -10737,7 +9658,7 @@ function createHandoffTool(handoff) {
|
|
|
10737
9658
|
return zodTool({
|
|
10738
9659
|
description: `Switch to another execution profile and continue the same task. Profiles: ${profileNames}. Call this as the only tool.`,
|
|
10739
9660
|
executionMode: "sequential",
|
|
10740
|
-
inputSchema:
|
|
9661
|
+
inputSchema: z36.object({
|
|
10741
9662
|
profile: profileSchema.describe("Target execution profile")
|
|
10742
9663
|
}).strict(),
|
|
10743
9664
|
outputSchema: handoffResultSchema,
|
|
@@ -10760,7 +9681,7 @@ function createHandoffTool(handoff) {
|
|
|
10760
9681
|
}
|
|
10761
9682
|
|
|
10762
9683
|
// src/chat/tools/web/fetch-tool.ts
|
|
10763
|
-
import { z as
|
|
9684
|
+
import { z as z37 } from "zod";
|
|
10764
9685
|
|
|
10765
9686
|
// src/chat/tools/web/constants.ts
|
|
10766
9687
|
var USER_AGENT = "junior-bot/0.1";
|
|
@@ -11210,9 +10131,9 @@ function createWebFetchTool(hooks, options = {}) {
|
|
|
11210
10131
|
destructiveHint: false,
|
|
11211
10132
|
openWorldHint: true
|
|
11212
10133
|
},
|
|
11213
|
-
inputSchema:
|
|
11214
|
-
url:
|
|
11215
|
-
max_chars:
|
|
10134
|
+
inputSchema: z37.object({
|
|
10135
|
+
url: z37.string().min(1).describe("HTTP(S) URL to fetch."),
|
|
10136
|
+
max_chars: z37.coerce.number().int().min(500).max(MAX_FETCH_CHARS).describe("Optional maximum number of extracted characters to return.").optional()
|
|
11216
10137
|
}),
|
|
11217
10138
|
outputSchema: juniorToolResultSchema,
|
|
11218
10139
|
execute: async ({ url, max_chars }) => {
|
|
@@ -11273,7 +10194,7 @@ function createWebFetchTool(hooks, options = {}) {
|
|
|
11273
10194
|
}
|
|
11274
10195
|
|
|
11275
10196
|
// src/chat/tools/web/search.ts
|
|
11276
|
-
import { z as
|
|
10197
|
+
import { z as z38 } from "zod";
|
|
11277
10198
|
import { generateText } from "ai";
|
|
11278
10199
|
import { createGatewayProvider } from "@ai-sdk/gateway";
|
|
11279
10200
|
var SEARCH_TIMEOUT_MS = 6e4;
|
|
@@ -11322,9 +10243,9 @@ function createWebSearchTool(modelId, override) {
|
|
|
11322
10243
|
destructiveHint: false,
|
|
11323
10244
|
openWorldHint: true
|
|
11324
10245
|
},
|
|
11325
|
-
inputSchema:
|
|
11326
|
-
query:
|
|
11327
|
-
max_results:
|
|
10246
|
+
inputSchema: z38.object({
|
|
10247
|
+
query: z38.string().min(1).max(500).describe("Search query."),
|
|
10248
|
+
max_results: z38.coerce.number().int().min(1).max(MAX_RESULTS2).describe("Max results to return.").optional()
|
|
11328
10249
|
}),
|
|
11329
10250
|
outputSchema: juniorToolResultSchema,
|
|
11330
10251
|
execute: async ({ query, max_results }) => {
|
|
@@ -11393,14 +10314,14 @@ function createWebSearchTool(modelId, override) {
|
|
|
11393
10314
|
}
|
|
11394
10315
|
|
|
11395
10316
|
// src/chat/tools/sandbox/write-file.ts
|
|
11396
|
-
import { z as
|
|
10317
|
+
import { z as z39 } from "zod";
|
|
11397
10318
|
function createWriteFileTool() {
|
|
11398
10319
|
return zodTool({
|
|
11399
10320
|
description: "Write UTF-8 content to a file in the sandbox workspace. Use for intentional file creation or deliberate full-file replacement after validation; use editFile instead for targeted changes to existing files. Do not use for exploratory analysis-only turns.",
|
|
11400
10321
|
executionMode: "sequential",
|
|
11401
|
-
inputSchema:
|
|
11402
|
-
path:
|
|
11403
|
-
content:
|
|
10322
|
+
inputSchema: z39.object({
|
|
10323
|
+
path: z39.string().min(1).describe("Path to write in the sandbox workspace."),
|
|
10324
|
+
content: z39.string().describe("UTF-8 file content to write.")
|
|
11404
10325
|
}),
|
|
11405
10326
|
outputSchema: juniorToolResultSchema,
|
|
11406
10327
|
execute: async () => {
|
|
@@ -11759,11 +10680,11 @@ function replaceGeneratedTextContent(content, text) {
|
|
|
11759
10680
|
...withoutFirstGeneratedTextContent(content)
|
|
11760
10681
|
];
|
|
11761
10682
|
}
|
|
11762
|
-
function
|
|
10683
|
+
function isRecord3(value) {
|
|
11763
10684
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
11764
10685
|
}
|
|
11765
10686
|
function hasContinuation(value) {
|
|
11766
|
-
return
|
|
10687
|
+
return isRecord3(value) && isRecord3(value.continuation);
|
|
11767
10688
|
}
|
|
11768
10689
|
function stringField(record, key) {
|
|
11769
10690
|
const value = record[key];
|
|
@@ -11778,7 +10699,7 @@ function stringListField(record, key) {
|
|
|
11778
10699
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
11779
10700
|
}
|
|
11780
10701
|
function accountText(value) {
|
|
11781
|
-
if (!
|
|
10702
|
+
if (!isRecord3(value)) {
|
|
11782
10703
|
return void 0;
|
|
11783
10704
|
}
|
|
11784
10705
|
const label = stringField(value, "label") || stringField(value, "id");
|
|
@@ -11789,7 +10710,7 @@ function accountText(value) {
|
|
|
11789
10710
|
return id && id !== label ? `${label} (${id})` : label;
|
|
11790
10711
|
}
|
|
11791
10712
|
function upstreamPermissionDeniedText(value) {
|
|
11792
|
-
if (!
|
|
10713
|
+
if (!isRecord3(value) || !isRecord3(value.permission_denied)) {
|
|
11793
10714
|
return void 0;
|
|
11794
10715
|
}
|
|
11795
10716
|
const signal = value.permission_denied;
|
|
@@ -11804,7 +10725,7 @@ function upstreamPermissionDeniedText(value) {
|
|
|
11804
10725
|
if (!provider || !message || !upstreamHost || !upstreamPath || !status) {
|
|
11805
10726
|
return void 0;
|
|
11806
10727
|
}
|
|
11807
|
-
const grant =
|
|
10728
|
+
const grant = isRecord3(signal.grant) ? signal.grant : {};
|
|
11808
10729
|
const grantName = stringField(grant, "name");
|
|
11809
10730
|
const grantAccess = stringField(grant, "access");
|
|
11810
10731
|
const grantReason = stringField(grant, "reason");
|
|
@@ -11927,9 +10848,6 @@ function pluginAuthRequiredSignal(details) {
|
|
|
11927
10848
|
...parsedSignal.authorization ? { authorization: parsedSignal.authorization } : {}
|
|
11928
10849
|
};
|
|
11929
10850
|
}
|
|
11930
|
-
function authorizationId(args) {
|
|
11931
|
-
return `${args.sessionId}:${args.kind}:${args.provider}`;
|
|
11932
|
-
}
|
|
11933
10851
|
function createPluginAuthOrchestration(input) {
|
|
11934
10852
|
let pendingPause;
|
|
11935
10853
|
const startAuthorizationPause = async (provider, options) => {
|
|
@@ -11966,7 +10884,8 @@ function createPluginAuthOrchestration(input) {
|
|
|
11966
10884
|
threadTs: input.threadTs,
|
|
11967
10885
|
...options?.scope ? { scope: options.scope } : {},
|
|
11968
10886
|
resumeConversationId: input.conversationId,
|
|
11969
|
-
resumeSessionId: input.sessionId
|
|
10887
|
+
resumeSessionId: input.sessionId,
|
|
10888
|
+
authorization: input.authorization
|
|
11970
10889
|
});
|
|
11971
10890
|
if (!oauthResult.ok) {
|
|
11972
10891
|
throw new Error(oauthResult.error);
|
|
@@ -12144,17 +11063,17 @@ function handleToolExecutionError(error, toolName, toolCallId, shouldTrace, trac
|
|
|
12144
11063
|
}
|
|
12145
11064
|
|
|
12146
11065
|
// src/chat/tools/execute-tool.ts
|
|
12147
|
-
import { z as
|
|
11066
|
+
import { z as z40 } from "zod";
|
|
12148
11067
|
var EXECUTE_TOOL_NAME = "executeTool";
|
|
12149
11068
|
function createExecuteToolTool() {
|
|
12150
11069
|
return zodTool({
|
|
12151
11070
|
description: "Execute any catalog tool by exact tool_name from searchTools or a tool-result execution hint. Put tool-specific parameters inside arguments.",
|
|
12152
11071
|
executionMode: "sequential",
|
|
12153
|
-
inputSchema:
|
|
12154
|
-
tool_name:
|
|
11072
|
+
inputSchema: z40.object({
|
|
11073
|
+
tool_name: z40.string().min(1).describe(
|
|
12155
11074
|
"Exact catalog tool_name returned by searchTools or a tool-result execution hint."
|
|
12156
11075
|
),
|
|
12157
|
-
arguments:
|
|
11076
|
+
arguments: z40.record(z40.string(), z40.unknown()).describe(
|
|
12158
11077
|
'Arguments matching the selected catalog tool schema, for example { "query": "..." }.'
|
|
12159
11078
|
).optional()
|
|
12160
11079
|
}).strict(),
|
|
@@ -12194,42 +11113,42 @@ function planToolExposure(tools) {
|
|
|
12194
11113
|
}
|
|
12195
11114
|
|
|
12196
11115
|
// src/chat/tools/search-tools.ts
|
|
12197
|
-
import { z as
|
|
11116
|
+
import { z as z41 } from "zod";
|
|
12198
11117
|
var SEARCH_TOOLS_NAME = "searchTools";
|
|
12199
11118
|
var DEFAULT_MAX_RESULTS2 = 5;
|
|
12200
11119
|
var MAX_RESULTS3 = 20;
|
|
12201
11120
|
var MODEL_VISIBLE_DESCRIPTION_CAP = 180;
|
|
12202
|
-
var searchToolsSourceSchema =
|
|
12203
|
-
id:
|
|
12204
|
-
description:
|
|
11121
|
+
var searchToolsSourceSchema = z41.object({
|
|
11122
|
+
id: z41.string(),
|
|
11123
|
+
description: z41.string()
|
|
12205
11124
|
}).strict();
|
|
12206
|
-
var toolCallExampleSchema =
|
|
12207
|
-
tool_name:
|
|
12208
|
-
arguments:
|
|
11125
|
+
var toolCallExampleSchema = z41.object({
|
|
11126
|
+
tool_name: z41.string(),
|
|
11127
|
+
arguments: z41.record(z41.string(), z41.string())
|
|
12209
11128
|
}).strict();
|
|
12210
|
-
var searchToolsToolSchema =
|
|
12211
|
-
tool_name:
|
|
12212
|
-
description:
|
|
12213
|
-
exposure:
|
|
12214
|
-
source:
|
|
12215
|
-
signature:
|
|
11129
|
+
var searchToolsToolSchema = z41.object({
|
|
11130
|
+
tool_name: z41.string(),
|
|
11131
|
+
description: z41.string(),
|
|
11132
|
+
exposure: z41.enum(["direct", "deferred", "modelOnly", "hidden"]),
|
|
11133
|
+
source: z41.string().optional(),
|
|
11134
|
+
signature: z41.string(),
|
|
12216
11135
|
call: toolCallExampleSchema,
|
|
12217
|
-
input_schema:
|
|
12218
|
-
input_schema_summary:
|
|
12219
|
-
call_notes:
|
|
12220
|
-
annotations:
|
|
11136
|
+
input_schema: z41.unknown(),
|
|
11137
|
+
input_schema_summary: z41.string(),
|
|
11138
|
+
call_notes: z41.array(z41.string()),
|
|
11139
|
+
annotations: z41.record(z41.string(), z41.unknown())
|
|
12221
11140
|
}).strict();
|
|
12222
11141
|
var searchToolsOutputSchema = juniorToolResultSchema.extend({
|
|
12223
|
-
query:
|
|
12224
|
-
source:
|
|
12225
|
-
sources:
|
|
12226
|
-
total_catalog_tools:
|
|
12227
|
-
total_eligible_tools:
|
|
12228
|
-
total_matches:
|
|
12229
|
-
returned_tools:
|
|
12230
|
-
execution_tool:
|
|
11142
|
+
query: z41.string().nullable(),
|
|
11143
|
+
source: z41.string().nullable(),
|
|
11144
|
+
sources: z41.array(searchToolsSourceSchema),
|
|
11145
|
+
total_catalog_tools: z41.number().int().nonnegative(),
|
|
11146
|
+
total_eligible_tools: z41.number().int().nonnegative(),
|
|
11147
|
+
total_matches: z41.number().int().nonnegative(),
|
|
11148
|
+
returned_tools: z41.number().int().nonnegative(),
|
|
11149
|
+
execution_tool: z41.literal("executeTool"),
|
|
12231
11150
|
execution_example: toolCallExampleSchema,
|
|
12232
|
-
tools:
|
|
11151
|
+
tools: z41.array(searchToolsToolSchema)
|
|
12233
11152
|
}).strict();
|
|
12234
11153
|
function normalize2(value) {
|
|
12235
11154
|
return value.toLowerCase().replace(/[^a-z0-9_]+/g, " ").trim();
|
|
@@ -12421,14 +11340,14 @@ function createSearchToolsTool(catalogTools) {
|
|
|
12421
11340
|
return zodTool({
|
|
12422
11341
|
description: renderSearchToolsDescription(knownSources),
|
|
12423
11342
|
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
12424
|
-
inputSchema:
|
|
12425
|
-
query:
|
|
11343
|
+
inputSchema: z41.object({
|
|
11344
|
+
query: z41.string().nullable().describe(
|
|
12426
11345
|
"Optional search terms describing the tool, owner, action, or arguments needed. Empty string lists catalog tools."
|
|
12427
11346
|
).optional(),
|
|
12428
|
-
source:
|
|
11347
|
+
source: z41.string().nullable().describe(
|
|
12429
11348
|
"Optional source id to search within, such as a plugin source returned in sources."
|
|
12430
11349
|
).optional(),
|
|
12431
|
-
max_results:
|
|
11350
|
+
max_results: z41.number().int().min(1).max(MAX_RESULTS3).nullable().describe("Maximum matching catalog tool descriptors to return.").optional()
|
|
12432
11351
|
}).strict(),
|
|
12433
11352
|
outputSchema: searchToolsOutputSchema,
|
|
12434
11353
|
privateTraceResult: (result) => ({
|
|
@@ -12675,570 +11594,43 @@ function createPiAgentTools(tools, sandbox, spanContext, onStatus, sandboxTools,
|
|
|
12675
11594
|
params: parsed,
|
|
12676
11595
|
signal,
|
|
12677
11596
|
setSpanAttributes: setSpanAttributes2,
|
|
12678
|
-
toolDef,
|
|
12679
|
-
toolName
|
|
12680
|
-
});
|
|
12681
|
-
} catch (error) {
|
|
12682
|
-
await notifyToolResult({
|
|
12683
|
-
error: error instanceof Error ? error.message : String(error),
|
|
12684
|
-
ok: false,
|
|
12685
|
-
params: executionParams,
|
|
12686
|
-
toolCallId,
|
|
12687
|
-
toolName: executionToolName
|
|
12688
|
-
});
|
|
12689
|
-
if (error instanceof AuthorizationPauseError || error instanceof AuthorizationFlowDisabledError) {
|
|
12690
|
-
throw error;
|
|
12691
|
-
}
|
|
12692
|
-
handleToolExecutionError(
|
|
12693
|
-
error,
|
|
12694
|
-
executionToolName,
|
|
12695
|
-
toolCallId,
|
|
12696
|
-
shouldTrace,
|
|
12697
|
-
spanContext,
|
|
12698
|
-
effectiveConversationPrivacy,
|
|
12699
|
-
setSpanAttributes2
|
|
12700
|
-
);
|
|
12701
|
-
}
|
|
12702
|
-
},
|
|
12703
|
-
{
|
|
12704
|
-
"gen_ai.operation.name": "execute_tool",
|
|
12705
|
-
"gen_ai.tool.name": toolName,
|
|
12706
|
-
"gen_ai.tool.description": toolDef.description,
|
|
12707
|
-
"gen_ai.tool.type": "function",
|
|
12708
|
-
...toolArgumentsMetadata,
|
|
12709
|
-
"gen_ai.tool.call.id": toolCallId,
|
|
12710
|
-
...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
|
|
12711
|
-
}
|
|
12712
|
-
);
|
|
12713
|
-
}
|
|
12714
|
-
}));
|
|
12715
|
-
}
|
|
12716
|
-
|
|
12717
|
-
// src/chat/mcp/oauth.ts
|
|
12718
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
12719
|
-
import { StreamableHTTPClientTransport as StreamableHTTPClientTransport2 } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
12720
|
-
|
|
12721
|
-
// src/chat/mcp/auth-store.ts
|
|
12722
|
-
import {
|
|
12723
|
-
sourceSchema as sourceSchema2
|
|
12724
|
-
} from "@sentry/junior-plugin-api";
|
|
12725
|
-
var MCP_AUTH_SESSION_PREFIX = "junior:mcp_oauth_attempt:v2";
|
|
12726
|
-
var MCP_AUTH_CREDENTIALS_PREFIX = "junior:mcp_auth_credentials";
|
|
12727
|
-
var MCP_AUTH_SESSION_INDEX_PREFIX = "junior:mcp_oauth_attempt_index:v2";
|
|
12728
|
-
var MCP_SERVER_SESSION_PREFIX = "junior:mcp_server_session";
|
|
12729
|
-
var MCP_AUTH_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
12730
|
-
var MCP_AUTH_CREDENTIALS_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
12731
|
-
var MCP_SERVER_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
12732
|
-
function sessionKey(authSessionId) {
|
|
12733
|
-
return `${MCP_AUTH_SESSION_PREFIX}:${authSessionId}`;
|
|
12734
|
-
}
|
|
12735
|
-
function credentialsKey(userId, provider) {
|
|
12736
|
-
return `${MCP_AUTH_CREDENTIALS_PREFIX}:${userId}:${provider}`;
|
|
12737
|
-
}
|
|
12738
|
-
function sessionIndexKey(userId, provider) {
|
|
12739
|
-
return `${MCP_AUTH_SESSION_INDEX_PREFIX}:${userId}:${provider}`;
|
|
12740
|
-
}
|
|
12741
|
-
function serverSessionKey(userId, provider) {
|
|
12742
|
-
return `${MCP_SERVER_SESSION_PREFIX}:${userId}:${provider}`;
|
|
12743
|
-
}
|
|
12744
|
-
function parseSessionIndex(value) {
|
|
12745
|
-
if (typeof value !== "string") {
|
|
12746
|
-
return [];
|
|
12747
|
-
}
|
|
12748
|
-
try {
|
|
12749
|
-
const parsed = JSON.parse(value);
|
|
12750
|
-
if (!Array.isArray(parsed)) {
|
|
12751
|
-
return [];
|
|
12752
|
-
}
|
|
12753
|
-
return [
|
|
12754
|
-
...new Set(parsed.filter((id) => typeof id === "string"))
|
|
12755
|
-
];
|
|
12756
|
-
} catch {
|
|
12757
|
-
return [];
|
|
12758
|
-
}
|
|
12759
|
-
}
|
|
12760
|
-
function parseMcpAuthSession(value) {
|
|
12761
|
-
if (typeof value !== "string") {
|
|
12762
|
-
return void 0;
|
|
12763
|
-
}
|
|
12764
|
-
try {
|
|
12765
|
-
const parsed = JSON.parse(value);
|
|
12766
|
-
if (!isRecord(parsed)) {
|
|
12767
|
-
return void 0;
|
|
12768
|
-
}
|
|
12769
|
-
if (parsed.schemaVersion !== 2 || typeof parsed.authSessionId !== "string" || typeof parsed.provider !== "string" || typeof parsed.userId !== "string" || typeof parsed.conversationId !== "string" || typeof parsed.sessionId !== "string" || typeof parsed.userMessage !== "string" || typeof parsed.createdAtMs !== "number" || typeof parsed.updatedAtMs !== "number") {
|
|
12770
|
-
return void 0;
|
|
12771
|
-
}
|
|
12772
|
-
const destination = parsed.destination === void 0 ? void 0 : parseDestination(parsed.destination);
|
|
12773
|
-
if (parsed.destination !== void 0 && !destination) {
|
|
12774
|
-
return void 0;
|
|
12775
|
-
}
|
|
12776
|
-
const source = parsed.source === void 0 ? void 0 : sourceSchema2.safeParse(parsed.source);
|
|
12777
|
-
if (parsed.source !== void 0 && (!source || !source.success)) {
|
|
12778
|
-
return void 0;
|
|
12779
|
-
}
|
|
12780
|
-
return {
|
|
12781
|
-
schemaVersion: 2,
|
|
12782
|
-
authSessionId: parsed.authSessionId,
|
|
12783
|
-
provider: parsed.provider,
|
|
12784
|
-
userId: parsed.userId,
|
|
12785
|
-
conversationId: parsed.conversationId,
|
|
12786
|
-
...destination ? { destination } : {},
|
|
12787
|
-
...source?.success ? { source: source.data } : {},
|
|
12788
|
-
sessionId: parsed.sessionId,
|
|
12789
|
-
userMessage: parsed.userMessage,
|
|
12790
|
-
createdAtMs: parsed.createdAtMs,
|
|
12791
|
-
updatedAtMs: parsed.updatedAtMs,
|
|
12792
|
-
...typeof parsed.channelId === "string" ? { channelId: parsed.channelId } : {},
|
|
12793
|
-
...typeof parsed.threadTs === "string" ? { threadTs: parsed.threadTs } : {},
|
|
12794
|
-
...typeof parsed.toolChannelId === "string" ? { toolChannelId: parsed.toolChannelId } : {},
|
|
12795
|
-
...isRecord(parsed.configuration) ? { configuration: parsed.configuration } : {},
|
|
12796
|
-
...isRecord(parsed.artifactState) ? { artifactState: parsed.artifactState } : {},
|
|
12797
|
-
...typeof parsed.authorizationUrl === "string" ? { authorizationUrl: parsed.authorizationUrl } : {},
|
|
12798
|
-
...typeof parsed.codeVerifier === "string" ? { codeVerifier: parsed.codeVerifier } : {}
|
|
12799
|
-
};
|
|
12800
|
-
} catch {
|
|
12801
|
-
return void 0;
|
|
12802
|
-
}
|
|
12803
|
-
}
|
|
12804
|
-
function parseServerSession(value) {
|
|
12805
|
-
if (typeof value !== "string") {
|
|
12806
|
-
return void 0;
|
|
12807
|
-
}
|
|
12808
|
-
try {
|
|
12809
|
-
const parsed = JSON.parse(value);
|
|
12810
|
-
if (!isRecord(parsed) || typeof parsed.sessionId !== "string" || typeof parsed.updatedAtMs !== "number") {
|
|
12811
|
-
return void 0;
|
|
12812
|
-
}
|
|
12813
|
-
return {
|
|
12814
|
-
sessionId: parsed.sessionId,
|
|
12815
|
-
updatedAtMs: parsed.updatedAtMs
|
|
12816
|
-
};
|
|
12817
|
-
} catch {
|
|
12818
|
-
return void 0;
|
|
12819
|
-
}
|
|
12820
|
-
}
|
|
12821
|
-
function parseStoredCredentials(value) {
|
|
12822
|
-
if (typeof value !== "string") {
|
|
12823
|
-
return void 0;
|
|
12824
|
-
}
|
|
12825
|
-
try {
|
|
12826
|
-
const parsed = JSON.parse(value);
|
|
12827
|
-
if (!isRecord(parsed)) {
|
|
12828
|
-
return void 0;
|
|
12829
|
-
}
|
|
12830
|
-
return {
|
|
12831
|
-
...isRecord(parsed.clientInformation) ? {
|
|
12832
|
-
clientInformation: parsed.clientInformation
|
|
12833
|
-
} : {},
|
|
12834
|
-
...isRecord(parsed.discoveryState) ? {
|
|
12835
|
-
discoveryState: parsed.discoveryState
|
|
12836
|
-
} : {},
|
|
12837
|
-
...isRecord(parsed.tokens) ? { tokens: parsed.tokens } : {}
|
|
12838
|
-
};
|
|
12839
|
-
} catch {
|
|
12840
|
-
return void 0;
|
|
12841
|
-
}
|
|
12842
|
-
}
|
|
12843
|
-
async function getConnectedStateAdapter() {
|
|
12844
|
-
const stateAdapter = getStateAdapter();
|
|
12845
|
-
await stateAdapter.connect();
|
|
12846
|
-
return stateAdapter;
|
|
12847
|
-
}
|
|
12848
|
-
async function getMcpAuthSession(authSessionId) {
|
|
12849
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12850
|
-
return parseMcpAuthSession(await stateAdapter.get(sessionKey(authSessionId)));
|
|
12851
|
-
}
|
|
12852
|
-
async function putMcpAuthSession(session, ttlMs2 = MCP_AUTH_SESSION_TTL_MS) {
|
|
12853
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12854
|
-
await stateAdapter.set(
|
|
12855
|
-
sessionKey(session.authSessionId),
|
|
12856
|
-
JSON.stringify(session),
|
|
12857
|
-
ttlMs2
|
|
12858
|
-
);
|
|
12859
|
-
const nextIndex = parseSessionIndex(
|
|
12860
|
-
await stateAdapter.get(sessionIndexKey(session.userId, session.provider))
|
|
12861
|
-
);
|
|
12862
|
-
if (!nextIndex.includes(session.authSessionId)) {
|
|
12863
|
-
nextIndex.push(session.authSessionId);
|
|
12864
|
-
}
|
|
12865
|
-
await stateAdapter.set(
|
|
12866
|
-
sessionIndexKey(session.userId, session.provider),
|
|
12867
|
-
JSON.stringify(nextIndex),
|
|
12868
|
-
ttlMs2
|
|
12869
|
-
);
|
|
12870
|
-
}
|
|
12871
|
-
async function patchMcpAuthSession(authSessionId, patch) {
|
|
12872
|
-
const current = await getMcpAuthSession(authSessionId);
|
|
12873
|
-
if (!current) {
|
|
12874
|
-
throw new Error(`Unknown MCP auth session: ${authSessionId}`);
|
|
12875
|
-
}
|
|
12876
|
-
const next = {
|
|
12877
|
-
...current,
|
|
12878
|
-
...patch,
|
|
12879
|
-
schemaVersion: 2,
|
|
12880
|
-
authSessionId: current.authSessionId,
|
|
12881
|
-
provider: current.provider,
|
|
12882
|
-
userId: current.userId,
|
|
12883
|
-
conversationId: current.conversationId,
|
|
12884
|
-
...current.destination ? { destination: current.destination } : {},
|
|
12885
|
-
sessionId: current.sessionId,
|
|
12886
|
-
userMessage: current.userMessage,
|
|
12887
|
-
createdAtMs: current.createdAtMs,
|
|
12888
|
-
updatedAtMs: Date.now()
|
|
12889
|
-
};
|
|
12890
|
-
await putMcpAuthSession(next);
|
|
12891
|
-
return next;
|
|
12892
|
-
}
|
|
12893
|
-
async function deleteMcpAuthSession(authSessionId) {
|
|
12894
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12895
|
-
const current = parseMcpAuthSession(
|
|
12896
|
-
await stateAdapter.get(sessionKey(authSessionId))
|
|
12897
|
-
);
|
|
12898
|
-
await stateAdapter.delete(sessionKey(authSessionId));
|
|
12899
|
-
if (!current) {
|
|
12900
|
-
return;
|
|
12901
|
-
}
|
|
12902
|
-
const nextIndex = parseSessionIndex(
|
|
12903
|
-
await stateAdapter.get(sessionIndexKey(current.userId, current.provider))
|
|
12904
|
-
).filter((id) => id !== authSessionId);
|
|
12905
|
-
if (nextIndex.length > 0) {
|
|
12906
|
-
await stateAdapter.set(
|
|
12907
|
-
sessionIndexKey(current.userId, current.provider),
|
|
12908
|
-
JSON.stringify(nextIndex),
|
|
12909
|
-
MCP_AUTH_SESSION_TTL_MS
|
|
12910
|
-
);
|
|
12911
|
-
return;
|
|
12912
|
-
}
|
|
12913
|
-
await stateAdapter.delete(sessionIndexKey(current.userId, current.provider));
|
|
12914
|
-
}
|
|
12915
|
-
async function deleteMcpAuthSessionsForUserProvider(userId, provider) {
|
|
12916
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12917
|
-
const indexKey = sessionIndexKey(userId, provider);
|
|
12918
|
-
const authSessionIds = parseSessionIndex(await stateAdapter.get(indexKey));
|
|
12919
|
-
for (const authSessionId of authSessionIds) {
|
|
12920
|
-
await stateAdapter.delete(sessionKey(authSessionId));
|
|
12921
|
-
}
|
|
12922
|
-
await stateAdapter.delete(indexKey);
|
|
12923
|
-
}
|
|
12924
|
-
async function getMcpStoredOAuthCredentials(userId, provider) {
|
|
12925
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12926
|
-
return parseStoredCredentials(
|
|
12927
|
-
await stateAdapter.get(credentialsKey(userId, provider))
|
|
12928
|
-
);
|
|
12929
|
-
}
|
|
12930
|
-
async function putMcpStoredOAuthCredentials(userId, provider, value, ttlMs2 = MCP_AUTH_CREDENTIALS_TTL_MS) {
|
|
12931
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12932
|
-
await stateAdapter.set(
|
|
12933
|
-
credentialsKey(userId, provider),
|
|
12934
|
-
JSON.stringify(value),
|
|
12935
|
-
ttlMs2
|
|
12936
|
-
);
|
|
12937
|
-
}
|
|
12938
|
-
async function deleteMcpStoredOAuthCredentials(userId, provider) {
|
|
12939
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12940
|
-
await stateAdapter.delete(credentialsKey(userId, provider));
|
|
12941
|
-
}
|
|
12942
|
-
async function getMcpServerSessionId(userId, provider) {
|
|
12943
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12944
|
-
return parseServerSession(
|
|
12945
|
-
await stateAdapter.get(serverSessionKey(userId, provider))
|
|
12946
|
-
)?.sessionId;
|
|
12947
|
-
}
|
|
12948
|
-
async function putMcpServerSessionId(userId, provider, sessionId, ttlMs2 = MCP_SERVER_SESSION_TTL_MS) {
|
|
12949
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12950
|
-
await stateAdapter.set(
|
|
12951
|
-
serverSessionKey(userId, provider),
|
|
12952
|
-
JSON.stringify({
|
|
12953
|
-
sessionId,
|
|
12954
|
-
updatedAtMs: Date.now()
|
|
12955
|
-
}),
|
|
12956
|
-
ttlMs2
|
|
12957
|
-
);
|
|
12958
|
-
}
|
|
12959
|
-
async function deleteMcpServerSessionId(userId, provider) {
|
|
12960
|
-
const stateAdapter = await getConnectedStateAdapter();
|
|
12961
|
-
await stateAdapter.delete(serverSessionKey(userId, provider));
|
|
12962
|
-
}
|
|
12963
|
-
|
|
12964
|
-
// src/chat/mcp/oauth-provider.ts
|
|
12965
|
-
function createClientMetadata(callbackUrl) {
|
|
12966
|
-
return {
|
|
12967
|
-
client_name: "Junior MCP Client",
|
|
12968
|
-
redirect_uris: [callbackUrl],
|
|
12969
|
-
grant_types: ["authorization_code", "refresh_token"],
|
|
12970
|
-
response_types: ["code"],
|
|
12971
|
-
token_endpoint_auth_method: "none"
|
|
12972
|
-
};
|
|
12973
|
-
}
|
|
12974
|
-
function clientAllowsRedirectUri(clientInformation, callbackUrl) {
|
|
12975
|
-
const redirectUris = clientInformation.redirect_uris;
|
|
12976
|
-
if (!Array.isArray(redirectUris)) {
|
|
12977
|
-
return true;
|
|
12978
|
-
}
|
|
12979
|
-
return redirectUris.includes(callbackUrl);
|
|
12980
|
-
}
|
|
12981
|
-
var StateBackedMcpOAuthClientProvider = class {
|
|
12982
|
-
constructor(authSessionId, callbackUrl, sessionContext, runCredentialMutation) {
|
|
12983
|
-
this.authSessionId = authSessionId;
|
|
12984
|
-
this.callbackUrl = callbackUrl;
|
|
12985
|
-
this.sessionContext = sessionContext;
|
|
12986
|
-
this.runCredentialMutation = runCredentialMutation;
|
|
12987
|
-
this.clientMetadata = createClientMetadata(callbackUrl);
|
|
12988
|
-
}
|
|
12989
|
-
authSessionId;
|
|
12990
|
-
callbackUrl;
|
|
12991
|
-
sessionContext;
|
|
12992
|
-
runCredentialMutation;
|
|
12993
|
-
clientMetadata;
|
|
12994
|
-
get redirectUrl() {
|
|
12995
|
-
return this.callbackUrl;
|
|
12996
|
-
}
|
|
12997
|
-
async state() {
|
|
12998
|
-
return this.authSessionId;
|
|
12999
|
-
}
|
|
13000
|
-
async clientInformation() {
|
|
13001
|
-
const session = await this.getCredentialContext();
|
|
13002
|
-
const credentials = await getMcpStoredOAuthCredentials(
|
|
13003
|
-
session.userId,
|
|
13004
|
-
session.provider
|
|
13005
|
-
);
|
|
13006
|
-
const clientInformation = credentials?.clientInformation;
|
|
13007
|
-
if (!clientInformation) {
|
|
13008
|
-
return void 0;
|
|
13009
|
-
}
|
|
13010
|
-
if (clientAllowsRedirectUri(clientInformation, this.callbackUrl)) {
|
|
13011
|
-
return clientInformation;
|
|
13012
|
-
}
|
|
13013
|
-
return void 0;
|
|
13014
|
-
}
|
|
13015
|
-
async saveClientInformation(clientInformation) {
|
|
13016
|
-
await this.mutateCredentials(async () => {
|
|
13017
|
-
const session = await this.getCredentialContext();
|
|
13018
|
-
const credentials = await getMcpStoredOAuthCredentials(
|
|
13019
|
-
session.userId,
|
|
13020
|
-
session.provider
|
|
13021
|
-
) ?? {};
|
|
13022
|
-
await putMcpStoredOAuthCredentials(session.userId, session.provider, {
|
|
13023
|
-
...credentials,
|
|
13024
|
-
clientInformation
|
|
13025
|
-
});
|
|
13026
|
-
});
|
|
13027
|
-
}
|
|
13028
|
-
async tokens() {
|
|
13029
|
-
const session = await this.getCredentialContext();
|
|
13030
|
-
const credentials = await getMcpStoredOAuthCredentials(
|
|
13031
|
-
session.userId,
|
|
13032
|
-
session.provider
|
|
13033
|
-
);
|
|
13034
|
-
return credentials?.tokens;
|
|
13035
|
-
}
|
|
13036
|
-
async saveTokens(tokens) {
|
|
13037
|
-
await this.mutateCredentials(async () => {
|
|
13038
|
-
const session = await this.getCredentialContext();
|
|
13039
|
-
const credentials = await getMcpStoredOAuthCredentials(
|
|
13040
|
-
session.userId,
|
|
13041
|
-
session.provider
|
|
13042
|
-
) ?? {};
|
|
13043
|
-
await putMcpStoredOAuthCredentials(session.userId, session.provider, {
|
|
13044
|
-
...credentials,
|
|
13045
|
-
tokens
|
|
13046
|
-
});
|
|
13047
|
-
});
|
|
13048
|
-
}
|
|
13049
|
-
async redirectToAuthorization(authorizationUrl) {
|
|
13050
|
-
const existing = await getMcpAuthSession(this.authSessionId);
|
|
13051
|
-
if (existing?.authorizationUrl && existing.authorizationUrl !== authorizationUrl.toString()) {
|
|
13052
|
-
throw new Error("MCP OAuth authorization attempt is already initialized");
|
|
13053
|
-
}
|
|
13054
|
-
await this.ensureSession({
|
|
13055
|
-
authorizationUrl: authorizationUrl.toString()
|
|
13056
|
-
});
|
|
13057
|
-
}
|
|
13058
|
-
async saveCodeVerifier(codeVerifier) {
|
|
13059
|
-
const existing = await getMcpAuthSession(this.authSessionId);
|
|
13060
|
-
if (existing?.codeVerifier && existing.codeVerifier !== codeVerifier) {
|
|
13061
|
-
throw new Error("MCP OAuth authorization attempt is already initialized");
|
|
13062
|
-
}
|
|
13063
|
-
await this.ensureSession({ codeVerifier });
|
|
13064
|
-
}
|
|
13065
|
-
async codeVerifier() {
|
|
13066
|
-
const session = await this.requireSession();
|
|
13067
|
-
if (!session.codeVerifier) {
|
|
13068
|
-
throw new Error("Missing MCP OAuth code verifier");
|
|
13069
|
-
}
|
|
13070
|
-
return session.codeVerifier;
|
|
13071
|
-
}
|
|
13072
|
-
async saveDiscoveryState(state) {
|
|
13073
|
-
await this.mutateCredentials(async () => {
|
|
13074
|
-
const session = await this.getCredentialContext();
|
|
13075
|
-
const credentials = await getMcpStoredOAuthCredentials(
|
|
13076
|
-
session.userId,
|
|
13077
|
-
session.provider
|
|
13078
|
-
) ?? {};
|
|
13079
|
-
await putMcpStoredOAuthCredentials(session.userId, session.provider, {
|
|
13080
|
-
...credentials,
|
|
13081
|
-
discoveryState: state
|
|
13082
|
-
});
|
|
13083
|
-
});
|
|
13084
|
-
}
|
|
13085
|
-
async discoveryState() {
|
|
13086
|
-
const session = await this.getCredentialContext();
|
|
13087
|
-
const credentials = await getMcpStoredOAuthCredentials(
|
|
13088
|
-
session.userId,
|
|
13089
|
-
session.provider
|
|
13090
|
-
);
|
|
13091
|
-
return credentials?.discoveryState;
|
|
13092
|
-
}
|
|
13093
|
-
async invalidateCredentials(scope) {
|
|
13094
|
-
await this.mutateCredentials(async () => {
|
|
13095
|
-
const session = await this.getCredentialContext();
|
|
13096
|
-
const credentials = await getMcpStoredOAuthCredentials(
|
|
13097
|
-
session.userId,
|
|
13098
|
-
session.provider
|
|
13099
|
-
) ?? {};
|
|
13100
|
-
await putMcpStoredOAuthCredentials(session.userId, session.provider, {
|
|
13101
|
-
...scope === "tokens" || scope === "all" ? {} : credentials.tokens ? { tokens: credentials.tokens } : {},
|
|
13102
|
-
...scope === "client" || scope === "all" ? {} : credentials.clientInformation ? { clientInformation: credentials.clientInformation } : {},
|
|
13103
|
-
...scope === "discovery" || scope === "all" ? {} : credentials.discoveryState ? { discoveryState: credentials.discoveryState } : {}
|
|
13104
|
-
});
|
|
13105
|
-
});
|
|
13106
|
-
}
|
|
13107
|
-
async getMcpServerSessionId() {
|
|
13108
|
-
const session = await this.getCredentialContext();
|
|
13109
|
-
return await getMcpServerSessionId(session.userId, session.provider);
|
|
13110
|
-
}
|
|
13111
|
-
async saveMcpServerSessionId(sessionId) {
|
|
13112
|
-
await this.mutateCredentials(async () => {
|
|
13113
|
-
const session = await this.getCredentialContext();
|
|
13114
|
-
if (!sessionId) {
|
|
13115
|
-
await deleteMcpServerSessionId(session.userId, session.provider);
|
|
13116
|
-
return;
|
|
13117
|
-
}
|
|
13118
|
-
await putMcpServerSessionId(session.userId, session.provider, sessionId);
|
|
13119
|
-
});
|
|
13120
|
-
}
|
|
13121
|
-
/** Route shared credential writes through callback-owned freshness control. */
|
|
13122
|
-
async mutateCredentials(mutation) {
|
|
13123
|
-
return this.runCredentialMutation ? await this.runCredentialMutation(mutation) : await mutation();
|
|
13124
|
-
}
|
|
13125
|
-
async getCredentialContext() {
|
|
13126
|
-
return this.sessionContext ?? await this.requireSession();
|
|
13127
|
-
}
|
|
13128
|
-
async ensureSession(patch) {
|
|
13129
|
-
const existing = await getMcpAuthSession(this.authSessionId);
|
|
13130
|
-
if (existing) {
|
|
13131
|
-
return await patchMcpAuthSession(this.authSessionId, patch);
|
|
13132
|
-
}
|
|
13133
|
-
if (!this.sessionContext) {
|
|
13134
|
-
throw new Error(`Unknown MCP auth session: ${this.authSessionId}`);
|
|
13135
|
-
}
|
|
13136
|
-
const now = Date.now();
|
|
13137
|
-
const nextSession = {
|
|
13138
|
-
schemaVersion: 2,
|
|
13139
|
-
authSessionId: this.authSessionId,
|
|
13140
|
-
...this.sessionContext,
|
|
13141
|
-
...patch,
|
|
13142
|
-
createdAtMs: now,
|
|
13143
|
-
updatedAtMs: now
|
|
13144
|
-
};
|
|
13145
|
-
await putMcpAuthSession(nextSession);
|
|
13146
|
-
return nextSession;
|
|
13147
|
-
}
|
|
13148
|
-
async requireSession() {
|
|
13149
|
-
const session = await getMcpAuthSession(this.authSessionId);
|
|
13150
|
-
if (!session) {
|
|
13151
|
-
throw new Error(`Unknown MCP auth session: ${this.authSessionId}`);
|
|
13152
|
-
}
|
|
13153
|
-
return session;
|
|
13154
|
-
}
|
|
13155
|
-
};
|
|
13156
|
-
|
|
13157
|
-
// src/chat/mcp/oauth.ts
|
|
13158
|
-
function getMcpOAuthCallbackPath(provider) {
|
|
13159
|
-
return `/api/oauth/callback/mcp/${provider}`;
|
|
13160
|
-
}
|
|
13161
|
-
function requirePluginWithMcp(provider) {
|
|
13162
|
-
const plugin = pluginCatalogRuntime.getDefinition(provider);
|
|
13163
|
-
if (!plugin?.manifest.mcp) {
|
|
13164
|
-
throw new Error(`Plugin "${provider}" does not support MCP`);
|
|
13165
|
-
}
|
|
13166
|
-
return plugin;
|
|
13167
|
-
}
|
|
13168
|
-
async function createMcpOAuthClientProvider(input) {
|
|
13169
|
-
requirePluginWithMcp(input.provider);
|
|
13170
|
-
const baseUrl = resolveBaseUrl();
|
|
13171
|
-
if (!baseUrl) {
|
|
13172
|
-
throw new Error(
|
|
13173
|
-
"Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
13174
|
-
);
|
|
13175
|
-
}
|
|
13176
|
-
const authSessionId = randomUUID3();
|
|
13177
|
-
return new StateBackedMcpOAuthClientProvider(
|
|
13178
|
-
authSessionId,
|
|
13179
|
-
`${baseUrl}${getMcpOAuthCallbackPath(input.provider)}`,
|
|
13180
|
-
{
|
|
13181
|
-
provider: input.provider,
|
|
13182
|
-
userId: input.userId,
|
|
13183
|
-
conversationId: input.conversationId,
|
|
13184
|
-
...input.destination ? { destination: input.destination } : {},
|
|
13185
|
-
...input.source ? { source: input.source } : {},
|
|
13186
|
-
sessionId: input.sessionId,
|
|
13187
|
-
userMessage: input.userMessage,
|
|
13188
|
-
...input.channelId ? { channelId: input.channelId } : {},
|
|
13189
|
-
...input.threadTs ? { threadTs: input.threadTs } : {},
|
|
13190
|
-
...input.toolChannelId ? { toolChannelId: input.toolChannelId } : {},
|
|
13191
|
-
...input.configuration ? { configuration: input.configuration } : {},
|
|
13192
|
-
...input.artifactState ? { artifactState: input.artifactState } : {}
|
|
11597
|
+
toolDef,
|
|
11598
|
+
toolName
|
|
11599
|
+
});
|
|
11600
|
+
} catch (error) {
|
|
11601
|
+
await notifyToolResult({
|
|
11602
|
+
error: error instanceof Error ? error.message : String(error),
|
|
11603
|
+
ok: false,
|
|
11604
|
+
params: executionParams,
|
|
11605
|
+
toolCallId,
|
|
11606
|
+
toolName: executionToolName
|
|
11607
|
+
});
|
|
11608
|
+
if (error instanceof AuthorizationPauseError || error instanceof AuthorizationFlowDisabledError) {
|
|
11609
|
+
throw error;
|
|
11610
|
+
}
|
|
11611
|
+
handleToolExecutionError(
|
|
11612
|
+
error,
|
|
11613
|
+
executionToolName,
|
|
11614
|
+
toolCallId,
|
|
11615
|
+
shouldTrace,
|
|
11616
|
+
spanContext,
|
|
11617
|
+
effectiveConversationPrivacy,
|
|
11618
|
+
setSpanAttributes2
|
|
11619
|
+
);
|
|
11620
|
+
}
|
|
11621
|
+
},
|
|
11622
|
+
{
|
|
11623
|
+
"gen_ai.operation.name": "execute_tool",
|
|
11624
|
+
"gen_ai.tool.name": toolName,
|
|
11625
|
+
"gen_ai.tool.description": toolDef.description,
|
|
11626
|
+
"gen_ai.tool.type": "function",
|
|
11627
|
+
...toolArgumentsMetadata,
|
|
11628
|
+
"gen_ai.tool.call.id": toolCallId,
|
|
11629
|
+
...toolArgumentsAttribute ? { "gen_ai.tool.call.arguments": toolArgumentsAttribute } : {}
|
|
11630
|
+
}
|
|
11631
|
+
);
|
|
13193
11632
|
}
|
|
13194
|
-
);
|
|
13195
|
-
}
|
|
13196
|
-
async function finalizeMcpAuthorization(provider, authSessionId, authorizationCode, runCredentialMutation) {
|
|
13197
|
-
const plugin = requirePluginWithMcp(provider);
|
|
13198
|
-
const mcp = plugin.manifest.mcp;
|
|
13199
|
-
if (!mcp) {
|
|
13200
|
-
throw new Error(`Plugin "${provider}" does not support MCP`);
|
|
13201
|
-
}
|
|
13202
|
-
const session = await getMcpAuthSession(authSessionId);
|
|
13203
|
-
if (!session) {
|
|
13204
|
-
throw new Error(`Unknown MCP auth session: ${authSessionId}`);
|
|
13205
|
-
}
|
|
13206
|
-
if (session.provider !== provider) {
|
|
13207
|
-
throw new Error(
|
|
13208
|
-
`MCP auth session provider mismatch: expected "${provider}", got "${session.provider}"`
|
|
13209
|
-
);
|
|
13210
|
-
}
|
|
13211
|
-
const baseUrl = resolveBaseUrl();
|
|
13212
|
-
if (!baseUrl) {
|
|
13213
|
-
throw new Error(
|
|
13214
|
-
"Cannot determine base URL (set JUNIOR_BASE_URL or deploy to Vercel)"
|
|
13215
|
-
);
|
|
13216
|
-
}
|
|
13217
|
-
const callbackUrl = `${baseUrl}${getMcpOAuthCallbackPath(provider)}`;
|
|
13218
|
-
const authProvider = new StateBackedMcpOAuthClientProvider(
|
|
13219
|
-
authSessionId,
|
|
13220
|
-
callbackUrl,
|
|
13221
|
-
void 0,
|
|
13222
|
-
runCredentialMutation
|
|
13223
|
-
);
|
|
13224
|
-
const requestInit = {};
|
|
13225
|
-
if (mcp.headers && Object.keys(mcp.headers).length > 0) {
|
|
13226
|
-
requestInit.headers = new Headers(mcp.headers);
|
|
13227
|
-
}
|
|
13228
|
-
const transport = new StreamableHTTPClientTransport2(new URL(mcp.url), {
|
|
13229
|
-
...Object.keys(requestInit).length > 0 ? { requestInit } : {},
|
|
13230
|
-
authProvider
|
|
13231
|
-
});
|
|
13232
|
-
try {
|
|
13233
|
-
await transport.finishAuth(authorizationCode);
|
|
13234
|
-
} finally {
|
|
13235
|
-
await transport.close();
|
|
13236
|
-
}
|
|
13237
|
-
const nextSession = await getMcpAuthSession(authSessionId);
|
|
13238
|
-
if (!nextSession) {
|
|
13239
|
-
throw new Error(`Unknown MCP auth session: ${authSessionId}`);
|
|
13240
|
-
}
|
|
13241
|
-
return nextSession;
|
|
11633
|
+
}));
|
|
13242
11634
|
}
|
|
13243
11635
|
|
|
13244
11636
|
// src/chat/services/mcp-auth-orchestration.ts
|
|
@@ -13247,9 +11639,6 @@ var McpAuthorizationPauseError = class extends AuthorizationPauseError {
|
|
|
13247
11639
|
super("mcp", provider, providerDisplayName, disposition, requestText);
|
|
13248
11640
|
}
|
|
13249
11641
|
};
|
|
13250
|
-
function authorizationId2(args) {
|
|
13251
|
-
return `${args.sessionId}:${args.kind}:${args.provider}`;
|
|
13252
|
-
}
|
|
13253
11642
|
function createMcpAuthOrchestration(input) {
|
|
13254
11643
|
let pendingPause;
|
|
13255
11644
|
const authSessionIdsByProvider = /* @__PURE__ */ new Map();
|
|
@@ -13274,7 +11663,8 @@ function createMcpAuthOrchestration(input) {
|
|
|
13274
11663
|
...input.threadTs ? { threadTs: input.threadTs } : {},
|
|
13275
11664
|
...input.toolChannelId ? { toolChannelId: input.toolChannelId } : {},
|
|
13276
11665
|
configuration: input.getConfiguration(),
|
|
13277
|
-
artifactState: input.getArtifactState()
|
|
11666
|
+
artifactState: input.getArtifactState(),
|
|
11667
|
+
createAuthorizationState: input.authorization?.createState
|
|
13278
11668
|
});
|
|
13279
11669
|
authSessionIdsByProvider.set(plugin.manifest.name, provider.authSessionId);
|
|
13280
11670
|
return provider;
|
|
@@ -13334,15 +11724,16 @@ function createMcpAuthOrchestration(input) {
|
|
|
13334
11724
|
);
|
|
13335
11725
|
}
|
|
13336
11726
|
await recordPendingAuth(nextPendingAuth);
|
|
13337
|
-
const
|
|
11727
|
+
const authorizationRequest = {
|
|
11728
|
+
authorizationUrl: authSession.authorizationUrl,
|
|
11729
|
+
label: `Click here to link your ${providerLabel} MCP access`,
|
|
11730
|
+
completionText: "Once you've authorized, Junior will continue automatically."
|
|
11731
|
+
};
|
|
11732
|
+
const delivery = await deliverOAuthAuthorization(authorizationRequest, {
|
|
11733
|
+
authorization: input.authorization,
|
|
13338
11734
|
channelId: authSession.channelId,
|
|
13339
11735
|
threadTs: authSession.threadTs,
|
|
13340
|
-
userId: authSession.userId
|
|
13341
|
-
text: formatOAuthAuthorizationMessage({
|
|
13342
|
-
authorizationUrl: authSession.authorizationUrl,
|
|
13343
|
-
label: `Click here to link your ${providerLabel} MCP access`,
|
|
13344
|
-
completionText: "Once you've authorized, this thread will continue automatically."
|
|
13345
|
-
})
|
|
11736
|
+
userId: authSession.userId
|
|
13346
11737
|
});
|
|
13347
11738
|
if (!delivery) {
|
|
13348
11739
|
await deleteMcpAuthSession(authSessionId);
|
|
@@ -13365,7 +11756,7 @@ function createMcpAuthOrchestration(input) {
|
|
|
13365
11756
|
kind: "mcp",
|
|
13366
11757
|
provider,
|
|
13367
11758
|
actorId,
|
|
13368
|
-
authorizationId:
|
|
11759
|
+
authorizationId: authorizationId({
|
|
13369
11760
|
kind: "mcp",
|
|
13370
11761
|
provider,
|
|
13371
11762
|
sessionId
|
|
@@ -13389,197 +11780,7 @@ function createMcpAuthOrchestration(input) {
|
|
|
13389
11780
|
}
|
|
13390
11781
|
|
|
13391
11782
|
// src/chat/egress/plugin.ts
|
|
13392
|
-
import { randomUUID as
|
|
13393
|
-
|
|
13394
|
-
// src/chat/plugins/credential-hooks.ts
|
|
13395
|
-
import {
|
|
13396
|
-
pluginAuthorizationSchema as pluginAuthorizationSchema2,
|
|
13397
|
-
pluginCredentialResultSchema,
|
|
13398
|
-
pluginGrantSchema as pluginGrantSchema2,
|
|
13399
|
-
pluginProviderAccountSchema as pluginProviderAccountSchema2
|
|
13400
|
-
} from "@sentry/junior-plugin-api";
|
|
13401
|
-
function parseSchema(schema, value, message) {
|
|
13402
|
-
const result = schema.safeParse(value);
|
|
13403
|
-
if (!result.success) {
|
|
13404
|
-
throw new Error(message);
|
|
13405
|
-
}
|
|
13406
|
-
return result.data;
|
|
13407
|
-
}
|
|
13408
|
-
function parseAuthorization(value, pluginName) {
|
|
13409
|
-
if (value === void 0) {
|
|
13410
|
-
return void 0;
|
|
13411
|
-
}
|
|
13412
|
-
const authorization = parseSchema(
|
|
13413
|
-
pluginAuthorizationSchema2,
|
|
13414
|
-
value,
|
|
13415
|
-
`Plugin "${pluginName}" grant authorization is invalid`
|
|
13416
|
-
);
|
|
13417
|
-
if (authorization.provider !== pluginName) {
|
|
13418
|
-
throw new Error(
|
|
13419
|
-
`Plugin "${pluginName}" grant authorization provider must match the issuing plugin`
|
|
13420
|
-
);
|
|
13421
|
-
}
|
|
13422
|
-
return authorization;
|
|
13423
|
-
}
|
|
13424
|
-
function parseGrant(value, pluginName) {
|
|
13425
|
-
return parseSchema(
|
|
13426
|
-
pluginGrantSchema2,
|
|
13427
|
-
value,
|
|
13428
|
-
`Plugin "${pluginName}" grantForEgress returned an invalid grant`
|
|
13429
|
-
);
|
|
13430
|
-
}
|
|
13431
|
-
function pluginFor(provider) {
|
|
13432
|
-
return getPlugins().find((candidate) => candidate.manifest.name === provider);
|
|
13433
|
-
}
|
|
13434
|
-
function basePluginContext(plugin) {
|
|
13435
|
-
const pluginName = plugin.manifest.name;
|
|
13436
|
-
return {
|
|
13437
|
-
plugin: { name: pluginName },
|
|
13438
|
-
log: createPluginLogger(pluginName),
|
|
13439
|
-
db: getDb()
|
|
13440
|
-
};
|
|
13441
|
-
}
|
|
13442
|
-
function parseCredentialResult(value, pluginName) {
|
|
13443
|
-
const result = parseSchema(
|
|
13444
|
-
pluginCredentialResultSchema,
|
|
13445
|
-
value,
|
|
13446
|
-
`Plugin "${pluginName}" issueCredential result is invalid`
|
|
13447
|
-
);
|
|
13448
|
-
if (result.type === "lease") {
|
|
13449
|
-
parseAuthorization(result.lease.authorization, pluginName);
|
|
13450
|
-
return result;
|
|
13451
|
-
}
|
|
13452
|
-
if (result.type === "unavailable") {
|
|
13453
|
-
return result;
|
|
13454
|
-
}
|
|
13455
|
-
parseAuthorization(result.authorization, pluginName);
|
|
13456
|
-
return result;
|
|
13457
|
-
}
|
|
13458
|
-
async function selectPluginGrant(input) {
|
|
13459
|
-
const plugin = pluginFor(input.provider);
|
|
13460
|
-
const hook = plugin?.hooks?.grantForEgress;
|
|
13461
|
-
if (!plugin || !hook) {
|
|
13462
|
-
return void 0;
|
|
13463
|
-
}
|
|
13464
|
-
const result = await hook({
|
|
13465
|
-
...basePluginContext(plugin),
|
|
13466
|
-
request: {
|
|
13467
|
-
...input.bodyText !== void 0 ? { bodyText: input.bodyText } : {},
|
|
13468
|
-
method: input.method,
|
|
13469
|
-
...input.operation ? { operation: input.operation } : {},
|
|
13470
|
-
url: input.upstreamUrl.toString()
|
|
13471
|
-
}
|
|
13472
|
-
});
|
|
13473
|
-
return result === void 0 ? void 0 : parseGrant(result, plugin.manifest.name);
|
|
13474
|
-
}
|
|
13475
|
-
async function onPluginEgressResponse(input) {
|
|
13476
|
-
const plugin = pluginFor(input.provider);
|
|
13477
|
-
const hook = plugin?.hooks?.onEgressResponse;
|
|
13478
|
-
if (!plugin || !hook) {
|
|
13479
|
-
return {};
|
|
13480
|
-
}
|
|
13481
|
-
let permissionDenied;
|
|
13482
|
-
await hook({
|
|
13483
|
-
...basePluginContext(plugin),
|
|
13484
|
-
grant: input.grant,
|
|
13485
|
-
permissionDenied(message) {
|
|
13486
|
-
const trimmed = message.trim();
|
|
13487
|
-
if (!trimmed) {
|
|
13488
|
-
throw new Error(
|
|
13489
|
-
`Plugin "${plugin.manifest.name}" onEgressResponse permissionDenied message is empty`
|
|
13490
|
-
);
|
|
13491
|
-
}
|
|
13492
|
-
permissionDenied = { message: trimmed };
|
|
13493
|
-
},
|
|
13494
|
-
request: {
|
|
13495
|
-
method: input.method,
|
|
13496
|
-
...input.operation ? { operation: input.operation } : {},
|
|
13497
|
-
url: input.upstreamUrl.toString()
|
|
13498
|
-
},
|
|
13499
|
-
response: input.response
|
|
13500
|
-
});
|
|
13501
|
-
return permissionDenied ? { permissionDenied } : {};
|
|
13502
|
-
}
|
|
13503
|
-
function hasEgressCredentialHooks(provider) {
|
|
13504
|
-
const hooks = pluginFor(provider)?.hooks;
|
|
13505
|
-
return Boolean(hooks?.grantForEgress || hooks?.issueCredential);
|
|
13506
|
-
}
|
|
13507
|
-
async function resolvePluginOAuthAccount(input) {
|
|
13508
|
-
const plugin = pluginFor(input.provider);
|
|
13509
|
-
const hook = plugin?.hooks?.resolveOAuthAccount;
|
|
13510
|
-
if (!plugin || !hook) {
|
|
13511
|
-
return void 0;
|
|
13512
|
-
}
|
|
13513
|
-
const account = await hook({
|
|
13514
|
-
...basePluginContext(plugin),
|
|
13515
|
-
tokens: input.tokens
|
|
13516
|
-
});
|
|
13517
|
-
return account === void 0 ? void 0 : parseSchema(
|
|
13518
|
-
pluginProviderAccountSchema2,
|
|
13519
|
-
account,
|
|
13520
|
-
`Plugin "${plugin.manifest.name}" resolveOAuthAccount returned an invalid account`
|
|
13521
|
-
);
|
|
13522
|
-
}
|
|
13523
|
-
async function issuePluginCredential(input) {
|
|
13524
|
-
const plugin = pluginFor(input.provider);
|
|
13525
|
-
const hook = plugin?.hooks?.issueCredential;
|
|
13526
|
-
if (!plugin || !hook) {
|
|
13527
|
-
throw new Error(`Plugin "${input.provider}" has no issueCredential hook`);
|
|
13528
|
-
}
|
|
13529
|
-
const currentUserId = "type" in input.actor ? input.actor.userId : void 0;
|
|
13530
|
-
const credentialSubjectUserId = input.credentialSubject?.userId;
|
|
13531
|
-
const result = await hook({
|
|
13532
|
-
...basePluginContext(plugin),
|
|
13533
|
-
actor: input.actor,
|
|
13534
|
-
grant: input.grant,
|
|
13535
|
-
...input.credentialSubject ? { credentialSubject: input.credentialSubject } : {},
|
|
13536
|
-
tokens: {
|
|
13537
|
-
...currentUserId ? {
|
|
13538
|
-
currentUser: {
|
|
13539
|
-
userId: currentUserId,
|
|
13540
|
-
get: async () => await input.userTokenStore.get(
|
|
13541
|
-
currentUserId,
|
|
13542
|
-
plugin.manifest.name
|
|
13543
|
-
),
|
|
13544
|
-
set: async (tokens) => {
|
|
13545
|
-
await input.userTokenStore.set(
|
|
13546
|
-
currentUserId,
|
|
13547
|
-
plugin.manifest.name,
|
|
13548
|
-
tokens
|
|
13549
|
-
);
|
|
13550
|
-
},
|
|
13551
|
-
withRefresh: async (callback) => await input.userTokenStore.withRefresh(
|
|
13552
|
-
currentUserId,
|
|
13553
|
-
plugin.manifest.name,
|
|
13554
|
-
callback
|
|
13555
|
-
)
|
|
13556
|
-
}
|
|
13557
|
-
} : {},
|
|
13558
|
-
...credentialSubjectUserId ? {
|
|
13559
|
-
credentialSubject: {
|
|
13560
|
-
userId: credentialSubjectUserId,
|
|
13561
|
-
get: async () => await input.userTokenStore.get(
|
|
13562
|
-
credentialSubjectUserId,
|
|
13563
|
-
plugin.manifest.name
|
|
13564
|
-
),
|
|
13565
|
-
set: async (tokens) => {
|
|
13566
|
-
await input.userTokenStore.set(
|
|
13567
|
-
credentialSubjectUserId,
|
|
13568
|
-
plugin.manifest.name,
|
|
13569
|
-
tokens
|
|
13570
|
-
);
|
|
13571
|
-
},
|
|
13572
|
-
withRefresh: async (callback) => await input.userTokenStore.withRefresh(
|
|
13573
|
-
credentialSubjectUserId,
|
|
13574
|
-
plugin.manifest.name,
|
|
13575
|
-
callback
|
|
13576
|
-
)
|
|
13577
|
-
}
|
|
13578
|
-
} : {}
|
|
13579
|
-
}
|
|
13580
|
-
});
|
|
13581
|
-
return parseCredentialResult(result, plugin.manifest.name);
|
|
13582
|
-
}
|
|
11783
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
13583
11784
|
|
|
13584
11785
|
// src/chat/sandbox/egress/credentials.ts
|
|
13585
11786
|
var HTTP_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
@@ -14352,8 +12553,8 @@ async function executeCredentialedEgressRequest(input) {
|
|
|
14352
12553
|
function credentialContextForPluginEgress(credentialContext) {
|
|
14353
12554
|
return {
|
|
14354
12555
|
credentials: credentialContext,
|
|
14355
|
-
contextId:
|
|
14356
|
-
egressId: `plugin-egress:${
|
|
12556
|
+
contextId: randomUUID3(),
|
|
12557
|
+
egressId: `plugin-egress:${randomUUID3()}`,
|
|
14357
12558
|
expiresAtMs: Date.now() + 30 * 60 * 1e3
|
|
14358
12559
|
};
|
|
14359
12560
|
}
|
|
@@ -14424,6 +12625,7 @@ async function wireAgentTools(args) {
|
|
|
14424
12625
|
skills: args.availableSkills,
|
|
14425
12626
|
traceContext: args.spanContext,
|
|
14426
12627
|
tracePropagation: args.policy.sandboxTracePropagation,
|
|
12628
|
+
egressSignals: args.policy.sandboxEgressSignals,
|
|
14427
12629
|
credentialEgress: args.routing.credentialContext,
|
|
14428
12630
|
actor: args.currentActor,
|
|
14429
12631
|
channelConfiguration: args.policy.channelConfiguration,
|
|
@@ -14454,7 +12656,8 @@ async function wireAgentTools(args) {
|
|
|
14454
12656
|
args.artifactStatePatch
|
|
14455
12657
|
),
|
|
14456
12658
|
recordPendingAuth: args.durability.recordPendingAuth,
|
|
14457
|
-
authorizationFlowMode: args.policy.authorizationFlowMode
|
|
12659
|
+
authorizationFlowMode: args.policy.authorizationFlowMode,
|
|
12660
|
+
authorization: args.authorization
|
|
14458
12661
|
});
|
|
14459
12662
|
const pluginAuth = createPluginAuthOrchestration({
|
|
14460
12663
|
abortAgent: args.abortAgent,
|
|
@@ -14469,7 +12672,8 @@ async function wireAgentTools(args) {
|
|
|
14469
12672
|
pendingAuth: args.state.pendingAuth,
|
|
14470
12673
|
recordPendingAuth: args.durability.recordPendingAuth,
|
|
14471
12674
|
authorizationFlowMode: args.policy.authorizationFlowMode,
|
|
14472
|
-
userTokenStore
|
|
12675
|
+
userTokenStore,
|
|
12676
|
+
authorization: args.authorization
|
|
14473
12677
|
});
|
|
14474
12678
|
const mcpToolManager = new McpToolManager(
|
|
14475
12679
|
pluginCatalogRuntime.getMcpProviders(),
|
|
@@ -14860,16 +13064,24 @@ function createResumeState(args) {
|
|
|
14860
13064
|
}
|
|
14861
13065
|
|
|
14862
13066
|
// src/chat/services/context-compaction.ts
|
|
14863
|
-
import {
|
|
14864
|
-
estimateContextTokens,
|
|
14865
|
-
estimateTokens
|
|
14866
|
-
} from "@earendil-works/pi-agent-core";
|
|
14867
13067
|
var RETAINED_USER_MESSAGE_TOKENS = 2e4;
|
|
14868
13068
|
var MAX_SUMMARY_INPUT_CHARS = 8e4;
|
|
14869
13069
|
var MAX_VISIBLE_CONTEXT_CHARS = 2e4;
|
|
14870
13070
|
var MAX_SUMMARY_CHARS = 6e3;
|
|
14871
13071
|
var MAX_RENDERED_MESSAGE_CHARS = 4e3;
|
|
14872
13072
|
var OMITTED_OLDER_CONTEXT_NOTICE = "[older context omitted]";
|
|
13073
|
+
var ContextInputLimitExceededError = class extends Error {
|
|
13074
|
+
constructor(estimatedTokens, inputLimitTokens) {
|
|
13075
|
+
super(
|
|
13076
|
+
`Agent context is ${estimatedTokens} estimated input tokens, above the ${inputLimitTokens} token limit`
|
|
13077
|
+
);
|
|
13078
|
+
this.estimatedTokens = estimatedTokens;
|
|
13079
|
+
this.inputLimitTokens = inputLimitTokens;
|
|
13080
|
+
this.name = "ContextInputLimitExceededError";
|
|
13081
|
+
}
|
|
13082
|
+
estimatedTokens;
|
|
13083
|
+
inputLimitTokens;
|
|
13084
|
+
};
|
|
14873
13085
|
function textPart(value) {
|
|
14874
13086
|
if (value && typeof value === "object" && value.type === "text" && typeof value.text === "string") {
|
|
14875
13087
|
return value.text;
|
|
@@ -15030,13 +13242,10 @@ async function summarizeContext(args, deps) {
|
|
|
15030
13242
|
return summary.slice(0, MAX_SUMMARY_CHARS);
|
|
15031
13243
|
}
|
|
15032
13244
|
function estimateHistoryTokens(messages) {
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
const structuralEstimate = stripped.reduce(
|
|
15036
|
-
(total, message) => total + estimateTokens(message),
|
|
13245
|
+
return messages.reduce(
|
|
13246
|
+
(total, message) => total + estimateTextTokens(JSON.stringify(message) ?? ""),
|
|
15037
13247
|
0
|
|
15038
13248
|
);
|
|
15039
|
-
return Math.max(usageEstimate, structuralEstimate);
|
|
15040
13249
|
}
|
|
15041
13250
|
function buildReplacementProvenance(args) {
|
|
15042
13251
|
return [
|
|
@@ -15060,11 +13269,11 @@ async function maybeCompactWithDeps(args, deps) {
|
|
|
15060
13269
|
if ("reason" in source) {
|
|
15061
13270
|
return { compacted: false, reason: source.reason };
|
|
15062
13271
|
}
|
|
15063
|
-
const triggerTokens = deps.autoCompactionTriggerTokens ?? getAgentContextCompactionTriggerTokens();
|
|
13272
|
+
const triggerTokens = deps.autoCompactionTriggerTokens ?? getAgentContextCompactionTriggerTokens(args.modelId);
|
|
15064
13273
|
if (source.estimatedTokens <= triggerTokens) {
|
|
15065
13274
|
return { compacted: false, reason: "below_threshold" };
|
|
15066
13275
|
}
|
|
15067
|
-
args.onCompactionStart?.();
|
|
13276
|
+
await args.onCompactionStart?.();
|
|
15068
13277
|
let summary;
|
|
15069
13278
|
try {
|
|
15070
13279
|
summary = await summarizeContext(
|
|
@@ -15095,7 +13304,8 @@ async function maybeCompactWithDeps(args, deps) {
|
|
|
15095
13304
|
}
|
|
15096
13305
|
return await writeCompactedThreadContext(args, source.messages, summary, {
|
|
15097
13306
|
estimatedTokens: source.estimatedTokens,
|
|
15098
|
-
triggerTokens
|
|
13307
|
+
triggerTokens,
|
|
13308
|
+
inputLimitTokens: getAgentContextInputLimitTokens(args.modelId)
|
|
15099
13309
|
});
|
|
15100
13310
|
}
|
|
15101
13311
|
async function writeCompactedThreadContext(args, sourceMessages, summary, context) {
|
|
@@ -15111,6 +13321,7 @@ async function writeCompactedThreadContext(args, sourceMessages, summary, contex
|
|
|
15111
13321
|
userMessage(`${COMPACTION_SUMMARY_PREFIX}
|
|
15112
13322
|
${summary}`)
|
|
15113
13323
|
];
|
|
13324
|
+
const replacementInputTokens = estimateHistoryTokens(replacement);
|
|
15114
13325
|
const replacementProvenance = buildReplacementProvenance({
|
|
15115
13326
|
retained,
|
|
15116
13327
|
sourceProvenance: sourceProjection.provenance
|
|
@@ -15121,6 +13332,17 @@ ${summary}`)
|
|
|
15121
13332
|
type: "compaction",
|
|
15122
13333
|
modelProfile: sourceProjection.modelProfile,
|
|
15123
13334
|
modelId: modelIdForProfile(botConfig, sourceProjection.modelProfile),
|
|
13335
|
+
details: {
|
|
13336
|
+
reason: "capacity",
|
|
13337
|
+
estimatedInputTokens: context.estimatedTokens,
|
|
13338
|
+
replacementInputTokens,
|
|
13339
|
+
triggerTokens: context.triggerTokens ?? getAgentContextCompactionTriggerTokens(args.modelId),
|
|
13340
|
+
inputLimitTokens: context.inputLimitTokens,
|
|
13341
|
+
inputMessageCount: sourceMessages.length,
|
|
13342
|
+
retainedMessageCount: replacement.length - 1,
|
|
13343
|
+
summaryChars: summary.length
|
|
13344
|
+
},
|
|
13345
|
+
summary,
|
|
15124
13346
|
replacementHistory: replacement.map((message, index) => {
|
|
15125
13347
|
const sourceEventSeq = index < retained.length ? sourceProjection.seqs[retained[index].sourceIndex] : void 0;
|
|
15126
13348
|
return {
|
|
@@ -15154,8 +13376,9 @@ async function compactContextForHandoff(args, deps) {
|
|
|
15154
13376
|
if (!runtimeMessage) {
|
|
15155
13377
|
throw new Error("Handoff requires the current runtime turn context");
|
|
15156
13378
|
}
|
|
13379
|
+
const generatedSummary = await summarizeContext(args, deps);
|
|
15157
13380
|
const summary = `${MODEL_HANDOFF_SUMMARY_PREFIX}
|
|
15158
|
-
${
|
|
13381
|
+
${generatedSummary}`;
|
|
15159
13382
|
const message = {
|
|
15160
13383
|
...runtimeMessage,
|
|
15161
13384
|
content: [
|
|
@@ -15174,6 +13397,7 @@ ${await summarizeContext(args, deps)}`;
|
|
|
15174
13397
|
modelId: args.target.modelId,
|
|
15175
13398
|
...args.target.reasoningLevel ? { reasoningLevel: args.target.reasoningLevel } : {},
|
|
15176
13399
|
...args.triggeringToolCallId ? { triggeringToolCallId: args.triggeringToolCallId } : {},
|
|
13400
|
+
summary: generatedSummary,
|
|
15177
13401
|
replacementHistory: replacementMessages.map((replacementMessage) => ({
|
|
15178
13402
|
message: replacementMessage,
|
|
15179
13403
|
provenance: contextProvenance
|
|
@@ -15182,6 +13406,115 @@ ${await summarizeContext(args, deps)}`;
|
|
|
15182
13406
|
});
|
|
15183
13407
|
return messages;
|
|
15184
13408
|
}
|
|
13409
|
+
async function compactActiveContextIfNeeded(args, deps) {
|
|
13410
|
+
const pendingMessages = args.pendingMessages ?? [];
|
|
13411
|
+
const source = loadCompactionSource([
|
|
13412
|
+
...args.piMessages,
|
|
13413
|
+
...pendingMessages.map((entry) => entry.message)
|
|
13414
|
+
]);
|
|
13415
|
+
if ("reason" in source) {
|
|
13416
|
+
return { compacted: false, reason: source.reason };
|
|
13417
|
+
}
|
|
13418
|
+
const triggerTokens = getAgentContextCompactionTriggerTokens(args.modelId);
|
|
13419
|
+
if (source.estimatedTokens <= triggerTokens) {
|
|
13420
|
+
return { compacted: false, reason: "below_threshold" };
|
|
13421
|
+
}
|
|
13422
|
+
await args.onCompactionStart?.();
|
|
13423
|
+
const inputLimitTokens = getAgentContextInputLimitTokens(args.modelId);
|
|
13424
|
+
let summary;
|
|
13425
|
+
try {
|
|
13426
|
+
summary = await summarizeContext(
|
|
13427
|
+
{
|
|
13428
|
+
...args,
|
|
13429
|
+
piMessages: args.piMessages
|
|
13430
|
+
},
|
|
13431
|
+
deps
|
|
13432
|
+
);
|
|
13433
|
+
} catch (error) {
|
|
13434
|
+
if (source.estimatedTokens >= inputLimitTokens) {
|
|
13435
|
+
throw new ContextInputLimitExceededError(
|
|
13436
|
+
source.estimatedTokens,
|
|
13437
|
+
inputLimitTokens
|
|
13438
|
+
);
|
|
13439
|
+
}
|
|
13440
|
+
logWarn(
|
|
13441
|
+
"active_context_compaction_summary_failed",
|
|
13442
|
+
{
|
|
13443
|
+
messageConversationId: args.metadata?.threadId,
|
|
13444
|
+
userId: args.metadata?.actorId,
|
|
13445
|
+
destinationName: args.metadata?.channelId,
|
|
13446
|
+
runId: args.metadata?.runId,
|
|
13447
|
+
assistantUserName: botConfig.userName,
|
|
13448
|
+
modelId: args.modelId
|
|
13449
|
+
},
|
|
13450
|
+
{
|
|
13451
|
+
"app.context_input_limit_tokens": inputLimitTokens,
|
|
13452
|
+
"app.context_tokens_estimated": source.estimatedTokens,
|
|
13453
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
13454
|
+
},
|
|
13455
|
+
"Active context compaction failed below the hard input limit"
|
|
13456
|
+
);
|
|
13457
|
+
return { compacted: false, reason: "summary_failed" };
|
|
13458
|
+
}
|
|
13459
|
+
const summaryText = `${COMPACTION_SUMMARY_PREFIX}
|
|
13460
|
+
${summary}`;
|
|
13461
|
+
const runtimeMessage = retainRuntimeTurnContext(args.piMessages).at(-1);
|
|
13462
|
+
const summaryMessages = runtimeMessage ? [
|
|
13463
|
+
{
|
|
13464
|
+
...runtimeMessage,
|
|
13465
|
+
content: [
|
|
13466
|
+
...runtimeMessage.content,
|
|
13467
|
+
{ type: "text", text: renderCurrentInstruction(summaryText) }
|
|
13468
|
+
]
|
|
13469
|
+
}
|
|
13470
|
+
] : [userMessage(renderCurrentInstruction(summaryText))];
|
|
13471
|
+
const messages = [
|
|
13472
|
+
...summaryMessages,
|
|
13473
|
+
...pendingMessages.map((entry) => entry.message)
|
|
13474
|
+
];
|
|
13475
|
+
const replacementInputTokens = estimateHistoryTokens(messages);
|
|
13476
|
+
if (replacementInputTokens >= inputLimitTokens) {
|
|
13477
|
+
throw new ContextInputLimitExceededError(
|
|
13478
|
+
replacementInputTokens,
|
|
13479
|
+
inputLimitTokens
|
|
13480
|
+
);
|
|
13481
|
+
}
|
|
13482
|
+
const replacementMessages = stripRuntimeTurnContext(messages);
|
|
13483
|
+
args.signal?.throwIfAborted();
|
|
13484
|
+
await getConversationEventStore().replaceHistory(args.conversationId, {
|
|
13485
|
+
createdAtMs: Date.now(),
|
|
13486
|
+
data: {
|
|
13487
|
+
type: "compaction",
|
|
13488
|
+
modelProfile: args.modelProfile,
|
|
13489
|
+
modelId: args.modelId,
|
|
13490
|
+
details: {
|
|
13491
|
+
reason: "capacity",
|
|
13492
|
+
estimatedInputTokens: source.estimatedTokens,
|
|
13493
|
+
replacementInputTokens,
|
|
13494
|
+
triggerTokens,
|
|
13495
|
+
inputLimitTokens,
|
|
13496
|
+
inputMessageCount: source.messages.length,
|
|
13497
|
+
retainedMessageCount: pendingMessages.length,
|
|
13498
|
+
summaryChars: summary.length
|
|
13499
|
+
},
|
|
13500
|
+
summary,
|
|
13501
|
+
replacementHistory: replacementMessages.map((message, index) => ({
|
|
13502
|
+
message,
|
|
13503
|
+
provenance: index === 0 ? contextProvenance : pendingMessages[index - 1]?.provenance ?? contextProvenance
|
|
13504
|
+
}))
|
|
13505
|
+
}
|
|
13506
|
+
});
|
|
13507
|
+
setSpanAttributes({
|
|
13508
|
+
"app.compaction.input_messages": source.messages.length,
|
|
13509
|
+
"app.compaction.replacement_tokens": replacementInputTokens,
|
|
13510
|
+
"app.compaction.retained_messages": pendingMessages.length,
|
|
13511
|
+
"app.compaction.summary_chars": summary.length,
|
|
13512
|
+
"app.compaction.trigger_tokens": triggerTokens,
|
|
13513
|
+
"app.context_input_limit_tokens": inputLimitTokens,
|
|
13514
|
+
"app.context_tokens_estimated": source.estimatedTokens
|
|
13515
|
+
});
|
|
13516
|
+
return { compacted: true, piMessages: messages };
|
|
13517
|
+
}
|
|
15185
13518
|
|
|
15186
13519
|
// src/chat/agent/index.ts
|
|
15187
13520
|
var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
|
|
@@ -15267,6 +13600,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15267
13600
|
const state = request.state ?? {};
|
|
15268
13601
|
const observers = request.observers ?? {};
|
|
15269
13602
|
const delivery = request.delivery;
|
|
13603
|
+
const authorization = request.authorization;
|
|
15270
13604
|
const durability = request.durability ?? {};
|
|
15271
13605
|
signal?.throwIfAborted();
|
|
15272
13606
|
assertRunRoutingConsistency(request);
|
|
@@ -15280,7 +13614,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15280
13614
|
let mcpToolManager;
|
|
15281
13615
|
let connectedMcpProviders = /* @__PURE__ */ new Set();
|
|
15282
13616
|
let turnUsage;
|
|
15283
|
-
let
|
|
13617
|
+
let priorPhaseUsage;
|
|
15284
13618
|
const configuredReasoningLevel = policy.reasoningLevel ?? botConfig.reasoningLevel;
|
|
15285
13619
|
let turnRoute = configuredReasoningLevel ? configuredTurnRoute(
|
|
15286
13620
|
STANDARD_MODEL_PROFILE,
|
|
@@ -15331,6 +13665,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15331
13665
|
const projection = await openConversationProjection({ conversationId });
|
|
15332
13666
|
activeModelProfile = projection.modelProfile;
|
|
15333
13667
|
activeModelId = modelIdForProfile(botConfig, activeModelProfile);
|
|
13668
|
+
let durableModelProfile = projection.modelProfile;
|
|
15334
13669
|
const shouldTrace = shouldEmitDevAgentTrace();
|
|
15335
13670
|
const spanContext = { modelId: activeModelId };
|
|
15336
13671
|
const availableSkills = await discoverRunSkills({
|
|
@@ -15435,7 +13770,30 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15435
13770
|
});
|
|
15436
13771
|
const { routerBlocks, userContentParts } = buildPromptInput(input);
|
|
15437
13772
|
const preAgentPromptMessages = () => existingSessionRecord?.piMessages ?? [...input.piMessages ?? []];
|
|
15438
|
-
|
|
13773
|
+
const storedTurnRoute = await loadTurnRoute({ conversationId, turnId });
|
|
13774
|
+
if (storedTurnRoute) {
|
|
13775
|
+
const resumedAfterHandoff = activeModelProfile !== STANDARD_MODEL_PROFILE && activeModelProfile !== storedTurnRoute.modelProfile;
|
|
13776
|
+
if (resumedAfterHandoff) {
|
|
13777
|
+
const activeProfileConfig = profileConfig(
|
|
13778
|
+
botConfig,
|
|
13779
|
+
activeModelProfile
|
|
13780
|
+
);
|
|
13781
|
+
const resumedReasoningLevel = existingSessionRecord?.reasoningLevel ? parseTurnReasoningLevel(existingSessionRecord.reasoningLevel) : void 0;
|
|
13782
|
+
turnRoute = {
|
|
13783
|
+
profile: activeModelProfile,
|
|
13784
|
+
reasoningLevel: activeProfileConfig.reasoningLevel ?? resumedReasoningLevel ?? storedTurnRoute.reasoningLevel,
|
|
13785
|
+
reason: `resumed_handoff:${storedTurnRoute.modelProfile}:${activeModelProfile}`
|
|
13786
|
+
};
|
|
13787
|
+
} else {
|
|
13788
|
+
turnRoute = {
|
|
13789
|
+
profile: storedTurnRoute.modelProfile,
|
|
13790
|
+
reasoningLevel: storedTurnRoute.reasoningLevel,
|
|
13791
|
+
...storedTurnRoute.confidence !== void 0 ? { confidence: storedTurnRoute.confidence } : {},
|
|
13792
|
+
reason: `persisted:${storedTurnRoute.source}`,
|
|
13793
|
+
source: storedTurnRoute.source
|
|
13794
|
+
};
|
|
13795
|
+
}
|
|
13796
|
+
} else if (activeModelProfile === STANDARD_MODEL_PROFILE) {
|
|
15439
13797
|
turnRoute = await selectTurnRoute({
|
|
15440
13798
|
completeObject,
|
|
15441
13799
|
conversationContext: input.conversationContext,
|
|
@@ -15450,7 +13808,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15450
13808
|
messageText: userInput,
|
|
15451
13809
|
profiles: botConfig.profiles
|
|
15452
13810
|
});
|
|
15453
|
-
|
|
13811
|
+
const routedProfileReasoningLevel = profileConfig(
|
|
13812
|
+
botConfig,
|
|
13813
|
+
turnRoute.profile
|
|
13814
|
+
).reasoningLevel;
|
|
13815
|
+
if (configuredReasoningLevel && !routedProfileReasoningLevel) {
|
|
15454
13816
|
turnRoute = {
|
|
15455
13817
|
...turnRoute,
|
|
15456
13818
|
reasoningLevel: configuredReasoningLevel,
|
|
@@ -15459,12 +13821,29 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15459
13821
|
}
|
|
15460
13822
|
} else {
|
|
15461
13823
|
const activeProfileConfig = profileConfig(botConfig, activeModelProfile);
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
activeProfileConfig.reasoningLevel
|
|
15466
|
-
|
|
13824
|
+
const reasoningSource = activeProfileConfig.reasoningLevel ? "profile" : policy.reasoningLevel ? "agent_config" : "default";
|
|
13825
|
+
turnRoute = {
|
|
13826
|
+
profile: activeModelProfile,
|
|
13827
|
+
reasoningLevel: activeProfileConfig.reasoningLevel ?? policy.reasoningLevel ?? botConfig.reasoningLevel ?? "medium",
|
|
13828
|
+
reason: `inherited:${reasoningSource}`,
|
|
13829
|
+
source: "inherited"
|
|
13830
|
+
};
|
|
13831
|
+
}
|
|
13832
|
+
const routedModelProfile = turnRoute.profile;
|
|
13833
|
+
const routedModelId = modelIdForProfile(botConfig, routedModelProfile);
|
|
13834
|
+
if (!storedTurnRoute) {
|
|
13835
|
+
await recordTurnRoute({
|
|
13836
|
+
conversationId,
|
|
13837
|
+
turnId,
|
|
13838
|
+
modelProfile: routedModelProfile,
|
|
13839
|
+
modelId: routedModelId,
|
|
13840
|
+
reasoningLevel: turnRoute.reasoningLevel,
|
|
13841
|
+
...turnRoute.confidence !== void 0 ? { confidence: turnRoute.confidence } : {},
|
|
13842
|
+
source: turnRoute.source ?? "configured"
|
|
13843
|
+
});
|
|
15467
13844
|
}
|
|
13845
|
+
activeModelProfile = routedModelProfile;
|
|
13846
|
+
activeModelId = routedModelId;
|
|
15468
13847
|
const generatedFiles = [];
|
|
15469
13848
|
const artifactStatePatch = {};
|
|
15470
13849
|
const toolCalls = [];
|
|
@@ -15477,17 +13856,20 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15477
13856
|
(profile) => profile !== STANDARD_MODEL_PROFILE && profile !== DEFAULT_HANDOFF_MODEL_PROFILE
|
|
15478
13857
|
).sort()
|
|
15479
13858
|
];
|
|
13859
|
+
const usageSinceCurrentBoundary = (messages) => {
|
|
13860
|
+
const usage = extractGenAiUsageSummary(
|
|
13861
|
+
...messages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
|
|
13862
|
+
);
|
|
13863
|
+
return hasAgentTurnUsage(usage) ? usage : void 0;
|
|
13864
|
+
};
|
|
15480
13865
|
const scheduleHandoff = async (args) => {
|
|
15481
|
-
if (args.profile ===
|
|
13866
|
+
if (args.profile === durableModelProfile) {
|
|
15482
13867
|
return;
|
|
15483
13868
|
}
|
|
15484
13869
|
const runtimeContext = retainRuntimeTurnContext(
|
|
15485
13870
|
args.runtimeContextSourceMessages ?? args.sourceMessages
|
|
15486
13871
|
);
|
|
15487
|
-
const
|
|
15488
|
-
...args.sourceMessages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
|
|
15489
|
-
);
|
|
15490
|
-
const phaseUsage = hasAgentTurnUsage(standardPhaseUsage) ? standardPhaseUsage : void 0;
|
|
13872
|
+
const phaseUsage = usageSinceCurrentBoundary(args.sourceMessages);
|
|
15491
13873
|
const selectedProfile = profileConfig(botConfig, args.profile);
|
|
15492
13874
|
const handoffReasoningLevel = selectedProfile.reasoningLevel ?? turnRoute.reasoningLevel;
|
|
15493
13875
|
const target = {
|
|
@@ -15525,8 +13907,11 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15525
13907
|
runId
|
|
15526
13908
|
}
|
|
15527
13909
|
},
|
|
15528
|
-
{
|
|
13910
|
+
{
|
|
13911
|
+
completeText: (args2) => completeText(args2)
|
|
13912
|
+
}
|
|
15529
13913
|
);
|
|
13914
|
+
durableModelProfile = args.profile;
|
|
15530
13915
|
if (handoffReasoningLevel !== turnRoute.reasoningLevel) {
|
|
15531
13916
|
turnRoute = {
|
|
15532
13917
|
...turnRoute,
|
|
@@ -15534,7 +13919,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15534
13919
|
reason: `profile_reasoning_override:${args.profile}:${turnRoute.reason}`
|
|
15535
13920
|
};
|
|
15536
13921
|
}
|
|
15537
|
-
|
|
13922
|
+
priorPhaseUsage = addAgentTurnUsage(priorPhaseUsage, phaseUsage);
|
|
15538
13923
|
pendingHandoff = {
|
|
15539
13924
|
messages: handoffMessages,
|
|
15540
13925
|
model: handoffModel,
|
|
@@ -15567,6 +13952,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15567
13952
|
connectedMcpProviders,
|
|
15568
13953
|
conversationPrivacy,
|
|
15569
13954
|
durability,
|
|
13955
|
+
authorization,
|
|
15570
13956
|
generatedFiles,
|
|
15571
13957
|
invokedSkill,
|
|
15572
13958
|
observers,
|
|
@@ -15647,6 +14033,52 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15647
14033
|
thinkingLevel
|
|
15648
14034
|
};
|
|
15649
14035
|
};
|
|
14036
|
+
const applyActiveContextCompaction = async (messages, hookSignal, pendingMessages, clearSteeringQueueOnCommit = false) => {
|
|
14037
|
+
const compaction = await compactActiveContextIfNeeded(
|
|
14038
|
+
{
|
|
14039
|
+
conversationContext: input.conversationContext,
|
|
14040
|
+
conversationId,
|
|
14041
|
+
metadata: {
|
|
14042
|
+
threadId: conversationId,
|
|
14043
|
+
channelId: slackDestination?.channelId,
|
|
14044
|
+
actorId: slackActor?.userId,
|
|
14045
|
+
runId
|
|
14046
|
+
},
|
|
14047
|
+
modelId: activeModelId,
|
|
14048
|
+
modelProfile: activeModelProfile,
|
|
14049
|
+
onCompactionStart: () => observers.onStatus?.({ text: "Compacting context" }),
|
|
14050
|
+
pendingMessages,
|
|
14051
|
+
piMessages: messages,
|
|
14052
|
+
signal: hookSignal
|
|
14053
|
+
},
|
|
14054
|
+
{
|
|
14055
|
+
completeText: (args) => completeText(args)
|
|
14056
|
+
}
|
|
14057
|
+
);
|
|
14058
|
+
if (!compaction.compacted || !compaction.piMessages) {
|
|
14059
|
+
return void 0;
|
|
14060
|
+
}
|
|
14061
|
+
priorPhaseUsage = addAgentTurnUsage(
|
|
14062
|
+
priorPhaseUsage,
|
|
14063
|
+
usageSinceCurrentBoundary(messages)
|
|
14064
|
+
);
|
|
14065
|
+
const replacement = [...compaction.piMessages];
|
|
14066
|
+
await runResume.requireDurableInputCheckpoint(replacement);
|
|
14067
|
+
agent.state.messages = replacement;
|
|
14068
|
+
runResume.setBeforeMessageCount(replacement.length);
|
|
14069
|
+
runResume.setTurnStartMessageIndex(0);
|
|
14070
|
+
runResume.adoptCommittedBoundary(replacement);
|
|
14071
|
+
if (clearSteeringQueueOnCommit && pendingMessages?.length) {
|
|
14072
|
+
agent.clearSteeringQueue();
|
|
14073
|
+
}
|
|
14074
|
+
return {
|
|
14075
|
+
context: {
|
|
14076
|
+
systemPrompt: baseInstructions,
|
|
14077
|
+
messages: replacement,
|
|
14078
|
+
tools: agent.state.tools
|
|
14079
|
+
}
|
|
14080
|
+
};
|
|
14081
|
+
};
|
|
15650
14082
|
let assistantMessageDeliveryError;
|
|
15651
14083
|
const deliverAssistantMessage = async (message) => {
|
|
15652
14084
|
const text = getAssistantMessageText(message);
|
|
@@ -15664,8 +14096,9 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15664
14096
|
};
|
|
15665
14097
|
const drainSteeringMessages = async () => {
|
|
15666
14098
|
if (!durability.drainSteeringMessages) {
|
|
15667
|
-
return;
|
|
14099
|
+
return [];
|
|
15668
14100
|
}
|
|
14101
|
+
const acceptedMessages = [];
|
|
15669
14102
|
try {
|
|
15670
14103
|
let steeredMessageCount = 0;
|
|
15671
14104
|
await durability.drainSteeringMessages(async (messages) => {
|
|
@@ -15683,6 +14116,12 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15683
14116
|
for (const message of piMessages) {
|
|
15684
14117
|
agent.steer(message);
|
|
15685
14118
|
}
|
|
14119
|
+
acceptedMessages.push(
|
|
14120
|
+
...piMessages.map((message, index) => ({
|
|
14121
|
+
message,
|
|
14122
|
+
provenance: messages[index].provenance
|
|
14123
|
+
}))
|
|
14124
|
+
);
|
|
15686
14125
|
steeredMessageCount += piMessages.length;
|
|
15687
14126
|
});
|
|
15688
14127
|
if (steeredMessageCount > 0) {
|
|
@@ -15708,6 +14147,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15708
14147
|
"Agent turn steering message drain failed"
|
|
15709
14148
|
);
|
|
15710
14149
|
}
|
|
14150
|
+
return acceptedMessages;
|
|
15711
14151
|
};
|
|
15712
14152
|
const apiKeyOverride = getPiGatewayApiKey();
|
|
15713
14153
|
let pendingPiHookError;
|
|
@@ -15731,15 +14171,27 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15731
14171
|
return void 0;
|
|
15732
14172
|
},
|
|
15733
14173
|
afterToolCall: async ({ result: result2 }, signal2) => runResume.timedOut && signal2?.aborted ? annotateTurnDeadlineToolResult(result2) : void 0,
|
|
15734
|
-
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
|
|
14174
|
+
prepareNextTurnWithContext: async (nextTurn, hookSignal) => {
|
|
14175
|
+
try {
|
|
14176
|
+
const handoffUpdate = applyPendingHandoff();
|
|
14177
|
+
const pendingMessages = await drainSteeringMessages();
|
|
14178
|
+
const capacityUpdate = await applyActiveContextCompaction(
|
|
14179
|
+
handoffUpdate ? currentAgentMessages() : nextTurn.context.messages,
|
|
14180
|
+
hookSignal,
|
|
14181
|
+
pendingMessages,
|
|
14182
|
+
true
|
|
14183
|
+
);
|
|
14184
|
+
const yieldError = runResume.prepareYieldIfDue(
|
|
14185
|
+
currentAgentMessages()
|
|
14186
|
+
);
|
|
14187
|
+
if (yieldError) {
|
|
14188
|
+
throw yieldError;
|
|
14189
|
+
}
|
|
14190
|
+
return capacityUpdate && handoffUpdate ? { ...handoffUpdate, ...capacityUpdate } : capacityUpdate ?? handoffUpdate;
|
|
14191
|
+
} catch (error) {
|
|
14192
|
+
pendingPiHookError = error instanceof Error ? error : new Error(String(error));
|
|
14193
|
+
throw error;
|
|
15741
14194
|
}
|
|
15742
|
-
return update;
|
|
15743
14195
|
},
|
|
15744
14196
|
initialState: {
|
|
15745
14197
|
systemPrompt: baseInstructions,
|
|
@@ -15904,6 +14356,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15904
14356
|
};
|
|
15905
14357
|
const requestedProfile = activeModelProfile === STANDARD_MODEL_PROFILE ? turnRoute.profile : void 0;
|
|
15906
14358
|
let run;
|
|
14359
|
+
let handoffApplied = false;
|
|
15907
14360
|
if (requestedProfile && requestedProfile !== STANDARD_MODEL_PROFILE) {
|
|
15908
14361
|
const handoffAbortController = new AbortController();
|
|
15909
14362
|
await runAgentStep(
|
|
@@ -15915,19 +14368,29 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15915
14368
|
}),
|
|
15916
14369
|
() => handoffAbortController.abort()
|
|
15917
14370
|
);
|
|
15918
|
-
applyPendingHandoff();
|
|
15919
|
-
if (shouldPromptAgent) {
|
|
15920
|
-
await runResume.requireDurableInputCheckpoint([
|
|
15921
|
-
...agent.state.messages,
|
|
15922
|
-
freshPromptMessage
|
|
15923
|
-
]);
|
|
15924
|
-
run = agent.prompt(freshPromptMessage);
|
|
15925
|
-
} else {
|
|
15926
|
-
run = agent.continue();
|
|
15927
|
-
}
|
|
15928
|
-
} else {
|
|
15929
|
-
run = shouldPromptAgent ? agent.prompt(freshPromptMessage) : agent.continue();
|
|
14371
|
+
handoffApplied = Boolean(applyPendingHandoff());
|
|
15930
14372
|
}
|
|
14373
|
+
const compactionAbortController = new AbortController();
|
|
14374
|
+
const capacityUpdate = await runAgentStep(
|
|
14375
|
+
applyActiveContextCompaction(
|
|
14376
|
+
[...agent.state.messages],
|
|
14377
|
+
compactionAbortController.signal,
|
|
14378
|
+
shouldPromptAgent ? [
|
|
14379
|
+
{
|
|
14380
|
+
message: freshPromptMessage,
|
|
14381
|
+
provenance: instructionProvenanceFor(actor)
|
|
14382
|
+
}
|
|
14383
|
+
] : void 0
|
|
14384
|
+
),
|
|
14385
|
+
() => compactionAbortController.abort()
|
|
14386
|
+
);
|
|
14387
|
+
if (shouldPromptAgent && handoffApplied && !capacityUpdate) {
|
|
14388
|
+
await runResume.requireDurableInputCheckpoint([
|
|
14389
|
+
...agent.state.messages,
|
|
14390
|
+
freshPromptMessage
|
|
14391
|
+
]);
|
|
14392
|
+
}
|
|
14393
|
+
run = shouldPromptAgent && !capacityUpdate ? agent.prompt(freshPromptMessage) : agent.continue();
|
|
15931
14394
|
let retryUsage;
|
|
15932
14395
|
try {
|
|
15933
14396
|
for (let attempt = 0; ; attempt += 1) {
|
|
@@ -15950,10 +14413,7 @@ async function executeAgentRunInPrivacyContext(request, conversationPrivacy, run
|
|
|
15950
14413
|
retryUsage,
|
|
15951
14414
|
currentUsage
|
|
15952
14415
|
);
|
|
15953
|
-
turnUsage = addAgentTurnUsage(
|
|
15954
|
-
handoffPhaseUsage,
|
|
15955
|
-
currentPhaseUsage
|
|
15956
|
-
);
|
|
14416
|
+
turnUsage = addAgentTurnUsage(priorPhaseUsage, currentPhaseUsage);
|
|
15957
14417
|
setSpanAttributes({
|
|
15958
14418
|
...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
|
|
15959
14419
|
...toGenAiMessagesTraceAttributes(
|
|
@@ -16153,24 +14613,12 @@ export {
|
|
|
16153
14613
|
getConfigDefaults,
|
|
16154
14614
|
shouldEmitDevAgentTrace,
|
|
16155
14615
|
AuthorizationFlowDisabledError,
|
|
16156
|
-
RetryableDeliveryError,
|
|
16157
|
-
truncateStatusText,
|
|
16158
|
-
escapeSlackMrkdwnText,
|
|
16159
|
-
formatSlackLink,
|
|
16160
|
-
normalizeSlackStatusText,
|
|
16161
|
-
splitSlackReplyText,
|
|
16162
|
-
buildSlackOutputMessage,
|
|
16163
14616
|
buildSteeringPiMessage,
|
|
16164
|
-
createUserTokenStore,
|
|
16165
|
-
postSlackMessage,
|
|
16166
|
-
addReactionToMessage,
|
|
16167
|
-
removeReactionFromMessage,
|
|
16168
|
-
parseOAuthStatePayload,
|
|
16169
|
-
formatProviderLabel,
|
|
16170
|
-
resolveBaseUrl,
|
|
16171
|
-
startOAuthFlow,
|
|
16172
14617
|
SANDBOX_EGRESS_PROXY_PATH,
|
|
16173
14618
|
parseSandboxEgressCredentialToken,
|
|
14619
|
+
clearSandboxEgressSignals,
|
|
14620
|
+
consumeSandboxEgressAuthRequiredSignal,
|
|
14621
|
+
consumeSandboxEgressPermissionDeniedSignal,
|
|
16174
14622
|
normalizeSandboxEgressTracePropagationDomains,
|
|
16175
14623
|
shouldPropagateSandboxEgressTrace,
|
|
16176
14624
|
resolveSandboxEgressProviderForHost,
|
|
@@ -16182,14 +14630,6 @@ export {
|
|
|
16182
14630
|
appendSlackLegacyAttachmentText,
|
|
16183
14631
|
latestReportedProgress,
|
|
16184
14632
|
PluginCredentialFailureError,
|
|
16185
|
-
getMcpAuthSession,
|
|
16186
|
-
deleteMcpAuthSession,
|
|
16187
|
-
deleteMcpAuthSessionsForUserProvider,
|
|
16188
|
-
getMcpStoredOAuthCredentials,
|
|
16189
|
-
deleteMcpStoredOAuthCredentials,
|
|
16190
|
-
deleteMcpServerSessionId,
|
|
16191
|
-
finalizeMcpAuthorization,
|
|
16192
|
-
resolvePluginOAuthAccount,
|
|
16193
14633
|
executeCredentialedEgressRequest,
|
|
16194
14634
|
createContextCompactor,
|
|
16195
14635
|
executeAgentRun,
|