@sentry/junior 0.52.0 → 0.54.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/README.md +3 -2
- package/dist/api-reference.d.ts +7 -0
- package/dist/app.d.ts +5 -10
- package/dist/app.js +1671 -1219
- package/dist/build/copy-build-content.d.ts +4 -0
- package/dist/build/glob-to-regex.d.ts +2 -0
- package/dist/build/rolldown-workarounds.d.ts +14 -0
- package/dist/build/virtual-config.d.ts +4 -0
- package/dist/chat/app/factory.d.ts +10 -0
- package/dist/chat/app/production.d.ts +6 -0
- package/dist/chat/app/services.d.ts +17 -0
- package/dist/chat/capabilities/catalog.d.ts +16 -0
- package/dist/chat/capabilities/factory.d.ts +10 -0
- package/dist/chat/capabilities/jr-rpc-command.d.ts +26 -0
- package/dist/chat/capabilities/router.d.ts +19 -0
- package/dist/chat/coerce.d.ts +6 -0
- package/dist/chat/config.d.ts +46 -0
- package/dist/chat/configuration/defaults.d.ts +4 -0
- package/dist/chat/configuration/service.d.ts +2 -0
- package/dist/chat/configuration/types.d.ts +37 -0
- package/dist/chat/configuration/validation.d.ts +2 -0
- package/dist/chat/credentials/broker.d.ts +22 -0
- package/dist/chat/credentials/header-transforms.d.ts +3 -0
- package/dist/chat/credentials/oauth-scope.d.ts +4 -0
- package/dist/chat/credentials/state-adapter-token-store.d.ts +9 -0
- package/dist/chat/credentials/test-broker.d.ts +19 -0
- package/dist/chat/credentials/unlink-provider.d.ts +2 -0
- package/dist/chat/credentials/user-token-store.d.ts +11 -0
- package/dist/chat/discovery.d.ts +47 -0
- package/dist/chat/ingress/junior-chat.d.ts +26 -0
- package/dist/chat/ingress/message-changed.d.ts +50 -0
- package/dist/chat/ingress/message-router.d.ts +9 -0
- package/dist/chat/ingress/slash-command.d.ts +3 -0
- package/dist/chat/ingress/workspace-membership.d.ts +10 -0
- package/dist/chat/interruption-marker.d.ts +2 -0
- package/dist/chat/logging.d.ts +88 -0
- package/dist/chat/mcp/auth-store.d.ts +41 -0
- package/dist/chat/mcp/client.d.ts +34 -0
- package/dist/chat/mcp/errors.d.ts +8 -0
- package/dist/chat/mcp/oauth-provider.d.ts +27 -0
- package/dist/chat/mcp/oauth.d.ts +17 -0
- package/dist/chat/mcp/tool-manager.d.ts +60 -0
- package/dist/chat/oauth-flow.d.ts +45 -0
- package/dist/chat/optional-string.d.ts +5 -0
- package/dist/chat/pi/client.d.ts +49 -0
- package/dist/chat/pi/messages.d.ts +3 -0
- package/dist/chat/pi/traced-stream.d.ts +9 -0
- package/dist/chat/plugins/auth/api-headers-broker.d.ts +6 -0
- package/dist/chat/plugins/auth/auth-token-placeholder.d.ts +3 -0
- package/dist/chat/plugins/auth/github-app-broker.d.ts +4 -0
- package/dist/chat/plugins/auth/oauth-bearer-broker.d.ts +6 -0
- package/dist/chat/plugins/auth/oauth-request.d.ts +18 -0
- package/dist/chat/plugins/command-env.d.ts +3 -0
- package/dist/chat/plugins/manifest.d.ts +3 -0
- package/dist/chat/plugins/package-discovery.d.ts +14 -0
- package/dist/chat/plugins/registry.d.ts +23 -0
- package/dist/chat/plugins/types.d.ts +146 -0
- package/dist/chat/prompt.d.ts +39 -0
- package/dist/chat/queue/thread-message-dispatcher.d.ts +33 -0
- package/dist/chat/respond-helpers.d.ts +77 -0
- package/dist/chat/respond.d.ts +64 -0
- package/dist/chat/runtime/auth-pause-state.d.ts +11 -0
- package/dist/chat/runtime/delivered-turn-state.d.ts +15 -0
- package/dist/chat/runtime/dev-agent-trace.d.ts +1 -0
- package/dist/chat/runtime/processing-reaction.d.ts +25 -0
- package/dist/chat/runtime/reply-executor.d.ts +56 -0
- package/dist/chat/runtime/report-progress.d.ts +3 -0
- package/dist/chat/runtime/slack-resume.d.ts +50 -0
- package/dist/chat/runtime/slack-runtime.d.ts +100 -0
- package/dist/chat/runtime/thread-context.d.ts +22 -0
- package/dist/chat/runtime/thread-state.d.ts +28 -0
- package/dist/chat/runtime/turn-preparation.d.ts +43 -0
- package/dist/chat/runtime/turn-user-message.d.ts +12 -0
- package/dist/chat/runtime/turn.d.ts +69 -0
- package/dist/chat/sandbox/credentials.d.ts +11 -0
- package/dist/chat/sandbox/egress-oidc.d.ts +3 -0
- package/dist/chat/sandbox/egress-policy.d.ts +11 -0
- package/dist/chat/sandbox/egress-proxy.d.ts +10 -0
- package/dist/chat/sandbox/egress-session.d.ts +27 -0
- package/dist/chat/sandbox/errors.d.ts +12 -0
- package/dist/chat/sandbox/eval-gh-stub.d.ts +2 -0
- package/dist/chat/sandbox/eval-oauth-stub.d.ts +2 -0
- package/dist/chat/sandbox/eval-sentry-stub.d.ts +2 -0
- package/dist/chat/sandbox/fault-injection.d.ts +2 -0
- package/dist/chat/sandbox/http-error-details.d.ts +18 -0
- package/dist/chat/sandbox/noninteractive-command.d.ts +17 -0
- package/dist/chat/sandbox/paths.d.ts +5 -0
- package/dist/chat/sandbox/runtime-dependency-snapshots.d.ts +20 -0
- package/dist/chat/sandbox/sandbox.d.ts +52 -0
- package/dist/chat/sandbox/session.d.ts +53 -0
- package/dist/chat/sandbox/skill-sandbox.d.ts +42 -0
- package/dist/chat/sandbox/skill-sync.d.ts +17 -0
- package/dist/chat/sandbox/workspace.d.ts +55 -0
- package/dist/chat/sentry.d.ts +2 -0
- package/dist/chat/services/attachment-claims.d.ts +2 -0
- package/dist/chat/services/auth-pause-response.d.ts +2 -0
- package/dist/chat/services/auth-pause.d.ts +12 -0
- package/dist/chat/services/channel-intent.d.ts +2 -0
- package/dist/chat/services/conversation-memory.d.ts +33 -0
- package/dist/chat/services/mcp-auth-orchestration.d.ts +29 -0
- package/dist/chat/services/pending-auth.d.ts +27 -0
- package/dist/chat/services/plugin-auth-orchestration.d.ts +36 -0
- package/dist/chat/services/provider-default-config.d.ts +9 -0
- package/dist/chat/services/provider-retry.d.ts +6 -0
- package/dist/chat/services/reply-delivery-plan.d.ts +17 -0
- package/dist/chat/services/subscribed-decision.d.ts +63 -0
- package/dist/chat/services/subscribed-reply-policy.d.ts +23 -0
- package/dist/chat/services/timeout-resume.d.ts +17 -0
- package/dist/chat/services/turn-checkpoint.d.ts +74 -0
- package/dist/chat/services/turn-continuation-response.d.ts +2 -0
- package/dist/chat/services/turn-failure-response.d.ts +15 -0
- package/dist/chat/services/turn-result.d.ts +55 -0
- package/dist/chat/services/turn-thinking-level.d.ts +49 -0
- package/dist/chat/services/vision-context.d.ts +61 -0
- package/dist/chat/skills.d.ts +48 -0
- package/dist/chat/slack/adapter.d.ts +9 -0
- package/dist/chat/slack/app-home.d.ts +11 -0
- package/dist/chat/slack/assistant-thread/lifecycle.d.ts +13 -0
- package/dist/chat/slack/assistant-thread/status-render.d.ts +28 -0
- package/dist/chat/slack/assistant-thread/status-scheduler.d.ts +23 -0
- package/dist/chat/slack/assistant-thread/status-send.d.ts +31 -0
- package/dist/chat/slack/assistant-thread/status.d.ts +36 -0
- package/dist/chat/slack/assistant-thread/title.d.ts +28 -0
- package/dist/chat/slack/canvas-references.d.ts +2 -0
- package/dist/chat/slack/channel.d.ts +48 -0
- package/dist/chat/slack/client.d.ts +52 -0
- package/dist/chat/slack/context.d.ts +9 -0
- package/dist/chat/slack/emoji.d.ts +1 -0
- package/dist/chat/slack/errors.d.ts +6 -0
- package/dist/chat/slack/footer.d.ts +42 -0
- package/dist/chat/slack/legacy-attachments.d.ts +4 -0
- package/dist/chat/slack/message.d.ts +6 -0
- package/dist/chat/slack/mrkdwn.d.ts +12 -0
- package/dist/chat/slack/outbound.d.ts +57 -0
- package/dist/chat/slack/output.d.ts +54 -0
- package/dist/chat/slack/reply.d.ts +33 -0
- package/dist/chat/slack/status-format.d.ts +2 -0
- package/dist/chat/slack/turn-continuation-notice.d.ts +8 -0
- package/dist/chat/slack/user.d.ts +7 -0
- package/dist/chat/slack/users.d.ts +39 -0
- package/dist/chat/state/adapter.d.ts +9 -0
- package/dist/chat/state/artifacts.d.ts +29 -0
- package/dist/chat/state/conversation.d.ts +81 -0
- package/dist/chat/state/pi-session-message-store.d.ts +15 -0
- package/dist/chat/state/turn-id.d.ts +2 -0
- package/dist/chat/state/turn-session-store.d.ts +49 -0
- package/dist/chat/tools/advisor/session-store.d.ts +9 -0
- package/dist/chat/tools/advisor/tool.d.ts +33 -0
- package/dist/chat/tools/agent-tools.d.ts +9 -0
- package/dist/chat/tools/channel-capabilities.d.ts +11 -0
- package/dist/chat/tools/definition.d.ts +17 -0
- package/dist/chat/tools/execution/build-sandbox-input.d.ts +2 -0
- package/dist/chat/tools/execution/normalize-result.d.ts +6 -0
- package/dist/chat/tools/execution/tool-error-handler.d.ts +3 -0
- package/dist/chat/tools/execution/tool-input-error.d.ts +6 -0
- package/dist/chat/tools/idempotency.d.ts +1 -0
- package/dist/chat/tools/index.d.ts +5 -0
- package/dist/chat/tools/runtime/report-progress.d.ts +4 -0
- package/dist/chat/tools/sandbox/attach-file.d.ts +7 -0
- package/dist/chat/tools/sandbox/bash.d.ts +5 -0
- package/dist/chat/tools/sandbox/edit-file.d.ts +37 -0
- package/dist/chat/tools/sandbox/file-utils.d.ts +52 -0
- package/dist/chat/tools/sandbox/find-files.d.ts +24 -0
- package/dist/chat/tools/sandbox/grep.d.ts +33 -0
- package/dist/chat/tools/sandbox/list-dir.d.ts +22 -0
- package/dist/chat/tools/sandbox/read-file.d.ts +32 -0
- package/dist/chat/tools/sandbox/text-edits.d.ts +28 -0
- package/dist/chat/tools/sandbox/write-file.d.ts +5 -0
- package/dist/chat/tools/skill/call-mcp-tool.d.ts +7 -0
- package/dist/chat/tools/skill/load-skill.d.ts +22 -0
- package/dist/chat/tools/skill/mcp-tool-summary.d.ts +31 -0
- package/dist/chat/tools/skill/search-mcp-tools.d.ts +8 -0
- package/dist/chat/tools/slack/canvas-tools.d.ts +29 -0
- package/dist/chat/tools/slack/canvases.d.ts +45 -0
- package/dist/chat/tools/slack/channel-list-messages.d.ts +9 -0
- package/dist/chat/tools/slack/channel-post-message.d.ts +4 -0
- package/dist/chat/tools/slack/list-tools.d.ts +21 -0
- package/dist/chat/tools/slack/lists.d.ts +42 -0
- package/dist/chat/tools/slack/message-add-reaction.d.ts +4 -0
- package/dist/chat/tools/slack/slack-message-url.d.ts +18 -0
- package/dist/chat/tools/slack/thread-read.d.ts +9 -0
- package/dist/chat/tools/slack/user-lookup.d.ts +8 -0
- package/dist/chat/tools/system-time.d.ts +1 -0
- package/dist/chat/tools/types.d.ts +55 -0
- package/dist/chat/tools/web/constants.d.ts +6 -0
- package/dist/chat/tools/web/fetch-content.d.ts +23 -0
- package/dist/chat/tools/web/fetch-tool.d.ts +5 -0
- package/dist/chat/tools/web/image-generate.d.ts +4 -0
- package/dist/chat/tools/web/network.d.ts +6 -0
- package/dist/chat/tools/web/search.d.ts +5 -0
- package/dist/chat/turn-context-tag.d.ts +6 -0
- package/dist/chat/usage.d.ts +24 -0
- package/dist/chat/xml.d.ts +1 -0
- package/dist/{chunk-XPXD3FCE.js → chunk-5LUISFEY.js} +189 -35
- package/dist/{chunk-KCOKQLBF.js → chunk-7WTXNEPF.js} +120 -15
- package/dist/{chunk-ZNFNY53B.js → chunk-QCHPJ4FD.js} +2 -2
- package/dist/{chunk-Q3FDONU7.js → chunk-YITDDLS3.js} +34 -28
- package/dist/cli/check.js +3 -3
- package/dist/cli/init.js +1 -0
- package/dist/cli/snapshot-warmup.js +3 -3
- package/dist/handlers/diagnostics-dashboard.d.ts +2 -0
- package/dist/handlers/diagnostics.d.ts +2 -0
- package/dist/handlers/health.d.ts +4 -0
- package/dist/handlers/mcp-oauth-callback.d.ts +2 -0
- package/dist/handlers/oauth-callback.d.ts +2 -0
- package/dist/handlers/oauth-html.d.ts +2 -0
- package/dist/handlers/sandbox-egress-proxy.d.ts +4 -0
- package/dist/handlers/turn-resume.d.ts +3 -0
- package/dist/handlers/types.d.ts +2 -0
- package/dist/handlers/webhooks.d.ts +15 -0
- package/dist/instrumentation.d.ts +1 -3
- package/dist/nitro.d.ts +4 -7
- package/dist/nitro.js +112 -54
- package/dist/package-resolution.d.ts +13 -0
- package/dist/vercel.d.ts +2 -4
- package/package.json +25 -25
- package/dist/cli/check.d.ts +0 -8
- package/dist/cli/env.d.ts +0 -7
- package/dist/cli/init.d.ts +0 -3
- package/dist/cli/run.d.ts +0 -12
- package/dist/cli/snapshot-warmup.d.ts +0 -3
- package/dist/types-X_iCClPb.d.ts +0 -75
package/dist/app.js
CHANGED
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
findSkillByName,
|
|
4
4
|
loadSkillsByName,
|
|
5
5
|
parseSkillInvocation
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QCHPJ4FD.js";
|
|
7
7
|
import {
|
|
8
|
+
ACTIVE_LOCK_TTL_MS,
|
|
8
9
|
GEN_AI_PROVIDER_NAME,
|
|
9
10
|
MISSING_GATEWAY_CREDENTIALS_ERROR,
|
|
10
11
|
SANDBOX_DATA_ROOT,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
runNonInteractiveCommand,
|
|
32
33
|
sandboxSkillDir,
|
|
33
34
|
sandboxSkillFile
|
|
34
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-7WTXNEPF.js";
|
|
35
36
|
import {
|
|
36
37
|
CredentialUnavailableError,
|
|
37
38
|
buildOAuthTokenRequest,
|
|
@@ -48,6 +49,7 @@ import {
|
|
|
48
49
|
getPluginDefinition,
|
|
49
50
|
getPluginMcpProviders,
|
|
50
51
|
getPluginOAuthConfig,
|
|
52
|
+
getPluginPackageContent,
|
|
51
53
|
getPluginProviders,
|
|
52
54
|
hasRequiredOAuthScope,
|
|
53
55
|
isPluginConfigKey,
|
|
@@ -70,18 +72,16 @@ import {
|
|
|
70
72
|
toOptionalString,
|
|
71
73
|
withContext,
|
|
72
74
|
withSpan
|
|
73
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-YITDDLS3.js";
|
|
74
76
|
import {
|
|
75
77
|
sentry_exports
|
|
76
78
|
} from "./chunk-Z3YD6NHK.js";
|
|
77
79
|
import {
|
|
78
|
-
discoverInstalledPluginPackageContent,
|
|
79
80
|
homeDir,
|
|
80
81
|
listReferenceFiles,
|
|
81
|
-
setPluginPackages,
|
|
82
82
|
soulPathCandidates,
|
|
83
83
|
worldPathCandidates
|
|
84
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-5LUISFEY.js";
|
|
85
85
|
import "./chunk-2KG3PWR4.js";
|
|
86
86
|
|
|
87
87
|
// src/app.ts
|
|
@@ -89,11 +89,22 @@ import { Hono } from "hono";
|
|
|
89
89
|
|
|
90
90
|
// src/chat/configuration/defaults.ts
|
|
91
91
|
var installDefaults = {};
|
|
92
|
+
function cloneDefaults(defaults) {
|
|
93
|
+
return structuredClone(defaults);
|
|
94
|
+
}
|
|
95
|
+
function isConfigDefaultsRecord(defaults) {
|
|
96
|
+
return typeof defaults === "object" && defaults !== null && !Array.isArray(defaults);
|
|
97
|
+
}
|
|
92
98
|
function setConfigDefaults(defaults) {
|
|
93
|
-
if (
|
|
99
|
+
if (defaults === void 0) {
|
|
94
100
|
installDefaults = {};
|
|
95
101
|
return;
|
|
96
102
|
}
|
|
103
|
+
if (!isConfigDefaultsRecord(defaults)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
"configDefaults must be an object keyed by plugin config key"
|
|
106
|
+
);
|
|
107
|
+
}
|
|
97
108
|
for (const key of Object.keys(defaults)) {
|
|
98
109
|
if (!isPluginConfigKey(key)) {
|
|
99
110
|
throw new Error(
|
|
@@ -101,10 +112,10 @@ function setConfigDefaults(defaults) {
|
|
|
101
112
|
);
|
|
102
113
|
}
|
|
103
114
|
}
|
|
104
|
-
installDefaults =
|
|
115
|
+
installDefaults = cloneDefaults(defaults);
|
|
105
116
|
}
|
|
106
117
|
function getConfigDefaults() {
|
|
107
|
-
return installDefaults;
|
|
118
|
+
return cloneDefaults(installDefaults);
|
|
108
119
|
}
|
|
109
120
|
|
|
110
121
|
// src/handlers/diagnostics.ts
|
|
@@ -122,7 +133,7 @@ function readDescriptionText() {
|
|
|
122
133
|
}
|
|
123
134
|
}
|
|
124
135
|
async function GET() {
|
|
125
|
-
const packagedContent =
|
|
136
|
+
const packagedContent = getPluginPackageContent();
|
|
126
137
|
const skills = await discoverSkills();
|
|
127
138
|
return Response.json({
|
|
128
139
|
cwd: process.cwd(),
|
|
@@ -494,6 +505,7 @@ function coerceThreadConversationState(value) {
|
|
|
494
505
|
const processing = {
|
|
495
506
|
activeTurnId: toOptionalString(rawProcessing.activeTurnId),
|
|
496
507
|
lastCompletedAtMs: toOptionalNumber(rawProcessing.lastCompletedAtMs),
|
|
508
|
+
lastSessionId: toOptionalString(rawProcessing.lastSessionId),
|
|
497
509
|
pendingAuth: coercePendingAuthState(rawProcessing.pendingAuth)
|
|
498
510
|
};
|
|
499
511
|
const rawStats = isRecord(rawConversation.stats) ? rawConversation.stats : {};
|
|
@@ -1478,6 +1490,9 @@ var StateBackedMcpOAuthClientProvider = class {
|
|
|
1478
1490
|
this.sessionContext = sessionContext;
|
|
1479
1491
|
this.clientMetadata = createClientMetadata(callbackUrl);
|
|
1480
1492
|
}
|
|
1493
|
+
authSessionId;
|
|
1494
|
+
callbackUrl;
|
|
1495
|
+
sessionContext;
|
|
1481
1496
|
clientMetadata;
|
|
1482
1497
|
get redirectUrl() {
|
|
1483
1498
|
return this.callbackUrl;
|
|
@@ -2101,17 +2116,24 @@ function startActiveTurn(args) {
|
|
|
2101
2116
|
args.conversation.processing.activeTurnId = args.nextTurnId;
|
|
2102
2117
|
args.updateConversationStats(args.conversation);
|
|
2103
2118
|
}
|
|
2104
|
-
function
|
|
2105
|
-
if (!
|
|
2106
|
-
|
|
2119
|
+
function clearActiveTurn(conversation, sessionId) {
|
|
2120
|
+
if (!sessionId || conversation.processing.activeTurnId === sessionId) {
|
|
2121
|
+
conversation.processing.activeTurnId = void 0;
|
|
2107
2122
|
}
|
|
2123
|
+
}
|
|
2124
|
+
function markTurnClosed(args) {
|
|
2125
|
+
clearActiveTurn(args.conversation, args.sessionId);
|
|
2126
|
+
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
2127
|
+
args.updateConversationStats(args.conversation);
|
|
2128
|
+
}
|
|
2129
|
+
function markTurnCompleted(args) {
|
|
2130
|
+
clearActiveTurn(args.conversation, args.sessionId);
|
|
2131
|
+
args.conversation.processing.lastSessionId = args.sessionId;
|
|
2108
2132
|
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
2109
2133
|
args.updateConversationStats(args.conversation);
|
|
2110
2134
|
}
|
|
2111
2135
|
function markTurnFailed(args) {
|
|
2112
|
-
|
|
2113
|
-
args.conversation.processing.activeTurnId = void 0;
|
|
2114
|
-
}
|
|
2136
|
+
clearActiveTurn(args.conversation, args.sessionId);
|
|
2115
2137
|
args.conversation.processing.lastCompletedAtMs = args.nowMs;
|
|
2116
2138
|
args.markConversationMessage(args.conversation, args.userMessageId, {
|
|
2117
2139
|
replied: false,
|
|
@@ -2120,39 +2142,6 @@ function markTurnFailed(args) {
|
|
|
2120
2142
|
args.updateConversationStats(args.conversation);
|
|
2121
2143
|
}
|
|
2122
2144
|
|
|
2123
|
-
// src/chat/runtime/turn-user-message.ts
|
|
2124
|
-
function normalizeSlackMessageTs(value) {
|
|
2125
|
-
const trimmed = value?.trim();
|
|
2126
|
-
return trimmed && /^\d+(?:\.\d+)?$/.test(trimmed) ? trimmed : void 0;
|
|
2127
|
-
}
|
|
2128
|
-
function getTurnUserMessage(conversation, sessionId) {
|
|
2129
|
-
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
2130
|
-
const message = conversation.messages[index];
|
|
2131
|
-
if (message?.role !== "user") {
|
|
2132
|
-
continue;
|
|
2133
|
-
}
|
|
2134
|
-
if (buildDeterministicTurnId(message.id) === sessionId) {
|
|
2135
|
-
return message;
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
return void 0;
|
|
2139
|
-
}
|
|
2140
|
-
function getTurnUserMessageId(conversation, sessionId) {
|
|
2141
|
-
return getTurnUserMessage(conversation, sessionId)?.id;
|
|
2142
|
-
}
|
|
2143
|
-
function getTurnUserSlackMessageTs(message) {
|
|
2144
|
-
return normalizeSlackMessageTs(message?.meta?.slackTs) ?? normalizeSlackMessageTs(message?.id);
|
|
2145
|
-
}
|
|
2146
|
-
function getTurnUserReplyAttachmentContext(message) {
|
|
2147
|
-
const inboundAttachmentCount = message?.meta?.attachmentCount ?? 0;
|
|
2148
|
-
const imageAttachmentCount = message?.meta?.imageAttachmentCount ?? 0;
|
|
2149
|
-
const imagesHydrated = message?.meta?.imagesHydrated === true;
|
|
2150
|
-
return {
|
|
2151
|
-
...inboundAttachmentCount > 0 ? { inboundAttachmentCount } : {},
|
|
2152
|
-
...!imagesHydrated && imageAttachmentCount > 0 ? { omittedImageAttachmentCount: imageAttachmentCount } : {}
|
|
2153
|
-
};
|
|
2154
|
-
}
|
|
2155
|
-
|
|
2156
2145
|
// src/chat/services/conversation-memory.ts
|
|
2157
2146
|
var CONTEXT_COMPACTION_TRIGGER_TOKENS = 9e3;
|
|
2158
2147
|
var CONTEXT_COMPACTION_TARGET_TOKENS = 7e3;
|
|
@@ -2443,141 +2432,578 @@ function getConversationMessageSlackTs(message) {
|
|
|
2443
2432
|
return message.meta?.slackTs ?? toOptionalString(message.id);
|
|
2444
2433
|
}
|
|
2445
2434
|
|
|
2446
|
-
// src/chat/
|
|
2447
|
-
import {
|
|
2448
|
-
|
|
2449
|
-
// src/chat/prompt.ts
|
|
2450
|
-
import fs from "fs";
|
|
2451
|
-
import path2 from "path";
|
|
2435
|
+
// src/chat/state/turn-session-store.ts
|
|
2436
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS2 } from "chat";
|
|
2452
2437
|
|
|
2453
|
-
// src/chat/
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2438
|
+
// src/chat/state/pi-session-message-store.ts
|
|
2439
|
+
import { isDeepStrictEqual } from "util";
|
|
2440
|
+
var PI_SESSION_MESSAGE_PREFIX = "junior:pi_session_message";
|
|
2441
|
+
function piSessionMessageKey(scope, index) {
|
|
2442
|
+
return `${PI_SESSION_MESSAGE_PREFIX}:${scope.conversationId}:${scope.sessionId}:${index}`;
|
|
2443
|
+
}
|
|
2444
|
+
function parsePiMessage(value) {
|
|
2445
|
+
return isRecord(value) ? value : void 0;
|
|
2446
|
+
}
|
|
2447
|
+
function normalizeMessageCount(value) {
|
|
2448
|
+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
2449
|
+
}
|
|
2450
|
+
function countMatchingPrefix(left, right) {
|
|
2451
|
+
const limit = Math.min(left.length, right.length);
|
|
2452
|
+
for (let index = 0; index < limit; index += 1) {
|
|
2453
|
+
if (!isDeepStrictEqual(left[index], right[index])) {
|
|
2454
|
+
return index;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
return limit;
|
|
2457
2458
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
return "";
|
|
2459
|
+
async function loadPiSessionMessages(args) {
|
|
2460
|
+
const stateAdapter = getStateAdapter();
|
|
2461
|
+
await stateAdapter.connect();
|
|
2462
|
+
const messageCount = normalizeMessageCount(args.messageCount);
|
|
2463
|
+
if (messageCount === 0) {
|
|
2464
|
+
return [];
|
|
2465
2465
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2466
|
+
const values = await Promise.all(
|
|
2467
|
+
Array.from(
|
|
2468
|
+
{ length: messageCount },
|
|
2469
|
+
(_, index) => stateAdapter.get(piSessionMessageKey(args, index))
|
|
2470
|
+
)
|
|
2471
|
+
);
|
|
2472
|
+
const messages = [];
|
|
2473
|
+
for (const value of values) {
|
|
2474
|
+
const message = parsePiMessage(value);
|
|
2475
|
+
if (!message) {
|
|
2476
|
+
break;
|
|
2477
|
+
}
|
|
2478
|
+
messages.push(message);
|
|
2468
2479
|
}
|
|
2469
|
-
return
|
|
2480
|
+
return messages.length === messageCount ? messages : void 0;
|
|
2470
2481
|
}
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
const
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
inCodeBlock = !inCodeBlock;
|
|
2490
|
-
result.push(line);
|
|
2491
|
-
continue;
|
|
2492
|
-
}
|
|
2493
|
-
if (inCodeBlock) {
|
|
2494
|
-
result.push(line);
|
|
2495
|
-
continue;
|
|
2496
|
-
}
|
|
2497
|
-
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
2498
|
-
if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
|
|
2499
|
-
result.push("");
|
|
2482
|
+
async function loadExistingPiSessionMessages(scope, maxCount) {
|
|
2483
|
+
const count = normalizeMessageCount(maxCount);
|
|
2484
|
+
if (count === 0) {
|
|
2485
|
+
return [];
|
|
2486
|
+
}
|
|
2487
|
+
const stateAdapter = getStateAdapter();
|
|
2488
|
+
await stateAdapter.connect();
|
|
2489
|
+
const values = await Promise.all(
|
|
2490
|
+
Array.from(
|
|
2491
|
+
{ length: count },
|
|
2492
|
+
(_, index) => stateAdapter.get(piSessionMessageKey(scope, index))
|
|
2493
|
+
)
|
|
2494
|
+
);
|
|
2495
|
+
const messages = [];
|
|
2496
|
+
for (const value of values) {
|
|
2497
|
+
const message = parsePiMessage(value);
|
|
2498
|
+
if (!message) {
|
|
2499
|
+
break;
|
|
2500
2500
|
}
|
|
2501
|
-
|
|
2501
|
+
messages.push(message);
|
|
2502
2502
|
}
|
|
2503
|
-
return
|
|
2504
|
-
}
|
|
2505
|
-
function renderSlackMrkdwn(text) {
|
|
2506
|
-
let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
|
|
2507
|
-
normalized = ensureBlockSpacing(normalized);
|
|
2508
|
-
return normalized.replace(/\n{3,}/g, "\n\n").trim();
|
|
2503
|
+
return messages;
|
|
2509
2504
|
}
|
|
2510
|
-
function
|
|
2511
|
-
const
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2505
|
+
async function commitPiSessionMessages(args) {
|
|
2506
|
+
const stateAdapter = getStateAdapter();
|
|
2507
|
+
await stateAdapter.connect();
|
|
2508
|
+
const existingMessages = await loadExistingPiSessionMessages(
|
|
2509
|
+
{ conversationId: args.conversationId, sessionId: args.sessionId },
|
|
2510
|
+
args.messages.length
|
|
2511
|
+
);
|
|
2512
|
+
const writeFromIndex = countMatchingPrefix(existingMessages, args.messages);
|
|
2513
|
+
await Promise.all(
|
|
2514
|
+
args.messages.slice(writeFromIndex).map(
|
|
2515
|
+
(message, offset) => stateAdapter.set(
|
|
2516
|
+
piSessionMessageKey(args, writeFromIndex + offset),
|
|
2517
|
+
message,
|
|
2518
|
+
args.ttlMs
|
|
2519
|
+
)
|
|
2520
|
+
)
|
|
2521
|
+
);
|
|
2516
2522
|
}
|
|
2517
2523
|
|
|
2518
|
-
// src/chat/
|
|
2519
|
-
var
|
|
2520
|
-
var
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
if (!text) {
|
|
2524
|
-
return 0;
|
|
2525
|
-
}
|
|
2526
|
-
return text.split("\n").length;
|
|
2524
|
+
// src/chat/state/turn-session-store.ts
|
|
2525
|
+
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
2526
|
+
var AGENT_TURN_SESSION_TTL_MS = THREAD_STATE_TTL_MS2;
|
|
2527
|
+
function agentTurnSessionKey(conversationId, sessionId) {
|
|
2528
|
+
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
2527
2529
|
}
|
|
2528
|
-
function
|
|
2529
|
-
return
|
|
2530
|
+
function toFiniteNonNegativeNumber(value) {
|
|
2531
|
+
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
2530
2532
|
}
|
|
2531
|
-
function
|
|
2532
|
-
if (
|
|
2533
|
-
return
|
|
2534
|
-
}
|
|
2535
|
-
const bounded = text.slice(0, maxChars);
|
|
2536
|
-
const newlineIndex = bounded.lastIndexOf("\n");
|
|
2537
|
-
if (newlineIndex > 0) {
|
|
2538
|
-
return newlineIndex;
|
|
2533
|
+
function parseAgentTurnUsage(value) {
|
|
2534
|
+
if (!isRecord(value)) {
|
|
2535
|
+
return void 0;
|
|
2539
2536
|
}
|
|
2540
|
-
const
|
|
2541
|
-
|
|
2542
|
-
|
|
2537
|
+
const usage = {};
|
|
2538
|
+
for (const field of [
|
|
2539
|
+
"inputTokens",
|
|
2540
|
+
"outputTokens",
|
|
2541
|
+
"cachedInputTokens",
|
|
2542
|
+
"cacheCreationTokens",
|
|
2543
|
+
"totalTokens"
|
|
2544
|
+
]) {
|
|
2545
|
+
const count = toFiniteNonNegativeNumber(value[field]);
|
|
2546
|
+
if (count !== void 0) {
|
|
2547
|
+
usage[field] = count;
|
|
2548
|
+
}
|
|
2543
2549
|
}
|
|
2544
|
-
return
|
|
2550
|
+
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
2545
2551
|
}
|
|
2546
|
-
function
|
|
2547
|
-
if (
|
|
2548
|
-
return
|
|
2552
|
+
function parseStoredRecord(value) {
|
|
2553
|
+
if (isRecord(value)) {
|
|
2554
|
+
return value;
|
|
2549
2555
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2556
|
+
if (typeof value !== "string") {
|
|
2557
|
+
return void 0;
|
|
2558
|
+
}
|
|
2559
|
+
try {
|
|
2560
|
+
const parsed = JSON.parse(value);
|
|
2561
|
+
return isRecord(parsed) ? parsed : void 0;
|
|
2562
|
+
} catch {
|
|
2563
|
+
return void 0;
|
|
2553
2564
|
}
|
|
2554
|
-
return lines.slice(0, maxLines).join("\n");
|
|
2555
2565
|
}
|
|
2556
|
-
function
|
|
2566
|
+
function parseAgentTurnSessionRecord(value) {
|
|
2567
|
+
const parsed = parseStoredRecord(value);
|
|
2568
|
+
if (!parsed) {
|
|
2569
|
+
return void 0;
|
|
2570
|
+
}
|
|
2571
|
+
const status = parsed.state;
|
|
2572
|
+
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
2573
|
+
return void 0;
|
|
2574
|
+
}
|
|
2575
|
+
const conversationId = parsed.conversationId;
|
|
2576
|
+
const sessionId = parsed.sessionId;
|
|
2577
|
+
const sliceId = parsed.sliceId;
|
|
2578
|
+
const checkpointVersion = parsed.checkpointVersion;
|
|
2579
|
+
const updatedAtMs = parsed.updatedAtMs;
|
|
2580
|
+
const cumulativeDurationMs = toFiniteNonNegativeNumber(
|
|
2581
|
+
parsed.cumulativeDurationMs
|
|
2582
|
+
);
|
|
2583
|
+
const cumulativeUsage = parseAgentTurnUsage(parsed.cumulativeUsage);
|
|
2584
|
+
if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
|
|
2585
|
+
return void 0;
|
|
2586
|
+
}
|
|
2587
|
+
const legacyPiMessages = Array.isArray(parsed.piMessages) ? parsed.piMessages : [];
|
|
2588
|
+
const messageCount = toFiniteNonNegativeNumber(parsed.messageCount) ?? legacyPiMessages.length;
|
|
2557
2589
|
return {
|
|
2558
|
-
|
|
2559
|
-
|
|
2590
|
+
legacyPiMessages,
|
|
2591
|
+
record: {
|
|
2592
|
+
checkpointVersion,
|
|
2593
|
+
conversationId,
|
|
2594
|
+
sessionId,
|
|
2595
|
+
sliceId,
|
|
2596
|
+
state: status,
|
|
2597
|
+
updatedAtMs,
|
|
2598
|
+
messageCount,
|
|
2599
|
+
...cumulativeDurationMs !== void 0 ? { cumulativeDurationMs } : {},
|
|
2600
|
+
...cumulativeUsage ? { cumulativeUsage } : {},
|
|
2601
|
+
...Array.isArray(parsed.loadedSkillNames) ? {
|
|
2602
|
+
loadedSkillNames: parsed.loadedSkillNames.filter(
|
|
2603
|
+
(value2) => typeof value2 === "string"
|
|
2604
|
+
)
|
|
2605
|
+
} : {},
|
|
2606
|
+
...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
|
|
2607
|
+
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
2608
|
+
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
|
|
2609
|
+
}
|
|
2560
2610
|
};
|
|
2561
2611
|
}
|
|
2562
|
-
function
|
|
2563
|
-
|
|
2612
|
+
function materializePiMessages(legacyPiMessages, messageCount, sessionMessages) {
|
|
2613
|
+
if (messageCount === 0) {
|
|
2614
|
+
return [];
|
|
2615
|
+
}
|
|
2616
|
+
if (sessionMessages) {
|
|
2617
|
+
return sessionMessages;
|
|
2618
|
+
}
|
|
2619
|
+
if (legacyPiMessages.length >= messageCount) {
|
|
2620
|
+
return legacyPiMessages.slice(0, messageCount);
|
|
2621
|
+
}
|
|
2622
|
+
return void 0;
|
|
2623
|
+
}
|
|
2624
|
+
async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
|
|
2625
|
+
const stateAdapter = getStateAdapter();
|
|
2626
|
+
await stateAdapter.connect();
|
|
2627
|
+
const value = await stateAdapter.get(
|
|
2628
|
+
agentTurnSessionKey(conversationId, sessionId)
|
|
2629
|
+
);
|
|
2630
|
+
const parsed = parseAgentTurnSessionRecord(value);
|
|
2631
|
+
if (!parsed) {
|
|
2632
|
+
return void 0;
|
|
2633
|
+
}
|
|
2634
|
+
const sessionMessages = await loadPiSessionMessages({
|
|
2635
|
+
conversationId,
|
|
2636
|
+
sessionId,
|
|
2637
|
+
messageCount: parsed.record.messageCount
|
|
2638
|
+
});
|
|
2639
|
+
const piMessages = materializePiMessages(
|
|
2640
|
+
parsed.legacyPiMessages,
|
|
2641
|
+
parsed.record.messageCount,
|
|
2642
|
+
sessionMessages
|
|
2643
|
+
);
|
|
2644
|
+
if (!piMessages) {
|
|
2645
|
+
return void 0;
|
|
2646
|
+
}
|
|
2564
2647
|
return {
|
|
2565
|
-
|
|
2566
|
-
|
|
2648
|
+
...parsed.record,
|
|
2649
|
+
piMessages
|
|
2567
2650
|
};
|
|
2568
2651
|
}
|
|
2569
|
-
function
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2652
|
+
async function upsertAgentTurnSessionCheckpoint(args) {
|
|
2653
|
+
const stateAdapter = getStateAdapter();
|
|
2654
|
+
await stateAdapter.connect();
|
|
2655
|
+
const existingValue = await stateAdapter.get(
|
|
2656
|
+
agentTurnSessionKey(args.conversationId, args.sessionId)
|
|
2657
|
+
);
|
|
2658
|
+
const existingRecord = parseAgentTurnSessionRecord(existingValue);
|
|
2659
|
+
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
2660
|
+
await commitPiSessionMessages({
|
|
2661
|
+
conversationId: args.conversationId,
|
|
2662
|
+
sessionId: args.sessionId,
|
|
2663
|
+
messages: args.piMessages,
|
|
2664
|
+
ttlMs
|
|
2665
|
+
});
|
|
2666
|
+
const storedMessageCount = args.piMessages.length;
|
|
2667
|
+
const checkpoint = {
|
|
2668
|
+
checkpointVersion: (existingRecord?.record.checkpointVersion ?? 0) + 1,
|
|
2669
|
+
conversationId: args.conversationId,
|
|
2670
|
+
sessionId: args.sessionId,
|
|
2671
|
+
sliceId: args.sliceId,
|
|
2672
|
+
state: args.state,
|
|
2673
|
+
updatedAtMs: Date.now(),
|
|
2674
|
+
messageCount: storedMessageCount,
|
|
2675
|
+
...typeof args.cumulativeDurationMs === "number" && Number.isFinite(args.cumulativeDurationMs) ? {
|
|
2676
|
+
cumulativeDurationMs: Math.max(
|
|
2677
|
+
0,
|
|
2678
|
+
Math.floor(args.cumulativeDurationMs)
|
|
2679
|
+
)
|
|
2680
|
+
} : {},
|
|
2681
|
+
...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
|
|
2682
|
+
...Array.isArray(args.loadedSkillNames) ? {
|
|
2683
|
+
loadedSkillNames: args.loadedSkillNames.filter(
|
|
2684
|
+
(value) => typeof value === "string"
|
|
2685
|
+
)
|
|
2686
|
+
} : {},
|
|
2687
|
+
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
2688
|
+
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
2689
|
+
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
|
|
2690
|
+
};
|
|
2691
|
+
await stateAdapter.set(
|
|
2692
|
+
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
2693
|
+
checkpoint,
|
|
2694
|
+
ttlMs
|
|
2695
|
+
);
|
|
2696
|
+
return {
|
|
2697
|
+
...checkpoint,
|
|
2698
|
+
piMessages: [...args.piMessages]
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
async function supersedeAgentTurnSessionCheckpoint(args) {
|
|
2702
|
+
const existing = await getAgentTurnSessionCheckpoint(
|
|
2703
|
+
args.conversationId,
|
|
2704
|
+
args.sessionId
|
|
2705
|
+
);
|
|
2706
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
2707
|
+
return void 0;
|
|
2708
|
+
}
|
|
2709
|
+
return await upsertAgentTurnSessionCheckpoint({
|
|
2710
|
+
conversationId: existing.conversationId,
|
|
2711
|
+
sessionId: existing.sessionId,
|
|
2712
|
+
sliceId: existing.sliceId,
|
|
2713
|
+
state: "superseded",
|
|
2714
|
+
piMessages: existing.piMessages,
|
|
2715
|
+
cumulativeDurationMs: existing.cumulativeDurationMs,
|
|
2716
|
+
cumulativeUsage: existing.cumulativeUsage,
|
|
2717
|
+
loadedSkillNames: existing.loadedSkillNames,
|
|
2718
|
+
resumeReason: existing.resumeReason,
|
|
2719
|
+
resumedFromSliceId: existing.resumedFromSliceId,
|
|
2720
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
async function failAgentTurnSessionCheckpoint(args) {
|
|
2724
|
+
const existing = await getAgentTurnSessionCheckpoint(
|
|
2725
|
+
args.conversationId,
|
|
2726
|
+
args.sessionId
|
|
2727
|
+
);
|
|
2728
|
+
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded" || typeof args.expectedCheckpointVersion === "number" && existing.checkpointVersion !== args.expectedCheckpointVersion) {
|
|
2729
|
+
return void 0;
|
|
2730
|
+
}
|
|
2731
|
+
return await upsertAgentTurnSessionCheckpoint({
|
|
2732
|
+
conversationId: existing.conversationId,
|
|
2733
|
+
sessionId: existing.sessionId,
|
|
2734
|
+
sliceId: existing.sliceId,
|
|
2735
|
+
state: "failed",
|
|
2736
|
+
piMessages: existing.piMessages,
|
|
2737
|
+
cumulativeDurationMs: existing.cumulativeDurationMs,
|
|
2738
|
+
cumulativeUsage: existing.cumulativeUsage,
|
|
2739
|
+
loadedSkillNames: existing.loadedSkillNames,
|
|
2740
|
+
resumeReason: existing.resumeReason,
|
|
2741
|
+
resumedFromSliceId: existing.resumedFromSliceId,
|
|
2742
|
+
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
2743
|
+
});
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
// src/chat/services/pending-auth.ts
|
|
2747
|
+
var AUTH_LINK_REUSE_WINDOW_MS = 10 * 60 * 1e3;
|
|
2748
|
+
function canReusePendingAuthLink(args) {
|
|
2749
|
+
const { pendingAuth } = args;
|
|
2750
|
+
if (!pendingAuth) {
|
|
2751
|
+
return false;
|
|
2752
|
+
}
|
|
2753
|
+
return pendingAuth.kind === args.kind && pendingAuth.provider === args.provider && pendingAuth.requesterId === args.requesterId && pendingAuth.linkSentAtMs + AUTH_LINK_REUSE_WINDOW_MS > (args.nowMs ?? Date.now());
|
|
2754
|
+
}
|
|
2755
|
+
function getConversationPendingAuth(args) {
|
|
2756
|
+
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
2757
|
+
if (!pendingAuth) {
|
|
2758
|
+
return void 0;
|
|
2759
|
+
}
|
|
2760
|
+
if (pendingAuth.kind !== args.kind || pendingAuth.provider !== args.provider || pendingAuth.requesterId !== args.requesterId) {
|
|
2761
|
+
return void 0;
|
|
2762
|
+
}
|
|
2763
|
+
return pendingAuth;
|
|
2764
|
+
}
|
|
2765
|
+
function clearPendingAuth(conversation, sessionId) {
|
|
2766
|
+
if (!conversation.processing.pendingAuth) {
|
|
2767
|
+
return;
|
|
2768
|
+
}
|
|
2769
|
+
if (sessionId && conversation.processing.pendingAuth.sessionId !== sessionId) {
|
|
2770
|
+
return;
|
|
2771
|
+
}
|
|
2772
|
+
conversation.processing.pendingAuth = void 0;
|
|
2773
|
+
}
|
|
2774
|
+
async function applyPendingAuthUpdate(args) {
|
|
2775
|
+
const previousPendingAuth = args.conversation.processing.pendingAuth;
|
|
2776
|
+
args.conversation.processing.pendingAuth = args.nextPendingAuth;
|
|
2777
|
+
if (previousPendingAuth && previousPendingAuth.sessionId !== args.nextPendingAuth.sessionId && args.conversationId) {
|
|
2778
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
2779
|
+
conversationId: args.conversationId,
|
|
2780
|
+
sessionId: previousPendingAuth.sessionId,
|
|
2781
|
+
errorMessage: "Superseded by a newer auth-blocked request in the same conversation."
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
function isPendingAuthLatestRequest(conversation, pendingAuth) {
|
|
2786
|
+
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
2787
|
+
const message = conversation.messages[index];
|
|
2788
|
+
if (message?.role !== "user") {
|
|
2789
|
+
continue;
|
|
2790
|
+
}
|
|
2791
|
+
return buildDeterministicTurnId(message.id) === pendingAuth.sessionId;
|
|
2792
|
+
}
|
|
2793
|
+
return false;
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
// src/chat/runtime/delivered-turn-state.ts
|
|
2797
|
+
function buildDeliveredTurnStatePatch(args) {
|
|
2798
|
+
const conversation = structuredClone(args.conversation);
|
|
2799
|
+
const artifactStatePatch = {
|
|
2800
|
+
...args.reply.artifactStatePatch ?? {},
|
|
2801
|
+
...args.artifactStatePatch ?? {}
|
|
2802
|
+
};
|
|
2803
|
+
const artifacts = Object.keys(artifactStatePatch).length > 0 ? mergeArtifactsState(args.artifacts, artifactStatePatch) : void 0;
|
|
2804
|
+
clearPendingAuth(conversation, args.sessionId);
|
|
2805
|
+
markConversationMessage(conversation, args.userMessageId, {
|
|
2806
|
+
replied: true,
|
|
2807
|
+
skippedReason: void 0
|
|
2808
|
+
});
|
|
2809
|
+
upsertConversationMessage(conversation, {
|
|
2810
|
+
id: generateConversationId("assistant"),
|
|
2811
|
+
role: "assistant",
|
|
2812
|
+
text: normalizeConversationText(args.reply.text) || "[empty response]",
|
|
2813
|
+
createdAtMs: Date.now(),
|
|
2814
|
+
author: {
|
|
2815
|
+
userName: botConfig.userName,
|
|
2816
|
+
isBot: true
|
|
2817
|
+
},
|
|
2818
|
+
meta: {
|
|
2819
|
+
replied: true
|
|
2820
|
+
}
|
|
2821
|
+
});
|
|
2822
|
+
markTurnCompleted({
|
|
2823
|
+
conversation,
|
|
2824
|
+
nowMs: Date.now(),
|
|
2825
|
+
sessionId: args.sessionId,
|
|
2826
|
+
updateConversationStats
|
|
2827
|
+
});
|
|
2828
|
+
return {
|
|
2829
|
+
artifacts,
|
|
2830
|
+
conversation,
|
|
2831
|
+
sandboxId: args.reply.sandboxId,
|
|
2832
|
+
sandboxDependencyProfileHash: args.reply.sandboxDependencyProfileHash
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
|
|
2836
|
+
// src/chat/runtime/turn-user-message.ts
|
|
2837
|
+
function normalizeSlackMessageTs(value) {
|
|
2838
|
+
const trimmed = value?.trim();
|
|
2839
|
+
return trimmed && /^\d+(?:\.\d+)?$/.test(trimmed) ? trimmed : void 0;
|
|
2840
|
+
}
|
|
2841
|
+
function getTurnUserMessage(conversation, sessionId) {
|
|
2842
|
+
for (let index = conversation.messages.length - 1; index >= 0; index -= 1) {
|
|
2843
|
+
const message = conversation.messages[index];
|
|
2844
|
+
if (message?.role !== "user") {
|
|
2845
|
+
continue;
|
|
2846
|
+
}
|
|
2847
|
+
if (buildDeterministicTurnId(message.id) === sessionId) {
|
|
2848
|
+
return message;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
return void 0;
|
|
2852
|
+
}
|
|
2853
|
+
function getTurnUserMessageId(conversation, sessionId) {
|
|
2854
|
+
return getTurnUserMessage(conversation, sessionId)?.id;
|
|
2855
|
+
}
|
|
2856
|
+
function getTurnUserSlackMessageTs(message) {
|
|
2857
|
+
return normalizeSlackMessageTs(message?.meta?.slackTs) ?? normalizeSlackMessageTs(message?.id);
|
|
2858
|
+
}
|
|
2859
|
+
function getTurnUserReplyAttachmentContext(message) {
|
|
2860
|
+
const inboundAttachmentCount = message?.meta?.attachmentCount ?? 0;
|
|
2861
|
+
const imageAttachmentCount = message?.meta?.imageAttachmentCount ?? 0;
|
|
2862
|
+
const imagesHydrated = message?.meta?.imagesHydrated === true;
|
|
2863
|
+
return {
|
|
2864
|
+
...inboundAttachmentCount > 0 ? { inboundAttachmentCount } : {},
|
|
2865
|
+
...!imagesHydrated && imageAttachmentCount > 0 ? { omittedImageAttachmentCount: imageAttachmentCount } : {}
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
// src/chat/respond.ts
|
|
2870
|
+
import { Agent as Agent2 } from "@earendil-works/pi-agent-core";
|
|
2871
|
+
|
|
2872
|
+
// src/chat/prompt.ts
|
|
2873
|
+
import fs from "fs";
|
|
2874
|
+
import path2 from "path";
|
|
2875
|
+
|
|
2876
|
+
// src/chat/turn-context-tag.ts
|
|
2877
|
+
var TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
2878
|
+
|
|
2879
|
+
// src/chat/interruption-marker.ts
|
|
2880
|
+
var INTERRUPTED_MARKER = "\n\n[Response interrupted before completion]";
|
|
2881
|
+
function getInterruptionMarker() {
|
|
2882
|
+
return INTERRUPTED_MARKER;
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
// src/chat/slack/status-format.ts
|
|
2886
|
+
var SLACK_STATUS_MAX_LENGTH = 50;
|
|
2887
|
+
function truncateStatusText(text) {
|
|
2888
|
+
const trimmed = text.trim();
|
|
2889
|
+
if (!trimmed) {
|
|
2890
|
+
return "";
|
|
2891
|
+
}
|
|
2892
|
+
if (trimmed.length <= SLACK_STATUS_MAX_LENGTH) {
|
|
2893
|
+
return trimmed;
|
|
2894
|
+
}
|
|
2895
|
+
return `${trimmed.slice(0, SLACK_STATUS_MAX_LENGTH - 3).trimEnd()}...`;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
// src/chat/slack/mrkdwn.ts
|
|
2899
|
+
function ensureBlockSpacing(text) {
|
|
2900
|
+
const codeBlockPattern = /^```/;
|
|
2901
|
+
const listItemPattern = /^[-*•]\s|^\d+\.\s/;
|
|
2902
|
+
const lines = text.split("\n");
|
|
2903
|
+
const result = [];
|
|
2904
|
+
let inCodeBlock = false;
|
|
2905
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2906
|
+
const line = lines[i];
|
|
2907
|
+
const isCodeFence = codeBlockPattern.test(line.trimStart());
|
|
2908
|
+
if (isCodeFence) {
|
|
2909
|
+
if (!inCodeBlock) {
|
|
2910
|
+
const prev2 = result.length > 0 ? result[result.length - 1] : void 0;
|
|
2911
|
+
if (prev2 !== void 0 && prev2.trim() !== "") {
|
|
2912
|
+
result.push("");
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
inCodeBlock = !inCodeBlock;
|
|
2916
|
+
result.push(line);
|
|
2917
|
+
continue;
|
|
2918
|
+
}
|
|
2919
|
+
if (inCodeBlock) {
|
|
2920
|
+
result.push(line);
|
|
2921
|
+
continue;
|
|
2922
|
+
}
|
|
2923
|
+
const prev = result.length > 0 ? result[result.length - 1] : void 0;
|
|
2924
|
+
if (prev !== void 0 && prev.trim() !== "" && line.trim() !== "" && !(listItemPattern.test(prev.trimStart()) && listItemPattern.test(line.trimStart()))) {
|
|
2925
|
+
result.push("");
|
|
2926
|
+
}
|
|
2927
|
+
result.push(line);
|
|
2928
|
+
}
|
|
2929
|
+
return result.join("\n");
|
|
2930
|
+
}
|
|
2931
|
+
function renderSlackMrkdwn(text) {
|
|
2932
|
+
let normalized = text.replace(/\r\n?/g, "\n").replace(/[ \t]+$/gm, "");
|
|
2933
|
+
normalized = ensureBlockSpacing(normalized);
|
|
2934
|
+
return normalized.replace(/\n{3,}/g, "\n\n").trim();
|
|
2935
|
+
}
|
|
2936
|
+
function normalizeSlackStatusText(text) {
|
|
2937
|
+
const trimmed = text.trim();
|
|
2938
|
+
if (!trimmed) {
|
|
2939
|
+
return "";
|
|
2940
|
+
}
|
|
2941
|
+
return truncateStatusText(trimmed.replace(/(?:\.\s*)+$/, "").trim());
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
// src/chat/slack/output.ts
|
|
2945
|
+
var MAX_INLINE_CHARS = 2200;
|
|
2946
|
+
var MAX_INLINE_LINES = 45;
|
|
2947
|
+
var CONTINUED_MARKER = "\n\n[Continued below]";
|
|
2948
|
+
function countSlackLines(text) {
|
|
2949
|
+
if (!text) {
|
|
2950
|
+
return 0;
|
|
2951
|
+
}
|
|
2952
|
+
return text.split("\n").length;
|
|
2953
|
+
}
|
|
2954
|
+
function fitsInlineBudget(text, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
2955
|
+
return text.length <= maxChars && countSlackLines(text) <= maxLines;
|
|
2956
|
+
}
|
|
2957
|
+
function findSplitIndex(text, maxChars) {
|
|
2958
|
+
if (text.length <= maxChars) {
|
|
2959
|
+
return text.length;
|
|
2960
|
+
}
|
|
2961
|
+
const bounded = text.slice(0, maxChars);
|
|
2962
|
+
const newlineIndex = bounded.lastIndexOf("\n");
|
|
2963
|
+
if (newlineIndex > 0) {
|
|
2964
|
+
return newlineIndex;
|
|
2965
|
+
}
|
|
2966
|
+
const spaceIndex = bounded.lastIndexOf(" ");
|
|
2967
|
+
if (spaceIndex > 0) {
|
|
2968
|
+
return spaceIndex;
|
|
2969
|
+
}
|
|
2970
|
+
return maxChars;
|
|
2971
|
+
}
|
|
2972
|
+
function splitByLineBudget(text, maxLines) {
|
|
2973
|
+
if (maxLines <= 0) {
|
|
2974
|
+
return "";
|
|
2975
|
+
}
|
|
2976
|
+
const lines = text.split("\n");
|
|
2977
|
+
if (lines.length <= maxLines) {
|
|
2978
|
+
return text;
|
|
2979
|
+
}
|
|
2980
|
+
return lines.slice(0, maxLines).join("\n");
|
|
2981
|
+
}
|
|
2982
|
+
function reserveInlineBudgetForSuffix(suffix, maxChars = MAX_INLINE_CHARS, maxLines = MAX_INLINE_LINES) {
|
|
2983
|
+
return {
|
|
2984
|
+
maxChars: Math.max(1, maxChars - suffix.length),
|
|
2985
|
+
maxLines: Math.max(1, maxLines - Math.max(0, countSlackLines(suffix) - 1))
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
function forceSplitBudget(text, budget) {
|
|
2989
|
+
const lineCount = countSlackLines(text);
|
|
2990
|
+
return {
|
|
2991
|
+
maxChars: text.length <= budget.maxChars ? Math.max(1, text.length - 1) : budget.maxChars,
|
|
2992
|
+
maxLines: lineCount <= budget.maxLines ? Math.max(1, lineCount - 1) : budget.maxLines
|
|
2993
|
+
};
|
|
2994
|
+
}
|
|
2995
|
+
function getFenceContinuation(text) {
|
|
2996
|
+
let open;
|
|
2997
|
+
for (const line of text.split("\n")) {
|
|
2998
|
+
const trimmed = line.trimStart();
|
|
2999
|
+
const openerMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
|
|
3000
|
+
if (!openerMatch) {
|
|
3001
|
+
continue;
|
|
3002
|
+
}
|
|
3003
|
+
if (!open) {
|
|
3004
|
+
open = {
|
|
3005
|
+
fence: openerMatch[1],
|
|
3006
|
+
openerLine: trimmed
|
|
2581
3007
|
};
|
|
2582
3008
|
continue;
|
|
2583
3009
|
}
|
|
@@ -2859,17 +3285,17 @@ function formatAvailableSkillsForPrompt(skills, invocation) {
|
|
|
2859
3285
|
(s) => s.disableModelInvocation === true && s.name === invocation.skillName
|
|
2860
3286
|
) : [];
|
|
2861
3287
|
const sections = [];
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
3288
|
+
if (autoSelectable.length > 0) {
|
|
3289
|
+
const available = [
|
|
3290
|
+
"<available-skills>",
|
|
2865
3291
|
"Scan before answering. Load the most specific matching skill; do not answer from memory when a skill fits. If none fits, do not load a skill."
|
|
2866
|
-
]
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
3292
|
+
];
|
|
3293
|
+
for (const skill of autoSelectable) {
|
|
3294
|
+
available.push(...formatSkillEntry(skill));
|
|
3295
|
+
}
|
|
3296
|
+
available.push("</available-skills>");
|
|
3297
|
+
sections.push(available.join("\n"));
|
|
2870
3298
|
}
|
|
2871
|
-
available.push("</available-skills>");
|
|
2872
|
-
sections.push(available.join("\n"));
|
|
2873
3299
|
if (invokedExplicitOnly.length > 0) {
|
|
2874
3300
|
const userCallable = [
|
|
2875
3301
|
"<user-callable-skills>",
|
|
@@ -2881,11 +3307,11 @@ function formatAvailableSkillsForPrompt(skills, invocation) {
|
|
|
2881
3307
|
userCallable.push("</user-callable-skills>");
|
|
2882
3308
|
sections.push(userCallable.join("\n"));
|
|
2883
3309
|
}
|
|
2884
|
-
return sections.join("\n");
|
|
3310
|
+
return sections.length > 0 ? sections.join("\n") : null;
|
|
2885
3311
|
}
|
|
2886
3312
|
function formatLoadedSkillsForPrompt(skills) {
|
|
2887
3313
|
if (skills.length === 0) {
|
|
2888
|
-
return
|
|
3314
|
+
return null;
|
|
2889
3315
|
}
|
|
2890
3316
|
const lines = ["<loaded-skills>"];
|
|
2891
3317
|
for (const skill of skills) {
|
|
@@ -3010,17 +3436,8 @@ function formatConfigurationLines(configuration) {
|
|
|
3010
3436
|
(key) => `- ${escapeXml(key)}: ${formatConfigurationValue(configuration?.[key])}`
|
|
3011
3437
|
);
|
|
3012
3438
|
}
|
|
3013
|
-
function formatSlackCapabilityNames(capabilities) {
|
|
3014
|
-
const names = [
|
|
3015
|
-
capabilities?.canCreateCanvas ? "canvas_create" : "",
|
|
3016
|
-
capabilities?.canPostToChannel ? "channel_post" : "",
|
|
3017
|
-
capabilities?.canAddReactions ? "reaction_add" : ""
|
|
3018
|
-
].filter(Boolean);
|
|
3019
|
-
return names.length > 0 ? names.join(", ") : "none";
|
|
3020
|
-
}
|
|
3021
3439
|
var HEADER = "You are a Slack-based helper assistant. Follow the personality block for voice and tone in every reply. The behavior and output blocks define platform mechanics and override personality only when those mechanics conflict.";
|
|
3022
3440
|
var TURN_CONTEXT_HEADER = "Per-turn runtime context for this request. Treat these blocks as trusted runtime facts and skill/provider instructions for the current turn; the static system prompt remains authoritative.";
|
|
3023
|
-
var TURN_CONTEXT_TAG = "runtime-turn-context";
|
|
3024
3441
|
var TOOL_POLICY_RULES = [
|
|
3025
3442
|
"- Tool schemas are the source of truth for parameters; tool names are case-sensitive, so call tools exactly by their exposed names and do not invent arguments.",
|
|
3026
3443
|
"- Use tools for actionable work and for facts that are mutable, external, repository-backed, provider-backed, or requested as verified/current. Stable general knowledge and already-provided context may be answered directly.",
|
|
@@ -3108,16 +3525,12 @@ function buildIdentitySection() {
|
|
|
3108
3525
|
}
|
|
3109
3526
|
function buildRuntimeSection(params) {
|
|
3110
3527
|
const lines = [
|
|
3111
|
-
`-
|
|
3112
|
-
params.
|
|
3113
|
-
params.fastModelId ? `- fast_model: ${escapeXml(params.fastModelId)}` : "",
|
|
3114
|
-
params.thinkingLevel ? `- thinking: ${escapeXml(params.thinkingLevel)}` : "",
|
|
3115
|
-
params.channelId ? "- channel: slack" : "",
|
|
3116
|
-
params.channelId ? `- slack_capabilities: ${escapeXml(
|
|
3117
|
-
formatSlackCapabilityNames(params.slackCapabilities)
|
|
3118
|
-
)}` : "",
|
|
3119
|
-
`- sandbox_workspace: ${escapeXml(SANDBOX_WORKSPACE_ROOT)}`
|
|
3528
|
+
params.conversationId ? `- gen_ai.conversation.id: ${escapeXml(params.conversationId)}` : "",
|
|
3529
|
+
params.traceId ? `- trace_id: ${escapeXml(params.traceId)}` : ""
|
|
3120
3530
|
].filter(Boolean);
|
|
3531
|
+
if (lines.length === 0) {
|
|
3532
|
+
return null;
|
|
3533
|
+
}
|
|
3121
3534
|
return renderTagBlock("runtime", lines.join("\n"));
|
|
3122
3535
|
}
|
|
3123
3536
|
function buildContextSection(params) {
|
|
@@ -3170,14 +3583,24 @@ function buildContextSection(params) {
|
|
|
3170
3583
|
);
|
|
3171
3584
|
}
|
|
3172
3585
|
const body = blocks.map((block) => block.join("\n")).join("\n\n");
|
|
3586
|
+
if (!body) {
|
|
3587
|
+
return null;
|
|
3588
|
+
}
|
|
3173
3589
|
return renderTagBlock("context", body);
|
|
3174
3590
|
}
|
|
3175
3591
|
function buildCapabilitiesSection(params) {
|
|
3176
3592
|
const blocks = [];
|
|
3177
|
-
|
|
3178
|
-
|
|
3593
|
+
const availableSkills = formatAvailableSkillsForPrompt(
|
|
3594
|
+
params.availableSkills,
|
|
3595
|
+
params.invocation
|
|
3179
3596
|
);
|
|
3180
|
-
|
|
3597
|
+
if (availableSkills) {
|
|
3598
|
+
blocks.push(availableSkills);
|
|
3599
|
+
}
|
|
3600
|
+
const loadedSkills = formatLoadedSkillsForPrompt(params.activeSkills);
|
|
3601
|
+
if (loadedSkills) {
|
|
3602
|
+
blocks.push(loadedSkills);
|
|
3603
|
+
}
|
|
3181
3604
|
const activeCatalogs = formatActiveMcpCatalogsForPrompt(
|
|
3182
3605
|
params.activeMcpCatalogs
|
|
3183
3606
|
);
|
|
@@ -3192,6 +3615,9 @@ function buildCapabilitiesSection(params) {
|
|
|
3192
3615
|
if (providerCatalog) {
|
|
3193
3616
|
blocks.push(renderTagBlock("providers", providerCatalog));
|
|
3194
3617
|
}
|
|
3618
|
+
if (blocks.length === 0) {
|
|
3619
|
+
return null;
|
|
3620
|
+
}
|
|
3195
3621
|
return renderTagBlock("capabilities", blocks.join("\n\n"));
|
|
3196
3622
|
}
|
|
3197
3623
|
var STATIC_SYSTEM_PROMPT = [
|
|
@@ -3225,7 +3651,7 @@ function buildTurnContextPrompt(params) {
|
|
|
3225
3651
|
}),
|
|
3226
3652
|
buildRuntimeSection(params.runtime ?? {}),
|
|
3227
3653
|
`</${TURN_CONTEXT_TAG}>`
|
|
3228
|
-
];
|
|
3654
|
+
].filter((section) => Boolean(section));
|
|
3229
3655
|
return sections.join("\n\n");
|
|
3230
3656
|
}
|
|
3231
3657
|
|
|
@@ -3939,6 +4365,8 @@ var PluginMcpClient = class {
|
|
|
3939
4365
|
this.plugin = plugin;
|
|
3940
4366
|
this.options = options;
|
|
3941
4367
|
}
|
|
4368
|
+
plugin;
|
|
4369
|
+
options;
|
|
3942
4370
|
client;
|
|
3943
4371
|
lastAttemptedTransportSessionId;
|
|
3944
4372
|
transport;
|
|
@@ -4220,6 +4648,7 @@ var McpToolManager = class {
|
|
|
4220
4648
|
}
|
|
4221
4649
|
}
|
|
4222
4650
|
}
|
|
4651
|
+
options;
|
|
4223
4652
|
pluginsByProvider = /* @__PURE__ */ new Map();
|
|
4224
4653
|
activeProviders = /* @__PURE__ */ new Set();
|
|
4225
4654
|
authorizationPendingProviders = /* @__PURE__ */ new Set();
|
|
@@ -7877,11 +8306,12 @@ function createSystemTimeTool() {
|
|
|
7877
8306
|
// src/chat/tools/advisor/tool.ts
|
|
7878
8307
|
import {
|
|
7879
8308
|
Agent
|
|
7880
|
-
} from "@
|
|
8309
|
+
} from "@earendil-works/pi-agent-core";
|
|
7881
8310
|
import { Type as Type21 } from "@sinclair/typebox";
|
|
7882
8311
|
|
|
7883
8312
|
// src/chat/respond-helpers.ts
|
|
7884
8313
|
var MAX_INLINE_ATTACHMENT_BASE64_CHARS = 12e4;
|
|
8314
|
+
var RUNTIME_TURN_CONTEXT_START = `<${TURN_CONTEXT_TAG}>`;
|
|
7885
8315
|
function getSessionIdentifiers(context) {
|
|
7886
8316
|
return {
|
|
7887
8317
|
conversationId: context.correlation?.conversationId ?? context.correlation?.threadId ?? context.correlation?.runId,
|
|
@@ -7961,44 +8391,20 @@ function summarizeMessageText(text) {
|
|
|
7961
8391
|
}
|
|
7962
8392
|
return normalized.length > 1200 ? `${normalized.slice(0, 1200)}...` : normalized;
|
|
7963
8393
|
}
|
|
7964
|
-
function buildUserTurnText(userInput, conversationContext
|
|
8394
|
+
function buildUserTurnText(userInput, conversationContext) {
|
|
7965
8395
|
const trimmedContext = conversationContext?.trim();
|
|
7966
|
-
|
|
7967
|
-
const traceId = metadata?.turnContext?.traceId;
|
|
7968
|
-
if (!trimmedContext && !conversationId && !traceId) {
|
|
8396
|
+
if (!trimmedContext) {
|
|
7969
8397
|
return userInput;
|
|
7970
8398
|
}
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
""
|
|
7978
|
-
);
|
|
7979
|
-
}
|
|
7980
|
-
if (conversationId) {
|
|
7981
|
-
sections.push(
|
|
7982
|
-
"<session-context>",
|
|
7983
|
-
`- gen_ai.conversation.id: ${conversationId}`,
|
|
7984
|
-
"</session-context>",
|
|
7985
|
-
""
|
|
7986
|
-
);
|
|
7987
|
-
}
|
|
7988
|
-
if (traceId) {
|
|
7989
|
-
sections.push(
|
|
7990
|
-
"<turn-context>",
|
|
7991
|
-
`- trace_id: ${traceId}`,
|
|
7992
|
-
"</turn-context>",
|
|
7993
|
-
""
|
|
7994
|
-
);
|
|
7995
|
-
}
|
|
7996
|
-
sections.push(
|
|
7997
|
-
'<current-instruction priority="highest">',
|
|
8399
|
+
return [
|
|
8400
|
+
"<thread-background>",
|
|
8401
|
+
trimmedContext,
|
|
8402
|
+
"</thread-background>",
|
|
8403
|
+
"",
|
|
8404
|
+
"<current-instruction>",
|
|
7998
8405
|
userInput,
|
|
7999
8406
|
"</current-instruction>"
|
|
8000
|
-
);
|
|
8001
|
-
return sections.join("\n");
|
|
8407
|
+
].join("\n");
|
|
8002
8408
|
}
|
|
8003
8409
|
function encodeNonImageAttachmentForPrompt(attachment) {
|
|
8004
8410
|
const base64 = attachment.data.toString("base64");
|
|
@@ -8037,12 +8443,81 @@ function isToolResultError(result) {
|
|
|
8037
8443
|
function isAssistantMessage(value) {
|
|
8038
8444
|
return typeof value === "object" && value !== null && value.role === "assistant";
|
|
8039
8445
|
}
|
|
8040
|
-
function getPiMessageRole(value) {
|
|
8041
|
-
if (!value || typeof value !== "object") {
|
|
8042
|
-
return void 0;
|
|
8043
|
-
}
|
|
8044
|
-
const role = value.role;
|
|
8045
|
-
return typeof role === "string" ? role : void 0;
|
|
8446
|
+
function getPiMessageRole(value) {
|
|
8447
|
+
if (!value || typeof value !== "object") {
|
|
8448
|
+
return void 0;
|
|
8449
|
+
}
|
|
8450
|
+
const role = value.role;
|
|
8451
|
+
return typeof role === "string" ? role : void 0;
|
|
8452
|
+
}
|
|
8453
|
+
function getUserMessageContent(message) {
|
|
8454
|
+
const record = message;
|
|
8455
|
+
return record.role === "user" && Array.isArray(record.content) ? record.content : void 0;
|
|
8456
|
+
}
|
|
8457
|
+
function isRuntimeTurnContextPart(part, marker) {
|
|
8458
|
+
return part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.startsWith(marker);
|
|
8459
|
+
}
|
|
8460
|
+
function replaceRuntimeTurnContext(message, turnContextPrompt) {
|
|
8461
|
+
const content = getUserMessageContent(message);
|
|
8462
|
+
if (!content) {
|
|
8463
|
+
return void 0;
|
|
8464
|
+
}
|
|
8465
|
+
const marker = turnContextPrompt.split("\n", 1)[0];
|
|
8466
|
+
const contextIndex = content.findIndex(
|
|
8467
|
+
(part) => isRuntimeTurnContextPart(part, marker)
|
|
8468
|
+
);
|
|
8469
|
+
if (contextIndex < 0) {
|
|
8470
|
+
return void 0;
|
|
8471
|
+
}
|
|
8472
|
+
const nextContent = [...content];
|
|
8473
|
+
nextContent[contextIndex] = {
|
|
8474
|
+
...nextContent[contextIndex],
|
|
8475
|
+
text: turnContextPrompt
|
|
8476
|
+
};
|
|
8477
|
+
return {
|
|
8478
|
+
...message,
|
|
8479
|
+
content: nextContent
|
|
8480
|
+
};
|
|
8481
|
+
}
|
|
8482
|
+
function refreshRuntimeTurnContext(messages, turnContextPrompt) {
|
|
8483
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
8484
|
+
const updated = replaceRuntimeTurnContext(
|
|
8485
|
+
messages[index],
|
|
8486
|
+
turnContextPrompt
|
|
8487
|
+
);
|
|
8488
|
+
if (!updated) {
|
|
8489
|
+
continue;
|
|
8490
|
+
}
|
|
8491
|
+
const nextMessages = [...messages];
|
|
8492
|
+
nextMessages[index] = updated;
|
|
8493
|
+
return nextMessages;
|
|
8494
|
+
}
|
|
8495
|
+
return [
|
|
8496
|
+
...messages,
|
|
8497
|
+
{
|
|
8498
|
+
role: "user",
|
|
8499
|
+
content: [{ type: "text", text: turnContextPrompt }],
|
|
8500
|
+
timestamp: Date.now()
|
|
8501
|
+
}
|
|
8502
|
+
];
|
|
8503
|
+
}
|
|
8504
|
+
function stripRuntimeTurnContext(messages) {
|
|
8505
|
+
return messages.flatMap((message) => {
|
|
8506
|
+
const content = getUserMessageContent(message);
|
|
8507
|
+
if (!content) {
|
|
8508
|
+
return [message];
|
|
8509
|
+
}
|
|
8510
|
+
const nextContent = content.filter(
|
|
8511
|
+
(part) => !isRuntimeTurnContextPart(part, RUNTIME_TURN_CONTEXT_START)
|
|
8512
|
+
);
|
|
8513
|
+
if (nextContent.length === content.length) {
|
|
8514
|
+
return [message];
|
|
8515
|
+
}
|
|
8516
|
+
if (nextContent.length === 0) {
|
|
8517
|
+
return [];
|
|
8518
|
+
}
|
|
8519
|
+
return [{ ...message, content: nextContent }];
|
|
8520
|
+
});
|
|
8046
8521
|
}
|
|
8047
8522
|
function extractAssistantText(message) {
|
|
8048
8523
|
const content = message.content ?? [];
|
|
@@ -8081,8 +8556,8 @@ function trimTrailingAssistantMessages(messages) {
|
|
|
8081
8556
|
}
|
|
8082
8557
|
|
|
8083
8558
|
// src/chat/tools/advisor/session-store.ts
|
|
8084
|
-
import { THREAD_STATE_TTL_MS as
|
|
8085
|
-
var ADVISOR_SESSION_TTL_MS =
|
|
8559
|
+
import { THREAD_STATE_TTL_MS as THREAD_STATE_TTL_MS3 } from "chat";
|
|
8560
|
+
var ADVISOR_SESSION_TTL_MS = THREAD_STATE_TTL_MS3;
|
|
8086
8561
|
function cloneMessages(messages) {
|
|
8087
8562
|
return structuredClone(messages);
|
|
8088
8563
|
}
|
|
@@ -9057,7 +9532,7 @@ function resolveChannelCapabilities(channelId) {
|
|
|
9057
9532
|
// src/chat/pi/traced-stream.ts
|
|
9058
9533
|
import {
|
|
9059
9534
|
streamSimple
|
|
9060
|
-
} from "@
|
|
9535
|
+
} from "@earendil-works/pi-ai";
|
|
9061
9536
|
function buildChatStartAttributes(model, context) {
|
|
9062
9537
|
const attributes = {
|
|
9063
9538
|
"gen_ai.operation.name": "chat",
|
|
@@ -9142,23 +9617,18 @@ import fs4 from "fs/promises";
|
|
|
9142
9617
|
import { createHmac, randomUUID as randomUUID3, timingSafeEqual } from "crypto";
|
|
9143
9618
|
var SANDBOX_EGRESS_PROXY_PATH = "/api/internal/sandbox-egress";
|
|
9144
9619
|
var SANDBOX_EGRESS_TOKEN_VERSION = "v1";
|
|
9620
|
+
var SANDBOX_EGRESS_HMAC_CONTEXT = "junior.sandbox_egress.v1";
|
|
9145
9621
|
var SANDBOX_EGRESS_LEASE_PREFIX = "sandbox-egress-lease";
|
|
9146
9622
|
var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
9147
9623
|
function leaseKey(provider, context) {
|
|
9148
9624
|
return `${SANDBOX_EGRESS_LEASE_PREFIX}:${provider}:${context.requesterId}:${context.egressId}:${context.contextId}`;
|
|
9149
9625
|
}
|
|
9150
9626
|
function getSandboxEgressSecret() {
|
|
9151
|
-
const
|
|
9152
|
-
if (
|
|
9153
|
-
return
|
|
9627
|
+
const secret = process.env.JUNIOR_SECRET?.trim();
|
|
9628
|
+
if (secret) {
|
|
9629
|
+
return secret;
|
|
9154
9630
|
}
|
|
9155
|
-
|
|
9156
|
-
if (sharedInternal) {
|
|
9157
|
-
return sharedInternal;
|
|
9158
|
-
}
|
|
9159
|
-
throw new Error(
|
|
9160
|
-
"Cannot determine sandbox egress secret (set JUNIOR_SANDBOX_EGRESS_SECRET or JUNIOR_INTERNAL_RESUME_SECRET)"
|
|
9161
|
-
);
|
|
9631
|
+
throw new Error("Cannot determine sandbox egress secret (set JUNIOR_SECRET)");
|
|
9162
9632
|
}
|
|
9163
9633
|
function base64Url(input) {
|
|
9164
9634
|
return Buffer.from(input, "utf8").toString("base64url");
|
|
@@ -9167,7 +9637,7 @@ function fromBase64Url(input) {
|
|
|
9167
9637
|
return Buffer.from(input, "base64url").toString("utf8");
|
|
9168
9638
|
}
|
|
9169
9639
|
function signPayload(payload) {
|
|
9170
|
-
return createHmac("sha256", getSandboxEgressSecret()).update(payload).digest("base64url");
|
|
9640
|
+
return createHmac("sha256", getSandboxEgressSecret()).update(`${SANDBOX_EGRESS_HMAC_CONTEXT}:${payload}`).digest("base64url");
|
|
9171
9641
|
}
|
|
9172
9642
|
function timingSafeMatch(expected, actual) {
|
|
9173
9643
|
const expectedBuffer = Buffer.from(expected);
|
|
@@ -11207,319 +11677,123 @@ var AuthorizationPauseError = class extends Error {
|
|
|
11207
11677
|
this.kind = kind;
|
|
11208
11678
|
this.provider = provider;
|
|
11209
11679
|
}
|
|
11210
|
-
};
|
|
11211
|
-
|
|
11212
|
-
// src/chat/runtime/report-progress.ts
|
|
11213
|
-
function buildReportedProgressStatus(input) {
|
|
11214
|
-
if (!input || typeof input !== "object") {
|
|
11215
|
-
return void 0;
|
|
11216
|
-
}
|
|
11217
|
-
const message = input.message;
|
|
11218
|
-
if (typeof message !== "string") {
|
|
11219
|
-
return void 0;
|
|
11220
|
-
}
|
|
11221
|
-
const text = message.trim();
|
|
11222
|
-
if (!text) {
|
|
11223
|
-
return void 0;
|
|
11224
|
-
}
|
|
11225
|
-
return { text };
|
|
11226
|
-
}
|
|
11227
|
-
|
|
11228
|
-
// src/chat/tools/execution/build-sandbox-input.ts
|
|
11229
|
-
function buildSandboxInput(toolName, params) {
|
|
11230
|
-
const optionalNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
11231
|
-
if (toolName === "bash") {
|
|
11232
|
-
return {
|
|
11233
|
-
command: String(params.command ?? ""),
|
|
11234
|
-
...optionalNumber(params.timeoutMs) ? { timeoutMs: optionalNumber(params.timeoutMs) } : {}
|
|
11235
|
-
};
|
|
11236
|
-
}
|
|
11237
|
-
if (toolName === "readFile") {
|
|
11238
|
-
return {
|
|
11239
|
-
path: String(params.path ?? ""),
|
|
11240
|
-
...optionalNumber(params.offset) ? { offset: optionalNumber(params.offset) } : {},
|
|
11241
|
-
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11242
|
-
};
|
|
11243
|
-
}
|
|
11244
|
-
if (toolName === "editFile") {
|
|
11245
|
-
return {
|
|
11246
|
-
path: String(params.path ?? ""),
|
|
11247
|
-
edits: Array.isArray(params.edits) ? params.edits : []
|
|
11248
|
-
};
|
|
11249
|
-
}
|
|
11250
|
-
if (toolName === "grep") {
|
|
11251
|
-
return {
|
|
11252
|
-
pattern: String(params.pattern ?? ""),
|
|
11253
|
-
...typeof params.path === "string" ? { path: params.path } : {},
|
|
11254
|
-
...typeof params.glob === "string" ? { glob: params.glob } : {},
|
|
11255
|
-
...typeof params.ignoreCase === "boolean" ? { ignoreCase: params.ignoreCase } : {},
|
|
11256
|
-
...typeof params.literal === "boolean" ? { literal: params.literal } : {},
|
|
11257
|
-
...optionalNumber(params.context) ? { context: optionalNumber(params.context) } : {},
|
|
11258
|
-
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11259
|
-
};
|
|
11260
|
-
}
|
|
11261
|
-
if (toolName === "findFiles") {
|
|
11262
|
-
return {
|
|
11263
|
-
pattern: String(params.pattern ?? ""),
|
|
11264
|
-
...typeof params.path === "string" ? { path: params.path } : {},
|
|
11265
|
-
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11266
|
-
};
|
|
11267
|
-
}
|
|
11268
|
-
if (toolName === "listDir") {
|
|
11269
|
-
return {
|
|
11270
|
-
...typeof params.path === "string" ? { path: params.path } : {},
|
|
11271
|
-
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11272
|
-
};
|
|
11273
|
-
}
|
|
11274
|
-
if (toolName === "writeFile") {
|
|
11275
|
-
return {
|
|
11276
|
-
path: String(params.path ?? ""),
|
|
11277
|
-
content: String(params.content ?? "")
|
|
11278
|
-
};
|
|
11279
|
-
}
|
|
11280
|
-
return params;
|
|
11281
|
-
}
|
|
11282
|
-
|
|
11283
|
-
// src/chat/tools/execution/normalize-result.ts
|
|
11284
|
-
function isStructuredToolExecutionResult(value) {
|
|
11285
|
-
const content = value?.content;
|
|
11286
|
-
return typeof value === "object" && value !== null && Array.isArray(content) && content.every((part) => {
|
|
11287
|
-
if (!part || typeof part !== "object") {
|
|
11288
|
-
return false;
|
|
11289
|
-
}
|
|
11290
|
-
const record = part;
|
|
11291
|
-
if (record.type === "text") {
|
|
11292
|
-
return typeof record.text === "string";
|
|
11293
|
-
}
|
|
11294
|
-
if (record.type === "image") {
|
|
11295
|
-
return typeof record.data === "string" && typeof record.mimeType === "string";
|
|
11296
|
-
}
|
|
11297
|
-
return false;
|
|
11298
|
-
}) && "details" in value;
|
|
11299
|
-
}
|
|
11300
|
-
function toToolContentText(value) {
|
|
11301
|
-
if (typeof value === "string") return value;
|
|
11302
|
-
try {
|
|
11303
|
-
return JSON.stringify(value);
|
|
11304
|
-
} catch {
|
|
11305
|
-
return String(value);
|
|
11306
|
-
}
|
|
11307
|
-
}
|
|
11308
|
-
function normalizeToolResult(result, isSandboxResult) {
|
|
11309
|
-
const unwrapped = isSandboxResult && result && typeof result === "object" && "result" in result ? result.result : result;
|
|
11310
|
-
if (isStructuredToolExecutionResult(unwrapped)) {
|
|
11311
|
-
return unwrapped;
|
|
11312
|
-
}
|
|
11313
|
-
return {
|
|
11314
|
-
content: [{ type: "text", text: toToolContentText(unwrapped) }],
|
|
11315
|
-
details: unwrapped
|
|
11316
|
-
};
|
|
11317
|
-
}
|
|
11318
|
-
|
|
11319
|
-
// src/chat/credentials/unlink-provider.ts
|
|
11320
|
-
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
11321
|
-
await Promise.all([
|
|
11322
|
-
userTokenStore.delete(userId, provider),
|
|
11323
|
-
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
11324
|
-
deleteMcpServerSessionId(userId, provider),
|
|
11325
|
-
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
11326
|
-
]);
|
|
11327
|
-
}
|
|
11328
|
-
|
|
11329
|
-
// src/chat/state/turn-session-store.ts
|
|
11330
|
-
var AGENT_TURN_SESSION_PREFIX = "junior:agent_turn_session";
|
|
11331
|
-
var AGENT_TURN_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11332
|
-
function agentTurnSessionKey(conversationId, sessionId) {
|
|
11333
|
-
return `${AGENT_TURN_SESSION_PREFIX}:${conversationId}:${sessionId}`;
|
|
11334
|
-
}
|
|
11335
|
-
function toFiniteNonNegativeNumber(value) {
|
|
11336
|
-
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : void 0;
|
|
11337
|
-
}
|
|
11338
|
-
function parseAgentTurnUsage(value) {
|
|
11339
|
-
if (!isRecord(value)) {
|
|
11340
|
-
return void 0;
|
|
11341
|
-
}
|
|
11342
|
-
const usage = {};
|
|
11343
|
-
for (const field of [
|
|
11344
|
-
"inputTokens",
|
|
11345
|
-
"outputTokens",
|
|
11346
|
-
"cachedInputTokens",
|
|
11347
|
-
"cacheCreationTokens",
|
|
11348
|
-
"totalTokens"
|
|
11349
|
-
]) {
|
|
11350
|
-
const count = toFiniteNonNegativeNumber(value[field]);
|
|
11351
|
-
if (count !== void 0) {
|
|
11352
|
-
usage[field] = count;
|
|
11353
|
-
}
|
|
11354
|
-
}
|
|
11355
|
-
return Object.keys(usage).length > 0 ? usage : void 0;
|
|
11356
|
-
}
|
|
11357
|
-
function parseAgentTurnSessionCheckpoint(value) {
|
|
11358
|
-
if (typeof value !== "string") {
|
|
11359
|
-
return void 0;
|
|
11360
|
-
}
|
|
11361
|
-
try {
|
|
11362
|
-
const parsed = JSON.parse(value);
|
|
11363
|
-
if (!isRecord(parsed)) {
|
|
11364
|
-
return void 0;
|
|
11365
|
-
}
|
|
11366
|
-
const status = parsed.state;
|
|
11367
|
-
if (status !== "running" && status !== "awaiting_resume" && status !== "completed" && status !== "failed" && status !== "superseded") {
|
|
11368
|
-
return void 0;
|
|
11369
|
-
}
|
|
11370
|
-
const conversationId = parsed.conversationId;
|
|
11371
|
-
const sessionId = parsed.sessionId;
|
|
11372
|
-
const sliceId = parsed.sliceId;
|
|
11373
|
-
const checkpointVersion = parsed.checkpointVersion;
|
|
11374
|
-
const updatedAtMs = parsed.updatedAtMs;
|
|
11375
|
-
const cumulativeDurationMs = toFiniteNonNegativeNumber(
|
|
11376
|
-
parsed.cumulativeDurationMs
|
|
11377
|
-
);
|
|
11378
|
-
const cumulativeUsage = parseAgentTurnUsage(parsed.cumulativeUsage);
|
|
11379
|
-
if (typeof conversationId !== "string" || typeof sessionId !== "string" || typeof sliceId !== "number" || typeof checkpointVersion !== "number" || typeof updatedAtMs !== "number") {
|
|
11380
|
-
return void 0;
|
|
11381
|
-
}
|
|
11382
|
-
return {
|
|
11383
|
-
checkpointVersion,
|
|
11384
|
-
conversationId,
|
|
11385
|
-
sessionId,
|
|
11386
|
-
sliceId,
|
|
11387
|
-
state: status,
|
|
11388
|
-
updatedAtMs,
|
|
11389
|
-
...cumulativeDurationMs !== void 0 ? { cumulativeDurationMs } : {},
|
|
11390
|
-
...cumulativeUsage ? { cumulativeUsage } : {},
|
|
11391
|
-
piMessages: Array.isArray(parsed.piMessages) ? parsed.piMessages : [],
|
|
11392
|
-
...Array.isArray(parsed.loadedSkillNames) ? {
|
|
11393
|
-
loadedSkillNames: parsed.loadedSkillNames.filter(
|
|
11394
|
-
(value2) => typeof value2 === "string"
|
|
11395
|
-
)
|
|
11396
|
-
} : {},
|
|
11397
|
-
...parsed.resumeReason === "timeout" || parsed.resumeReason === "auth" ? { resumeReason: parsed.resumeReason } : {},
|
|
11398
|
-
...typeof parsed.errorMessage === "string" ? { errorMessage: parsed.errorMessage } : {},
|
|
11399
|
-
...typeof parsed.resumedFromSliceId === "number" ? { resumedFromSliceId: parsed.resumedFromSliceId } : {}
|
|
11400
|
-
};
|
|
11401
|
-
} catch {
|
|
11402
|
-
return void 0;
|
|
11403
|
-
}
|
|
11404
|
-
}
|
|
11405
|
-
async function getAgentTurnSessionCheckpoint(conversationId, sessionId) {
|
|
11406
|
-
const stateAdapter = getStateAdapter();
|
|
11407
|
-
await stateAdapter.connect();
|
|
11408
|
-
const value = await stateAdapter.get(
|
|
11409
|
-
agentTurnSessionKey(conversationId, sessionId)
|
|
11410
|
-
);
|
|
11411
|
-
return parseAgentTurnSessionCheckpoint(value);
|
|
11412
|
-
}
|
|
11413
|
-
async function upsertAgentTurnSessionCheckpoint(args) {
|
|
11414
|
-
const stateAdapter = getStateAdapter();
|
|
11415
|
-
await stateAdapter.connect();
|
|
11416
|
-
const existing = await getAgentTurnSessionCheckpoint(
|
|
11417
|
-
args.conversationId,
|
|
11418
|
-
args.sessionId
|
|
11419
|
-
);
|
|
11420
|
-
const checkpoint = {
|
|
11421
|
-
checkpointVersion: (existing?.checkpointVersion ?? 0) + 1,
|
|
11422
|
-
conversationId: args.conversationId,
|
|
11423
|
-
sessionId: args.sessionId,
|
|
11424
|
-
sliceId: args.sliceId,
|
|
11425
|
-
state: args.state,
|
|
11426
|
-
updatedAtMs: Date.now(),
|
|
11427
|
-
piMessages: Array.isArray(args.piMessages) ? args.piMessages : [],
|
|
11428
|
-
...typeof args.cumulativeDurationMs === "number" && Number.isFinite(args.cumulativeDurationMs) ? {
|
|
11429
|
-
cumulativeDurationMs: Math.max(
|
|
11430
|
-
0,
|
|
11431
|
-
Math.floor(args.cumulativeDurationMs)
|
|
11432
|
-
)
|
|
11433
|
-
} : {},
|
|
11434
|
-
...args.cumulativeUsage ? { cumulativeUsage: args.cumulativeUsage } : {},
|
|
11435
|
-
...Array.isArray(args.loadedSkillNames) ? {
|
|
11436
|
-
loadedSkillNames: args.loadedSkillNames.filter(
|
|
11437
|
-
(value) => typeof value === "string"
|
|
11438
|
-
)
|
|
11439
|
-
} : {},
|
|
11440
|
-
...args.resumeReason ? { resumeReason: args.resumeReason } : {},
|
|
11441
|
-
...args.errorMessage ? { errorMessage: args.errorMessage } : {},
|
|
11442
|
-
...typeof args.resumedFromSliceId === "number" ? { resumedFromSliceId: args.resumedFromSliceId } : {}
|
|
11443
|
-
};
|
|
11444
|
-
const ttlMs = Math.max(1, args.ttlMs ?? AGENT_TURN_SESSION_TTL_MS);
|
|
11445
|
-
await stateAdapter.set(
|
|
11446
|
-
agentTurnSessionKey(args.conversationId, args.sessionId),
|
|
11447
|
-
JSON.stringify(checkpoint),
|
|
11448
|
-
ttlMs
|
|
11449
|
-
);
|
|
11450
|
-
return checkpoint;
|
|
11451
|
-
}
|
|
11452
|
-
async function supersedeAgentTurnSessionCheckpoint(args) {
|
|
11453
|
-
const existing = await getAgentTurnSessionCheckpoint(
|
|
11454
|
-
args.conversationId,
|
|
11455
|
-
args.sessionId
|
|
11456
|
-
);
|
|
11457
|
-
if (!existing || existing.state === "completed" || existing.state === "failed" || existing.state === "superseded") {
|
|
11458
|
-
return void 0;
|
|
11459
|
-
}
|
|
11460
|
-
return await upsertAgentTurnSessionCheckpoint({
|
|
11461
|
-
conversationId: existing.conversationId,
|
|
11462
|
-
sessionId: existing.sessionId,
|
|
11463
|
-
sliceId: existing.sliceId,
|
|
11464
|
-
state: "superseded",
|
|
11465
|
-
piMessages: existing.piMessages,
|
|
11466
|
-
cumulativeDurationMs: existing.cumulativeDurationMs,
|
|
11467
|
-
cumulativeUsage: existing.cumulativeUsage,
|
|
11468
|
-
loadedSkillNames: existing.loadedSkillNames,
|
|
11469
|
-
resumeReason: existing.resumeReason,
|
|
11470
|
-
resumedFromSliceId: existing.resumedFromSliceId,
|
|
11471
|
-
errorMessage: args.errorMessage ?? existing.errorMessage
|
|
11472
|
-
});
|
|
11473
|
-
}
|
|
11680
|
+
};
|
|
11474
11681
|
|
|
11475
|
-
// src/chat/
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
if (!pendingAuth) {
|
|
11480
|
-
return false;
|
|
11682
|
+
// src/chat/runtime/report-progress.ts
|
|
11683
|
+
function buildReportedProgressStatus(input) {
|
|
11684
|
+
if (!input || typeof input !== "object") {
|
|
11685
|
+
return void 0;
|
|
11481
11686
|
}
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
function getConversationPendingAuth(args) {
|
|
11485
|
-
const pendingAuth = args.conversation.processing.pendingAuth;
|
|
11486
|
-
if (!pendingAuth) {
|
|
11687
|
+
const message = input.message;
|
|
11688
|
+
if (typeof message !== "string") {
|
|
11487
11689
|
return void 0;
|
|
11488
11690
|
}
|
|
11489
|
-
|
|
11691
|
+
const text = message.trim();
|
|
11692
|
+
if (!text) {
|
|
11490
11693
|
return void 0;
|
|
11491
11694
|
}
|
|
11492
|
-
return
|
|
11695
|
+
return { text };
|
|
11493
11696
|
}
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11697
|
+
|
|
11698
|
+
// src/chat/tools/execution/build-sandbox-input.ts
|
|
11699
|
+
function buildSandboxInput(toolName, params) {
|
|
11700
|
+
const optionalNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
11701
|
+
if (toolName === "bash") {
|
|
11702
|
+
return {
|
|
11703
|
+
command: String(params.command ?? ""),
|
|
11704
|
+
...optionalNumber(params.timeoutMs) ? { timeoutMs: optionalNumber(params.timeoutMs) } : {}
|
|
11705
|
+
};
|
|
11497
11706
|
}
|
|
11498
|
-
if (
|
|
11499
|
-
return
|
|
11707
|
+
if (toolName === "readFile") {
|
|
11708
|
+
return {
|
|
11709
|
+
path: String(params.path ?? ""),
|
|
11710
|
+
...optionalNumber(params.offset) ? { offset: optionalNumber(params.offset) } : {},
|
|
11711
|
+
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11712
|
+
};
|
|
11500
11713
|
}
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
|
|
11504
|
-
|
|
11505
|
-
|
|
11506
|
-
|
|
11507
|
-
|
|
11508
|
-
|
|
11509
|
-
|
|
11510
|
-
|
|
11511
|
-
|
|
11714
|
+
if (toolName === "editFile") {
|
|
11715
|
+
return {
|
|
11716
|
+
path: String(params.path ?? ""),
|
|
11717
|
+
edits: Array.isArray(params.edits) ? params.edits : []
|
|
11718
|
+
};
|
|
11719
|
+
}
|
|
11720
|
+
if (toolName === "grep") {
|
|
11721
|
+
return {
|
|
11722
|
+
pattern: String(params.pattern ?? ""),
|
|
11723
|
+
...typeof params.path === "string" ? { path: params.path } : {},
|
|
11724
|
+
...typeof params.glob === "string" ? { glob: params.glob } : {},
|
|
11725
|
+
...typeof params.ignoreCase === "boolean" ? { ignoreCase: params.ignoreCase } : {},
|
|
11726
|
+
...typeof params.literal === "boolean" ? { literal: params.literal } : {},
|
|
11727
|
+
...optionalNumber(params.context) ? { context: optionalNumber(params.context) } : {},
|
|
11728
|
+
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11729
|
+
};
|
|
11730
|
+
}
|
|
11731
|
+
if (toolName === "findFiles") {
|
|
11732
|
+
return {
|
|
11733
|
+
pattern: String(params.pattern ?? ""),
|
|
11734
|
+
...typeof params.path === "string" ? { path: params.path } : {},
|
|
11735
|
+
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11736
|
+
};
|
|
11737
|
+
}
|
|
11738
|
+
if (toolName === "listDir") {
|
|
11739
|
+
return {
|
|
11740
|
+
...typeof params.path === "string" ? { path: params.path } : {},
|
|
11741
|
+
...optionalNumber(params.limit) ? { limit: optionalNumber(params.limit) } : {}
|
|
11742
|
+
};
|
|
11743
|
+
}
|
|
11744
|
+
if (toolName === "writeFile") {
|
|
11745
|
+
return {
|
|
11746
|
+
path: String(params.path ?? ""),
|
|
11747
|
+
content: String(params.content ?? "")
|
|
11748
|
+
};
|
|
11512
11749
|
}
|
|
11750
|
+
return params;
|
|
11513
11751
|
}
|
|
11514
|
-
|
|
11515
|
-
|
|
11516
|
-
|
|
11517
|
-
|
|
11518
|
-
|
|
11752
|
+
|
|
11753
|
+
// src/chat/tools/execution/normalize-result.ts
|
|
11754
|
+
function isStructuredToolExecutionResult(value) {
|
|
11755
|
+
const content = value?.content;
|
|
11756
|
+
return typeof value === "object" && value !== null && Array.isArray(content) && content.every((part) => {
|
|
11757
|
+
if (!part || typeof part !== "object") {
|
|
11758
|
+
return false;
|
|
11519
11759
|
}
|
|
11520
|
-
|
|
11760
|
+
const record = part;
|
|
11761
|
+
if (record.type === "text") {
|
|
11762
|
+
return typeof record.text === "string";
|
|
11763
|
+
}
|
|
11764
|
+
if (record.type === "image") {
|
|
11765
|
+
return typeof record.data === "string" && typeof record.mimeType === "string";
|
|
11766
|
+
}
|
|
11767
|
+
return false;
|
|
11768
|
+
}) && "details" in value;
|
|
11769
|
+
}
|
|
11770
|
+
function toToolContentText(value) {
|
|
11771
|
+
if (typeof value === "string") return value;
|
|
11772
|
+
try {
|
|
11773
|
+
return JSON.stringify(value);
|
|
11774
|
+
} catch {
|
|
11775
|
+
return String(value);
|
|
11521
11776
|
}
|
|
11522
|
-
|
|
11777
|
+
}
|
|
11778
|
+
function normalizeToolResult(result, isSandboxResult) {
|
|
11779
|
+
const unwrapped = isSandboxResult && result && typeof result === "object" && "result" in result ? result.result : result;
|
|
11780
|
+
if (isStructuredToolExecutionResult(unwrapped)) {
|
|
11781
|
+
return unwrapped;
|
|
11782
|
+
}
|
|
11783
|
+
return {
|
|
11784
|
+
content: [{ type: "text", text: toToolContentText(unwrapped) }],
|
|
11785
|
+
details: unwrapped
|
|
11786
|
+
};
|
|
11787
|
+
}
|
|
11788
|
+
|
|
11789
|
+
// src/chat/credentials/unlink-provider.ts
|
|
11790
|
+
async function unlinkProvider(userId, provider, userTokenStore) {
|
|
11791
|
+
await Promise.all([
|
|
11792
|
+
userTokenStore.delete(userId, provider),
|
|
11793
|
+
deleteMcpStoredOAuthCredentials(userId, provider),
|
|
11794
|
+
deleteMcpServerSessionId(userId, provider),
|
|
11795
|
+
deleteMcpAuthSessionsForUserProvider(userId, provider)
|
|
11796
|
+
]);
|
|
11523
11797
|
}
|
|
11524
11798
|
|
|
11525
11799
|
// src/chat/services/plugin-auth-orchestration.ts
|
|
@@ -12000,7 +12274,6 @@ function buildBriefPostCanvasReply(artifactStatePatch) {
|
|
|
12000
12274
|
function buildTurnResult(input) {
|
|
12001
12275
|
const {
|
|
12002
12276
|
newMessages,
|
|
12003
|
-
piMessages,
|
|
12004
12277
|
userInput,
|
|
12005
12278
|
replyFiles,
|
|
12006
12279
|
artifactStatePatch,
|
|
@@ -12035,7 +12308,11 @@ function buildTurnResult(input) {
|
|
|
12035
12308
|
hasFiles: replyFiles.length > 0
|
|
12036
12309
|
});
|
|
12037
12310
|
const sideEffectOnlySuccess = !primaryText && toolErrorCount === 0 && (reactionPerformed || channelPostPerformed || replyFiles.length > 0);
|
|
12038
|
-
|
|
12311
|
+
const lastAssistant = terminalAssistantMessages.at(-1);
|
|
12312
|
+
const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
|
|
12313
|
+
const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
12314
|
+
const isProviderError = stopReason === "error";
|
|
12315
|
+
if (!primaryText && !sideEffectOnlySuccess && !isProviderError) {
|
|
12039
12316
|
logWarn(
|
|
12040
12317
|
"ai_model_response_empty",
|
|
12041
12318
|
{
|
|
@@ -12054,11 +12331,15 @@ function buildTurnResult(input) {
|
|
|
12054
12331
|
"Model returned empty text response"
|
|
12055
12332
|
);
|
|
12056
12333
|
}
|
|
12057
|
-
const lastAssistant = terminalAssistantMessages.at(-1);
|
|
12058
|
-
const stopReason = typeof lastAssistant?.stopReason === "string" ? lastAssistant.stopReason : void 0;
|
|
12059
|
-
const errorMessage = typeof lastAssistant?.errorMessage === "string" ? lastAssistant.errorMessage : void 0;
|
|
12060
12334
|
const usedPrimaryText = Boolean(primaryText);
|
|
12061
|
-
|
|
12335
|
+
let outcome;
|
|
12336
|
+
if (isProviderError) {
|
|
12337
|
+
outcome = "provider_error";
|
|
12338
|
+
} else if (primaryText || sideEffectOnlySuccess) {
|
|
12339
|
+
outcome = "success";
|
|
12340
|
+
} else {
|
|
12341
|
+
outcome = "execution_failure";
|
|
12342
|
+
}
|
|
12062
12343
|
const suppressReactionOnlyText = reactionPerformed && !channelPostPerformed && replyFiles.length === 0 && Boolean(primaryText) && isReactionOnlyIntent(userInput);
|
|
12063
12344
|
const rawResponseText = suppressReactionOnlyText ? "" : primaryText;
|
|
12064
12345
|
const responseText = canvasCreated && isVerbosePostCanvasReply(rawResponseText) ? buildBriefPostCanvasReply(artifactStatePatch) : rawResponseText;
|
|
@@ -12104,7 +12385,6 @@ function buildTurnResult(input) {
|
|
|
12104
12385
|
text: resolvedText,
|
|
12105
12386
|
files: replyFiles.length > 0 ? replyFiles : void 0,
|
|
12106
12387
|
artifactStatePatch: Object.keys(artifactStatePatch).length > 0 ? artifactStatePatch : void 0,
|
|
12107
|
-
piMessages,
|
|
12108
12388
|
deliveryPlan,
|
|
12109
12389
|
deliveryMode,
|
|
12110
12390
|
sandboxId,
|
|
@@ -12113,6 +12393,27 @@ function buildTurnResult(input) {
|
|
|
12113
12393
|
};
|
|
12114
12394
|
}
|
|
12115
12395
|
|
|
12396
|
+
// src/chat/services/provider-retry.ts
|
|
12397
|
+
var RETRYABLE_PROVIDER_ERROR_PATTERN = /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i;
|
|
12398
|
+
var NON_RETRYABLE_PROVIDER_ERROR_PATTERN = /invalid.?api.?key|authentication|authorization|permission|forbidden|context.?length|context.?window|content.?policy|validation|bad request|400|401|403/i;
|
|
12399
|
+
function isRetryableProviderError(message) {
|
|
12400
|
+
if (message?.stopReason !== "error" || !message.errorMessage) {
|
|
12401
|
+
return false;
|
|
12402
|
+
}
|
|
12403
|
+
if (NON_RETRYABLE_PROVIDER_ERROR_PATTERN.test(message.errorMessage)) {
|
|
12404
|
+
return false;
|
|
12405
|
+
}
|
|
12406
|
+
return RETRYABLE_PROVIDER_ERROR_PATTERN.test(message.errorMessage);
|
|
12407
|
+
}
|
|
12408
|
+
function trimRetryableProviderErrorTail(messages) {
|
|
12409
|
+
const trimmed = trimTrailingAssistantMessages(messages);
|
|
12410
|
+
if (trimmed.length === messages.length) {
|
|
12411
|
+
return void 0;
|
|
12412
|
+
}
|
|
12413
|
+
const tailRole = getPiMessageRole(trimmed.at(-1));
|
|
12414
|
+
return tailRole === "user" || tailRole === "toolResult" ? trimmed : void 0;
|
|
12415
|
+
}
|
|
12416
|
+
|
|
12116
12417
|
// src/chat/services/turn-thinking-level.ts
|
|
12117
12418
|
import { z } from "zod";
|
|
12118
12419
|
var CLASSIFIER_CONFIDENCE_THRESHOLD = 0.75;
|
|
@@ -12516,7 +12817,7 @@ async function persistAuthPauseCheckpoint(args) {
|
|
|
12516
12817
|
const piMessages = trimTrailingAssistantMessages(
|
|
12517
12818
|
args.messages.length > 0 ? args.messages : latestCheckpoint?.piMessages ?? []
|
|
12518
12819
|
);
|
|
12519
|
-
await upsertAgentTurnSessionCheckpoint({
|
|
12820
|
+
return await upsertAgentTurnSessionCheckpoint({
|
|
12520
12821
|
conversationId: args.conversationId,
|
|
12521
12822
|
cumulativeDurationMs: addDurationMs(
|
|
12522
12823
|
latestCheckpoint?.cumulativeDurationMs,
|
|
@@ -12547,7 +12848,7 @@ async function persistAuthPauseCheckpoint(args) {
|
|
|
12547
12848
|
"Failed to persist auth checkpoint before retry"
|
|
12548
12849
|
);
|
|
12549
12850
|
}
|
|
12550
|
-
return
|
|
12851
|
+
return void 0;
|
|
12551
12852
|
}
|
|
12552
12853
|
async function persistTimeoutCheckpoint(args) {
|
|
12553
12854
|
const nextSliceId = args.currentSliceId + 1;
|
|
@@ -12686,6 +12987,10 @@ function createMcpAuthOrchestration(deps, abortAgent) {
|
|
|
12686
12987
|
}
|
|
12687
12988
|
|
|
12688
12989
|
// src/chat/respond.ts
|
|
12990
|
+
var PROVIDER_RETRY_DELAYS_MS = [1e3, 2e3];
|
|
12991
|
+
function sleep3(ms) {
|
|
12992
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12993
|
+
}
|
|
12689
12994
|
var startupDiscoveryLogged = false;
|
|
12690
12995
|
var MAX_ROUTER_ATTACHMENT_PREVIEW_CHARS = 2e3;
|
|
12691
12996
|
function buildOmittedImageAttachmentNotice(count) {
|
|
@@ -12787,69 +13092,6 @@ function buildUserTurnInput(args) {
|
|
|
12787
13092
|
}
|
|
12788
13093
|
return { routerBlocks, userContentParts };
|
|
12789
13094
|
}
|
|
12790
|
-
function refreshCheckpointTurnContext(messages, turnContextPrompt) {
|
|
12791
|
-
const marker = getTurnContextMarker(turnContextPrompt);
|
|
12792
|
-
for (let index = 0; index < messages.length; index += 1) {
|
|
12793
|
-
const content = getUserMessageContent(messages[index]);
|
|
12794
|
-
if (!content) {
|
|
12795
|
-
continue;
|
|
12796
|
-
}
|
|
12797
|
-
const contextIndex = content.findIndex(
|
|
12798
|
-
(part) => isTurnContextPart(part, marker)
|
|
12799
|
-
);
|
|
12800
|
-
if (contextIndex < 0) {
|
|
12801
|
-
continue;
|
|
12802
|
-
}
|
|
12803
|
-
const updatedMessages = [...messages];
|
|
12804
|
-
const updatedContent = [...content];
|
|
12805
|
-
updatedContent[contextIndex] = {
|
|
12806
|
-
...updatedContent[contextIndex],
|
|
12807
|
-
text: turnContextPrompt
|
|
12808
|
-
};
|
|
12809
|
-
updatedMessages[index] = {
|
|
12810
|
-
...messages[index],
|
|
12811
|
-
content: updatedContent
|
|
12812
|
-
};
|
|
12813
|
-
return updatedMessages;
|
|
12814
|
-
}
|
|
12815
|
-
return [
|
|
12816
|
-
...messages,
|
|
12817
|
-
{
|
|
12818
|
-
role: "user",
|
|
12819
|
-
content: [{ type: "text", text: turnContextPrompt }],
|
|
12820
|
-
timestamp: Date.now()
|
|
12821
|
-
}
|
|
12822
|
-
];
|
|
12823
|
-
}
|
|
12824
|
-
function stripTurnContextFromMessages(messages, turnContextPrompt) {
|
|
12825
|
-
const marker = getTurnContextMarker(turnContextPrompt);
|
|
12826
|
-
return messages.flatMap((message) => {
|
|
12827
|
-
const content = getUserMessageContent(message);
|
|
12828
|
-
if (!content) {
|
|
12829
|
-
return [message];
|
|
12830
|
-
}
|
|
12831
|
-
const strippedContent = content.filter(
|
|
12832
|
-
(part) => !isTurnContextPart(part, marker)
|
|
12833
|
-
);
|
|
12834
|
-
if (strippedContent.length === content.length) {
|
|
12835
|
-
return [message];
|
|
12836
|
-
}
|
|
12837
|
-
if (strippedContent.length === 0) {
|
|
12838
|
-
return [];
|
|
12839
|
-
}
|
|
12840
|
-
return [{ ...message, content: strippedContent }];
|
|
12841
|
-
});
|
|
12842
|
-
}
|
|
12843
|
-
function getTurnContextMarker(turnContextPrompt) {
|
|
12844
|
-
return turnContextPrompt.split("\n", 1)[0];
|
|
12845
|
-
}
|
|
12846
|
-
function getUserMessageContent(message) {
|
|
12847
|
-
const record = message;
|
|
12848
|
-
return record.role === "user" && Array.isArray(record.content) ? record.content : void 0;
|
|
12849
|
-
}
|
|
12850
|
-
function isTurnContextPart(part, marker) {
|
|
12851
|
-
return part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.startsWith(marker);
|
|
12852
|
-
}
|
|
12853
13095
|
async function generateAssistantReply(messageText, context = {}) {
|
|
12854
13096
|
const replyStartedAtMs = Date.now();
|
|
12855
13097
|
let timeoutResumeConversationId;
|
|
@@ -13046,11 +13288,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13046
13288
|
const promptConversationContext = context.piMessages && context.piMessages.length > 0 ? void 0 : context.conversationContext;
|
|
13047
13289
|
const userTurnText = buildUserTurnText(
|
|
13048
13290
|
userInput,
|
|
13049
|
-
promptConversationContext
|
|
13050
|
-
{
|
|
13051
|
-
sessionContext: { conversationId: sessionConversationId },
|
|
13052
|
-
turnContext: { traceId: getActiveTraceId() }
|
|
13053
|
-
}
|
|
13291
|
+
promptConversationContext
|
|
13054
13292
|
);
|
|
13055
13293
|
const { routerBlocks, userContentParts } = buildUserTurnInput({
|
|
13056
13294
|
omittedImageAttachmentCount: context.omittedImageAttachmentCount ?? 0,
|
|
@@ -13235,11 +13473,8 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13235
13473
|
activeMcpCatalogs,
|
|
13236
13474
|
toolGuidance,
|
|
13237
13475
|
runtime: {
|
|
13238
|
-
|
|
13239
|
-
|
|
13240
|
-
modelId: botConfig.modelId,
|
|
13241
|
-
slackCapabilities: channelCapabilities,
|
|
13242
|
-
thinkingLevel: thinkingSelection.thinkingLevel
|
|
13476
|
+
conversationId: spanContext.conversationId,
|
|
13477
|
+
traceId: getActiveTraceId()
|
|
13243
13478
|
},
|
|
13244
13479
|
invocation: skillInvocation,
|
|
13245
13480
|
requester: context.requester,
|
|
@@ -13362,7 +13597,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13362
13597
|
beforeMessageCount = agent.state.messages.length;
|
|
13363
13598
|
try {
|
|
13364
13599
|
if (resumedFromCheckpoint) {
|
|
13365
|
-
agent.state.messages =
|
|
13600
|
+
agent.state.messages = refreshRuntimeTurnContext(
|
|
13366
13601
|
existingCheckpoint.piMessages,
|
|
13367
13602
|
turnContextPrompt
|
|
13368
13603
|
);
|
|
@@ -13387,67 +13622,96 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13387
13622
|
freshPromptMessage
|
|
13388
13623
|
]);
|
|
13389
13624
|
}
|
|
13390
|
-
const
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13398
|
-
|
|
13399
|
-
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13409
|
-
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13625
|
+
const runAgentStep = async (run2) => {
|
|
13626
|
+
let timeoutId;
|
|
13627
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
13628
|
+
timeoutId = setTimeout(() => {
|
|
13629
|
+
timedOut = true;
|
|
13630
|
+
agent.abort();
|
|
13631
|
+
reject(
|
|
13632
|
+
new Error(
|
|
13633
|
+
`Agent turn timed out after ${botConfig.turnTimeoutMs}ms`
|
|
13634
|
+
)
|
|
13635
|
+
);
|
|
13636
|
+
}, botConfig.turnTimeoutMs);
|
|
13637
|
+
});
|
|
13638
|
+
try {
|
|
13639
|
+
return await Promise.race([run2, timeoutPromise]);
|
|
13640
|
+
} catch (error) {
|
|
13641
|
+
if (timedOut) {
|
|
13642
|
+
logWarn(
|
|
13643
|
+
"agent_turn_timeout",
|
|
13644
|
+
{},
|
|
13645
|
+
{
|
|
13646
|
+
"gen_ai.provider.name": GEN_AI_PROVIDER_NAME,
|
|
13647
|
+
"gen_ai.operation.name": "invoke_agent",
|
|
13648
|
+
"gen_ai.request.model": botConfig.modelId,
|
|
13649
|
+
...thinkingSelection ? {
|
|
13650
|
+
"app.ai.reasoning_effort": thinkingSelection.thinkingLevel
|
|
13651
|
+
} : {},
|
|
13652
|
+
"app.ai.turn_timeout_ms": botConfig.turnTimeoutMs
|
|
13653
|
+
},
|
|
13654
|
+
"Agent turn timed out and was aborted"
|
|
13655
|
+
);
|
|
13656
|
+
await run2.catch(() => {
|
|
13657
|
+
});
|
|
13658
|
+
timeoutResumeMessages = [...agent.state.messages];
|
|
13659
|
+
}
|
|
13660
|
+
if (getPendingAuthPause()) {
|
|
13661
|
+
timeoutResumeMessages = [...agent.state.messages];
|
|
13662
|
+
throw getPendingAuthPause();
|
|
13663
|
+
}
|
|
13664
|
+
throw error;
|
|
13665
|
+
} finally {
|
|
13666
|
+
if (timeoutId) {
|
|
13667
|
+
clearTimeout(timeoutId);
|
|
13668
|
+
}
|
|
13424
13669
|
}
|
|
13670
|
+
};
|
|
13671
|
+
let run = resumedFromCheckpoint ? agent.continue() : agent.prompt(freshPromptMessage);
|
|
13672
|
+
let retryUsage;
|
|
13673
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
13674
|
+
promptResult = await runAgentStep(run);
|
|
13675
|
+
newMessages = agent.state.messages.slice(beforeMessageCount);
|
|
13676
|
+
const outputMessages = newMessages.filter(isAssistantMessage);
|
|
13677
|
+
const outputMessagesAttribute = serializeGenAiAttribute(outputMessages);
|
|
13678
|
+
const usageSummary = extractGenAiUsageSummary(
|
|
13679
|
+
promptResult,
|
|
13680
|
+
agent.state,
|
|
13681
|
+
...outputMessages
|
|
13682
|
+
);
|
|
13683
|
+
const currentUsage = hasAgentTurnUsage(usageSummary) ? usageSummary : void 0;
|
|
13684
|
+
turnUsage = addAgentTurnUsage(retryUsage, currentUsage);
|
|
13685
|
+
setSpanAttributes({
|
|
13686
|
+
...outputMessagesAttribute ? { "gen_ai.output.messages": outputMessagesAttribute } : {},
|
|
13687
|
+
...extractGenAiUsageAttributes(usageSummary)
|
|
13688
|
+
});
|
|
13425
13689
|
if (getPendingAuthPause()) {
|
|
13426
13690
|
timeoutResumeMessages = [...agent.state.messages];
|
|
13427
13691
|
throw getPendingAuthPause();
|
|
13428
13692
|
}
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
if (
|
|
13432
|
-
|
|
13693
|
+
const lastAssistant = outputMessages.at(-1);
|
|
13694
|
+
const retryDelayMs = PROVIDER_RETRY_DELAYS_MS[attempt];
|
|
13695
|
+
if (retryDelayMs === void 0 || !isRetryableProviderError(lastAssistant)) {
|
|
13696
|
+
break;
|
|
13433
13697
|
}
|
|
13434
|
-
|
|
13435
|
-
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
|
|
13447
|
-
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
13698
|
+
retryUsage = turnUsage;
|
|
13699
|
+
const retryMessages = trimRetryableProviderErrorTail(
|
|
13700
|
+
agent.state.messages
|
|
13701
|
+
);
|
|
13702
|
+
if (!retryMessages) {
|
|
13703
|
+
break;
|
|
13704
|
+
}
|
|
13705
|
+
agent.state.messages = retryMessages;
|
|
13706
|
+
await persistSafeBoundary(retryMessages);
|
|
13707
|
+
logWarn(
|
|
13708
|
+
"agent_turn_provider_retry",
|
|
13709
|
+
spanContext,
|
|
13710
|
+
{},
|
|
13711
|
+
"Retrying transient provider failure"
|
|
13712
|
+
);
|
|
13713
|
+
await sleep3(retryDelayMs);
|
|
13714
|
+
run = agent.continue();
|
|
13451
13715
|
}
|
|
13452
13716
|
},
|
|
13453
13717
|
{
|
|
@@ -13475,10 +13739,6 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13475
13739
|
}
|
|
13476
13740
|
return buildTurnResult({
|
|
13477
13741
|
newMessages,
|
|
13478
|
-
piMessages: stripTurnContextFromMessages(
|
|
13479
|
-
agent.state.messages,
|
|
13480
|
-
turnContextPrompt
|
|
13481
|
-
),
|
|
13482
13742
|
userInput,
|
|
13483
13743
|
replyFiles,
|
|
13484
13744
|
artifactStatePatch,
|
|
@@ -13528,7 +13788,7 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13528
13788
|
beforeMessageCount
|
|
13529
13789
|
);
|
|
13530
13790
|
}
|
|
13531
|
-
const
|
|
13791
|
+
const checkpoint = await persistAuthPauseCheckpoint({
|
|
13532
13792
|
conversationId: timeoutResumeConversationId,
|
|
13533
13793
|
sessionId: timeoutResumeSessionId,
|
|
13534
13794
|
currentSliceId: timeoutResumeSliceId,
|
|
@@ -13539,21 +13799,23 @@ async function generateAssistantReply(messageText, context = {}) {
|
|
|
13539
13799
|
errorMessage: error.message,
|
|
13540
13800
|
logContext: checkpointLogContext
|
|
13541
13801
|
});
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13802
|
+
if (checkpoint) {
|
|
13803
|
+
throw new RetryableTurnError(
|
|
13804
|
+
error.kind === "plugin" ? "plugin_auth_resume" : "mcp_auth_resume",
|
|
13805
|
+
`conversation=${timeoutResumeConversationId} session=${timeoutResumeSessionId} slice=${checkpoint.sliceId}`,
|
|
13806
|
+
{
|
|
13807
|
+
authDisposition: error.disposition,
|
|
13808
|
+
authDurationMs: Date.now() - replyStartedAtMs,
|
|
13809
|
+
authKind: error.kind,
|
|
13810
|
+
authProvider: error.provider,
|
|
13811
|
+
authThinkingLevel: thinkingSelection?.thinkingLevel,
|
|
13812
|
+
authUsage: turnUsage,
|
|
13813
|
+
conversationId: timeoutResumeConversationId,
|
|
13814
|
+
sessionId: timeoutResumeSessionId,
|
|
13815
|
+
sliceId: checkpoint.sliceId
|
|
13816
|
+
}
|
|
13817
|
+
);
|
|
13818
|
+
}
|
|
13557
13819
|
}
|
|
13558
13820
|
if (isRetryableTurnError(error)) {
|
|
13559
13821
|
throw error;
|
|
@@ -14769,16 +15031,10 @@ function createResumeReplyContext(args, statusSession) {
|
|
|
14769
15031
|
};
|
|
14770
15032
|
}
|
|
14771
15033
|
async function resumeSlackTurn(args) {
|
|
14772
|
-
if (!args.replyContext?.requester?.userId) {
|
|
14773
|
-
throw new Error("Resumed turn requires replyContext.requester.userId");
|
|
14774
|
-
}
|
|
14775
15034
|
const stateAdapter = getStateAdapter();
|
|
14776
15035
|
await stateAdapter.connect();
|
|
14777
15036
|
const lockKey = args.lockKey ?? getDefaultLockKey(args.channelId, args.threadTs);
|
|
14778
|
-
const lock = await stateAdapter.acquireLock(
|
|
14779
|
-
lockKey,
|
|
14780
|
-
botConfig.turnTimeoutMs + 6e4
|
|
14781
|
-
);
|
|
15037
|
+
const lock = await stateAdapter.acquireLock(lockKey, ACTIVE_LOCK_TTL_MS);
|
|
14782
15038
|
if (!lock) {
|
|
14783
15039
|
throw new ResumeTurnBusyError(lockKey);
|
|
14784
15040
|
}
|
|
@@ -14790,31 +15046,44 @@ async function resumeSlackTurn(args) {
|
|
|
14790
15046
|
let deferredPauseKind;
|
|
14791
15047
|
let deferredPauseHandler;
|
|
14792
15048
|
let deferredFailureHandler;
|
|
15049
|
+
let finalReplyDelivered = false;
|
|
15050
|
+
let postDeliveryCommitError;
|
|
15051
|
+
let runArgs = args;
|
|
14793
15052
|
try {
|
|
14794
|
-
|
|
15053
|
+
const preparedArgs = await args.beforeStart?.();
|
|
15054
|
+
if (preparedArgs === false) {
|
|
15055
|
+
return;
|
|
15056
|
+
}
|
|
15057
|
+
if (preparedArgs) {
|
|
15058
|
+
runArgs = { ...args, ...preparedArgs };
|
|
15059
|
+
}
|
|
15060
|
+
if (!runArgs.replyContext?.requester?.userId) {
|
|
15061
|
+
throw new Error("Resumed turn requires replyContext.requester.userId");
|
|
15062
|
+
}
|
|
15063
|
+
if (runArgs.messageTs) {
|
|
14795
15064
|
processingReaction = await startSlackProcessingReactionForMessage({
|
|
14796
|
-
channelId:
|
|
14797
|
-
timestamp:
|
|
15065
|
+
channelId: runArgs.channelId,
|
|
15066
|
+
timestamp: runArgs.messageTs,
|
|
14798
15067
|
logException,
|
|
14799
|
-
logContext: { ...getResumeLogContext(
|
|
15068
|
+
logContext: { ...getResumeLogContext(runArgs, lockKey) }
|
|
14800
15069
|
});
|
|
14801
15070
|
}
|
|
14802
|
-
if (
|
|
15071
|
+
if (runArgs.initialText) {
|
|
14803
15072
|
await postSlackMessageBestEffort(
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
|
|
15073
|
+
runArgs.channelId,
|
|
15074
|
+
runArgs.threadTs,
|
|
15075
|
+
runArgs.initialText
|
|
14807
15076
|
);
|
|
14808
15077
|
}
|
|
14809
15078
|
status.start();
|
|
14810
|
-
const generateReply =
|
|
14811
|
-
const replyContext = createResumeReplyContext(
|
|
15079
|
+
const generateReply = runArgs.generateReply ?? generateAssistantReply;
|
|
15080
|
+
const replyContext = createResumeReplyContext(runArgs, status);
|
|
14812
15081
|
const priorCheckpoint = replyContext.correlation?.conversationId && replyContext.correlation?.turnId ? await getAgentTurnSessionCheckpoint(
|
|
14813
15082
|
replyContext.correlation.conversationId,
|
|
14814
15083
|
replyContext.correlation.turnId
|
|
14815
15084
|
) : void 0;
|
|
14816
|
-
const replyPromise = generateReply(
|
|
14817
|
-
const replyTimeoutMs = resolveReplyTimeoutMs(
|
|
15085
|
+
const replyPromise = generateReply(runArgs.messageText, replyContext);
|
|
15086
|
+
const replyTimeoutMs = resolveReplyTimeoutMs(runArgs.replyTimeoutMs);
|
|
14818
15087
|
let reply = typeof replyTimeoutMs === "number" ? await Promise.race([
|
|
14819
15088
|
replyPromise,
|
|
14820
15089
|
new Promise(
|
|
@@ -14831,11 +15100,11 @@ async function resumeSlackTurn(args) {
|
|
|
14831
15100
|
reply = finalizeFailedTurnReply({
|
|
14832
15101
|
reply,
|
|
14833
15102
|
logException,
|
|
14834
|
-
context: getResumeLogContext(
|
|
15103
|
+
context: getResumeLogContext(runArgs, lockKey)
|
|
14835
15104
|
});
|
|
14836
15105
|
await status.stop();
|
|
14837
15106
|
const footer = buildSlackReplyFooter({
|
|
14838
|
-
conversationId:
|
|
15107
|
+
conversationId: runArgs.replyContext?.correlation?.conversationId ?? lockKey,
|
|
14839
15108
|
durationMs: typeof priorCheckpoint?.cumulativeDurationMs === "number" || typeof reply.diagnostics.durationMs === "number" ? (priorCheckpoint?.cumulativeDurationMs ?? 0) + (reply.diagnostics.durationMs ?? 0) : void 0,
|
|
14840
15109
|
thinkingLevel: reply.diagnostics.thinkingLevel,
|
|
14841
15110
|
usage: addAgentTurnUsage(
|
|
@@ -14844,18 +15113,32 @@ async function resumeSlackTurn(args) {
|
|
|
14844
15113
|
) ?? reply.diagnostics.usage
|
|
14845
15114
|
});
|
|
14846
15115
|
await postSlackApiReplyPosts({
|
|
14847
|
-
channelId:
|
|
14848
|
-
threadTs:
|
|
15116
|
+
channelId: runArgs.channelId,
|
|
15117
|
+
threadTs: runArgs.threadTs,
|
|
14849
15118
|
posts: planSlackReplyPosts({ reply }),
|
|
14850
15119
|
fileUploadFailureMode: "best_effort",
|
|
14851
15120
|
footer
|
|
14852
15121
|
});
|
|
14853
|
-
|
|
15122
|
+
finalReplyDelivered = true;
|
|
15123
|
+
await runArgs.onSuccess?.(reply);
|
|
14854
15124
|
} catch (error) {
|
|
14855
15125
|
await status.stop();
|
|
14856
|
-
const onAuthPause =
|
|
14857
|
-
const onTimeoutPause =
|
|
14858
|
-
if (
|
|
15126
|
+
const onAuthPause = runArgs.onAuthPause;
|
|
15127
|
+
const onTimeoutPause = runArgs.onTimeoutPause;
|
|
15128
|
+
if (finalReplyDelivered) {
|
|
15129
|
+
postDeliveryCommitError = error;
|
|
15130
|
+
try {
|
|
15131
|
+
await runArgs.onPostDeliveryCommitFailure?.(error);
|
|
15132
|
+
} catch (terminalizeError) {
|
|
15133
|
+
logException(
|
|
15134
|
+
terminalizeError,
|
|
15135
|
+
"slack_resume_post_delivery_terminalize_failed",
|
|
15136
|
+
getResumeLogContext(runArgs, lockKey),
|
|
15137
|
+
{},
|
|
15138
|
+
"Failed to terminalize resumed turn after post-delivery commit failure"
|
|
15139
|
+
);
|
|
15140
|
+
}
|
|
15141
|
+
} else if ((isRetryableTurnError(error, "mcp_auth_resume") || isRetryableTurnError(error, "plugin_auth_resume")) && onAuthPause) {
|
|
14859
15142
|
deferredPauseKind = "auth";
|
|
14860
15143
|
deferredPauseHandler = async () => {
|
|
14861
15144
|
await onAuthPause(error);
|
|
@@ -14872,7 +15155,7 @@ async function resumeSlackTurn(args) {
|
|
|
14872
15155
|
error,
|
|
14873
15156
|
eventName: "slack_resume_turn_failed",
|
|
14874
15157
|
lockKey,
|
|
14875
|
-
resumeArgs:
|
|
15158
|
+
resumeArgs: runArgs
|
|
14876
15159
|
});
|
|
14877
15160
|
};
|
|
14878
15161
|
}
|
|
@@ -14880,20 +15163,30 @@ async function resumeSlackTurn(args) {
|
|
|
14880
15163
|
await processingReaction?.stop();
|
|
14881
15164
|
await stateAdapter.releaseLock(lock);
|
|
14882
15165
|
}
|
|
15166
|
+
if (postDeliveryCommitError) {
|
|
15167
|
+
logException(
|
|
15168
|
+
postDeliveryCommitError,
|
|
15169
|
+
"slack_resume_success_handler_failed",
|
|
15170
|
+
getResumeLogContext(runArgs, lockKey),
|
|
15171
|
+
{},
|
|
15172
|
+
"Failed to persist resumed turn state after final reply delivery"
|
|
15173
|
+
);
|
|
15174
|
+
throw postDeliveryCommitError;
|
|
15175
|
+
}
|
|
14883
15176
|
if (deferredPauseHandler) {
|
|
14884
15177
|
try {
|
|
14885
15178
|
await deferredPauseHandler();
|
|
14886
15179
|
if (deferredPauseKind === "auth") {
|
|
14887
15180
|
await postSlackMessageBestEffort(
|
|
14888
|
-
|
|
14889
|
-
|
|
15181
|
+
runArgs.channelId,
|
|
15182
|
+
runArgs.threadTs,
|
|
14890
15183
|
buildAuthPauseResponse()
|
|
14891
15184
|
);
|
|
14892
15185
|
}
|
|
14893
15186
|
if (deferredPauseKind === "timeout") {
|
|
14894
15187
|
await postTurnContinuationNoticeBestEffort({
|
|
14895
15188
|
lockKey,
|
|
14896
|
-
resumeArgs:
|
|
15189
|
+
resumeArgs: runArgs
|
|
14897
15190
|
});
|
|
14898
15191
|
}
|
|
14899
15192
|
return;
|
|
@@ -14903,7 +15196,7 @@ async function resumeSlackTurn(args) {
|
|
|
14903
15196
|
error: pauseError,
|
|
14904
15197
|
eventName: "slack_resume_pause_handler_failed",
|
|
14905
15198
|
lockKey,
|
|
14906
|
-
resumeArgs:
|
|
15199
|
+
resumeArgs: runArgs
|
|
14907
15200
|
});
|
|
14908
15201
|
return;
|
|
14909
15202
|
}
|
|
@@ -14926,6 +15219,8 @@ async function resumeAuthorizedRequest(args) {
|
|
|
14926
15219
|
onFailure: args.onFailure,
|
|
14927
15220
|
onAuthPause: args.onAuthPause,
|
|
14928
15221
|
onTimeoutPause: args.onTimeoutPause,
|
|
15222
|
+
onPostDeliveryCommitFailure: args.onPostDeliveryCommitFailure,
|
|
15223
|
+
beforeStart: args.beforeStart,
|
|
14929
15224
|
replyTimeoutMs: args.replyTimeoutMs
|
|
14930
15225
|
});
|
|
14931
15226
|
}
|
|
@@ -14940,7 +15235,7 @@ function completeAuthPauseTurn(args) {
|
|
|
14940
15235
|
skippedReason: void 0
|
|
14941
15236
|
}
|
|
14942
15237
|
);
|
|
14943
|
-
|
|
15238
|
+
markTurnClosed({
|
|
14944
15239
|
conversation: args.conversation,
|
|
14945
15240
|
nowMs: Date.now(),
|
|
14946
15241
|
sessionId: args.sessionId,
|
|
@@ -14960,6 +15255,7 @@ async function persistAuthPauseTurnState(args) {
|
|
|
14960
15255
|
// src/chat/services/timeout-resume.ts
|
|
14961
15256
|
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
14962
15257
|
var TURN_TIMEOUT_RESUME_PATH = "/api/internal/turn-resume";
|
|
15258
|
+
var TURN_TIMEOUT_RESUME_HMAC_CONTEXT = "junior.turn_timeout_resume.v1";
|
|
14963
15259
|
var TURN_TIMEOUT_RESUME_SIGNATURE_VERSION = "v1";
|
|
14964
15260
|
var TURN_TIMEOUT_RESUME_MAX_SKEW_MS = 5 * 60 * 1e3;
|
|
14965
15261
|
var TURN_TIMEOUT_RESUME_TIMESTAMP_HEADER = "x-junior-resume-timestamp";
|
|
@@ -14983,14 +15279,10 @@ async function getAwaitingTurnContinuationRequest(args) {
|
|
|
14983
15279
|
};
|
|
14984
15280
|
}
|
|
14985
15281
|
function getTurnTimeoutResumeSecret() {
|
|
14986
|
-
|
|
14987
|
-
if (explicit) {
|
|
14988
|
-
return explicit;
|
|
14989
|
-
}
|
|
14990
|
-
return getSlackSigningSecret();
|
|
15282
|
+
return process.env.JUNIOR_SECRET?.trim() || void 0;
|
|
14991
15283
|
}
|
|
14992
15284
|
function buildSignedPayload(timestamp, body) {
|
|
14993
|
-
return `${timestamp}:${body}`;
|
|
15285
|
+
return `${TURN_TIMEOUT_RESUME_HMAC_CONTEXT}:${timestamp}:${body}`;
|
|
14994
15286
|
}
|
|
14995
15287
|
function signTurnTimeoutResumeBody(secret, timestamp, body) {
|
|
14996
15288
|
const digest = createHmac2("sha256", secret).update(buildSignedPayload(timestamp, body)).digest("hex");
|
|
@@ -15028,7 +15320,7 @@ async function scheduleTurnTimeoutResume(request) {
|
|
|
15028
15320
|
const secret = getTurnTimeoutResumeSecret();
|
|
15029
15321
|
if (!secret) {
|
|
15030
15322
|
throw new Error(
|
|
15031
|
-
"Cannot determine timeout resume secret (set
|
|
15323
|
+
"Cannot determine timeout resume secret (set JUNIOR_SECRET)"
|
|
15032
15324
|
);
|
|
15033
15325
|
}
|
|
15034
15326
|
const body = JSON.stringify(request);
|
|
@@ -15126,57 +15418,43 @@ function htmlResponse(kind) {
|
|
|
15126
15418
|
const page = CALLBACK_PAGES[kind];
|
|
15127
15419
|
return htmlCallbackResponse(page.title, page.message, page.status);
|
|
15128
15420
|
}
|
|
15129
|
-
async function buildResumeConversationContext(channelId, threadTs, sessionId) {
|
|
15130
|
-
const threadId = `slack:${channelId}:${threadTs}`;
|
|
15131
|
-
const conversation = coerceThreadConversationState(
|
|
15132
|
-
await getPersistedThreadState(threadId)
|
|
15133
|
-
);
|
|
15134
|
-
const userMessageId = getTurnUserMessageId(conversation, sessionId);
|
|
15135
|
-
return buildConversationContext(conversation, {
|
|
15136
|
-
excludeMessageId: userMessageId
|
|
15137
|
-
});
|
|
15138
|
-
}
|
|
15139
15421
|
async function persistCompletedReplyState(channelId, threadTs, sessionId, reply) {
|
|
15140
15422
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
15141
15423
|
const currentState = await getPersistedThreadState(threadId);
|
|
15142
15424
|
const conversation = coerceThreadConversationState(currentState);
|
|
15143
15425
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
15144
|
-
const
|
|
15145
|
-
const
|
|
15146
|
-
|
|
15147
|
-
markConversationMessage(conversation, userMessageId, {
|
|
15148
|
-
replied: true,
|
|
15149
|
-
skippedReason: void 0
|
|
15150
|
-
});
|
|
15151
|
-
upsertConversationMessage(conversation, {
|
|
15152
|
-
id: generateConversationId("assistant"),
|
|
15153
|
-
role: "assistant",
|
|
15154
|
-
text: normalizeConversationText(reply.text) || "[empty response]",
|
|
15155
|
-
createdAtMs: Date.now(),
|
|
15156
|
-
author: {
|
|
15157
|
-
userName: botConfig.userName,
|
|
15158
|
-
isBot: true
|
|
15159
|
-
},
|
|
15160
|
-
meta: {
|
|
15161
|
-
replied: true
|
|
15162
|
-
}
|
|
15163
|
-
});
|
|
15164
|
-
if (reply.piMessages) {
|
|
15165
|
-
conversation.piMessages = reply.piMessages;
|
|
15166
|
-
}
|
|
15167
|
-
markTurnCompleted({
|
|
15426
|
+
const userMessage = getTurnUserMessage(conversation, sessionId);
|
|
15427
|
+
const statePatch = buildDeliveredTurnStatePatch({
|
|
15428
|
+
artifacts,
|
|
15168
15429
|
conversation,
|
|
15169
|
-
|
|
15430
|
+
reply,
|
|
15170
15431
|
sessionId,
|
|
15171
|
-
|
|
15432
|
+
userMessageId: userMessage?.id
|
|
15172
15433
|
});
|
|
15173
15434
|
await persistThreadStateById(threadId, {
|
|
15174
|
-
|
|
15175
|
-
conversation,
|
|
15176
|
-
sandboxId: reply.sandboxId,
|
|
15177
|
-
sandboxDependencyProfileHash: reply.sandboxDependencyProfileHash
|
|
15435
|
+
...statePatch
|
|
15178
15436
|
});
|
|
15179
15437
|
}
|
|
15438
|
+
async function failCheckpointBestEffort(args) {
|
|
15439
|
+
try {
|
|
15440
|
+
await failAgentTurnSessionCheckpoint({
|
|
15441
|
+
conversationId: args.conversationId,
|
|
15442
|
+
sessionId: args.sessionId,
|
|
15443
|
+
errorMessage: args.errorMessage
|
|
15444
|
+
});
|
|
15445
|
+
} catch (error) {
|
|
15446
|
+
logException(
|
|
15447
|
+
error,
|
|
15448
|
+
"mcp_oauth_callback_checkpoint_fail_persist_failed",
|
|
15449
|
+
{},
|
|
15450
|
+
{
|
|
15451
|
+
"app.ai.conversation_id": args.conversationId,
|
|
15452
|
+
"app.ai.session_id": args.sessionId
|
|
15453
|
+
},
|
|
15454
|
+
"Failed to mark MCP OAuth-resumed turn checkpoint failed"
|
|
15455
|
+
);
|
|
15456
|
+
}
|
|
15457
|
+
}
|
|
15180
15458
|
async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
15181
15459
|
const threadId = `slack:${channelId}:${threadTs}`;
|
|
15182
15460
|
const currentState = await getPersistedThreadState(threadId);
|
|
@@ -15190,6 +15468,11 @@ async function persistFailedReplyState(channelId, threadTs, sessionId) {
|
|
|
15190
15468
|
markConversationMessage,
|
|
15191
15469
|
updateConversationStats
|
|
15192
15470
|
});
|
|
15471
|
+
await failCheckpointBestEffort({
|
|
15472
|
+
conversationId: threadId,
|
|
15473
|
+
sessionId,
|
|
15474
|
+
errorMessage: "OAuth-resumed MCP turn failed"
|
|
15475
|
+
});
|
|
15193
15476
|
await persistThreadStateById(threadId, {
|
|
15194
15477
|
conversation
|
|
15195
15478
|
});
|
|
@@ -15202,7 +15485,6 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
15202
15485
|
const threadId = `slack:${authSession.channelId}:${authSession.threadTs}`;
|
|
15203
15486
|
const currentState = await getPersistedThreadState(threadId);
|
|
15204
15487
|
const conversation = coerceThreadConversationState(currentState);
|
|
15205
|
-
const artifacts = coerceThreadArtifactsState(currentState);
|
|
15206
15488
|
const pendingAuth = getConversationPendingAuth({
|
|
15207
15489
|
conversation,
|
|
15208
15490
|
kind: "mcp",
|
|
@@ -15228,132 +15510,174 @@ async function resumeAuthorizedMcpTurn(args) {
|
|
|
15228
15510
|
if (!userMessage) {
|
|
15229
15511
|
return;
|
|
15230
15512
|
}
|
|
15231
|
-
const channelConfiguration = getChannelConfigurationServiceById(
|
|
15232
|
-
authSession.channelId
|
|
15233
|
-
);
|
|
15234
|
-
const conversationContext = await buildResumeConversationContext(
|
|
15235
|
-
authSession.channelId,
|
|
15236
|
-
authSession.threadTs,
|
|
15237
|
-
resolvedSessionId
|
|
15238
|
-
);
|
|
15239
15513
|
await resumeAuthorizedRequest({
|
|
15240
15514
|
messageText: userMessage.text,
|
|
15241
15515
|
channelId: authSession.channelId,
|
|
15242
15516
|
threadTs: authSession.threadTs,
|
|
15243
15517
|
messageTs: getTurnUserSlackMessageTs(userMessage),
|
|
15244
|
-
lockKey:
|
|
15518
|
+
lockKey: threadId,
|
|
15245
15519
|
connectedText: "",
|
|
15246
|
-
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
turnId: resolvedSessionId,
|
|
15255
|
-
channelId: authSession.channelId,
|
|
15256
|
-
threadTs: authSession.threadTs,
|
|
15520
|
+
beforeStart: async () => {
|
|
15521
|
+
const lockedState = await getPersistedThreadState(threadId);
|
|
15522
|
+
const lockedConversation = coerceThreadConversationState(lockedState);
|
|
15523
|
+
const lockedArtifacts = coerceThreadArtifactsState(lockedState);
|
|
15524
|
+
const lockedPendingAuth = getConversationPendingAuth({
|
|
15525
|
+
conversation: lockedConversation,
|
|
15526
|
+
kind: "mcp",
|
|
15527
|
+
provider,
|
|
15257
15528
|
requesterId: authSession.userId
|
|
15258
|
-
}
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15262
|
-
piMessages: conversation.piMessages,
|
|
15263
|
-
configuration: authSession.configuration,
|
|
15264
|
-
pendingAuth,
|
|
15265
|
-
channelConfiguration,
|
|
15266
|
-
sandbox: getPersistedSandboxState(currentState),
|
|
15267
|
-
onAuthPending: async (nextPendingAuth) => {
|
|
15268
|
-
await applyPendingAuthUpdate({
|
|
15269
|
-
conversation,
|
|
15270
|
-
conversationId: authSession.conversationId,
|
|
15271
|
-
nextPendingAuth
|
|
15272
|
-
});
|
|
15273
|
-
await persistThreadStateById(threadId, { conversation });
|
|
15274
|
-
},
|
|
15275
|
-
...getTurnUserReplyAttachmentContext(userMessage)
|
|
15276
|
-
},
|
|
15277
|
-
onSuccess: async (reply) => {
|
|
15278
|
-
try {
|
|
15279
|
-
await persistCompletedReplyState(
|
|
15280
|
-
authSession.channelId,
|
|
15281
|
-
authSession.threadTs,
|
|
15282
|
-
resolvedSessionId,
|
|
15283
|
-
reply
|
|
15284
|
-
);
|
|
15285
|
-
} catch (persistError) {
|
|
15286
|
-
logException(
|
|
15287
|
-
persistError,
|
|
15288
|
-
"mcp_oauth_callback_resume_persist_failed",
|
|
15289
|
-
{},
|
|
15290
|
-
{ "app.credential.provider": provider },
|
|
15291
|
-
"Failed to persist resumed MCP turn state"
|
|
15292
|
-
);
|
|
15529
|
+
});
|
|
15530
|
+
const lockedSessionId = lockedPendingAuth?.sessionId ?? authSession.sessionId;
|
|
15531
|
+
if (lockedSessionId !== resolvedSessionId) {
|
|
15532
|
+
return false;
|
|
15293
15533
|
}
|
|
15294
|
-
|
|
15295
|
-
|
|
15296
|
-
|
|
15297
|
-
|
|
15298
|
-
|
|
15299
|
-
|
|
15300
|
-
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
|
|
15304
|
-
|
|
15305
|
-
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
);
|
|
15534
|
+
if (lockedPendingAuth) {
|
|
15535
|
+
if (!isPendingAuthLatestRequest(lockedConversation, lockedPendingAuth)) {
|
|
15536
|
+
clearPendingAuth(lockedConversation, lockedPendingAuth.sessionId);
|
|
15537
|
+
await persistThreadStateById(threadId, {
|
|
15538
|
+
conversation: lockedConversation
|
|
15539
|
+
});
|
|
15540
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
15541
|
+
conversationId: authSession.conversationId,
|
|
15542
|
+
sessionId: lockedPendingAuth.sessionId,
|
|
15543
|
+
errorMessage: "Auth completed after a newer thread message superseded this blocked request."
|
|
15544
|
+
});
|
|
15545
|
+
return false;
|
|
15546
|
+
}
|
|
15547
|
+
} else if (lockedConversation.processing.activeTurnId !== authSession.sessionId) {
|
|
15548
|
+
return false;
|
|
15310
15549
|
}
|
|
15311
|
-
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
sessionId: resolvedSessionId,
|
|
15315
|
-
threadStateId: `slack:${authSession.channelId}:${authSession.threadTs}`
|
|
15316
|
-
});
|
|
15317
|
-
logWarn(
|
|
15318
|
-
"mcp_oauth_callback_resume_reparked_for_auth",
|
|
15319
|
-
{},
|
|
15320
|
-
{
|
|
15321
|
-
"app.credential.provider": provider,
|
|
15322
|
-
...isRetryableTurnError(error) ? { "app.ai.retryable_reason": error.reason } : {}
|
|
15323
|
-
},
|
|
15324
|
-
"Resumed MCP turn requested another authorization flow"
|
|
15550
|
+
const lockedUserMessage = getTurnUserMessage(
|
|
15551
|
+
lockedConversation,
|
|
15552
|
+
lockedSessionId
|
|
15325
15553
|
);
|
|
15326
|
-
|
|
15327
|
-
|
|
15328
|
-
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
15329
|
-
throw error;
|
|
15330
|
-
}
|
|
15331
|
-
const checkpointVersion = error.metadata?.checkpointVersion;
|
|
15332
|
-
const nextSliceId = error.metadata?.sliceId;
|
|
15333
|
-
if (typeof checkpointVersion !== "number") {
|
|
15334
|
-
throw new Error(
|
|
15335
|
-
"Timed-out MCP resume did not include a checkpoint version"
|
|
15336
|
-
);
|
|
15554
|
+
if (!lockedUserMessage) {
|
|
15555
|
+
return false;
|
|
15337
15556
|
}
|
|
15338
|
-
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
15557
|
+
const lockedConversationContext = buildConversationContext(
|
|
15558
|
+
lockedConversation,
|
|
15559
|
+
{
|
|
15560
|
+
excludeMessageId: lockedUserMessage.id
|
|
15561
|
+
}
|
|
15562
|
+
);
|
|
15563
|
+
const lockedChannelConfiguration = getChannelConfigurationServiceById(
|
|
15564
|
+
authSession.channelId
|
|
15565
|
+
);
|
|
15566
|
+
return {
|
|
15567
|
+
messageText: lockedUserMessage.text,
|
|
15568
|
+
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
15569
|
+
replyContext: {
|
|
15570
|
+
requester: {
|
|
15571
|
+
userId: authSession.userId,
|
|
15572
|
+
userName: lockedUserMessage.author?.userName,
|
|
15573
|
+
fullName: lockedUserMessage.author?.fullName
|
|
15345
15574
|
},
|
|
15346
|
-
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15575
|
+
correlation: {
|
|
15576
|
+
conversationId: authSession.conversationId,
|
|
15577
|
+
turnId: lockedSessionId,
|
|
15578
|
+
channelId: authSession.channelId,
|
|
15579
|
+
threadTs: authSession.threadTs,
|
|
15580
|
+
requesterId: authSession.userId
|
|
15581
|
+
},
|
|
15582
|
+
toolChannelId: authSession.toolChannelId ?? lockedArtifacts.assistantContextChannelId ?? authSession.channelId,
|
|
15583
|
+
conversationContext: lockedConversationContext,
|
|
15584
|
+
artifactState: lockedArtifacts,
|
|
15585
|
+
piMessages: lockedConversation.piMessages,
|
|
15586
|
+
configuration: authSession.configuration,
|
|
15587
|
+
pendingAuth: lockedPendingAuth,
|
|
15588
|
+
channelConfiguration: lockedChannelConfiguration,
|
|
15589
|
+
sandbox: getPersistedSandboxState(lockedState),
|
|
15590
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
15591
|
+
await applyPendingAuthUpdate({
|
|
15592
|
+
conversation: lockedConversation,
|
|
15593
|
+
conversationId: authSession.conversationId,
|
|
15594
|
+
nextPendingAuth
|
|
15595
|
+
});
|
|
15596
|
+
await persistThreadStateById(threadId, {
|
|
15597
|
+
conversation: lockedConversation
|
|
15598
|
+
});
|
|
15599
|
+
},
|
|
15600
|
+
...getTurnUserReplyAttachmentContext(lockedUserMessage)
|
|
15601
|
+
},
|
|
15602
|
+
onSuccess: async (reply) => {
|
|
15603
|
+
await persistCompletedReplyState(
|
|
15604
|
+
authSession.channelId,
|
|
15605
|
+
authSession.threadTs,
|
|
15606
|
+
lockedSessionId,
|
|
15607
|
+
reply
|
|
15608
|
+
);
|
|
15609
|
+
},
|
|
15610
|
+
onPostDeliveryCommitFailure: async () => {
|
|
15611
|
+
await failAgentTurnSessionCheckpoint({
|
|
15612
|
+
conversationId: authSession.conversationId,
|
|
15613
|
+
sessionId: lockedSessionId,
|
|
15614
|
+
errorMessage: "OAuth-resumed MCP reply was delivered but completion state did not persist"
|
|
15615
|
+
});
|
|
15616
|
+
},
|
|
15617
|
+
onFailure: async () => {
|
|
15618
|
+
try {
|
|
15619
|
+
await persistFailedReplyState(
|
|
15620
|
+
authSession.channelId,
|
|
15621
|
+
authSession.threadTs,
|
|
15622
|
+
lockedSessionId
|
|
15623
|
+
);
|
|
15624
|
+
} catch (persistError) {
|
|
15625
|
+
logException(
|
|
15626
|
+
persistError,
|
|
15627
|
+
"mcp_oauth_callback_resume_failure_persist_failed",
|
|
15628
|
+
{},
|
|
15629
|
+
{ "app.credential.provider": provider },
|
|
15630
|
+
"Failed to persist failed MCP resume state"
|
|
15631
|
+
);
|
|
15632
|
+
}
|
|
15633
|
+
},
|
|
15634
|
+
onAuthPause: async (error) => {
|
|
15635
|
+
await persistAuthPauseTurnState({
|
|
15636
|
+
sessionId: lockedSessionId,
|
|
15637
|
+
threadStateId: threadId
|
|
15638
|
+
});
|
|
15639
|
+
logWarn(
|
|
15640
|
+
"mcp_oauth_callback_resume_reparked_for_auth",
|
|
15641
|
+
{},
|
|
15642
|
+
{
|
|
15643
|
+
"app.credential.provider": provider,
|
|
15644
|
+
...isRetryableTurnError(error) ? { "app.ai.retryable_reason": error.reason } : {}
|
|
15645
|
+
},
|
|
15646
|
+
"Resumed MCP turn requested another authorization flow"
|
|
15647
|
+
);
|
|
15648
|
+
},
|
|
15649
|
+
onTimeoutPause: async (error) => {
|
|
15650
|
+
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
15651
|
+
throw error;
|
|
15652
|
+
}
|
|
15653
|
+
const checkpointVersion = error.metadata?.checkpointVersion;
|
|
15654
|
+
const nextSliceId = error.metadata?.sliceId;
|
|
15655
|
+
if (typeof checkpointVersion !== "number") {
|
|
15656
|
+
throw new Error(
|
|
15657
|
+
"Timed-out MCP resume did not include a checkpoint version"
|
|
15658
|
+
);
|
|
15659
|
+
}
|
|
15660
|
+
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
15661
|
+
logWarn(
|
|
15662
|
+
"mcp_oauth_callback_resume_slice_limit_reached",
|
|
15663
|
+
{},
|
|
15664
|
+
{
|
|
15665
|
+
"app.credential.provider": provider,
|
|
15666
|
+
...typeof nextSliceId === "number" ? { "app.ai.resume_slice_id": nextSliceId } : {}
|
|
15667
|
+
},
|
|
15668
|
+
"Skipped automatic timeout resume because the turn exceeded the slice limit"
|
|
15669
|
+
);
|
|
15670
|
+
throw new Error(
|
|
15671
|
+
"Timed-out turn exceeded the automatic resume slice limit"
|
|
15672
|
+
);
|
|
15673
|
+
}
|
|
15674
|
+
await scheduleTurnTimeoutResume({
|
|
15675
|
+
conversationId: authSession.conversationId,
|
|
15676
|
+
sessionId: lockedSessionId,
|
|
15677
|
+
expectedCheckpointVersion: checkpointVersion
|
|
15678
|
+
});
|
|
15679
|
+
}
|
|
15680
|
+
};
|
|
15357
15681
|
}
|
|
15358
15682
|
});
|
|
15359
15683
|
}
|
|
@@ -15552,55 +15876,42 @@ async function publishAppHomeView(slackClient, userId, userTokenStore) {
|
|
|
15552
15876
|
function htmlErrorResponse(title, message, status) {
|
|
15553
15877
|
return htmlCallbackResponse(escapeXml(title), escapeXml(message), status);
|
|
15554
15878
|
}
|
|
15555
|
-
async function buildCheckpointConversationContext(conversationId, sessionId) {
|
|
15556
|
-
const conversation = coerceThreadConversationState(
|
|
15557
|
-
await getPersistedThreadState(conversationId)
|
|
15558
|
-
);
|
|
15559
|
-
const userMessage = getTurnUserMessage(conversation, sessionId);
|
|
15560
|
-
return buildConversationContext(conversation, {
|
|
15561
|
-
excludeMessageId: userMessage?.id
|
|
15562
|
-
});
|
|
15563
|
-
}
|
|
15564
15879
|
async function persistCompletedOAuthReplyState(args) {
|
|
15565
15880
|
const currentState = await getPersistedThreadState(args.conversationId);
|
|
15566
15881
|
const conversation = coerceThreadConversationState(currentState);
|
|
15567
15882
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
15568
|
-
const nextArtifacts = args.reply.artifactStatePatch ? mergeArtifactsState(artifacts, args.reply.artifactStatePatch) : void 0;
|
|
15569
15883
|
const userMessage = getTurnUserMessage(conversation, args.sessionId);
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
replied: true,
|
|
15573
|
-
skippedReason: void 0
|
|
15574
|
-
});
|
|
15575
|
-
upsertConversationMessage(conversation, {
|
|
15576
|
-
id: generateConversationId("assistant"),
|
|
15577
|
-
role: "assistant",
|
|
15578
|
-
text: normalizeConversationText(args.reply.text) || "[empty response]",
|
|
15579
|
-
createdAtMs: Date.now(),
|
|
15580
|
-
author: {
|
|
15581
|
-
userName: botConfig.userName,
|
|
15582
|
-
isBot: true
|
|
15583
|
-
},
|
|
15584
|
-
meta: {
|
|
15585
|
-
replied: true
|
|
15586
|
-
}
|
|
15587
|
-
});
|
|
15588
|
-
if (args.reply.piMessages) {
|
|
15589
|
-
conversation.piMessages = args.reply.piMessages;
|
|
15590
|
-
}
|
|
15591
|
-
markTurnCompleted({
|
|
15884
|
+
const statePatch = buildDeliveredTurnStatePatch({
|
|
15885
|
+
artifacts,
|
|
15592
15886
|
conversation,
|
|
15593
|
-
|
|
15887
|
+
reply: args.reply,
|
|
15594
15888
|
sessionId: args.sessionId,
|
|
15595
|
-
|
|
15889
|
+
userMessageId: userMessage?.id
|
|
15596
15890
|
});
|
|
15597
15891
|
await persistThreadStateById(args.conversationId, {
|
|
15598
|
-
|
|
15599
|
-
conversation,
|
|
15600
|
-
sandboxId: args.reply.sandboxId,
|
|
15601
|
-
sandboxDependencyProfileHash: args.reply.sandboxDependencyProfileHash
|
|
15892
|
+
...statePatch
|
|
15602
15893
|
});
|
|
15603
15894
|
}
|
|
15895
|
+
async function failCheckpointBestEffort2(args) {
|
|
15896
|
+
try {
|
|
15897
|
+
await failAgentTurnSessionCheckpoint({
|
|
15898
|
+
conversationId: args.conversationId,
|
|
15899
|
+
sessionId: args.sessionId,
|
|
15900
|
+
errorMessage: args.errorMessage
|
|
15901
|
+
});
|
|
15902
|
+
} catch (error) {
|
|
15903
|
+
logException(
|
|
15904
|
+
error,
|
|
15905
|
+
"oauth_callback_checkpoint_fail_persist_failed",
|
|
15906
|
+
{},
|
|
15907
|
+
{
|
|
15908
|
+
"app.ai.conversation_id": args.conversationId,
|
|
15909
|
+
"app.ai.session_id": args.sessionId
|
|
15910
|
+
},
|
|
15911
|
+
"Failed to mark OAuth-resumed turn checkpoint failed"
|
|
15912
|
+
);
|
|
15913
|
+
}
|
|
15914
|
+
}
|
|
15604
15915
|
async function persistFailedOAuthReplyState(args) {
|
|
15605
15916
|
const currentState = await getPersistedThreadState(args.conversationId);
|
|
15606
15917
|
const conversation = coerceThreadConversationState(currentState);
|
|
@@ -15613,6 +15924,11 @@ async function persistFailedOAuthReplyState(args) {
|
|
|
15613
15924
|
markConversationMessage,
|
|
15614
15925
|
updateConversationStats
|
|
15615
15926
|
});
|
|
15927
|
+
await failCheckpointBestEffort2({
|
|
15928
|
+
conversationId: args.conversationId,
|
|
15929
|
+
sessionId: args.sessionId,
|
|
15930
|
+
errorMessage: "OAuth-resumed turn failed"
|
|
15931
|
+
});
|
|
15616
15932
|
await persistThreadStateById(args.conversationId, {
|
|
15617
15933
|
conversation
|
|
15618
15934
|
});
|
|
@@ -15638,7 +15954,6 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
15638
15954
|
stored.resumeConversationId
|
|
15639
15955
|
);
|
|
15640
15956
|
const conversation = coerceThreadConversationState(currentState);
|
|
15641
|
-
const artifacts = coerceThreadArtifactsState(currentState);
|
|
15642
15957
|
const pendingAuth = getConversationPendingAuth({
|
|
15643
15958
|
conversation,
|
|
15644
15959
|
kind: "plugin",
|
|
@@ -15671,102 +15986,163 @@ async function resumeCheckpointedOAuthTurn(stored) {
|
|
|
15671
15986
|
if (!userMessage?.author?.userId || !resolvedSessionId) {
|
|
15672
15987
|
return false;
|
|
15673
15988
|
}
|
|
15674
|
-
const conversationContext = await buildCheckpointConversationContext(
|
|
15675
|
-
stored.resumeConversationId,
|
|
15676
|
-
resolvedSessionId
|
|
15677
|
-
);
|
|
15678
|
-
const channelConfiguration = getChannelConfigurationServiceById(
|
|
15679
|
-
stored.channelId
|
|
15680
|
-
);
|
|
15681
15989
|
await resumeSlackTurn({
|
|
15682
15990
|
messageText: stored.pendingMessage ?? userMessage.text,
|
|
15683
15991
|
channelId: stored.channelId,
|
|
15684
15992
|
threadTs: stored.threadTs,
|
|
15685
15993
|
messageTs: getTurnUserSlackMessageTs(userMessage),
|
|
15686
15994
|
lockKey: stored.resumeConversationId,
|
|
15687
|
-
initialText: "",
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15706
|
-
piMessages: conversation.piMessages,
|
|
15707
|
-
sandbox: getPersistedSandboxState(currentState),
|
|
15708
|
-
onAuthPending: async (nextPendingAuth) => {
|
|
15709
|
-
await applyPendingAuthUpdate({
|
|
15710
|
-
conversation,
|
|
15711
|
-
conversationId: stored.resumeConversationId,
|
|
15712
|
-
nextPendingAuth
|
|
15713
|
-
});
|
|
15714
|
-
await persistThreadStateById(stored.resumeConversationId, {
|
|
15715
|
-
conversation
|
|
15716
|
-
});
|
|
15717
|
-
},
|
|
15718
|
-
...getTurnUserReplyAttachmentContext(userMessage)
|
|
15719
|
-
},
|
|
15720
|
-
onSuccess: async (reply) => {
|
|
15721
|
-
logInfo(
|
|
15722
|
-
"oauth_callback_resume_complete",
|
|
15723
|
-
{},
|
|
15724
|
-
{
|
|
15725
|
-
"app.credential.provider": stored.provider,
|
|
15726
|
-
"app.ai.outcome": reply.diagnostics.outcome,
|
|
15727
|
-
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
15728
|
-
},
|
|
15729
|
-
"OAuth callback auto-resumed checkpoint finished replying"
|
|
15730
|
-
);
|
|
15731
|
-
await persistCompletedOAuthReplyState({
|
|
15732
|
-
conversationId: stored.resumeConversationId,
|
|
15733
|
-
sessionId: resolvedSessionId,
|
|
15734
|
-
reply
|
|
15735
|
-
});
|
|
15736
|
-
},
|
|
15737
|
-
onFailure: async () => {
|
|
15738
|
-
await persistFailedOAuthReplyState({
|
|
15739
|
-
conversationId: stored.resumeConversationId,
|
|
15740
|
-
sessionId: resolvedSessionId
|
|
15741
|
-
});
|
|
15742
|
-
},
|
|
15743
|
-
onAuthPause: async () => {
|
|
15744
|
-
await persistAuthPauseTurnState({
|
|
15745
|
-
sessionId: resolvedSessionId,
|
|
15746
|
-
threadStateId: stored.resumeConversationId
|
|
15995
|
+
initialText: "",
|
|
15996
|
+
beforeStart: async () => {
|
|
15997
|
+
const lockedCheckpoint = await getAgentTurnSessionCheckpoint(
|
|
15998
|
+
stored.resumeConversationId,
|
|
15999
|
+
stored.resumeSessionId
|
|
16000
|
+
);
|
|
16001
|
+
if (!lockedCheckpoint || lockedCheckpoint.state !== "awaiting_resume" || lockedCheckpoint.resumeReason !== "auth") {
|
|
16002
|
+
return false;
|
|
16003
|
+
}
|
|
16004
|
+
const lockedState = await getPersistedThreadState(
|
|
16005
|
+
stored.resumeConversationId
|
|
16006
|
+
);
|
|
16007
|
+
const lockedConversation = coerceThreadConversationState(lockedState);
|
|
16008
|
+
const lockedArtifacts = coerceThreadArtifactsState(lockedState);
|
|
16009
|
+
const lockedPendingAuth = getConversationPendingAuth({
|
|
16010
|
+
conversation: lockedConversation,
|
|
16011
|
+
kind: "plugin",
|
|
16012
|
+
provider: stored.provider,
|
|
16013
|
+
requesterId: stored.userId
|
|
15747
16014
|
});
|
|
15748
|
-
|
|
15749
|
-
|
|
15750
|
-
|
|
15751
|
-
throw error;
|
|
16015
|
+
const lockedSessionId = lockedPendingAuth?.sessionId ?? stored.resumeSessionId;
|
|
16016
|
+
if (lockedSessionId !== resolvedSessionId) {
|
|
16017
|
+
return false;
|
|
15752
16018
|
}
|
|
15753
|
-
|
|
15754
|
-
|
|
15755
|
-
|
|
15756
|
-
|
|
15757
|
-
|
|
15758
|
-
|
|
16019
|
+
if (lockedPendingAuth) {
|
|
16020
|
+
if (!isPendingAuthLatestRequest(lockedConversation, lockedPendingAuth)) {
|
|
16021
|
+
clearPendingAuth(lockedConversation, lockedPendingAuth.sessionId);
|
|
16022
|
+
await persistThreadStateById(stored.resumeConversationId, {
|
|
16023
|
+
conversation: lockedConversation
|
|
16024
|
+
});
|
|
16025
|
+
await supersedeAgentTurnSessionCheckpoint({
|
|
16026
|
+
conversationId: stored.resumeConversationId,
|
|
16027
|
+
sessionId: lockedPendingAuth.sessionId,
|
|
16028
|
+
errorMessage: "Auth completed after a newer thread message superseded this blocked request."
|
|
16029
|
+
});
|
|
16030
|
+
return false;
|
|
16031
|
+
}
|
|
16032
|
+
} else if (lockedConversation.processing.activeTurnId !== stored.resumeSessionId) {
|
|
16033
|
+
return false;
|
|
15759
16034
|
}
|
|
15760
|
-
|
|
15761
|
-
|
|
15762
|
-
|
|
15763
|
-
|
|
16035
|
+
const lockedUserMessage = getTurnUserMessage(
|
|
16036
|
+
lockedConversation,
|
|
16037
|
+
lockedSessionId
|
|
16038
|
+
);
|
|
16039
|
+
if (!lockedUserMessage?.author?.userId) {
|
|
16040
|
+
return false;
|
|
15764
16041
|
}
|
|
15765
|
-
|
|
15766
|
-
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
16042
|
+
const lockedConversationContext = buildConversationContext(
|
|
16043
|
+
lockedConversation,
|
|
16044
|
+
{
|
|
16045
|
+
excludeMessageId: lockedUserMessage.id
|
|
16046
|
+
}
|
|
16047
|
+
);
|
|
16048
|
+
const lockedChannelConfiguration = getChannelConfigurationServiceById(
|
|
16049
|
+
stored.channelId
|
|
16050
|
+
);
|
|
16051
|
+
return {
|
|
16052
|
+
messageText: stored.pendingMessage ?? lockedUserMessage.text,
|
|
16053
|
+
messageTs: getTurnUserSlackMessageTs(lockedUserMessage),
|
|
16054
|
+
replyContext: {
|
|
16055
|
+
requester: {
|
|
16056
|
+
userId: lockedUserMessage.author.userId,
|
|
16057
|
+
userName: lockedUserMessage.author.userName,
|
|
16058
|
+
fullName: lockedUserMessage.author.fullName
|
|
16059
|
+
},
|
|
16060
|
+
correlation: {
|
|
16061
|
+
conversationId: stored.resumeConversationId,
|
|
16062
|
+
turnId: lockedSessionId,
|
|
16063
|
+
channelId: stored.channelId,
|
|
16064
|
+
threadTs: stored.threadTs,
|
|
16065
|
+
requesterId: lockedUserMessage.author.userId
|
|
16066
|
+
},
|
|
16067
|
+
toolChannelId: lockedArtifacts.assistantContextChannelId ?? stored.channelId,
|
|
16068
|
+
artifactState: lockedArtifacts,
|
|
16069
|
+
pendingAuth: lockedPendingAuth,
|
|
16070
|
+
conversationContext: lockedConversationContext,
|
|
16071
|
+
channelConfiguration: lockedChannelConfiguration,
|
|
16072
|
+
piMessages: lockedConversation.piMessages,
|
|
16073
|
+
sandbox: getPersistedSandboxState(lockedState),
|
|
16074
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
16075
|
+
await applyPendingAuthUpdate({
|
|
16076
|
+
conversation: lockedConversation,
|
|
16077
|
+
conversationId: stored.resumeConversationId,
|
|
16078
|
+
nextPendingAuth
|
|
16079
|
+
});
|
|
16080
|
+
await persistThreadStateById(stored.resumeConversationId, {
|
|
16081
|
+
conversation: lockedConversation
|
|
16082
|
+
});
|
|
16083
|
+
},
|
|
16084
|
+
...getTurnUserReplyAttachmentContext(lockedUserMessage)
|
|
16085
|
+
},
|
|
16086
|
+
onSuccess: async (reply) => {
|
|
16087
|
+
logInfo(
|
|
16088
|
+
"oauth_callback_resume_complete",
|
|
16089
|
+
{},
|
|
16090
|
+
{
|
|
16091
|
+
"app.credential.provider": stored.provider,
|
|
16092
|
+
"app.ai.outcome": reply.diagnostics.outcome,
|
|
16093
|
+
"app.ai.tool_calls": reply.diagnostics.toolCalls.length
|
|
16094
|
+
},
|
|
16095
|
+
"OAuth callback auto-resumed checkpoint finished replying"
|
|
16096
|
+
);
|
|
16097
|
+
await persistCompletedOAuthReplyState({
|
|
16098
|
+
conversationId: stored.resumeConversationId,
|
|
16099
|
+
sessionId: lockedSessionId,
|
|
16100
|
+
reply
|
|
16101
|
+
});
|
|
16102
|
+
},
|
|
16103
|
+
onPostDeliveryCommitFailure: async () => {
|
|
16104
|
+
await failAgentTurnSessionCheckpoint({
|
|
16105
|
+
conversationId: stored.resumeConversationId,
|
|
16106
|
+
expectedCheckpointVersion: lockedCheckpoint.checkpointVersion,
|
|
16107
|
+
sessionId: lockedSessionId,
|
|
16108
|
+
errorMessage: "OAuth-resumed reply was delivered but completion state did not persist"
|
|
16109
|
+
});
|
|
16110
|
+
},
|
|
16111
|
+
onFailure: async () => {
|
|
16112
|
+
await persistFailedOAuthReplyState({
|
|
16113
|
+
conversationId: stored.resumeConversationId,
|
|
16114
|
+
sessionId: lockedSessionId
|
|
16115
|
+
});
|
|
16116
|
+
},
|
|
16117
|
+
onAuthPause: async () => {
|
|
16118
|
+
await persistAuthPauseTurnState({
|
|
16119
|
+
sessionId: lockedSessionId,
|
|
16120
|
+
threadStateId: stored.resumeConversationId
|
|
16121
|
+
});
|
|
16122
|
+
},
|
|
16123
|
+
onTimeoutPause: async (error) => {
|
|
16124
|
+
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
16125
|
+
throw error;
|
|
16126
|
+
}
|
|
16127
|
+
const checkpointVersion = error.metadata?.checkpointVersion;
|
|
16128
|
+
const nextSliceId = error.metadata?.sliceId;
|
|
16129
|
+
if (typeof checkpointVersion !== "number") {
|
|
16130
|
+
throw new Error(
|
|
16131
|
+
"Timed-out OAuth resume did not include a checkpoint version"
|
|
16132
|
+
);
|
|
16133
|
+
}
|
|
16134
|
+
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
16135
|
+
throw new Error(
|
|
16136
|
+
"Timed-out turn exceeded the automatic resume slice limit"
|
|
16137
|
+
);
|
|
16138
|
+
}
|
|
16139
|
+
await scheduleTurnTimeoutResume({
|
|
16140
|
+
conversationId: stored.resumeConversationId,
|
|
16141
|
+
sessionId: lockedSessionId,
|
|
16142
|
+
expectedCheckpointVersion: checkpointVersion
|
|
16143
|
+
});
|
|
16144
|
+
}
|
|
16145
|
+
};
|
|
15770
16146
|
}
|
|
15771
16147
|
});
|
|
15772
16148
|
return true;
|
|
@@ -16522,7 +16898,7 @@ function isSandboxEgressRequest(request) {
|
|
|
16522
16898
|
|
|
16523
16899
|
// src/handlers/turn-resume.ts
|
|
16524
16900
|
var TIMEOUT_RESUME_LOCK_RETRY_DELAYS_MS = [250, 1e3, 2e3];
|
|
16525
|
-
function
|
|
16901
|
+
function sleep4(ms) {
|
|
16526
16902
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
16527
16903
|
}
|
|
16528
16904
|
async function persistCompletedReplyState2(args) {
|
|
@@ -16531,45 +16907,42 @@ async function persistCompletedReplyState2(args) {
|
|
|
16531
16907
|
);
|
|
16532
16908
|
const conversation = coerceThreadConversationState(currentState);
|
|
16533
16909
|
const artifacts = coerceThreadArtifactsState(currentState);
|
|
16534
|
-
const nextArtifacts = args.reply.artifactStatePatch ? mergeArtifactsState(artifacts, args.reply.artifactStatePatch) : void 0;
|
|
16535
16910
|
const userMessage = getTurnUserMessage(
|
|
16536
16911
|
conversation,
|
|
16537
16912
|
args.checkpoint.sessionId
|
|
16538
16913
|
);
|
|
16539
|
-
|
|
16540
|
-
|
|
16541
|
-
replied: true,
|
|
16542
|
-
skippedReason: void 0
|
|
16543
|
-
});
|
|
16544
|
-
upsertConversationMessage(conversation, {
|
|
16545
|
-
id: generateConversationId("assistant"),
|
|
16546
|
-
role: "assistant",
|
|
16547
|
-
text: normalizeConversationText(args.reply.text) || "[empty response]",
|
|
16548
|
-
createdAtMs: Date.now(),
|
|
16549
|
-
author: {
|
|
16550
|
-
userName: botConfig.userName,
|
|
16551
|
-
isBot: true
|
|
16552
|
-
},
|
|
16553
|
-
meta: {
|
|
16554
|
-
replied: true
|
|
16555
|
-
}
|
|
16556
|
-
});
|
|
16557
|
-
if (args.reply.piMessages) {
|
|
16558
|
-
conversation.piMessages = args.reply.piMessages;
|
|
16559
|
-
}
|
|
16560
|
-
markTurnCompleted({
|
|
16914
|
+
const statePatch = buildDeliveredTurnStatePatch({
|
|
16915
|
+
artifacts,
|
|
16561
16916
|
conversation,
|
|
16562
|
-
|
|
16917
|
+
reply: args.reply,
|
|
16563
16918
|
sessionId: args.checkpoint.sessionId,
|
|
16564
|
-
|
|
16919
|
+
userMessageId: userMessage?.id
|
|
16565
16920
|
});
|
|
16566
16921
|
await persistThreadStateById(args.checkpoint.conversationId, {
|
|
16567
|
-
|
|
16568
|
-
conversation,
|
|
16569
|
-
sandboxId: args.reply.sandboxId,
|
|
16570
|
-
sandboxDependencyProfileHash: args.reply.sandboxDependencyProfileHash
|
|
16922
|
+
...statePatch
|
|
16571
16923
|
});
|
|
16572
16924
|
}
|
|
16925
|
+
async function failCheckpointBestEffort3(args) {
|
|
16926
|
+
try {
|
|
16927
|
+
await failAgentTurnSessionCheckpoint({
|
|
16928
|
+
conversationId: args.checkpoint.conversationId,
|
|
16929
|
+
expectedCheckpointVersion: args.checkpoint.checkpointVersion,
|
|
16930
|
+
sessionId: args.checkpoint.sessionId,
|
|
16931
|
+
errorMessage: args.errorMessage
|
|
16932
|
+
});
|
|
16933
|
+
} catch (error) {
|
|
16934
|
+
logException(
|
|
16935
|
+
error,
|
|
16936
|
+
"timeout_resume_checkpoint_fail_persist_failed",
|
|
16937
|
+
{},
|
|
16938
|
+
{
|
|
16939
|
+
"app.ai.conversation_id": args.checkpoint.conversationId,
|
|
16940
|
+
"app.ai.session_id": args.checkpoint.sessionId
|
|
16941
|
+
},
|
|
16942
|
+
"Failed to mark timed-out turn checkpoint failed"
|
|
16943
|
+
);
|
|
16944
|
+
}
|
|
16945
|
+
}
|
|
16573
16946
|
async function persistFailedReplyState2(checkpoint) {
|
|
16574
16947
|
const currentState = await getPersistedThreadState(checkpoint.conversationId);
|
|
16575
16948
|
const conversation = coerceThreadConversationState(currentState);
|
|
@@ -16582,145 +16955,152 @@ async function persistFailedReplyState2(checkpoint) {
|
|
|
16582
16955
|
markConversationMessage,
|
|
16583
16956
|
updateConversationStats
|
|
16584
16957
|
});
|
|
16958
|
+
await failCheckpointBestEffort3({
|
|
16959
|
+
checkpoint,
|
|
16960
|
+
errorMessage: "Timed-out turn failed while resuming"
|
|
16961
|
+
});
|
|
16585
16962
|
await persistThreadStateById(checkpoint.conversationId, {
|
|
16586
16963
|
conversation
|
|
16587
16964
|
});
|
|
16588
16965
|
}
|
|
16589
16966
|
async function resumeTimedOutTurn(payload) {
|
|
16590
|
-
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
16591
|
-
payload.conversationId,
|
|
16592
|
-
payload.sessionId
|
|
16593
|
-
);
|
|
16594
|
-
if (!checkpoint || checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "timeout" || checkpoint.checkpointVersion !== payload.expectedCheckpointVersion) {
|
|
16595
|
-
return;
|
|
16596
|
-
}
|
|
16597
16967
|
const thread = parseSlackThreadId(payload.conversationId);
|
|
16598
16968
|
if (!thread) {
|
|
16599
16969
|
throw new Error(
|
|
16600
16970
|
`Timeout resume requires a Slack thread conversation id, got "${payload.conversationId}"`
|
|
16601
16971
|
);
|
|
16602
16972
|
}
|
|
16603
|
-
const currentState = await getPersistedThreadState(payload.conversationId);
|
|
16604
|
-
const conversation = coerceThreadConversationState(currentState);
|
|
16605
|
-
const artifacts = coerceThreadArtifactsState(currentState);
|
|
16606
|
-
const userMessage = getTurnUserMessage(conversation, payload.sessionId);
|
|
16607
|
-
if (!userMessage?.author?.userId) {
|
|
16608
|
-
throw new Error(
|
|
16609
|
-
`Unable to locate the persisted user message for timeout resume session "${payload.sessionId}"`
|
|
16610
|
-
);
|
|
16611
|
-
}
|
|
16612
|
-
if (conversation.processing.activeTurnId !== payload.sessionId) {
|
|
16613
|
-
return;
|
|
16614
|
-
}
|
|
16615
|
-
const channelConfiguration = getChannelConfigurationServiceById(
|
|
16616
|
-
thread.channelId
|
|
16617
|
-
);
|
|
16618
|
-
const conversationContext = buildConversationContext(conversation, {
|
|
16619
|
-
excludeMessageId: userMessage.id
|
|
16620
|
-
});
|
|
16621
|
-
const sandbox = getPersistedSandboxState(currentState);
|
|
16622
16973
|
await resumeSlackTurn({
|
|
16623
|
-
messageText:
|
|
16974
|
+
messageText: "",
|
|
16624
16975
|
channelId: thread.channelId,
|
|
16625
16976
|
threadTs: thread.threadTs,
|
|
16626
16977
|
lockKey: payload.conversationId,
|
|
16627
|
-
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
fullName: userMessage.author.fullName
|
|
16632
|
-
},
|
|
16633
|
-
correlation: {
|
|
16634
|
-
conversationId: payload.conversationId,
|
|
16635
|
-
turnId: payload.sessionId,
|
|
16636
|
-
channelId: thread.channelId,
|
|
16637
|
-
threadTs: thread.threadTs,
|
|
16638
|
-
requesterId: userMessage.author.userId
|
|
16639
|
-
},
|
|
16640
|
-
toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId,
|
|
16641
|
-
artifactState: artifacts,
|
|
16642
|
-
pendingAuth: conversation.processing.pendingAuth,
|
|
16643
|
-
conversationContext,
|
|
16644
|
-
channelConfiguration,
|
|
16645
|
-
piMessages: conversation.piMessages,
|
|
16646
|
-
sandbox,
|
|
16647
|
-
onAuthPending: async (nextPendingAuth) => {
|
|
16648
|
-
await applyPendingAuthUpdate({
|
|
16649
|
-
conversation,
|
|
16650
|
-
conversationId: payload.conversationId,
|
|
16651
|
-
nextPendingAuth
|
|
16652
|
-
});
|
|
16653
|
-
await persistThreadStateById(payload.conversationId, {
|
|
16654
|
-
conversation
|
|
16655
|
-
});
|
|
16656
|
-
},
|
|
16657
|
-
...getTurnUserReplyAttachmentContext(userMessage)
|
|
16658
|
-
},
|
|
16659
|
-
onSuccess: async (reply) => {
|
|
16660
|
-
try {
|
|
16661
|
-
await persistCompletedReplyState2({ checkpoint, reply });
|
|
16662
|
-
} catch (persistError) {
|
|
16663
|
-
logException(
|
|
16664
|
-
persistError,
|
|
16665
|
-
"timeout_resume_complete_persist_failed",
|
|
16666
|
-
{},
|
|
16667
|
-
{
|
|
16668
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
16669
|
-
"app.ai.session_id": payload.sessionId
|
|
16670
|
-
},
|
|
16671
|
-
"Failed to persist completed timeout-resume state after reply delivery"
|
|
16672
|
-
);
|
|
16673
|
-
}
|
|
16674
|
-
},
|
|
16675
|
-
onFailure: async () => {
|
|
16676
|
-
await persistFailedReplyState2(checkpoint);
|
|
16677
|
-
},
|
|
16678
|
-
onAuthPause: async () => {
|
|
16679
|
-
await persistAuthPauseTurnState({
|
|
16680
|
-
sessionId: payload.sessionId,
|
|
16681
|
-
threadStateId: payload.conversationId
|
|
16682
|
-
});
|
|
16683
|
-
logWarn(
|
|
16684
|
-
"timeout_resume_reparked_for_auth",
|
|
16685
|
-
{},
|
|
16686
|
-
{
|
|
16687
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
16688
|
-
"app.ai.session_id": payload.sessionId
|
|
16689
|
-
},
|
|
16690
|
-
"Resumed timed-out turn parked for auth"
|
|
16978
|
+
beforeStart: async () => {
|
|
16979
|
+
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
16980
|
+
payload.conversationId,
|
|
16981
|
+
payload.sessionId
|
|
16691
16982
|
);
|
|
16692
|
-
|
|
16693
|
-
|
|
16694
|
-
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
16695
|
-
throw error;
|
|
16983
|
+
if (!checkpoint || checkpoint.state !== "awaiting_resume" || checkpoint.resumeReason !== "timeout" || checkpoint.checkpointVersion !== payload.expectedCheckpointVersion) {
|
|
16984
|
+
return false;
|
|
16696
16985
|
}
|
|
16697
|
-
const
|
|
16698
|
-
|
|
16699
|
-
|
|
16986
|
+
const currentState = await getPersistedThreadState(
|
|
16987
|
+
payload.conversationId
|
|
16988
|
+
);
|
|
16989
|
+
const conversation = coerceThreadConversationState(currentState);
|
|
16990
|
+
const artifacts = coerceThreadArtifactsState(currentState);
|
|
16991
|
+
const userMessage = getTurnUserMessage(conversation, payload.sessionId);
|
|
16992
|
+
if (!userMessage?.author?.userId) {
|
|
16700
16993
|
throw new Error(
|
|
16701
|
-
|
|
16994
|
+
`Unable to locate the persisted user message for timeout resume session "${payload.sessionId}"`
|
|
16702
16995
|
);
|
|
16703
16996
|
}
|
|
16704
|
-
if (
|
|
16705
|
-
|
|
16706
|
-
"timeout_resume_slice_limit_reached",
|
|
16707
|
-
{},
|
|
16708
|
-
{
|
|
16709
|
-
"app.ai.conversation_id": payload.conversationId,
|
|
16710
|
-
"app.ai.session_id": payload.sessionId,
|
|
16711
|
-
...typeof nextSliceId === "number" ? { "app.ai.resume_slice_id": nextSliceId } : {}
|
|
16712
|
-
},
|
|
16713
|
-
"Skipped automatic timeout resume because the turn exceeded the slice limit"
|
|
16714
|
-
);
|
|
16715
|
-
throw new Error(
|
|
16716
|
-
"Timed-out turn exceeded the automatic resume slice limit"
|
|
16717
|
-
);
|
|
16997
|
+
if (conversation.processing.activeTurnId !== payload.sessionId) {
|
|
16998
|
+
return false;
|
|
16718
16999
|
}
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
17000
|
+
const channelConfiguration = getChannelConfigurationServiceById(
|
|
17001
|
+
thread.channelId
|
|
17002
|
+
);
|
|
17003
|
+
const conversationContext = buildConversationContext(conversation, {
|
|
17004
|
+
excludeMessageId: userMessage.id
|
|
16723
17005
|
});
|
|
17006
|
+
const sandbox = getPersistedSandboxState(currentState);
|
|
17007
|
+
return {
|
|
17008
|
+
messageText: userMessage.text,
|
|
17009
|
+
messageTs: getTurnUserSlackMessageTs(userMessage),
|
|
17010
|
+
replyContext: {
|
|
17011
|
+
requester: {
|
|
17012
|
+
userId: userMessage.author.userId,
|
|
17013
|
+
userName: userMessage.author.userName,
|
|
17014
|
+
fullName: userMessage.author.fullName
|
|
17015
|
+
},
|
|
17016
|
+
correlation: {
|
|
17017
|
+
conversationId: payload.conversationId,
|
|
17018
|
+
turnId: payload.sessionId,
|
|
17019
|
+
channelId: thread.channelId,
|
|
17020
|
+
threadTs: thread.threadTs,
|
|
17021
|
+
requesterId: userMessage.author.userId
|
|
17022
|
+
},
|
|
17023
|
+
toolChannelId: artifacts.assistantContextChannelId ?? thread.channelId,
|
|
17024
|
+
artifactState: artifacts,
|
|
17025
|
+
pendingAuth: conversation.processing.pendingAuth,
|
|
17026
|
+
conversationContext,
|
|
17027
|
+
channelConfiguration,
|
|
17028
|
+
piMessages: conversation.piMessages,
|
|
17029
|
+
sandbox,
|
|
17030
|
+
onAuthPending: async (nextPendingAuth) => {
|
|
17031
|
+
await applyPendingAuthUpdate({
|
|
17032
|
+
conversation,
|
|
17033
|
+
conversationId: payload.conversationId,
|
|
17034
|
+
nextPendingAuth
|
|
17035
|
+
});
|
|
17036
|
+
await persistThreadStateById(payload.conversationId, {
|
|
17037
|
+
conversation
|
|
17038
|
+
});
|
|
17039
|
+
},
|
|
17040
|
+
...getTurnUserReplyAttachmentContext(userMessage)
|
|
17041
|
+
},
|
|
17042
|
+
onSuccess: async (reply) => {
|
|
17043
|
+
await persistCompletedReplyState2({ checkpoint, reply });
|
|
17044
|
+
},
|
|
17045
|
+
onFailure: async () => {
|
|
17046
|
+
await persistFailedReplyState2(checkpoint);
|
|
17047
|
+
},
|
|
17048
|
+
onPostDeliveryCommitFailure: async () => {
|
|
17049
|
+
await failAgentTurnSessionCheckpoint({
|
|
17050
|
+
conversationId: checkpoint.conversationId,
|
|
17051
|
+
expectedCheckpointVersion: checkpoint.checkpointVersion,
|
|
17052
|
+
sessionId: checkpoint.sessionId,
|
|
17053
|
+
errorMessage: "Timed-out turn reply was delivered but completion state did not persist"
|
|
17054
|
+
});
|
|
17055
|
+
},
|
|
17056
|
+
onAuthPause: async () => {
|
|
17057
|
+
await persistAuthPauseTurnState({
|
|
17058
|
+
sessionId: payload.sessionId,
|
|
17059
|
+
threadStateId: payload.conversationId
|
|
17060
|
+
});
|
|
17061
|
+
logWarn(
|
|
17062
|
+
"timeout_resume_reparked_for_auth",
|
|
17063
|
+
{},
|
|
17064
|
+
{
|
|
17065
|
+
"app.ai.conversation_id": payload.conversationId,
|
|
17066
|
+
"app.ai.session_id": payload.sessionId
|
|
17067
|
+
},
|
|
17068
|
+
"Resumed timed-out turn parked for auth"
|
|
17069
|
+
);
|
|
17070
|
+
},
|
|
17071
|
+
onTimeoutPause: async (error) => {
|
|
17072
|
+
if (!isRetryableTurnError(error, "turn_timeout_resume")) {
|
|
17073
|
+
throw error;
|
|
17074
|
+
}
|
|
17075
|
+
const checkpointVersion = error.metadata?.checkpointVersion;
|
|
17076
|
+
const nextSliceId = error.metadata?.sliceId;
|
|
17077
|
+
if (typeof checkpointVersion !== "number") {
|
|
17078
|
+
throw new Error(
|
|
17079
|
+
"Timed-out resume turn did not include a checkpoint version"
|
|
17080
|
+
);
|
|
17081
|
+
}
|
|
17082
|
+
if (!canScheduleTurnTimeoutResume(nextSliceId)) {
|
|
17083
|
+
logWarn(
|
|
17084
|
+
"timeout_resume_slice_limit_reached",
|
|
17085
|
+
{},
|
|
17086
|
+
{
|
|
17087
|
+
"app.ai.conversation_id": payload.conversationId,
|
|
17088
|
+
"app.ai.session_id": payload.sessionId,
|
|
17089
|
+
...typeof nextSliceId === "number" ? { "app.ai.resume_slice_id": nextSliceId } : {}
|
|
17090
|
+
},
|
|
17091
|
+
"Skipped automatic timeout resume because the turn exceeded the slice limit"
|
|
17092
|
+
);
|
|
17093
|
+
throw new Error(
|
|
17094
|
+
"Timed-out turn exceeded the automatic resume slice limit"
|
|
17095
|
+
);
|
|
17096
|
+
}
|
|
17097
|
+
await scheduleTurnTimeoutResume({
|
|
17098
|
+
conversationId: payload.conversationId,
|
|
17099
|
+
sessionId: payload.sessionId,
|
|
17100
|
+
expectedCheckpointVersion: checkpointVersion
|
|
17101
|
+
});
|
|
17102
|
+
}
|
|
17103
|
+
};
|
|
16724
17104
|
}
|
|
16725
17105
|
});
|
|
16726
17106
|
}
|
|
@@ -16745,8 +17125,9 @@ async function resumeTimedOutTurnWithLockRetry(payload) {
|
|
|
16745
17125
|
"app.ai.session_id": payload.sessionId,
|
|
16746
17126
|
"app.ai.resume_lock_retry_count": attempt
|
|
16747
17127
|
},
|
|
16748
|
-
"
|
|
17128
|
+
"Rescheduling timeout resume because another turn still owns the thread lock"
|
|
16749
17129
|
);
|
|
17130
|
+
await scheduleTurnTimeoutResume(payload);
|
|
16750
17131
|
return;
|
|
16751
17132
|
}
|
|
16752
17133
|
logWarn(
|
|
@@ -16760,7 +17141,7 @@ async function resumeTimedOutTurnWithLockRetry(payload) {
|
|
|
16760
17141
|
},
|
|
16761
17142
|
"Timeout resume lock was busy; retrying"
|
|
16762
17143
|
);
|
|
16763
|
-
await
|
|
17144
|
+
await sleep4(delayMs);
|
|
16764
17145
|
}
|
|
16765
17146
|
}
|
|
16766
17147
|
}
|
|
@@ -18260,6 +18641,31 @@ function getCurrentTurnCanvasUrl(args) {
|
|
|
18260
18641
|
function buildCanvasRecoveryReply(canvasUrl) {
|
|
18261
18642
|
return `I created the canvas, but the turn was interrupted before I could finish the thread reply: ${canvasUrl}`;
|
|
18262
18643
|
}
|
|
18644
|
+
async function loadPiMessagesForTurn(args) {
|
|
18645
|
+
const fallback = args.fallback.length > 0 ? [...args.fallback] : void 0;
|
|
18646
|
+
if (!args.conversationId) {
|
|
18647
|
+
return fallback;
|
|
18648
|
+
}
|
|
18649
|
+
if (args.activeTurnId) {
|
|
18650
|
+
const checkpoint2 = await getAgentTurnSessionCheckpoint(
|
|
18651
|
+
args.conversationId,
|
|
18652
|
+
args.activeTurnId
|
|
18653
|
+
);
|
|
18654
|
+
if (checkpoint2?.piMessages.length) {
|
|
18655
|
+
return stripRuntimeTurnContext(
|
|
18656
|
+
trimTrailingAssistantMessages(checkpoint2.piMessages)
|
|
18657
|
+
);
|
|
18658
|
+
}
|
|
18659
|
+
}
|
|
18660
|
+
if (!args.lastSessionId) {
|
|
18661
|
+
return fallback;
|
|
18662
|
+
}
|
|
18663
|
+
const checkpoint = await getAgentTurnSessionCheckpoint(
|
|
18664
|
+
args.conversationId,
|
|
18665
|
+
args.lastSessionId
|
|
18666
|
+
);
|
|
18667
|
+
return checkpoint?.state === "completed" && checkpoint.piMessages.length > 0 ? stripRuntimeTurnContext(checkpoint.piMessages) : fallback;
|
|
18668
|
+
}
|
|
18263
18669
|
function createReplyToThread(deps) {
|
|
18264
18670
|
return async function replyToThread(thread, message, options = {}) {
|
|
18265
18671
|
if (message.author.isMe) {
|
|
@@ -18412,6 +18818,7 @@ function createReplyToThread(deps) {
|
|
|
18412
18818
|
return;
|
|
18413
18819
|
}
|
|
18414
18820
|
}
|
|
18821
|
+
const lastSessionIdForHistory = preparedState.conversation.processing.lastSessionId;
|
|
18415
18822
|
const configReply = await maybeApplyProviderDefaultConfigRequest({
|
|
18416
18823
|
channelConfiguration: preparedState.channelConfiguration,
|
|
18417
18824
|
requesterId: message.author.userId,
|
|
@@ -18487,6 +18894,12 @@ function createReplyToThread(deps) {
|
|
|
18487
18894
|
}
|
|
18488
18895
|
);
|
|
18489
18896
|
const omittedImageAttachmentCount = !isVisionEnabled() && hasPotentialImageAttachment(message.attachments) ? countPotentialImageAttachments(message.attachments) : 0;
|
|
18897
|
+
const piMessages = await loadPiMessagesForTurn({
|
|
18898
|
+
conversationId,
|
|
18899
|
+
activeTurnId,
|
|
18900
|
+
lastSessionId: lastSessionIdForHistory,
|
|
18901
|
+
fallback: preparedState.conversation.piMessages
|
|
18902
|
+
});
|
|
18490
18903
|
const status = createSlackAdapterAssistantStatusSession({
|
|
18491
18904
|
channelId: assistantThreadContext?.channelId,
|
|
18492
18905
|
threadTs: assistantThreadContext?.threadTs,
|
|
@@ -18538,7 +18951,7 @@ function createReplyToThread(deps) {
|
|
|
18538
18951
|
},
|
|
18539
18952
|
conversationContext: preparedState.routingContext ?? preparedState.conversationContext,
|
|
18540
18953
|
artifactState: preparedState.artifacts,
|
|
18541
|
-
piMessages
|
|
18954
|
+
piMessages,
|
|
18542
18955
|
pendingAuth: preparedState.conversation.processing.pendingAuth,
|
|
18543
18956
|
configuration: preparedState.configuration,
|
|
18544
18957
|
channelConfiguration: preparedState.channelConfiguration,
|
|
@@ -18600,30 +19013,6 @@ function createReplyToThread(deps) {
|
|
|
18600
19013
|
context: diagnosticsContext
|
|
18601
19014
|
});
|
|
18602
19015
|
}
|
|
18603
|
-
markConversationMessage(
|
|
18604
|
-
preparedState.conversation,
|
|
18605
|
-
preparedState.userMessageId,
|
|
18606
|
-
{
|
|
18607
|
-
replied: true,
|
|
18608
|
-
skippedReason: void 0
|
|
18609
|
-
}
|
|
18610
|
-
);
|
|
18611
|
-
upsertConversationMessage(preparedState.conversation, {
|
|
18612
|
-
id: generateConversationId("assistant"),
|
|
18613
|
-
role: "assistant",
|
|
18614
|
-
text: normalizeConversationText(reply.text) || "[empty response]",
|
|
18615
|
-
createdAtMs: Date.now(),
|
|
18616
|
-
author: {
|
|
18617
|
-
userName: botConfig.userName,
|
|
18618
|
-
isBot: true
|
|
18619
|
-
},
|
|
18620
|
-
meta: {
|
|
18621
|
-
replied: true
|
|
18622
|
-
}
|
|
18623
|
-
});
|
|
18624
|
-
if (reply.piMessages) {
|
|
18625
|
-
preparedState.conversation.piMessages = reply.piMessages;
|
|
18626
|
-
}
|
|
18627
19016
|
const artifactStatePatch = reply.artifactStatePatch ? { ...reply.artifactStatePatch } : {};
|
|
18628
19017
|
const reactionPerformed = reply.diagnostics.toolCalls.includes(
|
|
18629
19018
|
"slackMessageAddReaction"
|
|
@@ -18696,19 +19085,18 @@ function createReplyToThread(deps) {
|
|
|
18696
19085
|
if (titleUpdateResult) {
|
|
18697
19086
|
artifactStatePatch.assistantTitleSourceMessageId = titleUpdateResult;
|
|
18698
19087
|
}
|
|
18699
|
-
const
|
|
18700
|
-
|
|
18701
|
-
|
|
19088
|
+
const completedState = buildDeliveredTurnStatePatch({
|
|
19089
|
+
artifactStatePatch,
|
|
19090
|
+
artifacts: preparedState.artifacts,
|
|
18702
19091
|
conversation: preparedState.conversation,
|
|
18703
|
-
|
|
18704
|
-
|
|
19092
|
+
reply,
|
|
19093
|
+
sessionId: turnId,
|
|
19094
|
+
userMessageId: preparedState.userMessageId
|
|
18705
19095
|
});
|
|
18706
19096
|
await persistThreadState(thread, {
|
|
18707
|
-
|
|
18708
|
-
conversation: preparedState.conversation,
|
|
18709
|
-
sandboxId: reply.sandboxId,
|
|
18710
|
-
sandboxDependencyProfileHash: reply.sandboxDependencyProfileHash
|
|
19097
|
+
...completedState
|
|
18711
19098
|
});
|
|
19099
|
+
preparedState.conversation = completedState.conversation;
|
|
18712
19100
|
persistedAtLeastOnce = true;
|
|
18713
19101
|
if (shouldEmitDevAgentTrace()) {
|
|
18714
19102
|
logInfo(
|
|
@@ -18826,9 +19214,10 @@ function createReplyToThread(deps) {
|
|
|
18826
19214
|
replied: true
|
|
18827
19215
|
}
|
|
18828
19216
|
});
|
|
18829
|
-
|
|
19217
|
+
markTurnClosed({
|
|
18830
19218
|
conversation: preparedState.conversation,
|
|
18831
19219
|
nowMs: Date.now(),
|
|
19220
|
+
sessionId: turnId,
|
|
18832
19221
|
updateConversationStats
|
|
18833
19222
|
});
|
|
18834
19223
|
await persistThreadState(thread, {
|
|
@@ -18845,12 +19234,30 @@ function createReplyToThread(deps) {
|
|
|
18845
19234
|
markTurnFailed({
|
|
18846
19235
|
conversation: preparedState.conversation,
|
|
18847
19236
|
nowMs: Date.now(),
|
|
19237
|
+
sessionId: turnId,
|
|
18848
19238
|
userMessageId: preparedState.userMessageId,
|
|
18849
19239
|
markConversationMessage: (conversation, messageId, patch) => {
|
|
18850
19240
|
markConversationMessage(conversation, messageId, patch);
|
|
18851
19241
|
},
|
|
18852
19242
|
updateConversationStats
|
|
18853
19243
|
});
|
|
19244
|
+
if (conversationId) {
|
|
19245
|
+
try {
|
|
19246
|
+
await failAgentTurnSessionCheckpoint({
|
|
19247
|
+
conversationId,
|
|
19248
|
+
sessionId: turnId,
|
|
19249
|
+
errorMessage: "Agent turn failed before final reply delivery completed"
|
|
19250
|
+
});
|
|
19251
|
+
} catch (checkpointError) {
|
|
19252
|
+
logException(
|
|
19253
|
+
checkpointError,
|
|
19254
|
+
"agent_turn_failed_checkpoint_persist_failed",
|
|
19255
|
+
turnTraceContext,
|
|
19256
|
+
{},
|
|
19257
|
+
"Failed to mark failed turn checkpoint"
|
|
19258
|
+
);
|
|
19259
|
+
}
|
|
19260
|
+
}
|
|
18854
19261
|
await persistThreadState(thread, {
|
|
18855
19262
|
conversation: preparedState.conversation
|
|
18856
19263
|
});
|
|
@@ -19983,22 +20390,67 @@ async function resolveBuildPluginConfig() {
|
|
|
19983
20390
|
try {
|
|
19984
20391
|
const mod = await import("#junior/config");
|
|
19985
20392
|
return mod.plugins;
|
|
19986
|
-
} catch {
|
|
19987
|
-
|
|
19988
|
-
|
|
19989
|
-
|
|
19990
|
-
|
|
19991
|
-
|
|
19992
|
-
}
|
|
20393
|
+
} catch (error) {
|
|
20394
|
+
if (!isMissingVirtualConfig(error)) {
|
|
20395
|
+
throw error;
|
|
20396
|
+
}
|
|
20397
|
+
const packages = readEnvPluginPackages();
|
|
20398
|
+
if (packages) {
|
|
20399
|
+
return { packages };
|
|
19993
20400
|
}
|
|
19994
20401
|
return void 0;
|
|
19995
20402
|
}
|
|
19996
20403
|
}
|
|
20404
|
+
function isMissingVirtualConfig(error) {
|
|
20405
|
+
if (!(error instanceof Error)) {
|
|
20406
|
+
return false;
|
|
20407
|
+
}
|
|
20408
|
+
const code = error.code;
|
|
20409
|
+
return (code === "ERR_PACKAGE_IMPORT_NOT_DEFINED" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") && error.message.includes("#junior/config");
|
|
20410
|
+
}
|
|
20411
|
+
function readEnvPluginPackages() {
|
|
20412
|
+
const env = process.env.JUNIOR_PLUGIN_PACKAGES;
|
|
20413
|
+
if (!env) {
|
|
20414
|
+
return void 0;
|
|
20415
|
+
}
|
|
20416
|
+
let parsed;
|
|
20417
|
+
try {
|
|
20418
|
+
parsed = JSON.parse(env);
|
|
20419
|
+
} catch (error) {
|
|
20420
|
+
throw new Error("JUNIOR_PLUGIN_PACKAGES must be valid JSON", {
|
|
20421
|
+
cause: error
|
|
20422
|
+
});
|
|
20423
|
+
}
|
|
20424
|
+
if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "string" || !value.trim())) {
|
|
20425
|
+
throw new Error(
|
|
20426
|
+
"JUNIOR_PLUGIN_PACKAGES must be a JSON array of package names"
|
|
20427
|
+
);
|
|
20428
|
+
}
|
|
20429
|
+
return parsed;
|
|
20430
|
+
}
|
|
20431
|
+
function hasConfiguredPluginCatalog(config) {
|
|
20432
|
+
if (!config) {
|
|
20433
|
+
return false;
|
|
20434
|
+
}
|
|
20435
|
+
return Boolean(
|
|
20436
|
+
config.packages?.length || Object.keys(config.manifests ?? {}).length
|
|
20437
|
+
);
|
|
20438
|
+
}
|
|
19997
20439
|
async function createApp(options) {
|
|
19998
20440
|
const pluginConfig = options?.plugins ?? await resolveBuildPluginConfig();
|
|
19999
|
-
|
|
20000
|
-
setPluginConfig(pluginConfig);
|
|
20001
|
-
|
|
20441
|
+
const shouldValidatePluginCatalog = hasConfiguredPluginCatalog(pluginConfig) || Boolean(Object.keys(options?.configDefaults ?? {}).length);
|
|
20442
|
+
const previousPluginConfig = setPluginConfig(pluginConfig);
|
|
20443
|
+
const previousConfigDefaults = getConfigDefaults();
|
|
20444
|
+
try {
|
|
20445
|
+
setConfigDefaults(options?.configDefaults);
|
|
20446
|
+
if (shouldValidatePluginCatalog) {
|
|
20447
|
+
getPluginCatalogSignature();
|
|
20448
|
+
}
|
|
20449
|
+
} catch (error) {
|
|
20450
|
+
setPluginConfig(previousPluginConfig);
|
|
20451
|
+
setConfigDefaults(previousConfigDefaults);
|
|
20452
|
+
throw error;
|
|
20453
|
+
}
|
|
20002
20454
|
const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
|
|
20003
20455
|
const app = new Hono();
|
|
20004
20456
|
app.onError((err, c) => {
|