@sentry/junior 0.195.0 → 0.197.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-VIBTIP5F.js → agent-hooks-CM4EKXL4.js} +13 -13
- package/dist/api/schema.js +3 -3
- package/dist/api.js +27 -27
- package/dist/app.d.ts +8 -2
- package/dist/app.js +102 -108
- package/dist/{catalog-runtime-6E6SGRZK.js → catalog-runtime-V7WGCXUD.js} +4 -4
- package/dist/chat/agent/handoff.d.ts +73 -0
- package/dist/chat/config.d.ts +2 -2
- package/dist/chat/model-profile.d.ts +12 -0
- package/dist/chat/no-reply.d.ts +4 -3
- package/dist/chat/services/automated-turn-limit.d.ts +13 -34
- package/dist/chat/services/subscribed-decision.d.ts +8 -0
- package/dist/chat/slack/automated-turn-limit-notice.d.ts +2 -24
- package/dist/chat/tools/types.d.ts +6 -2
- package/dist/{chunk-RKA22BQP.js → chunk-2K26W3FP.js} +1 -1
- package/dist/{chunk-FE3Y4KES.js → chunk-4USCVHQZ.js} +5 -5
- package/dist/{chunk-PFQRZ4IB.js → chunk-4UUKQB6I.js} +7 -7
- package/dist/{chunk-KQ3GZGBK.js → chunk-5GWV7SXF.js} +4 -11
- package/dist/{chunk-7PQMTME5.js → chunk-5MMQLB2B.js} +1 -1
- package/dist/{chunk-FPQPYJEG.js → chunk-6ZQ3CE6X.js} +1 -1
- package/dist/{chunk-HUQOXMDG.js → chunk-7ENMKNEK.js} +1 -1
- package/dist/{chunk-GFBR4S3Q.js → chunk-7NL7T6VU.js} +1 -1
- package/dist/{chunk-I4RH3KLJ.js → chunk-BGSAFVJO.js} +1 -1
- package/dist/{chunk-3VWIZVK7.js → chunk-BQ7UXCUO.js} +1 -1
- package/dist/{chunk-LMYRF6HX.js → chunk-BR5GNDPS.js} +85 -14
- package/dist/{chunk-L62JVQPL.js → chunk-C2SOA3KN.js} +3 -3
- package/dist/{chunk-JBSCMOLR.js → chunk-DBTI74IT.js} +6 -6
- package/dist/{chunk-X3PBQ4BK.js → chunk-DHWYAHAM.js} +1 -1
- package/dist/{chunk-RQO67SAO.js → chunk-DLXA2ZXJ.js} +2 -2
- package/dist/{chunk-KBPWC7NU.js → chunk-EZKISZ3Q.js} +1 -1
- package/dist/{chunk-6TRQLG3D.js → chunk-FCONFDZ4.js} +1 -1
- package/dist/{chunk-7UED4D5T.js → chunk-H6QL3FWC.js} +22 -22
- package/dist/{chunk-5HNXLP4W.js → chunk-HDSDOBWN.js} +1 -1
- package/dist/{chunk-QKHZF2LH.js → chunk-IGSAXSM7.js} +4 -4
- package/dist/{chunk-HE5PZAPS.js → chunk-ITP4OR7R.js} +4 -4
- package/dist/{chunk-QPZL2IYT.js → chunk-LBN67OZR.js} +1 -1
- package/dist/{chunk-R5OHONYU.js → chunk-LIK4T6CF.js} +6 -6
- package/dist/{chunk-RZ3PBNDV.js → chunk-LMTHFU5D.js} +7 -7
- package/dist/{chunk-5NR7N567.js → chunk-MJCJ62RZ.js} +4 -4
- package/dist/{chunk-PYTRZZ4V.js → chunk-NANQFFGO.js} +8 -8
- package/dist/{chunk-D4YV5TDV.js → chunk-NHJU2ZAL.js} +11 -0
- package/dist/{chunk-FEZIEEOP.js → chunk-O2H7JUEQ.js} +289 -334
- package/dist/{chunk-CE2P6TOV.js → chunk-O5RG7SKU.js} +3 -3
- package/dist/{chunk-7MYXH5LA.js → chunk-PF3PEX4Q.js} +1 -1
- package/dist/{chunk-C4NY6L7S.js → chunk-PM5WSDAD.js} +1 -1
- package/dist/{chunk-KUQRWV53.js → chunk-S3DRTWPX.js} +5 -5
- package/dist/{chunk-N67T4VY3.js → chunk-SBCWV4E4.js} +9 -9
- package/dist/{chunk-6GGDBXXP.js → chunk-T5MK3RUT.js} +3 -3
- package/dist/{chunk-OQHO62VA.js → chunk-V2IX5Q5L.js} +8 -8
- package/dist/{chunk-DTYOEHUP.js → chunk-VAUPAX3O.js} +4 -4
- package/dist/{chunk-BJZML4RT.js → chunk-WOJG25QZ.js} +9 -9
- package/dist/{chunk-QHJZSYKY.js → chunk-XALIC5SY.js} +2 -2
- package/dist/{chunk-VYDIA2UL.js → chunk-YC5CFKME.js} +2 -6
- package/dist/{chunk-LM7UE523.js → chunk-YLBJBBYX.js} +3 -3
- package/dist/{chunk-R5BGW4WR.js → chunk-YZQLXALE.js} +2 -2
- package/dist/cli/chat.js +35 -35
- package/dist/cli/check.js +5 -5
- package/dist/cli/plugins.js +14 -14
- package/dist/cli/snapshot-create.js +10 -10
- package/dist/cli/upgrade.js +7 -7
- package/dist/{db-FVCSDROR.js → db-6BD722UD.js} +8 -8
- package/dist/instrumentation.js +3 -3
- package/dist/nitro.js +7 -7
- package/dist/{oauth-callback-server-Z5PXTDOE.js → oauth-callback-server-P6AIT27Q.js} +31 -31
- package/dist/{runner-RHYU7OGV.js → runner-OKPDZOJC.js} +23 -23
- package/dist/{skills-QTN73DN5.js → skills-CQFX3V2I.js} +5 -5
- package/dist/spawn-GA5MADGU.js +42 -0
- package/dist/task-runner-7IIYO44C.js +36 -0
- package/dist/{validation-2ZK7GA6N.js → validation-STB4W433.js} +5 -5
- package/dist/version.js +1 -1
- package/dist/{work-7IBKSZRU.js → work-B7N5TEYI.js} +23 -23
- package/dist/worker-W6PBEHCA.js +24 -0
- package/package.json +5 -5
- package/dist/spawn-R56OWLUH.js +0 -42
- package/dist/task-runner-LLJZTLRP.js +0 -36
- package/dist/worker-RNVQVCO7.js +0 -24
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
PluginCredentialFailureError,
|
|
8
8
|
agentNameSchema,
|
|
9
9
|
createPluginAuthOrchestration
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DBTI74IT.js";
|
|
11
11
|
import {
|
|
12
12
|
parseSandboxEgressAuthRequiredSignal,
|
|
13
13
|
parseSandboxEgressPermissionDeniedSignal,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
searchSlackUsers,
|
|
39
39
|
selectPluginGrant,
|
|
40
40
|
toMcpProviderError
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-V2IX5Q5L.js";
|
|
42
42
|
import {
|
|
43
43
|
RetryableDeliveryError,
|
|
44
44
|
actorFromRun,
|
|
@@ -53,20 +53,19 @@ import {
|
|
|
53
53
|
surfaceFromRun,
|
|
54
54
|
toolInvocationDestination,
|
|
55
55
|
uploadFilesToConversation
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-LIK4T6CF.js";
|
|
57
57
|
import {
|
|
58
58
|
CooperativeTurnYieldError,
|
|
59
59
|
NO_REPLY_MARKER,
|
|
60
60
|
TurnInputCommitLostError,
|
|
61
61
|
abandonReplacedPendingAuth,
|
|
62
62
|
canReusePendingAuthLink,
|
|
63
|
-
containsNoReplyMarker,
|
|
64
63
|
decideReply,
|
|
65
64
|
getAssistantReplyText,
|
|
66
65
|
isNoReplyMarker,
|
|
67
66
|
isTurnInputCommitLostError,
|
|
68
67
|
sanitizeAssistantText
|
|
69
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-YC5CFKME.js";
|
|
70
69
|
import {
|
|
71
70
|
AGENTS_REMOVAL_NOTICE,
|
|
72
71
|
AGENTS_REPLACEMENT_NOTICE,
|
|
@@ -122,7 +121,7 @@ import {
|
|
|
122
121
|
stripRuntimeTurnContext,
|
|
123
122
|
trimTrailingAssistantMessages,
|
|
124
123
|
truncateText
|
|
125
|
-
} from "./chunk-
|
|
124
|
+
} from "./chunk-LMTHFU5D.js";
|
|
126
125
|
import {
|
|
127
126
|
SCHEDULED_TASK_SYSTEM_ACTOR,
|
|
128
127
|
WORKSPACE_SNAPSHOT_FAILED_EVENT,
|
|
@@ -163,13 +162,13 @@ import {
|
|
|
163
162
|
workspaceSnapshotFinishedEvent,
|
|
164
163
|
workspaceSnapshotWatch,
|
|
165
164
|
writableEventTask
|
|
166
|
-
} from "./chunk-
|
|
165
|
+
} from "./chunk-VAUPAX3O.js";
|
|
167
166
|
import {
|
|
168
167
|
parseSlackThreadId
|
|
169
|
-
} from "./chunk-
|
|
168
|
+
} from "./chunk-FCONFDZ4.js";
|
|
170
169
|
import {
|
|
171
170
|
appendAndEnqueueInboundMessage
|
|
172
|
-
} from "./chunk-
|
|
171
|
+
} from "./chunk-IGSAXSM7.js";
|
|
173
172
|
import {
|
|
174
173
|
SHORT_TITLE_MAX_LENGTH,
|
|
175
174
|
ToolInputError,
|
|
@@ -177,7 +176,7 @@ import {
|
|
|
177
176
|
generateShortTitle,
|
|
178
177
|
hasAgentTurnUsage,
|
|
179
178
|
resolveTaskTitle
|
|
180
|
-
} from "./chunk-
|
|
179
|
+
} from "./chunk-YZQLXALE.js";
|
|
181
180
|
import {
|
|
182
181
|
bindSandboxFileSystem,
|
|
183
182
|
createSandboxSession,
|
|
@@ -188,7 +187,7 @@ import {
|
|
|
188
187
|
resolve,
|
|
189
188
|
setSnapshotSpanAttributes,
|
|
190
189
|
stopSession
|
|
191
|
-
} from "./chunk-
|
|
190
|
+
} from "./chunk-C2SOA3KN.js";
|
|
192
191
|
import {
|
|
193
192
|
privateTraceResultAttributes
|
|
194
193
|
} from "./chunk-UIE3R5XU.js";
|
|
@@ -198,7 +197,7 @@ import {
|
|
|
198
197
|
} from "./chunk-X6GDDWQH.js";
|
|
199
198
|
import {
|
|
200
199
|
getTurnRequestDeadline
|
|
201
|
-
} from "./chunk-
|
|
200
|
+
} from "./chunk-BGSAFVJO.js";
|
|
202
201
|
import {
|
|
203
202
|
getVercelConversationWorkQueue
|
|
204
203
|
} from "./chunk-DKIJ2RP5.js";
|
|
@@ -245,23 +244,22 @@ import {
|
|
|
245
244
|
updateWorkspace,
|
|
246
245
|
withSlackRetries,
|
|
247
246
|
workspaceRepoCheckoutPath
|
|
248
|
-
} from "./chunk-
|
|
247
|
+
} from "./chunk-4UUKQB6I.js";
|
|
249
248
|
import {
|
|
250
249
|
JUNIOR_THREAD_STATE_TTL_MS
|
|
251
250
|
} from "./chunk-3Y47VMHV.js";
|
|
252
251
|
import {
|
|
253
|
-
destinationKey,
|
|
254
252
|
getConversationEventStore,
|
|
255
253
|
getConversationMessageSearchStore,
|
|
256
254
|
getConversationStore,
|
|
257
255
|
getDb,
|
|
258
256
|
getSqlExecutor
|
|
259
|
-
} from "./chunk-
|
|
257
|
+
} from "./chunk-5GWV7SXF.js";
|
|
260
258
|
import {
|
|
261
259
|
juniorArtifacts,
|
|
262
260
|
juniorIdentities,
|
|
263
261
|
juniorSchedulerRuns
|
|
264
|
-
} from "./chunk-
|
|
262
|
+
} from "./chunk-6ZQ3CE6X.js";
|
|
265
263
|
import {
|
|
266
264
|
SANDBOX_DATA_ROOT,
|
|
267
265
|
SANDBOX_REPOS_ROOT,
|
|
@@ -292,19 +290,19 @@ import {
|
|
|
292
290
|
throwSandboxOperationError,
|
|
293
291
|
workspaceSnapshotNotReadyUserMessage,
|
|
294
292
|
wrapSandboxSetupError
|
|
295
|
-
} from "./chunk-
|
|
293
|
+
} from "./chunk-YLBJBBYX.js";
|
|
296
294
|
import {
|
|
297
295
|
fenceLock,
|
|
298
296
|
getStateAdapter,
|
|
299
297
|
withLock
|
|
300
|
-
} from "./chunk-
|
|
298
|
+
} from "./chunk-PF3PEX4Q.js";
|
|
301
299
|
import {
|
|
302
300
|
KNOWN_CONVERSATION_EVENT_TYPES,
|
|
303
301
|
contextProvenance,
|
|
304
302
|
instructionActors,
|
|
305
303
|
instructionProvenanceFor,
|
|
306
304
|
sameActorIdentity
|
|
307
|
-
} from "./chunk-
|
|
305
|
+
} from "./chunk-7NL7T6VU.js";
|
|
308
306
|
import {
|
|
309
307
|
ACTIVE_TURN_COMPACTION_SUMMARY_PREFIX,
|
|
310
308
|
COMPACTION_SUMMARY_PREFIX,
|
|
@@ -330,14 +328,16 @@ import {
|
|
|
330
328
|
resolveGatewayCredential,
|
|
331
329
|
resolveGatewayModel,
|
|
332
330
|
unwrapCurrentInstruction
|
|
333
|
-
} from "./chunk-
|
|
331
|
+
} from "./chunk-BR5GNDPS.js";
|
|
334
332
|
import {
|
|
335
333
|
ModelProfileNotConfiguredError,
|
|
336
334
|
TURN_REASONING_LEVELS,
|
|
335
|
+
formatModelProfile,
|
|
336
|
+
formatModelProfiles,
|
|
337
337
|
modelIdForProfile,
|
|
338
338
|
modelProfileSchema,
|
|
339
339
|
profileConfig
|
|
340
|
-
} from "./chunk-
|
|
340
|
+
} from "./chunk-NHJU2ZAL.js";
|
|
341
341
|
import {
|
|
342
342
|
canExposeConversationPayload,
|
|
343
343
|
resolveConversationPrivacy,
|
|
@@ -347,16 +347,16 @@ import {
|
|
|
347
347
|
toGenAiMessagesTraceAttributes,
|
|
348
348
|
toGenAiPayloadMetadata,
|
|
349
349
|
toGenAiPayloadTraceAttributes
|
|
350
|
-
} from "./chunk-
|
|
350
|
+
} from "./chunk-EZKISZ3Q.js";
|
|
351
351
|
import {
|
|
352
352
|
discoverSkills,
|
|
353
353
|
findSkillByName,
|
|
354
354
|
loadSkillsByName,
|
|
355
355
|
parseSkillInvocation
|
|
356
|
-
} from "./chunk-
|
|
356
|
+
} from "./chunk-DLXA2ZXJ.js";
|
|
357
357
|
import {
|
|
358
358
|
pluginCatalogRuntime
|
|
359
|
-
} from "./chunk-
|
|
359
|
+
} from "./chunk-2K26W3FP.js";
|
|
360
360
|
import {
|
|
361
361
|
CredentialUnavailableError,
|
|
362
362
|
OAuthProviderError,
|
|
@@ -364,7 +364,7 @@ import {
|
|
|
364
364
|
getOAuthProviderErrorAttributes,
|
|
365
365
|
resolveAuthTokenPlaceholder,
|
|
366
366
|
resolvePluginCommandEnv
|
|
367
|
-
} from "./chunk-
|
|
367
|
+
} from "./chunk-DHWYAHAM.js";
|
|
368
368
|
import {
|
|
369
369
|
credentialContextForActor,
|
|
370
370
|
credentialUserSubjectId
|
|
@@ -400,7 +400,7 @@ import {
|
|
|
400
400
|
summarizeMessageText,
|
|
401
401
|
withLogContext,
|
|
402
402
|
withSpan
|
|
403
|
-
} from "./chunk-
|
|
403
|
+
} from "./chunk-7ENMKNEK.js";
|
|
404
404
|
import {
|
|
405
405
|
startInactiveSpan,
|
|
406
406
|
withActiveSpan
|
|
@@ -924,11 +924,6 @@ function buildTurnResult(input) {
|
|
|
924
924
|
if (!isProviderError) {
|
|
925
925
|
logInfo("ai.no_reply_marker.accepted", markerAttributes);
|
|
926
926
|
}
|
|
927
|
-
} else if (containsNoReplyMarker(rawPrimaryText)) {
|
|
928
|
-
logWarn("ai.no_reply_marker.mixed_text", {
|
|
929
|
-
"app.ai.no_reply_marker": true,
|
|
930
|
-
"app.ai.no_reply_marker_mode": "mixed"
|
|
931
|
-
});
|
|
932
927
|
}
|
|
933
928
|
if (!primaryText && !completedWithoutTerminalText && !isProviderError) {
|
|
934
929
|
logWarn("ai.model_response.empty", {
|
|
@@ -1146,11 +1141,11 @@ function trimContextForRouter(text) {
|
|
|
1146
1141
|
originalCharCount: trimmed.length
|
|
1147
1142
|
};
|
|
1148
1143
|
}
|
|
1149
|
-
function buildClassifierSystemPrompt(profileNames, defaultProfile) {
|
|
1144
|
+
function buildClassifierSystemPrompt(profiles, profileNames, defaultProfile) {
|
|
1145
|
+
const profileList = formatModelProfiles(profiles, profileNames);
|
|
1150
1146
|
return [
|
|
1151
1147
|
"You choose the model profile most likely to produce a complete, source-grounded answer.",
|
|
1152
1148
|
"Choose exactly one bucket: none, low, medium, high, or xhigh.",
|
|
1153
|
-
`Choose profile from the configured profiles: ${profileNames.join(", ")}.`,
|
|
1154
1149
|
"Choose profile independently from the reasoning bucket.",
|
|
1155
1150
|
"",
|
|
1156
1151
|
"Use none only for greetings, acknowledgments, and turns that need no substantive assistant work.",
|
|
@@ -1159,9 +1154,11 @@ function buildClassifierSystemPrompt(profileNames, defaultProfile) {
|
|
|
1159
1154
|
"Use high for research-heavy work, non-trivial drafting, or explicit requests to be thorough.",
|
|
1160
1155
|
"Use xhigh for the most complex tasks: code changes, debugging/root-cause analysis, broad refactors, architecture decisions, multi-file implementation, or any task where deep reasoning across multiple systems or files is required.",
|
|
1161
1156
|
"When unsure between two non-none buckets, choose the higher bucket. Do not use low as the default.",
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
"
|
|
1157
|
+
"",
|
|
1158
|
+
`Default profile: "${defaultProfile}". Keep it unless another profile's description is a clearly better fit.`,
|
|
1159
|
+
"Configured profiles (use each description's use and avoid cases; names are labels only; non-default does not mean stronger):",
|
|
1160
|
+
profileList,
|
|
1161
|
+
"Choose a non-default profile only when its description clearly fits the task. If no description clearly fits, keep the default profile.",
|
|
1165
1162
|
"",
|
|
1166
1163
|
"Classify based on the substance of the task, not the length of the current message. When the current instruction is a short affirmation (for example: 'go', 'do it', 'yes please', 'proceed') and prior thread context contains a pending task, classify the pending task \u2014 not the affirmation.",
|
|
1167
1164
|
"",
|
|
@@ -1299,6 +1296,7 @@ async function classifyTurn(args) {
|
|
|
1299
1296
|
prompt: args.prompt,
|
|
1300
1297
|
thinkingLevel: "low",
|
|
1301
1298
|
system: buildClassifierSystemPrompt(
|
|
1299
|
+
args.profiles,
|
|
1302
1300
|
Object.keys(args.profiles),
|
|
1303
1301
|
args.defaultProfile
|
|
1304
1302
|
),
|
|
@@ -1733,7 +1731,7 @@ var TOOL_POLICY_RULES = [
|
|
|
1733
1731
|
"- Event tasks make the task creator's connected credentials available by default when the requested work needs user-bound authorization. Do not ask for separate confirmation merely to use credentials needed for the requested work. On creation, omit credentialMode for the creator default and set system only when the creator explicitly requires it. For later changes, creator always means the task's original createdBy actor, never the current requester. If the requester is not that creator, do not attempt to enable creator credential use or suggest that confirmation could authorize it.",
|
|
1734
1732
|
"- Event tasks list for the current destination, not one thread. Public tasks can also be updated or deleted by task id from another destination in the same workspace. When listing them, use createdBy to explain creator-only credential changes and warn when triggerAvailable is false; an unavailable task remains stored but cannot receive events until its plugin event is enabled again.",
|
|
1735
1733
|
"- Scheduled tasks make the task creator's connected credentials available by default when the requested work needs user-bound authorization. Do not ask for separate confirmation merely to use credentials needed for the requested work. On creation, omit credential_mode for the creator default and set system only when the creator explicitly requires it. For later changes, creator always means the task's original created_by actor, never the current requester. If the requester is not that creator, do not attempt to enable creator credential use or suggest that confirmation could authorize it.",
|
|
1736
|
-
"-
|
|
1734
|
+
"- When another model profile fits the task better, call `handoff` before substantial work. Follow the profile list and selection rules in the tool description.",
|
|
1737
1735
|
"- Run `jr-rpc config get|set|unset|list` for provider defaults and `jr-rpc plugins list` for installed plugin introspection as standalone bash commands; do not chain them with `cd`, `&&`, pipes, or provider commands.",
|
|
1738
1736
|
"- If the first result is empty, stale, ambiguous, or incomplete, try a focused alternate query, path, command, or source before concluding the answer cannot be verified."
|
|
1739
1737
|
];
|
|
@@ -3119,11 +3117,8 @@ function sleep2(ms) {
|
|
|
3119
3117
|
timer.unref?.();
|
|
3120
3118
|
});
|
|
3121
3119
|
}
|
|
3122
|
-
function
|
|
3123
|
-
|
|
3124
|
-
return `${AUTOMATED_TURN_LIMIT_PREFIX}:conversation:${scope.conversationId}`;
|
|
3125
|
-
}
|
|
3126
|
-
return `${AUTOMATED_TURN_LIMIT_PREFIX}:destination:${destinationKey(scope.destination)}`;
|
|
3120
|
+
function conversationKey(conversationId) {
|
|
3121
|
+
return `${AUTOMATED_TURN_LIMIT_PREFIX}:conversation:${conversationId}`;
|
|
3127
3122
|
}
|
|
3128
3123
|
function lockKey(key) {
|
|
3129
3124
|
return `${key}:lock`;
|
|
@@ -3132,7 +3127,7 @@ function parseState(value) {
|
|
|
3132
3127
|
const parsed = automatedTurnLimitStateSchema.safeParse(value);
|
|
3133
3128
|
return parsed.success ? parsed.data : void 0;
|
|
3134
3129
|
}
|
|
3135
|
-
async function
|
|
3130
|
+
async function acquireConversationLock(state, key, waitDeadlineMs = Date.now() + AUTOMATED_TURN_LIMIT_LOCK_WAIT_MS) {
|
|
3136
3131
|
while (true) {
|
|
3137
3132
|
const lock = await state.acquireLock(
|
|
3138
3133
|
lockKey(key),
|
|
@@ -3147,8 +3142,8 @@ async function acquireScopeLock(state, key, waitDeadlineMs = Date.now() + AUTOMA
|
|
|
3147
3142
|
await sleep2(AUTOMATED_TURN_LIMIT_LOCK_RETRY_MS);
|
|
3148
3143
|
}
|
|
3149
3144
|
}
|
|
3150
|
-
async function
|
|
3151
|
-
const lock = await
|
|
3145
|
+
async function withConversationLock(state, key, callback) {
|
|
3146
|
+
const lock = await acquireConversationLock(state, key);
|
|
3152
3147
|
try {
|
|
3153
3148
|
return await callback();
|
|
3154
3149
|
} finally {
|
|
@@ -3162,36 +3157,14 @@ function emptyState(nowMs) {
|
|
|
3162
3157
|
updatedAtMs: nowMs
|
|
3163
3158
|
};
|
|
3164
3159
|
}
|
|
3165
|
-
function resumeInForScope(scope) {
|
|
3166
|
-
return scope.kind === "conversation" ? "thread" : "channel";
|
|
3167
|
-
}
|
|
3168
3160
|
function isAutomatedTurnSource(source) {
|
|
3169
3161
|
if (!source) {
|
|
3170
3162
|
return false;
|
|
3171
3163
|
}
|
|
3172
3164
|
return source.kind === "resource_event" || source.kind === "event_task" || source.kind === "scheduled_task" || source.kind === "plugin_dispatch" || source.kind === "agent_invocation";
|
|
3173
3165
|
}
|
|
3174
|
-
function automatedTurnLimitScopeForSource(args) {
|
|
3175
|
-
if (!isAutomatedTurnSource(args.source)) {
|
|
3176
|
-
return void 0;
|
|
3177
|
-
}
|
|
3178
|
-
if (args.source?.kind === "resource_event") {
|
|
3179
|
-
return {
|
|
3180
|
-
kind: "conversation",
|
|
3181
|
-
conversationId: args.conversationId
|
|
3182
|
-
};
|
|
3183
|
-
}
|
|
3184
|
-
if (args.destination) {
|
|
3185
|
-
return { kind: "destination", destination: args.destination };
|
|
3186
|
-
}
|
|
3187
|
-
return {
|
|
3188
|
-
kind: "conversation",
|
|
3189
|
-
conversationId: args.conversationId
|
|
3190
|
-
};
|
|
3191
|
-
}
|
|
3192
3166
|
function buildAutomatedTurnLimitResponse(args) {
|
|
3193
|
-
|
|
3194
|
-
return `I stopped automatic updates after ${args.maxTurns} replies without a new message from you. Send a message or @mention me in this ${place} to continue.`;
|
|
3167
|
+
return `I stopped automatic updates after ${args.maxTurns} replies without a new message from you. Send a message or @mention me in this thread to continue.`;
|
|
3195
3168
|
}
|
|
3196
3169
|
async function admitAutomatedTurn(args) {
|
|
3197
3170
|
if (args.maxTurns < 1) {
|
|
@@ -3200,8 +3173,8 @@ async function admitAutomatedTurn(args) {
|
|
|
3200
3173
|
const state = args.state ?? getStateAdapter();
|
|
3201
3174
|
await state.connect();
|
|
3202
3175
|
const nowMs = args.nowMs ?? Date.now();
|
|
3203
|
-
const key =
|
|
3204
|
-
return await
|
|
3176
|
+
const key = conversationKey(args.conversationId);
|
|
3177
|
+
return await withConversationLock(state, key, async () => {
|
|
3205
3178
|
const current = parseState(await state.get(key)) ?? emptyState(nowMs);
|
|
3206
3179
|
if (current.paused || current.consecutiveAutomatedTurns >= args.maxTurns) {
|
|
3207
3180
|
const shouldPostNotice = current.noticePostedAtMs === void 0;
|
|
@@ -3236,9 +3209,8 @@ async function countAutomatedTurn(args) {
|
|
|
3236
3209
|
const state = args.state ?? getStateAdapter();
|
|
3237
3210
|
await state.connect();
|
|
3238
3211
|
const nowMs = args.nowMs ?? Date.now();
|
|
3239
|
-
const key =
|
|
3240
|
-
|
|
3241
|
-
return await withScopeLock(state, key, async () => {
|
|
3212
|
+
const key = conversationKey(args.conversationId);
|
|
3213
|
+
return await withConversationLock(state, key, async () => {
|
|
3242
3214
|
const current = parseState(await state.get(key)) ?? emptyState(nowMs);
|
|
3243
3215
|
const consecutiveAutomatedTurns = current.consecutiveAutomatedTurns + 1;
|
|
3244
3216
|
const paused = consecutiveAutomatedTurns >= args.maxTurns;
|
|
@@ -3247,16 +3219,15 @@ async function countAutomatedTurn(args) {
|
|
|
3247
3219
|
consecutiveAutomatedTurns,
|
|
3248
3220
|
paused,
|
|
3249
3221
|
updatedAtMs: nowMs,
|
|
3250
|
-
// Claim the notice slot before
|
|
3251
|
-
//
|
|
3222
|
+
// Claim the notice slot before posting. Mark success after the post
|
|
3223
|
+
// lands; clear the claim if the post fails or is skipped.
|
|
3252
3224
|
...shouldPostNotice ? { noticePostedAtMs: nowMs } : current.noticePostedAtMs !== void 0 ? { noticePostedAtMs: current.noticePostedAtMs } : void 0
|
|
3253
3225
|
};
|
|
3254
3226
|
await state.set(key, next, JUNIOR_THREAD_STATE_TTL_MS);
|
|
3255
3227
|
return {
|
|
3256
3228
|
consecutiveAutomatedTurns,
|
|
3257
3229
|
paused,
|
|
3258
|
-
shouldPostNotice
|
|
3259
|
-
resumeIn
|
|
3230
|
+
shouldPostNotice
|
|
3260
3231
|
};
|
|
3261
3232
|
});
|
|
3262
3233
|
}
|
|
@@ -3264,8 +3235,8 @@ async function clearAutomatedTurnLimitNoticeClaim(args) {
|
|
|
3264
3235
|
const state = args.state ?? getStateAdapter();
|
|
3265
3236
|
await state.connect();
|
|
3266
3237
|
const nowMs = args.nowMs ?? Date.now();
|
|
3267
|
-
const key =
|
|
3268
|
-
await
|
|
3238
|
+
const key = conversationKey(args.conversationId);
|
|
3239
|
+
await withConversationLock(state, key, async () => {
|
|
3269
3240
|
const current = parseState(await state.get(key));
|
|
3270
3241
|
if (!current || current.noticePostedAtMs === void 0) {
|
|
3271
3242
|
return;
|
|
@@ -3284,41 +3255,26 @@ async function clearAutomatedTurnLimitNoticeClaim(args) {
|
|
|
3284
3255
|
async function resetAutomatedTurnLimit(args) {
|
|
3285
3256
|
const state = args.state ?? getStateAdapter();
|
|
3286
3257
|
await state.connect();
|
|
3287
|
-
const key =
|
|
3288
|
-
await
|
|
3258
|
+
const key = conversationKey(args.conversationId);
|
|
3259
|
+
await withConversationLock(state, key, async () => {
|
|
3289
3260
|
await state.delete(key);
|
|
3290
3261
|
});
|
|
3291
3262
|
}
|
|
3292
3263
|
async function recordFinishedTurnForAutomatedLimit(args) {
|
|
3293
3264
|
const nowMs = args.nowMs ?? Date.now();
|
|
3294
|
-
|
|
3295
|
-
conversationId: args.conversationId,
|
|
3296
|
-
destination: args.destination,
|
|
3297
|
-
source: args.source
|
|
3298
|
-
});
|
|
3299
|
-
if (automatedScope) {
|
|
3265
|
+
if (isAutomatedTurnSource(args.source)) {
|
|
3300
3266
|
return await countAutomatedTurn({
|
|
3267
|
+
conversationId: args.conversationId,
|
|
3301
3268
|
maxTurns: args.maxTurns,
|
|
3302
3269
|
nowMs,
|
|
3303
|
-
scope: automatedScope,
|
|
3304
3270
|
state: args.state
|
|
3305
3271
|
});
|
|
3306
3272
|
}
|
|
3307
3273
|
await resetAutomatedTurnLimit({
|
|
3274
|
+
conversationId: args.conversationId,
|
|
3308
3275
|
nowMs,
|
|
3309
|
-
scope: {
|
|
3310
|
-
kind: "conversation",
|
|
3311
|
-
conversationId: args.conversationId
|
|
3312
|
-
},
|
|
3313
3276
|
state: args.state
|
|
3314
3277
|
});
|
|
3315
|
-
if (args.destination) {
|
|
3316
|
-
await resetAutomatedTurnLimit({
|
|
3317
|
-
nowMs,
|
|
3318
|
-
scope: { kind: "destination", destination: args.destination },
|
|
3319
|
-
state: args.state
|
|
3320
|
-
});
|
|
3321
|
-
}
|
|
3322
3278
|
return void 0;
|
|
3323
3279
|
}
|
|
3324
3280
|
|
|
@@ -3331,10 +3287,6 @@ async function releaseFailedNoticeClaim(args) {
|
|
|
3331
3287
|
}
|
|
3332
3288
|
}
|
|
3333
3289
|
async function postAutomatedTurnLimitNoticeForConversation(args) {
|
|
3334
|
-
const scope = args.scope ?? {
|
|
3335
|
-
kind: "conversation",
|
|
3336
|
-
conversationId: args.conversationId
|
|
3337
|
-
};
|
|
3338
3290
|
try {
|
|
3339
3291
|
const conversation = await getConversationStore().get({
|
|
3340
3292
|
conversationId: args.conversationId
|
|
@@ -3346,18 +3298,17 @@ async function postAutomatedTurnLimitNoticeForConversation(args) {
|
|
|
3346
3298
|
"app.automated_turn_limit.reason": location ? "unsupported_location" : "missing_location"
|
|
3347
3299
|
});
|
|
3348
3300
|
await releaseFailedNoticeClaim({
|
|
3349
|
-
|
|
3350
|
-
|
|
3301
|
+
conversationId: args.conversationId,
|
|
3302
|
+
nowMs: args.nowMs
|
|
3351
3303
|
});
|
|
3352
3304
|
return false;
|
|
3353
3305
|
}
|
|
3354
3306
|
await postSlackMessage({
|
|
3355
3307
|
channelId: location.channelId,
|
|
3356
3308
|
text: buildAutomatedTurnLimitResponse({
|
|
3357
|
-
maxTurns: args.maxTurns
|
|
3358
|
-
resumeIn: args.resumeIn ?? "thread"
|
|
3309
|
+
maxTurns: args.maxTurns
|
|
3359
3310
|
}),
|
|
3360
|
-
...location.threadTs ? { threadTs: location.threadTs } : void 0
|
|
3311
|
+
...args.threadTs ? { threadTs: args.threadTs } : location.threadTs ? { threadTs: location.threadTs } : void 0
|
|
3361
3312
|
});
|
|
3362
3313
|
return true;
|
|
3363
3314
|
} catch (error) {
|
|
@@ -3365,45 +3316,8 @@ async function postAutomatedTurnLimitNoticeForConversation(args) {
|
|
|
3365
3316
|
conversationId: args.conversationId
|
|
3366
3317
|
});
|
|
3367
3318
|
await releaseFailedNoticeClaim({
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
});
|
|
3371
|
-
return false;
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
async function postAutomatedTurnLimitNoticeForDestination(args) {
|
|
3375
|
-
const scope = args.scope ?? {
|
|
3376
|
-
kind: "destination",
|
|
3377
|
-
destination: args.destination
|
|
3378
|
-
};
|
|
3379
|
-
if (args.destination.platform !== "slack") {
|
|
3380
|
-
logWarn("automated_turn_limit.notice_skipped", {
|
|
3381
|
-
"app.automated_turn_limit.reason": "unsupported_destination"
|
|
3382
|
-
});
|
|
3383
|
-
await releaseFailedNoticeClaim({
|
|
3384
|
-
nowMs: args.nowMs,
|
|
3385
|
-
scope
|
|
3386
|
-
});
|
|
3387
|
-
return false;
|
|
3388
|
-
}
|
|
3389
|
-
try {
|
|
3390
|
-
await postSlackMessage({
|
|
3391
|
-
channelId: args.destination.channelId,
|
|
3392
|
-
text: buildAutomatedTurnLimitResponse({
|
|
3393
|
-
maxTurns: args.maxTurns,
|
|
3394
|
-
resumeIn: args.resumeIn ?? "channel"
|
|
3395
|
-
}),
|
|
3396
|
-
...args.threadTs ? { threadTs: args.threadTs } : void 0
|
|
3397
|
-
});
|
|
3398
|
-
return true;
|
|
3399
|
-
} catch (error) {
|
|
3400
|
-
logException(error, "automated_turn_limit.notice_failed", {
|
|
3401
|
-
"app.slack.channel_id": args.destination.channelId,
|
|
3402
|
-
"app.slack.team_id": args.destination.teamId
|
|
3403
|
-
});
|
|
3404
|
-
await releaseFailedNoticeClaim({
|
|
3405
|
-
nowMs: args.nowMs,
|
|
3406
|
-
scope
|
|
3319
|
+
conversationId: args.conversationId,
|
|
3320
|
+
nowMs: args.nowMs
|
|
3407
3321
|
});
|
|
3408
3322
|
return false;
|
|
3409
3323
|
}
|
|
@@ -3412,39 +3326,11 @@ async function maybePostAutomatedTurnLimitNotice(args) {
|
|
|
3412
3326
|
if (!args.update?.shouldPostNotice) {
|
|
3413
3327
|
return;
|
|
3414
3328
|
}
|
|
3415
|
-
if (args.update.resumeIn === "thread") {
|
|
3416
|
-
await postAutomatedTurnLimitNoticeForConversation({
|
|
3417
|
-
conversationId: args.conversationId,
|
|
3418
|
-
maxTurns: args.maxTurns,
|
|
3419
|
-
nowMs: args.nowMs,
|
|
3420
|
-
resumeIn: "thread",
|
|
3421
|
-
scope: {
|
|
3422
|
-
kind: "conversation",
|
|
3423
|
-
conversationId: args.conversationId
|
|
3424
|
-
}
|
|
3425
|
-
});
|
|
3426
|
-
return;
|
|
3427
|
-
}
|
|
3428
|
-
if (args.destination) {
|
|
3429
|
-
await postAutomatedTurnLimitNoticeForDestination({
|
|
3430
|
-
destination: args.destination,
|
|
3431
|
-
maxTurns: args.maxTurns,
|
|
3432
|
-
nowMs: args.nowMs,
|
|
3433
|
-
resumeIn: "channel",
|
|
3434
|
-
scope: { kind: "destination", destination: args.destination },
|
|
3435
|
-
threadTs: args.threadTs
|
|
3436
|
-
});
|
|
3437
|
-
return;
|
|
3438
|
-
}
|
|
3439
3329
|
await postAutomatedTurnLimitNoticeForConversation({
|
|
3440
3330
|
conversationId: args.conversationId,
|
|
3441
3331
|
maxTurns: args.maxTurns,
|
|
3442
3332
|
nowMs: args.nowMs,
|
|
3443
|
-
|
|
3444
|
-
scope: {
|
|
3445
|
-
kind: "conversation",
|
|
3446
|
-
conversationId: args.conversationId
|
|
3447
|
-
}
|
|
3333
|
+
threadTs: args.threadTs
|
|
3448
3334
|
});
|
|
3449
3335
|
}
|
|
3450
3336
|
|
|
@@ -3452,7 +3338,7 @@ async function maybePostAutomatedTurnLimitNotice(args) {
|
|
|
3452
3338
|
function replyContractLines() {
|
|
3453
3339
|
return [
|
|
3454
3340
|
"When you reply, follow any reply format in the instructions.",
|
|
3455
|
-
`If no visible reply is needed,
|
|
3341
|
+
`If no visible reply is needed, your final assistant message must be exactly ${NO_REPLY_MARKER} and nothing else.`,
|
|
3456
3342
|
"Otherwise briefly summarize what you acted on and what you did or need next."
|
|
3457
3343
|
];
|
|
3458
3344
|
}
|
|
@@ -3589,12 +3475,9 @@ function createResourceEventInboundMessage(input) {
|
|
|
3589
3475
|
async function enqueueResourceEventNotification(args) {
|
|
3590
3476
|
const maxTurns = botConfig.maxConsecutiveAutomatedTurns;
|
|
3591
3477
|
const decision = await admitAutomatedTurn({
|
|
3478
|
+
conversationId: args.subscription.conversationId,
|
|
3592
3479
|
maxTurns,
|
|
3593
3480
|
nowMs: args.event.occurredAtMs,
|
|
3594
|
-
scope: {
|
|
3595
|
-
kind: "conversation",
|
|
3596
|
-
conversationId: args.subscription.conversationId
|
|
3597
|
-
},
|
|
3598
3481
|
state: args.state
|
|
3599
3482
|
});
|
|
3600
3483
|
if (decision.status === "paused") {
|
|
@@ -3609,12 +3492,7 @@ async function enqueueResourceEventNotification(args) {
|
|
|
3609
3492
|
await postAutomatedTurnLimitNoticeForConversation({
|
|
3610
3493
|
conversationId: args.subscription.conversationId,
|
|
3611
3494
|
maxTurns,
|
|
3612
|
-
nowMs: args.event.occurredAtMs
|
|
3613
|
-
resumeIn: "thread",
|
|
3614
|
-
scope: {
|
|
3615
|
-
kind: "conversation",
|
|
3616
|
-
conversationId: args.subscription.conversationId
|
|
3617
|
-
}
|
|
3495
|
+
nowMs: args.event.occurredAtMs
|
|
3618
3496
|
});
|
|
3619
3497
|
}
|
|
3620
3498
|
return { status: "duplicate" };
|
|
@@ -14258,12 +14136,19 @@ function stripReplacementHistoryField(value) {
|
|
|
14258
14136
|
// src/chat/tools/handoff/tool.ts
|
|
14259
14137
|
import { z as z59 } from "zod";
|
|
14260
14138
|
var HANDOFF_TOOL_NAME = "handoff";
|
|
14139
|
+
function formatHandoffProfiles(handoff) {
|
|
14140
|
+
return handoff.profiles.map(
|
|
14141
|
+
(profile) => `- ${formatModelProfile(profile.name, profile.description)}`
|
|
14142
|
+
).join("\n");
|
|
14143
|
+
}
|
|
14261
14144
|
function createHandoffTool(handoff) {
|
|
14262
|
-
const profileSchema = z59.enum(
|
|
14145
|
+
const profileSchema = z59.enum(
|
|
14146
|
+
handoff.profiles.map((profile) => profile.name)
|
|
14147
|
+
);
|
|
14263
14148
|
const handoffResultSchema = juniorToolOutputSchema.extend({
|
|
14264
14149
|
model_profile: modelProfileSchema
|
|
14265
14150
|
});
|
|
14266
|
-
const
|
|
14151
|
+
const profileList = formatHandoffProfiles(handoff);
|
|
14267
14152
|
return zodTool({
|
|
14268
14153
|
annotations: {
|
|
14269
14154
|
destructiveHint: false,
|
|
@@ -14271,10 +14156,21 @@ function createHandoffTool(handoff) {
|
|
|
14271
14156
|
openWorldHint: false,
|
|
14272
14157
|
readOnlyHint: false
|
|
14273
14158
|
},
|
|
14274
|
-
description:
|
|
14159
|
+
description: [
|
|
14160
|
+
"Switch this conversation to another configured model profile and continue the same task.",
|
|
14161
|
+
"Call this as the only tool in the assistant turn, before substantial analysis or implementation, when a listed profile's description fits the task better than the current profile.",
|
|
14162
|
+
"Use each description's use and avoid cases. Do not select by the profile name or assume that a non-default profile is stronger.",
|
|
14163
|
+
"Do not call this for ordinary lookups, short answers, light investigation, or only because the task mentions code.",
|
|
14164
|
+
"Do not call this after you have done the difficult work on the current profile. Do not combine it with other tools in the same assistant message.",
|
|
14165
|
+
"A successful handoff becomes the active profile for later turns. Another handoff can change it again.",
|
|
14166
|
+
`Available profiles:
|
|
14167
|
+
${profileList}`
|
|
14168
|
+
].join(" "),
|
|
14275
14169
|
executionMode: "sequential",
|
|
14276
14170
|
inputSchema: z59.object({
|
|
14277
|
-
profile: profileSchema.describe(
|
|
14171
|
+
profile: profileSchema.describe(
|
|
14172
|
+
"Exact configured profile name. Choose the profile whose description best fits the remaining task. If none clearly fits better, do not call handoff."
|
|
14173
|
+
)
|
|
14278
14174
|
}).strict(),
|
|
14279
14175
|
outputSchema: handoffResultSchema,
|
|
14280
14176
|
execute: async (input, options) => {
|
|
@@ -18987,6 +18883,145 @@ ${summary}`
|
|
|
18987
18883
|
return { compacted: true, piMessages: messages };
|
|
18988
18884
|
}
|
|
18989
18885
|
|
|
18886
|
+
// src/chat/agent/handoff.ts
|
|
18887
|
+
var EXCLUSIVE_TOOL_NAMES = [HANDOFF_TOOL_NAME, "switchWorkspace"];
|
|
18888
|
+
function exclusiveToolName(toolCalls) {
|
|
18889
|
+
for (const name of EXCLUSIVE_TOOL_NAMES) {
|
|
18890
|
+
if (toolCalls.some((call) => call.name === name)) {
|
|
18891
|
+
return name;
|
|
18892
|
+
}
|
|
18893
|
+
}
|
|
18894
|
+
return void 0;
|
|
18895
|
+
}
|
|
18896
|
+
function handoffControl(args) {
|
|
18897
|
+
if (!args.enabled) {
|
|
18898
|
+
return void 0;
|
|
18899
|
+
}
|
|
18900
|
+
const profileNames = [
|
|
18901
|
+
botConfig.defaultProfile,
|
|
18902
|
+
...Object.keys(botConfig.profiles).filter((profile) => profile !== botConfig.defaultProfile).sort()
|
|
18903
|
+
].filter((profile) => profile !== args.activeProfile);
|
|
18904
|
+
const [firstProfileName, ...otherProfileNames] = profileNames;
|
|
18905
|
+
if (!firstProfileName) {
|
|
18906
|
+
return void 0;
|
|
18907
|
+
}
|
|
18908
|
+
const toHandoffProfile = (name) => ({
|
|
18909
|
+
name,
|
|
18910
|
+
description: profileConfig(botConfig, name).description
|
|
18911
|
+
});
|
|
18912
|
+
return {
|
|
18913
|
+
profiles: [
|
|
18914
|
+
toHandoffProfile(firstProfileName),
|
|
18915
|
+
...otherProfileNames.map(toHandoffProfile)
|
|
18916
|
+
],
|
|
18917
|
+
execute: args.execute
|
|
18918
|
+
};
|
|
18919
|
+
}
|
|
18920
|
+
function toolsForHandoffProfile(args) {
|
|
18921
|
+
const control = handoffControl({
|
|
18922
|
+
activeProfile: args.activeProfile,
|
|
18923
|
+
enabled: args.enabled,
|
|
18924
|
+
execute: args.execute
|
|
18925
|
+
});
|
|
18926
|
+
if (!control) {
|
|
18927
|
+
return args.agentTools.filter((tool) => tool.name !== HANDOFF_TOOL_NAME);
|
|
18928
|
+
}
|
|
18929
|
+
const handoffAgentTool = args.agentTools.find(
|
|
18930
|
+
(tool) => tool.name === HANDOFF_TOOL_NAME
|
|
18931
|
+
);
|
|
18932
|
+
if (!handoffAgentTool) {
|
|
18933
|
+
throw new Error("Handoff control is missing its Pi tool");
|
|
18934
|
+
}
|
|
18935
|
+
const definition = createHandoffTool(control);
|
|
18936
|
+
return args.agentTools.map(
|
|
18937
|
+
(tool) => tool.name === HANDOFF_TOOL_NAME ? {
|
|
18938
|
+
...handoffAgentTool,
|
|
18939
|
+
description: definition.description,
|
|
18940
|
+
parameters: definition.inputSchema,
|
|
18941
|
+
prepareArguments: definition.prepareArguments
|
|
18942
|
+
} : tool
|
|
18943
|
+
);
|
|
18944
|
+
}
|
|
18945
|
+
async function commitHandoff(args) {
|
|
18946
|
+
if (args.profile === args.activeModelProfile) {
|
|
18947
|
+
return void 0;
|
|
18948
|
+
}
|
|
18949
|
+
const runtimeContext = retainRuntimeTurnContext(
|
|
18950
|
+
args.runtimeContextSourceMessages ?? args.sourceMessages
|
|
18951
|
+
);
|
|
18952
|
+
const phaseUsageSummary = extractGenAiUsageSummary(
|
|
18953
|
+
...args.sourceMessages.slice(args.beforeMessageCount).filter(isAssistantMessage)
|
|
18954
|
+
);
|
|
18955
|
+
const phaseUsage = hasAgentTurnUsage(phaseUsageSummary) ? phaseUsageSummary : void 0;
|
|
18956
|
+
const selectedProfile = profileConfig(botConfig, args.profile);
|
|
18957
|
+
const handoffReasoningLevel = selectedProfile.reasoningLevel ?? args.turnRoute.reasoningLevel;
|
|
18958
|
+
const target = {
|
|
18959
|
+
modelId: selectedProfile.modelId,
|
|
18960
|
+
modelProfile: args.profile,
|
|
18961
|
+
reasoningLevel: handoffReasoningLevel
|
|
18962
|
+
};
|
|
18963
|
+
void (async () => {
|
|
18964
|
+
await args.onStatus?.({ text: "Switching models" });
|
|
18965
|
+
})().catch((error) => {
|
|
18966
|
+
logWarn("assistant.status.observer.failed", {
|
|
18967
|
+
"exception.message": error instanceof Error ? error.message : String(error)
|
|
18968
|
+
});
|
|
18969
|
+
});
|
|
18970
|
+
const handoffMessages = await compactContextForHandoff(
|
|
18971
|
+
{
|
|
18972
|
+
conversationContext: args.conversationContext,
|
|
18973
|
+
conversationId: args.conversationId,
|
|
18974
|
+
piMessages: args.sourceMessages,
|
|
18975
|
+
runtimeContext,
|
|
18976
|
+
signal: args.signal,
|
|
18977
|
+
triggeringToolCallId: args.triggeringToolCallId,
|
|
18978
|
+
target,
|
|
18979
|
+
metadata: args.metadata
|
|
18980
|
+
},
|
|
18981
|
+
{
|
|
18982
|
+
completeText: (completeArgs) => completeText(completeArgs)
|
|
18983
|
+
}
|
|
18984
|
+
);
|
|
18985
|
+
const turnRoute = handoffReasoningLevel === args.turnRoute.reasoningLevel ? args.turnRoute : {
|
|
18986
|
+
...args.turnRoute,
|
|
18987
|
+
reasoningLevel: handoffReasoningLevel,
|
|
18988
|
+
reason: `profile_reasoning_override:${args.profile}:${args.turnRoute.reason}`
|
|
18989
|
+
};
|
|
18990
|
+
return {
|
|
18991
|
+
messages: handoffMessages,
|
|
18992
|
+
model: resolveGatewayModel(target.modelId),
|
|
18993
|
+
modelId: target.modelId,
|
|
18994
|
+
modelProfile: args.profile,
|
|
18995
|
+
phaseUsage,
|
|
18996
|
+
thinkingLevel: toPiReasoningLevel(handoffReasoningLevel),
|
|
18997
|
+
turnRoute
|
|
18998
|
+
};
|
|
18999
|
+
}
|
|
19000
|
+
function applyHandoff(args) {
|
|
19001
|
+
const replacement = [...args.pending.messages];
|
|
19002
|
+
args.agent.state.messages = replacement;
|
|
19003
|
+
args.agent.state.model = args.pending.model;
|
|
19004
|
+
args.agent.state.thinkingLevel = args.pending.thinkingLevel;
|
|
19005
|
+
args.agent.state.tools = args.tools;
|
|
19006
|
+
args.resume.setBeforeMessageCount(replacement.length);
|
|
19007
|
+
args.resume.setTurnStartMessageIndex(0);
|
|
19008
|
+
args.resume.adoptCommittedBoundary(replacement);
|
|
19009
|
+
setSpanAttributes({
|
|
19010
|
+
"gen_ai.agent.model": args.pending.modelId,
|
|
19011
|
+
"gen_ai.agent.model_profile": args.pending.modelProfile,
|
|
19012
|
+
"gen_ai.agent.reasoning.level": args.pending.turnRoute.reasoningLevel
|
|
19013
|
+
});
|
|
19014
|
+
return {
|
|
19015
|
+
context: {
|
|
19016
|
+
systemPrompt: args.baseInstructions,
|
|
19017
|
+
messages: replacement,
|
|
19018
|
+
tools: args.tools
|
|
19019
|
+
},
|
|
19020
|
+
model: args.pending.model,
|
|
19021
|
+
thinkingLevel: args.pending.thinkingLevel
|
|
19022
|
+
};
|
|
19023
|
+
}
|
|
19024
|
+
|
|
18990
19025
|
// src/chat/agent/index.ts
|
|
18991
19026
|
var AGENT_ABORT_SETTLE_GRACE_MS = 5e3;
|
|
18992
19027
|
async function readRunConversationVisibility(conversationId) {
|
|
@@ -19382,93 +19417,56 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
19382
19417
|
activeModelProfile = routedModelProfile;
|
|
19383
19418
|
activeModelId = routedModelId;
|
|
19384
19419
|
let pendingPiHookError;
|
|
19385
|
-
let pendingHandoff;
|
|
19386
19420
|
const currentAgentMessages = () => agent ? [...agent.state.messages] : [];
|
|
19387
|
-
const handoffProfiles = [
|
|
19388
|
-
botConfig.defaultProfile,
|
|
19389
|
-
...Object.keys(botConfig.profiles).filter((profile) => profile !== botConfig.defaultProfile).sort()
|
|
19390
|
-
];
|
|
19391
19421
|
const usageSinceCurrentBoundary = (messages) => {
|
|
19392
19422
|
const usage = extractGenAiUsageSummary(
|
|
19393
19423
|
...messages.slice(runResume.beforeMessageCount).filter(isAssistantMessage)
|
|
19394
19424
|
);
|
|
19395
19425
|
return hasAgentTurnUsage(usage) ? usage : void 0;
|
|
19396
19426
|
};
|
|
19427
|
+
let baseInstructions = "";
|
|
19428
|
+
let pendingHandoff;
|
|
19429
|
+
const handoffMetadata = {
|
|
19430
|
+
threadId: conversationId,
|
|
19431
|
+
channelId: slackChannelId,
|
|
19432
|
+
actorId: slackActor?.userId,
|
|
19433
|
+
runId
|
|
19434
|
+
};
|
|
19397
19435
|
const scheduleHandoff = async (args) => {
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
|
|
19401
|
-
|
|
19402
|
-
|
|
19403
|
-
|
|
19404
|
-
|
|
19405
|
-
|
|
19406
|
-
|
|
19407
|
-
|
|
19408
|
-
|
|
19409
|
-
|
|
19410
|
-
|
|
19411
|
-
};
|
|
19412
|
-
const handoffModel = resolveGatewayModel(target.modelId);
|
|
19413
|
-
const handoffThinkingLevel = toPiReasoningLevel(handoffReasoningLevel);
|
|
19414
|
-
void (async () => {
|
|
19415
|
-
await observers.onStatus?.({ text: "Switching models" });
|
|
19416
|
-
})().catch((error) => {
|
|
19417
|
-
logWarn("assistant.status.observer.failed", {
|
|
19418
|
-
"exception.message": error instanceof Error ? error.message : String(error)
|
|
19419
|
-
});
|
|
19436
|
+
const pending = await commitHandoff({
|
|
19437
|
+
activeModelProfile,
|
|
19438
|
+
beforeMessageCount: runResume.beforeMessageCount,
|
|
19439
|
+
conversationContext: input.conversationContext,
|
|
19440
|
+
conversationId,
|
|
19441
|
+
metadata: handoffMetadata,
|
|
19442
|
+
onStatus: observers.onStatus,
|
|
19443
|
+
profile: args.profile,
|
|
19444
|
+
runtimeContextSourceMessages: args.runtimeContextSourceMessages,
|
|
19445
|
+
signal: args.signal,
|
|
19446
|
+
sourceMessages: args.sourceMessages,
|
|
19447
|
+
triggeringToolCallId: args.triggeringToolCallId,
|
|
19448
|
+
turnRoute
|
|
19420
19449
|
});
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
conversationContext: input.conversationContext,
|
|
19424
|
-
conversationId,
|
|
19425
|
-
piMessages: args.sourceMessages,
|
|
19426
|
-
runtimeContext,
|
|
19427
|
-
signal: args.signal,
|
|
19428
|
-
triggeringToolCallId: args.triggeringToolCallId,
|
|
19429
|
-
target,
|
|
19430
|
-
metadata: {
|
|
19431
|
-
threadId: conversationId,
|
|
19432
|
-
channelId: slackChannelId,
|
|
19433
|
-
actorId: slackActor?.userId,
|
|
19434
|
-
runId
|
|
19435
|
-
}
|
|
19436
|
-
},
|
|
19437
|
-
{
|
|
19438
|
-
completeText: (args2) => completeText(args2)
|
|
19439
|
-
}
|
|
19440
|
-
);
|
|
19441
|
-
if (handoffReasoningLevel !== turnRoute.reasoningLevel) {
|
|
19442
|
-
turnRoute = {
|
|
19443
|
-
...turnRoute,
|
|
19444
|
-
reasoningLevel: handoffReasoningLevel,
|
|
19445
|
-
reason: `profile_reasoning_override:${args.profile}:${turnRoute.reason}`
|
|
19446
|
-
};
|
|
19450
|
+
if (!pending) {
|
|
19451
|
+
return;
|
|
19447
19452
|
}
|
|
19448
|
-
priorPhaseUsage = addAgentTurnUsage(priorPhaseUsage, phaseUsage);
|
|
19449
|
-
pendingHandoff =
|
|
19450
|
-
|
|
19451
|
-
|
|
19452
|
-
|
|
19453
|
-
};
|
|
19454
|
-
activeModelProfile = args.profile;
|
|
19455
|
-
activeModelId = target.modelId;
|
|
19456
|
-
};
|
|
19457
|
-
const handoffControlFor = (activeProfile) => {
|
|
19458
|
-
const profiles = handoffProfiles.filter(
|
|
19459
|
-
(profile) => profile !== activeProfile
|
|
19460
|
-
);
|
|
19461
|
-
return profiles.length > 0 ? {
|
|
19462
|
-
profiles,
|
|
19463
|
-
execute: async (profile, options) => await scheduleHandoff({
|
|
19464
|
-
profile,
|
|
19465
|
-
signal: options.signal,
|
|
19466
|
-
sourceMessages: [...agent.state.messages],
|
|
19467
|
-
triggeringToolCallId: options.toolCallId
|
|
19468
|
-
})
|
|
19469
|
-
} : void 0;
|
|
19453
|
+
priorPhaseUsage = addAgentTurnUsage(priorPhaseUsage, pending.phaseUsage);
|
|
19454
|
+
pendingHandoff = pending;
|
|
19455
|
+
activeModelProfile = pending.modelProfile;
|
|
19456
|
+
activeModelId = pending.modelId;
|
|
19457
|
+
turnRoute = pending.turnRoute;
|
|
19470
19458
|
};
|
|
19471
|
-
const
|
|
19459
|
+
const handoffExecute = async (profile, options) => await scheduleHandoff({
|
|
19460
|
+
profile,
|
|
19461
|
+
signal: options.signal,
|
|
19462
|
+
sourceMessages: [...agent.state.messages],
|
|
19463
|
+
triggeringToolCallId: options.toolCallId
|
|
19464
|
+
});
|
|
19465
|
+
const requestHandoff = handoffControl({
|
|
19466
|
+
activeProfile: activeModelProfile,
|
|
19467
|
+
enabled: handoffEnabled,
|
|
19468
|
+
execute: handoffExecute
|
|
19469
|
+
});
|
|
19472
19470
|
setTags({
|
|
19473
19471
|
...runLogContext,
|
|
19474
19472
|
modelId: activeModelId
|
|
@@ -19529,32 +19527,27 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
19529
19527
|
await recordAgentsTransition(initialRepositoryInstructions);
|
|
19530
19528
|
}
|
|
19531
19529
|
const getPendingAuthPause = wiring.getPendingAuthPause;
|
|
19532
|
-
const
|
|
19533
|
-
(
|
|
19534
|
-
|
|
19535
|
-
|
|
19536
|
-
|
|
19537
|
-
|
|
19538
|
-
|
|
19539
|
-
|
|
19540
|
-
|
|
19541
|
-
|
|
19542
|
-
|
|
19543
|
-
|
|
19544
|
-
|
|
19545
|
-
|
|
19546
|
-
|
|
19547
|
-
|
|
19548
|
-
|
|
19549
|
-
|
|
19550
|
-
description: definition.description,
|
|
19551
|
-
parameters: definition.inputSchema,
|
|
19552
|
-
prepareArguments: definition.prepareArguments
|
|
19553
|
-
} : tool
|
|
19554
|
-
);
|
|
19530
|
+
const applyPendingHandoff = () => {
|
|
19531
|
+
if (!pendingHandoff) {
|
|
19532
|
+
return void 0;
|
|
19533
|
+
}
|
|
19534
|
+
const pending = pendingHandoff;
|
|
19535
|
+
pendingHandoff = void 0;
|
|
19536
|
+
return applyHandoff({
|
|
19537
|
+
agent,
|
|
19538
|
+
baseInstructions,
|
|
19539
|
+
pending,
|
|
19540
|
+
resume: runResume,
|
|
19541
|
+
tools: toolsForHandoffProfile({
|
|
19542
|
+
activeProfile: pending.modelProfile,
|
|
19543
|
+
agentTools: wiring.agentTools,
|
|
19544
|
+
enabled: handoffEnabled,
|
|
19545
|
+
execute: handoffExecute
|
|
19546
|
+
})
|
|
19547
|
+
});
|
|
19555
19548
|
};
|
|
19556
19549
|
const {
|
|
19557
|
-
baseInstructions,
|
|
19550
|
+
baseInstructions: assembledBaseInstructions,
|
|
19558
19551
|
contextContentParts: promptContextContentParts,
|
|
19559
19552
|
inputMessages,
|
|
19560
19553
|
inputMessagesAttribute,
|
|
@@ -19590,6 +19583,7 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
19590
19583
|
toolRuntimeContext: wiring.toolRuntimeContext,
|
|
19591
19584
|
userContentParts
|
|
19592
19585
|
});
|
|
19586
|
+
baseInstructions = assembledBaseInstructions;
|
|
19593
19587
|
const repositoryInstructionsContext = createRepositoryInstructionsContext({
|
|
19594
19588
|
capture: wiring.captureRepositoryInstructions,
|
|
19595
19589
|
hasSandbox: () => Boolean(wiring.getSandboxRef()),
|
|
@@ -19604,36 +19598,6 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
19604
19598
|
shouldPromptAgent
|
|
19605
19599
|
});
|
|
19606
19600
|
runResume.setTurnContexts(turnContexts);
|
|
19607
|
-
const applyPendingHandoff = () => {
|
|
19608
|
-
if (!pendingHandoff) {
|
|
19609
|
-
return void 0;
|
|
19610
|
-
}
|
|
19611
|
-
const { messages, model, thinkingLevel } = pendingHandoff;
|
|
19612
|
-
const replacement = [...messages];
|
|
19613
|
-
pendingHandoff = void 0;
|
|
19614
|
-
agent.state.messages = replacement;
|
|
19615
|
-
agent.state.model = model;
|
|
19616
|
-
agent.state.thinkingLevel = thinkingLevel;
|
|
19617
|
-
const tools = toolsForActiveProfile();
|
|
19618
|
-
agent.state.tools = tools;
|
|
19619
|
-
runResume.setBeforeMessageCount(replacement.length);
|
|
19620
|
-
runResume.setTurnStartMessageIndex(0);
|
|
19621
|
-
runResume.adoptCommittedBoundary(replacement);
|
|
19622
|
-
setSpanAttributes({
|
|
19623
|
-
"gen_ai.agent.model": activeModelId,
|
|
19624
|
-
"gen_ai.agent.model_profile": activeModelProfile,
|
|
19625
|
-
"gen_ai.agent.reasoning.level": turnRoute.reasoningLevel
|
|
19626
|
-
});
|
|
19627
|
-
return {
|
|
19628
|
-
context: {
|
|
19629
|
-
systemPrompt: baseInstructions,
|
|
19630
|
-
messages: replacement,
|
|
19631
|
-
tools
|
|
19632
|
-
},
|
|
19633
|
-
model,
|
|
19634
|
-
thinkingLevel
|
|
19635
|
-
};
|
|
19636
|
-
};
|
|
19637
19601
|
const applyActiveContextCompaction = async (messages, hookSignal, pendingMessages, clearSteeringQueueOnCommit = false, pairPendingRuntimeContext = false) => {
|
|
19638
19602
|
const compaction = await compactActiveContextIfNeeded(
|
|
19639
19603
|
{
|
|
@@ -19764,13 +19728,7 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
19764
19728
|
const toolCalls2 = assistantMessage.content.filter(
|
|
19765
19729
|
(part) => part.type === "toolCall"
|
|
19766
19730
|
);
|
|
19767
|
-
const
|
|
19768
|
-
(call) => call.name === HANDOFF_TOOL_NAME
|
|
19769
|
-
);
|
|
19770
|
-
const containsWorkspaceSwitch = toolCalls2.some(
|
|
19771
|
-
(call) => call.name === "switchWorkspace"
|
|
19772
|
-
);
|
|
19773
|
-
const exclusiveTool = containsHandoff ? HANDOFF_TOOL_NAME : containsWorkspaceSwitch ? "switchWorkspace" : void 0;
|
|
19731
|
+
const exclusiveTool = exclusiveToolName(toolCalls2);
|
|
19774
19732
|
if (exclusiveTool && toolCalls2.length !== 1) {
|
|
19775
19733
|
return {
|
|
19776
19734
|
block: true,
|
|
@@ -19847,10 +19805,9 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
19847
19805
|
if (event.message.stopReason === "error" || event.message.stopReason === "aborted") {
|
|
19848
19806
|
return;
|
|
19849
19807
|
}
|
|
19850
|
-
|
|
19808
|
+
if (event.message.content.some(
|
|
19851
19809
|
(part) => part.type === "toolCall" && part.name === HANDOFF_TOOL_NAME
|
|
19852
|
-
)
|
|
19853
|
-
if (containsHandoff) {
|
|
19810
|
+
)) {
|
|
19854
19811
|
return;
|
|
19855
19812
|
}
|
|
19856
19813
|
return deliverAssistantMessage(event.message);
|
|
@@ -20000,9 +19957,9 @@ async function executeAgentRunInPrivacyContext(run, conversationPrivacy, runLogC
|
|
|
20000
19957
|
}
|
|
20001
19958
|
return result2;
|
|
20002
19959
|
};
|
|
20003
|
-
const requestedProfile = activeModelProfile === botConfig.defaultProfile ? turnRoute.profile : void 0;
|
|
20004
19960
|
let run2;
|
|
20005
19961
|
let handoffApplied = false;
|
|
19962
|
+
const requestedProfile = activeModelProfile === botConfig.defaultProfile ? turnRoute.profile : void 0;
|
|
20006
19963
|
if (requestedProfile && requestedProfile !== botConfig.defaultProfile) {
|
|
20007
19964
|
const handoffAbortController = new AbortController();
|
|
20008
19965
|
await runAgentStep(
|
|
@@ -20296,9 +20253,7 @@ export {
|
|
|
20296
20253
|
normalizeSandboxEgressTracePropagationDomains,
|
|
20297
20254
|
shouldPropagateSandboxEgressTrace,
|
|
20298
20255
|
resolveSandboxEgressProviderForHost,
|
|
20299
|
-
admitAutomatedTurn,
|
|
20300
20256
|
recordFinishedTurnForAutomatedLimit,
|
|
20301
|
-
postAutomatedTurnLimitNoticeForDestination,
|
|
20302
20257
|
maybePostAutomatedTurnLimitNotice,
|
|
20303
20258
|
renderTaskInput,
|
|
20304
20259
|
RESOURCE_EVENT_MESSAGE_AUTHOR,
|